Flutter Engine
The Flutter Engine
Loading...
Searching...
No Matches
OneLineShaper.cpp
Go to the documentation of this file.
1// Copyright 2019 Google LLC.
3
6#include "src/base/SkUTF.h"
7
8#include <algorithm>
9#include <cstdint>
10#include <unordered_set>
11
12using namespace skia_private;
13
14namespace skia {
15namespace textlayout {
16
18
19 fCurrentRun->commit();
20
21 auto oldUnresolvedCount = fUnresolvedBlocks.size();
22/*
23 SkDebugf("Run [%zu:%zu)\n", fCurrentRun->fTextRange.start, fCurrentRun->fTextRange.end);
24 for (size_t i = 0; i < fCurrentRun->size(); ++i) {
25 SkDebugf("[%zu] %hu %u %f\n", i, fCurrentRun->fGlyphs[i], fCurrentRun->fClusterIndexes[i], fCurrentRun->fPositions[i].fX);
26 }
27*/
28 // Find all unresolved blocks
29 sortOutGlyphs([&](GlyphRange block){
30 if (block.width() == 0) {
31 return;
32 }
33 addUnresolvedWithRun(block);
34 });
35
36 // Fill all the gaps between unresolved blocks with resolved ones
37 if (oldUnresolvedCount == fUnresolvedBlocks.size()) {
38 // No unresolved blocks added - we resolved the block with one run entirely
39 addFullyResolved();
40 return;
41 } else if (oldUnresolvedCount == fUnresolvedBlocks.size() - 1) {
42 auto& unresolved = fUnresolvedBlocks.back();
43 if (fCurrentRun->textRange() == unresolved.fText) {
44 // Nothing was resolved; preserve the initial run if it makes sense
45 auto& front = fUnresolvedBlocks.front();
46 if (front.fRun != nullptr) {
47 unresolved.fRun = front.fRun;
48 unresolved.fGlyphs = front.fGlyphs;
49 }
50 return;
51 }
52 }
53
54 fillGaps(oldUnresolvedCount);
55}
56
57#ifdef SK_DEBUG
58void OneLineShaper::printState() {
59 SkDebugf("Resolved: %zu\n", fResolvedBlocks.size());
60 for (auto& resolved : fResolvedBlocks) {
61 if (resolved.fRun == nullptr) {
62 SkDebugf("[%zu:%zu) unresolved\n",
63 resolved.fText.start, resolved.fText.end);
64 continue;
65 }
66 SkString name("???");
67 if (resolved.fRun->fFont.getTypeface() != nullptr) {
68 resolved.fRun->fFont.getTypeface()->getFamilyName(&name);
69 }
70 SkDebugf("[%zu:%zu) ", resolved.fGlyphs.start, resolved.fGlyphs.end);
71 SkDebugf("[%zu:%zu) with %s\n",
72 resolved.fText.start, resolved.fText.end,
73 name.c_str());
74 }
75
76 auto size = fUnresolvedBlocks.size();
77 SkDebugf("Unresolved: %zu\n", size);
78 for (const auto& unresolved : fUnresolvedBlocks) {
79 SkDebugf("[%zu:%zu)\n", unresolved.fText.start, unresolved.fText.end);
80 }
81}
82#endif
83
84void OneLineShaper::fillGaps(size_t startingCount) {
85 // Fill out gaps between all unresolved blocks
86 TextRange resolvedTextLimits = fCurrentRun->fTextRange;
87 if (!fCurrentRun->leftToRight()) {
88 std::swap(resolvedTextLimits.start, resolvedTextLimits.end);
89 }
90 TextIndex resolvedTextStart = resolvedTextLimits.start;
91 GlyphIndex resolvedGlyphsStart = 0;
92
93 auto begin = fUnresolvedBlocks.begin();
94 auto end = fUnresolvedBlocks.end();
95 begin += startingCount; // Skip the old ones, do the new ones
96 TextRange prevText = EMPTY_TEXT;
97 for (; begin != end; ++begin) {
98 auto& unresolved = *begin;
99
100 if (unresolved.fText == prevText) {
101 // Clean up repetitive blocks that appear inside the same grapheme block
102 unresolved.fText = EMPTY_TEXT;
103 continue;
104 } else {
105 prevText = unresolved.fText;
106 }
107
108 TextRange resolvedText(resolvedTextStart, fCurrentRun->leftToRight() ? unresolved.fText.start : unresolved.fText.end);
109 if (resolvedText.width() > 0) {
110 if (!fCurrentRun->leftToRight()) {
111 std::swap(resolvedText.start, resolvedText.end);
112 }
113
114 GlyphRange resolvedGlyphs(resolvedGlyphsStart, unresolved.fGlyphs.start);
115 RunBlock resolved(fCurrentRun, resolvedText, resolvedGlyphs, resolvedGlyphs.width());
116
117 if (resolvedGlyphs.width() == 0) {
118 // Extend the unresolved block with an empty resolved
119 if (unresolved.fText.end <= resolved.fText.start) {
120 unresolved.fText.end = resolved.fText.end;
121 }
122 if (unresolved.fText.start >= resolved.fText.end) {
123 unresolved.fText.start = resolved.fText.start;
124 }
125 } else {
126 fResolvedBlocks.emplace_back(resolved);
127 }
128 }
129 resolvedGlyphsStart = unresolved.fGlyphs.end;
130 resolvedTextStart = fCurrentRun->leftToRight()
131 ? unresolved.fText.end
132 : unresolved.fText.start;
133 }
134
135 TextRange resolvedText(resolvedTextStart,resolvedTextLimits.end);
136 if (resolvedText.width() > 0) {
137 if (!fCurrentRun->leftToRight()) {
138 std::swap(resolvedText.start, resolvedText.end);
139 }
140
141 GlyphRange resolvedGlyphs(resolvedGlyphsStart, fCurrentRun->size());
142 RunBlock resolved(fCurrentRun, resolvedText, resolvedGlyphs, resolvedGlyphs.width());
143 fResolvedBlocks.emplace_back(resolved);
144 }
145}
146
147void OneLineShaper::finish(const Block& block, SkScalar height, SkScalar& advanceX) {
148 auto blockText = block.fRange;
149
150 // Add all unresolved blocks to resolved blocks
151 while (!fUnresolvedBlocks.empty()) {
152 auto unresolved = fUnresolvedBlocks.front();
153 fUnresolvedBlocks.pop_front();
154 if (unresolved.fText.width() == 0) {
155 continue;
156 }
157 fResolvedBlocks.emplace_back(unresolved);
158 fUnresolvedGlyphs += unresolved.fGlyphs.width();
159 fParagraph->addUnresolvedCodepoints(unresolved.fText);
160 }
161
162 // Sort all pieces by text
163 std::sort(fResolvedBlocks.begin(), fResolvedBlocks.end(),
164 [](const RunBlock& a, const RunBlock& b) {
165 return a.fText.start < b.fText.start;
166 });
167
168 // Go through all of them
169 size_t lastTextEnd = blockText.start;
170 for (auto& resolvedBlock : fResolvedBlocks) {
171
172 if (resolvedBlock.fText.end <= blockText.start) {
173 continue;
174 }
175
176 if (resolvedBlock.fRun != nullptr) {
177 fParagraph->fFontSwitches.emplace_back(resolvedBlock.fText.start, resolvedBlock.fRun->fFont);
178 }
179
180 auto run = resolvedBlock.fRun;
181 auto glyphs = resolvedBlock.fGlyphs;
182 auto text = resolvedBlock.fText;
183 if (lastTextEnd != text.start) {
184 SkDEBUGF("Text ranges mismatch: ...:%zu] - [%zu:%zu] (%zu-%zu)\n",
185 lastTextEnd, text.start, text.end, glyphs.start, glyphs.end);
186 SkASSERT(false);
187 }
188 lastTextEnd = text.end;
189
190 if (resolvedBlock.isFullyResolved()) {
191 // Just move the entire run
192 resolvedBlock.fRun->fIndex = this->fParagraph->fRuns.size();
193 this->fParagraph->fRuns.emplace_back(*resolvedBlock.fRun);
194 resolvedBlock.fRun.reset();
195 continue;
196 } else if (run == nullptr) {
197 continue;
198 }
199
200 auto runAdvance = SkVector::Make(run->posX(glyphs.end) - run->posX(glyphs.start), run->fAdvance.fY);
202 run->fFont,
203 run->fBidiLevel,
204 runAdvance,
205 glyphs.width(),
206 SkShaper::RunHandler::Range(text.start - run->fClusterStart, text.width())
207 };
208 this->fParagraph->fRuns.emplace_back(
209 this->fParagraph,
210 info,
211 run->fClusterStart,
212 height,
213 block.fStyle.getHalfLeading(),
214 block.fStyle.getBaselineShift(),
215 this->fParagraph->fRuns.size(),
216 advanceX
217 );
218 auto piece = &this->fParagraph->fRuns.back();
219
220 // TODO: Optimize copying
221 SkPoint zero = {run->fPositions[glyphs.start].fX, 0};
222 for (size_t i = glyphs.start; i <= glyphs.end; ++i) {
223
224 auto index = i - glyphs.start;
225 if (i < glyphs.end) {
226 // There are only n glyphs in a run, not n+1.
227 piece->fGlyphs[index] = run->fGlyphs[i];
228
229 // fClusterIndexes n+1 is already set to the end of the run.
230 // Do not attempt to overwrite this value with the cluster index
231 // that starts the next Run.
232 // It is assumed later that all clusters in a Run are contained by the Run.
233 piece->fClusterIndexes[index] = run->fClusterIndexes[i];
234 }
235 piece->fPositions[index] = run->fPositions[i] - zero;
236 piece->fOffsets[index] = run->fOffsets[i];
237 piece->addX(index, advanceX);
238 }
239
240 // Carve out the line text out of the entire run text
241 fAdvance.fX += runAdvance.fX;
242 fAdvance.fY = std::max(fAdvance.fY, runAdvance.fY);
243 }
244
245 advanceX = fAdvance.fX;
246 if (lastTextEnd != blockText.end) {
247 SkDEBUGF("Last range mismatch: %zu - %zu\n", lastTextEnd, blockText.end);
248 SkASSERT(false);
249 }
250}
251
252// Make it [left:right) regardless of a text direction
253TextRange OneLineShaper::normalizeTextRange(GlyphRange glyphRange) {
254
255 if (fCurrentRun->leftToRight()) {
256 return TextRange(clusterIndex(glyphRange.start), clusterIndex(glyphRange.end));
257 } else {
258 return TextRange(clusterIndex(glyphRange.end - 1),
259 glyphRange.start > 0
260 ? clusterIndex(glyphRange.start - 1)
261 : fCurrentRun->fTextRange.end);
262 }
263}
264
265void OneLineShaper::addFullyResolved() {
266 if (this->fCurrentRun->size() == 0) {
267 return;
268 }
269 RunBlock resolved(fCurrentRun,
270 this->fCurrentRun->fTextRange,
271 GlyphRange(0, this->fCurrentRun->size()),
272 this->fCurrentRun->size());
273 fResolvedBlocks.emplace_back(resolved);
274}
275
276void OneLineShaper::addUnresolvedWithRun(GlyphRange glyphRange) {
277 auto extendedText = this->clusteredText(glyphRange); // It also modifies glyphRange if needed
278 RunBlock unresolved(fCurrentRun, extendedText, glyphRange, 0);
279 if (unresolved.fGlyphs.width() == fCurrentRun->size()) {
280 SkASSERT(unresolved.fText.width() == fCurrentRun->fTextRange.width());
281 } else if (!fUnresolvedBlocks.empty()) {
282 auto& lastUnresolved = fUnresolvedBlocks.back();
283 if (lastUnresolved.fRun != nullptr &&
284 lastUnresolved.fRun->fIndex == fCurrentRun->fIndex) {
285
286 if (lastUnresolved.fText.end == unresolved.fText.start) {
287 // Two pieces next to each other - can join them
288 lastUnresolved.fText.end = unresolved.fText.end;
289 lastUnresolved.fGlyphs.end = glyphRange.end;
290 return;
291 } else if(lastUnresolved.fText == unresolved.fText) {
292 // Nothing was resolved; ignore it
293 return;
294 } else if (lastUnresolved.fText.contains(unresolved.fText)) {
295 // We get here for the very first unresolved piece
296 return;
297 } else if (lastUnresolved.fText.intersects(unresolved.fText)) {
298 // Few pieces of the same unresolved text block can ignore the second one
299 lastUnresolved.fGlyphs.start = std::min(lastUnresolved.fGlyphs.start, glyphRange.start);
300 lastUnresolved.fGlyphs.end = std::max(lastUnresolved.fGlyphs.end, glyphRange.end);
301 lastUnresolved.fText = this->clusteredText(lastUnresolved.fGlyphs);
302 return;
303 }
304 }
305 }
306 fUnresolvedBlocks.emplace_back(unresolved);
307}
308
309// Glue whitespaces to the next/prev unresolved blocks
310// (so we don't have chinese text with english whitespaces broken into millions of tiny runs)
311void OneLineShaper::sortOutGlyphs(std::function<void(GlyphRange)>&& sortOutUnresolvedBLock) {
312
313 GlyphRange block = EMPTY_RANGE;
314 bool graphemeResolved = false;
315 TextIndex graphemeStart = EMPTY_INDEX;
316 for (size_t i = 0; i < fCurrentRun->size(); ++i) {
317
318 ClusterIndex ci = clusterIndex(i);
319 // Removing all pretty optimizations for whitespaces
320 // because they get in a way of grapheme rounding
321 // Inspect the glyph
322 auto glyph = fCurrentRun->fGlyphs[i];
323
324 GraphemeIndex gi = fParagraph->findPreviousGraphemeBoundary(ci);
325 if ((fCurrentRun->leftToRight() ? gi > graphemeStart : gi < graphemeStart) || graphemeStart == EMPTY_INDEX) {
326 // This is the Flutter change
327 // Do not count control codepoints as unresolved
328 bool isControl8 = fParagraph->codeUnitHasProperty(ci,
330 // We only count glyph resolved if all the glyphs in its grapheme are resolved
331 graphemeResolved = glyph != 0 || isControl8;
332 graphemeStart = gi;
333 } else if (glyph == 0) {
334 // Found unresolved glyph - the entire grapheme is unresolved now
335 graphemeResolved = false;
336 }
337
338 if (!graphemeResolved) { // Unresolved glyph and not control codepoint
339 if (block.start == EMPTY_INDEX) {
340 // Start new unresolved block
341 block.start = i;
342 block.end = EMPTY_INDEX;
343 } else {
344 // Keep skipping unresolved block
345 }
346 } else { // Resolved glyph or control codepoint
347 if (block.start == EMPTY_INDEX) {
348 // Keep skipping resolved code points
349 } else {
350 // This is the end of unresolved block
351 block.end = i;
352 sortOutUnresolvedBLock(block);
353 block = EMPTY_RANGE;
354 }
355 }
356 }
357
358 // One last block could have been left
359 if (block.start != EMPTY_INDEX) {
360 block.end = fCurrentRun->size();
361 sortOutUnresolvedBLock(block);
362 }
363}
364
365void OneLineShaper::iterateThroughFontStyles(TextRange textRange,
366 SkSpan<Block> styleSpan,
367 const ShapeSingleFontVisitor& visitor) {
368 Block combinedBlock;
370
371 auto addFeatures = [&features](const Block& block) {
372 for (auto& ff : block.fStyle.getFontFeatures()) {
373 if (ff.fName.size() != 4) {
374 SkDEBUGF("Incorrect font feature: %s=%d\n", ff.fName.c_str(), ff.fValue);
375 continue;
376 }
377 SkShaper::Feature feature = {
378 SkSetFourByteTag(ff.fName[0], ff.fName[1], ff.fName[2], ff.fName[3]),
379 SkToU32(ff.fValue),
380 block.fRange.start,
381 block.fRange.end
382 };
383 features.emplace_back(feature);
384 }
385 // Disable ligatures if letter spacing is enabled.
386 if (block.fStyle.getLetterSpacing() > 0) {
388 SkSetFourByteTag('l', 'i', 'g', 'a'), 0, block.fRange.start, block.fRange.end
389 });
390 }
391 };
392
393 for (auto& block : styleSpan) {
394 BlockRange blockRange(std::max(block.fRange.start, textRange.start), std::min(block.fRange.end, textRange.end));
395 if (blockRange.empty()) {
396 continue;
397 }
398 SkASSERT(combinedBlock.fRange.width() == 0 || combinedBlock.fRange.end == block.fRange.start);
399
400 if (!combinedBlock.fRange.empty()) {
401 if (block.fStyle.matchOneAttribute(StyleType::kFont, combinedBlock.fStyle)) {
402 combinedBlock.add(blockRange);
403 addFeatures(block);
404 continue;
405 }
406 // Resolve all characters in the block for this style
407 visitor(combinedBlock, features);
408 }
409
410 combinedBlock.fRange = blockRange;
411 combinedBlock.fStyle = block.fStyle;
412 features.clear();
413 addFeatures(block);
414 }
415
416 visitor(combinedBlock, features);
417#ifdef SK_DEBUG
418 //printState();
419#endif
420}
421
422void OneLineShaper::matchResolvedFonts(const TextStyle& textStyle,
423 const TypefaceVisitor& visitor) {
424 std::vector<sk_sp<SkTypeface>> typefaces = fParagraph->fFontCollection->findTypefaces(textStyle.getFontFamilies(), textStyle.getFontStyle(), textStyle.getFontArguments());
425
426 for (const auto& typeface : typefaces) {
427 if (visitor(typeface) == Resolved::Everything) {
428 // Resolved everything
429 return;
430 }
431 }
432
433 if (fParagraph->fFontCollection->fontFallbackEnabled()) {
434 // Give fallback a clue
435 // Some unresolved subblocks might be resolved with different fallback fonts
436 std::vector<RunBlock> hopelessBlocks;
437 while (!fUnresolvedBlocks.empty()) {
438 auto unresolvedRange = fUnresolvedBlocks.front().fText;
439 auto unresolvedText = fParagraph->text(unresolvedRange);
440 const char* ch = unresolvedText.begin();
441 // We have the global cache for all already found typefaces for SkUnichar
442 // but we still need to keep track of all SkUnichars used in this unresolved block
443 THashSet<SkUnichar> alreadyTriedCodepoints;
444 THashSet<SkTypefaceID> alreadyTriedTypefaces;
445 while (true) {
446 if (ch == unresolvedText.end()) {
447 // Not a single codepoint could be resolved but we finished the block
448 hopelessBlocks.push_back(fUnresolvedBlocks.front());
449 fUnresolvedBlocks.pop_front();
450 break;
451 }
452
453 // See if we can switch to the next DIFFERENT codepoint/emoji
454 SkUnichar codepoint = -1;
455 SkUnichar emojiStart = -1;
456 // We may loop until we find a new codepoint/emoji run
457 while (ch != unresolvedText.end()) {
459 fParagraph->fUnicode.get(),
460 &ch,
461 unresolvedText.end());
462 if (emojiStart != -1) {
463 // We do not keep a cache of emoji runs, but we need to move the cursor
464 break;
465 } else {
466 codepoint = SkUTF::NextUTF8WithReplacement(&ch, unresolvedText.end());
467 if (!alreadyTriedCodepoints.contains(codepoint)) {
468 alreadyTriedCodepoints.add(codepoint);
469 break;
470 }
471 }
472 }
473
474 SkASSERT(codepoint != -1 || emojiStart != -1);
475
476 sk_sp<SkTypeface> typeface = nullptr;
477 if (emojiStart == -1) {
478 // First try to find in in a cache
479 FontKey fontKey(codepoint, textStyle.getFontStyle(), textStyle.getLocale());
480 auto found = fFallbackFonts.find(fontKey);
481 if (found != nullptr) {
482 typeface = *found;
483 }
484 if (typeface == nullptr) {
485 typeface = fParagraph->fFontCollection->defaultFallback(
486 codepoint,
487 textStyle.getFontStyle(),
488 textStyle.getLocale());
489 if (typeface != nullptr) {
490 fFallbackFonts.set(fontKey, typeface);
491 }
492 }
493 } else {
494 typeface = fParagraph->fFontCollection->defaultEmojiFallback(
495 emojiStart,
496 textStyle.getFontStyle(),
497 textStyle.getLocale());
498 }
499
500 if (typeface == nullptr) {
501 // There is no fallback font for this character,
502 // so move on to the next character.
503 continue;
504 }
505
506 // Check if we already tried this font on this text range
507 if (!alreadyTriedTypefaces.contains(typeface->uniqueID())) {
508 alreadyTriedTypefaces.add(typeface->uniqueID());
509 } else {
510 continue;
511 }
512
513 auto resolvedBlocksBefore = fResolvedBlocks.size();
514 auto resolved = visitor(typeface);
515 if (resolved == Resolved::Everything) {
516 if (hopelessBlocks.empty()) {
517 // Resolved everything, no need to try another font
518 return;
519 } else if (resolvedBlocksBefore < fResolvedBlocks.size()) {
520 // There are some resolved blocks
521 resolved = Resolved::Something;
522 } else {
523 // All blocks are hopeless
524 resolved = Resolved::Nothing;
525 }
526 }
527
528 if (resolved == Resolved::Something) {
529 // Resolved something, no need to try another codepoint
530 break;
531 }
532 }
533 }
534
535 // Return hopeless blocks back
536 for (auto& block : hopelessBlocks) {
537 fUnresolvedBlocks.emplace_front(block);
538 }
539 }
540}
541
542bool OneLineShaper::iterateThroughShapingRegions(const ShapeVisitor& shape) {
543
544 size_t bidiIndex = 0;
545
546 SkScalar advanceX = 0;
547 for (auto& placeholder : fParagraph->fPlaceholders) {
548
549 if (placeholder.fTextBefore.width() > 0) {
550 // Shape the text by bidi regions
551 while (bidiIndex < fParagraph->fBidiRegions.size()) {
552 SkUnicode::BidiRegion& bidiRegion = fParagraph->fBidiRegions[bidiIndex];
553 auto start = std::max(bidiRegion.start, placeholder.fTextBefore.start);
554 auto end = std::min(bidiRegion.end, placeholder.fTextBefore.end);
555
556 // Set up the iterators (the style iterator points to a bigger region that it could
557 TextRange textRange(start, end);
558 auto blockRange = fParagraph->findAllBlocks(textRange);
559 if (!blockRange.empty()) {
560 SkSpan<Block> styleSpan(fParagraph->blocks(blockRange));
561
562 // Shape the text between placeholders
563 if (!shape(textRange, styleSpan, advanceX, start, bidiRegion.level)) {
564 return false;
565 }
566 }
567
568 if (end == bidiRegion.end) {
569 ++bidiIndex;
570 } else /*if (end == placeholder.fTextBefore.end)*/ {
571 break;
572 }
573 }
574 }
575
576 if (placeholder.fRange.width() == 0) {
577 continue;
578 }
579
580 // Get the placeholder font
581 std::vector<sk_sp<SkTypeface>> typefaces = fParagraph->fFontCollection->findTypefaces(
582 placeholder.fTextStyle.getFontFamilies(),
583 placeholder.fTextStyle.getFontStyle(),
584 placeholder.fTextStyle.getFontArguments());
585 sk_sp<SkTypeface> typeface = typefaces.empty() ? nullptr : typefaces.front();
586 SkFont font(typeface, placeholder.fTextStyle.getFontSize());
587
588 // "Shape" the placeholder
589 uint8_t bidiLevel = (bidiIndex < fParagraph->fBidiRegions.size())
590 ? fParagraph->fBidiRegions[bidiIndex].level
591 : 2;
593 font,
594 bidiLevel,
595 SkPoint::Make(placeholder.fStyle.fWidth, placeholder.fStyle.fHeight),
596 1,
597 SkShaper::RunHandler::Range(0, placeholder.fRange.width())
598 };
599 auto& run = fParagraph->fRuns.emplace_back(this->fParagraph,
600 runInfo,
601 placeholder.fRange.start,
602 0.0f,
603 0.0f,
604 false,
605 fParagraph->fRuns.size(),
606 advanceX);
607
608 run.fPositions[0] = { advanceX, 0 };
609 run.fOffsets[0] = {0, 0};
610 run.fClusterIndexes[0] = 0;
611 run.fPlaceholderIndex = &placeholder - fParagraph->fPlaceholders.begin();
612 advanceX += placeholder.fStyle.fWidth;
613 }
614 return true;
615}
616
618
619 // The text can be broken into many shaping sequences
620 // (by place holders, possibly, by hard line breaks or tabs, too)
621 auto limitlessWidth = std::numeric_limits<SkScalar>::max();
622
623 auto result = iterateThroughShapingRegions(
624 [this, limitlessWidth]
625 (TextRange textRange, SkSpan<Block> styleSpan, SkScalar& advanceX, TextIndex textStart, uint8_t defaultBidiLevel) {
626
627 // Set up the shaper and shape the next
628 auto shaper = SkShapers::HB::ShapeDontWrapOrReorder(fParagraph->fUnicode,
629 SkFontMgr::RefEmpty()); // no fallback
630 if (shaper == nullptr) {
631 // For instance, loadICU does not work. We have to stop the process
632 return false;
633 }
634
635 iterateThroughFontStyles(textRange, styleSpan,
636 [this, &shaper, defaultBidiLevel, limitlessWidth, &advanceX]
637 (Block block, TArray<SkShaper::Feature> features) {
638 auto blockSpan = SkSpan<Block>(&block, 1);
639
640 // Start from the beginning (hoping that it's a simple case one block - one run)
641 fHeight = block.fStyle.getHeightOverride() ? block.fStyle.getHeight() : 0;
642 fUseHalfLeading = block.fStyle.getHalfLeading();
643 fBaselineShift = block.fStyle.getBaselineShift();
644 fAdvance = SkVector::Make(advanceX, 0);
645 fCurrentText = block.fRange;
646 fUnresolvedBlocks.emplace_back(RunBlock(block.fRange));
647
648 this->matchResolvedFonts(block.fStyle, [&](sk_sp<SkTypeface> typeface) {
649
650 // Create one more font to try
651 SkFont font(std::move(typeface), block.fStyle.getFontSize());
652 font.setEdging(SkFont::Edging::kAntiAlias);
653 font.setHinting(SkFontHinting::kSlight);
654 font.setSubpixel(true);
655
656 // Apply fake bold and/or italic settings to the font if the
657 // typeface's attributes do not match the intended font style.
658 int wantedWeight = block.fStyle.getFontStyle().weight();
659 bool fakeBold =
660 wantedWeight >= SkFontStyle::kSemiBold_Weight &&
661 wantedWeight - font.getTypeface()->fontStyle().weight() >= 200;
662 bool fakeItalic =
663 block.fStyle.getFontStyle().slant() == SkFontStyle::kItalic_Slant &&
664 font.getTypeface()->fontStyle().slant() != SkFontStyle::kItalic_Slant;
665 font.setEmbolden(fakeBold);
666 font.setSkewX(fakeItalic ? -SK_Scalar1 / 4 : 0);
667
668 // Walk through all the currently unresolved blocks
669 // (ignoring those that appear later)
670 auto resolvedCount = fResolvedBlocks.size();
671 auto unresolvedCount = fUnresolvedBlocks.size();
672 while (unresolvedCount-- > 0) {
673 auto unresolvedRange = fUnresolvedBlocks.front().fText;
674 if (unresolvedRange == EMPTY_TEXT) {
675 // Duplicate blocks should be ignored
676 fUnresolvedBlocks.pop_front();
677 continue;
678 }
679 auto unresolvedText = fParagraph->text(unresolvedRange);
680
681 SkShaper::TrivialFontRunIterator fontIter(font, unresolvedText.size());
682 LangIterator langIter(unresolvedText, blockSpan,
683 fParagraph->paragraphStyle().getTextStyle());
684 SkShaper::TrivialBiDiRunIterator bidiIter(defaultBidiLevel, unresolvedText.size());
685 auto scriptIter = SkShapers::HB::ScriptRunIterator(unresolvedText.begin(),
686 unresolvedText.size());
687 fCurrentText = unresolvedRange;
688
689 // Map the block's features to subranges within the unresolved range.
690 TArray<SkShaper::Feature> adjustedFeatures(features.size());
691 for (const SkShaper::Feature& feature : features) {
692 SkRange<size_t> featureRange(feature.start, feature.end);
693 if (unresolvedRange.intersects(featureRange)) {
694 SkRange<size_t> adjustedRange = unresolvedRange.intersection(featureRange);
695 adjustedRange.Shift(-static_cast<std::make_signed_t<size_t>>(unresolvedRange.start));
696 adjustedFeatures.push_back({feature.tag, feature.value, adjustedRange.start, adjustedRange.end});
697 }
698 }
699
700 shaper->shape(unresolvedText.begin(), unresolvedText.size(),
701 fontIter, bidiIter,*scriptIter, langIter,
702 adjustedFeatures.data(), adjustedFeatures.size(),
703 limitlessWidth, this);
704
705 // Take off the queue the block we tried to resolved -
706 // whatever happened, we have now smaller pieces of it to deal with
707 fUnresolvedBlocks.pop_front();
708 }
709
710 if (fUnresolvedBlocks.empty()) {
711 // In some cases it does not mean everything
712 // (when we excluded some hopeless blocks from the list)
713 return Resolved::Everything;
714 } else if (resolvedCount < fResolvedBlocks.size()) {
715 return Resolved::Something;
716 } else {
717 return Resolved::Nothing;
718 }
719 });
720
721 this->finish(block, fHeight, advanceX);
722 });
723
724 return true;
725 });
726
727 return result;
728}
729
730// When we extend TextRange to the grapheme edges, we also extend glyphs range
731TextRange OneLineShaper::clusteredText(GlyphRange& glyphs) {
732
733 enum class Dir { left, right };
734 enum class Pos { inclusive, exclusive };
735
736 // [left: right)
737 auto findBaseChar = [&](TextIndex index, Dir dir) -> TextIndex {
738
739 if (dir == Dir::right) {
740 while (index < fCurrentRun->fTextRange.end) {
741 if (this->fParagraph->codeUnitHasProperty(index,
743 return index;
744 }
745 ++index;
746 }
747 return fCurrentRun->fTextRange.end;
748 } else {
749 while (index > fCurrentRun->fTextRange.start) {
750 if (this->fParagraph->codeUnitHasProperty(index,
752 return index;
753 }
754 --index;
755 }
756 return fCurrentRun->fTextRange.start;
757 }
758 };
759
760 TextRange textRange(normalizeTextRange(glyphs));
761 textRange.start = findBaseChar(textRange.start, Dir::left);
762 textRange.end = findBaseChar(textRange.end, Dir::right);
763
764 // Correct the glyphRange in case we extended the text to the grapheme edges
765 // TODO: code it without if (as a part of LTR/RTL refactoring)
766 if (fCurrentRun->leftToRight()) {
767 while (glyphs.start > 0 && clusterIndex(glyphs.start) > textRange.start) {
768 glyphs.start--;
769 }
770 while (glyphs.end < fCurrentRun->size() && clusterIndex(glyphs.end) < textRange.end) {
771 glyphs.end++;
772 }
773 } else {
774 while (glyphs.start > 0 && clusterIndex(glyphs.start - 1) < textRange.end) {
775 glyphs.start--;
776 }
777 while (glyphs.end < fCurrentRun->size() && clusterIndex(glyphs.end) > textRange.start) {
778 glyphs.end++;
779 }
780 }
781
782 return { textRange.start, textRange.end };
783}
784
785bool OneLineShaper::FontKey::operator==(const OneLineShaper::FontKey& other) const {
786 return fUnicode == other.fUnicode && fFontStyle == other.fFontStyle && fLocale == other.fLocale;
787}
788
789uint32_t OneLineShaper::FontKey::Hasher::operator()(const OneLineShaper::FontKey& key) const {
790 return SkGoodHash()(key.fUnicode) ^
791 SkGoodHash()(key.fFontStyle) ^
792 SkGoodHash()(key.fLocale);
793}
794
795
796// By definition any emoji_sequence starts from a codepoint that has
797// UCHAR_EMOJI property.
798// If the first codepoint does not have UCHAR_EMOJI_COMPONENT property,
799// we have an emoji sequence right away.
800// In two (and only two) cases an emoji sequence starts with a codepoint
801// that also has UCHAR_EMOJI_COMPONENT property.
802// emoji_flag_sequence := regional_indicator regional_indicator
803// emoji_keycap_sequence := [0-9#*] \x{FE0F 20E3}
804// These two cases require additional checks of the next codepoint(s).
805SkUnichar OneLineShaper::getEmojiSequenceStart(SkUnicode* unicode, const char** begin, const char* end) {
806 const char* next = *begin;
807 auto codepoint1 = SkUTF::NextUTF8WithReplacement(&next, end);
808
809 if (!unicode->isEmoji(codepoint1)) {
810 // This is not a basic emoji nor it an emoji sequence
811 return -1;
812 }
813
814 if (!unicode->isEmojiComponent(codepoint1)) {
815 // This is an emoji sequence start
816 *begin = next;
817 return codepoint1;
818 }
819
820 // Now we need to look at the next codepoint to see what is going on
821 const char* last = next;
822 auto codepoint2 = SkUTF::NextUTF8WithReplacement(&last, end);
823
824 // emoji_flag_sequence
825 if (unicode->isRegionalIndicator(codepoint2)) {
826 // We expect a second regional indicator here
827 if (unicode->isRegionalIndicator(codepoint2)) {
828 *begin = next;
829 return codepoint1;
830 } else {
831 // That really should not happen assuming correct UTF8 text
832 return -1;
833 }
834 }
835
836 // emoji_keycap_sequence
837 if (codepoint2 == 0xFE0F) {
838 auto codepoint3 = SkUTF::NextUTF8WithReplacement(&last, end);
839 if (codepoint3 == 0x20E3) {
840 *begin = next;
841 return codepoint1;
842 }
843 }
844
845 return -1;
846}
847
848} // namespace textlayout
849} // namespace skia
SkStrokeRec::Style fStyle
static void info(const char *fmt,...) SK_PRINTF_LIKE(1
Definition DM.cpp:213
uint16_t glyphs[5]
static float next(float f)
#define SkASSERT(cond)
Definition SkAssert.h:116
void SK_SPI SkDebugf(const char format[],...) SK_PRINTF_LIKE(1
#define SkDEBUGF(...)
Definition SkDebug.h:24
static bool left(const SkPoint &p0, const SkPoint &p1)
static bool right(const SkPoint &p0, const SkPoint &p1)
constexpr uint32_t SkToU32(S x)
Definition SkTo.h:26
int32_t SkUnichar
Definition SkTypes.h:175
static constexpr SkFourByteTag SkSetFourByteTag(char a, char b, char c, char d)
Definition SkTypes.h:167
static sk_sp< SkFontMgr > RefEmpty()
constexpr T * begin() const
Definition SkSpan_impl.h:90
@ kGraphemeStart
Definition SkUnicode.h:82
T * get() const
Definition SkRefCnt.h:303
static SkUnichar getEmojiSequenceStart(SkUnicode *unicode, const char **begin, const char *end)
void runInfo(const RunInfo &) override
void commitRunBuffer(const RunInfo &) override
bool codeUnitHasProperty(size_t index, SkUnicode::CodeUnitFlags property) const
SkSpan< const char > text() const
SkSpan< Block > blocks(BlockRange blockRange)
void addUnresolvedCodepoints(TextRange textRange)
BlockRange findAllBlocks(TextRange textRange)
TextIndex findPreviousGraphemeBoundary(TextIndex utf8) const
sk_sp< FontCollection > fFontCollection
Definition Paragraph.h:273
bool getHeightOverride() const
Definition TextStyle.h:264
SkScalar getHeight() const
Definition TextStyle.h:261
SkScalar getBaselineShift() const
Definition TextStyle.h:257
T & emplace_back(Args &&... args)
Definition SkTArray.h:243
void add(T item)
Definition SkTHash.h:573
bool contains(const T &item) const
Definition SkTHash.h:576
static const char * begin(const StringSlice &s)
Definition editor.cpp:252
float SkScalar
Definition extension.cpp:12
static bool b
struct MyStruct a[10]
if(end==-1)
glong glong end
GAsyncResult * result
const char * name
Definition fuchsia.cc:50
std::u16string text
SKSHAPER_API std::unique_ptr< SkShaper > ShapeDontWrapOrReorder(sk_sp< SkUnicode > unicode, sk_sp< SkFontMgr > fallback)
SK_SPI SkUnichar NextUTF8WithReplacement(const char **ptr, const char *end)
Definition SkUTF.cpp:154
DEF_SWITCHES_START aot vmservice shared library Name of the *so containing AOT compiled Dart assets for launching the service isolate vm snapshot The VM snapshot data that will be memory mapped as read only SnapshotAssetPath must be present isolate snapshot The isolate snapshot data that will be memory mapped as read only SnapshotAssetPath must be present cache dir Path to the cache directory This is different from the persistent_cache_path in embedder which is used for Skia shader cache icu native lib Path to the library file that exports the ICU data vm service The hostname IP address on which the Dart VM Service should be served If not defaults to or::depending on whether ipv6 is specified vm service A custom Dart VM Service port The default is to pick a randomly available open port disable vm Disable the Dart VM Service The Dart VM Service is never available in release mode disable vm service Disable mDNS Dart VM Service publication Bind to the IPv6 localhost address for the Dart VM Service Ignored if vm service host is set endless trace Enable an endless trace buffer The default is a ring buffer This is useful when very old events need to viewed For during application launch Memory usage will continue to grow indefinitely however Start app with an specific route defined on the framework flutter assets dir
Definition switches.h:145
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
font
Font Metadata and Metrics.
Definition run.py:1
const size_t EMPTY_INDEX
Definition DartTypes.h:91
const SkRange< size_t > EMPTY_TEXT
Definition TextStyle.h:338
const SkRange< size_t > EMPTY_RANGE
Definition DartTypes.h:128
SkRange< size_t > TextRange
Definition TextStyle.h:337
SkRange< GlyphIndex > GlyphRange
Definition Run.h:44
size_t ClusterIndex
Definition Run.h:35
SkRange< size_t > BlockRange
Definition TextStyle.h:356
size_t GlyphIndex
Definition Run.h:43
size_t GraphemeIndex
Definition Run.h:40
int32_t height
float fX
x-axis value
static constexpr SkPoint Make(float x, float y)
float fY
y-axis value