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