• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // Copyright 2021 Google LLC.
2 #include "experimental/sktext/include/Text.h"
3 #include "experimental/sktext/src/LogicalRun.h"
4 #include "experimental/sktext/src/VisualRun.h"
5 #include <memory>
6 #include <stack>
7 
8 using namespace skia_private;
9 
10 namespace skia {
11 namespace text {
UnicodeText(std::unique_ptr<SkUnicode> unicode,SkSpan<uint16_t> utf16)12 UnicodeText::UnicodeText(std::unique_ptr<SkUnicode> unicode, SkSpan<uint16_t> utf16)
13     : fText16(std::u16string((char16_t*)utf16.data(), utf16.size()))
14     , fUnicode(std::move(unicode)) {
15     initialize(utf16);
16 }
17 
UnicodeText(std::unique_ptr<SkUnicode> unicode,const SkString & utf8)18 UnicodeText::UnicodeText(std::unique_ptr<SkUnicode> unicode, const SkString& utf8)
19     : fUnicode(std::move(unicode)) {
20     fText16 = fUnicode->convertUtf8ToUtf16(utf8);
21     initialize(SkSpan<uint16_t>((uint16_t*)fText16.data(), fText16.size()));
22 }
23 
isWhitespaces(TextRange range) const24 bool UnicodeText::isWhitespaces(TextRange range) const {
25     for (auto i = range.fStart; i < range.fEnd; ++i) {
26         if (!this->hasProperty(i, SkUnicode::CodeUnitFlags::kPartOfWhiteSpaceBreak)) {
27             return false;
28         }
29     }
30     return true;
31 }
32 
initialize(SkSpan<uint16_t> utf16)33 void UnicodeText::initialize(SkSpan<uint16_t> utf16) {
34     if (!fUnicode) {
35         SkASSERT(fUnicode);
36         return;
37     }
38 
39     if (!fUnicode->computeCodeUnitFlags(
40                 (char16_t*)utf16.data(), utf16.size(), false, &fCodeUnitProperties)) {
41         return;
42     }
43 }
44 
resolveFonts(SkSpan<FontBlock> blocks)45 std::unique_ptr<FontResolvedText> UnicodeText::resolveFonts(SkSpan<FontBlock> blocks) {
46 
47     auto fontResolvedText = std::make_unique<FontResolvedText>();
48 
49     TextRange adjustedBlock(0, 0);
50     TextIndex index = 0;
51     for (auto& block : blocks) {
52 
53         index += block.charCount;
54         adjustedBlock.fStart = adjustedBlock.fEnd;
55         adjustedBlock.fEnd = index;
56         if (adjustedBlock.fStart >= adjustedBlock.fEnd) {
57             // The last block adjustment went over the entire block
58             continue;
59         }
60 
61         // Move the end of the block to the right until it's on the grapheme edge
62         while (adjustedBlock.fEnd < this->fText16.size() &&
63                !this->hasProperty(adjustedBlock.fEnd, SkUnicode::CodeUnitFlags::kGraphemeStart)) {
64             ++adjustedBlock.fEnd;
65         }
66         SkASSERT(block.type == BlockType::kFontChain);
67         fontResolvedText->resolveChain(this, adjustedBlock, *block.chain);
68     }
69 
70     std::sort(fontResolvedText->fResolvedFonts.begin(), fontResolvedText->fResolvedFonts.end(),
71               [](const ResolvedFontBlock& a, const ResolvedFontBlock& b) {
72                 return a.textRange.fStart < b.textRange.fStart;
73               });
74 /*
75     SkDebugf("Resolved:\n");
76     for (auto& f : fontResolvedText->fResolvedFonts) {
77         SkDebugf("[%d:%d)\n", f.textRange.fStart, f.textRange.fEnd);
78     }
79 */
80     return fontResolvedText;
81 }
82 
resolveChain(UnicodeText * unicodeText,TextRange textRange,const FontChain & fontChain)83 bool FontResolvedText::resolveChain(UnicodeText* unicodeText, TextRange textRange, const FontChain& fontChain) {
84 
85     std::deque<TextRange> unresolvedTexts;
86     unresolvedTexts.push_back(textRange);
87     for (auto fontIndex = 0; fontIndex < fontChain.count(); ++fontIndex) {
88         auto typeface = fontChain[fontIndex];
89 
90         std::deque<TextRange> newUnresolvedTexts;
91         // Check all text range that have not been resolved yet
92         while (!unresolvedTexts.empty()) {
93             // Take the first unresolved
94             auto unresolvedText = unresolvedTexts.front();
95             unresolvedTexts.pop_front();
96 
97             // Resolve font for the entire grapheme
98             auto start = newUnresolvedTexts.size();
99             unicodeText->forEachGrapheme(unresolvedText, [&](TextRange grapheme) {
100                 auto count = typeface->textToGlyphs(unicodeText->getText16().data() + grapheme.fStart, grapheme.width() * 2, SkTextEncoding::kUTF16, nullptr, 0);
101                 AutoTArray<SkGlyphID> glyphs(count);
102                 typeface->textToGlyphs(unicodeText->getText16().data() + grapheme.fStart, grapheme.width() * 2, SkTextEncoding::kUTF16, glyphs.data(), count);
103                 for (auto i = 0; i < count; ++i) {
104                     if (glyphs[i] == 0) {
105                         if (newUnresolvedTexts.empty() || newUnresolvedTexts.back().fEnd < grapheme.fStart) {
106                             // It's a new unresolved block
107                             newUnresolvedTexts.push_back(grapheme);
108                         } else {
109                             // Let's extend the last unresolved block
110                             newUnresolvedTexts.back().fEnd = grapheme.fEnd;
111                         }
112                         break;
113                     }
114                 }
115             });
116             // Let's fill the resolved blocks with the current font
117             TextRange resolvedText(unresolvedText.fStart, unresolvedText.fStart);
118             for (auto newUnresolvedText : newUnresolvedTexts) {
119                 if (start > 0) {
120                     --start;
121                     continue;
122                 }
123                 resolvedText.fEnd = newUnresolvedText.fStart;
124                 if (resolvedText.width() > 0) {
125                     // Add another resolved block
126                     fResolvedFonts.emplace_back(resolvedText, typeface, fontChain.fontSize(), SkFontStyle::Normal());
127                 }
128                 resolvedText.fStart = newUnresolvedText.fEnd;
129             }
130             resolvedText.fEnd = unresolvedText.fEnd;
131             if (resolvedText.width() > 0) {
132                 // Add the last resolved block
133                 fResolvedFonts.emplace_back(resolvedText, typeface, fontChain.fontSize(), SkFontStyle::Normal());
134             }
135         }
136 
137         // Try the next font in chain
138         SkASSERT(unresolvedTexts.empty());
139         unresolvedTexts = std::move(newUnresolvedTexts);
140     }
141 
142     return unresolvedTexts.empty();
143 }
144 
145 // Font iterator that finds all formatting marks
146 // and breaks runs on them (so we can select and interpret them later)
147 class FormattingFontIterator final : public SkShaper::FontRunIterator {
148 public:
FormattingFontIterator(TextIndex textCount,SkSpan<ResolvedFontBlock> fontBlocks,SkSpan<TextIndex> marks)149     FormattingFontIterator(TextIndex textCount,
150                            SkSpan<ResolvedFontBlock> fontBlocks,
151                            SkSpan<TextIndex> marks)
152             : fTextCount(textCount)
153             , fFontBlocks(fontBlocks)
154             , fFormattingMarks(marks)
155             , fCurrentBlock(fontBlocks.begin())
156             , fCurrentMark(marks.begin())
157             , fCurrentFontIndex(fCurrentBlock->textRange.fEnd) {
158         fCurrentFont = this->createFont(*fCurrentBlock);
159     }
consume()160     void consume() override {
161         SkASSERT(fCurrentBlock < fFontBlocks.end());
162         SkASSERT(fCurrentMark < fFormattingMarks.end());
163         if (fCurrentFontIndex > *fCurrentMark) {
164             ++fCurrentMark;
165             return;
166         }
167         if (fCurrentFontIndex == *fCurrentMark) {
168             ++fCurrentMark;
169         }
170         ++fCurrentBlock;
171         if (fCurrentBlock < fFontBlocks.end()) {
172             fCurrentFontIndex = fCurrentBlock->textRange.fEnd;
173             fCurrentFont = this->createFont(*fCurrentBlock);
174         }
175     }
endOfCurrentRun() const176     size_t endOfCurrentRun() const override {
177         SkASSERT(fCurrentMark != fFormattingMarks.end() || fCurrentBlock != fFontBlocks.end());
178         if (fCurrentMark == fFormattingMarks.end()) {
179             return fCurrentFontIndex;
180         } else if (fCurrentBlock == fFontBlocks.end()) {
181             return *fCurrentMark;
182         } else {
183             return std::min(fCurrentFontIndex, *fCurrentMark);
184         }
185     }
atEnd() const186     bool atEnd() const override {
187         return (fCurrentBlock == fFontBlocks.end() || fCurrentFontIndex == fTextCount) &&
188                (fCurrentMark == fFormattingMarks.end() || *fCurrentMark == fTextCount);
189     }
currentFont() const190     const SkFont& currentFont() const override { return fCurrentFont; }
createFont(const ResolvedFontBlock & resolvedFont) const191     SkFont createFont(const ResolvedFontBlock& resolvedFont) const {
192         SkFont font(resolvedFont.typeface, resolvedFont.size);
193         font.setEdging(SkFont::Edging::kAntiAlias);
194         font.setHinting(SkFontHinting::kSlight);
195         font.setSubpixel(true);
196         return font;
197     }
198 private:
199     TextIndex const fTextCount;
200     SkSpan<ResolvedFontBlock> fFontBlocks;
201     SkSpan<TextIndex> fFormattingMarks;
202     ResolvedFontBlock* fCurrentBlock;
203     TextIndex* fCurrentMark;
204     TextIndex fCurrentFontIndex;
205     SkFont fCurrentFont;
206 };
207 
shape(UnicodeText * unicodeText,TextDirection textDirection)208 std::unique_ptr<ShapedText> FontResolvedText::shape(UnicodeText* unicodeText,
209                                                     TextDirection textDirection) {
210     // Get utf8 <-> utf16 conversion tables.
211     // We need to pass to SkShaper indices in utf8 and then convert them back to utf16 for SkText
212     auto text16 = unicodeText->getText16();
213     auto text8 = SkUnicode::convertUtf16ToUtf8(std::u16string(text16.data(), text16.size()));
214     size_t utf16Index = 0;
215     SkTArray<size_t, true> UTF16FromUTF8;
216     SkTArray<size_t, true> UTF8FromUTF16;
217     UTF16FromUTF8.push_back_n(text8.size() + 1, utf16Index);
218     UTF8FromUTF16.push_back_n(text16.size() + 1, utf16Index);
219     unicodeText->getUnicode()->forEachCodepoint(text8.c_str(), text8.size(),
220     [&](SkUnichar unichar, int32_t start, int32_t end, int32_t count) {
221         // utf8 index group of 1, 2 or 3 can be represented with one utf16 index group
222         for (auto i = start; i < end; ++i) {
223             UTF16FromUTF8[i] = utf16Index;
224         }
225         // utf16 index group of 1 or 2  can refer to the same group of utf8 indices
226         for (; count != 0; --count) {
227             UTF8FromUTF16[utf16Index++] = start;
228         }
229     });
230     UTF16FromUTF8[text8.size()] = text16.size();
231     UTF8FromUTF16[text16.size()] = text8.size();
232     // Break text into pieces by font blocks and by formatting marks
233     // Formatting marks: \n (and possibly some other later)
234     std::vector<size_t> formattingMarks;
235     for (size_t i = 0; i < text16.size(); ++i) {
236         if (unicodeText->isHardLineBreak(i)) {
237             formattingMarks.emplace_back(UTF8FromUTF16[i]);
238             formattingMarks.emplace_back(UTF8FromUTF16[i + 1]);
239             ++i;
240         }
241     }
242     formattingMarks.emplace_back(text8.size()/* UTF8FromUTF16[text16.size() */);
243     // Convert fontBlocks from utf16 to utf8
244     SkTArray<ResolvedFontBlock, true> fontBlocks8;
245     for (auto& fb : fResolvedFonts) {
246         TextRange text8(UTF8FromUTF16[fb.textRange.fStart], UTF8FromUTF16[fb.textRange.fEnd]);
247         fontBlocks8.emplace_back(text8, fb.typeface, fb.size, fb.style);
248     }
249     auto shapedText = std::make_unique<ShapedText>();
250     // Shape the text
251     FormattingFontIterator fontIter(text8.size(),
252                                     SkSpan<ResolvedFontBlock>(fontBlocks8.data(), fontBlocks8.size()),
253                                     SkSpan<TextIndex>(&formattingMarks[0], formattingMarks.size()));
254     SkShaper::TrivialLanguageRunIterator langIter(text8.c_str(), text8.size());
255     std::unique_ptr<SkShaper::BiDiRunIterator> bidiIter(
256         SkShaper::MakeSkUnicodeBidiRunIterator(
257             unicodeText->getUnicode(), text8.c_str(), text8.size(), textDirection == TextDirection::kLtr ? 0 : 1));
258     std::unique_ptr<SkShaper::ScriptRunIterator> scriptIter(
259         SkShaper::MakeSkUnicodeHbScriptRunIterator(text8.c_str(), text8.size()));
260     auto shaper = SkShaper::MakeShapeDontWrapOrReorder(unicodeText->getUnicode()->copy());
261     if (shaper == nullptr) {
262         // For instance, loadICU does not work. We have to stop the process
263         return nullptr;
264     }
265     shaper->shape(
266             text8.c_str(), text8.size(),
267             fontIter, *bidiIter, *scriptIter, langIter,
268             std::numeric_limits<SkScalar>::max(), shapedText.get());
269     if (shapedText->fLogicalRuns.empty()) {
270         // Create a fake run for an empty text (to avoid all the checks)
271         SkShaper::RunHandler::RunInfo emptyInfo {
272             fontIter.createFont(fResolvedFonts.front()),
273             0,
274             SkVector::Make(0.0f, 0.0f),
275             0,
276             SkShaper::RunHandler::Range(0, 0)
277         };
278         shapedText->fLogicalRuns.emplace_back(emptyInfo, 0, 0.0f);
279         shapedText->fLogicalRuns.front().commit();
280     }
281     // Fill out all code unit properties
282     for (auto& logicalRun : shapedText->fLogicalRuns) {
283         // Convert utf8 range into utf16 range
284         logicalRun.convertUtf16Range([&](unsigned long index8) {
285             return UTF16FromUTF8[index8];
286         });
287         // Convert all utf8 indexes into utf16 indexes (and also shift them to be on the entire text scale, too)
288         logicalRun.convertClusterIndexes([&](TextIndex clusterIndex8) {
289             return UTF16FromUTF8[clusterIndex8];
290         });
291         // Detect and mark line break runs
292         if (logicalRun.getTextRange().width() == 1 &&
293             logicalRun.size() == 1 &&
294             unicodeText->isHardLineBreak(logicalRun.getTextRange().fStart)) {
295             logicalRun.setRunType(LogicalRunType::kLineBreak);
296         }
297     }
298     return shapedText;
299 }
300 
301 // TODO: Implement the vertical restriction (height) and add ellipsis
wrap(UnicodeText * unicodeText,float width,float height)302 std::unique_ptr<WrappedText> ShapedText::wrap(UnicodeText* unicodeText, float width, float height) {
303     auto wrappedText = std::unique_ptr<WrappedText>(new WrappedText());
304     // line + spaces + clusters
305     Stretch line;
306     Stretch spaces;
307     Stretch clusters;
308     Stretch cluster;
309     for (size_t runIndex = 0; runIndex < this->fLogicalRuns.size(); ++runIndex ) {
310         auto& run = this->fLogicalRuns[runIndex];
311         if (run.getRunType() == LogicalRunType::kLineBreak) {
312             // This is the end of the word, the end of the line
313             if (!clusters.isEmpty()) {
314                 line.moveTo(spaces);
315                 line.moveTo(clusters);
316                 spaces = clusters;
317             }
318             this->addLine(wrappedText.get(), unicodeText->getUnicode(), line, spaces, true);
319             line = spaces;
320             clusters = spaces;
321             continue;
322         }
323         TextMetrics runMetrics(run.fFont);
324 
325         // Let's wrap the text
326         GlyphRange clusterGlyphs;
327         DirTextRange clusterText(EMPTY_RANGE, run.leftToRight());
328         for (size_t glyphIndex = 0; glyphIndex < run.fPositions.size(); ++glyphIndex) {
329             auto textIndex = run.fClusters[glyphIndex];
330 
331             if (clusterText == EMPTY_RANGE) {
332                 // The beginning of a new line (or the first one)
333                 clusterText = DirTextRange(textIndex, textIndex, run.leftToRight());
334                 clusterGlyphs = GlyphRange(glyphIndex, glyphIndex);
335 
336                 Stretch empty(GlyphPos(runIndex, glyphIndex), textIndex, runMetrics);
337                 line = empty;
338                 spaces = empty;
339                 clusters = empty;
340                 continue;
341             }
342 
343             if (textIndex == clusterText.fStart) {
344                 // Skip until the next cluster
345                 continue;
346             }
347 
348             // Finish the cluster (notice that it belongs to a single run)
349             clusterText.fStart = clusterText.fEnd;
350             clusterText.fEnd = textIndex;
351             clusterGlyphs.fStart = clusterGlyphs.fEnd;
352             clusterGlyphs.fEnd = glyphIndex;
353             cluster = Stretch(runIndex, clusterGlyphs, clusterText.normalized(), run.calculateWidth(clusterGlyphs), runMetrics);
354 
355             auto isWhitespaces = unicodeText->isWhitespaces(cluster.textRange());
356             // line + spaces + clusters + cluster
357             if (isWhitespaces) {
358                 // This is the end of the word
359                 if (!clusters.isEmpty()) {
360                     line.moveTo(spaces);
361                     line.moveTo(clusters);
362                     spaces = clusters;
363                 }
364                 spaces.moveTo(cluster);
365                 clusters = cluster;
366                 // Whitespaces do not extend the line width so no wrapping
367                 continue;
368             } else if (!SkScalarIsFinite(width)) {
369                 // No wrapping - the endless line
370                 clusters.moveTo(cluster);
371                 continue;
372             }
373             // Now let's find out if we can add the cluster to the line
374             auto currentWidth = line.width() + spaces.width() + clusters.width() + cluster.width();
375             if (currentWidth > width) {
376                 // Finally, the wrapping case
377                 if (line.isEmpty()) {
378                     if (spaces.isEmpty() && clusters.isEmpty()) {
379                         // There is only this cluster and it's too long; we are drawing it anyway
380                         line.moveTo(cluster);
381                     } else {
382                         // We break the only one word on the line by this cluster
383                         line.moveTo(clusters);
384                     }
385                 } else {
386                   // We move clusters + cluster on the next line
387                   // TODO: Parametrise possible ways of breaking too long word
388                   //  (start it from a new line or squeeze the part of it on this line)
389                 }
390                 this->addLine(wrappedText.get(), unicodeText->getUnicode(), line, spaces, false);
391                 line = spaces;
392             }
393             clusters.moveTo(cluster);
394 
395             clusterGlyphs.fStart = clusterGlyphs.fEnd;
396             clusterText.fStart = clusterText.fEnd;
397         }
398     }
399 
400     // Deal with the last line
401     if (!clusters.isEmpty()) {
402         line.moveTo(spaces);
403         line.moveTo(clusters);
404         spaces = clusters;
405     } else if (wrappedText->fVisualLines.empty()) {
406         // Empty text; we still need a line to avoid checking for empty lines every time
407         line.moveTo(cluster);
408         spaces.moveTo(cluster);
409     }
410     this->addLine(wrappedText.get(), unicodeText->getUnicode(), line, spaces, false);
411     wrappedText->fActualSize.fWidth = width;
412     return wrappedText;
413 }
414 
getVisualOrder(SkUnicode * unicode,RunIndex startRun,RunIndex endRun)415 SkTArray<int32_t> ShapedText::getVisualOrder(SkUnicode* unicode, RunIndex startRun, RunIndex endRun) {
416     auto numRuns = endRun - startRun + 1;
417     SkTArray<int32_t> results;
418     results.push_back_n(numRuns);
419     if (numRuns == 0) {
420         return results;
421     }
422     SkTArray<SkUnicode::BidiLevel> runLevels;
423     runLevels.push_back_n(numRuns);
424     size_t runLevelsIndex = 0;
425     for (RunIndex runIndex = startRun; runIndex <= endRun; ++runIndex) {
426         runLevels[runLevelsIndex++] = fLogicalRuns[runIndex].bidiLevel();
427     }
428     SkASSERT(runLevelsIndex == numRuns);
429     unicode->reorderVisual(runLevels.data(), numRuns, results.data());
430     return results;
431 }
432 
433 // TODO: Fill line fOffset.fY
addLine(WrappedText * wrappedText,SkUnicode * unicode,Stretch & stretch,Stretch & spaces,bool hardLineBreak)434 void ShapedText::addLine(WrappedText* wrappedText, SkUnicode* unicode, Stretch& stretch, Stretch& spaces, bool hardLineBreak) {
435     auto spacesStart = spaces.glyphStart();
436     Stretch lineStretch = stretch;
437     lineStretch.moveTo(spaces);
438     auto startRun = lineStretch.glyphStart().runIndex();
439     auto endRun = lineStretch.glyphEnd().runIndex();
440     // Reorder and cut (if needed) runs so they fit the line
441     auto visualOrder = this->getVisualOrder(unicode, startRun, endRun);
442     // Walk through the line's runs in visual order
443     auto firstRunIndex = startRun;
444     auto runStart = wrappedText->fVisualRuns.size();
445     SkScalar runOffsetInLine = 0.0f;
446     for (auto visualIndex : visualOrder) {
447         auto& logicalRun = fLogicalRuns[firstRunIndex + visualIndex];
448         if (logicalRun.getRunType() == LogicalRunType::kLineBreak) {
449             SkASSERT(false);
450         }
451         bool isFirstRun = startRun == (firstRunIndex + visualIndex);
452         bool isLastRun = endRun == (firstRunIndex + visualIndex);
453         bool isSpaceRun = spacesStart.runIndex() == (firstRunIndex + visualIndex);
454         auto glyphStart = isFirstRun ? lineStretch.glyphStart().glyphIndex() : 0;
455         auto glyphEnd = isLastRun ? lineStretch.glyphEnd().glyphIndex() : logicalRun.size();
456         auto glyphSize = glyphEnd - glyphStart;
457         auto glyphSpaces = isSpaceRun ? spacesStart.glyphIndex() : glyphEnd;
458         if (glyphSpaces > glyphStart) {
459             auto textStart = isFirstRun ? lineStretch.textRange().fStart : logicalRun.fUtf16Range.fStart;
460             auto textEnd = isLastRun ? lineStretch.textRange().fEnd : logicalRun.fUtf16Range.fEnd;
461             wrappedText->fVisualRuns.emplace_back(TextRange(textStart, textEnd),
462                                                   glyphSpaces - glyphStart,
463                                                   logicalRun.fFont,
464                                                   lineStretch.textMetrics().baseline(),
465                                                   SkPoint::Make(runOffsetInLine, wrappedText->fActualSize.fHeight),
466                                                   logicalRun.leftToRight(),
467                                                   SkSpan<SkPoint>(&logicalRun.fPositions[glyphStart], glyphSize + 1),
468                                                   SkSpan<SkGlyphID>(&logicalRun.fGlyphs[glyphStart], glyphSize),
469                                                   SkSpan<uint32_t>((uint32_t*)&logicalRun.fClusters[glyphStart], glyphSize + 1));
470         }
471         runOffsetInLine += logicalRun.calculateWidth(glyphStart, glyphEnd);
472     }
473     auto runRange = wrappedText->fVisualRuns.size() == runStart
474                     ? SkSpan<VisualRun>(nullptr, 0)
475                     : SkSpan<VisualRun>(&wrappedText->fVisualRuns[runStart], wrappedText->fVisualRuns.size() - runStart);
476     wrappedText->fVisualLines.emplace_back(lineStretch.textRange(), hardLineBreak, wrappedText->fActualSize.fHeight, runRange);
477     wrappedText->fActualSize.fHeight += lineStretch.textMetrics().height();
478     wrappedText->fActualSize.fWidth = std::max(wrappedText->fActualSize.fWidth, lineStretch.width());
479     stretch.clean();
480     spaces.clean();
481 }
482 
format(TextAlign textAlign,TextDirection textDirection)483 void WrappedText::format(TextAlign textAlign, TextDirection textDirection) {
484     if (fAligned == textAlign) {
485         return;
486     }
487     SkScalar verticalOffset = 0.0f;
488     for (auto& line : this->fVisualLines) {
489         if (textAlign == TextAlign::kLeft) {
490             // Good by default
491         } else if (textAlign == TextAlign::kCenter) {
492             line.fOffset.fX = (this->fActualSize.width() - line.fActualWidth) / 2.0f;
493         } else {
494             // TODO: Implement all formatting features
495         }
496         line.fOffset.fY = verticalOffset;
497         verticalOffset += line.fTextMetrics.height();
498     }
499 }
500 
visit(Visitor * visitor) const501 void WrappedText::visit(Visitor* visitor) const {
502     size_t lineIndex = 0;
503     SkScalar verticalOffset = 0.0f;
504     for (auto& line : fVisualLines) {
505         visitor->onBeginLine(lineIndex, line.text(), line.isHardBreak(), SkRect::MakeXYWH(0, verticalOffset, line.fActualWidth, line.fTextMetrics.height()));
506         // Select the runs that are on the line
507         size_t glyphCount = 0ul;
508         for (auto& run : line.fRuns) {
509             auto diff = line.fTextMetrics.above() - run.fTextMetrics.above();
510             SkRect boundingRect = SkRect::MakeXYWH(line.fOffset.fX + run.fPositions[0].fX, line.fOffset.fY + diff, run.width(), run.fTextMetrics.height());
511             visitor->onGlyphRun(run.fFont, run.dirTextRange(), boundingRect, run.trailingSpacesStart(),
512                                 run.size(), run.fGlyphs.data(), run.fPositions.data(), run.fClusters.data());
513             glyphCount += run.size();
514         }
515         visitor->onEndLine(lineIndex, line.text(), line.trailingSpaces(), glyphCount);
516         verticalOffset += line.fTextMetrics.height();
517         ++lineIndex;
518     }
519 }
520 
chunksToBlocks(SkSpan<size_t> chunks)521 std::vector<TextIndex> WrappedText::chunksToBlocks(SkSpan<size_t> chunks) {
522     std::vector<TextIndex> blocks;
523     blocks.reserve(chunks.size() + 1);
524     TextIndex index = 0;
525     for (auto chunk : chunks) {
526         blocks.emplace_back(index);
527         index += chunk;
528     }
529     blocks.emplace_back(index);
530     return blocks;
531 }
532 
limitBlocks(TextRange textRange,SkSpan<TextIndex> blocks)533 SkSpan<TextIndex> WrappedText::limitBlocks(TextRange textRange, SkSpan<TextIndex> blocks) {
534     TextRange limited = EMPTY_RANGE;
535     for (auto i = 0ul; i < blocks.size(); ++i) {
536         auto block = blocks[i];
537         if (textRange.fEnd < block) {
538             continue;
539         } else if (textRange.fStart >= block) {
540             break;
541         } else if (limited.fStart == EMPTY_INDEX) {
542             limited.fStart = i;
543         }
544         limited.fEnd = i;
545     }
546 
547     return SkSpan<TextIndex>(&blocks[textRange.fStart], textRange.width());
548 }
549 
visit(UnicodeText * unicodeText,Visitor * visitor,PositionType positionType,SkSpan<size_t> chunks) const550 void WrappedText::visit(UnicodeText* unicodeText, Visitor* visitor, PositionType positionType, SkSpan<size_t> chunks) const {
551     // Decor blocks have to be sorted by text cannot intersect but can skip some parts of the text
552     // (in which case we use default text style from paragraph style)
553     // The edges of the decor blocks don't have to match glyph, grapheme or even unicode code point edges
554     // It's out responsibility to adjust them to some reasonable values
555     // [a:b) -> [c:d) where
556     // c is closest GG cluster edge to a from the left and d is closest GG cluster edge to b from the left
557     auto textBlocks = WrappedText::chunksToBlocks(chunks);
558     SkScalar verticalOffset = 0.0f;
559     LineIndex lineIndex = 0ul;
560     size_t glyphCount = 0ul;
561     for (auto& line : fVisualLines) {
562         visitor->onBeginLine(lineIndex, line.text(), line.isHardBreak(), SkRect::MakeXYWH(0, verticalOffset, line.fActualWidth, line.fTextMetrics.height()));
563         RunIndex runIndex = 0ul;
564         auto lineBlocks = WrappedText::limitBlocks(line.fText, SkSpan<TextIndex>(textBlocks.data(), textBlocks.size()));
565         for (auto& run : fVisualRuns) {
566             run.forEachTextBlockInGlyphRange(lineBlocks, [&](DirTextRange dirTextRange) {
567                 GlyphRange glyphRange = this->textToGlyphs(unicodeText, positionType, runIndex, dirTextRange);
568                 auto diff = line.fTextMetrics.above() - run.fTextMetrics.above();
569                 SkRect boundingRect =
570                         SkRect::MakeXYWH(line.fOffset.fX + run.fPositions[glyphRange.fStart].fX,
571                                          line.fOffset.fY + diff,
572                                          run.calculateWidth(glyphRange),
573                                          run.fTextMetrics.height());
574                 visitor->onGlyphRun(run.fFont,
575                                     dirTextRange,
576                                     boundingRect,
577                                     run.trailingSpacesStart(),
578                                     glyphRange.width(),
579                                     &run.fGlyphs[glyphRange.fStart],
580                                     &run.fPositions[glyphRange.fStart],
581                                     &run.fClusters[glyphRange.fStart]);
582             });
583             ++runIndex;
584             glyphCount += run.size();
585         }
586         visitor->onEndLine(lineIndex, line.text(), line.trailingSpaces(), glyphCount);
587         verticalOffset += line.fTextMetrics.height();
588         ++lineIndex;
589     }
590 }
591 
592 // TODO: Implement more effective search
textToGlyphs(UnicodeText * unicodeText,PositionType positionType,RunIndex runIndex,DirTextRange dirTextRange) const593 GlyphRange WrappedText::textToGlyphs(UnicodeText* unicodeText, PositionType positionType, RunIndex runIndex, DirTextRange dirTextRange) const {
594     SkASSERT(runIndex < fVisualRuns.size());
595     auto& run = fVisualRuns[runIndex];
596     SkASSERT(run.fDirTextRange.contains(dirTextRange));
597     GlyphRange glyphRange(0, run.size());
598     for (GlyphIndex glyph = 0; glyph < run.size(); ++glyph) {
599         auto textIndex = run.fClusters[glyph];
600         if (positionType == PositionType::kGraphemeCluster &&
601             unicodeText->hasProperty(textIndex, SkUnicode::CodeUnitFlags::kGraphemeStart)) {
602             if (dirTextRange.after(textIndex)) {
603                 glyphRange.fStart = glyph;
604             } else if (dirTextRange.before(textIndex)) {
605                 glyphRange.fEnd = glyph;
606             } else {
607                 return glyphRange;
608             }
609         }
610     }
611     SkASSERT(false);
612     return glyphRange;
613 }
614 
prepareToEdit(UnicodeText * unicodeText) const615 std::unique_ptr<SelectableText> WrappedText::prepareToEdit(UnicodeText* unicodeText) const {
616     auto selectableText = std::make_unique<SelectableText>();
617     this->visit(selectableText.get());
618     selectableText->fGlyphUnitProperties.push_back_n(unicodeText->getText16().size() + 1, GlyphUnitFlags::kNoGlyphUnitFlag);
619     for (auto index = 0; index < unicodeText->getText16().size(); ++index) {
620         if (unicodeText->hasProperty(index, SkUnicode::CodeUnitFlags::kHardLineBreakBefore)) {
621             selectableText->fGlyphUnitProperties[index] = GlyphUnitFlags::kGraphemeClusterStart;
622         }
623     }
624     for (const auto& run : fVisualRuns) {
625         for (auto& cluster : run.fClusters) {
626             if (unicodeText->hasProperty(cluster, SkUnicode::CodeUnitFlags::kGraphemeStart)) {
627                 selectableText->fGlyphUnitProperties[cluster] = GlyphUnitFlags::kGraphemeClusterStart;
628             }
629         }
630     }
631     return selectableText;
632 }
633 
onBeginLine(size_t index,TextRange lineText,bool hardBreak,SkRect bounds)634 void SelectableText::onBeginLine(size_t index, TextRange lineText, bool hardBreak, SkRect bounds) {
635     SkASSERT(fBoxLines.size() == index);
636     fBoxLines.emplace_back(index, lineText, hardBreak, bounds);
637 }
638 
onEndLine(size_t index,TextRange lineText,GlyphRange trailingSpaces,size_t glyphCount)639 void SelectableText::onEndLine(size_t index, TextRange lineText, GlyphRange trailingSpaces, size_t glyphCount) {
640     auto& line = fBoxLines.back();
641     line.fTextEnd = trailingSpaces.fStart;
642     line.fTrailingSpacesEnd = trailingSpaces.fEnd;
643     SkASSERT(line.fTextByGlyph.size() == glyphCount);
644     line.fBoxGlyphs.emplace_back(SkRect::MakeXYWH(line.fBounds.fRight, line.fBounds.fTop, 0.0f, line.fBounds.height()));
645     if (line.fTextByGlyph.empty()) {
646         // Let's create an empty fake box to avoid all the checks
647         line.fTextByGlyph.emplace_back(lineText.fEnd);
648     }
649     line.fTextByGlyph.emplace_back(lineText.fEnd);
650 }
651 
onGlyphRun(const SkFont & font,DirTextRange dirTextRange,SkRect bounds,TextIndex trailingSpaces,size_t glyphCount,const uint16_t glyphs[],const SkPoint positions[],const TextIndex clusters[])652 void SelectableText::onGlyphRun(const SkFont& font,
653                                 DirTextRange dirTextRange,
654                                 SkRect bounds,
655                                 TextIndex trailingSpaces,
656                                 size_t glyphCount,
657                                 const uint16_t glyphs[],
658                                 const SkPoint positions[],
659                                 const TextIndex clusters[]) {
660     auto& line = fBoxLines.back();
661     auto start = line.fTextByGlyph.size();
662     line.fBoxGlyphs.push_back_n(glyphCount);
663     line.fTextByGlyph.push_back_n(glyphCount);
664     for (auto i = 0; i < glyphCount; ++i) {
665         auto pos = positions[i];
666         auto pos1 = positions[i + 1];
667         line.fBoxGlyphs[start + i] = SkRect::MakeXYWH(pos.fX, bounds.fTop, pos1.fX - pos.fX, bounds.height());
668         line.fTextByGlyph[start + i] = clusters[i];
669     }
670 }
671 
672 // TODO: Do something (logN) that is not a linear search
findPosition(PositionType positionType,const BoxLine & line,SkScalar x) const673 Position SelectableText::findPosition(PositionType positionType, const BoxLine& line, SkScalar x) const {
674     Position position(positionType);
675     position.fGlyphRange = GlyphRange(0, line.fBoxGlyphs.size() - 1);
676     position.fTextRange = line.fTextRange;
677     position.fBoundaries.fTop = line.fBounds.fTop;
678     position.fBoundaries.fBottom = line.fBounds.fBottom;
679     // We look for the narrowest glyph range adjusted to positionType that contains the point.
680     // So far we made sure that one unit of any positionType does not cross the run edges
681     // Therefore it's going to be represented by a single text range only
682     for (; position.fGlyphRange.fStart < position.fGlyphRange.fEnd; ++position.fGlyphRange.fStart) {
683         auto glyphBox = line.fBoxGlyphs[position.fGlyphRange.fStart];
684         if (glyphBox.fLeft > x) {
685             break;
686         }
687         if (position.fPositionType == PositionType::kGraphemeCluster) {
688             auto textIndex = line.fTextByGlyph[position.fGlyphRange.fStart];
689             if (this->hasProperty(textIndex, GlyphUnitFlags::kGraphemeClusterStart)) {
690                 position.fTextRange.fStart = textIndex;
691             }
692         } else {
693             // TODO: Implement
694             SkASSERT(false);
695         }
696     }
697     for (; position.fGlyphRange.fEnd > position.fGlyphRange.fStart ; --position.fGlyphRange.fEnd) {
698         auto glyphBox = line.fBoxGlyphs[position.fGlyphRange.fStart];
699         if (glyphBox.fRight <= x) {
700             break;
701         }
702         if (position.fPositionType == PositionType::kGraphemeCluster) {
703             auto textIndex = line.fTextByGlyph[position.fGlyphRange.fEnd];
704             if (this->hasProperty(textIndex, GlyphUnitFlags::kGraphemeClusterStart)) {
705                 position.fTextRange.fEnd = textIndex;
706                 break;
707             }
708         } else {
709             // TODO: Implement
710             SkASSERT(false);
711         }
712     }
713     position.fLineIndex = line.fIndex;
714     position.fBoundaries.fLeft = line.fBoxGlyphs[position.fGlyphRange.fStart].fLeft;
715     position.fBoundaries.fRight = line.fBoxGlyphs[position.fGlyphRange.fEnd].fRight;
716     return position;
717 }
718 
adjustedPosition(PositionType positionType,SkPoint xy) const719 Position SelectableText::adjustedPosition(PositionType positionType, SkPoint xy) const {
720     xy.fX = std::min(xy.fX, this->fActualSize.fWidth);
721     xy.fY = std::min(xy.fY, this->fActualSize.fHeight);
722     Position position(positionType);
723     for (auto& line : fBoxLines) {
724         if (line.fBounds.fTop > xy.fY) {
725             // We are past the point vertically
726             break;
727         } else if (line.fBounds.fBottom <= xy.fY) {
728             // We haven't reached the point vertically yet
729             continue;
730         }
731         return this->findPosition(positionType, line, xy.fX);
732     }
733     return this->lastPosition(positionType);
734 }
735 
previousPosition(Position current) const736 Position SelectableText::previousPosition(Position current) const {
737     const BoxLine* currentLine = &fBoxLines[current.fLineIndex];
738     if (this->isFirstOnTheLine(current)) {
739         // Go to the previous line
740         if (current.fLineIndex == 0) {
741             // We reached the end; there is nowhere to move
742             current.fGlyphRange = GlyphRange(0, 0);
743             return current;
744         } else {
745             current.fLineIndex -= 1;
746             currentLine = &fBoxLines[current.fLineIndex];
747             current.fGlyphRange.fStart = currentLine->fBoxGlyphs.size();
748         }
749     }
750     auto position = this->findPosition(current.fPositionType, *currentLine, currentLine->fBoxGlyphs[current.fGlyphRange.fStart].centerX());
751     if (current.fPositionType == PositionType::kGraphemeCluster) {
752         // Either way we found us a grapheme cluster (just make sure of it)
753         SkASSERT(this->hasProperty(current.fTextRange.fStart, GlyphUnitFlags::kGraphemeClusterStart));
754     }
755     return position;
756 }
757 
nextPosition(Position current) const758 Position SelectableText::nextPosition(Position current) const {
759     const BoxLine* currentLine = &fBoxLines[current.fLineIndex];
760     if (this->isLastOnTheLine(current)) {
761         // Go to the next line
762         if (current.fLineIndex == this->fBoxLines.size() - 1) {
763             // We reached the end; there is nowhere to move
764             current.fGlyphRange = GlyphRange(currentLine->fBoxGlyphs.size(), currentLine->fBoxGlyphs.size());
765             return current;
766         } else {
767             current.fLineIndex += 1;
768             currentLine = &fBoxLines[current.fLineIndex];
769             current.fGlyphRange.fEnd = 0;
770         }
771     }
772     auto position = this->findPosition(current.fPositionType, *currentLine, currentLine->fBoxGlyphs[current.fGlyphRange.fStart].centerX());
773     if (current.fPositionType == PositionType::kGraphemeCluster) {
774         // Either way we found us a grapheme cluster (just make sure of it)
775         SkASSERT(this->hasProperty(current.fTextRange.fEnd, GlyphUnitFlags::kGraphemeClusterStart));
776     }
777     return position;
778 }
779 
upPosition(Position current) const780 Position SelectableText::upPosition(Position current) const {
781 
782     if (current.fLineIndex == 0) {
783         // We are on the first line; just move to the first position
784         return this->firstPosition(current.fPositionType);
785     }
786 
787     // Go to the previous line
788     const BoxLine* currentLine = &fBoxLines[current.fLineIndex];
789     auto position = this->findPosition(current.fPositionType, fBoxLines[current.fLineIndex - 1], currentLine->fBoxGlyphs[current.fGlyphRange.fStart].centerX());
790     if (current.fPositionType == PositionType::kGraphemeCluster) {
791         // Either way we found us a grapheme cluster (just make sure of it)
792         SkASSERT(this->hasProperty(current.fTextRange.fEnd, GlyphUnitFlags::kGraphemeClusterStart));
793     }
794     return position;
795 }
796 
downPosition(Position current) const797 Position SelectableText::downPosition(Position current) const {
798 
799     if (current.fLineIndex == this->countLines() - 1) {
800         // We are on the last line; just move to the last position
801         return this->lastPosition(current.fPositionType);
802     }
803 
804     // Go to the next line
805     const BoxLine* currentLine = &fBoxLines[current.fLineIndex];
806     auto position = this->findPosition(current.fPositionType, fBoxLines[current.fLineIndex + 1], currentLine->fBoxGlyphs[current.fGlyphRange.fStart].centerX());
807     if (current.fPositionType == PositionType::kGraphemeCluster) {
808         // Either way we found us a grapheme cluster (just make sure of it)
809         SkASSERT(this->hasProperty(current.fTextRange.fEnd, GlyphUnitFlags::kGraphemeClusterStart));
810     }
811     return position;
812 }
813 
firstPosition(PositionType positionType) const814 Position SelectableText::firstPosition(PositionType positionType) const {
815     auto firstLine = fBoxLines.front();
816     auto firstGlyph = firstLine.fBoxGlyphs.front();
817     Position beginningOfText(positionType);
818     // Set the glyph range after the last glyph
819     beginningOfText.fGlyphRange = GlyphRange { 0, 0};
820     beginningOfText.fLineIndex = 0;
821     beginningOfText.fBoundaries = SkRect::MakeXYWH(firstGlyph.fLeft, firstGlyph.fTop, 0, firstGlyph.height());
822     beginningOfText.fTextRange = this->glyphsToText(beginningOfText);
823     beginningOfText.fLineIndex = 0;
824     return beginningOfText;
825 }
826 
lastPosition(PositionType positionType) const827 Position SelectableText::lastPosition(PositionType positionType) const {
828     auto lastLine = fBoxLines.back();
829     auto lastGlyph = lastLine.fBoxGlyphs.back();
830     Position endOfText(positionType);
831     endOfText.fLineIndex = lastLine.fIndex;
832     endOfText.fGlyphRange = GlyphRange(lastLine.fBoxGlyphs.size() - 1, lastLine.fBoxGlyphs.size() - 1);
833     endOfText.fBoundaries = SkRect::MakeXYWH(lastGlyph.fRight, lastGlyph.fTop, 0, lastGlyph.height());
834     endOfText.fTextRange = this->glyphsToText(endOfText);
835     endOfText.fLineIndex = lastLine.fIndex;
836     return endOfText;
837 }
838 
firstInLinePosition(PositionType positionType,LineIndex lineIndex) const839 Position SelectableText::firstInLinePosition(PositionType positionType, LineIndex lineIndex) const {
840     SkASSERT(lineIndex >= 0 && lineIndex < fBoxLines.size());
841     auto& line = fBoxLines[lineIndex];
842     return this->findPosition(positionType, line, line.fBounds.left());
843 }
844 
lastInLinePosition(PositionType positionType,LineIndex lineIndex) const845 Position SelectableText::lastInLinePosition(PositionType positionType, LineIndex lineIndex) const {
846     auto& line = fBoxLines[lineIndex];
847     return this->findPosition(positionType, line, line.fBounds.right());
848 }
849 
850 
glyphsToText(Position position) const851 TextRange SelectableText::glyphsToText(Position position) const {
852     SkASSERT(position.fPositionType != PositionType::kRandomText);
853     auto line = this->getLine(position.fLineIndex);
854     TextRange textRange = EMPTY_RANGE;
855     for (auto glyph = position.fGlyphRange.fStart; glyph <= position.fGlyphRange.fEnd; ++glyph) {
856         if (textRange.fStart == EMPTY_INDEX) {
857             textRange.fStart = line.fTextByGlyph[glyph];
858         }
859         textRange.fEnd = line.fTextByGlyph[glyph];
860     }
861     return textRange;
862 }
863 } // namespace text
864 } // namespace skia
865