Skip to content

Commit 084fa1b

Browse files
fmalitaSkia Commit-Bot
authored andcommitted
[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 <[email protected]> Commit-Queue: Florin Malita <[email protected]>
1 parent 187e8f3 commit 084fa1b

3 files changed

Lines changed: 76 additions & 56 deletions

File tree

modules/skottie/src/SkottieTest.cpp

Lines changed: 42 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
#include "modules/skottie/include/Skottie.h"
1313
#include "modules/skottie/include/SkottieProperty.h"
1414
#include "modules/skottie/src/text/SkottieShaper.h"
15+
#include "src/core/SkTextBlobPriv.h"
1516

1617
#include "tests/Test.h"
1718

@@ -222,6 +223,42 @@ DEF_TEST(Skottie_Annotations, reporter) {
222223
REPORTER_ASSERT(reporter, std::get<2>(observer->fMarkers[1]) == 0.75f);
223224
}
224225

226+
static SkRect ComputeBlobBounds(const sk_sp<SkTextBlob>& blob) {
227+
auto bounds = SkRect::MakeEmpty();
228+
229+
if (!blob) {
230+
return bounds;
231+
}
232+
233+
SkAutoSTArray<16, SkRect> glyphBounds;
234+
235+
SkTextBlobRunIterator it(blob.get());
236+
237+
for (SkTextBlobRunIterator it(blob.get()); !it.done(); it.next()) {
238+
glyphBounds.reset(SkToInt(it.glyphCount()));
239+
it.font().getBounds(it.glyphs(), it.glyphCount(), glyphBounds.get(), nullptr);
240+
241+
SkASSERT(it.positioning() == SkTextBlobRunIterator::kFull_Positioning);
242+
for (uint32_t i = 0; i < it.glyphCount(); ++i) {
243+
bounds.join(glyphBounds[i].makeOffset(it.pos()[i * 2 ],
244+
it.pos()[i * 2 + 1]));
245+
}
246+
}
247+
248+
return bounds;
249+
}
250+
251+
static SkRect ComputeShapeResultBounds(const skottie::Shaper::Result& res) {
252+
auto bounds = SkRect::MakeEmpty();
253+
254+
for (const auto& fragment : res.fFragments) {
255+
bounds.join(ComputeBlobBounds(fragment.fBlob).makeOffset(fragment.fPos.x(),
256+
fragment.fPos.y()));
257+
}
258+
259+
return bounds;
260+
}
261+
225262
DEF_TEST(Skottie_Shaper_HAlign, reporter) {
226263
auto typeface = SkTypeface::MakeDefault();
227264
REPORTER_ASSERT(reporter, typeface);
@@ -266,7 +303,7 @@ DEF_TEST(Skottie_Shaper_HAlign, reporter) {
266303
REPORTER_ASSERT(reporter, shape_result.fFragments.size() == 1ul);
267304
REPORTER_ASSERT(reporter, shape_result.fFragments[0].fBlob);
268305

269-
const auto shape_bounds = shape_result.computeBounds();
306+
const auto shape_bounds = ComputeShapeResultBounds(shape_result);
270307
REPORTER_ASSERT(reporter, !shape_bounds.isEmpty());
271308

272309
const auto expected_l = text_point.x() - shape_bounds.width() * talign.l_selector;
@@ -296,9 +333,9 @@ DEF_TEST(Skottie_Shaper_VAlign, reporter) {
296333
// These gross tolerances are required for the test to pass on NativeFonts bots.
297334
// Might be worth investigating why we need so much slack.
298335
{ 5, 2.0f },
299-
{ 10, 2.0f },
300-
{ 15, 2.4f },
301-
{ 25, 4.4f },
336+
{ 10, 4.0f },
337+
{ 15, 5.5f },
338+
{ 25, 8.0f },
302339
};
303340

304341
struct {
@@ -329,7 +366,7 @@ DEF_TEST(Skottie_Shaper_VAlign, reporter) {
329366
REPORTER_ASSERT(reporter, shape_result.fFragments.size() == 1ul);
330367
REPORTER_ASSERT(reporter, shape_result.fFragments[0].fBlob);
331368

332-
const auto shape_bounds = shape_result.computeBounds();
369+
const auto shape_bounds = ComputeShapeResultBounds(shape_result);
333370
REPORTER_ASSERT(reporter, !shape_bounds.isEmpty());
334371

335372
const auto v_diff = text_box.height() - shape_bounds.height();

modules/skottie/src/text/SkottieShaper.cpp

Lines changed: 34 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -7,42 +7,17 @@
77

88
#include "modules/skottie/src/text/SkottieShaper.h"
99

10+
#include "include/core/SkFontMetrics.h"
1011
#include "include/core/SkTextBlob.h"
1112
#include "include/private/SkTemplates.h"
1213
#include "modules/skshaper/include/SkShaper.h"
13-
#include "src/core/SkTextBlobPriv.h"
1414
#include "src/utils/SkUTF.h"
1515

1616
#include <limits.h>
1717

1818
namespace skottie {
1919
namespace {
2020

21-
SkRect ComputeBlobBounds(const sk_sp<SkTextBlob>& blob) {
22-
auto bounds = SkRect::MakeEmpty();
23-
24-
if (!blob) {
25-
return bounds;
26-
}
27-
28-
SkAutoSTArray<16, SkRect> glyphBounds;
29-
30-
SkTextBlobRunIterator it(blob.get());
31-
32-
for (SkTextBlobRunIterator it(blob.get()); !it.done(); it.next()) {
33-
glyphBounds.reset(SkToInt(it.glyphCount()));
34-
it.font().getBounds(it.glyphs(), it.glyphCount(), glyphBounds.get(), nullptr);
35-
36-
SkASSERT(it.positioning() == SkTextBlobRunIterator::kFull_Positioning);
37-
for (uint32_t i = 0; i < it.glyphCount(); ++i) {
38-
bounds.join(glyphBounds[i].makeOffset(it.pos()[i * 2 ],
39-
it.pos()[i * 2 + 1]));
40-
}
41-
}
42-
43-
return bounds;
44-
}
45-
4621
// Helper for interfacing with SkShaper: buffers shaper-fed runs and performs
4722
// per-line position adjustments (for external line breaking, horizontal alignment, etc).
4823
class BlobMaker final : public SkShaper::RunHandler {
@@ -68,10 +43,19 @@ class BlobMaker final : public SkShaper::RunHandler {
6843

6944
fCurrentPosition = fOffset;
7045
fPendingLineAdvance = { 0, 0 };
46+
47+
fLastLineDescent = 0;
7148
}
7249

7350
void runInfo(const RunInfo& info) override {
7451
fPendingLineAdvance += info.fAdvance;
52+
53+
SkFontMetrics metrics;
54+
info.fFont.getMetrics(&metrics);
55+
if (!fLineCount) {
56+
fFirstLineAscent = SkTMin(fFirstLineAscent, metrics.fAscent);
57+
}
58+
fLastLineDescent = SkTMax(fLastLineDescent, metrics.fDescent);
7559
}
7660

7761
void commitRunInfo() override {}
@@ -116,36 +100,41 @@ class BlobMaker final : public SkShaper::RunHandler {
116100
fLineGlyphs.get() + run_offset,
117101
fLinePos.get() + run_offset,
118102
fLineClusters.get() + run_offset,
119-
fLineIndex);
103+
fLineCount);
120104
run_offset += rec.fGlyphCount;
121105
}
122106

123-
fLineIndex++;
107+
fLineCount++;
124108
}
125109

126-
Shaper::Result finalize() {
110+
Shaper::Result finalize(float* shaped_height) {
127111
if (!(fDesc.fFlags & Shaper::Flags::kFragmentGlyphs)) {
128112
// All glyphs are pending in a single blob.
129113
SkASSERT(fResult.fFragments.empty());
130114
fResult.fFragments.reserve(1);
131115
fResult.fFragments.push_back({fBuilder.make(), {fBox.x(), fBox.y()}, 0, false});
132116
}
133117

134-
// By default, first line is vertical-aligned on a baseline of 0.
118+
// By default, first line is vertically-aligned on a baseline of 0.
119+
// The content height considered for vertical alignment is the distance between the first
120+
// line top (ascent) to the last line bottom (descent).
121+
const auto content_height = fLastLineDescent - fFirstLineAscent +
122+
fDesc.fLineHeight * (fLineCount > 0 ? fLineCount - 1 : 0ul);
123+
135124
// Perform additional adjustments based on VAlign.
136125
float v_offset = 0;
137126
switch (fDesc.fVAlign) {
138127
case Shaper::VAlign::kTop:
139-
v_offset = fBox.fTop - fResult.computeBounds().fTop;
128+
v_offset = -fFirstLineAscent;
140129
break;
141130
case Shaper::VAlign::kTopBaseline:
142131
// Default behavior.
143132
break;
144133
case Shaper::VAlign::kCenter:
145-
v_offset = fBox.centerY() - fResult.computeBounds().centerY();
134+
v_offset = -fFirstLineAscent + (fBox.height() - content_height) * 0.5f;
146135
break;
147136
case Shaper::VAlign::kBottom:
148-
v_offset = fBox.fBottom - fResult.computeBounds().fBottom;
137+
v_offset = -fFirstLineAscent + (fBox.height() - content_height);
149138
break;
150139
case Shaper::VAlign::kResizeToFit:
151140
SkASSERT(false);
@@ -158,6 +147,10 @@ class BlobMaker final : public SkShaper::RunHandler {
158147
}
159148
}
160149

150+
if (shaped_height) {
151+
*shaped_height = content_height;
152+
}
153+
161154
return std::move(fResult);
162155
}
163156

@@ -248,14 +241,17 @@ class BlobMaker final : public SkShaper::RunHandler {
248241
SkPoint fCurrentPosition{ 0, 0 };
249242
SkPoint fOffset{ 0, 0 };
250243
SkVector fPendingLineAdvance{ 0, 0 };
251-
uint32_t fLineIndex = 0;
244+
uint32_t fLineCount = 0;
245+
float fFirstLineAscent = 0,
246+
fLastLineDescent = 0;
252247

253248
const char* fUTF8 = nullptr; // only valid during shapeLine() calls
254249

255250
Shaper::Result fResult;
256251
};
257252

258-
Shaper::Result ShapeImpl(const SkString& txt, const Shaper::TextDesc& desc, const SkRect& box) {
253+
Shaper::Result ShapeImpl(const SkString& txt, const Shaper::TextDesc& desc,
254+
const SkRect& box, float* shaped_height = nullptr) {
259255
SkASSERT(desc.fVAlign != Shaper::VAlign::kResizeToFit);
260256

261257
const auto& is_line_break = [](SkUnichar uch) {
@@ -276,7 +272,7 @@ Shaper::Result ShapeImpl(const SkString& txt, const Shaper::TextDesc& desc, cons
276272
}
277273
blobMaker.shapeLine(line_start, ptr);
278274

279-
return blobMaker.finalize();
275+
return blobMaker.finalize(shaped_height);
280276
}
281277

282278
Shaper::Result ShapeToFit(const SkString& txt, const Shaper::TextDesc& orig_desc,
@@ -307,8 +303,8 @@ Shaper::Result ShapeToFit(const SkString& txt, const Shaper::TextDesc& orig_desc
307303
desc.fTextSize = try_scale * orig_desc.fTextSize;
308304
desc.fLineHeight = try_scale * orig_desc.fLineHeight;
309305

310-
auto res = ShapeImpl(txt, desc, box);
311-
auto res_height = res.computeBounds().height();
306+
float res_height = 0;
307+
auto res = ShapeImpl(txt, desc, box, &res_height);
312308

313309
if (res_height > box.height()) {
314310
out_scale = try_scale;
@@ -348,15 +344,4 @@ Shaper::Result Shaper::Shape(const SkString& txt, const TextDesc& desc, const Sk
348344
: ShapeImpl(txt, desc, box);
349345
}
350346

351-
SkRect Shaper::Result::computeBounds() const {
352-
auto bounds = SkRect::MakeEmpty();
353-
354-
for (const auto& fragment : fFragments) {
355-
bounds.join(ComputeBlobBounds(fragment.fBlob).makeOffset(fragment.fPos.x(),
356-
fragment.fPos.y()));
357-
}
358-
359-
return bounds;
360-
}
361-
362347
} // namespace skottie

modules/skottie/src/text/SkottieShaper.h

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,8 +33,6 @@ class Shaper final {
3333

3434
struct Result {
3535
std::vector<Fragment> fFragments;
36-
37-
SkRect computeBounds() const;
3836
};
3937

4038
enum class VAlign : uint8_t {

0 commit comments

Comments
 (0)