diff --git a/modules/skparagraph/samples/SampleParagraph.cpp b/modules/skparagraph/samples/SampleParagraph.cpp index a0e823d44c..470ffda51a 100644 --- a/modules/skparagraph/samples/SampleParagraph.cpp +++ b/modules/skparagraph/samples/SampleParagraph.cpp @@ -3617,27 +3617,38 @@ protected: void onDrawContent(SkCanvas* canvas) override { canvas->drawColor(SK_ColorWHITE); - auto fontCollection = sk_make_sp(); - fontCollection->setDefaultFontManager(SkFontMgr::RefDefault()); + auto fontCollection = getFontCollection(); + + StrutStyle strut_style; + strut_style.setFontFamilies({SkString("Roboto")}); + strut_style.setStrutEnabled(true); + strut_style.setFontSize(8); + strut_style.setForceStrutHeight(true); + TextStyle text_style; text_style.setFontFamilies({SkString("Roboto")}); - text_style.setFontSize(100); - SkPaint black; - black.setColor(SK_ColorBLACK); - text_style.setForegroundColor(black); - SkPaint red; - red.setColor(SK_ColorRED); - text_style.setBackgroundColor(red); + text_style.setFontSize(14); + text_style.setColor(SK_ColorBLACK); ParagraphStyle paragraph_style; paragraph_style.setTextStyle(text_style); + paragraph_style.setStrutStyle(strut_style); ParagraphBuilderImpl builder(paragraph_style, fontCollection); builder.pushStyle(text_style); - builder.addText("."); + builder.addText("something"); auto paragraph = builder.Build(); paragraph->layout(SK_ScalarInfinity); paragraph->paint(canvas, 0, 0); + SkDebugf("height=%f\n", paragraph->getHeight()); + /* + auto boxes = + paragraph->getRectsForRange(0, 1, RectHeightStyle::kTight, RectWidthStyle::kTight); + for (auto& box : boxes) { + SkDebugf("[%f,%f:%f,%f]\n", + box.rect.fLeft, box.rect.fTop, box.rect.fRight, box.rect.fBottom); + } + */ } private: diff --git a/modules/skparagraph/src/ParagraphCache.cpp b/modules/skparagraph/src/ParagraphCache.cpp index 9b20f0d512..3c009e2315 100644 --- a/modules/skparagraph/src/ParagraphCache.cpp +++ b/modules/skparagraph/src/ParagraphCache.cpp @@ -73,7 +73,10 @@ public: , fWords(paragraph->fWords) , fBidiRegions(paragraph->fBidiRegions) , fUTF8IndexForUTF16Index(paragraph->fUTF8IndexForUTF16Index) - , fUTF16IndexForUTF8Index(paragraph->fUTF16IndexForUTF8Index) { } + , fUTF16IndexForUTF8Index(paragraph->fUTF16IndexForUTF8Index) + , fHasLineBreaks(paragraph->fHasLineBreaks) + , fHasWhitespacesInside(paragraph->fHasWhitespacesInside) + , fTrailingSpaces(paragraph->fTrailingSpaces) { } // Input == key ParagraphCacheKey fKey; @@ -88,6 +91,9 @@ public: std::vector fBidiRegions; SkTArray fUTF8IndexForUTF16Index; SkTArray fUTF16IndexForUTF8Index; + bool fHasLineBreaks; + bool fHasWhitespacesInside; + TextIndex fTrailingSpaces; }; uint32_t ParagraphCacheKey::mix(uint32_t hash, uint32_t data) { @@ -253,6 +259,9 @@ void ParagraphCache::updateTo(ParagraphImpl* paragraph, const Entry* entry) { paragraph->fBidiRegions = entry->fValue->fBidiRegions; paragraph->fUTF8IndexForUTF16Index = entry->fValue->fUTF8IndexForUTF16Index; paragraph->fUTF16IndexForUTF8Index = entry->fValue->fUTF16IndexForUTF8Index; + paragraph->fHasLineBreaks = entry->fValue->fHasLineBreaks; + paragraph->fHasWhitespacesInside = entry->fValue->fHasWhitespacesInside; + paragraph->fTrailingSpaces = entry->fValue->fTrailingSpaces; for (auto& run : paragraph->fRuns) { run.setOwner(paragraph); } diff --git a/modules/skparagraph/src/ParagraphImpl.cpp b/modules/skparagraph/src/ParagraphImpl.cpp index 723133d223..1f465a8146 100644 --- a/modules/skparagraph/src/ParagraphImpl.cpp +++ b/modules/skparagraph/src/ParagraphImpl.cpp @@ -78,6 +78,9 @@ ParagraphImpl::ParagraphImpl(const SkString& text, , fOldWidth(0) , fOldHeight(0) , fUnicode(std::move(unicode)) + , fHasLineBreaks(false) + , fHasWhitespacesInside(false) + , fTrailingSpaces(0) { SkASSERT(fUnicode); } @@ -237,21 +240,38 @@ bool ParagraphImpl::computeCodeUnitProperties() { return false; } - // Get all spaces + // Collect all spaces and some extra information + fTrailingSpaces = fText.size(); + TextIndex firstWhitespace = EMPTY_INDEX; fUnicode->forEachCodepoint(fText.c_str(), fText.size(), - [this](SkUnichar unichar, int32_t start, int32_t end, int32_t count) { + [this, &firstWhitespace](SkUnichar unichar, int32_t start, int32_t end, int32_t count) { if (fUnicode->isWhitespace(unichar)) { for (auto i = start; i < end; ++i) { fCodeUnitProperties[i] |= CodeUnitFlags::kPartOfWhiteSpaceBreak; } + if (fTrailingSpaces == fText.size()) { + fTrailingSpaces = start; + } + if (firstWhitespace == EMPTY_INDEX) { + firstWhitespace = start; + } + } else { + fTrailingSpaces = fText.size(); } if (fUnicode->isSpace(unichar)) { for (auto i = start; i < end; ++i) { fCodeUnitProperties[i] |= CodeUnitFlags::kPartOfIntraWordBreak; } } + if (fUnicode->isHardBreak(unichar)) { + fHasLineBreaks = true; + } }); + if (firstWhitespace < fTrailingSpaces) { + fHasWhitespacesInside = true; + } + // Get line breaks std::vector lineBreaks; if (!fUnicode->getLineBreaks(fText.c_str(), fText.size(), &lineBreaks)) { @@ -381,7 +401,7 @@ void ParagraphImpl::applySpacingAndBuildClusterTable() { this->buildClusterTable(); // This is something Flutter requires for (auto& cluster : fClusters) { - cluster.setHalfLetterSpacing(fTextStyles[0].fStyle.getLetterSpacing()/2); + cluster.setHalfLetterSpacing(style.getLetterSpacing()/2); } return; } @@ -515,6 +535,69 @@ bool ParagraphImpl::shapeTextIntoEndlessLine() { } void ParagraphImpl::breakShapedTextIntoLines(SkScalar maxWidth) { + + if (!fHasLineBreaks && + !fHasWhitespacesInside && + fPlaceholders.size() == 1 && + fRuns.size() == 1 && fRuns[0].fAdvance.fX <= maxWidth) { + // This is a short version of a line breaking when we know that: + // 1. We have only one line of text + // 2. It's shaped into a single run + // 3. There are no placeholders + // 4. There are no linebreaks (which will format text into multiple lines) + // 5. There are no whitespaces so the minIntrinsicWidth=maxIntrinsicWidth + // (To think about that, the last condition is not quite right; + // we should calculate minIntrinsicWidth by soft line breaks. + // However, it's how it's done in Flutter now) + auto& run = this->fRuns[0]; + auto advance = run.advance(); + auto textRange = TextRange(0, this->text().size()); + auto textExcludingSpaces = TextRange(0, fTrailingSpaces); + InternalLineMetrics metrics(this->strutForceHeight()); + metrics.add(&run); + if (this->strutEnabled()) { + this->strutMetrics().updateLineMetrics(metrics); + } + auto disableFirstAscent = this->paragraphStyle().getTextHeightBehavior() & + TextHeightBehavior::kDisableFirstAscent; + auto disableLastDescent = this->paragraphStyle().getTextHeightBehavior() & + TextHeightBehavior::kDisableLastDescent; + if (disableFirstAscent) { + metrics.fAscent = metrics.fRawAscent; + } + if (disableLastDescent) { + metrics.fDescent = metrics.fRawDescent; + } + ClusterIndex trailingSpaces = fClusters.size(); + do { + --trailingSpaces; + auto& cluster = fClusters[trailingSpaces]; + if (!cluster.isWhitespaceBreak()) { + ++trailingSpaces; + break; + } + advance.fX -= cluster.width(); + } while (trailingSpaces != 0); + + advance.fY = metrics.height(); + auto clusterRange = ClusterRange(0, trailingSpaces); + auto clusterRangeWithGhosts = ClusterRange(0, this->clusters().size() - 1); + this->addLine(SkPoint::Make(0, 0), advance, + textExcludingSpaces, textRange, textRange, + clusterRange, clusterRangeWithGhosts, run.advance().x(), + metrics); + + fLongestLine = nearlyZero(advance.fX) ? run.advance().fX : advance.fX; + fHeight = advance.fY; + fWidth = maxWidth; + fMaxIntrinsicWidth = run.advance().fX; + fMinIntrinsicWidth = advance.fX; + fAlphabeticBaseline = fLines.empty() ? fEmptyMetrics.alphabeticBaseline() : fLines.front().alphabeticBaseline(); + fIdeographicBaseline = fLines.empty() ? fEmptyMetrics.ideographicBaseline() : fLines.front().ideographicBaseline(); + fExceededMaxLines = false; + return; + } + TextWrapper textWrapper; textWrapper.breakTextIntoLines( this, @@ -536,7 +619,6 @@ void ParagraphImpl::breakShapedTextIntoLines(SkScalar maxWidth) { if (addEllipsis) { line.createEllipsis(maxWidth, getEllipsis(), true); } - fLongestLine = std::max(fLongestLine, nearlyZero(advance.fX) ? widthWithSpaces : advance.fX); }); diff --git a/modules/skparagraph/src/ParagraphImpl.h b/modules/skparagraph/src/ParagraphImpl.h index 866db369da..768a851633 100644 --- a/modules/skparagraph/src/ParagraphImpl.h +++ b/modules/skparagraph/src/ParagraphImpl.h @@ -270,6 +270,9 @@ private: SkScalar fMaxWidthWithTrailingSpaces; std::shared_ptr fUnicode; + bool fHasLineBreaks; + bool fHasWhitespacesInside; + TextIndex fTrailingSpaces; }; } // namespace textlayout } // namespace skia diff --git a/modules/skparagraph/src/Run.h b/modules/skparagraph/src/Run.h index 7aca8c5253..f83f4c170e 100644 --- a/modules/skparagraph/src/Run.h +++ b/modules/skparagraph/src/Run.h @@ -401,7 +401,6 @@ public: if (fForceStrut) { return; } - fAscent = std::min(fAscent, run->correctAscent()); fDescent = std::max(fDescent, run->correctDescent()); fLeading = std::max(fLeading, run->correctLeading()); @@ -463,6 +462,11 @@ public: fLeading = l; } + void updateRawData(SkScalar ra, SkScalar rd) { + fRawAscent = ra; + fRawDescent = rd; + } + SkScalar alphabeticBaseline() const { return fLeading / 2 - fAscent; } SkScalar ideographicBaseline() const { return fDescent - fAscent + fLeading; } SkScalar deltaBaselines() const { return fLeading / 2 + fDescent; } @@ -477,6 +481,7 @@ public: private: + friend class ParagraphImpl; friend class TextWrapper; friend class TextLine; diff --git a/modules/skparagraph/tests/SkParagraphTest.cpp b/modules/skparagraph/tests/SkParagraphTest.cpp index 9241826ca7..b27d1f76e6 100644 --- a/modules/skparagraph/tests/SkParagraphTest.cpp +++ b/modules/skparagraph/tests/SkParagraphTest.cpp @@ -6790,3 +6790,42 @@ UNIX_ONLY_TEST(SkParagraph_EllipsisGetRectForRange, reporter) { canvas.drawRects(SK_ColorRED, boxes1); canvas.drawRects(SK_ColorRED, boxes2); } + +UNIX_ONLY_TEST(SkParagraph_StrutAndTextBehavior, reporter) { + sk_sp fontCollection = sk_make_sp(); + if (!fontCollection->fontsFound()) return; + TestCanvas canvas("SkParagraph_StrutAndTextBehavior.png"); + const char* text = " "; + const size_t len = strlen(text); + + TextStyle text_style; + text_style.setFontFamilies({SkString("Ahem")}); + text_style.setFontSize(16.0); + text_style.setColor(SK_ColorBLACK); + StrutStyle strut_style; + strut_style.setStrutEnabled(true); + strut_style.setForceStrutHeight(true); + strut_style.setHeight(1.5); + strut_style.setHeightOverride(true); + strut_style.setFontFamilies({SkString("Ahem")}); + strut_style.setFontSize(16.0); + ParagraphStyle paragraph_style; + paragraph_style.setStrutStyle(strut_style); + paragraph_style.setTextStyle(text_style); + + auto draw = [&](TextHeightBehavior tb) { + paragraph_style.setTextHeightBehavior(tb); + ParagraphBuilderImpl builder(paragraph_style, fontCollection); + builder.pushStyle(text_style); + builder.addText(text, len); + auto paragraph = builder.Build(); + paragraph->layout(SK_ScalarInfinity); + return paragraph->getHeight(); + }; + + auto height1 = draw(TextHeightBehavior::kDisableAll); + auto height2 = draw(TextHeightBehavior::kAll); + + REPORTER_ASSERT(reporter, SkScalarNearlyEqual(height1, 16.0f)); + REPORTER_ASSERT(reporter, SkScalarNearlyEqual(height2, 24.0f)); +} diff --git a/modules/skunicode/include/SkUnicode.h b/modules/skunicode/include/SkUnicode.h index 797892201a..5c1d02b345 100644 --- a/modules/skunicode/include/SkUnicode.h +++ b/modules/skunicode/include/SkUnicode.h @@ -115,6 +115,7 @@ class SKUNICODE_API SkUnicode { virtual bool isControl(SkUnichar utf8) = 0; virtual bool isWhitespace(SkUnichar utf8) = 0; virtual bool isSpace(SkUnichar utf8) = 0; + virtual bool isHardBreak(SkUnichar utf8) = 0; virtual SkString toUpper(const SkString&) = 0; // Methods used in SkShaper and SkText diff --git a/modules/skunicode/src/SkUnicode_icu.cpp b/modules/skunicode/src/SkUnicode_icu.cpp index fb91800978..0f84cbc3a2 100644 --- a/modules/skunicode/src/SkUnicode_icu.cpp +++ b/modules/skunicode/src/SkUnicode_icu.cpp @@ -419,6 +419,11 @@ public: return SkScriptIterator_icu::makeScriptIterator(); } + static bool isHardLineBreak(SkUnichar utf8) { + auto property = sk_u_getIntPropertyValue(utf8, UCHAR_LINE_BREAK); + return property == U_LB_LINE_FEED || property == U_LB_MANDATORY_BREAK; + } + // TODO: Use ICU data file to detect controls and whitespaces bool isControl(SkUnichar utf8) override { return sk_u_iscntrl(utf8); @@ -432,9 +437,8 @@ public: return sk_u_isspace(utf8); } - static bool isHardLineBreak(SkUnichar utf8) { - auto property = sk_u_getIntPropertyValue(utf8, UCHAR_LINE_BREAK); - return property == U_LB_LINE_FEED || property == U_LB_MANDATORY_BREAK; + bool isHardBreak(SkUnichar utf8) override { + return SkUnicode_icu::isHardLineBreak(utf8); } SkString toUpper(const SkString& str) override {