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