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