diff --git a/modules/skparagraph/include/DartTypes.h b/modules/skparagraph/include/DartTypes.h index 3862ab5e61..162fe10445 100644 --- a/modules/skparagraph/include/DartTypes.h +++ b/modules/skparagraph/include/DartTypes.h @@ -133,6 +133,13 @@ enum TextHeightBehavior { kDisableAll = 0x1 | 0x2, }; +enum class LineMetricStyle : uint8_t { + // Use ascent, descent, etc from a fixed baseline. + Typographic, + // Use ascent, descent, etc like css with the leading split and with height adjustments + CSS +}; + } // namespace textlayout } // namespace skia diff --git a/modules/skparagraph/src/ParagraphImpl.cpp b/modules/skparagraph/src/ParagraphImpl.cpp index 92180cf34e..285ceaadcc 100644 --- a/modules/skparagraph/src/ParagraphImpl.cpp +++ b/modules/skparagraph/src/ParagraphImpl.cpp @@ -447,6 +447,7 @@ void ParagraphImpl::breakShapedTextIntoLines(SkScalar maxWidth) { fHeight += delta; // Shift all the lines up for (auto& line : fLines) { + if (line.isFirstLine()) continue; line.shiftVertically(delta); } } diff --git a/modules/skparagraph/src/Run.cpp b/modules/skparagraph/src/Run.cpp index 265cee8171..d2ca6f7981 100644 --- a/modules/skparagraph/src/Run.cpp +++ b/modules/skparagraph/src/Run.cpp @@ -168,7 +168,7 @@ void Run::iterateThroughClustersInTextOrder(const ClusterTextVisitor& visitor) { fClusterStart + cluster, fClusterStart + nextCluster, this->calculateWidth(start, glyph, glyph == size()), - this->calculateHeight()); + this->calculateHeight(LineMetricStyle::CSS, LineMetricStyle::CSS)); start = glyph; cluster = nextCluster; @@ -188,7 +188,7 @@ void Run::iterateThroughClustersInTextOrder(const ClusterTextVisitor& visitor) { fClusterStart + cluster, fClusterStart + nextCluster, this->calculateWidth(start, glyph, glyph == 0), - this->calculateHeight()); + this->calculateHeight(LineMetricStyle::CSS, LineMetricStyle::CSS)); glyph = start; cluster = nextCluster; diff --git a/modules/skparagraph/src/Run.h b/modules/skparagraph/src/Run.h index 34d9b47c1f..df61276e3e 100644 --- a/modules/skparagraph/src/Run.h +++ b/modules/skparagraph/src/Run.h @@ -111,7 +111,7 @@ public: bool leftToRight() const { return fBidiLevel % 2 == 0; } TextDirection getTextDirection() const { return leftToRight() ? TextDirection::kLtr : TextDirection::kRtl; } size_t index() const { return fIndex; } - SkScalar lineHeight() const { return fHeightMultiplier; } + SkScalar heightMultiplier() const { return fHeightMultiplier; } PlaceholderStyle* placeholderStyle() const; bool isPlaceholder() const { return fPlaceholderIndex != std::numeric_limits::max(); } size_t clusterIndex(size_t pos) const { return fClusterIndexes[pos]; } @@ -136,11 +136,12 @@ public: SkScalar addSpacesEvenly(SkScalar space, Cluster* cluster); void shift(const Cluster* cluster, SkScalar offset); - SkScalar calculateHeight() const { - if (fHeightMultiplier == 0) { - return fFontMetrics.fDescent - fFontMetrics.fAscent; - } - return fHeightMultiplier * fFont.getSize(); + SkScalar calculateHeight(LineMetricStyle ascentStyle, LineMetricStyle descentStyle) const { + auto ascent = ascentStyle == LineMetricStyle::Typographic ? this->ascent() + : this->correctAscent(); + auto descent = descentStyle == LineMetricStyle::Typographic ? this->descent() + : this->correctDescent(); + return descent - ascent; } SkScalar calculateWidth(size_t start, size_t end, bool clip) const; @@ -403,8 +404,9 @@ public: } } - SkScalar runTop(const Run* run) const { - return fLeading / 2 - fAscent + run->ascent() + delta(); + SkScalar runTop(const Run* run, LineMetricStyle ascentStyle) const { + return fLeading / 2 - fAscent + + (ascentStyle == LineMetricStyle::Typographic ? run->ascent() : run->correctAscent()) + delta(); } SkScalar height() const { @@ -424,6 +426,7 @@ public: private: friend class TextWrapper; + friend class TextLine; SkScalar fAscent; SkScalar fDescent; diff --git a/modules/skparagraph/src/TextLine.cpp b/modules/skparagraph/src/TextLine.cpp index 43ab570586..f363502ab7 100644 --- a/modules/skparagraph/src/TextLine.cpp +++ b/modules/skparagraph/src/TextLine.cpp @@ -82,7 +82,9 @@ TextLine::TextLine(ParagraphImpl* master, , fSizes(sizes) , fHasBackground(false) , fHasShadows(false) - , fHasDecorations(false) { + , fHasDecorations(false) + , fAscentStyle(LineMetricStyle::CSS) + , fDescentStyle(LineMetricStyle::CSS) { // Reorder visual runs auto& start = master->cluster(fGhostClusterRange.start); auto& end = master->cluster(fGhostClusterRange.end - 1); @@ -317,9 +319,13 @@ SkScalar TextLine::metricsWithoutMultiplier(TextHeightBehavior correction) { }); SkScalar delta = 0; if (correction == TextHeightBehavior::kDisableFirstAscent) { - delta += (this->fSizes.ascent() - result.ascent()); + delta += (this->fSizes.fAscent - result.fAscent); + this->fSizes.fAscent -= delta; + this->fAscentStyle = LineMetricStyle::Typographic; } else if (correction == TextHeightBehavior::kDisableLastDescent) { - delta -= (this->fSizes.descent() - result.descent()); + delta -= (this->fSizes.fDescent - result.fDescent); + this->fSizes.fDescent -= delta; + this->fDescentStyle = LineMetricStyle::Typographic; } fAdvance.fY += delta; return delta; @@ -545,7 +551,7 @@ Run* TextLine::shapeEllipsis(const SkString& ellipsis, Run* run) { SkString fEllipsis; }; - ShapeHandler handler(run->lineHeight(), ellipsis); + ShapeHandler handler(run->heightMultiplier(), ellipsis); std::unique_ptr shaper = SkShaper::MakeShapeDontWrapOrReorder(); SkASSERT_RELEASE(shaper != nullptr); shaper->shape(ellipsis.c_str(), ellipsis.size(), run->font(), true, @@ -568,22 +574,21 @@ TextLine::ClipContext TextLine::measureTextInsideOneRun(TextRange textRange, SkASSERT(textRange == run->textRange()); result.fTextShift = runOffsetInLine; result.clip = SkRect::MakeXYWH(runOffsetInLine, - sizes().runTop(run), + sizes().runTop(run, this->fAscentStyle), run->advance().fX, - run->calculateHeight()); + run->calculateHeight(this->fAscentStyle,this->fDescentStyle)); return result; } else if (run->isPlaceholder()) { if (SkScalarIsFinite(run->fFontMetrics.fAscent)) { result.clip = SkRect::MakeXYWH(runOffsetInLine, - sizes().runTop(run), + sizes().runTop(run, this->fAscentStyle), run->advance().fX, - run->calculateHeight()); + run->calculateHeight(this->fAscentStyle,this->fDescentStyle)); } else { result.clip = SkRect::MakeXYWH(runOffsetInLine, run->fFontMetrics.fAscent, run->advance().fX, 0); } return result; } - // Find [start:end] clusters for the text bool found; ClusterIndex startIndex; @@ -617,9 +622,9 @@ TextLine::ClipContext TextLine::measureTextInsideOneRun(TextRange textRange, // coming from letter spacing or word spacing or justification) result.clip = SkRect::MakeXYWH(0, - sizes().runTop(run), + sizes().runTop(run, this->fAscentStyle), run->calculateWidth(result.pos, result.pos + result.size, false), - run->calculateHeight()); + run->calculateHeight(this->fAscentStyle,this->fDescentStyle)); // Correct the width in case the text edges don't match clusters // TODO: This is where we get smart about selecting a part of a cluster @@ -908,27 +913,27 @@ void TextLine::getRectsForRange(TextRange textRange0, clip.fBottom = this->height(); clip.fTop = this->sizes().delta(); break; - case RectHeightStyle::kIncludeLineSpacingTop: - if (!isFirstLine()) { - clip.fTop -= this->sizes().runTop(context.run); - } - clip.fBottom -= this->sizes().runTop(context.run); - break; - case RectHeightStyle::kIncludeLineSpacingMiddle: - if (!isFirstLine()) { - clip.fTop -= this->sizes().runTop(context.run) / 2; + case RectHeightStyle::kIncludeLineSpacingTop: { + if (isFirstLine()) { + auto verticalShift = this->sizes().runTop(context.run, LineMetricStyle::Typographic); + clip.fTop += verticalShift; } + break; + } + case RectHeightStyle::kIncludeLineSpacingMiddle: { + auto verticalShift = this->sizes().runTop(context.run, LineMetricStyle::Typographic); + clip.fTop += isFirstLine() ? verticalShift : verticalShift / 2; + clip.fBottom += isLastLine() ? 0 : verticalShift / 2; + break; + } + case RectHeightStyle::kIncludeLineSpacingBottom: { + auto verticalShift = this->sizes().runTop(context.run, LineMetricStyle::Typographic); + clip.offset(0, verticalShift); if (isLastLine()) { - clip.fBottom -= this->sizes().runTop(context.run); - } else { - clip.fBottom -= this->sizes().runTop(context.run) / 2; + clip.fBottom -= verticalShift; } - break; - case RectHeightStyle::kIncludeLineSpacingBottom: - if (isLastLine()) { - clip.fBottom -= this->sizes().runTop(context.run); - } - break; + break; + } case RectHeightStyle::kStrut: { auto strutStyle = paragraphStyle.getStrutStyle(); if (strutStyle.getStrutEnabled() @@ -940,11 +945,15 @@ void TextLine::getRectsForRange(TextRange textRange0, } } break; - case RectHeightStyle::kTight: + case RectHeightStyle::kTight: { if (run->fHeightMultiplier > 0) { // This is a special case when we do not need to take in account this height multiplier - clip.fBottom = clip.fTop + clip.height() / run->fHeightMultiplier; + auto correctedHeight = clip.height() / run->fHeightMultiplier; + auto verticalShift = this->sizes().runTop(context.run, LineMetricStyle::Typographic); + clip.fTop += verticalShift; + clip.fBottom = clip.fTop + correctedHeight; } + } break; default: SkASSERT(false); @@ -1003,7 +1012,8 @@ void TextLine::getRectsForRange(TextRange textRange0, lastRun != nullptr && lastRun->placeholderStyle() == nullptr && context.run->placeholderStyle() == nullptr && - nearlyEqual(lastRun->lineHeight(), context.run->lineHeight()) && + nearlyEqual(lastRun->heightMultiplier(), + context.run->heightMultiplier()) && lastRun->font() == context.run->font()) { auto& lastBox = boxes.back(); diff --git a/modules/skparagraph/src/TextLine.h b/modules/skparagraph/src/TextLine.h index ea2ea21750..da54147e60 100644 --- a/modules/skparagraph/src/TextLine.h +++ b/modules/skparagraph/src/TextLine.h @@ -136,6 +136,9 @@ private: bool fHasBackground; bool fHasShadows; bool fHasDecorations; + + LineMetricStyle fAscentStyle; + LineMetricStyle fDescentStyle; }; } // namespace textlayout } // namespace skia diff --git a/samplecode/SampleParagraph.cpp b/samplecode/SampleParagraph.cpp index 908c6620d6..6208ea7d15 100644 --- a/samplecode/SampleParagraph.cpp +++ b/samplecode/SampleParagraph.cpp @@ -2636,6 +2636,101 @@ private: typedef Sample INHERITED; }; +class ParagraphView41 : public ParagraphView_Base { +protected: + SkString name() override { return SkString("Paragraph41"); } + + void onDrawContent(SkCanvas* canvas) override { + + canvas->drawColor(SK_ColorWHITE); + + auto fontCollection = sk_make_sp(); + fontCollection->setDefaultFontManager(SkFontMgr::RefDefault()); + fontCollection->enableFontFallback(); + + SkPaint line; + line.setColor(SK_ColorRED); + line.setStyle(SkPaint::kStroke_Style); + line.setAntiAlias(true); + line.setStrokeWidth(1); + + auto draw = [&](SkColor color, TextHeightBehavior thb) { + ParagraphStyle paragraph_style; + paragraph_style.setTextHeightBehavior(thb); + ParagraphBuilderImpl builder(paragraph_style, fontCollection); + TextStyle text_style; + text_style.setColor(SK_ColorBLACK); + SkPaint paint; + paint.setColor(color); + text_style.setBackgroundColor(paint); + text_style.setFontFamilies({SkString("Roboto")}); + text_style.setFontSize(20); + text_style.setHeight(5); + text_style.setHeightOverride(true); + builder.pushStyle(text_style); + builder.addText("World domination is such an ugly phrase - I prefer to call it world optimisation"); + auto paragraph = builder.Build(); + paragraph->layout(width()); + paragraph->paint(canvas, 0, 0); + canvas->drawLine(0, paragraph->getHeight(), paragraph->getMaxWidth(), paragraph->getHeight(), line); + canvas->translate(0, paragraph->getHeight()); + }; + + draw(SK_ColorLTGRAY, TextHeightBehavior::kDisableFirstAscent); + draw(SK_ColorYELLOW, TextHeightBehavior::kDisableLastDescent); + draw(SK_ColorGRAY, TextHeightBehavior::kDisableAll); + + } + +private: + typedef Sample INHERITED; +}; + +class ParagraphView42 : public ParagraphView_Base { +protected: + SkString name() override { return SkString("Paragraph42"); } + + void onDrawContent(SkCanvas* canvas) override { + + SkString text("Atwater Peel Sherbrooke Bonaventure\nhi\nwasssup!"); + canvas->drawColor(SK_ColorWHITE); + + auto fontCollection = sk_make_sp(GetResourcePath("fonts").c_str(), true, true); + + ParagraphStyle paragraph_style; + ParagraphBuilderImpl builder(paragraph_style, fontCollection); + TextStyle text_style; + text_style.setColor(SK_ColorBLACK); + text_style.setFontFamilies({SkString("Ahem")}); + text_style.setFontSize(16); + text_style.setHeight(4); + text_style.setHeightOverride(true); + builder.pushStyle(text_style); + builder.addText(text.c_str()); + auto paragraph = builder.Build(); + paragraph->layout(width()); + + auto boxes = paragraph->getRectsForRange(0, 7, RectHeightStyle::kIncludeLineSpacingTop, RectWidthStyle::kMax); + for (auto& box : boxes) { + SkPaint paint; + paint.setColor(SK_ColorGRAY); + canvas->drawRect(box.rect, paint); + } + + auto boxes2 = paragraph->getRectsForRange(0, 7, RectHeightStyle::kTight, RectWidthStyle::kMax); + for (auto& box : boxes2) { + SkPaint paint; + paint.setColor(SK_ColorRED); + canvas->drawRect(box.rect, paint); + } + + paragraph->paint(canvas, 0, 0); + } + +private: + typedef Sample INHERITED; +}; +// ////////////////////////////////////////////////////////////////////////////// DEF_SAMPLE(return new ParagraphView1();) DEF_SAMPLE(return new ParagraphView2();) @@ -2675,3 +2770,5 @@ DEF_SAMPLE(return new ParagraphView36();) DEF_SAMPLE(return new ParagraphView37();) DEF_SAMPLE(return new ParagraphView38();) DEF_SAMPLE(return new ParagraphView39();) +DEF_SAMPLE(return new ParagraphView41();) +DEF_SAMPLE(return new ParagraphView42();)