• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright 2019 Google Inc.
3  *
4  * Use of this source code is governed by a BSD-style license that can be
5  * found in the LICENSE file.
6  */
7 
8 #include "modules/skottie/src/text/SkottieShaper.h"
9 
10 #include "include/core/SkCanvas.h"
11 #include "include/core/SkFontMetrics.h"
12 #include "include/core/SkFontMgr.h"
13 #include "include/private/base/SkTPin.h"
14 #include "include/private/base/SkTemplates.h"
15 #include "modules/skshaper/include/SkShaper.h"
16 #include "modules/skunicode/include/SkUnicode.h"
17 #include "src/base/SkTLazy.h"
18 #include "src/base/SkUTF.h"
19 #include "src/core/SkFontPriv.h"
20 
21 #include <algorithm>
22 #include <limits.h>
23 #include <numeric>
24 
25 using namespace skia_private;
26 
27 namespace skottie {
28 namespace {
29 
is_whitespace(char c)30 static bool is_whitespace(char c) {
31     // TODO: we've been getting away with this simple heuristic,
32     // but ideally we should use SkUicode::isWhiteSpace().
33     return c == ' ' || c == '\t' || c == '\r' || c == '\n';
34 }
35 
36 // Helper for interfacing with SkShaper: buffers shaper-fed runs and performs
37 // per-line position adjustments (for external line breaking, horizontal alignment, etc).
38 class ResultBuilder final : public SkShaper::RunHandler {
39 public:
ResultBuilder(const Shaper::TextDesc & desc,const SkRect & box,const sk_sp<SkFontMgr> & fontmgr)40     ResultBuilder(const Shaper::TextDesc& desc, const SkRect& box, const sk_sp<SkFontMgr>& fontmgr)
41         : fDesc(desc)
42         , fBox(box)
43         , fHAlignFactor(HAlignFactor(fDesc.fHAlign))
44         , fFont(fDesc.fTypeface, fDesc.fTextSize)
45         , fShaper(SkShaper::Make(fontmgr)) {
46         fFont.setHinting(SkFontHinting::kNone);
47         fFont.setSubpixel(true);
48         fFont.setLinearMetrics(true);
49         fFont.setBaselineSnap(false);
50         fFont.setEdging(SkFont::Edging::kAntiAlias);
51     }
52 
beginLine()53     void beginLine() override {
54         fLineGlyphs.reset(0);
55         fLinePos.reset(0);
56         fLineClusters.reset(0);
57         fLineRuns.clear();
58         fLineGlyphCount = 0;
59 
60         fCurrentPosition = fOffset;
61         fPendingLineAdvance  = { 0, 0 };
62 
63         fLastLineDescent = 0;
64     }
65 
runInfo(const RunInfo & info)66     void runInfo(const RunInfo& info) override {
67         fPendingLineAdvance += info.fAdvance;
68 
69         SkFontMetrics metrics;
70         info.fFont.getMetrics(&metrics);
71         if (!fLineCount) {
72             fFirstLineAscent = std::min(fFirstLineAscent, metrics.fAscent);
73         }
74         fLastLineDescent = std::max(fLastLineDescent, metrics.fDescent);
75     }
76 
commitRunInfo()77     void commitRunInfo() override {}
78 
runBuffer(const RunInfo & info)79     Buffer runBuffer(const RunInfo& info) override {
80         const auto run_start_index = fLineGlyphCount;
81         fLineGlyphCount += info.glyphCount;
82 
83         fLineGlyphs.realloc(fLineGlyphCount);
84         fLinePos.realloc(fLineGlyphCount);
85         fLineClusters.realloc(fLineGlyphCount);
86         fLineRuns.push_back({info.fFont, info.glyphCount});
87 
88         SkVector alignmentOffset { fHAlignFactor * (fPendingLineAdvance.x() - fBox.width()), 0 };
89 
90         return {
91             fLineGlyphs.get()   + run_start_index,
92             fLinePos.get()      + run_start_index,
93             nullptr,
94             fLineClusters.get() + run_start_index,
95             fCurrentPosition + alignmentOffset
96         };
97     }
98 
commitRunBuffer(const RunInfo & info)99     void commitRunBuffer(const RunInfo& info) override {
100         fCurrentPosition += info.fAdvance;
101     }
102 
commitLine()103     void commitLine() override {
104         fOffset.fY += fDesc.fLineHeight;
105 
106         // Observed AE handling of whitespace, for alignment purposes:
107         //
108         //   - leading whitespace contributes to alignment
109         //   - trailing whitespace is ignored
110         //   - auto line breaking retains all separating whitespace on the first line (no artificial
111         //     leading WS is created).
112         auto adjust_trailing_whitespace = [this]() {
113             // For left-alignment, trailing WS doesn't make any difference.
114             if (fLineRuns.empty() || fDesc.fHAlign == SkTextUtils::Align::kLeft_Align) {
115                 return;
116             }
117 
118             // Technically, trailing whitespace could span multiple runs, but realistically,
119             // SkShaper has no reason to split it.  Hence we're only checking the last run.
120             size_t ws_count = 0;
121             for (size_t i = 0; i < fLineRuns.back().fSize; ++i) {
122                 if (is_whitespace(fUTF8[fLineClusters[SkToInt(fLineGlyphCount - i - 1)]])) {
123                     ++ws_count;
124                 } else {
125                     break;
126                 }
127             }
128 
129             // No trailing whitespace.
130             if (!ws_count) {
131                 return;
132             }
133 
134             // Compute the cumulative whitespace advance.
135             fAdvanceBuffer.resize(ws_count);
136             fLineRuns.back().fFont.getWidths(fLineGlyphs.data() + fLineGlyphCount - ws_count,
137                                              SkToInt(ws_count), fAdvanceBuffer.data(), nullptr);
138 
139             const auto ws_advance = std::accumulate(fAdvanceBuffer.begin(),
140                                                     fAdvanceBuffer.end(),
141                                                     0.0f);
142 
143             // Offset needed to compensate for whitespace.
144             const auto offset = ws_advance*-fHAlignFactor;
145 
146             // Shift the whole line horizontally by the computed offset.
147             std::transform(fLinePos.data(),
148                            fLinePos.data() + fLineGlyphCount,
149                            fLinePos.data(),
150                            [&offset](SkPoint pos) { return SkPoint{pos.fX + offset, pos.fY}; });
151         };
152 
153         adjust_trailing_whitespace();
154 
155         const auto commit_proc = (fDesc.fFlags & Shaper::Flags::kFragmentGlyphs)
156             ? &ResultBuilder::commitFragementedRun
157             : &ResultBuilder::commitConsolidatedRun;
158 
159         size_t run_offset = 0;
160         for (const auto& rec : fLineRuns) {
161             SkASSERT(run_offset < fLineGlyphCount);
162             (this->*commit_proc)(rec,
163                         fLineGlyphs.get()   + run_offset,
164                         fLinePos.get()      + run_offset,
165                         fLineClusters.get() + run_offset,
166                         fLineCount);
167             run_offset += rec.fSize;
168         }
169 
170         fLineCount++;
171     }
172 
finalize(SkSize * shaped_size)173     Shaper::Result finalize(SkSize* shaped_size) {
174         if (!(fDesc.fFlags & Shaper::Flags::kFragmentGlyphs)) {
175             // All glyphs (if any) are pending in a single fragment.
176             SkASSERT(fResult.fFragments.size() <= 1);
177         }
178 
179         const auto ascent = this->ascent();
180 
181         // For visual VAlign modes, we use a hybrid extent box computed as the union of
182         // actual visual bounds and the vertical typographical extent.
183         //
184         // This ensures that
185         //
186         //   a) text doesn't visually overflow the alignment boundaries
187         //
188         //   b) leading/trailing empty lines are still taken into account for alignment purposes
189 
190         auto extent_box = [&](bool include_typographical_extent) {
191             auto box = fResult.computeVisualBounds();
192 
193             if (include_typographical_extent) {
194                 // Hybrid visual alignment mode, based on typographical extent.
195 
196                 // By default, first line is vertically-aligned on a baseline of 0.
197                 // The typographical height considered for vertical alignment is the distance
198                 // between the first line top (ascent) to the last line bottom (descent).
199                 const auto typographical_top    = fBox.fTop + ascent,
200                            typographical_bottom = fBox.fTop + fLastLineDescent +
201                                           fDesc.fLineHeight*(fLineCount > 0 ? fLineCount - 1 : 0ul);
202 
203                 box.fTop    = std::min(box.fTop,    typographical_top);
204                 box.fBottom = std::max(box.fBottom, typographical_bottom);
205             }
206 
207             return box;
208         };
209 
210         // Only compute the extent box when needed.
211         SkTLazy<SkRect> ebox;
212 
213         // Vertical adjustments.
214         float v_offset = -fDesc.fLineShift;
215 
216         switch (fDesc.fVAlign) {
217         case Shaper::VAlign::kTop:
218             v_offset -= ascent;
219             break;
220         case Shaper::VAlign::kTopBaseline:
221             // Default behavior.
222             break;
223         case Shaper::VAlign::kHybridTop:
224         case Shaper::VAlign::kVisualTop:
225             ebox.init(extent_box(fDesc.fVAlign == Shaper::VAlign::kHybridTop));
226             v_offset += fBox.fTop - ebox->fTop;
227             break;
228         case Shaper::VAlign::kHybridCenter:
229         case Shaper::VAlign::kVisualCenter:
230             ebox.init(extent_box(fDesc.fVAlign == Shaper::VAlign::kHybridCenter));
231             v_offset += fBox.centerY() - ebox->centerY();
232             break;
233         case Shaper::VAlign::kHybridBottom:
234         case Shaper::VAlign::kVisualBottom:
235             ebox.init(extent_box(fDesc.fVAlign == Shaper::VAlign::kHybridBottom));
236             v_offset += fBox.fBottom - ebox->fBottom;
237             break;
238         }
239 
240         if (shaped_size) {
241             if (!ebox.isValid()) {
242                 ebox.init(extent_box(true));
243             }
244             *shaped_size = SkSize::Make(ebox->width(), ebox->height());
245         }
246 
247         if (v_offset) {
248             for (auto& fragment : fResult.fFragments) {
249                 fragment.fOrigin.fY += v_offset;
250             }
251         }
252 
253         return std::move(fResult);
254     }
255 
shapeLine(const char * start,const char * end,size_t utf8_offset)256     void shapeLine(const char* start, const char* end, size_t utf8_offset) {
257         if (!fShaper) {
258             return;
259         }
260 
261         SkASSERT(start <= end);
262         if (start == end) {
263             // SkShaper doesn't care for empty lines.
264             this->beginLine();
265             this->commitLine();
266 
267             // The calls above perform bookkeeping, but they do not add any fragments (since there
268             // are no runs to commit).
269             //
270             // Certain Skottie features (line-based range selectors) do require accurate indexing
271             // information even for empty lines though -- so we inject empty fragments solely for
272             // line index tracking.
273             //
274             // Note: we don't add empty fragments in consolidated mode because 1) consolidated mode
275             // assumes there is a single result fragment and 2) kFragmentGlyphs is always enabled
276             // for cases where line index tracking is relevant.
277             //
278             // TODO(fmalita): investigate whether it makes sense to move this special case down
279             // to commitFragmentedRun().
280             if (fDesc.fFlags & Shaper::Flags::kFragmentGlyphs) {
281                 fResult.fFragments.push_back({
282                     Shaper::ShapedGlyphs(),
283                     {fBox.x(),fBox.y()},
284                     0, 0,
285                     fLineCount - 1,
286                     false
287                 });
288             }
289 
290             return;
291         }
292 
293         // In default paragraph mode (VAlign::kTop), AE clips out lines when the baseline
294         // goes below the box lower edge.
295         if (fDesc.fVAlign == Shaper::VAlign::kTop) {
296             // fOffset is relative to the first line baseline.
297             const auto max_offset = fBox.height() + this->ascent(); // NB: ascent is negative
298             if (fOffset.y() > max_offset) {
299                 return;
300             }
301         }
302 
303         const auto shape_width = fDesc.fLinebreak == Shaper::LinebreakPolicy::kExplicit
304                                     ? SK_ScalarMax
305                                     : fBox.width();
306         const auto shape_ltr   = fDesc.fDirection == Shaper::Direction::kLTR;
307 
308         fUTF8 = start;
309         fUTF8Offset = utf8_offset;
310         fShaper->shape(start, SkToSizeT(end - start), fFont, shape_ltr, shape_width, this);
311         fUTF8 = nullptr;
312     }
313 
314 private:
commitFragementedRun(const skottie::Shaper::RunRec & run,const SkGlyphID * glyphs,const SkPoint * pos,const uint32_t * clusters,uint32_t line_index)315     void commitFragementedRun(const skottie::Shaper::RunRec& run,
316                               const SkGlyphID* glyphs,
317                               const SkPoint* pos,
318                               const uint32_t* clusters,
319                               uint32_t line_index) {
320         float ascent = 0;
321 
322         if (fDesc.fFlags & Shaper::Flags::kTrackFragmentAdvanceAscent) {
323             SkFontMetrics metrics;
324             run.fFont.getMetrics(&metrics);
325             ascent = metrics.fAscent;
326 
327             // Note: we use per-glyph advances for anchoring, but it's unclear whether this
328             // is exactly the same as AE.  E.g. are 'acute' glyphs anchored separately for fonts
329             // in which they're distinct?
330             fAdvanceBuffer.resize(run.fSize);
331             fFont.getWidths(glyphs, SkToInt(run.fSize), fAdvanceBuffer.data());
332         }
333 
334         // In fragmented mode we immediately push the glyphs to fResult,
335         // one fragment per glyph.  Glyph positioning is externalized
336         // (positions returned in Fragment::fPos).
337         for (size_t i = 0; i < run.fSize; ++i) {
338             const auto advance = (fDesc.fFlags & Shaper::Flags::kTrackFragmentAdvanceAscent)
339                     ? fAdvanceBuffer[SkToInt(i)]
340                     : 0.0f;
341 
342             fResult.fFragments.push_back({
343                 {
344                     { {run.fFont, 1} },
345                     { glyphs[i] },
346                     { {0,0} },
347                     fDesc.fFlags & Shaper::kClusters
348                         ? std::vector<size_t>{ fUTF8Offset + clusters[i] }
349                         : std::vector<size_t>({}),
350                 },
351                 { fBox.x() + pos[i].fX, fBox.y() + pos[i].fY },
352                 advance, ascent,
353                 line_index, is_whitespace(fUTF8[clusters[i]])
354             });
355 
356             // Note: we only check the first code point in the cluster for whitespace.
357             // It's unclear whether thers's a saner approach.
358             fResult.fMissingGlyphCount += (glyphs[i] == kMissingGlyphID);
359         }
360     }
361 
commitConsolidatedRun(const skottie::Shaper::RunRec & run,const SkGlyphID * glyphs,const SkPoint * pos,const uint32_t * clusters,uint32_t)362     void commitConsolidatedRun(const skottie::Shaper::RunRec& run,
363                                const SkGlyphID* glyphs,
364                                const SkPoint* pos,
365                                const uint32_t* clusters,
366                                uint32_t) {
367         // In consolidated mode we just accumulate glyphs to a single fragment in ResultBuilder.
368         // Glyph positions are baked in the fragment runs (Fragment::fPos only reflects the
369         // box origin).
370 
371         if (fResult.fFragments.empty()) {
372             fResult.fFragments.push_back({{{}, {}, {}, {}}, {fBox.x(), fBox.y()}, 0, 0, 0, false});
373         }
374 
375         auto& current_glyphs = fResult.fFragments.back().fGlyphs;
376         current_glyphs.fRuns.push_back(run);
377         current_glyphs.fGlyphIDs.insert(current_glyphs.fGlyphIDs.end(), glyphs, glyphs + run.fSize);
378         current_glyphs.fGlyphPos.insert(current_glyphs.fGlyphPos.end(), pos   , pos    + run.fSize);
379 
380         for (size_t i = 0; i < run.fSize; ++i) {
381             fResult.fMissingGlyphCount += (glyphs[i] == kMissingGlyphID);
382         }
383 
384         if (fDesc.fFlags & Shaper::kClusters) {
385             current_glyphs.fClusters.reserve(current_glyphs.fClusters.size() + run.fSize);
386             for (size_t i = 0; i < run.fSize; ++i) {
387                 current_glyphs.fClusters.push_back(fUTF8Offset + clusters[i]);
388             }
389         }
390     }
391 
HAlignFactor(SkTextUtils::Align align)392     static float HAlignFactor(SkTextUtils::Align align) {
393         switch (align) {
394         case SkTextUtils::kLeft_Align:   return  0.0f;
395         case SkTextUtils::kCenter_Align: return -0.5f;
396         case SkTextUtils::kRight_Align:  return -1.0f;
397         }
398         return 0.0f; // go home, msvc...
399     }
400 
ascent() const401     SkScalar ascent() const {
402         // Use the explicit ascent, when specified.
403         // Note: ascent values are negative (relative to the baseline).
404         return fDesc.fAscent ? fDesc.fAscent : fFirstLineAscent;
405     }
406 
407     inline static constexpr SkGlyphID kMissingGlyphID = 0;
408 
409     const Shaper::TextDesc&   fDesc;
410     const SkRect&             fBox;
411     const float               fHAlignFactor;
412 
413     SkFont                    fFont;
414     std::unique_ptr<SkShaper> fShaper;
415 
416     AutoSTMalloc<64, SkGlyphID>          fLineGlyphs;
417     AutoSTMalloc<64, SkPoint>            fLinePos;
418     AutoSTMalloc<64, uint32_t>           fLineClusters;
419     SkSTArray<16, skottie::Shaper::RunRec> fLineRuns;
420     size_t                                 fLineGlyphCount = 0;
421 
422     SkSTArray<64, float, true>             fAdvanceBuffer;
423 
424     SkPoint  fCurrentPosition{ 0, 0 };
425     SkPoint  fOffset{ 0, 0 };
426     SkVector fPendingLineAdvance{ 0, 0 };
427     uint32_t fLineCount = 0;
428     float    fFirstLineAscent = 0,
429              fLastLineDescent = 0;
430 
431     const char* fUTF8       = nullptr; // only valid during shapeLine() calls
432     size_t      fUTF8Offset = 0;       // current line offset within the original string
433 
434     Shaper::Result fResult;
435 };
436 
ShapeImpl(const SkString & txt,const Shaper::TextDesc & desc,const SkRect & box,const sk_sp<SkFontMgr> & fontmgr,SkSize * shaped_size=nullptr)437 Shaper::Result ShapeImpl(const SkString& txt, const Shaper::TextDesc& desc,
438                          const SkRect& box, const sk_sp<SkFontMgr>& fontmgr,
439                          SkSize* shaped_size = nullptr) {
440     const auto& is_line_break = [](SkUnichar uch) {
441         // TODO: other explicit breaks?
442         return uch == '\r';
443     };
444 
445     const char* ptr        = txt.c_str();
446     const char* line_start = ptr;
447     const char* begin      = ptr;
448     const char* end        = ptr + txt.size();
449 
450     ResultBuilder rbuilder(desc, box, fontmgr);
451     while (ptr < end) {
452         if (is_line_break(SkUTF::NextUTF8(&ptr, end))) {
453             rbuilder.shapeLine(line_start, ptr - 1, SkToSizeT(line_start - begin));
454             line_start = ptr;
455         }
456     }
457     rbuilder.shapeLine(line_start, ptr, SkToSizeT(line_start - begin));
458 
459     return rbuilder.finalize(shaped_size);
460 }
461 
result_fits(const Shaper::Result & res,const SkSize & res_size,const SkRect & box,const Shaper::TextDesc & desc)462 bool result_fits(const Shaper::Result& res, const SkSize& res_size,
463                  const SkRect& box, const Shaper::TextDesc& desc) {
464     // optional max line count constraint
465     if (desc.fMaxLines) {
466         const auto line_count = res.fFragments.empty()
467                 ? 0
468                 : res.fFragments.back().fLineIndex + 1;
469         if (line_count > desc.fMaxLines) {
470             return false;
471         }
472     }
473 
474     // geometric constraint
475     return res_size.width() <= box.width() && res_size.height() <= box.height();
476 }
477 
ShapeToFit(const SkString & txt,const Shaper::TextDesc & orig_desc,const SkRect & box,const sk_sp<SkFontMgr> & fontmgr)478 Shaper::Result ShapeToFit(const SkString& txt, const Shaper::TextDesc& orig_desc,
479                           const SkRect& box, const sk_sp<SkFontMgr>& fontmgr) {
480     Shaper::Result best_result;
481 
482     if (box.isEmpty() || orig_desc.fTextSize <= 0) {
483         return best_result;
484     }
485 
486     auto desc = orig_desc;
487 
488     const auto min_scale = std::max(desc.fMinTextSize / desc.fTextSize, 0.0f),
489                max_scale = std::max(desc.fMaxTextSize / desc.fTextSize, min_scale);
490 
491     float in_scale = min_scale,                          // maximum scale that fits inside
492          out_scale = max_scale,                          // minimum scale that doesn't fit
493          try_scale = SkTPin(1.0f, min_scale, max_scale); // current probe
494 
495     // Perform a binary search for the best vertical fit (SkShaper already handles
496     // horizontal fitting), starting with the specified text size.
497     //
498     // This hybrid loop handles both the binary search (when in/out extremes are known), and an
499     // exponential search for the extremes.
500     static constexpr size_t kMaxIter = 16;
501     for (size_t i = 0; i < kMaxIter; ++i) {
502         SkASSERT(try_scale >= in_scale && try_scale <= out_scale);
503         desc.fTextSize   = try_scale * orig_desc.fTextSize;
504         desc.fLineHeight = try_scale * orig_desc.fLineHeight;
505         desc.fLineShift  = try_scale * orig_desc.fLineShift;
506         desc.fAscent     = try_scale * orig_desc.fAscent;
507 
508         SkSize res_size = {0, 0};
509         auto res = ShapeImpl(txt, desc, box, fontmgr, &res_size);
510 
511         const auto prev_scale = try_scale;
512         if (!result_fits(res, res_size, box, desc)) {
513             out_scale = try_scale;
514             try_scale = (in_scale == min_scale)
515                     // initial in_scale not found yet - search exponentially
516                     ? std::max(min_scale, try_scale * 0.5f)
517                     // in_scale found - binary search
518                     : (in_scale + out_scale) * 0.5f;
519         } else {
520             // It fits - so it's a candidate.
521             best_result = std::move(res);
522             best_result.fScale = try_scale;
523 
524             in_scale = try_scale;
525             try_scale = (out_scale == max_scale)
526                     // initial out_scale not found yet - search exponentially
527                     ? std::min(max_scale, try_scale * 2)
528                     // out_scale found - binary search
529                     : (in_scale + out_scale) * 0.5f;
530         }
531 
532         if (try_scale == prev_scale) {
533             // no more progress
534             break;
535         }
536     }
537 
538     return best_result;
539 }
540 
541 
542 // Applies capitalization rules.
543 class AdjustedText {
544 public:
AdjustedText(const SkString & txt,const Shaper::TextDesc & desc)545     AdjustedText(const SkString& txt, const Shaper::TextDesc& desc)
546         : fText(txt) {
547         switch (desc.fCapitalization) {
548         case Shaper::Capitalization::kNone:
549             break;
550         case Shaper::Capitalization::kUpperCase:
551 #ifdef SK_UNICODE_AVAILABLE
552             if (auto skuni = SkUnicode::Make()) {
553                 *fText.writable() = skuni->toUpper(*fText);
554             }
555 #endif
556             break;
557         }
558     }
559 
operator const SkString&() const560     operator const SkString&() const { return *fText; }
561 
562 private:
563     SkTCopyOnFirstWrite<SkString> fText;
564 };
565 
566 } // namespace
567 
Shape(const SkString & text,const TextDesc & desc,const SkPoint & point,const sk_sp<SkFontMgr> & fontmgr)568 Shaper::Result Shaper::Shape(const SkString& text, const TextDesc& desc, const SkPoint& point,
569                              const sk_sp<SkFontMgr>& fontmgr) {
570     const AdjustedText adjText(text, desc);
571 
572     return (desc.fResize == ResizePolicy::kScaleToFit ||
573             desc.fResize == ResizePolicy::kDownscaleToFit) // makes no sense in point mode
574             ? Result()
575             : ShapeImpl(adjText, desc, SkRect::MakeEmpty().makeOffset(point.x(), point.y()),
576                         fontmgr);
577 }
578 
Shape(const SkString & text,const TextDesc & desc,const SkRect & box,const sk_sp<SkFontMgr> & fontmgr)579 Shaper::Result Shaper::Shape(const SkString& text, const TextDesc& desc, const SkRect& box,
580                              const sk_sp<SkFontMgr>& fontmgr) {
581     const AdjustedText adjText(text, desc);
582 
583     switch(desc.fResize) {
584     case ResizePolicy::kNone:
585         return ShapeImpl(adjText, desc, box, fontmgr);
586     case ResizePolicy::kScaleToFit:
587         return ShapeToFit(adjText, desc, box, fontmgr);
588     case ResizePolicy::kDownscaleToFit: {
589         SkSize size;
590         auto result = ShapeImpl(adjText, desc, box, fontmgr, &size);
591 
592         return result_fits(result, size, box, desc)
593                 ? result
594                 : ShapeToFit(adjText, desc, box, fontmgr);
595     }
596     }
597 
598     SkUNREACHABLE;
599 }
600 
computeBounds(BoundsType btype) const601 SkRect Shaper::ShapedGlyphs::computeBounds(BoundsType btype) const {
602     auto bounds = SkRect::MakeEmpty();
603 
604     AutoSTArray<16, SkRect> glyphBounds;
605 
606     size_t offset = 0;
607     for (const auto& run : fRuns) {
608         SkRect font_bounds;
609         if (btype == BoundsType::kConservative) {
610             font_bounds = SkFontPriv::GetFontBounds(run.fFont);
611 
612             // Empty font bounds is likely a font bug -- fall back to tight bounds.
613             if (font_bounds.isEmpty()) {
614                 btype = BoundsType::kTight;
615             }
616         }
617 
618         switch (btype) {
619         case BoundsType::kConservative: {
620             SkRect run_bounds;
621             run_bounds.setBounds(fGlyphPos.data() + offset, SkToInt(run.fSize));
622             run_bounds.fLeft   += font_bounds.left();
623             run_bounds.fTop    += font_bounds.top();
624             run_bounds.fRight  += font_bounds.right();
625             run_bounds.fBottom += font_bounds.bottom();
626 
627             bounds.join(run_bounds);
628         } break;
629         case BoundsType::kTight: {
630             glyphBounds.reset(SkToInt(run.fSize));
631             run.fFont.getBounds(fGlyphIDs.data() + offset,
632                                 SkToInt(run.fSize), glyphBounds.data(), nullptr);
633 
634             for (size_t i = 0; i < run.fSize; ++i) {
635                 bounds.join(glyphBounds[SkToInt(i)].makeOffset(fGlyphPos[offset + i]));
636             }
637         } break;
638         }
639 
640         offset += run.fSize;
641     }
642 
643     return bounds;
644 }
645 
draw(SkCanvas * canvas,const SkPoint & origin,const SkPaint & paint) const646 void Shaper::ShapedGlyphs::draw(SkCanvas* canvas,
647                                 const SkPoint& origin,
648                                 const SkPaint& paint) const {
649     size_t offset = 0;
650     for (const auto& run : fRuns) {
651         canvas->drawGlyphs(SkToInt(run.fSize),
652                            fGlyphIDs.data() + offset,
653                            fGlyphPos.data() + offset,
654                            origin,
655                            run.fFont,
656                            paint);
657         offset += run.fSize;
658     }
659 }
660 
computeVisualBounds() const661 SkRect Shaper::Result::computeVisualBounds() const {
662     auto bounds = SkRect::MakeEmpty();
663 
664     for (const auto& frag: fFragments) {
665         bounds.join(frag.fGlyphs.computeBounds(ShapedGlyphs::BoundsType::kTight)
666                                 .makeOffset(frag.fOrigin));
667     }
668 
669     return bounds;
670 }
671 
672 } // namespace skottie
673