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