Implementing baseline shift for JetBrains

Bug: skia:12390
Change-Id: I90d99e1ca18c1274ac53ab35a3f63b716d26cb5d
Reviewed-on: https://skia-review.googlesource.com/c/skia/+/457097
Reviewed-by: Ben Wagner <bungeman@google.com>
Commit-Queue: Julia Lavrova <jlavrova@google.com>
diff --git a/modules/skparagraph/include/TextStyle.h b/modules/skparagraph/include/TextStyle.h
index e576414..00487c7 100644
--- a/modules/skparagraph/include/TextStyle.h
+++ b/modules/skparagraph/include/TextStyle.h
@@ -215,6 +215,9 @@
         fFontFamilies = std::move(families);
     }
 
+    SkScalar getBaselineShift() const { return fBaselineShift; }
+    void setBaselineShift(SkScalar baselineShift) { fBaselineShift = baselineShift; }
+
     void setHeight(SkScalar height) { fHeight = height; }
     SkScalar getHeight() const { return fHeightOverride ? fHeight : 0; }
 
@@ -265,6 +268,7 @@
     SkScalar fFontSize = 14.0;
     SkScalar fHeight = 1.0;
     bool fHeightOverride = false;
+    SkScalar fBaselineShift = 0.0f;
     // true: half leading.
     // false: scale ascent/descent with fHeight.
     bool fHalfLeading = false;
diff --git a/modules/skparagraph/samples/SampleParagraph.cpp b/modules/skparagraph/samples/SampleParagraph.cpp
index e9e6acb..8817d5f 100644
--- a/modules/skparagraph/samples/SampleParagraph.cpp
+++ b/modules/skparagraph/samples/SampleParagraph.cpp
@@ -3611,6 +3611,151 @@
 private:
     using INHERITED = Sample;
 };
+
+// Baseline shift
+class ParagraphView63 : public ParagraphView_Base {
+protected:
+    SkString name() override { return SkString("ParagraphView63"); }
+
+    void onDrawContent(SkCanvas* canvas) override {
+
+        canvas->drawColor(SK_ColorWHITE);
+        auto fontCollection = getFontCollection();
+        fontCollection->setDefaultFontManager(SkFontMgr::RefDefault());
+        fontCollection->enableFontFallback();
+        TextStyle roboto;
+        roboto.setColor(SK_ColorBLACK);
+        roboto.setFontFamilies({SkString("Roboto")});
+        roboto.setFontSize(20.0f);
+
+        TextStyle assyrian;
+        assyrian.setColor(SK_ColorRED);
+        assyrian.setFontFamilies({SkString("Assyrian")});
+        assyrian.setFontSize(40.0f);
+
+        ParagraphStyle paragraph_style;
+        paragraph_style.setTextStyle(roboto);
+        ParagraphBuilderImpl builder(paragraph_style, fontCollection);
+
+        roboto.setBaselineShift(0.0f);
+        builder.pushStyle(roboto);
+        builder.addText("Notice that the line height increased on the lines with ");
+        assyrian.setBaselineShift(.0f);
+        builder.pushStyle(assyrian);
+        builder.addText("baseline shifts:\n");
+
+        roboto.setBaselineShift(0.0f);
+        builder.pushStyle(roboto);
+        builder.addText("Zero baseline shift text ");
+
+        assyrian.setBaselineShift(-20.0f);
+        builder.pushStyle(assyrian);
+        builder.addText("Up20");
+
+        roboto.setBaselineShift(-40.0f);
+        builder.pushStyle(roboto);
+        builder.addText("Up40");
+
+        assyrian.setBaselineShift(-60.0f);
+        builder.pushStyle(assyrian);
+        builder.addText("Up60");
+
+        roboto.setBaselineShift(-40.0f);
+        builder.pushStyle(roboto);
+        builder.addText("Up40");
+
+        assyrian.setBaselineShift(-20.0f);
+        builder.pushStyle(assyrian);
+        builder.addText("Up20");
+
+        roboto.setBaselineShift(-0.0f);
+        builder.pushStyle(roboto);
+        builder.addText(" Zero baseline shift text\n");
+
+        assyrian.addShadow(TextShadow(SK_ColorGREEN, SkPoint::Make(5, 5), 2));
+        assyrian.setDecorationStyle(TextDecorationStyle::kSolid);
+        assyrian.setDecoration(TextDecoration::kUnderline);
+        assyrian.setDecorationColor(SK_ColorBLUE);
+
+        roboto.setBaselineShift(0.0f);
+        builder.pushStyle(roboto);
+        builder.addText("Notice that shadows and decorations are shifted if there is a text with ");
+        assyrian.setBaselineShift(.0f);
+        builder.pushStyle(assyrian);
+        builder.addText("baseline shifts:\n");
+
+        assyrian.setDecoration(TextDecoration::kNoDecoration);
+
+        roboto.setBaselineShift(0.0f);
+        builder.pushStyle(roboto);
+        builder.addText("Zero baseline shift text ");
+
+        assyrian.setBaselineShift(20.0f);
+        builder.pushStyle(assyrian);
+        builder.addText("Down20");
+
+        roboto.setBaselineShift(40.0f);
+        builder.pushStyle(roboto);
+        builder.addText("Down40");
+
+        assyrian.setBaselineShift(60.0f);
+        builder.pushStyle(assyrian);
+        builder.addText("Down60");
+
+        roboto.setBaselineShift(40.0f);
+        builder.pushStyle(roboto);
+        builder.addText("Down40");
+
+        assyrian.setBaselineShift(20.0f);
+        builder.pushStyle(assyrian);
+        builder.addText("Down20");
+
+        roboto.setBaselineShift(0.0f);
+        builder.pushStyle(roboto);
+        builder.addText(" Zero baseline shift text\n");
+
+        assyrian.resetShadows();
+        assyrian.setDecorationStyle(TextDecorationStyle::kSolid);
+        assyrian.setDecoration(TextDecoration::kUnderline);
+        assyrian.setDecorationColor(SK_ColorBLUE);
+
+        roboto.setBaselineShift(0.0f);
+        builder.pushStyle(roboto);
+        builder.addText("Zero baseline shift text ");
+
+        assyrian.setBaselineShift(-20.0f);
+        builder.pushStyle(assyrian);
+        builder.addText("Up20");
+
+        roboto.setBaselineShift(-40.0f);
+        builder.pushStyle(roboto);
+        builder.addText("Up40");
+
+        assyrian.setBaselineShift(-60.0f);
+        builder.pushStyle(assyrian);
+        builder.addText("Up60");
+
+        roboto.setBaselineShift(-40.0f);
+        builder.pushStyle(roboto);
+        builder.addText("Up40");
+
+        assyrian.setBaselineShift(-20.0f);
+        builder.pushStyle(assyrian);
+        builder.addText("Up20");
+
+        roboto.setBaselineShift(-0.0f);
+        builder.pushStyle(roboto);
+        builder.addText(" Zero baseline shift text");
+
+        auto paragraph = builder.Build();
+        paragraph->layout(SK_ScalarInfinity);
+        paragraph->paint(canvas, 0, 0);
+    }
+
+private:
+    using INHERITED = Sample;
+};
+
 }  // namespace
 
 //////////////////////////////////////////////////////////////////////////////
@@ -3674,3 +3819,4 @@
 DEF_SAMPLE(return new ParagraphView60();)
 DEF_SAMPLE(return new ParagraphView61();)
 DEF_SAMPLE(return new ParagraphView62();)
+DEF_SAMPLE(return new ParagraphView63();)
diff --git a/modules/skparagraph/src/OneLineShaper.cpp b/modules/skparagraph/src/OneLineShaper.cpp
index 4066a65..b2a1279 100644
--- a/modules/skparagraph/src/OneLineShaper.cpp
+++ b/modules/skparagraph/src/OneLineShaper.cpp
@@ -210,6 +210,7 @@
                     run->fClusterStart,
                     height,
                     block.fStyle.getHalfLeading(),
+                    block.fStyle.getBaselineShift(),
                     this->fParagraph->fRuns.count(),
                     advanceX
                 );
@@ -606,6 +607,7 @@
                                        runInfo,
                                        placeholder.fRange.start,
                                        0.0f,
+                                       0.0f,
                                        false,
                                        fParagraph->fRuns.count(),
                                        advanceX);
@@ -643,6 +645,7 @@
             // Start from the beginning (hoping that it's a simple case one block - one run)
             fHeight = block.fStyle.getHeightOverride() ? block.fStyle.getHeight() : 0;
             fUseHalfLeading = block.fStyle.getHalfLeading();
+            fBaselineShift = block.fStyle.getBaselineShift();
             fAdvance = SkVector::Make(advanceX, 0);
             fCurrentText = block.fRange;
             fUnresolvedBlocks.emplace_back(RunBlock(block.fRange));
diff --git a/modules/skparagraph/src/OneLineShaper.h b/modules/skparagraph/src/OneLineShaper.h
index 929e4ad..a746f95 100644
--- a/modules/skparagraph/src/OneLineShaper.h
+++ b/modules/skparagraph/src/OneLineShaper.h
@@ -19,6 +19,7 @@
         : fParagraph(paragraph)
         , fHeight(0.0f)
         , fUseHalfLeading(false)
+        , fBaselineShift(0.0f)
         , fAdvance(SkPoint::Make(0.0f, 0.0f))
         , fUnresolvedGlyphs(0)
         , fUniqueRunId(paragraph->fRuns.size()){ }
@@ -83,6 +84,7 @@
                                            fCurrentText.start,
                                            fHeight,
                                            fUseHalfLeading,
+                                           fBaselineShift,
                                            ++fUniqueRunId,
                                            fAdvance.fX);
         return fCurrentRun->newRunBuffer();
@@ -104,6 +106,7 @@
     TextRange fCurrentText;
     SkScalar fHeight;
     bool fUseHalfLeading;
+    SkScalar fBaselineShift;
     SkVector fAdvance;
     size_t fUnresolvedGlyphs;
     size_t fUniqueRunId;
diff --git a/modules/skparagraph/src/ParagraphCache.cpp b/modules/skparagraph/src/ParagraphCache.cpp
index d9065e0..14c1977 100644
--- a/modules/skparagraph/src/ParagraphCache.cpp
+++ b/modules/skparagraph/src/ParagraphCache.cpp
@@ -93,6 +93,7 @@
         hash = mix(hash, SkGoodHash()(relax(ts.fStyle.getWordSpacing())));
         hash = mix(hash, SkGoodHash()(ts.fStyle.getLocale()));
         hash = mix(hash, SkGoodHash()(relax(ts.fStyle.getHeight())));
+        hash = mix(hash, SkGoodHash()(relax(ts.fStyle.getBaselineShift())));
         for (auto& ff : ts.fStyle.getFontFamilies()) {
             hash = mix(hash, SkGoodHash()(ff));
         }
diff --git a/modules/skparagraph/src/Run.cpp b/modules/skparagraph/src/Run.cpp
index 88f7c29..e50f412 100644
--- a/modules/skparagraph/src/Run.cpp
+++ b/modules/skparagraph/src/Run.cpp
@@ -19,6 +19,7 @@
          size_t firstChar,
          SkScalar heightMultiplier,
          bool useHalfLeading,
+         SkScalar baselineShift,
          size_t index,
          SkScalar offsetX)
     : fOwner(owner)
@@ -28,6 +29,7 @@
     , fClusterStart(firstChar)
     , fHeightMultiplier(heightMultiplier)
     , fUseHalfLeading(useHalfLeading)
+    , fBaselineShift(baselineShift)
 {
     fBidiLevel = info.fBidiLevel;
     fAdvance = info.fAdvance;
@@ -69,6 +71,9 @@
         fCorrectAscent *= multiplier;
         fCorrectDescent *= multiplier;
     }
+    // If we shift the baseline we need to make sure the shifted text fits the line
+    fCorrectAscent += fBaselineShift;
+    fCorrectDescent += fBaselineShift;
 }
 
 SkShaper::RunHandler::Buffer Run::newRunBuffer() {
diff --git a/modules/skparagraph/src/Run.h b/modules/skparagraph/src/Run.h
index 2eee6d0..517ced8 100644
--- a/modules/skparagraph/src/Run.h
+++ b/modules/skparagraph/src/Run.h
@@ -59,6 +59,7 @@
         size_t firstChar,
         SkScalar heightMultiplier,
         bool useHalfLeading,
+        SkScalar baselineShift,
         size_t index,
         SkScalar shiftX);
     Run(const Run&) = default;
@@ -85,11 +86,11 @@
         return SkVector::Make(fAdvance.fX, fFontMetrics.fDescent - fFontMetrics.fAscent + fFontMetrics.fLeading);
     }
     SkVector offset() const { return fOffset; }
-    SkScalar ascent() const { return fFontMetrics.fAscent; }
-    SkScalar descent() const { return fFontMetrics.fDescent; }
+    SkScalar ascent() const { return fFontMetrics.fAscent + fBaselineShift; }
+    SkScalar descent() const { return fFontMetrics.fDescent + fBaselineShift; }
     SkScalar leading() const { return fFontMetrics.fLeading; }
-    SkScalar correctAscent() const { return fCorrectAscent; }
-    SkScalar correctDescent() const { return fCorrectDescent; }
+    SkScalar correctAscent() const { return fCorrectAscent + fBaselineShift; }
+    SkScalar correctDescent() const { return fCorrectDescent + fBaselineShift; }
     SkScalar correctLeading() const { return fCorrectLeading; }
     const SkFont& font() const { return fFont; }
     bool leftToRight() const { return fBidiLevel % 2 == 0; }
@@ -97,6 +98,7 @@
     size_t index() const { return fIndex; }
     SkScalar heightMultiplier() const { return fHeightMultiplier; }
     bool useHalfLeading() const { return fUseHalfLeading; }
+    SkScalar baselineShift() const { return fBaselineShift; }
     PlaceholderStyle* placeholderStyle() const;
     bool isPlaceholder() const { return fPlaceholderIndex != std::numeric_limits<size_t>::max(); }
     size_t clusterIndex(size_t pos) const { return fClusterIndexes[pos]; }
@@ -193,6 +195,7 @@
     SkFontMetrics fFontMetrics;
     const SkScalar fHeightMultiplier;
     const bool fUseHalfLeading;
+    const SkScalar fBaselineShift;
     SkScalar fCorrectAscent;
     SkScalar fCorrectDescent;
     SkScalar fCorrectLeading;
diff --git a/modules/skparagraph/src/TextLine.cpp b/modules/skparagraph/src/TextLine.cpp
index 752c51b..02b6564 100644
--- a/modules/skparagraph/src/TextLine.cpp
+++ b/modules/skparagraph/src/TextLine.cpp
@@ -337,7 +337,8 @@
         record.fClipRect = context.clip.makeOffset(this->offset());
     }
 
-    SkScalar correctedBaseline = SkScalarFloorToScalar(this->baseline() + 0.5);
+    SkASSERT(nearlyEqual(context.run->baselineShift(), style.getBaselineShift()));
+    SkScalar correctedBaseline = SkScalarFloorToScalar(this->baseline() + style.getBaselineShift() +  0.5);
     record.fBlob = builder.make();
     if (record.fBlob != nullptr) {
         record.fBounds.joinPossiblyEmptyRect(record.fBlob->bounds());
@@ -366,7 +367,7 @@
 
 SkRect TextLine::paintShadow(SkCanvas* canvas, SkScalar x, SkScalar y, TextRange textRange, const TextStyle& style, const ClipContext& context) const {
 
-    SkScalar correctedBaseline = SkScalarFloorToScalar(this->baseline() + 0.5);
+    SkScalar correctedBaseline = SkScalarFloorToScalar(this->baseline() + style.getBaselineShift() + 0.5);
     SkRect shadowBounds = SkRect::MakeEmpty();
 
     for (TextShadow shadow : style.getShadows()) {
@@ -413,9 +414,9 @@
 void TextLine::paintDecorations(SkCanvas* canvas, SkScalar x, SkScalar y, TextRange textRange, const TextStyle& style, const ClipContext& context) const {
 
     SkAutoCanvasRestore acr(canvas, true);
-    canvas->translate(x + this->offset().fX, y + this->offset().fY);
+    canvas->translate(x + this->offset().fX, y + this->offset().fY + style.getBaselineShift());
     Decorations decorations;
-    SkScalar correctedBaseline = SkScalarFloorToScalar(this->baseline() + 0.5);
+    SkScalar correctedBaseline = SkScalarFloorToScalar(this->baseline() + style.getBaselineShift() + 0.5);
     decorations.paint(canvas, style, context, correctedBaseline);
 }
 
@@ -541,8 +542,8 @@
 
     class ShapeHandler final : public SkShaper::RunHandler {
     public:
-        ShapeHandler(SkScalar lineHeight, bool useHalfLeading, const SkString& ellipsis)
-            : fRun(nullptr), fLineHeight(lineHeight), fUseHalfLeading(useHalfLeading), fEllipsis(ellipsis) {}
+        ShapeHandler(SkScalar lineHeight, bool useHalfLeading, SkScalar baselineShift, const SkString& ellipsis)
+            : fRun(nullptr), fLineHeight(lineHeight), fUseHalfLeading(useHalfLeading), fBaselineShift(baselineShift), fEllipsis(ellipsis) {}
         Run* run() & { return fRun.get(); }
         std::unique_ptr<Run> run() && { return std::move(fRun); }
 
@@ -555,7 +556,7 @@
 
         Buffer runBuffer(const RunInfo& info) override {
             SkASSERT(!fRun);
-            fRun = std::make_unique<Run>(nullptr, info, 0, fLineHeight, fUseHalfLeading, 0, 0);
+            fRun = std::make_unique<Run>(nullptr, info, 0, fLineHeight, fUseHalfLeading, fBaselineShift, 0, 0);
             return fRun->newRunBuffer();
         }
 
@@ -571,10 +572,11 @@
         std::unique_ptr<Run> fRun;
         SkScalar fLineHeight;
         bool fUseHalfLeading;
+        SkScalar fBaselineShift;
         SkString fEllipsis;
     };
 
-    ShapeHandler handler(run.heightMultiplier(), run.useHalfLeading(), ellipsis);
+    ShapeHandler handler(run.heightMultiplier(), run.useHalfLeading(), run.baselineShift(), ellipsis);
     std::unique_ptr<SkShaper> shaper = SkShaper::MakeShapeDontWrapOrReorder();
     SkASSERT_RELEASE(shaper != nullptr);
     shaper->shape(ellipsis.c_str(), ellipsis.size(), run.font(), true,
diff --git a/modules/skparagraph/src/TextStyle.cpp b/modules/skparagraph/src/TextStyle.cpp
index 49c2f3b..1f56733 100644
--- a/modules/skparagraph/src/TextStyle.cpp
+++ b/modules/skparagraph/src/TextStyle.cpp
@@ -21,6 +21,7 @@
     fIsPlaceholder = placeholder;
     fFontFeatures = other.fFontFeatures;
     fHalfLeading = other.fHalfLeading;
+    fBaselineShift = other.fBaselineShift;
 }
 
 bool TextStyle::equals(const TextStyle& other) const {
@@ -53,6 +54,9 @@
     if (fHalfLeading != other.fHalfLeading) {
         return false;
     }
+    if (fBaselineShift != other.fBaselineShift) {
+        return false;
+    }
     if (fFontSize != other.fFontSize) {
         return false;
     }
@@ -94,6 +98,7 @@
            nearlyEqual(fLetterSpacing, that.fLetterSpacing) &&
            nearlyEqual(fWordSpacing, that.fWordSpacing) &&
            nearlyEqual(fHeight, that.fHeight) &&
+           nearlyEqual(fBaselineShift, that.fBaselineShift) &&
            nearlyEqual(fFontSize, that.fFontSize) &&
            fLocale == that.fLocale;
 }
@@ -139,7 +144,9 @@
                    fFontFamilies == other.fFontFamilies &&
                    fFontSize == other.fFontSize &&
                    fHeight == other.fHeight &&
-                   fHalfLeading == other.fHalfLeading;
+                   fHeight == other.fHeight &&
+                   fHalfLeading == other.fHalfLeading &&
+                   fBaselineShift == other.fBaselineShift;
         default:
             SkASSERT(false);
             return false;
@@ -162,6 +169,9 @@
         metrics->fAscent = (metrics->fAscent - metrics->fLeading / 2);
         metrics->fDescent = (metrics->fDescent + metrics->fLeading / 2);
     }
+    // If we shift the baseline we need to make sure the shifted text fits the line
+    metrics->fAscent += fBaselineShift;
+    metrics->fDescent += fBaselineShift;
 }
 
 bool PlaceholderStyle::equals(const PlaceholderStyle& other) const {