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