Julia Lavrova | a3552c5 | 2019-05-30 16:12:56 -0400 | [diff] [blame] | 1 | // Copyright 2019 Google LLC. |
| 2 | #include "modules/skparagraph/src/TextLine.h" |
| 3 | #include <unicode/brkiter.h> |
| 4 | #include <unicode/ubidi.h> |
| 5 | #include "modules/skparagraph/src/ParagraphImpl.h" |
| 6 | |
| 7 | #include "include/core/SkMaskFilter.h" |
| 8 | #include "include/effects/SkDashPathEffect.h" |
| 9 | #include "include/effects/SkDiscretePathEffect.h" |
| 10 | #include "src/core/SkMakeUnique.h" |
| 11 | |
| 12 | namespace { |
| 13 | |
| 14 | SkSpan<const char> intersected(const SkSpan<const char>& a, const SkSpan<const char>& b) { |
| 15 | auto begin = SkTMax(a.begin(), b.begin()); |
| 16 | auto end = SkTMin(a.end(), b.end()); |
| 17 | return SkSpan<const char>(begin, end > begin ? end - begin : 0); |
| 18 | } |
| 19 | |
| 20 | int32_t intersectedSize(SkSpan<const char> a, SkSpan<const char> b) { |
| 21 | if (a.begin() == nullptr || b.begin() == nullptr) { |
| 22 | return -1; |
| 23 | } |
| 24 | auto begin = SkTMax(a.begin(), b.begin()); |
| 25 | auto end = SkTMin(a.end(), b.end()); |
| 26 | return SkToS32(end - begin); |
| 27 | } |
| 28 | } // namespace |
| 29 | |
| 30 | namespace skia { |
| 31 | namespace textlayout { |
| 32 | |
| 33 | SkTHashMap<SkFont, Run> TextLine::fEllipsisCache; |
| 34 | |
| 35 | TextLine::TextLine(SkVector offset, SkVector advance, SkSpan<const TextBlock> blocks, |
| 36 | SkSpan<const char> text, SkSpan<const char> textWithSpaces, |
| 37 | SkSpan<const Cluster> clusters, size_t startPos, size_t endPos, |
| 38 | LineMetrics sizes) |
| 39 | : fBlocks(blocks) |
| 40 | , fText(text) |
| 41 | , fTextWithSpaces(textWithSpaces) |
| 42 | , fClusters(clusters) |
| 43 | //, fStartPos(startPos) |
| 44 | //, fEndPos(endPos) |
| 45 | , fLogical() |
| 46 | , fShift(0) |
| 47 | , fAdvance(advance) |
| 48 | , fOffset(offset) |
| 49 | , fEllipsis(nullptr) |
| 50 | , fSizes(sizes) { |
| 51 | TRACE_EVENT0("skia", TRACE_FUNC); |
| 52 | // Reorder visual runs |
| 53 | auto start = fClusters.begin(); |
| 54 | auto end = fClusters.end() - 1; |
| 55 | size_t numRuns = end->run()->index() - start->run()->index() + 1; |
| 56 | |
| 57 | // Get the logical order |
| 58 | std::vector<UBiDiLevel> runLevels; |
| 59 | for (auto run = start->run(); run <= end->run(); ++run) { |
| 60 | runLevels.emplace_back(run->fBidiLevel); |
| 61 | } |
| 62 | |
| 63 | std::vector<int32_t> logicalOrder(numRuns); |
| 64 | ubidi_reorderVisual(runLevels.data(), SkToU32(numRuns), logicalOrder.data()); |
| 65 | |
| 66 | auto firstRun = start->run(); |
| 67 | for (auto index : logicalOrder) { |
| 68 | fLogical.push_back(firstRun + index); |
| 69 | } |
| 70 | |
| 71 | // TODO: use fStartPos and fEndPos really |
| 72 | // SkASSERT(fStartPos <= start->run()->size()); |
| 73 | // SkASSERT(fEndPos <= end->run()->size()); |
| 74 | } |
| 75 | |
| 76 | TextLine::TextLine(TextLine&& other) { |
| 77 | TRACE_EVENT0("skia", TRACE_FUNC); |
| 78 | this->fBlocks = other.fBlocks; |
| 79 | this->fText = other.fText; |
| 80 | this->fTextWithSpaces = other.fTextWithSpaces; |
| 81 | this->fLogical.reset(); |
| 82 | this->fLogical = std::move(other.fLogical); |
| 83 | this->fShift = other.fShift; |
| 84 | this->fAdvance = other.fAdvance; |
| 85 | this->fOffset = other.fOffset; |
| 86 | this->fEllipsis = std::move(other.fEllipsis); |
| 87 | this->fSizes = other.sizes(); |
| 88 | this->fClusters = other.fClusters; |
| 89 | } |
| 90 | |
| 91 | void TextLine::paint(SkCanvas* textCanvas) { |
| 92 | TRACE_EVENT0("skia", TRACE_FUNC); |
| 93 | if (this->empty()) { |
| 94 | return; |
| 95 | } |
| 96 | |
| 97 | textCanvas->save(); |
| 98 | textCanvas->translate(this->offset().fX, this->offset().fY); |
| 99 | |
| 100 | this->iterateThroughStylesInTextOrder( |
| 101 | StyleType::kBackground, |
| 102 | [textCanvas, this](SkSpan<const char> text, TextStyle style, SkScalar offsetX) { |
| 103 | return this->paintBackground(textCanvas, text, style, offsetX); |
| 104 | }); |
| 105 | |
| 106 | this->iterateThroughStylesInTextOrder( |
| 107 | StyleType::kShadow, |
| 108 | [textCanvas, this](SkSpan<const char> text, TextStyle style, SkScalar offsetX) { |
| 109 | return this->paintShadow(textCanvas, text, style, offsetX); |
| 110 | }); |
| 111 | |
| 112 | this->iterateThroughStylesInTextOrder( |
| 113 | StyleType::kForeground, |
| 114 | [textCanvas, this](SkSpan<const char> text, TextStyle style, SkScalar offsetX) { |
| 115 | return this->paintText(textCanvas, text, style, offsetX); |
| 116 | }); |
| 117 | |
| 118 | this->iterateThroughStylesInTextOrder( |
| 119 | StyleType::kDecorations, |
| 120 | [textCanvas, this](SkSpan<const char> text, TextStyle style, SkScalar offsetX) { |
| 121 | return this->paintDecorations(textCanvas, text, style, offsetX); |
| 122 | }); |
| 123 | |
| 124 | textCanvas->restore(); |
| 125 | } |
| 126 | |
| 127 | void TextLine::format(TextAlign effectiveAlign, SkScalar maxWidth, bool notLastLine) { |
| 128 | TRACE_EVENT0("skia", TRACE_FUNC); |
| 129 | SkScalar delta = maxWidth - this->width(); |
| 130 | if (delta <= 0) { |
| 131 | return; |
| 132 | } |
| 133 | |
| 134 | if (effectiveAlign == TextAlign::kJustify && notLastLine) { |
| 135 | this->justify(maxWidth); |
| 136 | } else if (effectiveAlign == TextAlign::kRight) { |
| 137 | this->shiftTo(delta); |
| 138 | } else if (effectiveAlign == TextAlign::kCenter) { |
| 139 | this->shiftTo(delta / 2); |
| 140 | } |
| 141 | } |
| 142 | |
| 143 | void TextLine::scanStyles(StyleType style, const StyleVisitor& visitor) { |
| 144 | if (this->empty()) { |
| 145 | return; |
| 146 | } |
| 147 | |
| 148 | this->iterateThroughStylesInTextOrder( |
| 149 | style, [this, visitor](SkSpan<const char> text, TextStyle style, SkScalar offsetX) { |
| 150 | visitor(text, style, offsetX); |
| 151 | return this->iterateThroughRuns( |
| 152 | text, offsetX, false, |
| 153 | [](Run*, int32_t, size_t, SkRect, SkScalar, bool) { return true; }); |
| 154 | }); |
| 155 | } |
| 156 | |
| 157 | void TextLine::scanRuns(const RunVisitor& visitor) { |
| 158 | this->iterateThroughRuns( |
| 159 | fText, 0, false, |
| 160 | [visitor](Run* run, int32_t pos, size_t size, SkRect clip, SkScalar sc, bool b) { |
| 161 | visitor(run, pos, size, clip, sc, b); |
| 162 | return true; |
| 163 | }); |
| 164 | } |
| 165 | |
| 166 | SkScalar TextLine::paintText(SkCanvas* canvas, SkSpan<const char> text, const TextStyle& style, |
| 167 | SkScalar offsetX) const { |
| 168 | TRACE_EVENT0("skia", TRACE_FUNC); |
| 169 | SkPaint paint; |
| 170 | if (style.hasForeground()) { |
| 171 | paint = style.getForeground(); |
| 172 | } else { |
| 173 | paint.setColor(style.getColor()); |
| 174 | } |
| 175 | |
| 176 | auto shiftDown = this->baseline(); |
| 177 | return this->iterateThroughRuns( |
| 178 | text, offsetX, false, |
| 179 | [paint, canvas, shiftDown](Run* run, int32_t pos, size_t size, SkRect clip, |
| 180 | SkScalar shift, bool clippingNeeded) { |
| 181 | SkTextBlobBuilder builder; |
| 182 | run->copyTo(builder, SkToU32(pos), size, SkVector::Make(0, shiftDown)); |
| 183 | canvas->save(); |
| 184 | if (clippingNeeded) { |
| 185 | canvas->clipRect(clip); |
| 186 | } |
| 187 | canvas->translate(shift, 0); |
| 188 | canvas->drawTextBlob(builder.make(), 0, 0, paint); |
| 189 | canvas->restore(); |
| 190 | return true; |
| 191 | }); |
| 192 | } |
| 193 | |
| 194 | SkScalar TextLine::paintBackground(SkCanvas* canvas, SkSpan<const char> text, |
| 195 | const TextStyle& style, SkScalar offsetX) const { |
| 196 | TRACE_EVENT0("skia", TRACE_FUNC); |
| 197 | return this->iterateThroughRuns(text, offsetX, false, |
| 198 | [canvas, style](Run* run, int32_t pos, size_t size, SkRect clip, |
| 199 | SkScalar shift, bool clippingNeeded) { |
| 200 | if (style.hasBackground()) { |
| 201 | canvas->drawRect(clip, style.getBackground()); |
| 202 | } |
| 203 | return true; |
| 204 | }); |
| 205 | } |
| 206 | |
| 207 | SkScalar TextLine::paintShadow(SkCanvas* canvas, SkSpan<const char> text, const TextStyle& style, |
| 208 | SkScalar offsetX) const { |
| 209 | TRACE_EVENT0("skia", TRACE_FUNC); |
| 210 | if (style.getShadowNumber() == 0) { |
| 211 | // Still need to calculate text advance |
| 212 | return iterateThroughRuns( |
| 213 | text, offsetX, false, |
| 214 | [](Run*, int32_t, size_t, SkRect, SkScalar, bool) { return true; }); |
| 215 | } |
| 216 | |
| 217 | SkScalar result = 0; |
| 218 | for (TextShadow shadow : style.getShadows()) { |
| 219 | if (!shadow.hasShadow()) continue; |
| 220 | |
| 221 | SkPaint paint; |
| 222 | paint.setColor(shadow.fColor); |
| 223 | if (shadow.fBlurRadius != 0.0) { |
| 224 | auto filter = SkMaskFilter::MakeBlur(kNormal_SkBlurStyle, |
| 225 | SkDoubleToScalar(shadow.fBlurRadius), false); |
| 226 | paint.setMaskFilter(filter); |
| 227 | } |
| 228 | |
| 229 | auto shiftDown = this->baseline(); |
| 230 | result = this->iterateThroughRuns( |
| 231 | text, offsetX, false, |
| 232 | [canvas, shadow, paint, shiftDown](Run* run, size_t pos, size_t size, SkRect clip, |
| 233 | SkScalar shift, bool clippingNeeded) { |
| 234 | SkTextBlobBuilder builder; |
| 235 | run->copyTo(builder, pos, size, SkVector::Make(0, shiftDown)); |
| 236 | canvas->save(); |
| 237 | clip.offset(shadow.fOffset); |
| 238 | if (clippingNeeded) { |
| 239 | canvas->clipRect(clip); |
| 240 | } |
| 241 | canvas->translate(shift, 0); |
| 242 | canvas->drawTextBlob(builder.make(), shadow.fOffset.x(), shadow.fOffset.y(), |
| 243 | paint); |
| 244 | canvas->restore(); |
| 245 | return true; |
| 246 | }); |
| 247 | } |
| 248 | |
| 249 | return result; |
| 250 | } |
| 251 | |
| 252 | SkScalar TextLine::paintDecorations(SkCanvas* canvas, SkSpan<const char> text, |
| 253 | const TextStyle& style, SkScalar offsetX) const { |
| 254 | TRACE_EVENT0("skia", TRACE_FUNC); |
| 255 | return this->iterateThroughRuns( |
| 256 | text, offsetX, false, |
| 257 | [this, canvas, style](Run* run, int32_t pos, size_t size, SkRect clip, SkScalar shift, |
| 258 | bool clippingNeeded) { |
| 259 | if (style.getDecoration() == TextDecoration::kNoDecoration) { |
| 260 | return true; |
| 261 | } |
| 262 | |
| 263 | for (auto decoration : AllTextDecorations) { |
| 264 | if (style.getDecoration() && decoration == 0) { |
| 265 | continue; |
| 266 | } |
| 267 | |
| 268 | SkScalar thickness = style.getDecorationThicknessMultiplier(); |
| 269 | // |
| 270 | SkScalar position = 0; |
| 271 | switch (style.getDecoration()) { |
| 272 | case TextDecoration::kUnderline: |
| 273 | position = -run->ascent() + thickness; |
| 274 | break; |
| 275 | case TextDecoration::kOverline: |
| 276 | position = 0; |
| 277 | break; |
| 278 | case TextDecoration::kLineThrough: { |
| 279 | position = (run->descent() - run->ascent() - thickness) / 2; |
| 280 | break; |
| 281 | } |
| 282 | default: |
| 283 | // TODO: can we actually get here? |
| 284 | break; |
| 285 | } |
| 286 | |
| 287 | auto width = clip.width(); |
| 288 | SkScalar x = clip.left(); |
| 289 | SkScalar y = clip.top() + position; |
| 290 | |
| 291 | // Decoration paint (for now) and/or path |
| 292 | SkPaint paint; |
| 293 | SkPath path; |
| 294 | this->computeDecorationPaint(paint, clip, style, path); |
| 295 | paint.setStrokeWidth(thickness); |
| 296 | |
| 297 | switch (style.getDecorationStyle()) { |
| 298 | case TextDecorationStyle::kWavy: |
| 299 | path.offset(x, y); |
| 300 | canvas->drawPath(path, paint); |
| 301 | break; |
| 302 | case TextDecorationStyle::kDouble: { |
| 303 | canvas->drawLine(x, y, x + width, y, paint); |
| 304 | SkScalar bottom = y + thickness * 2; |
| 305 | canvas->drawLine(x, bottom, x + width, bottom, paint); |
| 306 | break; |
| 307 | } |
| 308 | case TextDecorationStyle::kDashed: |
| 309 | case TextDecorationStyle::kDotted: |
| 310 | case TextDecorationStyle::kSolid: |
| 311 | canvas->drawLine(x, y, x + width, y, paint); |
| 312 | break; |
| 313 | default: |
| 314 | break; |
| 315 | } |
| 316 | } |
| 317 | |
| 318 | return true; |
| 319 | }); |
| 320 | } |
| 321 | |
| 322 | void TextLine::computeDecorationPaint(SkPaint& paint, |
| 323 | SkRect clip, |
| 324 | const TextStyle& style, |
| 325 | SkPath& path) const { |
| 326 | paint.setStyle(SkPaint::kStroke_Style); |
| 327 | if (style.getDecorationColor() == SK_ColorTRANSPARENT) { |
| 328 | paint.setColor(style.getColor()); |
| 329 | } else { |
| 330 | paint.setColor(style.getDecorationColor()); |
| 331 | } |
| 332 | |
| 333 | SkScalar scaleFactor = style.getFontSize() / 14.f; |
| 334 | |
| 335 | switch (style.getDecorationStyle()) { |
| 336 | case TextDecorationStyle::kSolid: |
| 337 | break; |
| 338 | |
| 339 | case TextDecorationStyle::kDouble: |
| 340 | break; |
| 341 | |
| 342 | // Note: the intervals are scaled by the thickness of the line, so it is |
| 343 | // possible to change spacing by changing the decoration_thickness |
| 344 | // property of TextStyle. |
| 345 | case TextDecorationStyle::kDotted: { |
| 346 | const SkScalar intervals[] = {1.0f * scaleFactor, 1.5f * scaleFactor, |
| 347 | 1.0f * scaleFactor, 1.5f * scaleFactor}; |
| 348 | size_t count = sizeof(intervals) / sizeof(intervals[0]); |
| 349 | paint.setPathEffect(SkPathEffect::MakeCompose( |
| 350 | SkDashPathEffect::Make(intervals, (int32_t)count, 0.0f), |
| 351 | SkDiscretePathEffect::Make(0, 0))); |
| 352 | break; |
| 353 | } |
| 354 | // Note: the intervals are scaled by the thickness of the line, so it is |
| 355 | // possible to change spacing by changing the decoration_thickness |
| 356 | // property of TextStyle. |
| 357 | case TextDecorationStyle::kDashed: { |
| 358 | const SkScalar intervals[] = {4.0f * scaleFactor, 2.0f * scaleFactor, |
| 359 | 4.0f * scaleFactor, 2.0f * scaleFactor}; |
| 360 | size_t count = sizeof(intervals) / sizeof(intervals[0]); |
| 361 | paint.setPathEffect(SkPathEffect::MakeCompose( |
| 362 | SkDashPathEffect::Make(intervals, (int32_t)count, 0.0f), |
| 363 | SkDiscretePathEffect::Make(0, 0))); |
| 364 | break; |
| 365 | } |
| 366 | case TextDecorationStyle::kWavy: { |
| 367 | int wave_count = 0; |
| 368 | SkScalar x_start = 0; |
| 369 | SkScalar wavelength = scaleFactor * style.getDecorationThicknessMultiplier(); |
| 370 | auto width = clip.width(); |
| 371 | path.moveTo(0, 0); |
| 372 | while (x_start + wavelength * 2 < width) { |
| 373 | path.rQuadTo(wavelength, |
| 374 | wave_count % 2 != 0 ? wavelength : -wavelength, |
| 375 | wavelength * 2, |
| 376 | 0); |
| 377 | x_start += wavelength * 2; |
| 378 | ++wave_count; |
| 379 | } |
| 380 | break; |
| 381 | } |
| 382 | } |
| 383 | } |
| 384 | |
| 385 | void TextLine::justify(SkScalar maxWidth) { |
| 386 | TRACE_EVENT0("skia", TRACE_FUNC); |
| 387 | // Count words and the extra spaces to spread across the line |
| 388 | // TODO: do it at the line breaking?.. |
| 389 | size_t whitespacePatches = 0; |
| 390 | SkScalar textLen = 0; |
| 391 | bool whitespacePatch = false; |
| 392 | this->iterateThroughClustersInGlyphsOrder( |
| 393 | false, [&whitespacePatches, &textLen, &whitespacePatch](const Cluster* cluster) { |
| 394 | if (cluster->isWhitespaces()) { |
| 395 | if (!whitespacePatch) { |
| 396 | whitespacePatch = true; |
| 397 | ++whitespacePatches; |
| 398 | } |
| 399 | } else { |
| 400 | whitespacePatch = false; |
| 401 | } |
| 402 | textLen += cluster->width(); |
| 403 | return true; |
| 404 | }); |
| 405 | |
| 406 | if (whitespacePatches == 0) { |
| 407 | this->fShift = 0; |
| 408 | return; |
| 409 | } |
| 410 | |
| 411 | SkScalar step = (maxWidth - textLen) / whitespacePatches; |
| 412 | SkScalar shift = 0; |
| 413 | |
| 414 | // Spread the extra whitespaces |
| 415 | whitespacePatch = false; |
| 416 | this->iterateThroughClustersInGlyphsOrder(false, [&](const Cluster* cluster) { |
| 417 | if (cluster->isWhitespaces()) { |
| 418 | if (!whitespacePatch) { |
| 419 | shift += step; |
| 420 | whitespacePatch = true; |
| 421 | --whitespacePatches; |
| 422 | } |
| 423 | } else { |
| 424 | whitespacePatch = false; |
| 425 | } |
| 426 | cluster->shift(shift); |
| 427 | return true; |
| 428 | }); |
| 429 | |
| 430 | SkAssertResult(SkScalarNearlyEqual(shift, maxWidth - textLen)); |
| 431 | SkASSERT(whitespacePatches == 0); |
| 432 | this->fShift = 0; |
| 433 | this->fAdvance.fX = maxWidth; |
| 434 | } |
| 435 | |
| 436 | void TextLine::createEllipsis(SkScalar maxWidth, const SkString& ellipsis, bool) { |
| 437 | TRACE_EVENT0("skia", TRACE_FUNC); |
| 438 | // Replace some clusters with the ellipsis |
| 439 | // Go through the clusters in the reverse logical order |
| 440 | // taking off cluster by cluster until the ellipsis fits |
| 441 | SkScalar width = fAdvance.fX; |
| 442 | iterateThroughClustersInGlyphsOrder( |
| 443 | true, [this, &width, ellipsis, maxWidth](const Cluster* cluster) { |
| 444 | if (cluster->isWhitespaces()) { |
| 445 | width -= cluster->width(); |
| 446 | return true; |
| 447 | } |
| 448 | |
| 449 | // Shape the ellipsis |
| 450 | Run* cached = fEllipsisCache.find(cluster->run()->font()); |
| 451 | if (cached == nullptr) { |
| 452 | cached = shapeEllipsis(ellipsis, cluster->run()); |
| 453 | } |
| 454 | fEllipsis = skstd::make_unique<Run>(*cached); |
| 455 | |
| 456 | // See if it fits |
| 457 | if (width + fEllipsis->advance().fX > maxWidth) { |
| 458 | width -= cluster->width(); |
| 459 | // Continue if it's not |
| 460 | return true; |
| 461 | } |
| 462 | |
| 463 | fEllipsis->shift(width, 0); |
| 464 | fAdvance.fX = width; |
| 465 | return false; |
| 466 | }); |
| 467 | } |
| 468 | |
| 469 | Run* TextLine::shapeEllipsis(const SkString& ellipsis, Run* run) { |
| 470 | TRACE_EVENT0("skia", TRACE_FUNC); |
| 471 | class ShapeHandler final : public SkShaper::RunHandler { |
| 472 | public: |
| 473 | explicit ShapeHandler(SkScalar lineHeight) : fRun(nullptr), fLineHeight(lineHeight) {} |
| 474 | Run* run() { return fRun; } |
| 475 | |
| 476 | private: |
| 477 | void beginLine() override {} |
| 478 | |
| 479 | void runInfo(const RunInfo&) override {} |
| 480 | |
| 481 | void commitRunInfo() override {} |
| 482 | |
| 483 | Buffer runBuffer(const RunInfo& info) override { |
| 484 | fRun = fEllipsisCache.set(info.fFont, |
| 485 | Run(SkSpan<const char>(), info, fLineHeight, 0, 0)); |
| 486 | return fRun->newRunBuffer(); |
| 487 | } |
| 488 | |
| 489 | void commitRunBuffer(const RunInfo& info) override { |
| 490 | fRun->fAdvance.fX = info.fAdvance.fX; |
| 491 | fRun->fAdvance.fY = fRun->descent() - fRun->ascent(); |
| 492 | } |
| 493 | |
| 494 | void commitLine() override {} |
| 495 | |
| 496 | Run* fRun; |
| 497 | SkScalar fLineHeight; |
| 498 | }; |
| 499 | |
| 500 | ShapeHandler handler(run->lineHeight()); |
| 501 | std::unique_ptr<SkShaper> shaper = SkShaper::MakeShapeDontWrapOrReorder(); |
| 502 | SkASSERT_RELEASE(shaper != nullptr); |
| 503 | shaper->shape(ellipsis.c_str(), ellipsis.size(), run->font(), true, |
| 504 | std::numeric_limits<SkScalar>::max(), &handler); |
| 505 | handler.run()->fText = SkSpan<const char>(ellipsis.c_str(), ellipsis.size()); |
| 506 | return handler.run(); |
| 507 | } |
| 508 | |
| 509 | SkRect TextLine::measureTextInsideOneRun( |
| 510 | SkSpan<const char> text, Run* run, size_t& pos, size_t& size, bool& clippingNeeded) const { |
| 511 | TRACE_EVENT0("skia", TRACE_FUNC); |
| 512 | SkASSERT(intersectedSize(run->text(), text) >= 0); |
| 513 | |
| 514 | // Find [start:end] clusters for the text |
| 515 | bool found; |
| 516 | Cluster* start; |
| 517 | Cluster* end; |
| 518 | std::tie(found, start, end) = run->findLimitingClusters(text); |
| 519 | if (!found) { |
| 520 | SkASSERT(text.empty()); |
| 521 | return SkRect::MakeEmpty(); |
| 522 | } |
| 523 | |
| 524 | pos = start->startPos(); |
| 525 | size = end->endPos() - start->startPos(); |
| 526 | |
| 527 | // Calculate the clipping rectangle for the text with cluster edges |
| 528 | // There are 2 cases: |
| 529 | // EOL (when we expect the last cluster clipped without any spaces) |
| 530 | // Anything else (when we want the cluster width contain all the spaces - |
| 531 | // coming from letter spacing or word spacing or justification) |
| 532 | bool needsClipping = (run->leftToRight() ? end : start) == clusters().end() - 1; |
| 533 | SkRect clip = |
| 534 | SkRect::MakeXYWH(run->positionX(start->startPos()) - run->positionX(0), |
| 535 | sizes().runTop(run), |
| 536 | run->calculateWidth(start->startPos(), end->endPos(), needsClipping), |
| 537 | run->calculateHeight()); |
| 538 | |
| 539 | // Correct the width in case the text edges don't match clusters |
| 540 | // TODO: This is where we get smart about selecting a part of a cluster |
| 541 | // by shaping each grapheme separately and then use the result sizes |
| 542 | // to calculate the proportions |
| 543 | auto leftCorrection = start->sizeToChar(text.begin()); |
| 544 | auto rightCorrection = end->sizeFromChar(text.end() - 1); |
| 545 | clip.fLeft += leftCorrection; |
| 546 | clip.fRight -= rightCorrection; |
| 547 | clippingNeeded = leftCorrection != 0 || rightCorrection != 0; |
| 548 | |
| 549 | // SkDebugf("measureTextInsideOneRun: '%s'[%d:%d]\n", text.begin(), pos, pos + size); |
| 550 | |
| 551 | return clip; |
| 552 | } |
| 553 | |
| 554 | void TextLine::iterateThroughClustersInGlyphsOrder(bool reverse, |
| 555 | const ClustersVisitor& visitor) const { |
| 556 | TRACE_EVENT0("skia", TRACE_FUNC); |
| 557 | for (size_t r = 0; r != fLogical.size(); ++r) { |
| 558 | auto& run = fLogical[reverse ? fLogical.size() - r - 1 : r]; |
| 559 | // Walk through the clusters in the logical order (or reverse) |
| 560 | auto normalOrder = run->leftToRight() != reverse; |
| 561 | auto start = normalOrder ? run->clusters().begin() : run->clusters().end() - 1; |
| 562 | auto end = normalOrder ? run->clusters().end() : run->clusters().begin() - 1; |
| 563 | for (auto cluster = start; cluster != end; normalOrder ? ++cluster : --cluster) { |
| 564 | if (!this->contains(cluster)) { |
| 565 | continue; |
| 566 | } |
| 567 | if (!visitor(cluster)) { |
| 568 | return; |
| 569 | } |
| 570 | } |
| 571 | } |
| 572 | } |
| 573 | |
| 574 | // Walk through the runs in the logical order |
| 575 | SkScalar TextLine::iterateThroughRuns(SkSpan<const char> text, |
| 576 | SkScalar runOffset, |
| 577 | bool includeEmptyText, |
| 578 | const RunVisitor& visitor) const { |
| 579 | TRACE_EVENT0("skia", TRACE_FUNC); |
| 580 | |
| 581 | SkScalar width = 0; |
| 582 | for (auto& run : fLogical) { |
| 583 | // Only skip the text if it does not even touch the run |
| 584 | if (intersectedSize(run->text(), text) < 0) { |
| 585 | continue; |
| 586 | } |
| 587 | |
| 588 | SkSpan<const char> intersect = intersected(run->text(), text); |
| 589 | if (run->text().empty() || intersect.empty()) { |
| 590 | continue; |
| 591 | } |
| 592 | |
| 593 | size_t pos; |
| 594 | size_t size; |
| 595 | bool clippingNeeded; |
| 596 | SkRect clip = this->measureTextInsideOneRun(intersect, run, pos, size, clippingNeeded); |
| 597 | if (clip.height() == 0) { |
| 598 | continue; |
| 599 | } |
| 600 | |
| 601 | auto shift = runOffset - clip.fLeft; |
| 602 | clip.offset(shift, 0); |
| 603 | if (clip.fRight > fAdvance.fX) { |
| 604 | clip.fRight = fAdvance.fX; |
| 605 | clippingNeeded = true; // Correct the clip in case there was an ellipsis |
| 606 | } else if (run == fLogical.back() && this->ellipsis() != nullptr) { |
| 607 | clippingNeeded = true; // To avoid trouble |
| 608 | } |
| 609 | |
| 610 | if (!visitor(run, pos, size, clip, shift - run->positionX(0), clippingNeeded)) { |
| 611 | return width; |
| 612 | } |
| 613 | |
| 614 | width += clip.width(); |
| 615 | runOffset += clip.width(); |
| 616 | } |
| 617 | |
| 618 | if (this->ellipsis() != nullptr) { |
| 619 | auto ellipsis = this->ellipsis(); |
| 620 | if (!visitor(ellipsis, 0, ellipsis->size(), ellipsis->clip(), ellipsis->clip().fLeft, |
| 621 | false)) { |
| 622 | return width; |
| 623 | } |
| 624 | width += ellipsis->clip().width(); |
| 625 | } |
| 626 | |
| 627 | return width; |
| 628 | } |
| 629 | |
| 630 | void TextLine::iterateThroughStylesInTextOrder(StyleType styleType, |
| 631 | const StyleVisitor& visitor) const { |
| 632 | TRACE_EVENT0("skia", TRACE_FUNC); |
| 633 | const char* start = nullptr; |
| 634 | size_t size = 0; |
| 635 | TextStyle prevStyle; |
| 636 | |
| 637 | SkScalar offsetX = 0; |
| 638 | for (auto& block : fBlocks) { |
| 639 | auto intersect = intersected(block.text(), this->trimmedText()); |
| 640 | if (intersect.empty()) { |
| 641 | if (start == nullptr) { |
| 642 | // This style is not applicable to the line |
| 643 | continue; |
| 644 | } else { |
| 645 | // We have found all the good styles already |
| 646 | break; |
| 647 | } |
| 648 | } |
| 649 | |
| 650 | auto style = block.style(); |
| 651 | if (start != nullptr && style.matchOneAttribute(styleType, prevStyle)) { |
| 652 | size += intersect.size(); |
| 653 | continue; |
| 654 | } else if (size == 0) { |
| 655 | // First time only |
| 656 | prevStyle = style; |
| 657 | size = intersect.size(); |
| 658 | start = intersect.begin(); |
| 659 | continue; |
| 660 | } |
| 661 | |
| 662 | auto width = visitor(SkSpan<const char>(start, size), prevStyle, offsetX); |
| 663 | offsetX += width; |
| 664 | |
| 665 | // Start all over again |
| 666 | prevStyle = style; |
| 667 | start = intersect.begin(); |
| 668 | size = intersect.size(); |
| 669 | } |
| 670 | |
| 671 | // The very last style |
| 672 | auto width = visitor(SkSpan<const char>(start, size), prevStyle, offsetX); |
| 673 | offsetX += width; |
| 674 | |
| 675 | // This is a very important assert! |
| 676 | // It asserts that 2 different ways of calculation come with the same results |
| 677 | if (!SkScalarNearlyEqual(offsetX, this->width())) { |
| 678 | SkDebugf("ASSERT: %f != %f\n", offsetX, this->width()); |
| 679 | } |
| 680 | SkASSERT(SkScalarNearlyEqual(offsetX, this->width())); |
| 681 | } |
| 682 | } // namespace textlayout |
| 683 | } // namespace skia |