• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 #ifdef ENABLE_TEXT_ENHANCE
8 #include "src/Run.h"
9 #include "include/TextGlobalConfig.h"
10 #include "trace.h"
11 #endif
12 
13 #include <algorithm>
14 #include <cstdint>
15 #include <unordered_set>
16 
17 using namespace skia_private;
18 #ifdef ENABLE_TEXT_ENHANCE
nextUtf8Unit(const char ** ptr,const char * end)19 static inline SkUnichar nextUtf8Unit(const char** ptr, const char* end) {
20     SkUnichar val = SkUTF::NextUTF8(ptr, end);
21     return val < 0 ? 0xFFFD : val;
22 }
23 #endif
24 namespace skia {
25 namespace textlayout {
26 #ifdef ENABLE_TEXT_ENHANCE
27 constexpr uint8_t UBIDI_LTR = 0;
28 constexpr uint8_t UBIDI_MIXED = 2;
29 #endif
30 
commitRunBuffer(const RunInfo &)31 void OneLineShaper::commitRunBuffer(const RunInfo&) {
32 
33     fCurrentRun->commit();
34 
35     auto oldUnresolvedCount = fUnresolvedBlocks.size();
36 /*
37     SkDebugf("Run [%zu:%zu)\n", fCurrentRun->fTextRange.start, fCurrentRun->fTextRange.end);
38     for (size_t i = 0; i < fCurrentRun->size(); ++i) {
39         SkDebugf("[%zu] %hu %u %f\n", i, fCurrentRun->fGlyphs[i], fCurrentRun->fClusterIndexes[i], fCurrentRun->fPositions[i].fX);
40     }
41 */
42     // Find all unresolved blocks
43     sortOutGlyphs([&](GlyphRange block){
44         if (block.width() == 0) {
45             return;
46         }
47         addUnresolvedWithRun(block);
48     });
49 
50     // Fill all the gaps between unresolved blocks with resolved ones
51     if (oldUnresolvedCount == fUnresolvedBlocks.size()) {
52         // No unresolved blocks added - we resolved the block with one run entirely
53         addFullyResolved();
54         return;
55     } else if (oldUnresolvedCount == fUnresolvedBlocks.size() - 1) {
56         auto& unresolved = fUnresolvedBlocks.back();
57         if (fCurrentRun->textRange() == unresolved.fText) {
58             // Nothing was resolved; preserve the initial run if it makes sense
59             auto& front = fUnresolvedBlocks.front();
60 #ifdef ENABLE_TEXT_ENHANCE
61             bool useTofu =
62                 unresolved.fRun != nullptr && unresolved.fRun->fFont.GetTypeface() != nullptr &&
63                 TextGlobalConfig::UndefinedGlyphDisplayUseTofu(unresolved.fRun->fFont.GetTypeface()->GetFamilyName());
64             if (front.fRun != nullptr && !useTofu) {
65                 unresolved.fRun = front.fRun;
66                 unresolved.fGlyphs = front.fGlyphs;
67             }
68 #else
69             if (front.fRun != nullptr) {
70                unresolved.fRun = front.fRun;
71                unresolved.fGlyphs = front.fGlyphs;
72             }
73 #endif
74             return;
75         }
76     }
77 
78     fillGaps(oldUnresolvedCount);
79 }
80 
81 #ifdef SK_DEBUG
printState()82 void OneLineShaper::printState() {
83     SkDebugf("Resolved: %zu\n", fResolvedBlocks.size());
84     for (auto& resolved : fResolvedBlocks) {
85         if (resolved.fRun ==  nullptr) {
86             SkDebugf("[%zu:%zu) unresolved\n",
87                     resolved.fText.start, resolved.fText.end);
88             continue;
89         }
90         SkString name("???");
91         if (resolved.fRun->fFont.getTypeface() != nullptr) {
92             resolved.fRun->fFont.getTypeface()->getFamilyName(&name);
93         }
94         SkDebugf("[%zu:%zu) ", resolved.fGlyphs.start, resolved.fGlyphs.end);
95         SkDebugf("[%zu:%zu) with %s\n",
96                 resolved.fText.start, resolved.fText.end,
97                 name.c_str());
98     }
99 
100     auto size = fUnresolvedBlocks.size();
101     SkDebugf("Unresolved: %zu\n", size);
102     for (const auto& unresolved : fUnresolvedBlocks) {
103         SkDebugf("[%zu:%zu)\n", unresolved.fText.start, unresolved.fText.end);
104     }
105 }
106 #endif
107 
fillGaps(size_t startingCount)108 void OneLineShaper::fillGaps(size_t startingCount) {
109     // Fill out gaps between all unresolved blocks
110     TextRange resolvedTextLimits = fCurrentRun->fTextRange;
111     if (!fCurrentRun->leftToRight()) {
112         std::swap(resolvedTextLimits.start, resolvedTextLimits.end);
113     }
114     TextIndex resolvedTextStart = resolvedTextLimits.start;
115     GlyphIndex resolvedGlyphsStart = 0;
116 
117     auto begin = fUnresolvedBlocks.begin();
118     auto end = fUnresolvedBlocks.end();
119     begin += startingCount; // Skip the old ones, do the new ones
120     TextRange prevText = EMPTY_TEXT;
121     for (; begin != end; ++begin) {
122         auto& unresolved = *begin;
123 
124         if (unresolved.fText == prevText) {
125             // Clean up repetitive blocks that appear inside the same grapheme block
126             unresolved.fText = EMPTY_TEXT;
127             continue;
128         } else {
129             prevText = unresolved.fText;
130         }
131 
132         TextRange resolvedText(resolvedTextStart, fCurrentRun->leftToRight() ? unresolved.fText.start : unresolved.fText.end);
133         if (resolvedText.width() > 0) {
134             if (!fCurrentRun->leftToRight()) {
135                 std::swap(resolvedText.start, resolvedText.end);
136             }
137 
138             GlyphRange resolvedGlyphs(resolvedGlyphsStart, unresolved.fGlyphs.start);
139             RunBlock resolved(fCurrentRun, resolvedText, resolvedGlyphs, resolvedGlyphs.width());
140 
141             if (resolvedGlyphs.width() == 0) {
142                 // Extend the unresolved block with an empty resolved
143                 if (unresolved.fText.end <= resolved.fText.start) {
144                     unresolved.fText.end = resolved.fText.end;
145                 }
146                 if (unresolved.fText.start >= resolved.fText.end) {
147                     unresolved.fText.start = resolved.fText.start;
148                 }
149             } else {
150                 fResolvedBlocks.emplace_back(resolved);
151             }
152         }
153         resolvedGlyphsStart = unresolved.fGlyphs.end;
154         resolvedTextStart =  fCurrentRun->leftToRight()
155                                 ? unresolved.fText.end
156                                 : unresolved.fText.start;
157     }
158 
159     TextRange resolvedText(resolvedTextStart,resolvedTextLimits.end);
160     if (resolvedText.width() > 0) {
161         if (!fCurrentRun->leftToRight()) {
162             std::swap(resolvedText.start, resolvedText.end);
163         }
164 
165         GlyphRange resolvedGlyphs(resolvedGlyphsStart, fCurrentRun->size());
166         RunBlock resolved(fCurrentRun, resolvedText, resolvedGlyphs, resolvedGlyphs.width());
167         fResolvedBlocks.emplace_back(resolved);
168     }
169 }
170 
171 #ifdef ENABLE_TEXT_ENHANCE
172 // 1. glyphs in run between [glyphStart, glyphEnd) are all equal to zero.
173 // 2. run is nullptr.
174 // 3. charStart flag has kCombine.
175 // one of the above conditions is met, will return true.
176 // anything else, return false.
isUnresolvedCombineGlyphRange(std::shared_ptr<Run> run,size_t glyphStart,size_t glyphEnd,size_t charStart) const177 bool OneLineShaper::isUnresolvedCombineGlyphRange(std::shared_ptr<Run> run, size_t glyphStart, size_t glyphEnd,
178     size_t charStart) const {
179     if (run == nullptr ||
180         (fParagraph != nullptr && !fParagraph->codeUnitHasProperty(charStart, SkUnicode::kCombine))) {
181         return true;
182     }
183     size_t iterGlyphEnd = std::min(run->size(), glyphEnd);
184     for (size_t glyph = glyphStart; glyph < iterGlyphEnd; ++glyph) {
185         if (run->fGlyphs[glyph] != 0) {
186             return false;
187         }
188     }
189 
190     return true;
191 }
192 
193 // split unresolvedBlock.
194 // extract resolvedBlock(which isUnresolvedGlyphRange is false), emplace back to stagedUnresolvedBlocks.
195 // the rest block emplace back to fUnresolvedBlocks without run.
splitUnresolvedBlockAndStageResolvedSubBlock(std::deque<RunBlock> & stagedUnresolvedBlocks,const RunBlock & unresolvedBlock)196 void OneLineShaper::splitUnresolvedBlockAndStageResolvedSubBlock(
197     std::deque<RunBlock>& stagedUnresolvedBlocks, const RunBlock& unresolvedBlock) {
198     if (unresolvedBlock.fRun == nullptr) {
199         return;
200     }
201     std::shared_ptr<Run> run = unresolvedBlock.fRun;
202     bool hasUnresolvedText = false;
203     size_t curTextStart = EMPTY_INDEX;
204     size_t curGlyphEdge = EMPTY_INDEX;
205     run->iterateGlyphRangeInTextOrder(unresolvedBlock.fGlyphs,
206         [&stagedUnresolvedBlocks, &hasUnresolvedText, &curTextStart, &curGlyphEdge, run, this]
207         (size_t glyphStart, size_t glyphEnd, size_t charStart, size_t charEnd) {
208             if (!isUnresolvedCombineGlyphRange(run, glyphStart, glyphEnd, charStart)) {
209                 if (curTextStart == EMPTY_INDEX) {
210                     curTextStart = charStart;
211                 }
212                 if (curGlyphEdge == EMPTY_INDEX) {
213                     curGlyphEdge = run->leftToRight() ? glyphStart : glyphEnd;
214                 }
215                 return;
216             }
217             hasUnresolvedText = true;
218             this->fUnresolvedBlocks.emplace_back(RunBlock(TextRange(charStart, charEnd)));
219             if (curTextStart == EMPTY_INDEX) {
220                 return;
221             }
222             if (run->leftToRight()) {
223                 stagedUnresolvedBlocks.emplace_back(
224                     run, TextRange(curTextStart, charStart), GlyphRange(curGlyphEdge, glyphStart), 0);
225             } else {
226                 stagedUnresolvedBlocks.emplace_back(
227                     run, TextRange(curTextStart, charStart), GlyphRange(glyphEnd, curGlyphEdge), 0);
228             }
229             curTextStart = EMPTY_INDEX;
230             curGlyphEdge = EMPTY_INDEX;
231         });
232     if (!hasUnresolvedText) {
233         stagedUnresolvedBlocks.emplace_back(unresolvedBlock);
234         return;
235     }
236     if (curTextStart == EMPTY_INDEX) {
237         return;
238     }
239     if (run->leftToRight()) {
240         stagedUnresolvedBlocks.emplace_back(run, TextRange(curTextStart, unresolvedBlock.fText.end),
241             GlyphRange(curGlyphEdge, unresolvedBlock.fGlyphs.end), 0);
242     } else {
243         stagedUnresolvedBlocks.emplace_back(run, TextRange(curTextStart, unresolvedBlock.fText.end),
244             GlyphRange(unresolvedBlock.fGlyphs.start, curGlyphEdge), 0);
245     }
246 }
247 
248 // shape unresolved text separately.
shapeUnresolvedTextSeparatelyFromUnresolvedBlock(const TextStyle & textStyle,const TypefaceVisitor & visitor)249 void OneLineShaper::shapeUnresolvedTextSeparatelyFromUnresolvedBlock(
250     const TextStyle& textStyle, const TypefaceVisitor& visitor) {
251     if (fUnresolvedBlocks.empty()) {
252         return;
253     }
254     TEXT_TRACE_FUNC();
255     std::deque<OneLineShaper::RunBlock> stagedUnresolvedBlocks;
256     size_t unresolvedBlockCount = fUnresolvedBlocks.size();
257     while (unresolvedBlockCount-- > 0) {
258         RunBlock unresolvedBlock = fUnresolvedBlocks.front();
259         fUnresolvedBlocks.pop_front();
260 
261         if (unresolvedBlock.fText.width() <= 1 || unresolvedBlock.fRun == nullptr) {
262             stagedUnresolvedBlocks.emplace_back(unresolvedBlock);
263             continue;
264         }
265 
266         splitUnresolvedBlockAndStageResolvedSubBlock(stagedUnresolvedBlocks, unresolvedBlock);
267     }
268 
269     this->matchResolvedFonts(textStyle, visitor);
270 
271     while (!stagedUnresolvedBlocks.empty()) {
272         auto block = stagedUnresolvedBlocks.front();
273         stagedUnresolvedBlocks.pop_front();
274         fUnresolvedBlocks.emplace_back(block);
275     }
276 }
277 #endif
278 
finish(const Block & block,SkScalar height,SkScalar & advanceX)279 void OneLineShaper::finish(const Block& block, SkScalar height, SkScalar& advanceX) {
280     auto blockText = block.fRange;
281 
282     // Add all unresolved blocks to resolved blocks
283     while (!fUnresolvedBlocks.empty()) {
284         auto unresolved = fUnresolvedBlocks.front();
285         fUnresolvedBlocks.pop_front();
286         if (unresolved.fText.width() == 0) {
287             continue;
288         }
289         fResolvedBlocks.emplace_back(unresolved);
290         fUnresolvedGlyphs += unresolved.fGlyphs.width();
291         fParagraph->addUnresolvedCodepoints(unresolved.fText);
292     }
293 
294     // Sort all pieces by text
295     std::sort(fResolvedBlocks.begin(), fResolvedBlocks.end(),
296               [](const RunBlock& a, const RunBlock& b) {
297                 return a.fText.start < b.fText.start;
298               });
299 
300     // Go through all of them
301     size_t lastTextEnd = blockText.start;
302     for (auto& resolvedBlock : fResolvedBlocks) {
303 
304         if (resolvedBlock.fText.end <= blockText.start) {
305             continue;
306         }
307 
308         if (resolvedBlock.fRun != nullptr) {
309             fParagraph->fFontSwitches.emplace_back(resolvedBlock.fText.start, resolvedBlock.fRun->fFont);
310         }
311 
312         auto run = resolvedBlock.fRun;
313         auto glyphs = resolvedBlock.fGlyphs;
314         auto text = resolvedBlock.fText;
315         if (lastTextEnd != text.start) {
316             SkDEBUGF("Text ranges mismatch: ...:%zu] - [%zu:%zu] (%zu-%zu)\n",
317                      lastTextEnd, text.start, text.end,  glyphs.start, glyphs.end);
318             SkASSERT(false);
319         }
320         lastTextEnd = text.end;
321 
322         if (resolvedBlock.isFullyResolved()) {
323             // Just move the entire run
324             resolvedBlock.fRun->fIndex = this->fParagraph->fRuns.size();
325             this->fParagraph->fRuns.emplace_back(*resolvedBlock.fRun);
326             resolvedBlock.fRun.reset();
327             continue;
328         } else if (run == nullptr) {
329             continue;
330         }
331 
332         auto runAdvance = SkVector::Make(run->posX(glyphs.end) - run->posX(glyphs.start), run->fAdvance.fY);
333         const SkShaper::RunHandler::RunInfo info = {
334                 run->fFont,
335                 run->fBidiLevel,
336                 runAdvance,
337                 glyphs.width(),
338                 SkShaper::RunHandler::Range(text.start - run->fClusterStart, text.width())
339         };
340         this->fParagraph->fRuns.emplace_back(
341                     this->fParagraph,
342                     info,
343                     run->fClusterStart,
344                     height,
345                     block.fStyle.getHalfLeading(),
346 #ifdef ENABLE_TEXT_ENHANCE
347                     block.fStyle.getBaselineShift() + block.fStyle.getBadgeBaseLineShift(),
348 #else
349                     block.fStyle.getBaselineShift(),
350 #endif
351                     this->fParagraph->fRuns.size(),
352                     advanceX
353                 );
354         auto piece = &this->fParagraph->fRuns.back();
355 
356         // TODO: Optimize copying
357         SkPoint zero = {run->fPositions[glyphs.start].fX, 0};
358         for (size_t i = glyphs.start; i <= glyphs.end; ++i) {
359 
360             auto index = i - glyphs.start;
361             if (i < glyphs.end) {
362                 // There are only n glyphs in a run, not n+1.
363                 piece->fGlyphs[index] = run->fGlyphs[i];
364 #ifdef ENABLE_TEXT_ENHANCE
365             }
366             piece->fClusterIndexes[index] = run->fClusterIndexes[i];
367 #else
368                 // fClusterIndexes n+1 is already set to the end of the run.
369                 // Do not attempt to overwrite this value with the cluster index
370                 // that starts the next Run.
371                 // It is assumed later that all clusters in a Run are contained by the Run.
372                 piece->fClusterIndexes[index] = run->fClusterIndexes[i];
373             }
374 #endif
375             piece->fPositions[index] = run->fPositions[i] - zero;
376             piece->fOffsets[index] = run->fOffsets[i];
377 #ifdef ENABLE_TEXT_ENHANCE
378             piece->fGlyphAdvances[index] = run->fGlyphAdvances[i];
379 #endif
380             piece->addX(index, advanceX);
381         }
382 
383         // Carve out the line text out of the entire run text
384         fAdvance.fX += runAdvance.fX;
385         fAdvance.fY = std::max(fAdvance.fY, runAdvance.fY);
386     }
387 
388     advanceX = fAdvance.fX;
389     if (lastTextEnd != blockText.end) {
390         SkDEBUGF("Last range mismatch: %zu - %zu\n", lastTextEnd, blockText.end);
391         SkASSERT(false);
392     }
393 }
394 
395 // Make it [left:right) regardless of a text direction
normalizeTextRange(GlyphRange glyphRange)396 TextRange OneLineShaper::normalizeTextRange(GlyphRange glyphRange) {
397 
398     if (fCurrentRun->leftToRight()) {
399         return TextRange(clusterIndex(glyphRange.start), clusterIndex(glyphRange.end));
400     } else {
401 #ifdef ENABLE_TEXT_ENHANCE
402         return TextRange(clusterIndex(glyphRange.end),
403             glyphRange.start > 0 ?
404             clusterIndex(glyphRange.start) :
405             fCurrentRun->fTextRange.end);
406 #else
407         return TextRange(clusterIndex(glyphRange.end - 1),
408                 glyphRange.start > 0
409                 ? clusterIndex(glyphRange.start - 1)
410                 : fCurrentRun->fTextRange.end);
411 #endif
412     }
413 }
414 
addFullyResolved()415 void OneLineShaper::addFullyResolved() {
416     if (this->fCurrentRun->size() == 0) {
417         return;
418     }
419     RunBlock resolved(fCurrentRun,
420                       this->fCurrentRun->fTextRange,
421                       GlyphRange(0, this->fCurrentRun->size()),
422                       this->fCurrentRun->size());
423     fResolvedBlocks.emplace_back(resolved);
424 }
425 
addUnresolvedWithRun(GlyphRange glyphRange)426 void OneLineShaper::addUnresolvedWithRun(GlyphRange glyphRange) {
427     auto extendedText = this->clusteredText(glyphRange); // It also modifies glyphRange if needed
428     RunBlock unresolved(fCurrentRun, extendedText, glyphRange, 0);
429     if (unresolved.fGlyphs.width() == fCurrentRun->size()) {
430         SkASSERT(unresolved.fText.width() == fCurrentRun->fTextRange.width());
431     } else if (!fUnresolvedBlocks.empty()) {
432         auto& lastUnresolved = fUnresolvedBlocks.back();
433         if (lastUnresolved.fRun != nullptr &&
434             lastUnresolved.fRun->fIndex == fCurrentRun->fIndex) {
435 
436             if (lastUnresolved.fText.end == unresolved.fText.start) {
437               // Two pieces next to each other - can join them
438               lastUnresolved.fText.end = unresolved.fText.end;
439               lastUnresolved.fGlyphs.end = glyphRange.end;
440               return;
441             } else if(lastUnresolved.fText == unresolved.fText) {
442                 // Nothing was resolved; ignore it
443                 return;
444             } else if (lastUnresolved.fText.contains(unresolved.fText)) {
445                 // We get here for the very first unresolved piece
446                 return;
447             } else if (lastUnresolved.fText.intersects(unresolved.fText)) {
448                 // Few pieces of the same unresolved text block can ignore the second one
449                 lastUnresolved.fGlyphs.start = std::min(lastUnresolved.fGlyphs.start, glyphRange.start);
450                 lastUnresolved.fGlyphs.end = std::max(lastUnresolved.fGlyphs.end, glyphRange.end);
451                 lastUnresolved.fText = this->clusteredText(lastUnresolved.fGlyphs);
452                 return;
453             }
454         }
455     }
456     fUnresolvedBlocks.emplace_back(unresolved);
457 }
458 
459 // Glue whitespaces to the next/prev unresolved blocks
460 // (so we don't have chinese text with english whitespaces broken into millions of tiny runs)
sortOutGlyphs(std::function<void (GlyphRange)> && sortOutUnresolvedBLock)461 void OneLineShaper::sortOutGlyphs(std::function<void(GlyphRange)>&& sortOutUnresolvedBLock) {
462 
463     GlyphRange block = EMPTY_RANGE;
464     bool graphemeResolved = false;
465     TextIndex graphemeStart = EMPTY_INDEX;
466     for (size_t i = 0; i < fCurrentRun->size(); ++i) {
467 
468 #ifdef ENABLE_TEXT_ENHANCE
469         ClusterIndex ci = fCurrentRun->leftToRight() ? clusterIndex(i) : clusterIndex(i + 1);
470 #else
471         ClusterIndex ci = clusterIndex(i);
472 #endif
473         // Removing all pretty optimizations for whitespaces
474         // because they get in a way of grapheme rounding
475         // Inspect the glyph
476         auto glyph = fCurrentRun->fGlyphs[i];
477 
478         GraphemeIndex gi = fParagraph->findPreviousGraphemeBoundary(ci);
479         if ((fCurrentRun->leftToRight() ? gi > graphemeStart : gi < graphemeStart) || graphemeStart == EMPTY_INDEX) {
480             // This is the Flutter change
481             // Do not count control codepoints as unresolved
482             bool isControl8 = fParagraph->codeUnitHasProperty(ci,
483                                                               SkUnicode::CodeUnitFlags::kControl);
484             // We only count glyph resolved if all the glyphs in its grapheme are resolved
485             graphemeResolved = glyph != 0 || isControl8;
486             graphemeStart = gi;
487         } else if (glyph == 0) {
488             // Found unresolved glyph - the entire grapheme is unresolved now
489             graphemeResolved = false;
490         }
491 
492         if (!graphemeResolved) { // Unresolved glyph and not control codepoint
493             if (block.start == EMPTY_INDEX) {
494                 // Start new unresolved block
495                 block.start = i;
496                 block.end = EMPTY_INDEX;
497             } else {
498                 // Keep skipping unresolved block
499             }
500         } else { // Resolved glyph or control codepoint
501             if (block.start == EMPTY_INDEX) {
502                 // Keep skipping resolved code points
503             } else {
504                 // This is the end of unresolved block
505                 block.end = i;
506                 sortOutUnresolvedBLock(block);
507                 block = EMPTY_RANGE;
508             }
509         }
510     }
511 
512     // One last block could have been left
513     if (block.start != EMPTY_INDEX) {
514         block.end = fCurrentRun->size();
515         sortOutUnresolvedBLock(block);
516     }
517 }
518 
519 #ifdef ENABLE_TEXT_ENHANCE
generateBlockRange(const Block & block,const TextRange & textRange)520 BlockRange OneLineShaper::generateBlockRange(const Block& block, const TextRange& textRange) {
521     size_t start = std::max(block.fRange.start, textRange.start);
522     size_t end = std::min(block.fRange.end, textRange.end);
523     return BlockRange(start, end);
524 }
525 #endif
526 
iterateThroughFontStyles(TextRange textRange,SkSpan<Block> styleSpan,const ShapeSingleFontVisitor & visitor)527 void OneLineShaper::iterateThroughFontStyles(TextRange textRange,
528                                              SkSpan<Block> styleSpan,
529                                              const ShapeSingleFontVisitor& visitor) {
530     Block combinedBlock;
531     TArray<SkShaper::Feature> features;
532 
533     auto addFeatures = [&features](const Block& block) {
534         for (auto& ff : block.fStyle.getFontFeatures()) {
535             if (ff.fName.size() != 4) {
536                 SkDEBUGF("Incorrect font feature: %s=%d\n", ff.fName.c_str(), ff.fValue);
537                 continue;
538             }
539             SkShaper::Feature feature = {
540                 SkSetFourByteTag(ff.fName[0], ff.fName[1], ff.fName[2], ff.fName[3]),
541                 SkToU32(ff.fValue),
542                 block.fRange.start,
543                 block.fRange.end
544             };
545             features.emplace_back(feature);
546         }
547         // Disable ligatures if letter spacing is enabled.
548         if (block.fStyle.getLetterSpacing() > 0) {
549             features.emplace_back(SkShaper::Feature{
550                 SkSetFourByteTag('l', 'i', 'g', 'a'), 0, block.fRange.start, block.fRange.end
551             });
552         }
553     };
554 
555     for (auto& block : styleSpan) {
556 #ifdef ENABLE_TEXT_ENHANCE
557         BlockRange blockRange = generateBlockRange(block, textRange);
558 #else
559         BlockRange blockRange(std::max(block.fRange.start, textRange.start), std::min(block.fRange.end, textRange.end));
560 #endif
561         if (blockRange.empty()) {
562             continue;
563         }
564         SkASSERT(combinedBlock.fRange.width() == 0 || combinedBlock.fRange.end == block.fRange.start);
565 
566         if (!combinedBlock.fRange.empty()) {
567             if (block.fStyle.matchOneAttribute(StyleType::kFont, combinedBlock.fStyle)) {
568                 combinedBlock.add(blockRange);
569                 addFeatures(block);
570                 continue;
571             }
572             // Resolve all characters in the block for this style
573             visitor(combinedBlock, features);
574         }
575 
576         combinedBlock.fRange = blockRange;
577         combinedBlock.fStyle = block.fStyle;
578         features.clear();
579         addFeatures(block);
580     }
581 
582     visitor(combinedBlock, features);
583 #ifdef SK_DEBUG
584     //printState();
585 #endif
586 }
587 
588 #ifdef ENABLE_TEXT_ENHANCE
matchResolvedFontsFindTypeface(const TextStyle & textStyle,std::shared_ptr<RSTypeface> & typeface,SkUnichar & unicode)589 void OneLineShaper::matchResolvedFontsFindTypeface(const TextStyle& textStyle, std::shared_ptr<RSTypeface>& typeface,
590     SkUnichar& unicode) {
591     FontKey fontKey(unicode, textStyle.getFontStyle(), textStyle.getLocale());
592     auto found = fFallbackFonts.find(fontKey);
593     if (found != fFallbackFonts.end()) {
594         typeface = found->second;
595     } else {
596         typeface = fParagraph->fFontCollection->defaultFallback(
597             unicode, textStyle.getFontStyle(), textStyle.getLocale());
598         if (typeface == nullptr) {
599             // There is no fallback font for this character, so move on to the next character.
600             return;
601         }
602         fFallbackFonts.emplace(fontKey, typeface);
603     }
604 }
605 
matchResolvedFontsByUnicode(const TextStyle & textStyle,const TypefaceVisitor & visitor,std::vector<RunBlock> & hopelessBlocks)606 void OneLineShaper::matchResolvedFontsByUnicode(const TextStyle& textStyle, const TypefaceVisitor& visitor,
607     std::vector<RunBlock>& hopelessBlocks) {
608     auto unresolvedRange = fUnresolvedBlocks.front().fText;
609     auto unresolvedText = fParagraph->text(unresolvedRange);
610     const char* ch = unresolvedText.begin();
611     // We have the global cache for all already found typefaces for SkUnichar
612     // but we still need to keep track of all SkUnichars used in this unresolved block
613     THashSet<SkUnichar> alreadyTriedCodepoints;
614     THashSet<SkTypefaceID> alreadyTriedTypefaces;
615     while (true) {
616         if (ch == unresolvedText.end()) {
617             // Not a single codepoint could be resolved but we finished the block
618             hopelessBlocks.push_back(fUnresolvedBlocks.front());
619             fUnresolvedBlocks.pop_front();
620             break;
621         }
622         // See if we can switch to the next DIFFERENT codepoint
623         SkUnichar unicode = -1;
624         while (ch != unresolvedText.end()) {
625             unicode = nextUtf8Unit(&ch, unresolvedText.end());
626             if (!alreadyTriedCodepoints.contains(unicode)) {
627                 alreadyTriedCodepoints.add(unicode);
628                 break;
629             }
630         }
631         SkASSERT(unicode != -1);
632         std::shared_ptr<RSTypeface> typeface;
633 
634         matchResolvedFontsFindTypeface(textStyle, typeface, unicode);
635         if (typeface == nullptr) {
636             continue;
637         }
638 
639         // Check if we already tried this font on this text range
640         if (!alreadyTriedTypefaces.contains(typeface->GetUniqueID())) {
641             alreadyTriedTypefaces.add(typeface->GetUniqueID());
642         } else {
643             continue;
644         }
645 
646         if (typeface && textStyle.getFontArguments()) {
647             typeface = fParagraph->fFontCollection->CloneTypeface(typeface, textStyle.getFontArguments());
648         }
649 
650         auto resolvedBlocksBefore = fResolvedBlocks.size();
651         auto resolved = visitor(typeface);
652         if (resolved == Resolved::Everything) {
653             if (hopelessBlocks.empty()) {
654                 // Resolved everything, no need to try another font
655                 return;
656             } else if (resolvedBlocksBefore < fResolvedBlocks.size()) {
657                 // There are some resolved blocks
658                 resolved = Resolved::Something;
659             } else {
660                 // All blocks are hopeless
661                 resolved = Resolved::Nothing;
662             }
663         }
664 
665         if (resolved == Resolved::Something) {
666             // Resolved something, no need to try another codepoint
667             break;
668         }
669     }
670 }
671 
matchResolvedFonts(const TextStyle & textStyle,const TypefaceVisitor & visitor)672 void OneLineShaper::matchResolvedFonts(const TextStyle& textStyle,
673                                        const TypefaceVisitor& visitor) {
674     std::vector<std::shared_ptr<RSTypeface>> typefaces = fParagraph->fFontCollection->findTypefaces(
675         textStyle.getFontFamilies(), textStyle.getFontStyle(), textStyle.getFontArguments());
676     for (const auto& typeface : typefaces) {
677         if (visitor(typeface) == Resolved::Everything) {
678             // Resolved everything
679             return;
680         }
681     }
682 
683     if (fParagraph->fFontCollection->fontFallbackEnabled()) {
684         // Give fallback a clue
685         // Some unresolved subblocks might be resolved with different fallback fonts
686         std::vector<RunBlock> hopelessBlocks;
687         while (!fUnresolvedBlocks.empty()) {
688             matchResolvedFontsByUnicode(textStyle, visitor, hopelessBlocks);
689         }
690 
691         // Return hopeless blocks back
692         for (auto& block : hopelessBlocks) {
693             fUnresolvedBlocks.emplace_front(block);
694         }
695     }
696     if (TextGlobalConfig::UndefinedGlyphDisplayUseTofu()) {
697         std::shared_ptr<RSTypeface> notdef = RSTypeface::MakeFromName(NOTDEF_FAMILY, textStyle.getFontStyle());
698         if (notdef != nullptr) {
699             visitor(notdef);
700         }
701     }
702 }
703 #else
matchResolvedFonts(const TextStyle & textStyle,const TypefaceVisitor & visitor)704 void OneLineShaper::matchResolvedFonts(const TextStyle& textStyle,
705                                        const TypefaceVisitor& visitor) {
706     std::vector<sk_sp<SkTypeface>> typefaces = fParagraph->fFontCollection->findTypefaces(textStyle.getFontFamilies(), textStyle.getFontStyle(), textStyle.getFontArguments());
707 
708     for (const auto& typeface : typefaces) {
709         if (visitor(typeface) == Resolved::Everything) {
710             // Resolved everything
711             return;
712         }
713     }
714 
715     if (fParagraph->fFontCollection->fontFallbackEnabled()) {
716         // Give fallback a clue
717         // Some unresolved subblocks might be resolved with different fallback fonts
718         std::vector<RunBlock> hopelessBlocks;
719         while (!fUnresolvedBlocks.empty()) {
720             auto unresolvedRange = fUnresolvedBlocks.front().fText;
721             auto unresolvedText = fParagraph->text(unresolvedRange);
722             const char* ch = unresolvedText.begin();
723             // We have the global cache for all already found typefaces for SkUnichar
724             // but we still need to keep track of all SkUnichars used in this unresolved block
725             THashSet<SkUnichar> alreadyTriedCodepoints;
726             THashSet<SkTypefaceID> alreadyTriedTypefaces;
727             while (true) {
728                 if (ch == unresolvedText.end()) {
729                     // Not a single codepoint could be resolved but we finished the block
730                     hopelessBlocks.push_back(fUnresolvedBlocks.front());
731                     fUnresolvedBlocks.pop_front();
732                     break;
733                 }
734 
735                 // See if we can switch to the next DIFFERENT codepoint/emoji
736                 SkUnichar codepoint = -1;
737                 SkUnichar emojiStart = -1;
738                 // We may loop until we find a new codepoint/emoji run
739                 while (ch != unresolvedText.end()) {
740                   emojiStart = OneLineShaper::getEmojiSequenceStart(
741                                                 fParagraph->fUnicode.get(),
742                                                 &ch,
743                                                 unresolvedText.end());
744                     if (emojiStart != -1) {
745                         // We do not keep a cache of emoji runs, but we need to move the cursor
746                         break;
747                     } else {
748                         codepoint = SkUTF::NextUTF8WithReplacement(&ch, unresolvedText.end());
749                         if (!alreadyTriedCodepoints.contains(codepoint)) {
750                             alreadyTriedCodepoints.add(codepoint);
751                             break;
752                         }
753                     }
754                 }
755 
756                 SkASSERT(codepoint != -1 || emojiStart != -1);
757 
758                 sk_sp<SkTypeface> typeface = nullptr;
759                 if (emojiStart == -1) {
760                     // First try to find in in a cache
761                     FontKey fontKey(codepoint, textStyle.getFontStyle(), textStyle.getLocale());
762                     auto found = fFallbackFonts.find(fontKey);
763                     if (found != nullptr) {
764                         typeface = *found;
765                     }
766                     if (typeface == nullptr) {
767                         typeface = fParagraph->fFontCollection->defaultFallback(
768                                                     codepoint,
769                                                     textStyle.getFontStyle(),
770                                                     textStyle.getLocale());
771                         if (typeface != nullptr) {
772                             fFallbackFonts.set(fontKey, typeface);
773                         }
774                     }
775                 } else {
776                     typeface = fParagraph->fFontCollection->defaultEmojiFallback(
777                                                 emojiStart,
778                                                 textStyle.getFontStyle(),
779                                                 textStyle.getLocale());
780                 }
781 
782                 if (typeface == nullptr) {
783                     // There is no fallback font for this character,
784                     // so move on to the next character.
785                     continue;
786                 }
787 
788                 // Check if we already tried this font on this text range
789                 if (!alreadyTriedTypefaces.contains(typeface->uniqueID())) {
790                     alreadyTriedTypefaces.add(typeface->uniqueID());
791                 } else {
792                     continue;
793                 }
794 
795                 auto resolvedBlocksBefore = fResolvedBlocks.size();
796                 auto resolved = visitor(typeface);
797                 if (resolved == Resolved::Everything) {
798                     if (hopelessBlocks.empty()) {
799                         // Resolved everything, no need to try another font
800                         return;
801                     } else if (resolvedBlocksBefore < fResolvedBlocks.size()) {
802                         // There are some resolved blocks
803                         resolved = Resolved::Something;
804                     } else {
805                         // All blocks are hopeless
806                         resolved = Resolved::Nothing;
807                     }
808                 }
809 
810                 if (resolved == Resolved::Something) {
811                     // Resolved something, no need to try another codepoint
812                     break;
813                 }
814             }
815         }
816 
817         // Return hopeless blocks back
818         for (auto& block : hopelessBlocks) {
819             fUnresolvedBlocks.emplace_front(block);
820         }
821     }
822 }
823 #endif
824 
iterateThroughShapingRegions(const ShapeVisitor & shape)825 bool OneLineShaper::iterateThroughShapingRegions(const ShapeVisitor& shape) {
826 
827     size_t bidiIndex = 0;
828 
829     SkScalar advanceX = 0;
830     for (auto& placeholder : fParagraph->fPlaceholders) {
831 
832         if (placeholder.fTextBefore.width() > 0) {
833             // Shape the text by bidi regions
834             while (bidiIndex < fParagraph->fBidiRegions.size()) {
835                 SkUnicode::BidiRegion& bidiRegion = fParagraph->fBidiRegions[bidiIndex];
836                 auto start = std::max(bidiRegion.start, placeholder.fTextBefore.start);
837                 auto end = std::min(bidiRegion.end, placeholder.fTextBefore.end);
838                 // Set up the iterators (the style iterator points to a bigger region that it could
839                 TextRange textRange(start, end);
840                 auto blockRange = fParagraph->findAllBlocks(textRange);
841                 if (!blockRange.empty()) {
842                     SkSpan<Block> styleSpan(fParagraph->blocks(blockRange));
843 
844                     // Shape the text between placeholders
845                     if (!shape(textRange, styleSpan, advanceX, start, bidiRegion.level)) {
846                         return false;
847                     }
848                 }
849 
850                 if (end == bidiRegion.end) {
851                     ++bidiIndex;
852                 } else /*if (end == placeholder.fTextBefore.end)*/ {
853                     break;
854                 }
855             }
856         }
857 
858         if (placeholder.fRange.width() == 0) {
859             continue;
860         }
861 
862 #ifdef ENABLE_TEXT_ENHANCE
863         std::vector<std::shared_ptr<RSTypeface>> typefaces = fParagraph->fFontCollection->findTypefaces(
864             placeholder.fTextStyle.getFontFamilies(),
865             placeholder.fTextStyle.getFontStyle(),
866             placeholder.fTextStyle.getFontArguments());
867         std::shared_ptr<RSTypeface> typeface = typefaces.empty() ? nullptr : typefaces.front();
868         RSFont font(typeface, placeholder.fTextStyle.getFontSize(), 1, 0);
869 #else
870         // Get the placeholder font
871         std::vector<sk_sp<SkTypeface>> typefaces = fParagraph->fFontCollection->findTypefaces(
872             placeholder.fTextStyle.getFontFamilies(),
873             placeholder.fTextStyle.getFontStyle(),
874             placeholder.fTextStyle.getFontArguments());
875         sk_sp<SkTypeface> typeface = typefaces.empty() ? nullptr : typefaces.front();
876         SkFont font(typeface, placeholder.fTextStyle.getFontSize());
877 #endif
878 
879         // "Shape" the placeholder
880 #ifdef ENABLE_TEXT_ENHANCE
881         uint8_t bidiLevel = (fParagraph->paragraphStyle().getTextDirection() == TextDirection::kLtr)
882             ? UBIDI_LTR : UBIDI_MIXED;
883 #else
884         uint8_t bidiLevel = (bidiIndex < fParagraph->fBidiRegions.size())
885             ? fParagraph->fBidiRegions[bidiIndex].level
886             : 2;
887 #endif
888         const SkShaper::RunHandler::RunInfo runInfo = {
889             font,
890             bidiLevel,
891             SkPoint::Make(placeholder.fStyle.fWidth, placeholder.fStyle.fHeight),
892             1,
893             SkShaper::RunHandler::Range(0, placeholder.fRange.width())
894         };
895         auto& run = fParagraph->fRuns.emplace_back(this->fParagraph,
896                                        runInfo,
897                                        placeholder.fRange.start,
898                                        0.0f,
899                                        0.0f,
900                                        false,
901                                        fParagraph->fRuns.size(),
902                                        advanceX);
903 
904         run.fPositions[0] = { advanceX, 0 };
905         run.fOffsets[0] = {0, 0};
906         run.fClusterIndexes[0] = 0;
907         run.fPlaceholderIndex = &placeholder - fParagraph->fPlaceholders.begin();
908         advanceX += placeholder.fStyle.fWidth;
909     }
910     return true;
911 }
912 
shape()913 bool OneLineShaper::shape() {
914 #ifdef ENABLE_TEXT_ENHANCE
915     TEXT_TRACE_FUNC();
916 #endif
917     // The text can be broken into many shaping sequences
918     // (by place holders, possibly, by hard line breaks or tabs, too)
919     auto limitlessWidth = std::numeric_limits<SkScalar>::max();
920 
921     auto result = iterateThroughShapingRegions(
922             [this, limitlessWidth]
923             (TextRange textRange, SkSpan<Block> styleSpan, SkScalar& advanceX, TextIndex textStart, uint8_t defaultBidiLevel) {
924 
925         // Set up the shaper and shape the next
926 #ifdef ENABLE_TEXT_ENHANCE
927         auto shaper = SkShapers::HB::ShapeDontWrapOrReorder(fParagraph->fUnicode,
928             RSFontMgr::CreateDefaultFontMgr());
929 #else
930         auto shaper = SkShapers::HB::ShapeDontWrapOrReorder(fParagraph->fUnicode,
931                                                             SkFontMgr::RefEmpty());  // no fallback
932 #endif
933 		if (shaper == nullptr) {
934             // For instance, loadICU does not work. We have to stop the process
935             return false;
936         }
937 
938         iterateThroughFontStyles(textRange, styleSpan,
939                 [this, &shaper, defaultBidiLevel, limitlessWidth, &advanceX]
940                 (Block block, TArray<SkShaper::Feature> features) {
941             auto blockSpan = SkSpan<Block>(&block, 1);
942 
943             // Start from the beginning (hoping that it's a simple case one block - one run)
944             fHeight = block.fStyle.getHeightOverride() ? block.fStyle.getHeight() : 0;
945             fUseHalfLeading = block.fStyle.getHalfLeading();
946 #ifdef ENABLE_TEXT_ENHANCE
947             fBaselineShift = block.fStyle.getBaselineShift() + block.fStyle.getBadgeBaseLineShift();
948 #else
949             fBaselineShift = block.fStyle.getBaselineShift();
950 #endif
951             fAdvance = SkVector::Make(advanceX, 0);
952             fCurrentText = block.fRange;
953             fUnresolvedBlocks.emplace_back(RunBlock(block.fRange));
954 
955 #ifdef ENABLE_TEXT_ENHANCE
956             auto typefaceVisitor = [&](std::shared_ptr<RSTypeface> typeface) {
957                 // Create one more font to try
958                 RSFont font(std::move(typeface), block.fStyle.getCorrectFontSize(), 1, 0);
959                 font.SetEdging(RSDrawing::FontEdging::ANTI_ALIAS);
960                 font.SetHinting(RSDrawing::FontHinting::NONE);
961                 font.SetSubpixel(true);
962                 font.SetBaselineSnap(false);
963 #else
964             this->matchResolvedFonts(block.fStyle, [&](sk_sp<SkTypeface> typeface) {
965                 SkFont font(std::move(typeface), block.fStyle.getFontSize());
966                 font.setEdging(SkFont::Edging::kAntiAlias);
967                 font.setHinting(SkFontHinting::kSlight);
968                 font.setSubpixel(true);
969                 font.setBaselineSnap(false);
970 #endif // ENABLE_TEXT_ENHANCE
971 
972 
973 #ifdef ENABLE_TEXT_ENHANCE
974                 scaleFontWithCompressionConfig(font, ScaleOP::COMPRESS);
975 #endif
976                 // Apply fake bold and/or italic settings to the font if the
977                 // typeface's attributes do not match the intended font style.
978 #ifdef ENABLE_TEXT_ENHANCE
979                 int wantedWeight = block.fStyle.getFontStyle().GetWeight();
980                 bool isCustomSymbol = block.fStyle.isCustomSymbol();
981                 bool fakeBold =
982                     wantedWeight >= RSFontStyle::SEMI_BOLD_WEIGHT && !isCustomSymbol &&
983                     wantedWeight - font.GetTypeface()->GetFontStyle().GetWeight() >= 200;
984                 bool fakeItalic =
985                     block.fStyle.getFontStyle().GetSlant() == RSFontStyle::ITALIC_SLANT &&
986                     font.GetTypeface()->GetFontStyle().GetSlant() != RSFontStyle::ITALIC_SLANT;
987                 font.SetEmbolden(fakeBold);
988                 font.SetSkewX(fakeItalic ? -SK_Scalar1 / 4 : 0);
989 #else
990                 int wantedWeight = block.fStyle.getFontStyle().weight();
991                 bool fakeBold =
992                     wantedWeight >= SkFontStyle::kSemiBold_Weight &&
993                     wantedWeight - font.getTypeface()->fontStyle().weight() >= 200;
994                 bool fakeItalic =
995                     block.fStyle.getFontStyle().slant() == SkFontStyle::kItalic_Slant &&
996                     font.getTypeface()->fontStyle().slant() != SkFontStyle::kItalic_Slant;
997                 font.setEmbolden(fakeBold);
998                 font.setSkewX(fakeItalic ? -SK_Scalar1 / 4 : 0);
999 #endif
1000 
1001                 // Walk through all the currently unresolved blocks
1002                 // (ignoring those that appear later)
1003                 auto resolvedCount = fResolvedBlocks.size();
1004                 auto unresolvedCount = fUnresolvedBlocks.size();
1005                 while (unresolvedCount-- > 0) {
1006                     auto unresolvedRange = fUnresolvedBlocks.front().fText;
1007                     if (unresolvedRange == EMPTY_TEXT) {
1008                         // Duplicate blocks should be ignored
1009                         fUnresolvedBlocks.pop_front();
1010                         continue;
1011                     }
1012                     auto unresolvedText = fParagraph->text(unresolvedRange);
1013 
1014                     SkShaper::TrivialFontRunIterator fontIter(font, unresolvedText.size());
1015                     LangIterator langIter(unresolvedText, blockSpan,
1016                                       fParagraph->paragraphStyle().getTextStyle());
1017                     SkShaper::TrivialBiDiRunIterator bidiIter(defaultBidiLevel, unresolvedText.size());
1018                     auto scriptIter = SkShapers::HB::ScriptRunIterator(unresolvedText.begin(),
1019                                                                        unresolvedText.size());
1020                     fCurrentText = unresolvedRange;
1021 
1022                     // Map the block's features to subranges within the unresolved range.
1023                     TArray<SkShaper::Feature> adjustedFeatures(features.size());
1024                     for (const SkShaper::Feature& feature : features) {
1025                         SkRange<size_t> featureRange(feature.start, feature.end);
1026                         if (unresolvedRange.intersects(featureRange)) {
1027                             SkRange<size_t> adjustedRange = unresolvedRange.intersection(featureRange);
1028                             adjustedRange.Shift(-static_cast<std::make_signed_t<size_t>>(unresolvedRange.start));
1029                             adjustedFeatures.push_back({feature.tag, feature.value, adjustedRange.start, adjustedRange.end});
1030                         }
1031                     }
1032 
1033                     shaper->shape(unresolvedText.begin(), unresolvedText.size(),
1034                             fontIter, bidiIter,*scriptIter, langIter,
1035                             adjustedFeatures.data(), adjustedFeatures.size(),
1036                             limitlessWidth, this);
1037 
1038                     // Take off the queue the block we tried to resolved -
1039                     // whatever happened, we have now smaller pieces of it to deal with
1040                     fUnresolvedBlocks.pop_front();
1041                 }
1042 
1043                 if (fUnresolvedBlocks.empty()) {
1044                     // In some cases it does not mean everything
1045                     // (when we excluded some hopeless blocks from the list)
1046                     return Resolved::Everything;
1047                 } else if (resolvedCount < fResolvedBlocks.size()) {
1048                     return Resolved::Something;
1049                 } else {
1050                     return Resolved::Nothing;
1051                 }
1052 #ifdef ENABLE_TEXT_ENHANCE
1053             };
1054             this->matchResolvedFonts(block.fStyle, typefaceVisitor);
1055             this->shapeUnresolvedTextSeparatelyFromUnresolvedBlock(block.fStyle, typefaceVisitor);
1056 #else
1057             });
1058 #endif
1059             this->finish(block, fHeight, advanceX);
1060         });
1061 
1062         return true;
1063     });
1064 
1065     return result;
1066 }
1067 
1068 #ifdef ENABLE_TEXT_ENHANCE
adjustRange(GlyphRange & glyphs,TextRange & textRange)1069 void OneLineShaper::adjustRange(GlyphRange& glyphs, TextRange& textRange) {
1070     if (fCurrentRun->leftToRight()) {
1071         while (glyphs.start > 0 && clusterIndex(glyphs.start) > textRange.start) {
1072             glyphs.start--;
1073             ClusterIndex currentIndex = clusterIndex(glyphs.start);
1074             if (currentIndex < textRange.start) {
1075                 textRange.start = currentIndex;
1076             }
1077         }
1078         while (glyphs.end < fCurrentRun->size() && clusterIndex(glyphs.end) < textRange.end) {
1079             glyphs.end++;
1080             ClusterIndex currentIndex = clusterIndex(glyphs.end);
1081             if (currentIndex > textRange.end) {
1082                 textRange.end = currentIndex;
1083             }
1084         }
1085     } else {
1086         while (glyphs.start > 0 && clusterIndex(glyphs.start) < textRange.end) {
1087             glyphs.start--;
1088             if (glyphs.start == 0) {
1089                 break;
1090             }
1091             ClusterIndex currentIndex = clusterIndex(glyphs.start);
1092             if (currentIndex > textRange.end) {
1093                 textRange.end = currentIndex;
1094             }
1095         }
1096         while (glyphs.end < fCurrentRun->size() && clusterIndex(glyphs.end + 1) > textRange.start) {
1097             glyphs.end++;
1098             ClusterIndex currentIndex = clusterIndex(glyphs.end);
1099             if (currentIndex < textRange.start) {
1100                 textRange.start = currentIndex;
1101             }
1102         }
1103     }
1104 }
1105 #endif
1106 
1107 // When we extend TextRange to the grapheme edges, we also extend glyphs range
clusteredText(GlyphRange & glyphs)1108 TextRange OneLineShaper::clusteredText(GlyphRange& glyphs) {
1109 
1110     enum class Dir { left, right };
1111     enum class Pos { inclusive, exclusive };
1112 
1113     // [left: right)
1114     auto findBaseChar = [&](TextIndex index, Dir dir) -> TextIndex {
1115 
1116         if (dir == Dir::right) {
1117             while (index < fCurrentRun->fTextRange.end) {
1118                 if (this->fParagraph->codeUnitHasProperty(index,
1119                                                       SkUnicode::CodeUnitFlags::kGraphemeStart)) {
1120                     return index;
1121                 }
1122                 ++index;
1123             }
1124             return fCurrentRun->fTextRange.end;
1125         } else {
1126             while (index > fCurrentRun->fTextRange.start) {
1127                 if (this->fParagraph->codeUnitHasProperty(index,
1128                                                       SkUnicode::CodeUnitFlags::kGraphemeStart)) {
1129                     return index;
1130                 }
1131                 --index;
1132             }
1133             return fCurrentRun->fTextRange.start;
1134         }
1135     };
1136 
1137     TextRange textRange(normalizeTextRange(glyphs));
1138     textRange.start = findBaseChar(textRange.start, Dir::left);
1139     textRange.end = findBaseChar(textRange.end, Dir::right);
1140 
1141     // Correct the glyphRange in case we extended the text to the grapheme edges
1142     // TODO: code it without if (as a part of LTR/RTL refactoring)
1143 #ifdef ENABLE_TEXT_ENHANCE
1144     adjustRange(glyphs, textRange);
1145 #else
1146     if (fCurrentRun->leftToRight()) {
1147         while (glyphs.start > 0 && clusterIndex(glyphs.start) > textRange.start) {
1148           glyphs.start--;
1149         }
1150         while (glyphs.end < fCurrentRun->size() && clusterIndex(glyphs.end) < textRange.end) {
1151           glyphs.end++;
1152         }
1153     } else {
1154         while (glyphs.start > 0 && clusterIndex(glyphs.start - 1) < textRange.end) {
1155           glyphs.start--;
1156         }
1157         while (glyphs.end < fCurrentRun->size() && clusterIndex(glyphs.end) > textRange.start) {
1158           glyphs.end++;
1159         }
1160     }
1161 #endif
1162     return { textRange.start, textRange.end };
1163 }
1164 
operator ==(const OneLineShaper::FontKey & other) const1165 bool OneLineShaper::FontKey::operator==(const OneLineShaper::FontKey& other) const {
1166     return fUnicode == other.fUnicode && fFontStyle == other.fFontStyle && fLocale == other.fLocale;
1167 }
1168 
operator ()(const OneLineShaper::FontKey & key) const1169 uint32_t OneLineShaper::FontKey::Hasher::operator()(const OneLineShaper::FontKey& key) const {
1170     return SkGoodHash()(key.fUnicode) ^
1171            SkGoodHash()(key.fFontStyle) ^
1172            SkGoodHash()(key.fLocale);
1173 }
1174 
1175 
1176 // By definition any emoji_sequence starts from a codepoint that has
1177 // UCHAR_EMOJI property.
1178 // If the first codepoint does not have UCHAR_EMOJI_COMPONENT property,
1179 // we have an emoji sequence right away.
1180 // In two (and only two) cases an emoji sequence starts with a codepoint
1181 // that also has UCHAR_EMOJI_COMPONENT property.
1182 // emoji_flag_sequence   := regional_indicator regional_indicator
1183 // emoji_keycap_sequence := [0-9#*] \x{FE0F 20E3}
1184 // These two cases require additional checks of the next codepoint(s).
getEmojiSequenceStart(SkUnicode * unicode,const char ** begin,const char * end)1185 SkUnichar OneLineShaper::getEmojiSequenceStart(SkUnicode* unicode, const char** begin, const char* end) {
1186     const char* next = *begin;
1187     auto codepoint1 = SkUTF::NextUTF8WithReplacement(&next, end);
1188 
1189     if (!unicode->isEmoji(codepoint1)) {
1190         // This is not a basic emoji nor it an emoji sequence
1191         return -1;
1192     }
1193 
1194     if (!unicode->isEmojiComponent(codepoint1)) {
1195         // This is an emoji sequence start
1196         *begin = next;
1197         return codepoint1;
1198     }
1199 
1200     // Now we need to look at the next codepoint to see what is going on
1201     const char* last = next;
1202     auto codepoint2 = SkUTF::NextUTF8WithReplacement(&last, end);
1203 
1204     // emoji_flag_sequence
1205     if (unicode->isRegionalIndicator(codepoint2)) {
1206         // We expect a second regional indicator here
1207         if (unicode->isRegionalIndicator(codepoint2)) {
1208             *begin = next;
1209             return codepoint1;
1210         } else {
1211             // That really should not happen assuming correct UTF8 text
1212             return -1;
1213         }
1214     }
1215 
1216     // emoji_keycap_sequence
1217     if (codepoint2 == 0xFE0F) {
1218         auto codepoint3 = SkUTF::NextUTF8WithReplacement(&last, end);
1219         if (codepoint3 == 0x20E3) {
1220             *begin = next;
1221             return codepoint1;
1222         }
1223     }
1224 
1225     return -1;
1226 }
1227 
1228 }  // namespace textlayout
1229 }  // namespace skia
1230