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
1818namespace skottie {
1919namespace {
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).
4823class 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
282278Shaper::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
0 commit comments