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