blob: b82f2d858da0d2834ebfb933e7783099be42dd8a [file] [log] [blame]
Julia Lavrova7856eb82021-06-02 16:01:04 -04001// Copyright 2021 Google LLC.
2
3#include "experimental/sktext/include/Text.h"
4#include <stack>
5
6namespace skia {
7namespace text {
8
9std::unique_ptr<UnicodeText> Text::parse(SkSpan<uint16_t> utf16) {
10
11 auto unicodeText = std::unique_ptr<UnicodeText>(new UnicodeText());
12 unicodeText->fUnicode = std::move(SkUnicode::Make());
13 if (nullptr == unicodeText->fUnicode) {
14 return nullptr;
15 }
16
17 // Create utf8 -> utf16 conversion table
Julia Lavrovaad5944c2021-07-21 19:51:37 -040018 unicodeText->fText16 = std::u16string((char16_t*)utf16.data(), utf16.size());
19 unicodeText->fText8 = unicodeText->fUnicode->convertUtf16ToUtf8(unicodeText->fText16);
Julia Lavrova7856eb82021-06-02 16:01:04 -040020 size_t utf16Index = 0;
21 unicodeText->fUTF16FromUTF8.push_back_n(unicodeText->fText8.size() + 1, utf16Index);
Julia Lavrovaad5944c2021-07-21 19:51:37 -040022 unicodeText->fUTF8FromUTF16.push_back_n(unicodeText->fText16.size() + 1, utf16Index);
Julia Lavrova7856eb82021-06-02 16:01:04 -040023
24 // Fill out all code unit properties
25 unicodeText->fCodeUnitProperties.push_back_n(utf16.size() + 1, CodeUnitFlags::kNoCodeUnitFlag);
26 unicodeText->fUnicode->forEachCodepoint(unicodeText->fText8.c_str(), unicodeText->fText8.size(),
Julia Lavrova398ef442021-09-14 20:37:58 +000027 [&unicodeText, &utf16Index](SkUnichar unichar, int32_t start, int32_t end, size_t count) {
Julia Lavrovaad5944c2021-07-21 19:51:37 -040028 for (auto i = start; i < end; ++i) {
29 unicodeText->fUTF16FromUTF8[i] = utf16Index;
30 }
31 unicodeText->fUTF8FromUTF16[utf16Index] = start;
32 ++utf16Index;
33 });
34 unicodeText->fUTF16FromUTF8[unicodeText->fText8.size()] = unicodeText->fText16.size();
35 unicodeText->fUTF8FromUTF16[unicodeText->fText16.size()] = unicodeText->fText8.size();
Julia Lavrova7856eb82021-06-02 16:01:04 -040036
Julia Lavrovaad5944c2021-07-21 19:51:37 -040037 // Get white spaces
38 // TODO: It's a bug. We need to operate on utf16 indexes everywhere
Julia Lavrova7856eb82021-06-02 16:01:04 -040039 unicodeText->fUnicode->forEachCodepoint(unicodeText->fText8.c_str(), unicodeText->fText8.size(),
Julia Lavrova398ef442021-09-14 20:37:58 +000040 [&unicodeText](SkUnichar unichar, int32_t start, int32_t end, size_t count) {
Julia Lavrova7856eb82021-06-02 16:01:04 -040041 if (unicodeText->fUnicode->isWhitespace(unichar)) {
42 for (auto i = start; i < end; ++i) {
43 unicodeText->fCodeUnitProperties[i] |= CodeUnitFlags::kPartOfWhiteSpace;
44 }
45 }
46 });
47
Julia Lavrova7856eb82021-06-02 16:01:04 -040048 // Get graphemes
49 unicodeText->fUnicode->forEachBreak((char16_t*)utf16.data(), utf16.size(), SkUnicode::BreakType::kGraphemes,
50 [&unicodeText](SkBreakIterator::Position pos, SkBreakIterator::Status){
51 unicodeText->fCodeUnitProperties[pos]|= CodeUnitFlags::kGraphemeStart;
52 });
53
Julia Lavrovaad5944c2021-07-21 19:51:37 -040054 // Get line breaks
55 unicodeText->fUnicode->forEachBreak((char16_t*)utf16.data(), utf16.size(), SkUnicode::BreakType::kLines,
56 [&unicodeText](SkBreakIterator::Position pos, SkBreakIterator::Status status) {
57 if (status == (SkBreakIterator::Status)SkUnicode::LineBreakType::kHardLineBreak) {
58 // Hard line breaks clears off all the other flags
59 // TODO: Treat \n as a formatting mark and do not pass it to SkShaper
60 unicodeText->fCodeUnitProperties[pos - 1] = CodeUnitFlags::kHardLineBreakBefore;
61 } else {
62 unicodeText->fCodeUnitProperties[pos] |= CodeUnitFlags::kSoftLineBreakBefore;
63 }
64 });
65
Julia Lavrova7856eb82021-06-02 16:01:04 -040066 return std::move(unicodeText);
67}
68
Julia Lavrovaad5944c2021-07-21 19:51:37 -040069// Break text into pieces by font blocks and by formatting marks
70// Formatting marks: \n (and possibly some other later)
71std::unique_ptr<ShapedText> UnicodeText::shape(SkSpan<FontBlock> fontBlocks,
Julia Lavrova7856eb82021-06-02 16:01:04 -040072 TextDirection textDirection) {
Julia Lavrovaad5944c2021-07-21 19:51:37 -040073 // TODO: Deal with all the cases
74 // A run can start
75 // 1. From the beginning of the text
76 // 2. From the beginning of the paragraph after newline
77 // 3. From the beginning of the font block
78 // 4. One shaping call can produce multiple runs (for which we should apply 1 - 3)
79 fRunGlyphStart = 0.0f;
80 fParagraphTextStart = 0;
81 std::vector<TextIndex> formattingMarks;
82 formattingMarks.emplace_back(fText8.size());
Julia Lavrova7856eb82021-06-02 16:01:04 -040083
Julia Lavrovaad5944c2021-07-21 19:51:37 -040084 // Copy flags and find all the formatting marks
Julia Lavrova7856eb82021-06-02 16:01:04 -040085 fShapedText = std::unique_ptr<ShapedText>(new ShapedText());
Julia Lavrova7856eb82021-06-02 16:01:04 -040086 fShapedText->fGlyphUnitProperties.push_back_n(this->fCodeUnitProperties.size(), GlyphUnitFlags::kNoGlyphUnitFlag);
87 for (size_t i = 0; i < this->fCodeUnitProperties.size(); ++i) {
88 fShapedText->fGlyphUnitProperties[i] = (GlyphUnitFlags)this->fCodeUnitProperties[i];
Julia Lavrovaad5944c2021-07-21 19:51:37 -040089 if (this->isHardLineBreak(i)) {
90 formattingMarks.emplace_back(i);
Julia Lavrova7856eb82021-06-02 16:01:04 -040091 }
92 }
93
Julia Lavrovaad5944c2021-07-21 19:51:37 -040094 // TODO: Deal with placeholders (have to be treated the same way to avoid all trouble with bidi)
95
96 // Break the text into a list of paragraphs by \n
97 // and shape each paragraph separately
98 // We start each paragraph from a new line
99 fRunGlyphStart = 0.0f;
100
101 FormattingFontIterator fontIter(fText8.size(), fontBlocks, SkSpan<TextIndex>(&formattingMarks[0], 1));
102 SkShaper::TrivialLanguageRunIterator langIter(fText8.c_str(), fText8.size());
103 std::unique_ptr<SkShaper::BiDiRunIterator> bidiIter(
104 SkShaper::MakeSkUnicodeBidiRunIterator(
105 this->fUnicode.get(), fText8.c_str(), fText8.size(), textDirection == TextDirection::kLtr ? 0 : 1));
106 std::unique_ptr<SkShaper::ScriptRunIterator> scriptIter(
107 SkShaper::MakeSkUnicodeHbScriptRunIterator(this->fUnicode.get(), fText8.c_str(), fText8.size()));
108 auto shaper = SkShaper::MakeShapeDontWrapOrReorder();
109 if (shaper == nullptr) {
110 // For instance, loadICU does not work. We have to stop the process
111 return nullptr;
112 }
113
114 shaper->shape(
115 fText8.c_str(), fText8.size(),
116 fontIter, *bidiIter, *scriptIter, langIter,
117 std::numeric_limits<SkScalar>::max(), this);
118
119 // Create a fake run for an empty text (to avoid all the checks)
120 if (fShapedText->fRuns.empty()) {
121 // We still need one empty run to keep the metrics
122 SkShaper::RunHandler::RunInfo emptyInfo {
123 this->createFont(fontBlocks.front()),
124 0,
125 SkVector::Make(0.0f, 0.0f),
126 0,
127 Range(0, 0)
128 };
129 fShapedText->fRuns.emplace_back(emptyInfo, 0, 0.0f);
130 fShapedText->fRuns.front().commit();
131 }
132
Julia Lavrova7856eb82021-06-02 16:01:04 -0400133 return std::move(fShapedText);
134}
135
136void UnicodeText::commitRunBuffer(const RunInfo&) {
137 fCurrentRun->commit();
138
Julia Lavrovaf8e69ca2021-08-17 13:21:23 -0400139 // Convert utf8 range into utf16 range
140 fCurrentRun->convertUtf16Range([this](unsigned long index) {
141 return this->fUTF16FromUTF8[index + fParagraphTextStart];
142 });
143
Julia Lavrovaad5944c2021-07-21 19:51:37 -0400144 // Convert all utf8 indexes into utf16 indexes (and also shift them to be on the entire text scale, too)
145 fCurrentRun->convertClusterIndexes([this](TextIndex clusterIndex) {
146 auto converted = this->fUTF16FromUTF8[clusterIndex + fParagraphTextStart];
147 fShapedText->fGlyphUnitProperties[converted] |= GlyphUnitFlags::kGlyphClusterStart;
148 if (this->hasProperty(converted, CodeUnitFlags::kGraphemeStart)) {
149 fShapedText->fGlyphUnitProperties[converted] |= GlyphUnitFlags::kGraphemeClusterStart;
150 }
151 return converted;
152 });
Julia Lavrova7856eb82021-06-02 16:01:04 -0400153
Julia Lavrova7856eb82021-06-02 16:01:04 -0400154 fShapedText->fRuns.emplace_back(std::move(*fCurrentRun));
Julia Lavrovaad5944c2021-07-21 19:51:37 -0400155
156 fRunGlyphStart += fCurrentRun->width();
Julia Lavrova7856eb82021-06-02 16:01:04 -0400157}
158
Julia Lavrovaad5944c2021-07-21 19:51:37 -0400159SkFont UnicodeText::createFont(const FontBlock& fontBlock) {
Julia Lavrova7856eb82021-06-02 16:01:04 -0400160
Julia Lavrovaad5944c2021-07-21 19:51:37 -0400161 if (fontBlock.chain->count() == 0) {
Julia Lavrova7856eb82021-06-02 16:01:04 -0400162 return SkFont();
163 }
Julia Lavrovaad5944c2021-07-21 19:51:37 -0400164 sk_sp<SkTypeface> typeface = fontBlock.chain->operator[](0);
Julia Lavrova7856eb82021-06-02 16:01:04 -0400165
Julia Lavrovaad5944c2021-07-21 19:51:37 -0400166 SkFont font(std::move(typeface), fontBlock.chain->size());
Julia Lavrova7856eb82021-06-02 16:01:04 -0400167 font.setEdging(SkFont::Edging::kAntiAlias);
168 font.setHinting(SkFontHinting::kSlight);
169 font.setSubpixel(true);
170
171 return font;
172}
173
174std::unique_ptr<WrappedText> ShapedText::wrap(SkScalar width, SkScalar heightCurrentlyIgnored, SkUnicode* unicode) {
175
176 auto wrappedText = std::unique_ptr<WrappedText>(new WrappedText());
177 wrappedText->fRuns = this->fRuns;
Julia Lavrovaecc8e3b2021-06-10 10:26:36 -0400178 wrappedText->fGlyphUnitProperties = this->fGlyphUnitProperties; // copy
Julia Lavrova7856eb82021-06-02 16:01:04 -0400179
180 // line : spaces : clusters
181 Stretch line;
182 Stretch spaces;
183 Stretch clusters;
Julia Lavrovaad5944c2021-07-21 19:51:37 -0400184 Stretch cluster;
Julia Lavrova7856eb82021-06-02 16:01:04 -0400185
186 for (size_t runIndex = 0; runIndex < this->fRuns.size(); ++runIndex ) {
187
188 auto& run = this->fRuns[runIndex];
189 TextMetrics runMetrics(run.fFont);
Julia Lavrova7856eb82021-06-02 16:01:04 -0400190 if (!run.leftToRight()) {
191 cluster.setTextRange({ run.fUtf16Range.fStart, run.fUtf16Range.fEnd});
192 }
193
194 for (size_t glyphIndex = 0; glyphIndex < run.fPositions.size(); ++glyphIndex) {
195 auto textIndex = run.fClusters[glyphIndex];
196
197 if (cluster.isEmpty()) {
198 cluster = Stretch(GlyphPos(runIndex, glyphIndex), textIndex, runMetrics);
199 continue;
200 }
201
202 // The entire cluster belongs to a single run
203 SkASSERT(cluster.glyphStart().runIndex() == runIndex);
204
Julia Lavrova18884662021-06-10 16:33:49 -0400205 auto clusterWidth = run.calculateWidth(cluster.glyphStartIndex(), glyphIndex);
Julia Lavrova7856eb82021-06-02 16:01:04 -0400206 cluster.finish(glyphIndex, textIndex, clusterWidth);
207
208 auto isHardLineBreak = this->isHardLineBreak(cluster.textStart());
209 auto isSoftLineBreak = this->isSoftLineBreak(cluster.textStart());
210 auto isWhitespaces = this->isWhitespaces(cluster.textRange());
211 auto isEndOfText = run.leftToRight() ? textIndex == run.fUtf16Range.fEnd : textIndex == run.fUtf16Range.fStart;
212
Julia Lavrova18884662021-06-10 16:33:49 -0400213 if (isWhitespaces || isHardLineBreak) {
Julia Lavrova7856eb82021-06-02 16:01:04 -0400214 // This is the end of the word
215 if (!clusters.isEmpty()) {
216 line.moveTo(spaces);
217 line.moveTo(clusters);
218 spaces = clusters;
219 }
220 }
221
222 // line + spaces + clusters + cluster
223 if (isWhitespaces) {
224 // Whitespaces do not extend the line width
225 spaces.moveTo(cluster);
226 clusters = cluster;
227 continue;
228 } else if (isHardLineBreak) {
229 // Hard line break ends the line but does not extend the width
230 // Same goes for the end of the text
Julia Lavrovaad5944c2021-07-21 19:51:37 -0400231 spaces.moveTo(cluster);
232 wrappedText->addLine(line, spaces, unicode, true);
233 line = spaces;
234 clusters = spaces;
235 continue;
Julia Lavrova7856eb82021-06-02 16:01:04 -0400236 } else if (!SkScalarIsFinite(width)) {
237 clusters.moveTo(cluster);
238 continue;
239 }
240
241 // Now let's find out if we can add the cluster to the line
242 if ((line.width() + spaces.width() + clusters.width() + cluster.width()) <= width) {
243 clusters.moveTo(cluster);
244 } else {
245 if (line.isEmpty()) {
246 if (spaces.isEmpty() && clusters.isEmpty()) {
247 // There is only this cluster and it's too long;
248 // we are drawing it anyway
249 line.moveTo(cluster);
250 } else {
251 // We break the only one word on the line by this cluster
252 line.moveTo(clusters);
253 }
254 } else {
255 // We move clusters + cluster on the next line
256 // TODO: Parametrise possible ways of breaking too long word
257 // (start it from a new line or squeeze the part of it on this line)
258 }
Julia Lavrovaad5944c2021-07-21 19:51:37 -0400259 wrappedText->addLine(line, spaces, unicode, false);
Julia Lavrova18884662021-06-10 16:33:49 -0400260 line = spaces;
Julia Lavrova7856eb82021-06-02 16:01:04 -0400261 clusters.moveTo(cluster);
262 }
263
264 cluster = Stretch(GlyphPos(runIndex, glyphIndex), textIndex, runMetrics);
265 }
266 }
267
268 if (!clusters.isEmpty()) {
269 line.moveTo(spaces);
270 line.moveTo(clusters);
271 spaces = clusters;
Julia Lavrovaad5944c2021-07-21 19:51:37 -0400272 } else if (wrappedText->fLines.empty()) {
273 // Empty text; we still need a line to avoid checking for empty lines every time
274 line.moveTo(cluster);
275 spaces.moveTo(cluster);
Julia Lavrova7856eb82021-06-02 16:01:04 -0400276 }
Julia Lavrova7856eb82021-06-02 16:01:04 -0400277
Julia Lavrovaad5944c2021-07-21 19:51:37 -0400278 wrappedText->addLine(line, spaces, unicode, false);
279
280 wrappedText->fActualSize.fWidth = width;
Julia Lavrova7856eb82021-06-02 16:01:04 -0400281 return std::move(wrappedText);
282}
283
Julia Lavrovaad5944c2021-07-21 19:51:37 -0400284void WrappedText::addLine(Stretch& stretch, Stretch& spaces, SkUnicode* unicode, bool hardLineBreak) {
Julia Lavrova7856eb82021-06-02 16:01:04 -0400285
286 // This is just chosen to catch the common/fast cases. Feel free to tweak.
287 constexpr int kPreallocCount = 4;
288 auto start = stretch.glyphStart().runIndex();
289 auto end = spaces.glyphEnd().runIndex();
290 auto numRuns = end - start + 1;
291 SkAutoSTArray<kPreallocCount, SkUnicode::BidiLevel> runLevels(numRuns);
292 size_t runLevelsIndex = 0;
293 for (auto runIndex = start; runIndex <= end; ++runIndex) {
294 auto& run = fRuns[runIndex];
295 runLevels[runLevelsIndex++] = run.bidiLevel();
296 }
297 SkASSERT(runLevelsIndex == numRuns);
298 SkAutoSTArray<kPreallocCount, int32_t> logicalOrder(numRuns);
299 SkSTArray<1, size_t, true> visualOrder;
300 unicode->reorderVisual(runLevels.data(), numRuns, logicalOrder.data());
301 auto firstRunIndex = start;
302 for (auto index : logicalOrder) {
303 visualOrder.push_back(firstRunIndex + index);
304 }
Julia Lavrovaad5944c2021-07-21 19:51:37 -0400305 this->fLines.emplace_back(stretch, spaces, std::move(visualOrder), fActualSize.fHeight, hardLineBreak);
306 fActualSize.fHeight += stretch.textMetrics().height();
Julia Lavrova7856eb82021-06-02 16:01:04 -0400307
308 stretch.clean();
309 spaces.clean();
310}
311
312sk_sp<FormattedText> WrappedText::format(TextAlign textAlign, TextDirection textDirection) {
313 auto formattedText = sk_sp<FormattedText>(new FormattedText());
314
315 formattedText->fRuns = this->fRuns;
316 formattedText->fLines = this->fLines;
317 formattedText->fGlyphUnitProperties = this->fGlyphUnitProperties;
Julia Lavrovaad5944c2021-07-21 19:51:37 -0400318 formattedText->fActualSize = this->fActualSize;
319 if (textAlign == TextAlign::kLeft) {
320 // Good by default
321 } else if (textAlign == TextAlign::kCenter) {
322 for (auto& line : formattedText->fLines) {
323 line.fHorizontalOffset = (this->fActualSize.width() - line.fTextWidth) / 2.0f;
324 }
325 formattedText->fActualSize.fWidth = this->fActualSize.fWidth;
326 } else {
327 // TODO: Implement all formatting features
328 }
Julia Lavrova7856eb82021-06-02 16:01:04 -0400329
330 return std::move(formattedText);
331}
332
333void FormattedText::visit(Visitor* visitor) const {
334
335 SkPoint offset = SkPoint::Make(0 , 0);
336 for (auto& line : this->fLines) {
337 offset.fX = 0;
Julia Lavrovac87e9512021-08-11 16:24:24 -0400338 visitor->onBeginLine(line.text());
Julia Lavrovaad5944c2021-07-21 19:51:37 -0400339 for (auto index = 0; index < line.runsNumber(); ++index) {
340 auto runIndex = line.visualRun(index);
Julia Lavrova7856eb82021-06-02 16:01:04 -0400341 auto& run = this->fRuns[runIndex];
Julia Lavrovac87e9512021-08-11 16:24:24 -0400342
Julia Lavrovaf8e69ca2021-08-17 13:21:23 -0400343 GlyphRange glyphRange(line.glyphRange(runIndex, run.size(), false /* excluding trailing spaces */));
Julia Lavrova7856eb82021-06-02 16:01:04 -0400344 // Update positions
Julia Lavrovac87e9512021-08-11 16:24:24 -0400345 SkAutoSTMalloc<256, SkPoint> positions(glyphRange.width() + 1);
346 SkPoint shift = SkPoint::Make(-run.fPositions[glyphRange.fStart].fX, line.baseline());
347 for (size_t i = glyphRange.fStart; i <= glyphRange.fEnd; ++i) {
348 positions[i - glyphRange.fStart] = run.fPositions[i] + shift + offset;
Julia Lavrova7856eb82021-06-02 16:01:04 -0400349 }
Julia Lavrovac87e9512021-08-11 16:24:24 -0400350 SkRect boundingRect = SkRect::MakeXYWH(shift.fX + offset.fX, offset.fY, run.fPositions[glyphRange.fEnd].fX , run.fTextMetrics.height());
351 visitor->onGlyphRun(run.fFont, run.getTextRange(glyphRange), boundingRect, glyphRange.width(), run.fGlyphs.data() + glyphRange.fStart, positions.data(), run.fOffsets.data() + glyphRange.fStart);
Julia Lavrovaf8e69ca2021-08-17 13:21:23 -0400352 offset.fX += run.calculateWidth(glyphRange);
Julia Lavrova7856eb82021-06-02 16:01:04 -0400353 }
Julia Lavrovac87e9512021-08-11 16:24:24 -0400354 visitor->onEndLine(line.text());
Julia Lavrovaad5944c2021-07-21 19:51:37 -0400355 offset.fY += line.height();
Julia Lavrova7856eb82021-06-02 16:01:04 -0400356 }
357}
358
Julia Lavrovaf8e69ca2021-08-17 13:21:23 -0400359void FormattedText::visit(Visitor* visitor, SkSpan<TextIndex> textChunks) const {
Julia Lavrova7856eb82021-06-02 16:01:04 -0400360 // Decor blocks have to be sorted by text cannot intersect but can skip some parts of the text
361 // (in which case we use default text style from paragraph style)
362 // The edges of the decor blocks don't have to match glyph, grapheme or even unicode code point edges
363 // It's out responsibility to adjust them to some reasonable values
364 // [a:b) -> [c:d) where
365 // c is closest GG cluster edge to a from the left and d is closest GG cluster edge to b from the left
Julia Lavrova7856eb82021-06-02 16:01:04 -0400366 SkPoint offset = SkPoint::Make(0 , 0);
367 for (auto& line : this->fLines) {
368 offset.fX = 0;
Julia Lavrovac87e9512021-08-11 16:24:24 -0400369 visitor->onBeginLine(line.text());
Julia Lavrovaad5944c2021-07-21 19:51:37 -0400370 for (auto index = 0; index < line.runsNumber(); ++index) {
371 auto runIndex = line.visualRun(index);
Julia Lavrova7856eb82021-06-02 16:01:04 -0400372 auto& run = this->fRuns[runIndex];
Julia Lavrovaad5944c2021-07-21 19:51:37 -0400373 if (run.size() == 0) {
374 continue;
375 }
Julia Lavrovac87e9512021-08-11 16:24:24 -0400376
Julia Lavrovaf8e69ca2021-08-17 13:21:23 -0400377 GlyphRange runGlyphRange(line.glyphRange(runIndex, run.size(), false /* excluding trailing spaces */));
Julia Lavrovac87e9512021-08-11 16:24:24 -0400378 SkPoint shift = SkPoint::Make(-run.fPositions[runGlyphRange.fStart].fX, line.baseline());
Julia Lavrova7856eb82021-06-02 16:01:04 -0400379 // The run edges are good (aligned to GGC)
380 // "ABCdef" -> "defCBA"
381 // "AB": red
382 // "Cd": green
383 // "ef": blue
384 // green[d] blue[ef] green [C] red [BA]
Julia Lavrova7856eb82021-06-02 16:01:04 -0400385
Julia Lavrovaf8e69ca2021-08-17 13:21:23 -0400386 // chunks[text index] -> chunks [glyph index] -> walk through in glyph index order (the visual order)
387 run.forEachTextChunkInGlyphRange(textChunks, runGlyphRange, [&](TextRange textRange, GlyphRange glyphRange){
Julia Lavrovaad5944c2021-07-21 19:51:37 -0400388 // Update positions & calculate the bounding rect
Julia Lavrova7856eb82021-06-02 16:01:04 -0400389 SkAutoSTMalloc<256, SkPoint> positions(glyphRange.width() + 1);
Julia Lavrovaf8e69ca2021-08-17 13:21:23 -0400390 for (GlyphIndex i = glyphRange.fStart; i <= glyphRange.fEnd; ++i) {
Julia Lavrovac87e9512021-08-11 16:24:24 -0400391 positions[i - glyphRange.fStart] = run.fPositions[i] + shift + offset + SkPoint::Make(line.horizontalOffset(), 0.0f);
Julia Lavrova7856eb82021-06-02 16:01:04 -0400392 }
Julia Lavrovac87e9512021-08-11 16:24:24 -0400393 SkRect boundingRect = SkRect::MakeXYWH(positions[0].fX + line.horizontalOffset(), offset.fY, positions[glyphRange.width()].fX - positions[0].fX, run.fTextMetrics.height());
394 visitor->onGlyphRun(run.fFont, run.getTextRange(glyphRange), boundingRect, glyphRange.width(), run.fGlyphs.data() + glyphRange.fStart, positions.data(), run.fOffsets.data() + glyphRange.fStart);
Julia Lavrova7856eb82021-06-02 16:01:04 -0400395
Julia Lavrovaf8e69ca2021-08-17 13:21:23 -0400396 });
Julia Lavrova7856eb82021-06-02 16:01:04 -0400397
Julia Lavrovaf8e69ca2021-08-17 13:21:23 -0400398 offset.fX += run.calculateWidth(runGlyphRange);
Julia Lavrova7856eb82021-06-02 16:01:04 -0400399 }
Julia Lavrovac87e9512021-08-11 16:24:24 -0400400 visitor->onEndLine(line.text());
Julia Lavrovaad5944c2021-07-21 19:51:37 -0400401 offset.fY += line.height();
Julia Lavrova7856eb82021-06-02 16:01:04 -0400402 }
403}
404
Julia Lavrovaad5944c2021-07-21 19:51:37 -0400405// Find the element that includes textIndex
406Position FormattedText::adjustedPosition(PositionType positionType, TextIndex textIndex) const {
407 Position position(positionType);
408 SkScalar shift = 0;
Julia Lavrova7856eb82021-06-02 16:01:04 -0400409 for (auto& line : fLines) {
Julia Lavrovaad5944c2021-07-21 19:51:37 -0400410 position.fBoundaries.fTop = position.fBoundaries.fBottom;
411 position.fBoundaries.fBottom = line.verticalOffset() + line.height();
412 if (!line.text().contains(textIndex) && !line.whitespaces().contains(textIndex) ) {
Julia Lavrova7856eb82021-06-02 16:01:04 -0400413 continue;
414 }
Julia Lavrovaecc8e3b2021-06-10 10:26:36 -0400415
Julia Lavrovaad5944c2021-07-21 19:51:37 -0400416 shift = 0;
417 for (auto index = 0; index < line.runsNumber(); ++index) {
418 auto runIndex = line.visualRun(index);
Julia Lavrova7856eb82021-06-02 16:01:04 -0400419 auto& run = fRuns[runIndex];
Julia Lavrovaad5944c2021-07-21 19:51:37 -0400420
Julia Lavrovaf8e69ca2021-08-17 13:21:23 -0400421 GlyphRange runGlyphRange(line.glyphRange(runIndex, run.size(), true /* including trailing spaces */));
Julia Lavrovac87e9512021-08-11 16:24:24 -0400422 auto runWidth = run.calculateWidth(runGlyphRange);
Julia Lavrovaad5944c2021-07-21 19:51:37 -0400423
424 if (!run.fUtf16Range.contains(textIndex)) {
425 shift += runWidth;
426 continue;
Julia Lavrova7856eb82021-06-02 16:01:04 -0400427 }
Julia Lavrovaad5944c2021-07-21 19:51:37 -0400428
Julia Lavrovaf8e69ca2021-08-17 13:21:23 -0400429 // Find the position left
430 GlyphIndex found = run.findGlyphIndexLeftOf(runGlyphRange, textIndex);
Julia Lavrovaad5944c2021-07-21 19:51:37 -0400431
432 position.fLineIndex = lineIndex(&line);
433 position.fRun = &run;
Julia Lavrovac87e9512021-08-11 16:24:24 -0400434 position.fGlyphRange = GlyphRange(found, found == runGlyphRange.fEnd ? found : found + 1);
435 position.fTextRange = position.fRun->getTextRange(position.fGlyphRange);
436 this->adjustTextRange(&position);
437 position.fBoundaries.fLeft += shift + run.calculateWidth(runGlyphRange.fStart, position.fGlyphRange.fStart);
438 position.fBoundaries.fRight += shift + run.calculateWidth(runGlyphRange.fStart, position.fGlyphRange.fEnd);
Julia Lavrovaad5944c2021-07-21 19:51:37 -0400439 return position;
Julia Lavrova7856eb82021-06-02 16:01:04 -0400440 }
Julia Lavrovaad5944c2021-07-21 19:51:37 -0400441 // The cursor is not on the text anymore; position it after the last element
442 break;
Julia Lavrova7856eb82021-06-02 16:01:04 -0400443 }
Julia Lavrovaecc8e3b2021-06-10 10:26:36 -0400444
Julia Lavrovaad5944c2021-07-21 19:51:37 -0400445 position.fLineIndex = fLines.size() - 1;
446 position.fRun = this->visuallyLastRun(position.fLineIndex);
447 position.fGlyphRange = GlyphRange(position.fRun->size(), position.fRun->size());
Julia Lavrovac87e9512021-08-11 16:24:24 -0400448 position.fTextRange = position.fRun->getTextRange(position.fGlyphRange);
449 this->adjustTextRange(&position);
Julia Lavrovaad5944c2021-07-21 19:51:37 -0400450 position.fBoundaries.fLeft = fLines.back().withWithTrailingSpaces();
451 position.fBoundaries.fRight = fLines.back().withWithTrailingSpaces();
452 return position;
Julia Lavrova7856eb82021-06-02 16:01:04 -0400453}
454
Julia Lavrovaad5944c2021-07-21 19:51:37 -0400455Position FormattedText::adjustedPosition(PositionType positionType, SkPoint xy) const {
456
Julia Lavrovac87e9512021-08-11 16:24:24 -0400457 xy.fX = std::min(xy.fX, this->fActualSize.fWidth);
458 xy.fY = std::min(xy.fY, this->fActualSize.fHeight);
Julia Lavrova7856eb82021-06-02 16:01:04 -0400459
Julia Lavrovaad5944c2021-07-21 19:51:37 -0400460 Position position(positionType);
Julia Lavrovac87e9512021-08-11 16:24:24 -0400461 position.fRun = this->visuallyLastRun(this->fLines.size() - 1);
Julia Lavrova7856eb82021-06-02 16:01:04 -0400462 for (auto& line : fLines) {
Julia Lavrovaad5944c2021-07-21 19:51:37 -0400463 position.fBoundaries.fTop = position.fBoundaries.fBottom;
464 position.fBoundaries.fBottom = line.verticalOffset() + line.height();
Julia Lavrova8343e002021-08-11 14:45:35 -0400465 position.fLineIndex = this->lineIndex(&line);
Julia Lavrovaad5944c2021-07-21 19:51:37 -0400466 if (position.fBoundaries.fTop > xy.fY) {
Julia Lavrovac87e9512021-08-11 16:24:24 -0400467 // We are past the point vertically
Julia Lavrova7856eb82021-06-02 16:01:04 -0400468 break;
Julia Lavrovaad5944c2021-07-21 19:51:37 -0400469 } else if (position.fBoundaries.fBottom <= xy.fY) {
Julia Lavrovac87e9512021-08-11 16:24:24 -0400470 // We haven't reached the point vertically yet
Julia Lavrova7856eb82021-06-02 16:01:04 -0400471 continue;
472 }
473
Julia Lavrovac87e9512021-08-11 16:24:24 -0400474 // We found the line that contains the point;
475 // let's walk through all its runs and find the element
476 SkScalar runOffsetInLine = 0;
Julia Lavrovaad5944c2021-07-21 19:51:37 -0400477 for (auto index = 0; index < line.runsNumber(); ++index) {
478 auto runIndex = line.visualRun(index);
Julia Lavrova7856eb82021-06-02 16:01:04 -0400479 auto& run = fRuns[runIndex];
Julia Lavrovaf8e69ca2021-08-17 13:21:23 -0400480 GlyphRange runGlyphRangeInLine = line.glyphRange(runIndex, run.size(), true /* including trailing space */);
Julia Lavrovac87e9512021-08-11 16:24:24 -0400481 SkScalar runWidthInLine = run.calculateWidth(runGlyphRangeInLine);
482 position.fRun = &run;
483 if (runOffsetInLine > xy.fX) {
484 // We are past the point horizontally
Julia Lavrova7856eb82021-06-02 16:01:04 -0400485 break;
Julia Lavrovac87e9512021-08-11 16:24:24 -0400486 } else if (runOffsetInLine + runWidthInLine < xy.fX) {
487 // We haven't reached the point horizontally yet
488 runOffsetInLine += runWidthInLine;
Julia Lavrova7856eb82021-06-02 16:01:04 -0400489 continue;
490 }
Julia Lavrovac87e9512021-08-11 16:24:24 -0400491
492 // We found the run that contains the point
493 // let's find the glyph
494 GlyphIndex found = runGlyphRangeInLine.fStart;
495 for (auto i = runGlyphRangeInLine.fStart; i <= runGlyphRangeInLine.fEnd; ++i) {
496 if (runOffsetInLine + run.calculateWidth(runGlyphRangeInLine.fStart, i) > xy.fX) {
Julia Lavrova7856eb82021-06-02 16:01:04 -0400497 break;
498 }
Julia Lavrovaecc8e3b2021-06-10 10:26:36 -0400499 found = i;
Julia Lavrova7856eb82021-06-02 16:01:04 -0400500 }
Julia Lavrovaad5944c2021-07-21 19:51:37 -0400501
Julia Lavrovac87e9512021-08-11 16:24:24 -0400502 position.fGlyphRange = GlyphRange(found, found == runGlyphRangeInLine.fEnd ? found : found + 1);
503 position.fTextRange = position.fRun->getTextRange(position.fGlyphRange);
504 this->adjustTextRange(&position);
505 position.fBoundaries.fLeft = runOffsetInLine + run.calculateWidth(runGlyphRangeInLine.fStart, position.fGlyphRange.fStart);
506 position.fBoundaries.fRight = runOffsetInLine + run.calculateWidth(runGlyphRangeInLine.fStart, position.fGlyphRange.fEnd);
Julia Lavrovaad5944c2021-07-21 19:51:37 -0400507 return position;
Julia Lavrova7856eb82021-06-02 16:01:04 -0400508 }
Julia Lavrovac87e9512021-08-11 16:24:24 -0400509 // The cursor is not on the text anymore; position it after the last element of the last visual run of the current line
Julia Lavrovaad5944c2021-07-21 19:51:37 -0400510 break;
Julia Lavrova7856eb82021-06-02 16:01:04 -0400511 }
Julia Lavrovaad5944c2021-07-21 19:51:37 -0400512
Julia Lavrovaad5944c2021-07-21 19:51:37 -0400513 position.fRun = this->visuallyLastRun(position.fLineIndex);
Julia Lavrovac87e9512021-08-11 16:24:24 -0400514 auto line = this->line(position.fLineIndex);
Julia Lavrova8343e002021-08-11 14:45:35 -0400515 position.fGlyphRange.fStart =
Julia Lavrovaf8e69ca2021-08-17 13:21:23 -0400516 position.fGlyphRange.fEnd = line->glyphRange(this->runIndex(position.fRun), position.fRun->size(), true /* including trailing spaces */).fEnd;
Julia Lavrovac87e9512021-08-11 16:24:24 -0400517 position.fTextRange = position.fRun->getTextRange(position.fGlyphRange);
518 this->adjustTextRange(&position);
Julia Lavrova8343e002021-08-11 14:45:35 -0400519 position.fBoundaries.fLeft =
520 position.fBoundaries.fRight = line->withWithTrailingSpaces();
Julia Lavrovaad5944c2021-07-21 19:51:37 -0400521 return position;
Julia Lavrova7856eb82021-06-02 16:01:04 -0400522}
523
Julia Lavrovac87e9512021-08-11 16:24:24 -0400524// Adjust the text positions to the position type
525// (assuming for now that a grapheme cannot cross run edges; it's actually not true)
Julia Lavrovaad5944c2021-07-21 19:51:37 -0400526void FormattedText::adjustTextRange(Position* position) const {
Julia Lavrovac87e9512021-08-11 16:24:24 -0400527 // The textRange is aligned on a glyph cluster
528 if (position->fPositionType == PositionType::kGraphemeCluster) {
529 // Move left to the beginning of the run
530 while (position->fTextRange.fStart > position->fRun->fUtf8Range.begin() &&
531 !this->hasProperty(position->fTextRange.fStart, GlyphUnitFlags::kGraphemeStart)) {
532 --position->fTextRange.fStart;
533 }
534 // Update glyphRange, too
535 while (position->fRun->fClusters[position->fGlyphRange.fStart] > position->fTextRange.fStart) {
536 --position->fGlyphRange.fStart;
537 }
538
539 // Move right to the end of the run updating glyphRange, too
540 while (position->fTextRange.fEnd < position->fRun->fUtf8Range.end() &&
541 !this->hasProperty(position->fTextRange.fEnd, GlyphUnitFlags::kGraphemeStart)) {
542 ++position->fTextRange.fEnd;
543 }
544 // Update glyphRange, too
545 while (position->fRun->fClusters[position->fGlyphRange.fEnd] < position->fTextRange.fEnd) {
546 ++position->fGlyphRange.fEnd;
547 }
548 } else {
549 // TODO: Implement all the other position types
550 SkASSERT(false);
551 }
Julia Lavrovaad5944c2021-07-21 19:51:37 -0400552 position->fTextRange = TextRange(position->fRun->fClusters[position->fGlyphRange.fStart], position->fRun->fClusters[position->fGlyphRange.fEnd]);
553}
554
555// The range is guaranteed on the same line
556bool FormattedText::recalculateBoundaries(Position& position) const {
557
558 auto line = this->line(position.fLineIndex);
559 auto runIndex = this->runIndex(position.fRun);
560
561 GlyphIndex start = runIndex == line->glyphStart().runIndex() ? line->glyphStart().glyphIndex() : 0;
562 GlyphIndex end = runIndex == line->glyphTrailingEnd().runIndex() ? line->glyphTrailingEnd().glyphIndex() : position.fRun->fGlyphs.size();
563
564 SkASSERT (start <= position.fGlyphRange.fStart && end >= position.fGlyphRange.fEnd);
565 auto left = position.fRun->calculateWidth(start, position.fGlyphRange.fStart);
566 auto width = position.fRun->calculateWidth(position.fGlyphRange);
567 position.fBoundaries = SkRect::MakeXYWH(left, line->verticalOffset(), width, line->getMetrics().height());
568 return true;
569}
570
571const TextRun* FormattedText::visuallyPreviousRun(size_t lineIndex, const TextRun* run) const {
572 auto line = this->line(lineIndex);
573 auto runIndex = this->runIndex(run);
574 for (auto i = 0; i < line->runsNumber(); ++i) {
575 if (line->visualRun(i) == runIndex) {
576 if (i == 0) {
577 // Go to the previous line
578 if (lineIndex == 0) {
579 // This is the first line
580 return nullptr;
581 }
582 // Previous line, last visual run
583 line = this->line(--lineIndex);
584 i = line->runsNumber() - 1;
585 }
586 return &fRuns[line->visualRun(i)];
587 }
588 }
589 SkASSERT(false);
590 return nullptr;
591}
592
593const TextRun* FormattedText::visuallyNextRun(size_t lineIndex, const TextRun* run) const {
594 auto line = this->line(lineIndex);
595 auto runIndex = this->runIndex(run);
596 for (auto i = 0; i < line->runsNumber(); ++i) {
597 if (line->visualRun(i) == runIndex) {
598 if (i == line->runsNumber() - 1) {
599 // Go to the next line
600 if (lineIndex == fLines.size() - 1) {
601 // This is the last line
602 return nullptr;
603 }
604 // Next line, first visual run
605 line = this->line(++lineIndex);
606 i = 0;
607 }
608 return &fRuns[line->visualRun(i)];
609 }
610 }
611 SkASSERT(false);
612 return nullptr;
613}
614
615const TextRun* FormattedText::visuallyFirstRun(size_t lineIndex) const {
616 auto line = this->line(lineIndex);
617 return &fRuns[line->visualRun(0)];
618}
619
620const TextRun* FormattedText::visuallyLastRun(size_t lineIndex) const {
621 auto line = this->line(lineIndex);
622 return &fRuns[line->visualRun(line->runsNumber() - 1)];
623}
624
625Position FormattedText::previousElement(Position element) const {
626
627 if (element.fGlyphRange.fStart == 0) {
628 if (this->isVisuallyFirst(element.fLineIndex, element.fRun)) {
629 element.fGlyphRange = GlyphRange { 0, 0};
630 return element;
631 }
632 // We need to go to the visually previous run
633 // (skipping all the empty runs if there are any)
634 element.fRun = this->visuallyPreviousRun(element.fLineIndex, element.fRun);
635 // Set the glyph range after the last glyph
636 element.fGlyphRange = GlyphRange { element.fRun->fGlyphs.size(), element.fRun->fGlyphs.size()};
637 if (element.fRun == nullptr) {
638 return element;
639 }
640 }
641
642 auto& clusters = element.fRun->fClusters;
643 element.fGlyphRange = GlyphRange(element.fGlyphRange.fStart, element.fGlyphRange.fStart);
644 element.fTextRange = TextRange(clusters[element.fGlyphRange.fStart],
645 clusters[element.fGlyphRange.fStart]);
646 while (element.fGlyphRange.fStart > 0) {
647 // Shift left visually
648 element.fTextRange.fStart = clusters[--element.fGlyphRange.fStart];
649 if (element.fPositionType == PositionType::kGraphemeCluster) {
650 if (this->hasProperty(element.fTextRange.fStart, GlyphUnitFlags::kGraphemeStart)) {
651 break;
652 }
653 }
654 }
655
656 // Update the line
657 auto line = this->line(element.fLineIndex);
658 if (line->glyphStart().runIndex() == this->runIndex(element.fRun) &&
659 line->glyphStart().glyphIndex() > element.fGlyphRange.fStart) {
660 --element.fLineIndex;
661 }
662
663 // Either way we found us a grapheme cluster (just make sure of it)
664 SkASSERT(this->hasProperty(element.fTextRange.fStart, GlyphUnitFlags::kGraphemeStart));
665 return element;
666}
667
668Position FormattedText::nextElement(Position element) const {
669
670 if (element.fGlyphRange.fEnd == element.fRun->size()) {
671 // We need to go to the visually next run
672 // (skipping all the empty runs if there are any)
673 if (this->isVisuallyLast(element.fLineIndex, element.fRun)) {
674 element.fGlyphRange = GlyphRange { element.fRun->size(), element.fRun->size() };
675 return element;
676 }
677 element.fRun = this->visuallyNextRun(element.fLineIndex, element.fRun);
678 // Set the glyph range after the last glyph
679 element.fGlyphRange = GlyphRange { 0, 0};
680 if (element.fRun == nullptr) {
681 return element;
682 }
683 }
684
685 auto& clusters = element.fRun->fClusters;
686 element.fGlyphRange = GlyphRange(element.fGlyphRange.fEnd, element.fGlyphRange.fEnd);
687 element.fTextRange = TextRange(clusters[element.fGlyphRange.fEnd],
688 clusters[element.fGlyphRange.fEnd]);
689 while (element.fGlyphRange.fEnd < element.fRun->size()) {
690 // Shift left visually
691 element.fTextRange.fEnd = clusters[++element.fGlyphRange.fEnd];
692 if (element.fPositionType == PositionType::kGraphemeCluster) {
Julia Lavrovac87e9512021-08-11 16:24:24 -0400693 if (this->hasProperty(element.fTextRange.fEnd, GlyphUnitFlags::kGraphemeStart)) {
Julia Lavrovaad5944c2021-07-21 19:51:37 -0400694 break;
695 }
696 }
697 }
698 // Update the line
699 auto line = this->line(element.fLineIndex);
700 if (line->glyphTrailingEnd().runIndex() == this->runIndex(element.fRun) &&
701 line->glyphTrailingEnd().glyphIndex() < element.fGlyphRange.fEnd) {
702 ++element.fLineIndex;
703 }
704
705 // Either way we found us a grapheme cluster (just make sure of it)
706 SkASSERT(this->hasProperty(element.fTextRange.fEnd, GlyphUnitFlags::kGraphemeStart));
707 return element;
708}
709
710bool FormattedText::isFirstOnTheLine(Position element) const {
711 auto lineStart = this->line(element.fLineIndex)->glyphStart();
712 return lineStart.runIndex() == this->runIndex(element.fRun) &&
713 lineStart.glyphIndex() == element.fGlyphRange.fStart;
714}
715
716bool FormattedText::isLastOnTheLine(Position element) const {
717 auto lineEnd = this->line(element.fLineIndex)->glyphEnd();
718 return lineEnd.runIndex() == this->runIndex(element.fRun) &&
719 lineEnd.glyphIndex() == element.fGlyphRange.fStart;
720}
721
722Position FormattedText::firstElement(PositionType positionType) const {
723
724 Position beginningOfText(positionType);
725 // We need to go to the visually next run
726 // (skipping all the empty runs if there are any)
727 beginningOfText.fRun = this->visuallyFirstRun(0);
728 // Set the glyph range after the last glyph
729 beginningOfText.fGlyphRange = GlyphRange { 0, 0};
730 beginningOfText.fLineIndex = 0;
731
732 return beginningOfText;// this->nextElement(beginningOfText);
733}
734
735Position FormattedText::lastElement(PositionType positionType) const {
736
737 Position endOfText(positionType);
738 // We need to go to the visually next run
739 // (skipping all the empty runs if there are any)
740 endOfText.fRun = this->visuallyLastRun(fLines.size() - 1);
741 // Set the glyph range after the last glyph
742 endOfText.fGlyphRange = GlyphRange { endOfText.fRun->size(), endOfText.fRun->size() };
743 endOfText.fLineIndex = this->countLines() - 1;
744
745 return endOfText; // this->previousElement(endOfText);
746}
747
748bool FormattedText::isVisuallyFirst(size_t lineIndex, const TextRun* run) const {
749 auto firstLine = this->line(lineIndex);
750 return this->runIndex(run) == firstLine->visualRun(0);
751}
752
753bool FormattedText::isVisuallyLast(size_t lineIndex, const TextRun* run) const {
754 auto lastLine = this->line(lineIndex);
755 return this->runIndex(run) == lastLine->visualRun(lastLine->runsNumber() - 1);
756}
Julia Lavrova7856eb82021-06-02 16:01:04 -0400757} // namespace text
758} // namespace skia