1 // Copyright 2019 Google LLC.
2
3 #include "modules/skparagraph/src/Iterators.h"
4 #include "modules/skparagraph/src/OneLineShaper.h"
5 #include "src/base/SkUTF.h"
6 #include <algorithm>
7 #include <unordered_set>
8
nextUtf8Unit(const char ** ptr,const char * end)9 static inline SkUnichar nextUtf8Unit(const char** ptr, const char* end) {
10 SkUnichar val = SkUTF::NextUTF8(ptr, end);
11 return val < 0 ? 0xFFFD : val;
12 }
13
14 namespace skia {
15 namespace textlayout {
16
commitRunBuffer(const RunInfo &)17 void OneLineShaper::commitRunBuffer(const RunInfo&) {
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
printState()58 void 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
fillGaps(size_t startingCount)84 void 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
finish(const Block & block,SkScalar height,SkScalar & advanceX)147 void 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 }
160
161 // Sort all pieces by text
162 std::sort(fResolvedBlocks.begin(), fResolvedBlocks.end(),
163 [](const RunBlock& a, const RunBlock& b) {
164 return a.fText.start < b.fText.start;
165 });
166
167 // Go through all of them
168 size_t lastTextEnd = blockText.start;
169 for (auto& resolvedBlock : fResolvedBlocks) {
170
171 if (resolvedBlock.fText.end <= blockText.start) {
172 continue;
173 }
174
175 if (resolvedBlock.fRun != nullptr) {
176 fParagraph->fFontSwitches.emplace_back(resolvedBlock.fText.start, resolvedBlock.fRun->fFont);
177 }
178
179 auto run = resolvedBlock.fRun;
180 auto glyphs = resolvedBlock.fGlyphs;
181 auto text = resolvedBlock.fText;
182 if (lastTextEnd != text.start) {
183 SkDEBUGF("Text ranges mismatch: ...:%zu] - [%zu:%zu] (%zu-%zu)\n",
184 lastTextEnd, text.start, text.end, glyphs.start, glyphs.end);
185 SkASSERT(false);
186 }
187 lastTextEnd = text.end;
188
189 if (resolvedBlock.isFullyResolved()) {
190 // Just move the entire run
191 resolvedBlock.fRun->fIndex = this->fParagraph->fRuns.size();
192 this->fParagraph->fRuns.emplace_back(*resolvedBlock.fRun);
193 resolvedBlock.fRun.reset();
194 continue;
195 } else if (run == nullptr) {
196 continue;
197 }
198
199 auto runAdvance = SkVector::Make(run->posX(glyphs.end) - run->posX(glyphs.start), run->fAdvance.fY);
200 const SkShaper::RunHandler::RunInfo info = {
201 run->fFont,
202 run->fBidiLevel,
203 runAdvance,
204 glyphs.width(),
205 SkShaper::RunHandler::Range(text.start - run->fClusterStart, text.width())
206 };
207 this->fParagraph->fRuns.emplace_back(
208 this->fParagraph,
209 info,
210 run->fClusterStart,
211 height,
212 block.fStyle.getHalfLeading(),
213 block.fStyle.getBaselineShift(),
214 this->fParagraph->fRuns.size(),
215 advanceX
216 );
217 auto piece = &this->fParagraph->fRuns.back();
218
219 // TODO: Optimize copying
220 SkPoint zero = {run->fPositions[glyphs.start].fX, 0};
221 for (size_t i = glyphs.start; i <= glyphs.end; ++i) {
222
223 auto index = i - glyphs.start;
224 if (i < glyphs.end) {
225 piece->fGlyphs[index] = run->fGlyphs[i];
226 }
227 piece->fClusterIndexes[index] = run->fClusterIndexes[i];
228 piece->fPositions[index] = run->fPositions[i] - zero;
229 piece->fOffsets[index] = run->fOffsets[i];
230 piece->addX(index, advanceX);
231 }
232
233 // Carve out the line text out of the entire run text
234 fAdvance.fX += runAdvance.fX;
235 fAdvance.fY = std::max(fAdvance.fY, runAdvance.fY);
236 }
237
238 advanceX = fAdvance.fX;
239 if (lastTextEnd != blockText.end) {
240 SkDEBUGF("Last range mismatch: %zu - %zu\n", lastTextEnd, blockText.end);
241 SkASSERT(false);
242 }
243 }
244
245 // Make it [left:right) regardless of a text direction
normalizeTextRange(GlyphRange glyphRange)246 TextRange OneLineShaper::normalizeTextRange(GlyphRange glyphRange) {
247
248 if (fCurrentRun->leftToRight()) {
249 return TextRange(clusterIndex(glyphRange.start), clusterIndex(glyphRange.end));
250 } else {
251 return TextRange(clusterIndex(glyphRange.end - 1),
252 glyphRange.start > 0
253 ? clusterIndex(glyphRange.start - 1)
254 : fCurrentRun->fTextRange.end);
255 }
256 }
257
addFullyResolved()258 void OneLineShaper::addFullyResolved() {
259 if (this->fCurrentRun->size() == 0) {
260 return;
261 }
262 RunBlock resolved(fCurrentRun,
263 this->fCurrentRun->fTextRange,
264 GlyphRange(0, this->fCurrentRun->size()),
265 this->fCurrentRun->size());
266 fResolvedBlocks.emplace_back(resolved);
267 }
268
addUnresolvedWithRun(GlyphRange glyphRange)269 void OneLineShaper::addUnresolvedWithRun(GlyphRange glyphRange) {
270 auto extendedText = this->clusteredText(glyphRange); // It also modifies glyphRange if needed
271 RunBlock unresolved(fCurrentRun, extendedText, glyphRange, 0);
272 if (unresolved.fGlyphs.width() == fCurrentRun->size()) {
273 SkASSERT(unresolved.fText.width() == fCurrentRun->fTextRange.width());
274 } else if (fUnresolvedBlocks.size() > 0) {
275 auto& lastUnresolved = fUnresolvedBlocks.back();
276 if (lastUnresolved.fRun != nullptr &&
277 lastUnresolved.fRun->fIndex == fCurrentRun->fIndex) {
278
279 if (lastUnresolved.fText.end == unresolved.fText.start) {
280 // Two pieces next to each other - can join them
281 lastUnresolved.fText.end = unresolved.fText.end;
282 lastUnresolved.fGlyphs.end = glyphRange.end;
283 return;
284 } else if(lastUnresolved.fText == unresolved.fText) {
285 // Nothing was resolved; ignore it
286 return;
287 } else if (lastUnresolved.fText.contains(unresolved.fText)) {
288 // We get here for the very first unresolved piece
289 return;
290 } else if (lastUnresolved.fText.intersects(unresolved.fText)) {
291 // Few pieces of the same unresolved text block can ignore the second one
292 lastUnresolved.fGlyphs.start = std::min(lastUnresolved.fGlyphs.start, glyphRange.start);
293 lastUnresolved.fGlyphs.end = std::max(lastUnresolved.fGlyphs.end, glyphRange.end);
294 lastUnresolved.fText = this->clusteredText(lastUnresolved.fGlyphs);
295 return;
296 }
297 }
298 }
299 fUnresolvedBlocks.emplace_back(unresolved);
300 }
301
302 // Glue whitespaces to the next/prev unresolved blocks
303 // (so we don't have chinese text with english whitespaces broken into millions of tiny runs)
sortOutGlyphs(std::function<void (GlyphRange)> && sortOutUnresolvedBLock)304 void OneLineShaper::sortOutGlyphs(std::function<void(GlyphRange)>&& sortOutUnresolvedBLock) {
305
306 GlyphRange block = EMPTY_RANGE;
307 bool graphemeResolved = false;
308 TextIndex graphemeStart = EMPTY_INDEX;
309 for (size_t i = 0; i < fCurrentRun->size(); ++i) {
310
311 ClusterIndex ci = clusterIndex(i);
312 // Removing all pretty optimizations for whitespaces
313 // because they get in a way of grapheme rounding
314 // Inspect the glyph
315 auto glyph = fCurrentRun->fGlyphs[i];
316
317 GraphemeIndex gi = fParagraph->findPreviousGraphemeBoundary(ci);
318 if ((fCurrentRun->leftToRight() ? gi > graphemeStart : gi < graphemeStart) || graphemeStart == EMPTY_INDEX) {
319 // This is the Flutter change
320 // Do not count control codepoints as unresolved
321 bool isControl8 = fParagraph->codeUnitHasProperty(ci,
322 SkUnicode::CodeUnitFlags::kControl);
323 // We only count glyph resolved if all the glyphs in its grapheme are resolved
324 graphemeResolved = glyph != 0 || isControl8;
325 graphemeStart = gi;
326 } else if (glyph == 0) {
327 // Found unresolved glyph - the entire grapheme is unresolved now
328 graphemeResolved = false;
329 }
330
331 if (!graphemeResolved) { // Unresolved glyph and not control codepoint
332 if (block.start == EMPTY_INDEX) {
333 // Start new unresolved block
334 block.start = i;
335 block.end = EMPTY_INDEX;
336 } else {
337 // Keep skipping unresolved block
338 }
339 } else { // Resolved glyph or control codepoint
340 if (block.start == EMPTY_INDEX) {
341 // Keep skipping resolved code points
342 } else {
343 // This is the end of unresolved block
344 block.end = i;
345 sortOutUnresolvedBLock(block);
346 block = EMPTY_RANGE;
347 }
348 }
349 }
350
351 // One last block could have been left
352 if (block.start != EMPTY_INDEX) {
353 block.end = fCurrentRun->size();
354 sortOutUnresolvedBLock(block);
355 }
356 }
357
iterateThroughFontStyles(TextRange textRange,SkSpan<Block> styleSpan,const ShapeSingleFontVisitor & visitor)358 void OneLineShaper::iterateThroughFontStyles(TextRange textRange,
359 SkSpan<Block> styleSpan,
360 const ShapeSingleFontVisitor& visitor) {
361 Block combinedBlock;
362 SkTArray<SkShaper::Feature> features;
363
364 auto addFeatures = [&features](const Block& block) {
365 for (auto& ff : block.fStyle.getFontFeatures()) {
366 if (ff.fName.size() != 4) {
367 SkDEBUGF("Incorrect font feature: %s=%d\n", ff.fName.c_str(), ff.fValue);
368 continue;
369 }
370 SkShaper::Feature feature = {
371 SkSetFourByteTag(ff.fName[0], ff.fName[1], ff.fName[2], ff.fName[3]),
372 SkToU32(ff.fValue),
373 block.fRange.start,
374 block.fRange.end
375 };
376 features.emplace_back(feature);
377 }
378 // Disable ligatures if letter spacing is enabled.
379 if (block.fStyle.getLetterSpacing() > 0) {
380 features.emplace_back(SkShaper::Feature{
381 SkSetFourByteTag('l', 'i', 'g', 'a'), 0, block.fRange.start, block.fRange.end
382 });
383 }
384 };
385
386 for (auto& block : styleSpan) {
387 BlockRange blockRange(std::max(block.fRange.start, textRange.start), std::min(block.fRange.end, textRange.end));
388 if (blockRange.empty()) {
389 continue;
390 }
391 SkASSERT(combinedBlock.fRange.width() == 0 || combinedBlock.fRange.end == block.fRange.start);
392
393 if (!combinedBlock.fRange.empty()) {
394 if (block.fStyle.matchOneAttribute(StyleType::kFont, combinedBlock.fStyle)) {
395 combinedBlock.add(blockRange);
396 addFeatures(block);
397 continue;
398 }
399 // Resolve all characters in the block for this style
400 visitor(combinedBlock, features);
401 }
402
403 combinedBlock.fRange = blockRange;
404 combinedBlock.fStyle = block.fStyle;
405 features.clear();
406 addFeatures(block);
407 }
408
409 visitor(combinedBlock, features);
410 #ifdef SK_DEBUG
411 //printState();
412 #endif
413 }
414
matchResolvedFonts(const TextStyle & textStyle,const TypefaceVisitor & visitor)415 void OneLineShaper::matchResolvedFonts(const TextStyle& textStyle,
416 const TypefaceVisitor& visitor) {
417 std::vector<sk_sp<SkTypeface>> typefaces = fParagraph->fFontCollection->findTypefaces(textStyle.getFontFamilies(), textStyle.getFontStyle(), textStyle.getFontArguments());
418
419 for (const auto& typeface : typefaces) {
420 if (visitor(typeface) == Resolved::Everything) {
421 // Resolved everything
422 return;
423 }
424 }
425
426 if (fParagraph->fFontCollection->fontFallbackEnabled()) {
427 // Give fallback a clue
428 // Some unresolved subblocks might be resolved with different fallback fonts
429 std::vector<RunBlock> hopelessBlocks;
430 while (!fUnresolvedBlocks.empty()) {
431 auto unresolvedRange = fUnresolvedBlocks.front().fText;
432 auto unresolvedText = fParagraph->text(unresolvedRange);
433 const char* ch = unresolvedText.begin();
434 // We have the global cache for all already found typefaces for SkUnichar
435 // but we still need to keep track of all SkUnichars used in this unresolved block
436 SkTHashSet<SkUnichar> alreadyTriedCodepoints;
437 SkTHashSet<SkTypefaceID> alreadyTriedTypefaces;
438 while (true) {
439
440 if (ch == unresolvedText.end()) {
441 // Not a single codepoint could be resolved but we finished the block
442 hopelessBlocks.push_back(fUnresolvedBlocks.front());
443 fUnresolvedBlocks.pop_front();
444 break;
445 }
446
447 // See if we can switch to the next DIFFERENT codepoint
448 SkUnichar unicode = -1;
449 while (ch != unresolvedText.end()) {
450 unicode = nextUtf8Unit(&ch, unresolvedText.end());
451 if (!alreadyTriedCodepoints.contains(unicode)) {
452 alreadyTriedCodepoints.add(unicode);
453 break;
454 }
455 }
456 SkASSERT(unicode != -1);
457
458 // First try to find in in a cache
459 sk_sp<SkTypeface> typeface;
460 FontKey fontKey(unicode, textStyle.getFontStyle(), textStyle.getLocale());
461 auto found = fFallbackFonts.find(fontKey);
462 if (found != nullptr) {
463 typeface = *found;
464 } else {
465 typeface = fParagraph->fFontCollection->defaultFallback(
466 unicode, textStyle.getFontStyle(), textStyle.getLocale());
467
468 if (typeface == nullptr) {
469 // There is no fallback font for this character, so move on to the next character.
470 continue;
471 }
472 fFallbackFonts.set(fontKey, typeface);
473 }
474
475 // Check if we already tried this font on this text range
476 if (!alreadyTriedTypefaces.contains(typeface->uniqueID())) {
477 alreadyTriedTypefaces.add(typeface->uniqueID());
478 } else {
479 continue;
480 }
481
482 auto resolvedBlocksBefore = fResolvedBlocks.size();
483 auto resolved = visitor(typeface);
484 if (resolved == Resolved::Everything) {
485 if (hopelessBlocks.empty()) {
486 // Resolved everything, no need to try another font
487 return;
488 } else if (resolvedBlocksBefore < fResolvedBlocks.size()) {
489 // There are some resolved blocks
490 resolved = Resolved::Something;
491 } else {
492 // All blocks are hopeless
493 resolved = Resolved::Nothing;
494 }
495 }
496
497 if (resolved == Resolved::Something) {
498 // Resolved something, no need to try another codepoint
499 break;
500 }
501 }
502 }
503
504 // Return hopeless blocks back
505 for (auto& block : hopelessBlocks) {
506 fUnresolvedBlocks.emplace_front(block);
507 }
508 }
509 }
510
iterateThroughShapingRegions(const ShapeVisitor & shape)511 bool OneLineShaper::iterateThroughShapingRegions(const ShapeVisitor& shape) {
512
513 size_t bidiIndex = 0;
514
515 SkScalar advanceX = 0;
516 for (auto& placeholder : fParagraph->fPlaceholders) {
517
518 if (placeholder.fTextBefore.width() > 0) {
519 // Shape the text by bidi regions
520 while (bidiIndex < fParagraph->fBidiRegions.size()) {
521 SkUnicode::BidiRegion& bidiRegion = fParagraph->fBidiRegions[bidiIndex];
522 auto start = std::max(bidiRegion.start, placeholder.fTextBefore.start);
523 auto end = std::min(bidiRegion.end, placeholder.fTextBefore.end);
524
525 // Set up the iterators (the style iterator points to a bigger region that it could
526 TextRange textRange(start, end);
527 auto blockRange = fParagraph->findAllBlocks(textRange);
528 if (!blockRange.empty()) {
529 SkSpan<Block> styleSpan(fParagraph->blocks(blockRange));
530
531 // Shape the text between placeholders
532 if (!shape(textRange, styleSpan, advanceX, start, bidiRegion.level)) {
533 return false;
534 }
535 }
536
537 if (end == bidiRegion.end) {
538 ++bidiIndex;
539 } else /*if (end == placeholder.fTextBefore.end)*/ {
540 break;
541 }
542 }
543 }
544
545 if (placeholder.fRange.width() == 0) {
546 continue;
547 }
548
549 // Get the placeholder font
550 std::vector<sk_sp<SkTypeface>> typefaces = fParagraph->fFontCollection->findTypefaces(
551 placeholder.fTextStyle.getFontFamilies(),
552 placeholder.fTextStyle.getFontStyle(),
553 placeholder.fTextStyle.getFontArguments());
554 sk_sp<SkTypeface> typeface = typefaces.size() ? typefaces.front() : nullptr;
555 SkFont font(typeface, placeholder.fTextStyle.getFontSize());
556
557 // "Shape" the placeholder
558 uint8_t bidiLevel = (bidiIndex < fParagraph->fBidiRegions.size())
559 ? fParagraph->fBidiRegions[bidiIndex].level
560 : 2;
561 const SkShaper::RunHandler::RunInfo runInfo = {
562 font,
563 bidiLevel,
564 SkPoint::Make(placeholder.fStyle.fWidth, placeholder.fStyle.fHeight),
565 1,
566 SkShaper::RunHandler::Range(0, placeholder.fRange.width())
567 };
568 auto& run = fParagraph->fRuns.emplace_back(this->fParagraph,
569 runInfo,
570 placeholder.fRange.start,
571 0.0f,
572 0.0f,
573 false,
574 fParagraph->fRuns.size(),
575 advanceX);
576
577 run.fPositions[0] = { advanceX, 0 };
578 run.fOffsets[0] = {0, 0};
579 run.fClusterIndexes[0] = 0;
580 run.fPlaceholderIndex = &placeholder - fParagraph->fPlaceholders.begin();
581 advanceX += placeholder.fStyle.fWidth;
582 }
583 return true;
584 }
585
shape()586 bool OneLineShaper::shape() {
587
588 // The text can be broken into many shaping sequences
589 // (by place holders, possibly, by hard line breaks or tabs, too)
590 auto limitlessWidth = std::numeric_limits<SkScalar>::max();
591
592 auto result = iterateThroughShapingRegions(
593 [this, limitlessWidth]
594 (TextRange textRange, SkSpan<Block> styleSpan, SkScalar& advanceX, TextIndex textStart, uint8_t defaultBidiLevel) {
595
596 // Set up the shaper and shape the next
597 auto shaper = SkShaper::MakeShapeDontWrapOrReorder(fParagraph->fUnicode->copy());
598 if (shaper == nullptr) {
599 // For instance, loadICU does not work. We have to stop the process
600 return false;
601 }
602
603 iterateThroughFontStyles(textRange, styleSpan,
604 [this, &shaper, defaultBidiLevel, limitlessWidth, &advanceX]
605 (Block block, SkTArray<SkShaper::Feature> features) {
606 auto blockSpan = SkSpan<Block>(&block, 1);
607
608 // Start from the beginning (hoping that it's a simple case one block - one run)
609 fHeight = block.fStyle.getHeightOverride() ? block.fStyle.getHeight() : 0;
610 fUseHalfLeading = block.fStyle.getHalfLeading();
611 fBaselineShift = block.fStyle.getBaselineShift();
612 fAdvance = SkVector::Make(advanceX, 0);
613 fCurrentText = block.fRange;
614 fUnresolvedBlocks.emplace_back(RunBlock(block.fRange));
615
616 this->matchResolvedFonts(block.fStyle, [&](sk_sp<SkTypeface> typeface) {
617
618 // Create one more font to try
619 SkFont font(std::move(typeface), block.fStyle.getFontSize());
620 font.setEdging(SkFont::Edging::kAntiAlias);
621 font.setHinting(SkFontHinting::kSlight);
622 font.setSubpixel(true);
623
624 // Apply fake bold and/or italic settings to the font if the
625 // typeface's attributes do not match the intended font style.
626 int wantedWeight = block.fStyle.getFontStyle().weight();
627 bool fakeBold =
628 wantedWeight >= SkFontStyle::kSemiBold_Weight &&
629 wantedWeight - font.getTypeface()->fontStyle().weight() >= 200;
630 bool fakeItalic =
631 block.fStyle.getFontStyle().slant() == SkFontStyle::kItalic_Slant &&
632 font.getTypeface()->fontStyle().slant() != SkFontStyle::kItalic_Slant;
633 font.setEmbolden(fakeBold);
634 font.setSkewX(fakeItalic ? -SK_Scalar1 / 4 : 0);
635
636 // Walk through all the currently unresolved blocks
637 // (ignoring those that appear later)
638 auto resolvedCount = fResolvedBlocks.size();
639 auto unresolvedCount = fUnresolvedBlocks.size();
640 while (unresolvedCount-- > 0) {
641 auto unresolvedRange = fUnresolvedBlocks.front().fText;
642 if (unresolvedRange == EMPTY_TEXT) {
643 // Duplicate blocks should be ignored
644 fUnresolvedBlocks.pop_front();
645 continue;
646 }
647 auto unresolvedText = fParagraph->text(unresolvedRange);
648
649 SkShaper::TrivialFontRunIterator fontIter(font, unresolvedText.size());
650 LangIterator langIter(unresolvedText, blockSpan,
651 fParagraph->paragraphStyle().getTextStyle());
652 SkShaper::TrivialBiDiRunIterator bidiIter(defaultBidiLevel, unresolvedText.size());
653 auto scriptIter = SkShaper::MakeSkUnicodeHbScriptRunIterator(
654 unresolvedText.begin(), unresolvedText.size());
655 fCurrentText = unresolvedRange;
656
657 // Map the block's features to subranges within the unresolved range.
658 SkTArray<SkShaper::Feature> adjustedFeatures(features.size());
659 for (const SkShaper::Feature& feature : features) {
660 SkRange<size_t> featureRange(feature.start, feature.end);
661 if (unresolvedRange.intersects(featureRange)) {
662 SkRange<size_t> adjustedRange = unresolvedRange.intersection(featureRange);
663 adjustedRange.Shift(-static_cast<std::make_signed_t<size_t>>(unresolvedRange.start));
664 adjustedFeatures.push_back({feature.tag, feature.value, adjustedRange.start, adjustedRange.end});
665 }
666 }
667
668 shaper->shape(unresolvedText.begin(), unresolvedText.size(),
669 fontIter, bidiIter,*scriptIter, langIter,
670 adjustedFeatures.data(), adjustedFeatures.size(),
671 limitlessWidth, this);
672
673 // Take off the queue the block we tried to resolved -
674 // whatever happened, we have now smaller pieces of it to deal with
675 fUnresolvedBlocks.pop_front();
676 }
677
678 if (fUnresolvedBlocks.empty()) {
679 // In some cases it does not mean everything
680 // (when we excluded some hopeless blocks from the list)
681 return Resolved::Everything;
682 } else if (resolvedCount < fResolvedBlocks.size()) {
683 return Resolved::Something;
684 } else {
685 return Resolved::Nothing;
686 }
687 });
688
689 this->finish(block, fHeight, advanceX);
690 });
691
692 return true;
693 });
694
695 return result;
696 }
697
698 // When we extend TextRange to the grapheme edges, we also extend glyphs range
clusteredText(GlyphRange & glyphs)699 TextRange OneLineShaper::clusteredText(GlyphRange& glyphs) {
700
701 enum class Dir { left, right };
702 enum class Pos { inclusive, exclusive };
703
704 // [left: right)
705 auto findBaseChar = [&](TextIndex index, Dir dir) -> TextIndex {
706
707 if (dir == Dir::right) {
708 while (index < fCurrentRun->fTextRange.end) {
709 if (this->fParagraph->codeUnitHasProperty(index,
710 SkUnicode::CodeUnitFlags::kGraphemeStart)) {
711 return index;
712 }
713 ++index;
714 }
715 return fCurrentRun->fTextRange.end;
716 } else {
717 while (index > fCurrentRun->fTextRange.start) {
718 if (this->fParagraph->codeUnitHasProperty(index,
719 SkUnicode::CodeUnitFlags::kGraphemeStart)) {
720 return index;
721 }
722 --index;
723 }
724 return fCurrentRun->fTextRange.start;
725 }
726 };
727
728 TextRange textRange(normalizeTextRange(glyphs));
729 textRange.start = findBaseChar(textRange.start, Dir::left);
730 textRange.end = findBaseChar(textRange.end, Dir::right);
731
732 // Correct the glyphRange in case we extended the text to the grapheme edges
733 // TODO: code it without if (as a part of LTR/RTL refactoring)
734 if (fCurrentRun->leftToRight()) {
735 while (glyphs.start > 0 && clusterIndex(glyphs.start) > textRange.start) {
736 glyphs.start--;
737 }
738 while (glyphs.end < fCurrentRun->size() && clusterIndex(glyphs.end) < textRange.end) {
739 glyphs.end++;
740 }
741 } else {
742 while (glyphs.start > 0 && clusterIndex(glyphs.start - 1) < textRange.end) {
743 glyphs.start--;
744 }
745 while (glyphs.end < fCurrentRun->size() && clusterIndex(glyphs.end) > textRange.start) {
746 glyphs.end++;
747 }
748 }
749
750 return { textRange.start, textRange.end };
751 }
752
operator ==(const OneLineShaper::FontKey & other) const753 bool OneLineShaper::FontKey::operator==(const OneLineShaper::FontKey& other) const {
754 return fUnicode == other.fUnicode && fFontStyle == other.fFontStyle && fLocale == other.fLocale;
755 }
756
operator ()(const OneLineShaper::FontKey & key) const757 size_t OneLineShaper::FontKey::Hasher::operator()(const OneLineShaper::FontKey& key) const {
758
759 return SkGoodHash()(key.fUnicode) ^
760 SkGoodHash()(key.fFontStyle) ^
761 SkGoodHash()(key.fLocale.c_str());
762 }
763
764 } // namespace textlayout
765 } // namespace skia
766