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