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