xref: /aosp_15_r20/external/skia/modules/skparagraph/src/OneLineShaper.cpp (revision c8dee2aa9b3f27cf6c858bd81872bdeb2c07ed17)
1 // Copyright 2019 Google LLC.
2 #include "modules/skparagraph/src/OneLineShaper.h"
3 
4 #include "modules/skparagraph/src/Iterators.h"
5 #include "modules/skshaper/include/SkShaper_harfbuzz.h"
6 #include "src/base/SkUTF.h"
7 
8 #include <algorithm>
9 #include <cstdint>
10 #include <unordered_set>
11 
12 using namespace skia_private;
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         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);
201         const SkShaper::RunHandler::RunInfo info = {
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
normalizeTextRange(GlyphRange glyphRange)253 TextRange 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 
addFullyResolved()265 void 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 
addUnresolvedWithRun(GlyphRange glyphRange)276 void 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)
sortOutGlyphs(std::function<void (GlyphRange)> && sortOutUnresolvedBLock)311 void 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,
329                                                               SkUnicode::CodeUnitFlags::kControl);
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 
iterateThroughFontStyles(TextRange textRange,SkSpan<Block> styleSpan,const ShapeSingleFontVisitor & visitor)365 void OneLineShaper::iterateThroughFontStyles(TextRange textRange,
366                                              SkSpan<Block> styleSpan,
367                                              const ShapeSingleFontVisitor& visitor) {
368     Block combinedBlock;
369     TArray<SkShaper::Feature> features;
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) {
387             features.emplace_back(SkShaper::Feature{
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 
matchResolvedFonts(const TextStyle & textStyle,const TypefaceVisitor & visitor)422 void 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()) {
458                   emojiStart = OneLineShaper::getEmojiSequenceStart(
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 
iterateThroughShapingRegions(const ShapeVisitor & shape)542 bool 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;
592         const SkShaper::RunHandler::RunInfo runInfo = {
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 
shape()617 bool OneLineShaper::shape() {
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
clusteredText(GlyphRange & glyphs)731 TextRange 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,
742                                                       SkUnicode::CodeUnitFlags::kGraphemeStart)) {
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,
751                                                       SkUnicode::CodeUnitFlags::kGraphemeStart)) {
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 
operator ==(const OneLineShaper::FontKey & other) const785 bool OneLineShaper::FontKey::operator==(const OneLineShaper::FontKey& other) const {
786     return fUnicode == other.fUnicode && fFontStyle == other.fFontStyle && fLocale == other.fLocale;
787 }
788 
operator ()(const OneLineShaper::FontKey & key) const789 uint32_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).
getEmojiSequenceStart(SkUnicode * unicode,const char ** begin,const char * end)805 SkUnichar 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
850