diff --git a/modules/skparagraph/samples/SampleParagraph.cpp b/modules/skparagraph/samples/SampleParagraph.cpp index f3163c9993..134b486b9c 100644 --- a/modules/skparagraph/samples/SampleParagraph.cpp +++ b/modules/skparagraph/samples/SampleParagraph.cpp @@ -3416,20 +3416,6 @@ private: using INHERITED = Sample; }; -void print_line_metrics(std::vector metrics) { - for (size_t i = 0; i < metrics.size(); ++i) { - auto& line = metrics[i]; - printf("Line[%zd] startIndex=%zd, endIndex=%zd, endExcludingWhitespaces=%zd, _endIncludingNewline=%zd, hardBreak=%d, baseline=%.2f, ascent=%.2f, descent=%.2f, unscaledAscent=%.2f, height=%.2f, width=%.2f, left=%.2f, lineNumber=%zd\n", - i, - line.fStartIndex, line.fEndIndex, - line.fEndExcludingWhitespaces, line.fEndIncludingNewline, - line.fHardBreak, - line.fBaseline, line.fAscent, line.fDescent, line.fUnscaledAscent, - line.fHeight, line.fWidth, line.fLeft, - line.fLineNumber); - } -} - class ParagraphView58 : public ParagraphView_Base { protected: SkString name() override { return SkString("Paragraph58"); } @@ -3446,21 +3432,14 @@ protected: ParagraphBuilderImpl builder(paragraph_style, fontCollection); TextStyle text_style; text_style.setFontFamilies({SkString("Roboto")}); - text_style.setFontSize(32); - text_style.setHeight(5.0); - text_style.setHeightOverride(true); + text_style.setFontSize(40); text_style.setColor(SK_ColorBLACK); builder.pushStyle(text_style); - builder.addText("Line1\nLine2\nLine3\nLine4\n"); + builder.addText(u"Text1 Google\u00A0Pay Text2"); auto paragraph = builder.Build(); paragraph->layout(width()); paragraph->paint(canvas, 0, 0); - - std::vector metrics; - paragraph->getLineMetrics(metrics); - - print_line_metrics(metrics); } private: diff --git a/modules/skparagraph/src/ParagraphImpl.cpp b/modules/skparagraph/src/ParagraphImpl.cpp index af917bf33c..2862ce2a1d 100644 --- a/modules/skparagraph/src/ParagraphImpl.cpp +++ b/modules/skparagraph/src/ParagraphImpl.cpp @@ -28,6 +28,11 @@ namespace textlayout { namespace { +static inline SkUnichar nextUtf8Unit(const char** ptr, const char* end) { + SkUnichar val = SkUTF::NextUTF8(ptr, end); + return val < 0 ? 0xFFFD : val; +} + SkScalar littleRound(SkScalar a) { // This rounding is done to match Flutter tests. Must be removed.. auto val = std::fabs(a); @@ -736,6 +741,18 @@ size_t ParagraphImpl::getWhitespacesLength(TextRange textRange) { return len; } +bool ParagraphImpl::isSpace(TextRange textRange) { + auto text = ParagraphImpl::text(textRange); + const char* ch = text.begin(); + while (ch != text.end()) { + SkUnichar unicode = nextUtf8Unit(&ch, text.end()); + if (!fUnicode->isSpace(unicode)) { + return false; + } + } + return true; +} + void ParagraphImpl::getLineMetrics(std::vector& metrics) { metrics.clear(); for (auto& line : fLines) { diff --git a/modules/skparagraph/src/ParagraphImpl.h b/modules/skparagraph/src/ParagraphImpl.h index fde9e5563d..84fa98ec42 100644 --- a/modules/skparagraph/src/ParagraphImpl.h +++ b/modules/skparagraph/src/ParagraphImpl.h @@ -220,6 +220,7 @@ public: using CodeUnitRangeVisitor = std::function; void forEachCodeUnitPropertyRange(CodeUnitFlags property, CodeUnitRangeVisitor visitor); size_t getWhitespacesLength(TextRange textRange); + bool isSpace(TextRange textRange); bool codeUnitHasProperty(size_t index, CodeUnitFlags property) const { return (fCodeUnitProperties[index] & property) == property; } diff --git a/modules/skparagraph/src/Run.cpp b/modules/skparagraph/src/Run.cpp index de9a1e4932..e59c990e9c 100644 --- a/modules/skparagraph/src/Run.cpp +++ b/modules/skparagraph/src/Run.cpp @@ -383,6 +383,7 @@ Cluster::Cluster(ParagraphImpl* owner, , fHalfLetterSpacing(0.0) { size_t len = fOwner->getWhitespacesLength(fTextRange); fIsWhiteSpaces = (len == this->fTextRange.width()); + fIsSpaces = fOwner->isSpace(fTextRange); } } // namespace textlayout diff --git a/modules/skparagraph/src/Run.h b/modules/skparagraph/src/Run.h index 15d4d25ee2..fd62ef6a65 100644 --- a/modules/skparagraph/src/Run.h +++ b/modules/skparagraph/src/Run.h @@ -248,6 +248,7 @@ public: } bool isWhitespaces() const { return fIsWhiteSpaces; } + bool isSpaces() const { return fIsSpaces; } bool isHardBreak() const; bool isSoftBreak() const; bool isGraphemeBreak() const; @@ -297,6 +298,7 @@ private: SkScalar fHeight; SkScalar fHalfLetterSpacing; bool fIsWhiteSpaces; + bool fIsSpaces; }; class InternalLineMetrics { diff --git a/modules/skparagraph/src/TextWrapper.cpp b/modules/skparagraph/src/TextWrapper.cpp index e942a05b57..94428a8e70 100644 --- a/modules/skparagraph/src/TextWrapper.cpp +++ b/modules/skparagraph/src/TextWrapper.cpp @@ -29,6 +29,7 @@ void TextWrapper::lookAhead(SkScalar maxWidth, Cluster* endOfClusters) { fClusters.startFrom(fEndLine.startCluster(), fEndLine.startPos()); fClip.startFrom(fEndLine.startCluster(), fEndLine.startPos()); + Cluster* nextNonBreakingSpace = nullptr; for (auto cluster = fEndLine.endCluster(); cluster < endOfClusters; ++cluster) { // TODO: Trying to deal with flutter rounding problem. Must be removed... auto width = fWords.width() + fClusters.width() + cluster->width(); @@ -61,6 +62,7 @@ void TextWrapper::lookAhead(SkScalar maxWidth, Cluster* endOfClusters) { // Walk further to see if there is a too long word, cluster or glyph SkScalar nextWordLength = fClusters.width(); + SkScalar nextShortWordLength = nextWordLength; for (auto further = cluster; further != endOfClusters; ++further) { if (further->isSoftBreak() || further->isHardBreak() || further->isWhitespaces()) { break; @@ -69,6 +71,13 @@ void TextWrapper::lookAhead(SkScalar maxWidth, Cluster* endOfClusters) { // Placeholder ends the word break; } + + if (further->isSpaces() && nextWordLength <= maxWidth) { + // The cluster is spaces but not the end of the word in a normal sense + nextNonBreakingSpace = further; + nextShortWordLength = nextWordLength; + } + if (maxWidth == 0) { // This is a tricky flutter case: layout(width:0) places 1 cluster on each line nextWordLength = std::max(nextWordLength, further->width()); @@ -77,6 +86,22 @@ void TextWrapper::lookAhead(SkScalar maxWidth, Cluster* endOfClusters) { } } if (nextWordLength > maxWidth) { + if (nextNonBreakingSpace != nullptr) { + // We only get here if the non-breaking space improves our situation + // (allows us to break the text to fit the word) + auto shortLength = littleRound(fWords.width() + nextShortWordLength); + if (shortLength <= maxWidth) { + // We can add the short word to the existing line + fClusters = TextStretch(fClusters.startCluster(), nextNonBreakingSpace, fClusters.metrics().getForceStrut()); + fMinIntrinsicWidth = std::max(fMinIntrinsicWidth, nextShortWordLength); + fWords.extend(fClusters); + } else { + // We can place the short word on the next line + fClusters.clean(); + } + // Either way we are not in "word is too long" situation anymore + break; + } // If the word is too long we can break it right now and hope it's enough fMinIntrinsicWidth = std::max(fMinIntrinsicWidth, nextWordLength); if (fClusters.endPos() - fClusters.startPos() > 1 || diff --git a/modules/skparagraph/src/TextWrapper.h b/modules/skparagraph/src/TextWrapper.h index 736635e4ef..81244dfeab 100644 --- a/modules/skparagraph/src/TextWrapper.h +++ b/modules/skparagraph/src/TextWrapper.h @@ -41,7 +41,11 @@ class TextWrapper { if (c->run() != nullptr) { fMetrics.add(c->run()); } + if (c < e) { + fWidth += c->width(); + } } + fWidthWithGhostSpaces = fWidth; } inline SkScalar width() const { return fWidth; } diff --git a/modules/skshaper/src/SkUnicode.h b/modules/skshaper/src/SkUnicode.h index 0db2302ca5..5ec3739e82 100644 --- a/modules/skshaper/src/SkUnicode.h +++ b/modules/skshaper/src/SkUnicode.h @@ -113,6 +113,7 @@ class SKUNICODE_API SkUnicode { virtual bool isControl(SkUnichar utf8) = 0; virtual bool isWhitespace(SkUnichar utf8) = 0; + virtual bool isSpace(SkUnichar utf8) = 0; virtual SkString convertUtf16ToUtf8(const std::u16string& utf16) = 0; // Methods used in SkShaper diff --git a/modules/skshaper/src/SkUnicode_icu.cpp b/modules/skshaper/src/SkUnicode_icu.cpp index 99bb55b53d..ce420d80dd 100644 --- a/modules/skshaper/src/SkUnicode_icu.cpp +++ b/modules/skshaper/src/SkUnicode_icu.cpp @@ -459,6 +459,10 @@ public: return u_isWhitespace(utf8); } + bool isSpace(SkUnichar utf8) override { + return u_isspace(utf8); + } + static bool isHardLineBreak(SkUnichar utf8) { auto property = u_getIntPropertyValue(utf8, UCHAR_LINE_BREAK); return property == U_LB_LINE_FEED || property == U_LB_MANDATORY_BREAK;