Julia Lavrova | 7856eb8 | 2021-06-02 16:01:04 -0400 | [diff] [blame] | 1 | // Copyright 2021 Google LLC. |
| 2 | |
| 3 | #include "experimental/sktext/include/Text.h" |
| 4 | #include <stack> |
| 5 | |
| 6 | namespace skia { |
| 7 | namespace text { |
| 8 | |
| 9 | std::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 Lavrova | ad5944c | 2021-07-21 19:51:37 -0400 | [diff] [blame] | 18 | unicodeText->fText16 = std::u16string((char16_t*)utf16.data(), utf16.size()); |
| 19 | unicodeText->fText8 = unicodeText->fUnicode->convertUtf16ToUtf8(unicodeText->fText16); |
Julia Lavrova | 7856eb8 | 2021-06-02 16:01:04 -0400 | [diff] [blame] | 20 | size_t utf16Index = 0; |
| 21 | unicodeText->fUTF16FromUTF8.push_back_n(unicodeText->fText8.size() + 1, utf16Index); |
Julia Lavrova | ad5944c | 2021-07-21 19:51:37 -0400 | [diff] [blame] | 22 | unicodeText->fUTF8FromUTF16.push_back_n(unicodeText->fText16.size() + 1, utf16Index); |
Julia Lavrova | 7856eb8 | 2021-06-02 16:01:04 -0400 | [diff] [blame] | 23 | |
| 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 Lavrova | 398ef44 | 2021-09-14 20:37:58 +0000 | [diff] [blame^] | 27 | [&unicodeText, &utf16Index](SkUnichar unichar, int32_t start, int32_t end, size_t count) { |
Julia Lavrova | ad5944c | 2021-07-21 19:51:37 -0400 | [diff] [blame] | 28 | 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 Lavrova | 7856eb8 | 2021-06-02 16:01:04 -0400 | [diff] [blame] | 36 | |
Julia Lavrova | ad5944c | 2021-07-21 19:51:37 -0400 | [diff] [blame] | 37 | // Get white spaces |
| 38 | // TODO: It's a bug. We need to operate on utf16 indexes everywhere |
Julia Lavrova | 7856eb8 | 2021-06-02 16:01:04 -0400 | [diff] [blame] | 39 | unicodeText->fUnicode->forEachCodepoint(unicodeText->fText8.c_str(), unicodeText->fText8.size(), |
Julia Lavrova | 398ef44 | 2021-09-14 20:37:58 +0000 | [diff] [blame^] | 40 | [&unicodeText](SkUnichar unichar, int32_t start, int32_t end, size_t count) { |
Julia Lavrova | 7856eb8 | 2021-06-02 16:01:04 -0400 | [diff] [blame] | 41 | if (unicodeText->fUnicode->isWhitespace(unichar)) { |
| 42 | for (auto i = start; i < end; ++i) { |
| 43 | unicodeText->fCodeUnitProperties[i] |= CodeUnitFlags::kPartOfWhiteSpace; |
| 44 | } |
| 45 | } |
| 46 | }); |
| 47 | |
Julia Lavrova | 7856eb8 | 2021-06-02 16:01:04 -0400 | [diff] [blame] | 48 | // 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 Lavrova | ad5944c | 2021-07-21 19:51:37 -0400 | [diff] [blame] | 54 | // 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 Lavrova | 7856eb8 | 2021-06-02 16:01:04 -0400 | [diff] [blame] | 66 | return std::move(unicodeText); |
| 67 | } |
| 68 | |
Julia Lavrova | ad5944c | 2021-07-21 19:51:37 -0400 | [diff] [blame] | 69 | // Break text into pieces by font blocks and by formatting marks |
| 70 | // Formatting marks: \n (and possibly some other later) |
| 71 | std::unique_ptr<ShapedText> UnicodeText::shape(SkSpan<FontBlock> fontBlocks, |
Julia Lavrova | 7856eb8 | 2021-06-02 16:01:04 -0400 | [diff] [blame] | 72 | TextDirection textDirection) { |
Julia Lavrova | ad5944c | 2021-07-21 19:51:37 -0400 | [diff] [blame] | 73 | // 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 Lavrova | 7856eb8 | 2021-06-02 16:01:04 -0400 | [diff] [blame] | 83 | |
Julia Lavrova | ad5944c | 2021-07-21 19:51:37 -0400 | [diff] [blame] | 84 | // Copy flags and find all the formatting marks |
Julia Lavrova | 7856eb8 | 2021-06-02 16:01:04 -0400 | [diff] [blame] | 85 | fShapedText = std::unique_ptr<ShapedText>(new ShapedText()); |
Julia Lavrova | 7856eb8 | 2021-06-02 16:01:04 -0400 | [diff] [blame] | 86 | 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 Lavrova | ad5944c | 2021-07-21 19:51:37 -0400 | [diff] [blame] | 89 | if (this->isHardLineBreak(i)) { |
| 90 | formattingMarks.emplace_back(i); |
Julia Lavrova | 7856eb8 | 2021-06-02 16:01:04 -0400 | [diff] [blame] | 91 | } |
| 92 | } |
| 93 | |
Julia Lavrova | ad5944c | 2021-07-21 19:51:37 -0400 | [diff] [blame] | 94 | // 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 Lavrova | 7856eb8 | 2021-06-02 16:01:04 -0400 | [diff] [blame] | 133 | return std::move(fShapedText); |
| 134 | } |
| 135 | |
| 136 | void UnicodeText::commitRunBuffer(const RunInfo&) { |
| 137 | fCurrentRun->commit(); |
| 138 | |
Julia Lavrova | f8e69ca | 2021-08-17 13:21:23 -0400 | [diff] [blame] | 139 | // Convert utf8 range into utf16 range |
| 140 | fCurrentRun->convertUtf16Range([this](unsigned long index) { |
| 141 | return this->fUTF16FromUTF8[index + fParagraphTextStart]; |
| 142 | }); |
| 143 | |
Julia Lavrova | ad5944c | 2021-07-21 19:51:37 -0400 | [diff] [blame] | 144 | // 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 Lavrova | 7856eb8 | 2021-06-02 16:01:04 -0400 | [diff] [blame] | 153 | |
Julia Lavrova | 7856eb8 | 2021-06-02 16:01:04 -0400 | [diff] [blame] | 154 | fShapedText->fRuns.emplace_back(std::move(*fCurrentRun)); |
Julia Lavrova | ad5944c | 2021-07-21 19:51:37 -0400 | [diff] [blame] | 155 | |
| 156 | fRunGlyphStart += fCurrentRun->width(); |
Julia Lavrova | 7856eb8 | 2021-06-02 16:01:04 -0400 | [diff] [blame] | 157 | } |
| 158 | |
Julia Lavrova | ad5944c | 2021-07-21 19:51:37 -0400 | [diff] [blame] | 159 | SkFont UnicodeText::createFont(const FontBlock& fontBlock) { |
Julia Lavrova | 7856eb8 | 2021-06-02 16:01:04 -0400 | [diff] [blame] | 160 | |
Julia Lavrova | ad5944c | 2021-07-21 19:51:37 -0400 | [diff] [blame] | 161 | if (fontBlock.chain->count() == 0) { |
Julia Lavrova | 7856eb8 | 2021-06-02 16:01:04 -0400 | [diff] [blame] | 162 | return SkFont(); |
| 163 | } |
Julia Lavrova | ad5944c | 2021-07-21 19:51:37 -0400 | [diff] [blame] | 164 | sk_sp<SkTypeface> typeface = fontBlock.chain->operator[](0); |
Julia Lavrova | 7856eb8 | 2021-06-02 16:01:04 -0400 | [diff] [blame] | 165 | |
Julia Lavrova | ad5944c | 2021-07-21 19:51:37 -0400 | [diff] [blame] | 166 | SkFont font(std::move(typeface), fontBlock.chain->size()); |
Julia Lavrova | 7856eb8 | 2021-06-02 16:01:04 -0400 | [diff] [blame] | 167 | font.setEdging(SkFont::Edging::kAntiAlias); |
| 168 | font.setHinting(SkFontHinting::kSlight); |
| 169 | font.setSubpixel(true); |
| 170 | |
| 171 | return font; |
| 172 | } |
| 173 | |
| 174 | std::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 Lavrova | ecc8e3b | 2021-06-10 10:26:36 -0400 | [diff] [blame] | 178 | wrappedText->fGlyphUnitProperties = this->fGlyphUnitProperties; // copy |
Julia Lavrova | 7856eb8 | 2021-06-02 16:01:04 -0400 | [diff] [blame] | 179 | |
| 180 | // line : spaces : clusters |
| 181 | Stretch line; |
| 182 | Stretch spaces; |
| 183 | Stretch clusters; |
Julia Lavrova | ad5944c | 2021-07-21 19:51:37 -0400 | [diff] [blame] | 184 | Stretch cluster; |
Julia Lavrova | 7856eb8 | 2021-06-02 16:01:04 -0400 | [diff] [blame] | 185 | |
| 186 | for (size_t runIndex = 0; runIndex < this->fRuns.size(); ++runIndex ) { |
| 187 | |
| 188 | auto& run = this->fRuns[runIndex]; |
| 189 | TextMetrics runMetrics(run.fFont); |
Julia Lavrova | 7856eb8 | 2021-06-02 16:01:04 -0400 | [diff] [blame] | 190 | 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 Lavrova | 1888466 | 2021-06-10 16:33:49 -0400 | [diff] [blame] | 205 | auto clusterWidth = run.calculateWidth(cluster.glyphStartIndex(), glyphIndex); |
Julia Lavrova | 7856eb8 | 2021-06-02 16:01:04 -0400 | [diff] [blame] | 206 | 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 Lavrova | 1888466 | 2021-06-10 16:33:49 -0400 | [diff] [blame] | 213 | if (isWhitespaces || isHardLineBreak) { |
Julia Lavrova | 7856eb8 | 2021-06-02 16:01:04 -0400 | [diff] [blame] | 214 | // 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 Lavrova | ad5944c | 2021-07-21 19:51:37 -0400 | [diff] [blame] | 231 | spaces.moveTo(cluster); |
| 232 | wrappedText->addLine(line, spaces, unicode, true); |
| 233 | line = spaces; |
| 234 | clusters = spaces; |
| 235 | continue; |
Julia Lavrova | 7856eb8 | 2021-06-02 16:01:04 -0400 | [diff] [blame] | 236 | } 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 Lavrova | ad5944c | 2021-07-21 19:51:37 -0400 | [diff] [blame] | 259 | wrappedText->addLine(line, spaces, unicode, false); |
Julia Lavrova | 1888466 | 2021-06-10 16:33:49 -0400 | [diff] [blame] | 260 | line = spaces; |
Julia Lavrova | 7856eb8 | 2021-06-02 16:01:04 -0400 | [diff] [blame] | 261 | 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 Lavrova | ad5944c | 2021-07-21 19:51:37 -0400 | [diff] [blame] | 272 | } 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 Lavrova | 7856eb8 | 2021-06-02 16:01:04 -0400 | [diff] [blame] | 276 | } |
Julia Lavrova | 7856eb8 | 2021-06-02 16:01:04 -0400 | [diff] [blame] | 277 | |
Julia Lavrova | ad5944c | 2021-07-21 19:51:37 -0400 | [diff] [blame] | 278 | wrappedText->addLine(line, spaces, unicode, false); |
| 279 | |
| 280 | wrappedText->fActualSize.fWidth = width; |
Julia Lavrova | 7856eb8 | 2021-06-02 16:01:04 -0400 | [diff] [blame] | 281 | return std::move(wrappedText); |
| 282 | } |
| 283 | |
Julia Lavrova | ad5944c | 2021-07-21 19:51:37 -0400 | [diff] [blame] | 284 | void WrappedText::addLine(Stretch& stretch, Stretch& spaces, SkUnicode* unicode, bool hardLineBreak) { |
Julia Lavrova | 7856eb8 | 2021-06-02 16:01:04 -0400 | [diff] [blame] | 285 | |
| 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 Lavrova | ad5944c | 2021-07-21 19:51:37 -0400 | [diff] [blame] | 305 | this->fLines.emplace_back(stretch, spaces, std::move(visualOrder), fActualSize.fHeight, hardLineBreak); |
| 306 | fActualSize.fHeight += stretch.textMetrics().height(); |
Julia Lavrova | 7856eb8 | 2021-06-02 16:01:04 -0400 | [diff] [blame] | 307 | |
| 308 | stretch.clean(); |
| 309 | spaces.clean(); |
| 310 | } |
| 311 | |
| 312 | sk_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 Lavrova | ad5944c | 2021-07-21 19:51:37 -0400 | [diff] [blame] | 318 | 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 Lavrova | 7856eb8 | 2021-06-02 16:01:04 -0400 | [diff] [blame] | 329 | |
| 330 | return std::move(formattedText); |
| 331 | } |
| 332 | |
| 333 | void FormattedText::visit(Visitor* visitor) const { |
| 334 | |
| 335 | SkPoint offset = SkPoint::Make(0 , 0); |
| 336 | for (auto& line : this->fLines) { |
| 337 | offset.fX = 0; |
Julia Lavrova | c87e951 | 2021-08-11 16:24:24 -0400 | [diff] [blame] | 338 | visitor->onBeginLine(line.text()); |
Julia Lavrova | ad5944c | 2021-07-21 19:51:37 -0400 | [diff] [blame] | 339 | for (auto index = 0; index < line.runsNumber(); ++index) { |
| 340 | auto runIndex = line.visualRun(index); |
Julia Lavrova | 7856eb8 | 2021-06-02 16:01:04 -0400 | [diff] [blame] | 341 | auto& run = this->fRuns[runIndex]; |
Julia Lavrova | c87e951 | 2021-08-11 16:24:24 -0400 | [diff] [blame] | 342 | |
Julia Lavrova | f8e69ca | 2021-08-17 13:21:23 -0400 | [diff] [blame] | 343 | GlyphRange glyphRange(line.glyphRange(runIndex, run.size(), false /* excluding trailing spaces */)); |
Julia Lavrova | 7856eb8 | 2021-06-02 16:01:04 -0400 | [diff] [blame] | 344 | // Update positions |
Julia Lavrova | c87e951 | 2021-08-11 16:24:24 -0400 | [diff] [blame] | 345 | 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 Lavrova | 7856eb8 | 2021-06-02 16:01:04 -0400 | [diff] [blame] | 349 | } |
Julia Lavrova | c87e951 | 2021-08-11 16:24:24 -0400 | [diff] [blame] | 350 | 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 Lavrova | f8e69ca | 2021-08-17 13:21:23 -0400 | [diff] [blame] | 352 | offset.fX += run.calculateWidth(glyphRange); |
Julia Lavrova | 7856eb8 | 2021-06-02 16:01:04 -0400 | [diff] [blame] | 353 | } |
Julia Lavrova | c87e951 | 2021-08-11 16:24:24 -0400 | [diff] [blame] | 354 | visitor->onEndLine(line.text()); |
Julia Lavrova | ad5944c | 2021-07-21 19:51:37 -0400 | [diff] [blame] | 355 | offset.fY += line.height(); |
Julia Lavrova | 7856eb8 | 2021-06-02 16:01:04 -0400 | [diff] [blame] | 356 | } |
| 357 | } |
| 358 | |
Julia Lavrova | f8e69ca | 2021-08-17 13:21:23 -0400 | [diff] [blame] | 359 | void FormattedText::visit(Visitor* visitor, SkSpan<TextIndex> textChunks) const { |
Julia Lavrova | 7856eb8 | 2021-06-02 16:01:04 -0400 | [diff] [blame] | 360 | // 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 Lavrova | 7856eb8 | 2021-06-02 16:01:04 -0400 | [diff] [blame] | 366 | SkPoint offset = SkPoint::Make(0 , 0); |
| 367 | for (auto& line : this->fLines) { |
| 368 | offset.fX = 0; |
Julia Lavrova | c87e951 | 2021-08-11 16:24:24 -0400 | [diff] [blame] | 369 | visitor->onBeginLine(line.text()); |
Julia Lavrova | ad5944c | 2021-07-21 19:51:37 -0400 | [diff] [blame] | 370 | for (auto index = 0; index < line.runsNumber(); ++index) { |
| 371 | auto runIndex = line.visualRun(index); |
Julia Lavrova | 7856eb8 | 2021-06-02 16:01:04 -0400 | [diff] [blame] | 372 | auto& run = this->fRuns[runIndex]; |
Julia Lavrova | ad5944c | 2021-07-21 19:51:37 -0400 | [diff] [blame] | 373 | if (run.size() == 0) { |
| 374 | continue; |
| 375 | } |
Julia Lavrova | c87e951 | 2021-08-11 16:24:24 -0400 | [diff] [blame] | 376 | |
Julia Lavrova | f8e69ca | 2021-08-17 13:21:23 -0400 | [diff] [blame] | 377 | GlyphRange runGlyphRange(line.glyphRange(runIndex, run.size(), false /* excluding trailing spaces */)); |
Julia Lavrova | c87e951 | 2021-08-11 16:24:24 -0400 | [diff] [blame] | 378 | SkPoint shift = SkPoint::Make(-run.fPositions[runGlyphRange.fStart].fX, line.baseline()); |
Julia Lavrova | 7856eb8 | 2021-06-02 16:01:04 -0400 | [diff] [blame] | 379 | // 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 Lavrova | 7856eb8 | 2021-06-02 16:01:04 -0400 | [diff] [blame] | 385 | |
Julia Lavrova | f8e69ca | 2021-08-17 13:21:23 -0400 | [diff] [blame] | 386 | // 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 Lavrova | ad5944c | 2021-07-21 19:51:37 -0400 | [diff] [blame] | 388 | // Update positions & calculate the bounding rect |
Julia Lavrova | 7856eb8 | 2021-06-02 16:01:04 -0400 | [diff] [blame] | 389 | SkAutoSTMalloc<256, SkPoint> positions(glyphRange.width() + 1); |
Julia Lavrova | f8e69ca | 2021-08-17 13:21:23 -0400 | [diff] [blame] | 390 | for (GlyphIndex i = glyphRange.fStart; i <= glyphRange.fEnd; ++i) { |
Julia Lavrova | c87e951 | 2021-08-11 16:24:24 -0400 | [diff] [blame] | 391 | positions[i - glyphRange.fStart] = run.fPositions[i] + shift + offset + SkPoint::Make(line.horizontalOffset(), 0.0f); |
Julia Lavrova | 7856eb8 | 2021-06-02 16:01:04 -0400 | [diff] [blame] | 392 | } |
Julia Lavrova | c87e951 | 2021-08-11 16:24:24 -0400 | [diff] [blame] | 393 | 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 Lavrova | 7856eb8 | 2021-06-02 16:01:04 -0400 | [diff] [blame] | 395 | |
Julia Lavrova | f8e69ca | 2021-08-17 13:21:23 -0400 | [diff] [blame] | 396 | }); |
Julia Lavrova | 7856eb8 | 2021-06-02 16:01:04 -0400 | [diff] [blame] | 397 | |
Julia Lavrova | f8e69ca | 2021-08-17 13:21:23 -0400 | [diff] [blame] | 398 | offset.fX += run.calculateWidth(runGlyphRange); |
Julia Lavrova | 7856eb8 | 2021-06-02 16:01:04 -0400 | [diff] [blame] | 399 | } |
Julia Lavrova | c87e951 | 2021-08-11 16:24:24 -0400 | [diff] [blame] | 400 | visitor->onEndLine(line.text()); |
Julia Lavrova | ad5944c | 2021-07-21 19:51:37 -0400 | [diff] [blame] | 401 | offset.fY += line.height(); |
Julia Lavrova | 7856eb8 | 2021-06-02 16:01:04 -0400 | [diff] [blame] | 402 | } |
| 403 | } |
| 404 | |
Julia Lavrova | ad5944c | 2021-07-21 19:51:37 -0400 | [diff] [blame] | 405 | // Find the element that includes textIndex |
| 406 | Position FormattedText::adjustedPosition(PositionType positionType, TextIndex textIndex) const { |
| 407 | Position position(positionType); |
| 408 | SkScalar shift = 0; |
Julia Lavrova | 7856eb8 | 2021-06-02 16:01:04 -0400 | [diff] [blame] | 409 | for (auto& line : fLines) { |
Julia Lavrova | ad5944c | 2021-07-21 19:51:37 -0400 | [diff] [blame] | 410 | 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 Lavrova | 7856eb8 | 2021-06-02 16:01:04 -0400 | [diff] [blame] | 413 | continue; |
| 414 | } |
Julia Lavrova | ecc8e3b | 2021-06-10 10:26:36 -0400 | [diff] [blame] | 415 | |
Julia Lavrova | ad5944c | 2021-07-21 19:51:37 -0400 | [diff] [blame] | 416 | shift = 0; |
| 417 | for (auto index = 0; index < line.runsNumber(); ++index) { |
| 418 | auto runIndex = line.visualRun(index); |
Julia Lavrova | 7856eb8 | 2021-06-02 16:01:04 -0400 | [diff] [blame] | 419 | auto& run = fRuns[runIndex]; |
Julia Lavrova | ad5944c | 2021-07-21 19:51:37 -0400 | [diff] [blame] | 420 | |
Julia Lavrova | f8e69ca | 2021-08-17 13:21:23 -0400 | [diff] [blame] | 421 | GlyphRange runGlyphRange(line.glyphRange(runIndex, run.size(), true /* including trailing spaces */)); |
Julia Lavrova | c87e951 | 2021-08-11 16:24:24 -0400 | [diff] [blame] | 422 | auto runWidth = run.calculateWidth(runGlyphRange); |
Julia Lavrova | ad5944c | 2021-07-21 19:51:37 -0400 | [diff] [blame] | 423 | |
| 424 | if (!run.fUtf16Range.contains(textIndex)) { |
| 425 | shift += runWidth; |
| 426 | continue; |
Julia Lavrova | 7856eb8 | 2021-06-02 16:01:04 -0400 | [diff] [blame] | 427 | } |
Julia Lavrova | ad5944c | 2021-07-21 19:51:37 -0400 | [diff] [blame] | 428 | |
Julia Lavrova | f8e69ca | 2021-08-17 13:21:23 -0400 | [diff] [blame] | 429 | // Find the position left |
| 430 | GlyphIndex found = run.findGlyphIndexLeftOf(runGlyphRange, textIndex); |
Julia Lavrova | ad5944c | 2021-07-21 19:51:37 -0400 | [diff] [blame] | 431 | |
| 432 | position.fLineIndex = lineIndex(&line); |
| 433 | position.fRun = &run; |
Julia Lavrova | c87e951 | 2021-08-11 16:24:24 -0400 | [diff] [blame] | 434 | 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 Lavrova | ad5944c | 2021-07-21 19:51:37 -0400 | [diff] [blame] | 439 | return position; |
Julia Lavrova | 7856eb8 | 2021-06-02 16:01:04 -0400 | [diff] [blame] | 440 | } |
Julia Lavrova | ad5944c | 2021-07-21 19:51:37 -0400 | [diff] [blame] | 441 | // The cursor is not on the text anymore; position it after the last element |
| 442 | break; |
Julia Lavrova | 7856eb8 | 2021-06-02 16:01:04 -0400 | [diff] [blame] | 443 | } |
Julia Lavrova | ecc8e3b | 2021-06-10 10:26:36 -0400 | [diff] [blame] | 444 | |
Julia Lavrova | ad5944c | 2021-07-21 19:51:37 -0400 | [diff] [blame] | 445 | position.fLineIndex = fLines.size() - 1; |
| 446 | position.fRun = this->visuallyLastRun(position.fLineIndex); |
| 447 | position.fGlyphRange = GlyphRange(position.fRun->size(), position.fRun->size()); |
Julia Lavrova | c87e951 | 2021-08-11 16:24:24 -0400 | [diff] [blame] | 448 | position.fTextRange = position.fRun->getTextRange(position.fGlyphRange); |
| 449 | this->adjustTextRange(&position); |
Julia Lavrova | ad5944c | 2021-07-21 19:51:37 -0400 | [diff] [blame] | 450 | position.fBoundaries.fLeft = fLines.back().withWithTrailingSpaces(); |
| 451 | position.fBoundaries.fRight = fLines.back().withWithTrailingSpaces(); |
| 452 | return position; |
Julia Lavrova | 7856eb8 | 2021-06-02 16:01:04 -0400 | [diff] [blame] | 453 | } |
| 454 | |
Julia Lavrova | ad5944c | 2021-07-21 19:51:37 -0400 | [diff] [blame] | 455 | Position FormattedText::adjustedPosition(PositionType positionType, SkPoint xy) const { |
| 456 | |
Julia Lavrova | c87e951 | 2021-08-11 16:24:24 -0400 | [diff] [blame] | 457 | xy.fX = std::min(xy.fX, this->fActualSize.fWidth); |
| 458 | xy.fY = std::min(xy.fY, this->fActualSize.fHeight); |
Julia Lavrova | 7856eb8 | 2021-06-02 16:01:04 -0400 | [diff] [blame] | 459 | |
Julia Lavrova | ad5944c | 2021-07-21 19:51:37 -0400 | [diff] [blame] | 460 | Position position(positionType); |
Julia Lavrova | c87e951 | 2021-08-11 16:24:24 -0400 | [diff] [blame] | 461 | position.fRun = this->visuallyLastRun(this->fLines.size() - 1); |
Julia Lavrova | 7856eb8 | 2021-06-02 16:01:04 -0400 | [diff] [blame] | 462 | for (auto& line : fLines) { |
Julia Lavrova | ad5944c | 2021-07-21 19:51:37 -0400 | [diff] [blame] | 463 | position.fBoundaries.fTop = position.fBoundaries.fBottom; |
| 464 | position.fBoundaries.fBottom = line.verticalOffset() + line.height(); |
Julia Lavrova | 8343e00 | 2021-08-11 14:45:35 -0400 | [diff] [blame] | 465 | position.fLineIndex = this->lineIndex(&line); |
Julia Lavrova | ad5944c | 2021-07-21 19:51:37 -0400 | [diff] [blame] | 466 | if (position.fBoundaries.fTop > xy.fY) { |
Julia Lavrova | c87e951 | 2021-08-11 16:24:24 -0400 | [diff] [blame] | 467 | // We are past the point vertically |
Julia Lavrova | 7856eb8 | 2021-06-02 16:01:04 -0400 | [diff] [blame] | 468 | break; |
Julia Lavrova | ad5944c | 2021-07-21 19:51:37 -0400 | [diff] [blame] | 469 | } else if (position.fBoundaries.fBottom <= xy.fY) { |
Julia Lavrova | c87e951 | 2021-08-11 16:24:24 -0400 | [diff] [blame] | 470 | // We haven't reached the point vertically yet |
Julia Lavrova | 7856eb8 | 2021-06-02 16:01:04 -0400 | [diff] [blame] | 471 | continue; |
| 472 | } |
| 473 | |
Julia Lavrova | c87e951 | 2021-08-11 16:24:24 -0400 | [diff] [blame] | 474 | // 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 Lavrova | ad5944c | 2021-07-21 19:51:37 -0400 | [diff] [blame] | 477 | for (auto index = 0; index < line.runsNumber(); ++index) { |
| 478 | auto runIndex = line.visualRun(index); |
Julia Lavrova | 7856eb8 | 2021-06-02 16:01:04 -0400 | [diff] [blame] | 479 | auto& run = fRuns[runIndex]; |
Julia Lavrova | f8e69ca | 2021-08-17 13:21:23 -0400 | [diff] [blame] | 480 | GlyphRange runGlyphRangeInLine = line.glyphRange(runIndex, run.size(), true /* including trailing space */); |
Julia Lavrova | c87e951 | 2021-08-11 16:24:24 -0400 | [diff] [blame] | 481 | SkScalar runWidthInLine = run.calculateWidth(runGlyphRangeInLine); |
| 482 | position.fRun = &run; |
| 483 | if (runOffsetInLine > xy.fX) { |
| 484 | // We are past the point horizontally |
Julia Lavrova | 7856eb8 | 2021-06-02 16:01:04 -0400 | [diff] [blame] | 485 | break; |
Julia Lavrova | c87e951 | 2021-08-11 16:24:24 -0400 | [diff] [blame] | 486 | } else if (runOffsetInLine + runWidthInLine < xy.fX) { |
| 487 | // We haven't reached the point horizontally yet |
| 488 | runOffsetInLine += runWidthInLine; |
Julia Lavrova | 7856eb8 | 2021-06-02 16:01:04 -0400 | [diff] [blame] | 489 | continue; |
| 490 | } |
Julia Lavrova | c87e951 | 2021-08-11 16:24:24 -0400 | [diff] [blame] | 491 | |
| 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 Lavrova | 7856eb8 | 2021-06-02 16:01:04 -0400 | [diff] [blame] | 497 | break; |
| 498 | } |
Julia Lavrova | ecc8e3b | 2021-06-10 10:26:36 -0400 | [diff] [blame] | 499 | found = i; |
Julia Lavrova | 7856eb8 | 2021-06-02 16:01:04 -0400 | [diff] [blame] | 500 | } |
Julia Lavrova | ad5944c | 2021-07-21 19:51:37 -0400 | [diff] [blame] | 501 | |
Julia Lavrova | c87e951 | 2021-08-11 16:24:24 -0400 | [diff] [blame] | 502 | 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 Lavrova | ad5944c | 2021-07-21 19:51:37 -0400 | [diff] [blame] | 507 | return position; |
Julia Lavrova | 7856eb8 | 2021-06-02 16:01:04 -0400 | [diff] [blame] | 508 | } |
Julia Lavrova | c87e951 | 2021-08-11 16:24:24 -0400 | [diff] [blame] | 509 | // The cursor is not on the text anymore; position it after the last element of the last visual run of the current line |
Julia Lavrova | ad5944c | 2021-07-21 19:51:37 -0400 | [diff] [blame] | 510 | break; |
Julia Lavrova | 7856eb8 | 2021-06-02 16:01:04 -0400 | [diff] [blame] | 511 | } |
Julia Lavrova | ad5944c | 2021-07-21 19:51:37 -0400 | [diff] [blame] | 512 | |
Julia Lavrova | ad5944c | 2021-07-21 19:51:37 -0400 | [diff] [blame] | 513 | position.fRun = this->visuallyLastRun(position.fLineIndex); |
Julia Lavrova | c87e951 | 2021-08-11 16:24:24 -0400 | [diff] [blame] | 514 | auto line = this->line(position.fLineIndex); |
Julia Lavrova | 8343e00 | 2021-08-11 14:45:35 -0400 | [diff] [blame] | 515 | position.fGlyphRange.fStart = |
Julia Lavrova | f8e69ca | 2021-08-17 13:21:23 -0400 | [diff] [blame] | 516 | position.fGlyphRange.fEnd = line->glyphRange(this->runIndex(position.fRun), position.fRun->size(), true /* including trailing spaces */).fEnd; |
Julia Lavrova | c87e951 | 2021-08-11 16:24:24 -0400 | [diff] [blame] | 517 | position.fTextRange = position.fRun->getTextRange(position.fGlyphRange); |
| 518 | this->adjustTextRange(&position); |
Julia Lavrova | 8343e00 | 2021-08-11 14:45:35 -0400 | [diff] [blame] | 519 | position.fBoundaries.fLeft = |
| 520 | position.fBoundaries.fRight = line->withWithTrailingSpaces(); |
Julia Lavrova | ad5944c | 2021-07-21 19:51:37 -0400 | [diff] [blame] | 521 | return position; |
Julia Lavrova | 7856eb8 | 2021-06-02 16:01:04 -0400 | [diff] [blame] | 522 | } |
| 523 | |
Julia Lavrova | c87e951 | 2021-08-11 16:24:24 -0400 | [diff] [blame] | 524 | // 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 Lavrova | ad5944c | 2021-07-21 19:51:37 -0400 | [diff] [blame] | 526 | void FormattedText::adjustTextRange(Position* position) const { |
Julia Lavrova | c87e951 | 2021-08-11 16:24:24 -0400 | [diff] [blame] | 527 | // 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 Lavrova | ad5944c | 2021-07-21 19:51:37 -0400 | [diff] [blame] | 552 | 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 |
| 556 | bool 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 | |
| 571 | const 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 | |
| 593 | const 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 | |
| 615 | const TextRun* FormattedText::visuallyFirstRun(size_t lineIndex) const { |
| 616 | auto line = this->line(lineIndex); |
| 617 | return &fRuns[line->visualRun(0)]; |
| 618 | } |
| 619 | |
| 620 | const TextRun* FormattedText::visuallyLastRun(size_t lineIndex) const { |
| 621 | auto line = this->line(lineIndex); |
| 622 | return &fRuns[line->visualRun(line->runsNumber() - 1)]; |
| 623 | } |
| 624 | |
| 625 | Position 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 | |
| 668 | Position 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 Lavrova | c87e951 | 2021-08-11 16:24:24 -0400 | [diff] [blame] | 693 | if (this->hasProperty(element.fTextRange.fEnd, GlyphUnitFlags::kGraphemeStart)) { |
Julia Lavrova | ad5944c | 2021-07-21 19:51:37 -0400 | [diff] [blame] | 694 | 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 | |
| 710 | bool 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 | |
| 716 | bool 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 | |
| 722 | Position 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 | |
| 735 | Position 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 | |
| 748 | bool 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 | |
| 753 | bool 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 Lavrova | 7856eb8 | 2021-06-02 16:01:04 -0400 | [diff] [blame] | 757 | } // namespace text |
| 758 | } // namespace skia |