Dealing with ghost spaces

Change-Id: I9cf133e915658f17d00f279ee1fa2662effa2021
Reviewed-on: https://skia-review.googlesource.com/c/skia/+/231646
Commit-Queue: Julia Lavrova <jlavrova@google.com>
Reviewed-by: Ben Wagner <bungeman@google.com>
Reviewed-by: Julia Lavrova <jlavrova@google.com>
This commit is contained in:
Julia Lavrova 2019-08-01 16:02:17 -04:00 committed by Skia Commit-Bot
parent 5ed96f7213
commit db9f669eb1
16 changed files with 1590 additions and 843 deletions

View File

@ -27,11 +27,14 @@ struct StrutStyle {
void setLeading(SkScalar Leading) { fLeading = Leading; }
SkScalar getLeading() const { return fLeading; }
bool getStrutEnabled() const { return fStrutEnabled; }
void setStrutEnabled(bool v) { fStrutEnabled = v; }
bool getStrutEnabled() const { return fEnabled; }
void setStrutEnabled(bool v) { fEnabled = v; }
bool getForceStrutHeight() const { return fForceStrutHeight; }
void setForceStrutHeight(bool v) { fForceStrutHeight = v; }
bool getForceStrutHeight() const { return fForceHeight; }
void setForceStrutHeight(bool v) { fForceHeight = v; }
bool getHeightOverride() const { return fHeightOverride; }
void setHeightOverride(bool v) { fHeightOverride = v; }
private:
@ -40,8 +43,9 @@ private:
SkScalar fFontSize;
SkScalar fHeight;
SkScalar fLeading;
bool fForceStrutHeight;
bool fStrutEnabled;
bool fForceHeight;
bool fEnabled;
bool fHeightOverride;
};
struct ParagraphStyle {

View File

@ -120,7 +120,10 @@ public:
}
void setHeight(SkScalar height) { fHeight = height; }
SkScalar getHeight() const { return fHeight; }
SkScalar getHeight() const { return fHeightOverride ? fHeight : 0; }
void setHeightOverride(bool heightOverride) { fHeightOverride = heightOverride; }
bool getHeightOverride() const { return fHeightOverride; }
void setLetterSpacing(SkScalar letterSpacing) { fLetterSpacing = letterSpacing; }
SkScalar getLetterSpacing() const { return fLetterSpacing; }
@ -138,15 +141,7 @@ public:
TextBaseline getTextBaseline() const { return fTextBaseline; }
void setTextBaseline(TextBaseline baseline) { fTextBaseline = baseline; }
// TODO: Not to use SkFontMetrics class (it has different purpose and meaning)
void getFontMetrics(SkFontMetrics* metrics) const {
SkFont font(fTypeface, fFontSize);
font.getMetrics(metrics);
metrics->fAscent =
(metrics->fAscent - metrics->fLeading / 2) * (fHeight == 0 ? 1 : fHeight);
metrics->fDescent =
(metrics->fDescent + metrics->fLeading / 2) * (fHeight == 0 ? 1 : fHeight);
}
void getFontMetrics(SkFontMetrics* metrics) const;
private:
Decoration fDecoration;
@ -156,6 +151,7 @@ private:
std::vector<SkString> fFontFamilies;
SkScalar fFontSize;
SkScalar fHeight;
bool fHeightOverride;
SkString fLocale;
SkScalar fLetterSpacing;
SkScalar fWordSpacing;

View File

@ -205,9 +205,7 @@ void FontResolver::addResolvedWhitespacesToMapping() {
fUnresolved -= resolvedWhitespaces;
}
FontDescr FontResolver::makeFont(sk_sp<SkTypeface> typeface,
SkScalar size,
SkScalar height) {
FontDescr FontResolver::makeFont(sk_sp<SkTypeface> typeface, SkScalar size, SkScalar height) {
SkFont font(typeface, size);
font.setEdging(SkFont::Edging::kAntiAlias);
font.setHinting(SkFontHinting::kSlight);

View File

@ -19,28 +19,29 @@ namespace textlayout {
class FontIterator final : public SkShaper::FontRunIterator {
public:
FontIterator(SkSpan<const char> utf8, FontResolver* fontResolver)
: fText(utf8), fCurrentChar(utf8.begin()), fFontResolver(fontResolver) { }
: fText(utf8), fCurrentChar(utf8.begin()), fFontResolver(fontResolver) {
}
void consume() override {
SkASSERT(fCurrentChar < fText.end());
SkString locale;
auto found = fFontResolver->findNext(fCurrentChar, &fFont, &fLineHeight);
SkASSERT(found);
// Move until we find the first character that cannot be resolved with the current font
while (++fCurrentChar != fText.end()) {
SkFont font;
SkScalar height;
SkString locale;
found = fFontResolver->findNext(fCurrentChar, &font, &height);
if (found) {
if (fFont == font && fLineHeight == height) {
continue;
}
SkASSERT(fCurrentChar < fText.end());
auto found = fFontResolver->findNext(fCurrentChar, &fFont, &fLineHeight);
SkASSERT(found);
if (!found) {
fCurrentChar = fText.end();
return;
}
// Move until we find the first character that cannot be resolved with the current font
while (++fCurrentChar != fText.end()) {
SkFont font;
SkScalar height;
if (fFontResolver->findNext(fCurrentChar, &font, &height)) {
if (fFont == font && fLineHeight == height) {
continue;
}
break;
}
}
}
}
}
}
size_t endOfCurrentRun() const override { return fCurrentChar - fText.begin(); }
bool atEnd() const override { return fCurrentChar == fText.end(); }

View File

@ -3,6 +3,7 @@
#include <unicode/brkiter.h>
#include <unicode/ubidi.h>
#include <unicode/unistr.h>
#include <unicode/urename.h>
#include "include/core/SkBlurTypes.h"
#include "include/core/SkCanvas.h"
#include "include/core/SkFontMgr.h"
@ -26,7 +27,7 @@ public:
bool initialize(SkSpan<const char> text, UBreakIteratorType type) {
UErrorCode status = U_ZERO_ERROR;
fIterator = nullptr;
fSize = text.size();
UText utf8UText = UTEXT_INITIALIZER;
utext_openUTF8(&utf8UText, text.begin(), text.size(), &status);
@ -36,13 +37,13 @@ public:
SkDebugf("Could not create utf8UText: %s", u_errorName(status));
return false;
}
fIterator = ubrk_open(type, "en", nullptr, 0, &status);
fIterator.reset(ubrk_open(type, "en", nullptr, 0, &status));
if (U_FAILURE(status)) {
SkDebugf("Could not create line break iterator: %s", u_errorName(status));
SK_ABORT("");
}
ubrk_setUText(fIterator, &utf8UText, &status);
ubrk_setUText(fIterator.get(), &utf8UText, &status);
if (U_FAILURE(status)) {
SkDebugf("Could not setText on break iterator: %s", u_errorName(status));
return false;
@ -53,24 +54,22 @@ public:
}
size_t first() {
fPos = ubrk_first(fIterator);
fPos = ubrk_first(fIterator.get());
return eof() ? fSize : fPos;
}
size_t next() {
fPos = ubrk_next(fIterator);
fPos = ubrk_next(fIterator.get());
return eof() ? fSize : fPos;
}
int32_t status() { return ubrk_getRuleStatus(fIterator); }
int32_t status() { return ubrk_getRuleStatus(fIterator.get()); }
bool eof() { return fPos == icu::BreakIterator::DONE; }
~TextBreaker() = default;
private:
std::unique_ptr<UText, SkFunctionWrapper<UText*, UText, utext_close>> fAutoClose;
UBreakIterator* fIterator;
std::unique_ptr<UBreakIterator, SkFunctionWrapper<void, UBreakIterator, ubrk_close>> fIterator;
int32_t fPos;
size_t fSize;
};
@ -80,6 +79,7 @@ namespace skia {
namespace textlayout {
TextRange operator*(const TextRange& a, const TextRange& b) {
if (a.start == b.start && a.end == b.end) return a;
auto begin = SkTMax(a.start, b.start);
auto end = SkTMin(a.end, b.end);
return end > begin ? TextRange(begin, end) : EMPTY_TEXT;
@ -95,6 +95,7 @@ ParagraphImpl::ParagraphImpl(const SkString& text,
, fTextSpan(fText.c_str(), fText.size())
, fState(kUnknown)
, fPicture(nullptr)
, fStrutMetrics(false)
, fOldWidth(0)
, fOldHeight(0) {
// TODO: extractStyles();
@ -108,6 +109,7 @@ ParagraphImpl::ParagraphImpl(const std::u16string& utf16text,
, fTextStyles(std::move(blocks))
, fState(kUnknown)
, fPicture(nullptr)
, fStrutMetrics(false)
, fOldWidth(0)
, fOldHeight(0) {
icu::UnicodeString unicode((UChar*)utf16text.data(), SkToS32(utf16text.size()));
@ -139,7 +141,7 @@ void ParagraphImpl::layout(SkScalar width) {
FontIterator font(SkMakeSpan(" "), &fFontResolver);
// Get the font metrics
font.consume();
LineMetrics lineMetrics(font.currentFont());
LineMetrics lineMetrics(font.currentFont(), paragraphStyle().getStrutStyle().getForceStrutHeight());
// Set the important values that are not zero
fHeight = lineMetrics.height();
fAlphabeticBaseline = lineMetrics.alphabeticBaseline();
@ -263,30 +265,45 @@ void ParagraphImpl::markLineBreaks() {
}
}
// Walk through all the clusters in the direction of input text
// Walk through all the clusters in the direction of shaped text
Block* currentStyle = this->fTextStyles.begin();
SkScalar shift = 0;
for (auto& cluster : fClusters) {
auto run = cluster.run();
for (auto& run : fRuns) {
// Shift the cluster
run->shift(&cluster, shift);
bool soFarWhitespacesOnly = true;
for (size_t index = 0; index != run.clusterRange().width(); ++index) {
auto correctIndex = run.leftToRight()
? index + run.clusterRange().start
: run.clusterRange().end - index - 1;
const auto cluster = &this->cluster(correctIndex);
// Synchronize styles (one cluster can be covered by few styles)
while (!cluster.startsIn(currentStyle->fRange)) {
currentStyle++;
SkASSERT(currentStyle != this->fTextStyles.end());
}
// Shift the cluster (shift collected from the previous clusters)
run.shift(cluster, shift);
// Take spacing styles in account
if (currentStyle->fStyle.getWordSpacing() != 0 &&
fParagraphStyle.getTextAlign() != TextAlign::kJustify) {
if (cluster.isWhitespaces() && cluster.isSoftBreak()) {
shift += run->addSpacesAtTheEnd(currentStyle->fStyle.getWordSpacing(), &cluster);
// Synchronize styles (one cluster can be covered by few styles)
while (!cluster->startsIn(currentStyle->fRange)) {
currentStyle++;
SkASSERT(currentStyle != this->fTextStyles.end());
}
// Process word spacing
if (currentStyle->fStyle.getWordSpacing() != 0/* &&
fParagraphStyle.getTextAlign() != TextAlign::kJustify*/) {
if (cluster->isWhitespaces() && cluster->isSoftBreak()) {
if (!soFarWhitespacesOnly) {
shift += run.addSpacesAtTheEnd(currentStyle->fStyle.getWordSpacing(), cluster);
}
}
}
// Process letter spacing
if (currentStyle->fStyle.getLetterSpacing() != 0) {
shift += run.addSpacesEvenly(currentStyle->fStyle.getLetterSpacing(), cluster);
}
if (soFarWhitespacesOnly && !cluster->isWhitespaces()) {
soFarWhitespacesOnly = false;
}
}
if (currentStyle->fStyle.getLetterSpacing() != 0) {
shift += run->addSpacesEvenly(currentStyle->fStyle.getLetterSpacing(), &cluster);
}
}
@ -385,6 +402,8 @@ void ParagraphImpl::breakShapedTextIntoLines(SkScalar maxWidth) {
[&](TextRange text,
TextRange textWithSpaces,
ClusterRange clusters,
ClusterRange clustersWithGhosts,
SkScalar widthWithSpaces,
size_t startPos,
size_t endPos,
SkVector offset,
@ -393,7 +412,7 @@ void ParagraphImpl::breakShapedTextIntoLines(SkScalar maxWidth) {
bool addEllipsis) {
// Add the line
// TODO: Take in account clipped edges
auto& line = this->addLine(offset, advance, text, textWithSpaces, clusters, metrics);
auto& line = this->addLine(offset, advance, text, textWithSpaces, clusters, clustersWithGhosts, widthWithSpaces, metrics);
if (addEllipsis) {
line.createEllipsis(maxWidth, fParagraphStyle.getEllipsis(), true);
}
@ -409,7 +428,10 @@ void ParagraphImpl::breakShapedTextIntoLines(SkScalar maxWidth) {
void ParagraphImpl::formatLines(SkScalar maxWidth) {
auto effectiveAlign = fParagraphStyle.effective_align();
for (auto& line : fLines) {
line.format(effectiveAlign, maxWidth, &line != &fLines.back());
if (&line == &fLines.back() && effectiveAlign == TextAlign::kJustify) {
effectiveAlign = line.assumedTextAlign();
}
line.format(effectiveAlign, maxWidth);
}
}
@ -426,7 +448,7 @@ void ParagraphImpl::paintLinesIntoPicture() {
void ParagraphImpl::resolveStrut() {
auto strutStyle = this->paragraphStyle().getStrutStyle();
if (!strutStyle.getStrutEnabled()) {
if (!strutStyle.getStrutEnabled() || strutStyle.getFontSize() < 0) {
return;
}
@ -438,18 +460,26 @@ void ParagraphImpl::resolveStrut() {
}
}
if (typeface.get() == nullptr) {
typeface = SkTypeface::MakeDefault();
return;
}
SkFont font(typeface, strutStyle.getFontSize());
SkFontMetrics metrics;
font.getMetrics(&metrics);
fStrutMetrics = LineMetrics(metrics.fAscent * strutStyle.getHeight(),
metrics.fDescent * strutStyle.getHeight(),
strutStyle.getLeading() < 0
? metrics.fLeading
: strutStyle.getLeading() * strutStyle.getFontSize());
if (strutStyle.getHeightOverride()) {
auto strutHeight = metrics.fDescent - metrics.fAscent + metrics.fLeading;
auto strutMultiplier = strutStyle.getHeight() * strutStyle.getFontSize();
fStrutMetrics = LineMetrics(
metrics.fAscent / strutHeight * strutMultiplier,
metrics.fDescent / strutHeight * strutMultiplier,
strutStyle.getLeading() < 0 ? 0 : strutStyle.getLeading() * strutStyle.getFontSize());
} else {
fStrutMetrics = LineMetrics(
metrics.fAscent,
metrics.fDescent,
strutStyle.getLeading() < 0 ? 0 : strutStyle.getLeading() * strutStyle.getFontSize());
}
}
BlockRange ParagraphImpl::findAllBlocks(TextRange textRange) {
@ -477,11 +507,13 @@ TextLine& ParagraphImpl::addLine(SkVector offset,
TextRange text,
TextRange textWithSpaces,
ClusterRange clusters,
ClusterRange clustersWithGhosts,
SkScalar widthWithSpaces,
LineMetrics sizes) {
// Define a list of styles that covers the line
auto blocks = findAllBlocks(text);
return fLines.emplace_back(this, offset, advance, blocks, text, textWithSpaces, clusters, sizes);
return fLines.emplace_back(this, offset, advance, blocks, text, textWithSpaces, clusters, clustersWithGhosts, widthWithSpaces, sizes);
}
// Returns a vector of bounding boxes that enclose all text between
@ -495,21 +527,26 @@ std::vector<TextBox> ParagraphImpl::getRectsForRange(unsigned start,
return results;
}
// Calculate the utf8 substring
// Make sure the edges are set on the glyph edges
TextRange text;
const char* first = fTextSpan.begin();
for (unsigned i = 0; i < start; ++i) {
size_t startPos = 0;
while (startPos < start) {
++startPos;
utf8_next(&first, fTextSpan.end());
}
const char* last = first;
for (unsigned i = start; i < end; ++i) {
utf8_next(&last, fTextSpan.end());
text.start = first - fTextSpan.begin();
size_t endPos = startPos;
while (endPos < end) {
++endPos;
utf8_next(&first, fTextSpan.end());
}
TextRange text(first - fTextSpan.begin(), last - fTextSpan.begin());
text.end = first - fTextSpan.begin();
for (auto& line : fLines) {
auto lineText = line.textWithSpaces();
auto intersect = lineText * text;
if (intersect.empty() && (!lineText.empty() || lineText.start != text.start)) {
if (intersect.empty() && lineText.start != text.start) {
continue;
}
@ -520,60 +557,85 @@ std::vector<TextBox> ParagraphImpl::getRectsForRange(unsigned start,
before, 0, true,
[](Run*, size_t, size_t, SkRect, SkScalar, bool) { return true; });
}
auto firstBox = results.size();
line.iterateThroughRuns(intersect,
runOffset,
true,
[&results, &line](Run* run, size_t pos, size_t size, SkRect clip,
SkScalar shift, bool clippingNeeded) {
clip.offset(line.offset());
results.emplace_back(clip, run->leftToRight()
? TextDirection::kLtr
: TextDirection::kRtl);
return true;
});
auto firstBoxOnTheLine = results.size();
auto paragraphTextDirection = paragraphStyle().getTextDirection();
line.iterateThroughRuns(
intersect,
runOffset,
paragraphStyle().getTextAlign() != TextAlign::kJustify || &line == &fLines.back(),
[&results, &line, rectHeightStyle, this, paragraphTextDirection](Run* run, size_t pos, size_t size, SkRect clip,
SkScalar shift, bool clippingNeeded) {
SkRect trailingSpaces = SkRect::MakeEmpty();
auto spaces = clip.left() >= line.width() ? 0 : clip.right() - line.width();
if (spaces > 0) {
// There are trailing spaces; let's make a special box for them
if (paragraphTextDirection == TextDirection::kRtl) {
trailingSpaces.fLeft = clip.fLeft - spaces;
trailingSpaces.fRight = clip.fLeft;
clip.fRight = line.width();
} else if (clip.fRight < line.widthWithSpaces()){
trailingSpaces.fLeft = line.width();
trailingSpaces.fRight = clip.fRight;
clip.fRight = line.width();
}
trailingSpaces.fTop = clip.fTop;
trailingSpaces.fBottom = clip.fBottom;
trailingSpaces.offset(line.offset());
}
clip.offset(line.offset());
if (rectHeightStyle != RectHeightStyle::kTight) {
// Align all the rectangles
for (auto i = firstBox; i < results.size(); ++i) {
auto& rect = results[i].rect;
if (rectHeightStyle == RectHeightStyle::kMax) {
rect.fTop = line.offset().fY + line.roundingDelta();
rect.fBottom = line.offset().fY + line.height();
// Mimicking TxtLib: clip.fTop = line.offset().fY + line.roundingDelta();
clip.fBottom = line.offset().fY + line.height();
} else if (rectHeightStyle == RectHeightStyle::kIncludeLineSpacingTop) {
rect.fTop = line.offset().fY;
if (&line != &fLines.front()) {
clip.fTop -= line.sizes().runTop(run);
}
clip.fBottom -= line.sizes().runTop(run);
} else if (rectHeightStyle == RectHeightStyle::kIncludeLineSpacingMiddle) {
rect.fTop -= (rect.fTop - line.offset().fY) / 2;
rect.fBottom += (line.offset().fY + line.height() - rect.fBottom) / 2;
if (&line != &fLines.front()) {
clip.fTop -= line.sizes().runTop(run) / 2;
}
if (&line == &fLines.back()) {
clip.fBottom -= line.sizes().runTop(run);
} else {
clip.fBottom -= line.sizes().runTop(run) / 2;
}
} else if (rectHeightStyle == RectHeightStyle::kIncludeLineSpacingBottom) {
rect.fBottom = line.offset().fY + line.height();
if (&line == &fLines.back()) {
clip.fBottom -= line.sizes().runTop(run);
}
}
}
} else {
// Just leave the boxes the way they are
}
results.emplace_back(
clip, run->leftToRight() ? TextDirection::kLtr : TextDirection::kRtl);
if (trailingSpaces.width() > 0) {
results.emplace_back(trailingSpaces, paragraphTextDirection);
}
return true;
});
if (rectWidthStyle == RectWidthStyle::kMax) {
for (auto& i = firstBox; i < results.size(); ++i) {
auto clip = results[i].rect;
auto dir = results[i].direction;
if (clip.fLeft > line.offset().fX) {
SkRect left = SkRect::MakeXYWH(0, clip.fTop, clip.fLeft - line.offset().fX,
clip.fBottom);
results.insert(results.begin() + i, {left, dir});
++i;
}
if (clip.fRight < line.offset().fX + line.width()) {
SkRect right = SkRect::MakeXYWH(clip.fRight - line.offset().fX,
clip.fTop,
line.width() - (clip.fRight - line.offset().fX),
clip.fBottom);
results.insert(results.begin() + i, {right, dir});
++i;
}
// Align the very left/right box horizontally
auto lineStart = line.offset().fX;
auto lineEnd = line.offset().fX + line.width();
auto left = results.front();
auto right = results.back();
if (left.rect.fLeft > lineStart && left.direction == TextDirection::kRtl) {
left.rect.fRight = left.rect.fLeft;
left.rect.fLeft = 0;
results.insert(results.begin() + firstBoxOnTheLine + 1, left);
}
if (right.direction == TextDirection::kLtr &&
right.rect.fRight >= lineEnd && right.rect.fRight < this->fMaxWidthWithTrailingSpaces) {
right.rect.fLeft = right.rect.fRight;
right.rect.fRight = this->fMaxWidthWithTrailingSpaces;
results.emplace_back(right);
}
}
}
@ -601,7 +663,7 @@ PositionWithAffinity ParagraphImpl::getGlyphPositionAtCoordinate(SkScalar dx, Sk
if (dx >= clip.fRight) {
// We have to keep looking but just in case keep the last one as the closes
// so far
result = {SkToS32(run->fClusterIndexes[pos + size]), kUpstream};
result = {SkToS32(run->fClusterIndexes[pos + size - 1]) + 1, kUpstream};
return true;
}
@ -614,9 +676,7 @@ PositionWithAffinity ParagraphImpl::getGlyphPositionAtCoordinate(SkScalar dx, Sk
found = i;
}
if (found == pos) {
result = {SkToS32(run->fClusterIndexes[found]), kDownstream};
} else if (found == pos + size - 1) {
if (found == pos + size - 1) {
result = {SkToS32(run->fClusterIndexes[found]), kUpstream};
} else {
auto center = (run->positionX(found + 1) + run->positionX(found)) / 2;

View File

@ -67,7 +67,8 @@ public:
size_t lineNumber() override { return fLines.size(); }
TextLine& addLine(SkVector offset, SkVector advance, TextRange text, TextRange textWithSpaces,
ClusterRange clusters, LineMetrics sizes);
ClusterRange clusters, ClusterRange clustersWithGhosts, SkScalar AddLineToParagraph,
LineMetrics sizes);
SkSpan<const char> text() const { return fTextSpan; }
InternalState state() const { return fState; }
@ -77,7 +78,7 @@ public:
return SkSpan<Block>(fTextStyles.data(), fTextStyles.size());
}
SkSpan<TextLine> lines() { return SkSpan<TextLine>(fLines.data(), fLines.size()); }
ParagraphStyle paragraphStyle() const { return fParagraphStyle; }
const ParagraphStyle& paragraphStyle() const { return fParagraphStyle; }
SkSpan<Cluster> clusters() { return SkSpan<Cluster>(fClusters.begin(), fClusters.size()); }
sk_sp<FontCollection> fontCollection() const { return fFontCollection; }
void formatLines(SkScalar maxWidth);
@ -101,6 +102,9 @@ public:
bool strutForceHeight() const {
return paragraphStyle().getStrutStyle().getForceStrutHeight();
}
bool strutHeightOverride() const {
return paragraphStyle().getStrutStyle().getHeightOverride();
}
LineMetrics strutMetrics() const { return fStrutMetrics; }
Measurement measurement() {
@ -149,6 +153,8 @@ private:
friend class ParagraphCacheValue;
friend class ParagraphCache;
friend class TextWrapper;
BlockRange findAllBlocks(TextRange textRange);
void extractStyles();
@ -177,6 +183,7 @@ private:
SkScalar fOldWidth;
SkScalar fOldHeight;
SkScalar fMaxWidthWithTrailingSpaces;
};
} // namespace textlayout
} // namespace skia

View File

@ -11,8 +11,8 @@ StrutStyle::StrutStyle() {
fFontSize = 14;
fHeight = 1;
fLeading = -1;
fForceStrutHeight = false;
fStrutEnabled = false;
fForceHeight = false;
fEnabled = false;
}
ParagraphStyle::ParagraphStyle() {

View File

@ -4,6 +4,15 @@
#include "include/core/SkFontMetrics.h"
#include "modules/skparagraph/src/ParagraphImpl.h"
#include <algorithm>
#include "src/utils/SkUTF.h"
namespace {
SkUnichar utf8_next(const char** ptr, const char* end) {
SkUnichar val = SkUTF::NextUTF8(ptr, end);
return val < 0 ? 0xFFFD : val;
}
}
namespace skia {
namespace textlayout {
@ -198,12 +207,14 @@ void Run::shift(const Cluster* cluster, SkScalar offset) {
}
void Cluster::setIsWhiteSpaces() {
auto text = fMaster->text();
auto pos = fTextRange.end;
while (--pos >= fTextRange.start) {
auto ch = text[pos];
if (!u_isspace(ch) && u_charType(ch) != U_CONTROL_CHAR &&
u_charType(ch) != U_NON_SPACING_MARK) {
fWhiteSpaces = false;
auto span = fMaster->text(fTextRange);
const char* ch = span.begin();
while (ch < span.end()) {
auto unichar = utf8_next(&ch, span.end());
if (!u_isWhitespace(unichar)) {
return;
}
}

View File

@ -65,6 +65,30 @@ public:
SkScalar ascent() const { return fFontMetrics.fAscent; }
SkScalar descent() const { return fFontMetrics.fDescent; }
SkScalar leading() const { return fFontMetrics.fLeading; }
SkScalar correctAscent() const {
if (fHeightMultiplier == 0 || fHeightMultiplier == 1) {
return fFontMetrics.fAscent;
}
return fFontMetrics.fAscent * fHeightMultiplier * fFont.getSize() /
(fFontMetrics.fDescent - fFontMetrics.fAscent + fFontMetrics.fLeading);
}
SkScalar correctDescent() const {
if (fHeightMultiplier == 0 || fHeightMultiplier == 1) {
return fFontMetrics.fDescent;
}
return fFontMetrics.fDescent * fHeightMultiplier * fFont.getSize() /
(fFontMetrics.fDescent - fFontMetrics.fAscent + fFontMetrics.fLeading);
}
SkScalar correctLeading() const {
if (fHeightMultiplier == 0 || fHeightMultiplier == 1) {
return fFontMetrics.fAscent;
}
return fFontMetrics.fLeading * fHeightMultiplier * fFont.getSize() /
(fFontMetrics.fDescent - fFontMetrics.fAscent + fFontMetrics.fLeading);
}
const SkFont& font() const { return fFont; }
bool leftToRight() const { return fBidiLevel % 2 == 0; }
size_t index() const { return fIndex; }
@ -84,7 +108,12 @@ public:
SkScalar addSpacesEvenly(SkScalar space, Cluster* cluster);
void shift(const Cluster* cluster, SkScalar offset);
SkScalar calculateHeight() const { return fFontMetrics.fDescent - fFontMetrics.fAscent; }
SkScalar calculateHeight() const {
if (fHeightMultiplier == 0 || fHeightMultiplier == 1) {
return fFontMetrics.fDescent - fFontMetrics.fAscent;
}
return fHeightMultiplier * fFont.getSize();
}
SkScalar calculateWidth(size_t start, size_t end, bool clip) const;
void copyTo(SkTextBlobBuilder& builder, size_t pos, size_t size, SkVector offset) const;
@ -98,15 +127,16 @@ public:
void iterateThroughClustersInTextOrder(const ClusterVisitor& visitor);
std::tuple<bool, ClusterIndex, ClusterIndex> findLimitingClusters(TextRange);
SkSpan<const SkGlyphID> glyphs() {
SkSpan<const SkGlyphID> glyphs() const {
return SkSpan<const SkGlyphID>(fGlyphs.begin(), fGlyphs.size());
}
SkSpan<const SkPoint> positions() {
SkSpan<const SkPoint> positions() const {
return SkSpan<const SkPoint>(fPositions.begin(), fPositions.size());
}
SkSpan<const uint32_t> clusterIndexes() {
SkSpan<const uint32_t> clusterIndexes() const {
return SkSpan<const uint32_t>(fClusterIndexes.begin(), fClusterIndexes.size());
}
SkSpan<const SkScalar> offsets() const { return SkSpan<const SkScalar>(fOffsets.begin(), fOffsets.size()); }
private:
friend class ParagraphImpl;
@ -234,15 +264,17 @@ private:
class LineMetrics {
public:
LineMetrics() { clean(); }
LineMetrics(SkScalar a, SkScalar d, SkScalar l) {
LineMetrics() { clean(); }
LineMetrics(bool forceStrut) : fForceStrut(forceStrut) { clean(); }
LineMetrics(SkScalar a, SkScalar d, SkScalar l) : fForceStrut(false) {
fAscent = a;
fDescent = d;
fLeading = l;
}
LineMetrics(const SkFont& font) {
LineMetrics(const SkFont& font, bool forceStrut) : fForceStrut(forceStrut) {
SkFontMetrics metrics;
font.getMetrics(&metrics);
fAscent = metrics.fAscent;
@ -251,9 +283,21 @@ public:
}
void add(Run* run) {
fAscent = SkTMin(fAscent, run->ascent() * run->lineHeight());
fDescent = SkTMax(fDescent, run->descent() * run->lineHeight());
fLeading = SkTMax(fLeading, run->leading() * run->lineHeight());
if (fForceStrut) {
return;
}
if (run->lineHeight() == 0 || run->lineHeight() == 1) {
fAscent = SkTMin(fAscent, run->ascent());
fDescent = SkTMax(fDescent, run->descent());
fLeading = SkTMax(fLeading, run->leading());
} else {
fAscent = SkTMin(fAscent, run->correctAscent());
fDescent = SkTMax(fDescent, run->correctDescent());
fLeading = SkTMax(fLeading, run->correctLeading());
}
}
void add(LineMetrics other) {
@ -269,16 +313,10 @@ public:
SkScalar delta() const { return height() - ideographicBaseline(); }
void updateLineMetrics(LineMetrics& metrics, bool forceHeight) {
if (forceHeight) {
metrics.fAscent = fAscent;
metrics.fDescent = fDescent;
metrics.fLeading = fLeading;
} else {
metrics.fAscent = SkTMin(metrics.fAscent, fAscent);
metrics.fDescent = SkTMax(metrics.fDescent, fDescent);
metrics.fLeading = SkTMax(metrics.fLeading, fLeading);
}
void updateLineMetrics(LineMetrics& metrics) {
metrics.fAscent = SkTMin(metrics.fAscent, fAscent);
metrics.fDescent = SkTMax(metrics.fDescent, fDescent);
metrics.fLeading = SkTMax(metrics.fLeading, fLeading);
}
SkScalar runTop(Run* run) const { return fLeading / 2 - fAscent + run->ascent() + delta(); }
@ -294,6 +332,7 @@ private:
SkScalar fAscent;
SkScalar fDescent;
SkScalar fLeading;
bool fForceStrut;
};
} // namespace textlayout
} // namespace skia

View File

@ -11,7 +11,7 @@
namespace skia {
namespace textlayout {
// TODO: deal with all the intersection functionality
int32_t intersectedSize(TextRange a, TextRange b) {
if (a.empty() || b.empty()) {
return -1;
@ -22,9 +22,10 @@ int32_t intersectedSize(TextRange a, TextRange b) {
}
TextRange intersected(const TextRange& a, const TextRange& b) {
if (a.start == b.start && a.end == b.end) return a;
auto begin = SkTMax(a.start, b.start);
auto end = SkTMin(a.end, b.end);
return end > begin ? TextRange(begin, end) : EMPTY_TEXT;
return end >= begin ? TextRange(begin, end) : EMPTY_TEXT;
}
SkTHashMap<SkFont, Run> TextLine::fEllipsisCache;
@ -36,25 +37,29 @@ TextLine::TextLine(ParagraphImpl* master,
TextRange text,
TextRange textWithSpaces,
ClusterRange clusters,
ClusterRange clustersWithGhosts,
SkScalar widthWithSpaces,
LineMetrics sizes)
: fMaster(master)
, fBlockRange(blocks)
, fTextRange(text)
, fTextWithWhitespacesRange(textWithSpaces)
, fClusterRange(clusters)
, fGhostClusterRange(clustersWithGhosts)
, fLogical()
, fAdvance(advance)
, fOffset(offset)
, fShift(0.0)
, fWidthWithSpaces(widthWithSpaces)
, fEllipsis(nullptr)
, fSizes(sizes)
, fHasBackground(false)
, fHasShadows(false)
, fHasDecorations(false) {
// Reorder visual runs
auto start = master->clusters().begin() + fClusterRange.start;
auto end = master->clusters().begin() + fClusterRange.end - 1;
size_t numRuns = end->runIndex() - start->runIndex() + 1;
auto& start = master->cluster(fGhostClusterRange.start);
auto& end = master->cluster(fGhostClusterRange.end - 1);
size_t numRuns = end.runIndex() - start.runIndex() + 1;
for (BlockIndex index = fBlockRange.start; index < fBlockRange.end; ++index) {
auto b = fMaster->styles().begin() + index;
@ -71,7 +76,7 @@ TextLine::TextLine(ParagraphImpl* master,
// Get the logical order
std::vector<UBiDiLevel> runLevels;
for (auto runIndex = start->runIndex(); runIndex <= end->runIndex(); ++runIndex) {
for (auto runIndex = start.runIndex(); runIndex <= end.runIndex(); ++runIndex) {
auto& run = fMaster->run(runIndex);
runLevels.emplace_back(run.fBidiLevel);
}
@ -79,7 +84,7 @@ TextLine::TextLine(ParagraphImpl* master,
std::vector<int32_t> logicalOrder(numRuns);
ubidi_reorderVisual(runLevels.data(), SkToU32(numRuns), logicalOrder.data());
auto firstRunIndex = start->runIndex();
auto firstRunIndex = start.runIndex();
for (auto index : logicalOrder) {
fLogical.push_back(firstRunIndex + index);
}
@ -126,13 +131,13 @@ void TextLine::paint(SkCanvas* textCanvas) {
textCanvas->restore();
}
void TextLine::format(TextAlign effectiveAlign, SkScalar maxWidth, bool notLastLine) {
void TextLine::format(TextAlign effectiveAlign, SkScalar maxWidth) {
SkScalar delta = maxWidth - this->width();
if (delta <= 0) {
return;
}
if (effectiveAlign == TextAlign::kJustify && notLastLine) {
if (effectiveAlign == TextAlign::kJustify) {
this->justify(maxWidth);
} else if (effectiveAlign == TextAlign::kRight) {
fShift = delta;
@ -141,6 +146,18 @@ void TextLine::format(TextAlign effectiveAlign, SkScalar maxWidth, bool notLastL
}
}
TextAlign TextLine::assumedTextAlign() const {
if (this->fMaster->paragraphStyle().getTextAlign() != TextAlign::kJustify) {
return this->fMaster->paragraphStyle().getTextAlign();
}
if (fClusterRange.empty()) {
return TextAlign::kLeft;
} else {
auto run = this->fMaster->cluster(fClusterRange.end - 1).run();
return run->leftToRight() ? TextAlign::kLeft : TextAlign::kRight;
}
}
void TextLine::scanStyles(StyleType style, const StyleVisitor& visitor) {
if (this->empty()) {
return;
@ -257,14 +274,14 @@ SkScalar TextLine::paintDecorations(SkCanvas* canvas, TextRange textRange,
}
for (auto decoration : AllTextDecorations) {
if (style.getDecorationType() && decoration == 0) {
if ((style.getDecorationType() & decoration) == 0) {
continue;
}
SkScalar thickness = style.getDecorationThicknessMultiplier();
//
SkScalar position = 0;
switch (style.getDecorationType()) {
switch (decoration) {
case TextDecoration::kUnderline:
position = -run->ascent() + thickness;
break;
@ -277,6 +294,7 @@ SkScalar TextLine::paintDecorations(SkCanvas* canvas, TextRange textRange,
}
default:
// TODO: can we actually get here?
SkASSERT(false);
break;
}
@ -504,7 +522,7 @@ Run* TextLine::shapeEllipsis(const SkString& ellipsis, Run* run) {
}
SkRect TextLine::measureTextInsideOneRun(
TextRange textRange, Run* run, size_t& pos, size_t& size, bool& clippingNeeded) const {
TextRange textRange, Run* run, size_t& pos, size_t& size, bool includeGhostSpaces, bool& clippingNeeded) const {
SkASSERT(intersectedSize(run->textRange(), textRange) >= 0);
@ -528,7 +546,8 @@ SkRect TextLine::measureTextInsideOneRun(
// EOL (when we expect the last cluster clipped without any spaces)
// Anything else (when we want the cluster width contain all the spaces -
// coming from letter spacing or word spacing or justification)
bool needsClipping = (run->leftToRight() ? endIndex : startIndex) == fClusterRange.end - 1;
auto range = includeGhostSpaces ? fGhostClusterRange : fClusterRange;
bool needsClipping = (run->leftToRight() ? endIndex == range.end - 1 : startIndex == range.end);
SkRect clip =
SkRect::MakeXYWH(run->positionX(start->startPos()) - run->positionX(0),
sizes().runTop(run),
@ -574,15 +593,16 @@ void TextLine::iterateThroughClustersInGlyphsOrder(bool reverse,
// Walk through the runs in the logical order
SkScalar TextLine::iterateThroughRuns(TextRange textRange,
SkScalar runOffset,
bool includeEmptyText,
bool includeGhostWhitespaces,
const RunVisitor& visitor) const {
TRACE_EVENT0("skia", TRACE_FUNC);
SkScalar width = 0;
for (auto& runIndex : fLogical) {
auto run = this->fMaster->runs().begin() + runIndex;
const auto run = &this->fMaster->run(runIndex);
// Only skip the text if it does not even touch the run
if (intersectedSize(run->textRange(), textRange) < 0) {
auto intersection = intersectedSize(run->textRange(), textRange);
if (intersection < 0 || (intersection == 0 && textRange.end != run->textRange().end)) {
continue;
}
@ -591,21 +611,25 @@ SkScalar TextLine::iterateThroughRuns(TextRange textRange,
continue;
}
// Measure the text
size_t pos;
size_t size;
bool clippingNeeded;
SkRect clip = this->measureTextInsideOneRun(intersect, run, pos, size, clippingNeeded);
SkRect clip = this->measureTextInsideOneRun(intersect, run, pos, size, includeGhostWhitespaces, clippingNeeded);
if (clip.height() == 0) {
continue;
}
// Clip the text
auto shift = runOffset - clip.fLeft;
clip.offset(shift, 0);
if (clip.fRight > fAdvance.fX) {
clip.fRight = fAdvance.fX;
clippingNeeded = true; // Correct the clip in case there was an ellipsis
} else if (runIndex == fLogical.back() && this->ellipsis() != nullptr) {
clippingNeeded = true; // To avoid trouble
if (includeGhostWhitespaces) {
clippingNeeded = false;
} else {
clippingNeeded = true;
if (clip.fRight > fAdvance.fX) {
clip.fRight = fAdvance.fX;
}
}
if (!visitor(run, pos, size, clip, shift - run->positionX(0), clippingNeeded)) {

View File

@ -25,6 +25,8 @@ public:
TextRange text,
TextRange textWithSpaces,
ClusterRange clusters,
ClusterRange clustersWithGhosts,
SkScalar widthWithSpaces,
LineMetrics sizes);
inline void setMaster(ParagraphImpl* master) { fMaster = master; }
@ -40,6 +42,7 @@ public:
return fAdvance.fX + (fEllipsis != nullptr ? fEllipsis->fAdvance.fX : 0);
}
inline SkScalar shift() const { return fShift; }
SkScalar widthWithSpaces() const { return fWidthWithSpaces; }
SkVector offset() const;
inline SkScalar alphabeticBaseline() const { return fSizes.alphabeticBaseline(); }
@ -55,13 +58,13 @@ public:
SkScalar shift, bool clippingNeeded)>;
SkScalar iterateThroughRuns(TextRange textRange,
SkScalar offsetX,
bool includeEmptyText,
bool includeGhostWhitespaces,
const RunVisitor& visitor) const;
using ClustersVisitor = std::function<bool(const Cluster* cluster, ClusterIndex index)>;
void iterateThroughClustersInGlyphsOrder(bool reverse, const ClustersVisitor& visitor) const;
void format(TextAlign effectiveAlign, SkScalar maxWidth, bool last);
void format(TextAlign effectiveAlign, SkScalar maxWidth);
void paint(SkCanvas* canvas);
void createEllipsis(SkScalar maxWidth, const SkString& ellipsis, bool ltr);
@ -70,6 +73,8 @@ public:
void scanStyles(StyleType style, const StyleVisitor& visitor);
void scanRuns(const RunVisitor& visitor);
TextAlign assumedTextAlign() const;
private:
Run* shapeEllipsis(const SkString& ellipsis, Run* run);
@ -79,6 +84,7 @@ private:
Run* run,
size_t& pos,
size_t& size,
bool includeGhostSpaces,
bool& clippingNeeded) const;
SkScalar paintText(SkCanvas* canvas, TextRange textRange, const TextStyle& style, SkScalar offsetX) const;
@ -101,11 +107,13 @@ private:
TextRange fTextRange;
TextRange fTextWithWhitespacesRange;
ClusterRange fClusterRange;
ClusterRange fGhostClusterRange;
SkTArray<size_t, true> fLogical;
SkVector fAdvance; // Text size
SkVector fOffset; // Text position
SkScalar fShift; // Left right
SkScalar fWidthWithSpaces;
std::shared_ptr<Run> fEllipsis; // In case the line ends with the ellipsis
LineMetrics fSizes; // Line metrics as a max of all run metrics
bool fHasBackground;

View File

@ -21,6 +21,7 @@ TextStyle::TextStyle() : fFontStyle() {
fLetterSpacing = 0.0;
fWordSpacing = 0.0;
fHeight = 1.0;
fHeightOverride = false;
fHasBackground = false;
fHasForeground = false;
fTextBaseline = TextBaseline::kAlphabetic;
@ -121,5 +122,18 @@ bool TextStyle::matchOneAttribute(StyleType styleType, const TextStyle& other) c
}
}
void TextStyle::getFontMetrics(SkFontMetrics* metrics) const {
SkFont font(fTypeface, fFontSize);
font.getMetrics(metrics);
if (fHeight == 0 || fHeight == 1) {
metrics->fAscent = (metrics->fAscent - metrics->fLeading / 2);
metrics->fDescent = (metrics->fDescent + metrics->fLeading / 2);
} else {
auto height = metrics->fDescent - metrics->fAscent + metrics->fLeading;
metrics->fAscent = (metrics->fAscent - metrics->fLeading / 2) * height / fHeight;
metrics->fDescent = (metrics->fDescent + metrics->fLeading / 2) * height / fHeight;
}
}
} // namespace textlayout
} // namespace skia

View File

@ -79,16 +79,27 @@ void TextWrapper::moveForward() {
}
// Special case for start/end cluster since they can be clipped
void TextWrapper::trimEndSpaces() {
void TextWrapper::trimEndSpaces(TextAlign align) {
// Remember the breaking position
fEndLine.saveBreak();
// Move the end of the line to the left
// Skip all space cluster at the end
//bool left = align == TextAlign::kStart || align == TextAlign::kLeft;
bool right = align == TextAlign::kRight || align == TextAlign::kEnd;
for (auto cluster = fEndLine.endCluster();
cluster >= fEndLine.startCluster() && cluster->isWhitespaces();
--cluster) {
fEndLine.trim(cluster);
if ((/*left && */cluster->run()->leftToRight()) ||
(right && !cluster->run()->leftToRight()) ||
align == TextAlign::kJustify || align == TextAlign::kCenter) {
fEndLine.trim(cluster);
continue;
} else {
break;
}
}
if (!right) {
fEndLine.trim();
}
fEndLine.trim();
}
SkScalar TextWrapper::getClustersTrimmedWidth() {
@ -105,26 +116,26 @@ SkScalar TextWrapper::getClustersTrimmedWidth() {
}
// Trim the beginning spaces in case of soft line break
void TextWrapper::trimStartSpaces(Cluster* endOfClusters) {
// Restore the breaking position
fEndLine.restoreBreak();
fEndLine.nextPos();
std::tuple<Cluster*, size_t, SkScalar> TextWrapper::trimStartSpaces(Cluster* endOfClusters) {
if (fHardLineBreak) {
// End of line is always end of cluster, but need to skip \n
fEndLine.startFrom(fEndLine.endCluster(), 0);
return;
}
if (fEndLine.endPos() != 0) {
// Clipping
fEndLine.startFrom(fEndLine.endCluster(), fEndLine.endPos());
return;
auto width = fEndLine.width();
auto cluster = fEndLine.endCluster() + 1;
while (cluster < fEndLine.breakCluster() && cluster->isWhitespaces()) {
width += cluster->width();
++cluster;
}
return { fEndLine.breakCluster() + 1, 0, width };
}
auto cluster = fEndLine.endCluster();
auto width = fEndLine.width();
auto cluster = fEndLine.breakCluster() + 1;
while (cluster < endOfClusters && cluster->isWhitespaces()) {
width += cluster->width();
++cluster;
}
fEndLine.startFrom(cluster, 0);
return { cluster, 0, width };
}
void TextWrapper::breakTextIntoLines(ParagraphImpl* parent,
@ -132,13 +143,15 @@ void TextWrapper::breakTextIntoLines(ParagraphImpl* parent,
const AddLineToParagraph& addLine) {
auto span = parent->clusters();
auto maxLines = parent->paragraphStyle().getMaxLines();
auto ellipsisStr = parent->paragraphStyle().getEllipsis();
auto& ellipsisStr = parent->paragraphStyle().getEllipsis();
auto align = parent->paragraphStyle().getTextAlign();
fHeight = 0;
fMinIntrinsicWidth = 0;
fMaxIntrinsicWidth = 0;
fEndLine = TextStretch(span.begin(), span.begin());
auto end = &span.back();
fEndLine = TextStretch(span.begin(), span.begin(), parent->strutForceHeight());
auto end = span.end() - 1;
auto start = span.begin();
while (fEndLine.endCluster() != end) {
reset();
@ -146,7 +159,13 @@ void TextWrapper::breakTextIntoLines(ParagraphImpl* parent,
moveForward();
// Do not trim end spaces on the naturally last line of the left aligned text
trimEndSpaces();
trimEndSpaces(align);
// For soft line breaks add to the line all the spaces next to it
Cluster* startLine;
size_t pos;
SkScalar widthWithSpaces;
std::tie(startLine, pos, widthWithSpaces) = trimStartSpaces(end);
auto lastLine = maxLines == std::numeric_limits<size_t>::max() ||
fLineNumber >= maxLines;
@ -159,15 +178,20 @@ void TextWrapper::breakTextIntoLines(ParagraphImpl* parent,
// TODO: perform ellipsis work here
if (parent->strutEnabled()) {
// Make sure font metrics are not less than the strut
parent->strutMetrics().updateLineMetrics(fEndLine.metrics(),
parent->strutForceHeight());
parent->strutMetrics().updateLineMetrics(fEndLine.metrics());
}
fMaxIntrinsicWidth = SkMaxScalar(fMaxIntrinsicWidth, fEndLine.width());
// TODO: keep start/end/break info for text and runs but in a better way that below
TextRange text(fEndLine.startCluster()->textRange().start, fEndLine.endCluster()->textRange().end);
TextRange textWithSpaces(fEndLine.startCluster()->textRange().start, fEndLine.breakCluster()->textRange().end);
ClusterRange clusters(fEndLine.startCluster() - parent->clusters().begin(), fEndLine.endCluster() - parent->clusters().begin() + 1);
addLine(text, textWithSpaces, clusters,
TextRange textWithSpaces(fEndLine.startCluster()->textRange().start, startLine->textRange().start);
if (fEndLine.breakCluster()->isHardBreak()) {
textWithSpaces.end = fEndLine.breakCluster()->textRange().start;
} else if (startLine == end) {
textWithSpaces.end = fEndLine.breakCluster()->textRange().end;
}
ClusterRange clusters(fEndLine.startCluster() - start, fEndLine.endCluster() - start);
ClusterRange clustersWithGhosts(fEndLine.startCluster() - start, startLine - start);
addLine(text, textWithSpaces, clusters, clustersWithGhosts, widthWithSpaces,
fEndLine.startPos(),
fEndLine.endPos(),
SkVector::Make(0, fHeight),
@ -178,7 +202,11 @@ void TextWrapper::breakTextIntoLines(ParagraphImpl* parent,
// Start a new line
fHeight += fEndLine.metrics().height();
trimStartSpaces(end);
if (!fHardLineBreak) {
fEndLine.clean();
}
fEndLine.startFrom(startLine, pos);
parent->fMaxWidthWithTrailingSpaces = SkMaxScalar(parent->fMaxWidthWithTrailingSpaces, widthWithSpaces);
if (needEllipsis || fLineNumber >= maxLines) {
break;
@ -190,12 +218,13 @@ void TextWrapper::breakTextIntoLines(ParagraphImpl* parent,
// Last character is a line break
if (parent->strutEnabled()) {
// Make sure font metrics are not less than the strut
parent->strutMetrics().updateLineMetrics(fEndLine.metrics(),
parent->strutForceHeight());
parent->strutMetrics().updateLineMetrics(fEndLine.metrics());
}
TextRange empty(fEndLine.breakCluster()->textRange().start, fEndLine.breakCluster()->textRange().start);
ClusterRange clusters(fEndLine.breakCluster() - parent->clusters().begin(), fEndLine.breakCluster() - parent->clusters().begin());
addLine(empty, empty, clusters,
TextRange hardBreak(fEndLine.breakCluster()->textRange().end, fEndLine.breakCluster()->textRange().end);
ClusterRange clusters(fEndLine.breakCluster() - start, fEndLine.breakCluster() - start);
addLine(empty, hardBreak, clusters, clusters,
0,
0,
0,
SkVector::Make(0, fHeight),

View File

@ -34,9 +34,9 @@ class TextWrapper {
};
class TextStretch {
public:
TextStretch() : fStart(), fEnd(), fWidth(0) {}
TextStretch(Cluster* s, Cluster* e)
: fStart(s, 0), fEnd(e, e->endPos()), fMetrics(), fWidth(0) {
TextStretch() : fStart(), fEnd(), fWidth(0), fWidthWithGhostSpaces(0) {}
TextStretch(Cluster* s, Cluster* e, bool forceStrut)
: fStart(s, 0), fEnd(e, e->endPos()), fMetrics(forceStrut), fWidth(0), fWidthWithGhostSpaces(0) {
for (auto c = s; c <= e; ++c) {
if (c->run() != nullptr) {
fMetrics.add(c->run());
@ -45,12 +45,14 @@ class TextWrapper {
}
inline SkScalar width() const { return fWidth; }
SkScalar withWithGhostSpaces() const { return fWidthWithGhostSpaces; }
inline Cluster* startCluster() const { return fStart.cluster(); }
inline Cluster* endCluster() const { return fEnd.cluster(); }
inline Cluster* breakCluster() const { return fBreak.cluster(); }
inline LineMetrics& metrics() { return fMetrics; }
inline size_t startPos() const { return fStart.position(); }
inline size_t endPos() const { return fEnd.position(); }
inline size_t breakPos() const { return fBreak.position(); }
bool endOfCluster() { return fEnd.position() == fEnd.cluster()->endPos(); }
bool endOfWord() {
return endOfCluster() &&
@ -89,15 +91,22 @@ class TextWrapper {
fWidth = 0;
}
void nextPos() {
if (fEnd.position() == fEnd.cluster()->endPos()) {
fEnd.move(true);
void nextBreakPos(Cluster* endOfClusters) {
if (fBreak.position() == fBreak.cluster()->endPos()) {
if (fBreak.cluster() < endOfClusters) {
fBreak.move(true);
} else {
fBreak.setPosition(0);
}
} else {
fEnd.setPosition(fEnd.cluster()->endPos());
fBreak.setPosition(fBreak.cluster()->endPos());
}
}
void saveBreak() { fBreak = fEnd; }
void saveBreak() {
fWidthWithGhostSpaces = fWidth;
fBreak = fEnd;
}
void restoreBreak() { fEnd = fBreak; }
@ -128,6 +137,7 @@ class TextWrapper {
ClusterPos fBreak;
LineMetrics fMetrics;
SkScalar fWidth;
SkScalar fWidthWithGhostSpaces;
};
public:
@ -136,6 +146,8 @@ public:
using AddLineToParagraph = std::function<void(TextRange text,
TextRange textWithSpaces,
ClusterRange clusters,
ClusterRange clustersWithGhosts,
SkScalar AddLineToParagraph,
size_t startClip,
size_t endClip,
SkVector offset,
@ -175,8 +187,8 @@ private:
void lookAhead(SkScalar maxWidth, Cluster* endOfClusters);
void moveForward();
void trimEndSpaces();
void trimStartSpaces(Cluster* endOfClusters);
void trimEndSpaces(TextAlign align);
std::tuple<Cluster*, size_t, SkScalar> trimStartSpaces(Cluster* endOfClusters);
SkScalar getClustersTrimmedWidth();
};
} // namespace textlayout

View File

@ -972,7 +972,7 @@ protected:
canvas->drawColor(background);
const char* line =
"World domination is such an ugly phrase - I prefer to call it world optimisation";
"World domination is such an ugly phrase - I prefer to call it world optimisation.";
ParagraphStyle paragraphStyle;
paragraphStyle.setTextAlign(TextAlign::kLeft);
@ -1033,7 +1033,7 @@ private:
class ParagraphView8 : public ParagraphView_Base {
protected:
SkString name() override { return SkString("Paragraph7"); }
SkString name() override { return SkString("Paragraph8"); }
void drawText(SkCanvas* canvas, SkColor background, SkScalar wordSpace, SkScalar w,
SkScalar h) {
@ -1042,7 +1042,7 @@ protected:
canvas->drawColor(background);
const char* line =
"World domination is such an ugly phrase - I prefer to call it world optimisation";
"World domination is such an ugly phrase - I prefer to call it world optimisation.";
ParagraphStyle paragraphStyle;
paragraphStyle.setTextAlign(TextAlign::kLeft);

File diff suppressed because it is too large Load Diff