Flutter Engine
The Flutter Engine
TextShaper.cpp
Go to the documentation of this file.
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
9
14#include "include/core/SkRect.h"
16#include "include/core/SkSize.h"
27#include "src/base/SkTLazy.h"
28#include "src/base/SkUTF.h"
29#include "src/core/SkFontPriv.h"
30
31#include <algorithm>
32#include <memory>
33#include <numeric>
34#include <utility>
35
36#if !defined(SK_DISABLE_LEGACY_SHAPER_FACTORY)
38#endif
39
40class SkPaint;
41
42using namespace skia_private;
43
44namespace skottie {
45namespace {
46static bool is_whitespace(char c) {
47 // TODO: we've been getting away with this simple heuristic,
48 // but ideally we should use SkUicode::isWhiteSpace().
49 return c == ' ' || c == '\t' || c == '\r' || c == '\n';
50}
51
52// Helper for interfacing with SkShaper: buffers shaper-fed runs and performs
53// per-line position adjustments (for external line breaking, horizontal alignment, etc).
54class ResultBuilder final : public SkShaper::RunHandler {
55public:
56 ResultBuilder(const Shaper::TextDesc& desc, const SkRect& box, const sk_sp<SkFontMgr>& fontmgr,
57 const sk_sp<SkShapers::Factory>& shapingFactory)
58 : fDesc(desc)
59 , fBox(box)
60 , fHAlignFactor(HAlignFactor(fDesc.fHAlign))
61 , fFont(fDesc.fTypeface, fDesc.fTextSize)
62 , fFontMgr(fontmgr)
63 , fShapingFactory(shapingFactory) {
64 // If the shaper callback returns null, fallback to the primitive shaper.
65 SkASSERT(fShapingFactory);
66 fShaper = fShapingFactory->makeShaper(fFontMgr);
67 if (!fShaper) {
69 fShapingFactory = SkShapers::Primitive::Factory();
70 }
72 fFont.setSubpixel(true);
73 fFont.setLinearMetrics(true);
74 fFont.setBaselineSnap(false);
76 }
77
78 void beginLine() override {
79 fLineGlyphs.reset(0);
80 fLinePos.reset(0);
81 fLineClusters.reset(0);
82 fLineRuns.clear();
83 fLineGlyphCount = 0;
84
85 fCurrentPosition = fOffset;
86 fPendingLineAdvance = { 0, 0 };
87
88 fLastLineDescent = 0;
89 }
90
91 void runInfo(const RunInfo& info) override {
92 fPendingLineAdvance += info.fAdvance;
93
94 SkFontMetrics metrics;
95 info.fFont.getMetrics(&metrics);
96 if (!fLineCount) {
97 fFirstLineAscent = std::min(fFirstLineAscent, metrics.fAscent);
98 }
99 fLastLineDescent = std::max(fLastLineDescent, metrics.fDescent);
100 }
101
102 void commitRunInfo() override {}
103
104 Buffer runBuffer(const RunInfo& info) override {
105 const auto run_start_index = fLineGlyphCount;
106 fLineGlyphCount += info.glyphCount;
107
108 fLineGlyphs.realloc(fLineGlyphCount);
109 fLinePos.realloc(fLineGlyphCount);
110 fLineClusters.realloc(fLineGlyphCount);
111 fLineRuns.push_back({info.fFont, info.glyphCount});
112
113 SkVector alignmentOffset { fHAlignFactor * (fPendingLineAdvance.x() - fBox.width()), 0 };
114
115 return {
116 fLineGlyphs.get() + run_start_index,
117 fLinePos.get() + run_start_index,
118 nullptr,
119 fLineClusters.get() + run_start_index,
120 fCurrentPosition + alignmentOffset
121 };
122 }
123
124 void commitRunBuffer(const RunInfo& info) override {
125 fCurrentPosition += info.fAdvance;
126 }
127
128 void commitLine() override {
129 fOffset.fY += fDesc.fLineHeight;
130
131 // Observed AE handling of whitespace, for alignment purposes:
132 //
133 // - leading whitespace contributes to alignment
134 // - trailing whitespace is ignored
135 // - auto line breaking retains all separating whitespace on the first line (no artificial
136 // leading WS is created).
137 auto adjust_trailing_whitespace = [this]() {
138 // For left-alignment, trailing WS doesn't make any difference.
139 if (fLineRuns.empty() || fDesc.fHAlign == SkTextUtils::Align::kLeft_Align) {
140 return;
141 }
142
143 // Technically, trailing whitespace could span multiple runs, but realistically,
144 // SkShaper has no reason to split it. Hence we're only checking the last run.
145 size_t ws_count = 0;
146 for (size_t i = 0; i < fLineRuns.back().fSize; ++i) {
147 if (is_whitespace(fUTF8[fLineClusters[SkToInt(fLineGlyphCount - i - 1)]])) {
148 ++ws_count;
149 } else {
150 break;
151 }
152 }
153
154 // No trailing whitespace.
155 if (!ws_count) {
156 return;
157 }
158
159 // Compute the cumulative whitespace advance.
160 fAdvanceBuffer.resize(ws_count);
161 fLineRuns.back().fFont.getWidths(fLineGlyphs.data() + fLineGlyphCount - ws_count,
162 SkToInt(ws_count), fAdvanceBuffer.data(), nullptr);
163
164 const auto ws_advance = std::accumulate(fAdvanceBuffer.begin(),
165 fAdvanceBuffer.end(),
166 0.0f);
167
168 // Offset needed to compensate for whitespace.
169 const auto offset = ws_advance*-fHAlignFactor;
170
171 // Shift the whole line horizontally by the computed offset.
172 std::transform(fLinePos.data(),
173 fLinePos.data() + fLineGlyphCount,
174 fLinePos.data(),
175 [&offset](SkPoint pos) { return SkPoint{pos.fX + offset, pos.fY}; });
176 };
177
178 adjust_trailing_whitespace();
179
180 const auto commit_proc = (fDesc.fFlags & Shaper::Flags::kFragmentGlyphs)
181 ? &ResultBuilder::commitFragementedRun
182 : &ResultBuilder::commitConsolidatedRun;
183
184 size_t run_offset = 0;
185 for (const auto& rec : fLineRuns) {
186 SkASSERT(run_offset < fLineGlyphCount);
187 (this->*commit_proc)(rec,
188 fLineGlyphs.get() + run_offset,
189 fLinePos.get() + run_offset,
190 fLineClusters.get() + run_offset,
191 fLineCount);
192 run_offset += rec.fSize;
193 }
194
195 fLineCount++;
196 }
197
198 Shaper::Result finalize(SkSize* shaped_size) {
199 if (!(fDesc.fFlags & Shaper::Flags::kFragmentGlyphs)) {
200 // All glyphs (if any) are pending in a single fragment.
201 SkASSERT(fResult.fFragments.size() <= 1);
202 }
203
204 const auto ascent = this->ascent();
205
206 // For visual VAlign modes, we use a hybrid extent box computed as the union of
207 // actual visual bounds and the vertical typographical extent.
208 //
209 // This ensures that
210 //
211 // a) text doesn't visually overflow the alignment boundaries
212 //
213 // b) leading/trailing empty lines are still taken into account for alignment purposes
214
215 auto extent_box = [&](bool include_typographical_extent) {
216 auto box = fResult.computeVisualBounds();
217
218 if (include_typographical_extent) {
219 // Hybrid visual alignment mode, based on typographical extent.
220
221 // By default, first line is vertically-aligned on a baseline of 0.
222 // The typographical height considered for vertical alignment is the distance
223 // between the first line top (ascent) to the last line bottom (descent).
224 const auto typographical_top = fBox.fTop + ascent,
225 typographical_bottom = fBox.fTop + fLastLineDescent +
226 fDesc.fLineHeight*(fLineCount > 0 ? fLineCount - 1 : 0ul);
227
228 box.fTop = std::min(box.fTop, typographical_top);
229 box.fBottom = std::max(box.fBottom, typographical_bottom);
230 }
231
232 return box;
233 };
234
235 // Only compute the extent box when needed.
236 SkTLazy<SkRect> ebox;
237
238 // Vertical adjustments.
239 float v_offset = -fDesc.fLineShift;
240
241 switch (fDesc.fVAlign) {
243 v_offset -= ascent;
244 break;
246 // Default behavior.
247 break;
250 ebox.init(extent_box(fDesc.fVAlign == Shaper::VAlign::kHybridTop));
251 v_offset += fBox.fTop - ebox->fTop;
252 break;
255 ebox.init(extent_box(fDesc.fVAlign == Shaper::VAlign::kHybridCenter));
256 v_offset += fBox.centerY() - ebox->centerY();
257 break;
260 ebox.init(extent_box(fDesc.fVAlign == Shaper::VAlign::kHybridBottom));
261 v_offset += fBox.fBottom - ebox->fBottom;
262 break;
263 }
264
265 if (shaped_size) {
266 if (!ebox.isValid()) {
267 ebox.init(extent_box(true));
268 }
269 *shaped_size = SkSize::Make(ebox->width(), ebox->height());
270 }
271
272 if (v_offset) {
273 for (auto& fragment : fResult.fFragments) {
274 fragment.fOrigin.fY += v_offset;
275 }
276 }
277
278 return std::move(fResult);
279 }
280
281 void shapeLine(const char* start, const char* end, size_t utf8_offset) {
282 if (!fShaper) {
283 return;
284 }
285
286 SkASSERT(start <= end);
287 if (start == end) {
288 // SkShaper doesn't care for empty lines.
289 this->beginLine();
290 this->commitLine();
291
292 // The calls above perform bookkeeping, but they do not add any fragments (since there
293 // are no runs to commit).
294 //
295 // Certain Skottie features (line-based range selectors) do require accurate indexing
296 // information even for empty lines though -- so we inject empty fragments solely for
297 // line index tracking.
298 //
299 // Note: we don't add empty fragments in consolidated mode because 1) consolidated mode
300 // assumes there is a single result fragment and 2) kFragmentGlyphs is always enabled
301 // for cases where line index tracking is relevant.
302 //
303 // TODO(fmalita): investigate whether it makes sense to move this special case down
304 // to commitFragmentedRun().
305 if (fDesc.fFlags & Shaper::Flags::kFragmentGlyphs) {
306 fResult.fFragments.push_back({
307 Shaper::ShapedGlyphs(),
308 {fBox.x(),fBox.y()},
309 0, 0,
310 fLineCount - 1,
311 false
312 });
313 }
314
315 return;
316 }
317
318 // In default paragraph mode (VAlign::kTop), AE clips out lines when the baseline
319 // goes below the box lower edge.
320 if (fDesc.fVAlign == Shaper::VAlign::kTop) {
321 // fOffset is relative to the first line baseline.
322 const auto max_offset = fBox.height() + this->ascent(); // NB: ascent is negative
323 if (fOffset.y() > max_offset) {
324 return;
325 }
326 }
327
328 const auto shape_width = fDesc.fLinebreak == Shaper::LinebreakPolicy::kExplicit
330 : fBox.width();
331 const auto shape_ltr = fDesc.fDirection == Shaper::Direction::kLTR;
332 const size_t utf8_bytes = SkToSizeT(end - start);
333
334 static constexpr uint8_t kBidiLevelLTR = 0,
335 kBidiLevelRTL = 1;
336 const auto lang_iter = fDesc.fLocale
337 ? std::make_unique<SkShaper::TrivialLanguageRunIterator>(fDesc.fLocale, utf8_bytes)
338 : SkShaper::MakeStdLanguageRunIterator(start, utf8_bytes);
339#if defined(SKOTTIE_TRIVIAL_FONTRUN_ITER)
340 // Chrome Linux/CrOS does not have a fallback-capable fontmgr, and crashes if fallback is
341 // triggered. Using a TrivialFontRunIterator avoids the issue (https://crbug.com/1520148).
342 const auto font_iter = std::make_unique<SkShaper::TrivialFontRunIterator>(fFont,
343 utf8_bytes);
344#else
345 const auto font_iter = SkShaper::MakeFontMgrRunIterator(
346 start, utf8_bytes, fFont,
347 fFontMgr ? fFontMgr : SkFontMgr::RefEmpty(), // used as fallback
348 fDesc.fFontFamily,
349 fFont.getTypeface()->fontStyle(),
350 lang_iter.get());
351#endif
352
353 std::unique_ptr<SkShaper::BiDiRunIterator> bidi_iter =
354 fShapingFactory->makeBidiRunIterator(start, utf8_bytes,
355 shape_ltr ? kBidiLevelLTR : kBidiLevelRTL);
356 if (!bidi_iter) {
357 bidi_iter = std::make_unique<SkShaper::TrivialBiDiRunIterator>(
358 shape_ltr ? kBidiLevelLTR : kBidiLevelRTL, utf8_bytes);
359 }
360
361 constexpr SkFourByteTag unknownScript = SkSetFourByteTag('Z', 'z', 'z', 'z');
362 std::unique_ptr<SkShaper::ScriptRunIterator> scpt_iter =
363 fShapingFactory->makeScriptRunIterator(start, utf8_bytes, unknownScript);
364 if (!scpt_iter) {
365 scpt_iter = std::make_unique<SkShaper::TrivialScriptRunIterator>(unknownScript, utf8_bytes);
366 }
367
368 if (!font_iter || !bidi_iter || !scpt_iter || !lang_iter) {
369 return;
370 }
371
372 fUTF8 = start;
373 fUTF8Offset = utf8_offset;
374 fShaper->shape(start,
375 utf8_bytes,
376 *font_iter,
377 *bidi_iter,
378 *scpt_iter,
379 *lang_iter,
380 nullptr,
381 0,
382 shape_width,
383 this);
384 fUTF8 = nullptr;
385 }
386
387private:
388 void commitFragementedRun(const skottie::Shaper::RunRec& run,
389 const SkGlyphID* glyphs,
390 const SkPoint* pos,
391 const uint32_t* clusters,
392 uint32_t line_index) {
393 float ascent = 0;
394
395 if (fDesc.fFlags & Shaper::Flags::kTrackFragmentAdvanceAscent) {
396 SkFontMetrics metrics;
397 run.fFont.getMetrics(&metrics);
398 ascent = metrics.fAscent;
399
400 // Note: we use per-glyph advances for anchoring, but it's unclear whether this
401 // is exactly the same as AE. E.g. are 'acute' glyphs anchored separately for fonts
402 // in which they're distinct?
403 fAdvanceBuffer.resize(run.fSize);
404 fFont.getWidths(glyphs, SkToInt(run.fSize), fAdvanceBuffer.data());
405 }
406
407 // In fragmented mode we immediately push the glyphs to fResult,
408 // one fragment per glyph. Glyph positioning is externalized
409 // (positions returned in Fragment::fPos).
410 for (size_t i = 0; i < run.fSize; ++i) {
411 const auto advance = (fDesc.fFlags & Shaper::Flags::kTrackFragmentAdvanceAscent)
412 ? fAdvanceBuffer[SkToInt(i)]
413 : 0.0f;
414
415 fResult.fFragments.push_back({
416 {
417 { {run.fFont, 1} },
418 { glyphs[i] },
419 { {0,0} },
421 ? std::vector<size_t>{ fUTF8Offset + clusters[i] }
422 : std::vector<size_t>({}),
423 },
424 { fBox.x() + pos[i].fX, fBox.y() + pos[i].fY },
425 advance, ascent,
426 line_index, is_whitespace(fUTF8[clusters[i]])
427 });
428
429 // Note: we only check the first code point in the cluster for whitespace.
430 // It's unclear whether thers's a saner approach.
431 fResult.fMissingGlyphCount += (glyphs[i] == kMissingGlyphID);
432 }
433 }
434
435 void commitConsolidatedRun(const skottie::Shaper::RunRec& run,
436 const SkGlyphID* glyphs,
437 const SkPoint* pos,
438 const uint32_t* clusters,
439 uint32_t) {
440 // In consolidated mode we just accumulate glyphs to a single fragment in ResultBuilder.
441 // Glyph positions are baked in the fragment runs (Fragment::fPos only reflects the
442 // box origin).
443
444 if (fResult.fFragments.empty()) {
445 fResult.fFragments.push_back({{{}, {}, {}, {}}, {fBox.x(), fBox.y()}, 0, 0, 0, false});
446 }
447
448 auto& current_glyphs = fResult.fFragments.back().fGlyphs;
449 current_glyphs.fRuns.push_back(run);
450 current_glyphs.fGlyphIDs.insert(current_glyphs.fGlyphIDs.end(), glyphs, glyphs + run.fSize);
451 current_glyphs.fGlyphPos.insert(current_glyphs.fGlyphPos.end(), pos , pos + run.fSize);
452
453 for (size_t i = 0; i < run.fSize; ++i) {
454 fResult.fMissingGlyphCount += (glyphs[i] == kMissingGlyphID);
455 }
456
457 if (fDesc.fFlags & Shaper::kClusters) {
458 current_glyphs.fClusters.reserve(current_glyphs.fClusters.size() + run.fSize);
459 for (size_t i = 0; i < run.fSize; ++i) {
460 current_glyphs.fClusters.push_back(fUTF8Offset + clusters[i]);
461 }
462 }
463 }
464
465 static float HAlignFactor(SkTextUtils::Align align) {
466 switch (align) {
467 case SkTextUtils::kLeft_Align: return 0.0f;
468 case SkTextUtils::kCenter_Align: return -0.5f;
469 case SkTextUtils::kRight_Align: return -1.0f;
470 }
471 return 0.0f; // go home, msvc...
472 }
473
474 SkScalar ascent() const {
475 // Use the explicit ascent, when specified.
476 // Note: ascent values are negative (relative to the baseline).
477 return fDesc.fAscent ? fDesc.fAscent : fFirstLineAscent;
478 }
479
480 inline static constexpr SkGlyphID kMissingGlyphID = 0;
481
482 const Shaper::TextDesc& fDesc;
483 const SkRect& fBox;
484 const float fHAlignFactor;
485
486 SkFont fFont;
487 const sk_sp<SkFontMgr> fFontMgr;
488 std::unique_ptr<SkShaper> fShaper;
489 sk_sp<SkShapers::Factory> fShapingFactory;
490
491 AutoSTMalloc<64, SkGlyphID> fLineGlyphs;
493 AutoSTMalloc<64, uint32_t> fLineClusters;
495 size_t fLineGlyphCount = 0;
496
497 STArray<64, float, true> fAdvanceBuffer;
498
499 SkPoint fCurrentPosition{ 0, 0 };
500 SkPoint fOffset{ 0, 0 };
501 SkVector fPendingLineAdvance{ 0, 0 };
502 uint32_t fLineCount = 0;
503 float fFirstLineAscent = 0,
504 fLastLineDescent = 0;
505
506 const char* fUTF8 = nullptr; // only valid during shapeLine() calls
507 size_t fUTF8Offset = 0; // current line offset within the original string
508
509 Shaper::Result fResult;
510};
511
512Shaper::Result ShapeImpl(const SkString& txt, const Shaper::TextDesc& desc,
513 const SkRect& box, const sk_sp<SkFontMgr>& fontmgr,
514 const sk_sp<SkShapers::Factory>& shapingFactory,
515 SkSize* shaped_size) {
516 const auto& is_line_break = [](SkUnichar uch) {
517 // TODO: other explicit breaks?
518 return uch == '\r';
519 };
520
521 const char* ptr = txt.c_str();
522 const char* line_start = ptr;
523 const char* begin = ptr;
524 const char* end = ptr + txt.size();
525
526 ResultBuilder rbuilder(desc, box, fontmgr, shapingFactory);
527 while (ptr < end) {
528 if (is_line_break(SkUTF::NextUTF8(&ptr, end))) {
529 rbuilder.shapeLine(line_start, ptr - 1, SkToSizeT(line_start - begin));
530 line_start = ptr;
531 }
532 }
533 rbuilder.shapeLine(line_start, ptr, SkToSizeT(line_start - begin));
534
535 return rbuilder.finalize(shaped_size);
536}
537
538bool result_fits(const Shaper::Result& res, const SkSize& res_size,
539 const SkRect& box, const Shaper::TextDesc& desc) {
540 // optional max line count constraint
541 if (desc.fMaxLines) {
542 const auto line_count = res.fFragments.empty()
543 ? 0
544 : res.fFragments.back().fLineIndex + 1;
545 if (line_count > desc.fMaxLines) {
546 return false;
547 }
548 }
549
550 // geometric constraint
551 return res_size.width() <= box.width() && res_size.height() <= box.height();
552}
553
554Shaper::Result ShapeToFit(const SkString& txt, const Shaper::TextDesc& orig_desc,
555 const SkRect& box, const sk_sp<SkFontMgr>& fontmgr,
556 const sk_sp<SkShapers::Factory>& shapingFactory) {
557 Shaper::Result best_result;
558
559 if (box.isEmpty() || orig_desc.fTextSize <= 0) {
560 return best_result;
561 }
562
563 auto desc = orig_desc;
564
565 const auto min_scale = std::max(desc.fMinTextSize / desc.fTextSize, 0.0f),
566 max_scale = std::max(desc.fMaxTextSize / desc.fTextSize, min_scale);
567
568 float in_scale = min_scale, // maximum scale that fits inside
569 out_scale = max_scale, // minimum scale that doesn't fit
570 try_scale = SkTPin(1.0f, min_scale, max_scale); // current probe
571
572 // Perform a binary search for the best vertical fit (SkShaper already handles
573 // horizontal fitting), starting with the specified text size.
574 //
575 // This hybrid loop handles both the binary search (when in/out extremes are known), and an
576 // exponential search for the extremes.
577 static constexpr size_t kMaxIter = 16;
578 for (size_t i = 0; i < kMaxIter; ++i) {
579 SkASSERT(try_scale >= in_scale && try_scale <= out_scale);
580 desc.fTextSize = try_scale * orig_desc.fTextSize;
581 desc.fLineHeight = try_scale * orig_desc.fLineHeight;
582 desc.fLineShift = try_scale * orig_desc.fLineShift;
583 desc.fAscent = try_scale * orig_desc.fAscent;
584
585 SkSize res_size = {0, 0};
586 auto res = ShapeImpl(txt, desc, box, fontmgr, shapingFactory, &res_size);
587
588 const auto prev_scale = try_scale;
589 if (!result_fits(res, res_size, box, desc)) {
590 out_scale = try_scale;
591 try_scale = (in_scale == min_scale)
592 // initial in_scale not found yet - search exponentially
593 ? std::max(min_scale, try_scale * 0.5f)
594 // in_scale found - binary search
595 : (in_scale + out_scale) * 0.5f;
596 } else {
597 // It fits - so it's a candidate.
598 best_result = std::move(res);
599 best_result.fScale = try_scale;
600
601 in_scale = try_scale;
602 try_scale = (out_scale == max_scale)
603 // initial out_scale not found yet - search exponentially
604 ? std::min(max_scale, try_scale * 2)
605 // out_scale found - binary search
606 : (in_scale + out_scale) * 0.5f;
607 }
608
609 if (try_scale == prev_scale) {
610 // no more progress
611 break;
612 }
613 }
614
615 return best_result;
616}
617
618
619// Applies capitalization rules.
620class AdjustedText {
621public:
622 AdjustedText(const SkString& txt, const Shaper::TextDesc& desc, SkUnicode* unicode)
623 : fText(txt) {
624 switch (desc.fCapitalization) {
626 break;
628 if (unicode) {
629 *fText.writable() = unicode->toUpper(*fText);
630 }
631 break;
632 }
633 }
634
635 operator const SkString&() const { return *fText; }
636
637private:
639};
640
641} // namespace
642
644 const sk_sp<SkFontMgr>& fontmgr, const sk_sp<SkShapers::Factory>& shapingFactory) {
645 const AdjustedText adjText(text, desc, shapingFactory->getUnicode());
646
647 return (desc.fResize == ResizePolicy::kScaleToFit ||
648 desc.fResize == ResizePolicy::kDownscaleToFit) // makes no sense in point mode
649 ? Result()
650 : ShapeImpl(adjText, desc, SkRect::MakeEmpty().makeOffset(point.x(), point.y()),
651 fontmgr, shapingFactory, nullptr);
652}
653
655 const sk_sp<SkFontMgr>& fontmgr, const sk_sp<SkShapers::Factory>& shapingFactory) {
656 const AdjustedText adjText(text, desc, shapingFactory->getUnicode());
657
658 switch(desc.fResize) {
660 return ShapeImpl(adjText, desc, box, fontmgr, shapingFactory, nullptr);
661 case ResizePolicy::kScaleToFit:
662 return ShapeToFit(adjText, desc, box, fontmgr, shapingFactory);
663 case ResizePolicy::kDownscaleToFit: {
664 SkSize size;
665 auto result = ShapeImpl(adjText, desc, box, fontmgr, shapingFactory, &size);
666
667 return result_fits(result, size, box, desc)
668 ? result
669 : ShapeToFit(adjText, desc, box, fontmgr, shapingFactory);
670 }
671 }
672
674}
675
676SkRect Shaper::ShapedGlyphs::computeBounds(BoundsType btype) const {
677 auto bounds = SkRect::MakeEmpty();
678
679 AutoSTArray<16, SkRect> glyphBounds;
680
681 size_t offset = 0;
682 for (const auto& run : fRuns) {
683 SkRect font_bounds;
684 if (btype == BoundsType::kConservative) {
685 font_bounds = SkFontPriv::GetFontBounds(run.fFont);
686
687 // Empty font bounds is likely a font bug -- fall back to tight bounds.
688 if (font_bounds.isEmpty()) {
689 btype = BoundsType::kTight;
690 }
691 }
692
693 switch (btype) {
694 case BoundsType::kConservative: {
695 SkRect run_bounds;
696 run_bounds.setBounds(fGlyphPos.data() + offset, SkToInt(run.fSize));
697 run_bounds.fLeft += font_bounds.left();
698 run_bounds.fTop += font_bounds.top();
699 run_bounds.fRight += font_bounds.right();
700 run_bounds.fBottom += font_bounds.bottom();
701
702 bounds.join(run_bounds);
703 } break;
704 case BoundsType::kTight: {
705 glyphBounds.reset(SkToInt(run.fSize));
706 run.fFont.getBounds(fGlyphIDs.data() + offset,
707 SkToInt(run.fSize), glyphBounds.data(), nullptr);
708
709 for (size_t i = 0; i < run.fSize; ++i) {
710 bounds.join(glyphBounds[SkToInt(i)].makeOffset(fGlyphPos[offset + i]));
711 }
712 } break;
713 }
714
715 offset += run.fSize;
716 }
717
718 return bounds;
719}
720
722 const SkPoint& origin,
723 const SkPaint& paint) const {
724 size_t offset = 0;
725 for (const auto& run : fRuns) {
726 canvas->drawGlyphs(SkToInt(run.fSize),
727 fGlyphIDs.data() + offset,
728 fGlyphPos.data() + offset,
729 origin,
730 run.fFont,
731 paint);
732 offset += run.fSize;
733 }
734}
735
736SkRect Shaper::Result::computeVisualBounds() const {
737 auto bounds = SkRect::MakeEmpty();
738
739 for (const auto& frag: fFragments) {
740 bounds.join(frag.fGlyphs.computeBounds(ShapedGlyphs::BoundsType::kTight)
741 .makeOffset(frag.fOrigin));
742 }
743
744 return bounds;
745}
746
747#if !defined(SK_DISABLE_LEGACY_SHAPER_FACTORY)
749 const sk_sp<SkFontMgr>& fontmgr) {
750 return Shaper::Shape(text, desc, point, fontmgr, SkShapers::BestAvailable());
751}
752
754 const sk_sp<SkFontMgr>& fontmgr) {
755 return Shaper::Shape(text, desc, box, fontmgr, SkShapers::BestAvailable());
756}
757
758#endif
759
760} // namespace skottie
@ kLeft_Align
static void info(const char *fmt,...) SK_PRINTF_LIKE(1
Definition: DM.cpp:213
uint16_t glyphs[5]
Definition: FontMgrTest.cpp:46
SkPoint pos
#define SkUNREACHABLE
Definition: SkAssert.h:135
#define SkASSERT(cond)
Definition: SkAssert.h:116
static bool is_whitespace(char c)
@ kNone
glyph outlines unchanged
LoopControlFlowInfo fResult
#define SK_ScalarMax
Definition: SkScalar.h:24
static constexpr const T & SkTPin(const T &x, const T &lo, const T &hi)
Definition: SkTPin.h:19
constexpr size_t SkToSizeT(S x)
Definition: SkTo.h:31
constexpr int SkToInt(S x)
Definition: SkTo.h:29
int32_t SkUnichar
Definition: SkTypes.h:175
uint16_t SkGlyphID
Definition: SkTypes.h:179
uint32_t SkFourByteTag
Definition: SkTypes.h:166
static constexpr SkFourByteTag SkSetFourByteTag(char a, char b, char c, char d)
Definition: SkTypes.h:167
static void draw(SkCanvas *canvas, SkRect &target, int x, int y)
Definition: aaclip.cpp:27
Shape
Definition: aaxfermodes.cpp:43
void drawGlyphs(int count, const SkGlyphID glyphs[], const SkPoint positions[], const uint32_t clusters[], int textByteCount, const char utf8text[], SkPoint origin, const SkFont &font, const SkPaint &paint)
static sk_sp< SkFontMgr > RefEmpty()
Definition: SkFontMgr.cpp:154
static SkRect GetFontBounds(const SkFont &)
Definition: SkFont.cpp:354
Definition: SkFont.h:35
void setSubpixel(bool subpixel)
Definition: SkFont.cpp:109
void setLinearMetrics(bool linearMetrics)
Definition: SkFont.cpp:112
SkTypeface * getTypeface() const
Definition: SkFont.h:208
void getWidths(const SkGlyphID glyphs[], int count, SkScalar widths[], SkRect bounds[]) const
Definition: SkFont.h:366
void setBaselineSnap(bool baselineSnap)
Definition: SkFont.cpp:118
void setEdging(Edging edging)
Definition: SkFont.cpp:121
void setHinting(SkFontHinting hintingLevel)
Definition: SkFont.cpp:125
@ kAntiAlias
may have transparent pixels on glyph edges
static std::unique_ptr< FontRunIterator > MakeFontMgrRunIterator(const char *utf8, size_t utf8Bytes, const SkFont &font, sk_sp< SkFontMgr > fallback)
Definition: SkShaper.cpp:187
virtual std::unique_ptr< SkShaper::BiDiRunIterator > makeBidiRunIterator(const char *utf8, size_t utf8Bytes, uint8_t bidiLevel)=0
virtual std::unique_ptr< SkShaper > makeShaper(sk_sp< SkFontMgr > fallback)=0
virtual SkUnicode * getUnicode()=0
virtual std::unique_ptr< SkShaper::ScriptRunIterator > makeScriptRunIterator(const char *utf8, size_t utf8Bytes, SkFourByteTag script)=0
T * init(Args &&... args)
Definition: SkTLazy.h:45
bool isValid() const
Definition: SkTLazy.h:77
SkFontStyle fontStyle() const
Definition: SkTypeface.h:55
void reset(int count)
Definition: SkTemplates.h:195
const T * data() const
Definition: SkTemplates.h:251
const Paint & paint
Definition: color_source.cc:38
static const char * begin(const StringSlice &s)
Definition: editor.cpp:252
float SkScalar
Definition: extension.cpp:12
glong glong end
GAsyncResult * result
static float max(float r, float g, float b)
Definition: hsl.cpp:49
static float min(float r, float g, float b)
Definition: hsl.cpp:48
std::u16string text
Optional< SkRect > bounds
Definition: SkRecords.h:189
SKSHAPER_API sk_sp< Factory > Factory()
SKSHAPER_API std::unique_ptr< SkShaper > PrimitiveText()
sk_sp< Factory > BestAvailable()
SK_SPI SkUnichar NextUTF8(const char **ptr, const char *end)
Definition: SkUTF.cpp:118
@ kNone
Definition: layer.h:53
it will be possible to load the file into Perfetto s trace viewer disable asset Prevents usage of any non test fonts unless they were explicitly Loaded via prefetched default font Indicates whether the embedding started a prefetch of the default font manager before creating the engine run In non interactive keep the shell running after the Dart script has completed enable serial On low power devices with low core running concurrent GC tasks on threads can cause them to contend with the UI thread which could potentially lead to jank This option turns off all concurrent GC activities domain network JSON encoded network policy per domain This overrides the DisallowInsecureConnections switch Embedder can specify whether to allow or disallow insecure connections at a domain level old gen heap size
Definition: switches.h:259
Definition: run.py:1
static SkColor4f transform(SkColor4f c, SkColorSpace *src, SkColorSpace *dst)
Definition: p3.cpp:47
SeparatedVector2 offset
SkScalar fAscent
distance to reserve above baseline, typically negative
Definition: SkFontMetrics.h:54
SkScalar fDescent
distance to reserve below baseline, typically positive
Definition: SkFontMetrics.h:55
float fX
x-axis value
Definition: SkPoint_impl.h:164
float fY
y-axis value
Definition: SkPoint_impl.h:165
constexpr float y() const
Definition: SkPoint_impl.h:187
constexpr float x() const
Definition: SkPoint_impl.h:181
static constexpr SkRect MakeEmpty()
Definition: SkRect.h:595
SkScalar fBottom
larger y-axis bounds
Definition: extension.cpp:17
constexpr float left() const
Definition: SkRect.h:734
constexpr float top() const
Definition: SkRect.h:741
SkScalar fLeft
smaller x-axis bounds
Definition: extension.cpp:14
constexpr float x() const
Definition: SkRect.h:720
constexpr float y() const
Definition: SkRect.h:727
SkScalar fRight
larger x-axis bounds
Definition: extension.cpp:16
constexpr float height() const
Definition: SkRect.h:769
constexpr float right() const
Definition: SkRect.h:748
void setBounds(const SkPoint pts[], int count)
Definition: SkRect.h:881
constexpr float centerY() const
Definition: SkRect.h:785
constexpr float width() const
Definition: SkRect.h:762
bool isEmpty() const
Definition: SkRect.h:693
constexpr float bottom() const
Definition: SkRect.h:755
SkScalar fTop
smaller y-axis bounds
Definition: extension.cpp:15
Definition: SkSize.h:52
static constexpr SkSize Make(SkScalar w, SkScalar h)
Definition: SkSize.h:56
SkScalar width() const
Definition: SkSize.h:76
SkScalar height() const
Definition: SkSize.h:77
float y
Definition: SkM44.h:20
LinebreakPolicy fLinebreak
Definition: TextShaper.h:167
const char * fFontFamily
Definition: TextShaper.h:173