Reland "[skottie] Use metrics for Shaper vertical alignment"
This is a reland of 084fa1b52f30c1b1e349b809deda5f240b90b039
Original change's description:
> [skottie] Use metrics for Shaper vertical alignment
>
> Relying on visual bounds yields incorrect results in some cases (e.g.
> leading/trailing empty lines).
>
> Update the vertical alignment logic to use metrics instead:
>
> - track the first line ascent and last line descent
> - compute content height as
>
> first_ascent + last_descent + line_height * (line_count - 1)
>
> - relocate Result::computeBounds() to the unit test (only user)
>
> Empirically, this causes top-alignment to be less snug (likely due to
> ascent slack in the tested fonts).
>
> Bug: skia:9098
> Change-Id: Ib92bf907af8889d6b0d0fda22ef41a2cc8b50901
> Reviewed-on: https://skia-review.googlesource.com/c/skia/+/220656
> Reviewed-by: Mike Reed <reed@google.com>
> Commit-Queue: Florin Malita <fmalita@chromium.org>
TBR=
No-Try: true
Bug: skia:9098
Change-Id: Iaba53968840749e35b9c3ed04b15d6e2cda55e72
Reviewed-on: https://skia-review.googlesource.com/c/skia/+/220916
Reviewed-by: Florin Malita <fmalita@chromium.org>
Commit-Queue: Florin Malita <fmalita@chromium.org>
diff --git a/modules/skottie/src/SkottieTest.cpp b/modules/skottie/src/SkottieTest.cpp
index 61b08ad..72069d5 100644
--- a/modules/skottie/src/SkottieTest.cpp
+++ b/modules/skottie/src/SkottieTest.cpp
@@ -12,6 +12,7 @@
#include "modules/skottie/include/Skottie.h"
#include "modules/skottie/include/SkottieProperty.h"
#include "modules/skottie/src/text/SkottieShaper.h"
+#include "src/core/SkTextBlobPriv.h"
#include "tests/Test.h"
@@ -222,6 +223,42 @@
REPORTER_ASSERT(reporter, std::get<2>(observer->fMarkers[1]) == 0.75f);
}
+static SkRect ComputeBlobBounds(const sk_sp<SkTextBlob>& blob) {
+ auto bounds = SkRect::MakeEmpty();
+
+ if (!blob) {
+ return bounds;
+ }
+
+ SkAutoSTArray<16, SkRect> glyphBounds;
+
+ SkTextBlobRunIterator it(blob.get());
+
+ for (SkTextBlobRunIterator it(blob.get()); !it.done(); it.next()) {
+ glyphBounds.reset(SkToInt(it.glyphCount()));
+ it.font().getBounds(it.glyphs(), it.glyphCount(), glyphBounds.get(), nullptr);
+
+ SkASSERT(it.positioning() == SkTextBlobRunIterator::kFull_Positioning);
+ for (uint32_t i = 0; i < it.glyphCount(); ++i) {
+ bounds.join(glyphBounds[i].makeOffset(it.pos()[i * 2 ],
+ it.pos()[i * 2 + 1]));
+ }
+ }
+
+ return bounds;
+}
+
+static SkRect ComputeShapeResultBounds(const skottie::Shaper::Result& res) {
+ auto bounds = SkRect::MakeEmpty();
+
+ for (const auto& fragment : res.fFragments) {
+ bounds.join(ComputeBlobBounds(fragment.fBlob).makeOffset(fragment.fPos.x(),
+ fragment.fPos.y()));
+ }
+
+ return bounds;
+}
+
DEF_TEST(Skottie_Shaper_HAlign, reporter) {
auto typeface = SkTypeface::MakeDefault();
REPORTER_ASSERT(reporter, typeface);
@@ -266,7 +303,7 @@
REPORTER_ASSERT(reporter, shape_result.fFragments.size() == 1ul);
REPORTER_ASSERT(reporter, shape_result.fFragments[0].fBlob);
- const auto shape_bounds = shape_result.computeBounds();
+ const auto shape_bounds = ComputeShapeResultBounds(shape_result);
REPORTER_ASSERT(reporter, !shape_bounds.isEmpty());
const auto expected_l = text_point.x() - shape_bounds.width() * talign.l_selector;
@@ -296,9 +333,9 @@
// These gross tolerances are required for the test to pass on NativeFonts bots.
// Might be worth investigating why we need so much slack.
{ 5, 2.0f },
- { 10, 2.0f },
- { 15, 2.4f },
- { 25, 4.4f },
+ { 10, 4.0f },
+ { 15, 5.5f },
+ { 25, 8.0f },
};
struct {
@@ -329,7 +366,7 @@
REPORTER_ASSERT(reporter, shape_result.fFragments.size() == 1ul);
REPORTER_ASSERT(reporter, shape_result.fFragments[0].fBlob);
- const auto shape_bounds = shape_result.computeBounds();
+ const auto shape_bounds = ComputeShapeResultBounds(shape_result);
REPORTER_ASSERT(reporter, !shape_bounds.isEmpty());
const auto v_diff = text_box.height() - shape_bounds.height();
diff --git a/modules/skottie/src/text/SkottieShaper.cpp b/modules/skottie/src/text/SkottieShaper.cpp
index 172d213..bd2921f 100644
--- a/modules/skottie/src/text/SkottieShaper.cpp
+++ b/modules/skottie/src/text/SkottieShaper.cpp
@@ -7,10 +7,10 @@
#include "modules/skottie/src/text/SkottieShaper.h"
+#include "include/core/SkFontMetrics.h"
#include "include/core/SkTextBlob.h"
#include "include/private/SkTemplates.h"
#include "modules/skshaper/include/SkShaper.h"
-#include "src/core/SkTextBlobPriv.h"
#include "src/utils/SkUTF.h"
#include <limits.h>
@@ -18,31 +18,6 @@
namespace skottie {
namespace {
-SkRect ComputeBlobBounds(const sk_sp<SkTextBlob>& blob) {
- auto bounds = SkRect::MakeEmpty();
-
- if (!blob) {
- return bounds;
- }
-
- SkAutoSTArray<16, SkRect> glyphBounds;
-
- SkTextBlobRunIterator it(blob.get());
-
- for (SkTextBlobRunIterator it(blob.get()); !it.done(); it.next()) {
- glyphBounds.reset(SkToInt(it.glyphCount()));
- it.font().getBounds(it.glyphs(), it.glyphCount(), glyphBounds.get(), nullptr);
-
- SkASSERT(it.positioning() == SkTextBlobRunIterator::kFull_Positioning);
- for (uint32_t i = 0; i < it.glyphCount(); ++i) {
- bounds.join(glyphBounds[i].makeOffset(it.pos()[i * 2 ],
- it.pos()[i * 2 + 1]));
- }
- }
-
- return bounds;
-}
-
// Helper for interfacing with SkShaper: buffers shaper-fed runs and performs
// per-line position adjustments (for external line breaking, horizontal alignment, etc).
class BlobMaker final : public SkShaper::RunHandler {
@@ -68,10 +43,19 @@
fCurrentPosition = fOffset;
fPendingLineAdvance = { 0, 0 };
+
+ fLastLineDescent = 0;
}
void runInfo(const RunInfo& info) override {
fPendingLineAdvance += info.fAdvance;
+
+ SkFontMetrics metrics;
+ info.fFont.getMetrics(&metrics);
+ if (!fLineCount) {
+ fFirstLineAscent = SkTMin(fFirstLineAscent, metrics.fAscent);
+ }
+ fLastLineDescent = SkTMax(fLastLineDescent, metrics.fDescent);
}
void commitRunInfo() override {}
@@ -116,14 +100,14 @@
fLineGlyphs.get() + run_offset,
fLinePos.get() + run_offset,
fLineClusters.get() + run_offset,
- fLineIndex);
+ fLineCount);
run_offset += rec.fGlyphCount;
}
- fLineIndex++;
+ fLineCount++;
}
- Shaper::Result finalize() {
+ Shaper::Result finalize(float* shaped_height) {
if (!(fDesc.fFlags & Shaper::Flags::kFragmentGlyphs)) {
// All glyphs are pending in a single blob.
SkASSERT(fResult.fFragments.empty());
@@ -131,21 +115,26 @@
fResult.fFragments.push_back({fBuilder.make(), {fBox.x(), fBox.y()}, 0, false});
}
- // By default, first line is vertical-aligned on a baseline of 0.
+ // By default, first line is vertically-aligned on a baseline of 0.
+ // The content height considered for vertical alignment is the distance between the first
+ // line top (ascent) to the last line bottom (descent).
+ const auto content_height = fLastLineDescent - fFirstLineAscent +
+ fDesc.fLineHeight * (fLineCount > 0 ? fLineCount - 1 : 0ul);
+
// Perform additional adjustments based on VAlign.
float v_offset = 0;
switch (fDesc.fVAlign) {
case Shaper::VAlign::kTop:
- v_offset = fBox.fTop - fResult.computeBounds().fTop;
+ v_offset = -fFirstLineAscent;
break;
case Shaper::VAlign::kTopBaseline:
// Default behavior.
break;
case Shaper::VAlign::kCenter:
- v_offset = fBox.centerY() - fResult.computeBounds().centerY();
+ v_offset = -fFirstLineAscent + (fBox.height() - content_height) * 0.5f;
break;
case Shaper::VAlign::kBottom:
- v_offset = fBox.fBottom - fResult.computeBounds().fBottom;
+ v_offset = -fFirstLineAscent + (fBox.height() - content_height);
break;
case Shaper::VAlign::kResizeToFit:
SkASSERT(false);
@@ -158,6 +147,10 @@
}
}
+ if (shaped_height) {
+ *shaped_height = content_height;
+ }
+
return std::move(fResult);
}
@@ -248,14 +241,17 @@
SkPoint fCurrentPosition{ 0, 0 };
SkPoint fOffset{ 0, 0 };
SkVector fPendingLineAdvance{ 0, 0 };
- uint32_t fLineIndex = 0;
+ uint32_t fLineCount = 0;
+ float fFirstLineAscent = 0,
+ fLastLineDescent = 0;
const char* fUTF8 = nullptr; // only valid during shapeLine() calls
Shaper::Result fResult;
};
-Shaper::Result ShapeImpl(const SkString& txt, const Shaper::TextDesc& desc, const SkRect& box) {
+Shaper::Result ShapeImpl(const SkString& txt, const Shaper::TextDesc& desc,
+ const SkRect& box, float* shaped_height = nullptr) {
SkASSERT(desc.fVAlign != Shaper::VAlign::kResizeToFit);
const auto& is_line_break = [](SkUnichar uch) {
@@ -276,7 +272,7 @@
}
blobMaker.shapeLine(line_start, ptr);
- return blobMaker.finalize();
+ return blobMaker.finalize(shaped_height);
}
Shaper::Result ShapeToFit(const SkString& txt, const Shaper::TextDesc& orig_desc,
@@ -307,8 +303,8 @@
desc.fTextSize = try_scale * orig_desc.fTextSize;
desc.fLineHeight = try_scale * orig_desc.fLineHeight;
- auto res = ShapeImpl(txt, desc, box);
- auto res_height = res.computeBounds().height();
+ float res_height = 0;
+ auto res = ShapeImpl(txt, desc, box, &res_height);
if (res_height > box.height()) {
out_scale = try_scale;
@@ -348,15 +344,4 @@
: ShapeImpl(txt, desc, box);
}
-SkRect Shaper::Result::computeBounds() const {
- auto bounds = SkRect::MakeEmpty();
-
- for (const auto& fragment : fFragments) {
- bounds.join(ComputeBlobBounds(fragment.fBlob).makeOffset(fragment.fPos.x(),
- fragment.fPos.y()));
- }
-
- return bounds;
-}
-
} // namespace skottie
diff --git a/modules/skottie/src/text/SkottieShaper.h b/modules/skottie/src/text/SkottieShaper.h
index e419e6d..a919e43 100644
--- a/modules/skottie/src/text/SkottieShaper.h
+++ b/modules/skottie/src/text/SkottieShaper.h
@@ -33,8 +33,6 @@
struct Result {
std::vector<Fragment> fFragments;
-
- SkRect computeBounds() const;
};
enum class VAlign : uint8_t {