Flutter Engine
The Flutter Engine
Loading...
Searching...
No Matches
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) {
68 fShaper = SkShapers::Primitive::PrimitiveText();
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().
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
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
643Shaper::Result Shaper::Shape(const SkString& text, const TextDesc& desc, const SkPoint& point,
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
654Shaper::Result Shaper::Shape(const SkString& text, const TextDesc& desc, const SkRect& box,
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) {
659 case ResizePolicy::kNone:
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
721void Shaper::ShapedGlyphs::draw(SkCanvas* canvas,
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)
748Shaper::Result Shaper::Shape(const SkString& text, const TextDesc& desc, const SkPoint& point,
749 const sk_sp<SkFontMgr>& fontmgr) {
750 return Shaper::Shape(text, desc, point, fontmgr, SkShapers::BestAvailable());
751}
752
753Shaper::Result Shaper::Shape(const SkString& text, const TextDesc& desc, const SkRect& box,
754 const sk_sp<SkFontMgr>& fontmgr) {
755 return Shaper::Shape(text, desc, box, fontmgr, SkShapers::BestAvailable());
756}
757
758#endif
759
760} // namespace skottie
static void info(const char *fmt,...) SK_PRINTF_LIKE(1
Definition DM.cpp:213
uint16_t glyphs[5]
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
#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
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 SkRect GetFontBounds(const SkFont &)
Definition SkFont.cpp:354
void setSubpixel(bool subpixel)
Definition SkFont.cpp:109
void setLinearMetrics(bool linearMetrics)
Definition SkFont.cpp:112
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 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
const T * data() const
@ kTrackFragmentAdvanceAscent
Definition TextShaper.h:150
const Paint & paint
static const char * begin(const StringSlice &s)
Definition editor.cpp:252
float SkScalar
Definition extension.cpp:12
glong glong end
GAsyncResult * result
std::u16string text
SKSHAPER_API sk_sp< Factory > Factory()
sk_sp< Factory > BestAvailable()
SK_SPI SkUnichar NextUTF8(const char **ptr, const char *end)
Definition SkUTF.cpp:118
const myers::Point & get(const myers::Segment &)
Definition run.py:1
Point offset
SkScalar fAscent
distance to reserve above baseline, typically negative
SkScalar fDescent
distance to reserve below baseline, typically positive
float fX
x-axis value
float fY
y-axis value
constexpr float y() const
constexpr float x() const
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
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
std::vector< Fragment > fFragments
Definition TextShaper.h:79
SkRect computeVisualBounds() const
LinebreakPolicy fLinebreak
Definition TextShaper.h:167