Cache should work with INF values

Change-Id: I1ae8d95bb85d28fdce9e0cf270583f0224e4dfed
Bug: skia:9874
Reviewed-on: https://skia-review.googlesource.com/c/skia/+/268938
Commit-Queue: Julia Lavrova <jlavrova@google.com>
Reviewed-by: Ben Wagner <bungeman@google.com>
diff --git a/modules/skparagraph/include/ParagraphStyle.h b/modules/skparagraph/include/ParagraphStyle.h
index 528c30b..542a8fa 100644
--- a/modules/skparagraph/include/ParagraphStyle.h
+++ b/modules/skparagraph/include/ParagraphStyle.h
@@ -37,6 +37,17 @@
     bool getHeightOverride() const { return fHeightOverride; }
     void setHeightOverride(bool v) { fHeightOverride = v; }
 
+    bool operator==(const StrutStyle& rhs) const {
+        return this->fEnabled == rhs.fEnabled &&
+               this->fHeightOverride == rhs.fHeightOverride &&
+               this->fForceHeight == rhs.fForceHeight &&
+               nearlyEqual(this->fLeading, rhs.fLeading) &&
+               nearlyEqual(this->fHeight, rhs.fHeight) &&
+               nearlyEqual(this->fFontSize, rhs.fFontSize) &&
+               this->fFontStyle == rhs.fFontStyle &&
+               this->fFontFamilies == rhs.fFontFamilies;
+    }
+
 private:
 
     std::vector<SkString> fFontFamilies;
diff --git a/modules/skparagraph/include/TextStyle.h b/modules/skparagraph/include/TextStyle.h
index 454dc38..08509d7 100644
--- a/modules/skparagraph/include/TextStyle.h
+++ b/modules/skparagraph/include/TextStyle.h
@@ -18,6 +18,21 @@
 namespace skia {
 namespace textlayout {
 
+static inline bool nearlyZero(SkScalar x, SkScalar tolerance = SK_ScalarNearlyZero) {
+    if (SkScalarIsFinite(x)) {
+        return SkScalarNearlyZero(x, tolerance);
+    }
+    return false;
+}
+
+static inline bool nearlyEqual(SkScalar x, SkScalar y, SkScalar tolerance = SK_ScalarNearlyZero) {
+    if (SkScalarIsFinite(x) && SkScalarIsFinite(x)) {
+        return SkScalarNearlyEqual(x, y, tolerance);
+    }
+    // Inf == Inf, anything else is false
+    return x == y;
+}
+
 // Multiple decorations can be applied at once. Ex: Underline and overline is
 // (0x1 | 0x2)
 enum TextDecoration {
@@ -100,7 +115,13 @@
 };
 
 struct PlaceholderStyle {
-    PlaceholderStyle() { }
+    PlaceholderStyle()
+            : fWidth(0)
+            , fHeight(0)
+            , fAlignment(PlaceholderAlignment::kBaseline)
+            , fBaseline(TextBaseline::kAlphabetic)
+            , fBaselineOffset(0) {}
+
     PlaceholderStyle(SkScalar width, SkScalar height, PlaceholderAlignment alignment,
                      TextBaseline baseline, SkScalar offset)
             : fWidth(width)
@@ -111,13 +132,10 @@
 
     bool equals(const PlaceholderStyle& other) const;
 
-    SkScalar fWidth = 0;
-    SkScalar fHeight = 0;
-
+    SkScalar fWidth;
+    SkScalar fHeight;
     PlaceholderAlignment fAlignment;
-
     TextBaseline fBaseline;
-
     // Distance from the top edge of the rect to the baseline position. This
     // baseline will be aligned against the alphabetic baseline of the surrounding
     // text.
@@ -126,7 +144,7 @@
     // small or negative values will cause the rect to be positioned underneath
     // the line. When baseline == height, the bottom edge of the rect will rest on
     // the alphabetic baseline.
-    SkScalar fBaselineOffset = 0;
+    SkScalar fBaselineOffset;
 };
 
 class TextStyle {
diff --git a/modules/skparagraph/src/OneLineShaper.cpp b/modules/skparagraph/src/OneLineShaper.cpp
index 22b7c72..b28a80a 100644
--- a/modules/skparagraph/src/OneLineShaper.cpp
+++ b/modules/skparagraph/src/OneLineShaper.cpp
@@ -483,7 +483,7 @@
         run.fPositions[0] = { advanceX, 0 };
         run.fOffsets[0] = {0, 0};
         run.fClusterIndexes[0] = 0;
-        run.fPlaceholder = &placeholder.fStyle;
+        run.fPlaceholderIndex = &placeholder - fParagraph->fPlaceholders.begin();
         advanceX += placeholder.fStyle.fWidth;
     }
     return true;
diff --git a/modules/skparagraph/src/ParagraphCache.cpp b/modules/skparagraph/src/ParagraphCache.cpp
index 522ef7d..f98c496 100644
--- a/modules/skparagraph/src/ParagraphCache.cpp
+++ b/modules/skparagraph/src/ParagraphCache.cpp
@@ -8,8 +8,12 @@
 namespace {
     SkScalar relax(SkScalar a) {
         // This rounding is done to match Flutter tests. Must be removed..
-      auto threshold = SkIntToScalar(1 << 12);
-      return SkScalarRoundToScalar(a * threshold)/threshold;
+        if (SkScalarIsFinite(a)) {
+          auto threshold = SkIntToScalar(1 << 12);
+          return SkScalarRoundToScalar(a * threshold)/threshold;
+        } else {
+          return a;
+        }
     }
 }
 
@@ -31,19 +35,15 @@
 public:
     ParagraphCacheValue(const ParagraphImpl* paragraph)
         : fKey(ParagraphCacheKey(paragraph))
-        , fInternalState(paragraph->fState)
         , fRuns(paragraph->fRuns)
-        , fClusters(paragraph->fClusters)
-        , fUnresolvedGlyphs(paragraph->fUnresolvedGlyphs){ }
+        , fClusters(paragraph->fClusters) { }
 
     // Input == key
     ParagraphCacheKey fKey;
 
-    // Shaped results:
-    InternalState fInternalState;
+    // Shaped results
     SkTArray<Run, false> fRuns;
     SkTArray<Cluster, true> fClusters;
-    size_t fUnresolvedGlyphs;
 };
 
 uint32_t ParagraphCache::KeyHash::mix(uint32_t hash, uint32_t data) const {
@@ -56,21 +56,20 @@
 uint32_t ParagraphCache::KeyHash::operator()(const ParagraphCacheKey& key) const {
     uint32_t hash = 0;
     for (auto& ph : key.fPlaceholders) {
-        if (&ph == &key.fPlaceholders.back()) {
-            // Skip the last "dummy" placeholder
-            break;
+        if (ph.fRange.width() == 0) {
+            continue;
         }
         hash = mix(hash, SkGoodHash()(ph.fRange.start));
         hash = mix(hash, SkGoodHash()(ph.fRange.end));
-        hash = mix(hash, SkGoodHash()(relax(ph.fStyle.fBaselineOffset)));
-        hash = mix(hash, SkGoodHash()(ph.fStyle.fBaseline));
+        hash = mix(hash, SkGoodHash()(relax(ph.fStyle.fHeight)));
+        hash = mix(hash, SkGoodHash()(relax(ph.fStyle.fWidth)));
         hash = mix(hash, SkGoodHash()(ph.fStyle.fAlignment));
+        hash = mix(hash, SkGoodHash()(ph.fStyle.fBaseline));
         if (ph.fStyle.fAlignment == PlaceholderAlignment::kBaseline) {
             hash = mix(hash, SkGoodHash()(relax(ph.fStyle.fBaselineOffset)));
         }
-        hash = mix(hash, SkGoodHash()(relax(ph.fStyle.fHeight)));
-        hash = mix(hash, SkGoodHash()(relax(ph.fStyle.fWidth)));
     }
+
     for (auto& ts : key.fTextStyles) {
         if (ts.fStyle.isPlaceholder()) {
             continue;
@@ -79,22 +78,34 @@
         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()(ts.fRange));
         for (auto& ff : ts.fStyle.getFontFamilies()) {
             hash = mix(hash, SkGoodHash()(ff));
         }
         for (auto& ff : ts.fStyle.getFontFeatures()) {
-            hash = mix(hash, SkGoodHash()(ff));
+            hash = mix(hash, SkGoodHash()(ff.fValue));
+            hash = mix(hash, SkGoodHash()(ff.fName));
         }
         hash = mix(hash, SkGoodHash()(ts.fStyle.getFontStyle()));
         hash = mix(hash, SkGoodHash()(relax(ts.fStyle.getFontSize())));
-        hash = mix(hash, SkGoodHash()(ts.fRange.start));
-        hash = mix(hash, SkGoodHash()(ts.fRange.end));
+        hash = mix(hash, SkGoodHash()(ts.fRange));
     }
 
     hash = mix(hash, SkGoodHash()(relax(key.fParagraphStyle.getHeight())));
     hash = mix(hash, SkGoodHash()(key.fParagraphStyle.getTextDirection()));
 
+    auto& strutStyle = key.fParagraphStyle.getStrutStyle();
+    if (strutStyle.getStrutEnabled()) {
+        hash = mix(hash, SkGoodHash()(relax(strutStyle.getHeight())));
+        hash = mix(hash, SkGoodHash()(relax(strutStyle.getLeading())));
+        hash = mix(hash, SkGoodHash()(relax(strutStyle.getFontSize())));
+        hash = mix(hash, SkGoodHash()(strutStyle.getHeightOverride()));
+        hash = mix(hash, SkGoodHash()(strutStyle.getFontStyle()));
+        hash = mix(hash, SkGoodHash()(strutStyle.getForceStrutHeight()));
+        for (auto& ff : strutStyle.getFontFamilies()) {
+            hash = mix(hash, SkGoodHash()(ff));
+        }
+    }
+
     hash = mix(hash, SkGoodHash()(key.fText));
     return hash;
 }
@@ -114,13 +125,17 @@
     }
 
     // There is no need to compare default paragraph styles - they are included into fTextStyles
-    if (!SkScalarNearlyEqual(a.fParagraphStyle.getHeight(), b.fParagraphStyle.getHeight())) {
+    if (!nearlyEqual(a.fParagraphStyle.getHeight(), b.fParagraphStyle.getHeight())) {
         return false;
     }
     if (a.fParagraphStyle.getTextDirection() != b.fParagraphStyle.getTextDirection()) {
         return false;
     }
 
+    if (!(a.fParagraphStyle.getStrutStyle() == b.fParagraphStyle.getStrutStyle())) {
+        return false;
+    }
+
     for (size_t i = 0; i < a.fTextStyles.size(); ++i) {
         auto& tsa = a.fTextStyles[i];
         auto& tsb = b.fTextStyles[i];
@@ -137,9 +152,12 @@
             return false;
         }
     }
-    for (size_t i = 0; i < a.fPlaceholders.size() - 1; ++i) {
+    for (size_t i = 0; i < a.fPlaceholders.size(); ++i) {
         auto& tsa = a.fPlaceholders[i];
         auto& tsb = b.fPlaceholders[i];
+        if (tsa.fRange.width() == 0 && tsb.fRange.width() == 0) {
+            continue;
+        }
         if (!(tsa.fStyle.equals(tsb.fStyle))) {
             return false;
         }
@@ -175,7 +193,6 @@
 
 void ParagraphCache::updateFrom(const ParagraphImpl* paragraph, Entry* entry) {
 
-    entry->fValue->fInternalState = paragraph->state();
     for (size_t i = 0; i < paragraph->fRuns.size(); ++i) {
         auto& run = paragraph->fRuns[i];
         if (run.fSpaced) {
@@ -185,6 +202,7 @@
 }
 
 void ParagraphCache::updateTo(ParagraphImpl* paragraph, const Entry* entry) {
+
     paragraph->fRuns.reset();
     paragraph->fRuns = entry->fValue->fRuns;
     for (auto& run : paragraph->fRuns) {
@@ -197,8 +215,7 @@
         cluster.setMaster(paragraph);
     }
 
-    paragraph->fState = entry->fValue->fInternalState;
-    paragraph->fUnresolvedGlyphs = entry->fValue->fUnresolvedGlyphs;
+    paragraph->fState = kMarked;
 }
 
 void ParagraphCache::printStatistics() {
diff --git a/modules/skparagraph/src/ParagraphImpl.cpp b/modules/skparagraph/src/ParagraphImpl.cpp
index 5d6459b..66a20de 100644
--- a/modules/skparagraph/src/ParagraphImpl.cpp
+++ b/modules/skparagraph/src/ParagraphImpl.cpp
@@ -414,7 +414,7 @@
                     }
                 }
 
-                fLongestLine = SkTMax(fLongestLine, SkScalarNearlyZero(advance.fX) ? widthWithSpaces : advance.fX);
+                fLongestLine = SkTMax(fLongestLine, nearlyZero(advance.fX) ? widthWithSpaces : advance.fX);
             });
     fHeight = textWrapper.height();
     fWidth = maxWidth;
@@ -427,9 +427,17 @@
 
 void ParagraphImpl::formatLines(SkScalar maxWidth) {
     auto effectiveAlign = fParagraphStyle.effective_align();
+
+    if (!SkScalarIsFinite(maxWidth) && effectiveAlign != TextAlign::kLeft) {
+        // Special case: clean all text in case of maxWidth == INF & align != left
+        // We had to go through shaping though because we need all the measurement numbers
+        fLines.reset();
+        return;
+    }
     if (effectiveAlign == TextAlign::kJustify) {
         this->resetRunShifts();
     }
+
     for (auto& line : fLines) {
         if (&line == &fLines.back() && effectiveAlign == TextAlign::kJustify) {
             effectiveAlign = line.assumedTextAlign();
@@ -768,16 +776,16 @@
                     bool mergedBoxes = false;
                     if (!results.empty() &&
                         lastRun != nullptr &&
-                        lastRun->placeholder() == nullptr &&
-                        context.run->placeholder() == nullptr &&
-                        SkScalarNearlyEqual(lastRun->lineHeight(), context.run->lineHeight()) &&
+                        lastRun->placeholderStyle() == nullptr &&
+                        context.run->placeholderStyle() == nullptr &&
+                        nearlyEqual(lastRun->lineHeight(), context.run->lineHeight()) &&
                         lastRun->font() == context.run->font())
                     {
                         auto& lastBox = results.back();
-                        if (SkScalarNearlyEqual(lastBox.rect.fTop, clip.fTop) &&
-                            SkScalarNearlyEqual(lastBox.rect.fBottom, clip.fBottom) &&
-                                (SkScalarNearlyEqual(lastBox.rect.fLeft, clip.fRight) ||
-                                 SkScalarNearlyEqual(lastBox.rect.fRight, clip.fLeft)))
+                        if (nearlyEqual(lastBox.rect.fTop, clip.fTop) &&
+                            nearlyEqual(lastBox.rect.fBottom, clip.fBottom) &&
+                                (nearlyEqual(lastBox.rect.fLeft, clip.fRight) ||
+                                 nearlyEqual(lastBox.rect.fRight, clip.fLeft)))
                         {
                             lastBox.rect.fLeft = SkTMin(lastBox.rect.fLeft, clip.fLeft);
                             lastBox.rect.fRight = SkTMax(lastBox.rect.fRight, clip.fRight);
@@ -792,7 +800,7 @@
                     results.emplace_back(
                         clip, context.run->leftToRight() ? TextDirection::kLtr : TextDirection::kRtl);
                 }
-                if (!SkScalarNearlyZero(trailingSpaces.width()) && !merge(trailingSpaces)) {
+                if (!nearlyZero(trailingSpaces.width()) && !merge(trailingSpaces)) {
                     results.emplace_back(trailingSpaces, paragraphTextDirection);
                 }
 
@@ -843,7 +851,7 @@
                   auto context =
                           line.measureTextInsideOneRun(textRange, run, runOffset, 0, true, false);
                   *width = context.clip.width();
-                  if (run->placeholder() == nullptr) {
+                  if (run->placeholderStyle() == nullptr) {
                       return true;
                   }
                   if (run->textRange().width() == 0) {
@@ -1120,7 +1128,9 @@
                                       fEmptyMetrics.leading() * multiplier);
     }
 
-    fStrutMetrics.updateLineMetrics(fEmptyMetrics);
+    if (fParagraphStyle.getStrutStyle().getStrutEnabled()) {
+        fStrutMetrics.updateLineMetrics(fEmptyMetrics);
+    }
 }
 
 void ParagraphImpl::updateText(size_t from, SkString text) {
diff --git a/modules/skparagraph/src/ParagraphImpl.h b/modules/skparagraph/src/ParagraphImpl.h
index fd39e10..2f4d2cd 100644
--- a/modules/skparagraph/src/ParagraphImpl.h
+++ b/modules/skparagraph/src/ParagraphImpl.h
@@ -135,6 +135,9 @@
     SkSpan<Block> styles() {
         return SkSpan<Block>(fTextStyles.data(), fTextStyles.size());
     }
+    SkSpan<Placeholder> placeholders() {
+        return SkSpan<Placeholder>(fPlaceholders.data(), fPlaceholders.size());
+    }
     SkSpan<TextLine> lines() { return SkSpan<TextLine>(fLines.data(), fLines.size()); }
     const ParagraphStyle& paragraphStyle() const { return fParagraphStyle; }
     SkSpan<Cluster> clusters() { return SkSpan<Cluster>(fClusters.begin(), fClusters.size()); }
diff --git a/modules/skparagraph/src/Run.cpp b/modules/skparagraph/src/Run.cpp
index 8d9ad08..bc1618e 100644
--- a/modules/skparagraph/src/Run.cpp
+++ b/modules/skparagraph/src/Run.cpp
@@ -47,7 +47,7 @@
     fOffsets[info.glyphCount] = { 0, 0};
     fClusterIndexes[info.glyphCount] = this->leftToRight() ? info.utf8Range.end() : info.utf8Range.begin();
     fEllipsis = false;
-    fPlaceholder = nullptr;
+    fPlaceholderIndex = std::numeric_limits<size_t>::max();
 }
 
 SkShaper::RunHandler::Buffer Run::newRunBuffer() {
@@ -238,9 +238,11 @@
 
 void Run::updateMetrics(InternalLineMetrics* endlineMetrics) {
 
+    SkASSERT(isPlaceholder());
+    auto placeholderStyle = this->placeholderStyle();
     // Difference between the placeholder baseline and the line bottom
     SkScalar baselineAdjustment = 0;
-    switch (fPlaceholder->fBaseline) {
+    switch (placeholderStyle->fBaseline) {
         case TextBaseline::kAlphabetic:
             break;
 
@@ -249,11 +251,11 @@
             break;
     }
 
-    auto height = fPlaceholder->fHeight;
-    auto offset = fPlaceholder->fBaselineOffset;
+    auto height = placeholderStyle->fHeight;
+    auto offset = placeholderStyle->fBaselineOffset;
 
     fFontMetrics.fLeading = 0;
-    switch (fPlaceholder->fAlignment) {
+    switch (placeholderStyle->fAlignment) {
         case PlaceholderAlignment::kBaseline:
             fFontMetrics.fAscent = baselineAdjustment - offset;
             fFontMetrics.fDescent = baselineAdjustment + height - offset;
@@ -339,6 +341,14 @@
     return posX(pos) + fShifts[pos] + fMaster->posShift(fIndex, pos);
 }
 
+PlaceholderStyle* Run::placeholderStyle() const {
+    if (isPlaceholder()) {
+        return &fMaster->placeholders()[fPlaceholderIndex].fStyle;
+    } else {
+        return nullptr;
+    }
+}
+
 Run* Cluster::run() const {
     if (fRunIndex >= fMaster->runs().size()) {
         return nullptr;
diff --git a/modules/skparagraph/src/Run.h b/modules/skparagraph/src/Run.h
index 498e74d..3cc5d38 100644
--- a/modules/skparagraph/src/Run.h
+++ b/modules/skparagraph/src/Run.h
@@ -104,8 +104,8 @@
     bool leftToRight() const { return fBidiLevel % 2 == 0; }
     size_t index() const { return fIndex; }
     SkScalar lineHeight() const { return fHeightMultiplier; }
-    PlaceholderStyle* placeholder() const { return fPlaceholder; }
-    bool isPlaceholder() const { return fPlaceholder != nullptr; }
+    PlaceholderStyle* placeholderStyle() const;
+    bool isPlaceholder() const { return fPlaceholderIndex != std::numeric_limits<size_t>::max(); }
     size_t clusterIndex(size_t pos) const { return fClusterIndexes[pos]; }
     SkScalar positionX(size_t pos) const;
 
@@ -177,7 +177,7 @@
     SkFont fFont;
     SkFontMetrics fFontMetrics;
     SkScalar fHeightMultiplier;
-    PlaceholderStyle* fPlaceholder;
+    size_t fPlaceholderIndex;
     bool fEllipsis;
     size_t fIndex;
     uint8_t fBidiLevel;
diff --git a/modules/skparagraph/src/TextLine.cpp b/modules/skparagraph/src/TextLine.cpp
index 39e8651..25af76d 100644
--- a/modules/skparagraph/src/TextLine.cpp
+++ b/modules/skparagraph/src/TextLine.cpp
@@ -34,7 +34,7 @@
     // It has to be relative to be useful
     auto base = SkTMax(SkScalarAbs(a), SkScalarAbs(b));
     auto diff = SkScalarAbs(a - b);
-    if (SkScalarNearlyZero(base) || diff / base < 0.001f) {
+    if (nearlyZero(base) || diff / base < 0.001f) {
         return 0;
     }
 
@@ -223,7 +223,7 @@
     this->iterateThroughVisualRuns(false,
             [textCanvas, this]
             (const Run* run, SkScalar runOffsetInLine, TextRange textRange, SkScalar* runWidthInLine) {
-            if (run->placeholder() != nullptr) {
+            if (run->placeholderStyle() != nullptr) {
                 *runWidthInLine = run->advance().fX;
                 return true;
             }
@@ -300,7 +300,7 @@
 
 void TextLine::paintText(SkCanvas* canvas, TextRange textRange, const TextStyle& style, const ClipContext& context) const {
 
-    if (context.run->placeholder() != nullptr) {
+    if (context.run->placeholderStyle() != nullptr) {
         return;
     }
 
@@ -578,7 +578,7 @@
         return true;
     });
 
-    SkAssertResult(SkScalarNearlyEqual(shift, maxWidth - textLen));
+    SkAssertResult(nearlyEqual(shift, maxWidth - textLen));
     SkASSERT(whitespacePatches == 0);
 
     this->fWidthWithSpaces += ghostShift;
@@ -644,7 +644,7 @@
         void commitRunBuffer(const RunInfo& info) override {
             fRun->fAdvance.fX = info.fAdvance.fX;
             fRun->fAdvance.fY = fRun->advance().fY;
-            fRun->fPlaceholder = nullptr;
+            fRun->fPlaceholderIndex = std::numeric_limits<size_t>::max();
             fRun->fEllipsis = true;
         }
 
@@ -673,7 +673,7 @@
                                                         bool limitToClusters) const {
     ClipContext result = { run, 0, run->size(), 0, SkRect::MakeEmpty(), false };
 
-    if (run->placeholder() != nullptr || run->fEllipsis) {
+    if (run->placeholderStyle() != nullptr || run->fEllipsis) {
         // Both ellipsis and placeholders can only be measured as one glyph
         SkASSERT(textRange == run->textRange());
         result.fTextShift = runOffsetInLine;
@@ -931,7 +931,7 @@
     this->iterateThroughVisualRuns(false,
         [this, &result]
         (const Run* run, SkScalar runOffsetInLine, TextRange textRange, SkScalar* runWidthInLine) {
-        if (run->placeholder() != nullptr) {
+        if (run->placeholderStyle() != nullptr) {
             *runWidthInLine = run->advance().fX;
             return true;
         }
diff --git a/modules/skparagraph/src/TextStyle.cpp b/modules/skparagraph/src/TextStyle.cpp
index e3d645c..d4ce181 100644
--- a/modules/skparagraph/src/TextStyle.cpp
+++ b/modules/skparagraph/src/TextStyle.cpp
@@ -108,10 +108,10 @@
            fFontStyle == that.fFontStyle &&
            fFontFamilies == that.fFontFamilies &&
            fFontFeatures == that.fFontFeatures &&
-           SkScalarNearlyEqual(fLetterSpacing, that.fLetterSpacing) &&
-           SkScalarNearlyEqual(fWordSpacing, that.fWordSpacing) &&
-           SkScalarNearlyEqual(fHeight, that.fHeight) &&
-           SkScalarNearlyEqual(fFontSize, that.fFontSize) &&
+           nearlyEqual(fLetterSpacing, that.fLetterSpacing) &&
+           nearlyEqual(fWordSpacing, that.fWordSpacing) &&
+           nearlyEqual(fHeight, that.fHeight) &&
+           nearlyEqual(fFontSize, that.fFontSize) &&
            fLocale == that.fLocale;
 }
 
@@ -180,12 +180,12 @@
 }
 
 bool PlaceholderStyle::equals(const PlaceholderStyle& other) const {
-    return SkScalarNearlyEqual(fWidth, other.fWidth) &&
-           SkScalarNearlyEqual(fHeight, other.fHeight) &&
+    return nearlyEqual(fWidth, other.fWidth) &&
+           nearlyEqual(fHeight, other.fHeight) &&
            fAlignment == other.fAlignment &&
            fBaseline == other.fBaseline &&
            (fAlignment != PlaceholderAlignment::kBaseline ||
-            SkScalarNearlyEqual(fBaselineOffset, other.fBaselineOffset));
+            nearlyEqual(fBaselineOffset, other.fBaselineOffset));
 }
 
 }  // namespace textlayout
diff --git a/modules/skparagraph/src/TextWrapper.cpp b/modules/skparagraph/src/TextWrapper.cpp
index c2891a6..8d3f163 100644
--- a/modules/skparagraph/src/TextWrapper.cpp
+++ b/modules/skparagraph/src/TextWrapper.cpp
@@ -220,7 +220,7 @@
                 continue;
             }
             lastRun = cluster->run();
-            if (lastRun->placeholder() != nullptr) {
+            if (lastRun->placeholderStyle() != nullptr) {
                 SkASSERT(lastRun->size() == 1);
                 // Update the placeholder metrics so we can get the placeholder positions later
                 // and the line metrics (to make sure the placeholder fits)
diff --git a/modules/skparagraph/src/TextWrapper.h b/modules/skparagraph/src/TextWrapper.h
index 0db41cc..ab24648 100644
--- a/modules/skparagraph/src/TextWrapper.h
+++ b/modules/skparagraph/src/TextWrapper.h
@@ -112,7 +112,7 @@
             if (fEnd.cluster() != nullptr &&
                 fEnd.cluster()->master() != nullptr &&
                 fEnd.cluster()->run() != nullptr &&
-                fEnd.cluster()->run()->placeholder() == nullptr &&
+                fEnd.cluster()->run()->placeholderStyle() == nullptr &&
                 fWidth > 0) {
                 fWidth -= (fEnd.cluster()->width() - fEnd.cluster()->trimmedWidth(fEnd.position()));
             }
diff --git a/samplecode/SampleParagraph.cpp b/samplecode/SampleParagraph.cpp
index 8141c14..9bccc41 100644
--- a/samplecode/SampleParagraph.cpp
+++ b/samplecode/SampleParagraph.cpp
@@ -1869,27 +1869,61 @@
 
     void onDrawContent(SkCanvas* canvas) override {
         canvas->drawColor(SK_ColorWHITE);
-
-        const char* text =  "Overflowing endorsement that has a large lengthy text and is a lot longer than expected";
+/*
+ * Shell: ParagraphStyle: 1.000000 1
+Shell: Strut enabled: 0 1.000000 14.000000 400 5 0
+Shell: Font Families: 0
+Shell: DefaultTextStyle: 16.000000 500 5 0
+Shell: Font Families: 1 Roboto
+Shell: Font Features: 0
+Shell: TextStyle#0: [0:22) 16.000000 500 5 0
+Shell: Font Families: 1 Roboto
+Shell: Font Features: 0
+Shell: TextStyle#1: [25:49) 16.000000 500 5 0
+Shell: Font Families: 1 Roboto
+Shell: Font Features: 0
+Shell: Placeholder#0: [22:25) 32.000000 32.000000 32.000000 0 5
+Shell: Placeholder#1: [49:52) 19.000000 41.000000 19.000000 0 4
+Shell: Placeholder#2: [52:52) 0.000000 0.000000 0.000000 0 5
+Shell: layout('Go to device settings  and set up a passcode. ', 280.000000): 280.000000 * 38.000000
+ */
+        auto fontCollection = getFontCollection();
+        //fontCollection->getParagraphCache()->turnOn(false);
+        const char* text1 =  "Go to device settings ";
+        const char* text2 = "and set up a passcode.";
         ParagraphStyle paragraph_style;
-        paragraph_style.setEllipsis(u"\u2026");
-        paragraph_style.setMaxLines(std::numeric_limits<size_t>::max());
-        ParagraphBuilderImpl builder(paragraph_style, getFontCollection());
+        StrutStyle strut_style;
+        strut_style.setStrutEnabled(false);
+        strut_style.setFontSize(14);
+        strut_style.setForceStrutHeight(false);
+        strut_style.setHeight(14);
+        paragraph_style.setStrutStyle(strut_style);
         TextStyle text_style;
         text_style.setColor(SK_ColorBLACK);
-        text_style.setFontFamilies({SkString("Google Sans")});
-        text_style.setFontSize(20);
-        builder.pushStyle(text_style);
-        builder.addText(text);
-        auto paragraph = builder.Build();
-        paragraph->layout(594.0f);
-        paragraph->paint(canvas, 0, 0);
-        canvas->translate(0, 200);
-        paragraph->layout(std::numeric_limits<SkScalar>::max());
-        paragraph->paint(canvas, 0, 0);
-        canvas->translate(0, 200);
-        paragraph->layout(787.0f);
-        paragraph->paint(canvas, 0, 0);
+        text_style.setFontFamilies({SkString("Roboto")});
+        text_style.setFontSize(16);
+        PlaceholderStyle placeholder_style;
+        {
+            ParagraphBuilderImpl builder(paragraph_style, fontCollection);
+            builder.pushStyle(text_style);
+            builder.addText(text1);
+            placeholder_style.fHeight = 32;
+            placeholder_style.fWidth = 32;
+            placeholder_style.fBaselineOffset = 32;
+            placeholder_style.fBaseline = TextBaseline::kAlphabetic;
+            placeholder_style.fAlignment = PlaceholderAlignment::kMiddle;
+            builder.addPlaceholder(placeholder_style);
+            builder.addText(text2);
+            placeholder_style.fHeight = 19;
+            placeholder_style.fWidth = 41;
+            placeholder_style.fBaselineOffset = 19;
+            placeholder_style.fBaseline = TextBaseline::kAlphabetic;
+            placeholder_style.fAlignment = PlaceholderAlignment::kTop;
+            builder.addPlaceholder(placeholder_style);
+            auto paragraph = builder.Build();
+            paragraph->layout(280);
+            paragraph->paint(canvas, 0, 0);
+        }
     }
 
 private:
@@ -2089,5 +2123,5 @@
 DEF_SAMPLE(return new ParagraphView23();)
 DEF_SAMPLE(return new ParagraphView24();)
 DEF_SAMPLE(return new ParagraphView25();)
-DEF_SAMPLE(return new ParagraphView26();)
-DEF_SAMPLE(return new ParagraphView27();)
+//DEF_SAMPLE(return new ParagraphView26();)
+//DEF_SAMPLE(return new ParagraphView27();)
diff --git a/tests/SkParagraphTest.cpp b/tests/SkParagraphTest.cpp
index 9378773..1c9a38f 100644
--- a/tests/SkParagraphTest.cpp
+++ b/tests/SkParagraphTest.cpp
@@ -5223,3 +5223,55 @@
 		//std::this_thread::sleep_for(std::chrono::milliseconds(1000));
 	}
 };
+
+DEF_TEST(SkParagraph_FormattingInfinity, reporter) {
+    sk_sp<ResourceFontCollection> fontCollection = sk_make_sp<ResourceFontCollection>();
+    if (!fontCollection->fontsFound()) return;
+    fontCollection->setDefaultFontManager(SkFontMgr::RefDefault());
+    TestCanvas canvas("SkParagraph_FormattingInfinity.png");
+
+    const char* text = "Some text\nAnother line";
+
+    SkPaint paint;
+    paint.setAntiAlias(true);
+    paint.setColor(SK_ColorBLACK);
+
+    TextStyle textStyle;
+    textStyle.setForegroundColor(paint);
+    textStyle.setFontFamilies({ SkString("Roboto") });
+    ParagraphStyle paragraphStyle;
+    paragraphStyle.setTextStyle(textStyle);
+
+    auto draw = [&](const char* prefix, TextAlign textAlign) {
+        paragraphStyle.setTextAlign(textAlign);
+        ParagraphBuilderImpl builder(paragraphStyle, fontCollection);
+        builder.addText(text);
+        auto paragraph = builder.Build();
+        paragraph->layout(SK_ScalarInfinity);
+        paragraph->paint(canvas.get(), 0, 0);
+        canvas.get()->translate(0, 100);
+    };
+
+    draw("left", TextAlign::kLeft);
+    draw("right", TextAlign::kRight);
+    draw("center", TextAlign::kCenter);
+    draw("justify", TextAlign::kJustify);
+};
+
+DEF_TEST(SkParagraph_Infinity, reporter) {
+    SkASSERT(nearlyEqual(1, SK_ScalarInfinity) == false);
+    SkASSERT(nearlyEqual(1, SK_ScalarNegativeInfinity) == false);
+    SkASSERT(nearlyEqual(1, SK_ScalarNaN) == false);
+
+    SkASSERT(nearlyEqual(SK_ScalarInfinity, SK_ScalarInfinity) == true);
+    SkASSERT(nearlyEqual(SK_ScalarInfinity, SK_ScalarNegativeInfinity) == false);
+    SkASSERT(nearlyEqual(SK_ScalarInfinity, SK_ScalarNaN) == false);
+
+    SkASSERT(nearlyEqual(SK_ScalarNegativeInfinity, SK_ScalarInfinity) == false);
+    SkASSERT(nearlyEqual(SK_ScalarNegativeInfinity, SK_ScalarNegativeInfinity) == true);
+    SkASSERT(nearlyEqual(SK_ScalarNegativeInfinity, SK_ScalarNaN) == false);
+
+    SkASSERT(nearlyEqual(SK_ScalarNaN, SK_ScalarInfinity) == false);
+    SkASSERT(nearlyEqual(SK_ScalarNaN, SK_ScalarNegativeInfinity) == false);
+    SkASSERT(nearlyEqual(SK_ScalarNaN, SK_ScalarNaN) == false);
+};