1 // Copyright 2019 Google LLC.
2
3 #include "include/core/SkBlurTypes.h"
4 #include "include/core/SkFont.h"
5 #include "include/core/SkFontMetrics.h"
6 #include "include/core/SkMaskFilter.h"
7 #include "include/core/SkPaint.h"
8 #include "include/core/SkSpan.h"
9 #include "include/core/SkString.h"
10 #include "include/core/SkTextBlob.h"
11 #include "include/core/SkTypes.h"
12 #include "include/private/SkTemplates.h"
13 #include "include/private/SkTo.h"
14 #include "modules/skparagraph/include/DartTypes.h"
15 #include "modules/skparagraph/include/Metrics.h"
16 #include "modules/skparagraph/include/ParagraphPainter.h"
17 #include "modules/skparagraph/include/ParagraphStyle.h"
18 #include "modules/skparagraph/include/TextShadow.h"
19 #include "modules/skparagraph/include/TextStyle.h"
20 #include "modules/skparagraph/src/Decorations.h"
21 #include "modules/skparagraph/src/ParagraphImpl.h"
22 #include "modules/skparagraph/src/ParagraphPainterImpl.h"
23 #include "modules/skparagraph/src/TextLine.h"
24 #include "modules/skshaper/include/SkShaper.h"
25
26 #include <algorithm>
27 #include <iterator>
28 #include <limits>
29 #include <map>
30 #include <memory>
31 #include <tuple>
32 #include <type_traits>
33 #include <utility>
34
35 namespace skia {
36 namespace textlayout {
37 #define MAX_INT_VALUE 0x7FFFFFFF
38
39 namespace {
40
41 // TODO: deal with all the intersection functionality
intersected(const TextRange & a,const TextRange & b)42 TextRange intersected(const TextRange& a, const TextRange& b) {
43 if (a.start == b.start && a.end == b.end) return a;
44 auto begin = std::max(a.start, b.start);
45 auto end = std::min(a.end, b.end);
46 return end >= begin ? TextRange(begin, end) : EMPTY_TEXT;
47 }
48
littleRound(SkScalar a)49 SkScalar littleRound(SkScalar a) {
50 // This rounding is done to match Flutter tests. Must be removed..
51 return SkScalarRoundToScalar(a * 100.0)/100.0;
52 }
53
operator *(const TextRange & a,const TextRange & b)54 TextRange operator*(const TextRange& a, const TextRange& b) {
55 if (a.start == b.start && a.end == b.end) return a;
56 auto begin = std::max(a.start, b.start);
57 auto end = std::min(a.end, b.end);
58 return end > begin ? TextRange(begin, end) : EMPTY_TEXT;
59 }
60
compareRound(SkScalar a,SkScalar b,bool applyRoundingHack)61 int compareRound(SkScalar a, SkScalar b, bool applyRoundingHack) {
62 // There is a rounding error that gets bigger when maxWidth gets bigger
63 // VERY long zalgo text (> 100000) on a VERY long line (> 10000)
64 // Canvas scaling affects it
65 // Letter spacing affects it
66 // It has to be relative to be useful
67 auto base = std::max(SkScalarAbs(a), SkScalarAbs(b));
68 auto diff = SkScalarAbs(a - b);
69 if (nearlyZero(base) || diff / base < 0.001f) {
70 return 0;
71 }
72
73 auto ra = a;
74 auto rb = b;
75
76 if (applyRoundingHack) {
77 ra = littleRound(a);
78 rb = littleRound(b);
79 }
80 if (ra < rb) {
81 return -1;
82 } else {
83 return 1;
84 }
85 }
86
87 } // namespace
88
TextLine(ParagraphImpl * owner,SkVector offset,SkVector advance,BlockRange blocks,TextRange textExcludingSpaces,TextRange text,TextRange textIncludingNewlines,ClusterRange clusters,ClusterRange clustersWithGhosts,SkScalar widthWithSpaces,InternalLineMetrics sizes)89 TextLine::TextLine(ParagraphImpl* owner,
90 SkVector offset,
91 SkVector advance,
92 BlockRange blocks,
93 TextRange textExcludingSpaces,
94 TextRange text,
95 TextRange textIncludingNewlines,
96 ClusterRange clusters,
97 ClusterRange clustersWithGhosts,
98 SkScalar widthWithSpaces,
99 InternalLineMetrics sizes)
100 : fOwner(owner)
101 , fBlockRange(blocks)
102 , fTextExcludingSpaces(textExcludingSpaces)
103 , fText(text)
104 , fTextIncludingNewlines(textIncludingNewlines)
105 , fClusterRange(clusters)
106 , fGhostClusterRange(clustersWithGhosts)
107 , fRunsInVisualOrder()
108 , fAdvance(advance)
109 , fOffset(offset)
110 , fShift(0.0)
111 , fWidthWithSpaces(widthWithSpaces)
112 , fEllipsis(nullptr)
113 , fSizes(sizes)
114 , fHasBackground(false)
115 , fHasShadows(false)
116 , fHasDecorations(false)
117 , fAscentStyle(LineMetricStyle::CSS)
118 , fDescentStyle(LineMetricStyle::CSS)
119 , fTextBlobCachePopulated(false) {
120 // Reorder visual runs
121 auto& start = owner->cluster(fGhostClusterRange.start);
122 auto& end = owner->cluster(fGhostClusterRange.end - 1);
123 size_t numRuns = end.runIndex() - start.runIndex() + 1;
124
125 for (BlockIndex index = fBlockRange.start; index < fBlockRange.end; ++index) {
126 auto b = fOwner->styles().begin() + index;
127 if (b->fStyle.hasBackground()) {
128 fHasBackground = true;
129 }
130 if (b->fStyle.getDecorationType() != TextDecoration::kNoDecoration) {
131 fHasDecorations = true;
132 }
133 if (b->fStyle.getShadowNumber() > 0) {
134 fHasShadows = true;
135 }
136 }
137
138 // Get the logical order
139
140 // This is just chosen to catch the common/fast cases. Feel free to tweak.
141 constexpr int kPreallocCount = 4;
142 SkAutoSTArray<kPreallocCount, SkUnicode::BidiLevel> runLevels(numRuns);
143 std::vector<RunIndex> placeholdersInOriginalOrder;
144 size_t runLevelsIndex = 0;
145 // Placeholders must be laid out using the original order in which they were added
146 // in the input. The API does not provide a way to indicate that a placeholder
147 // position was moved due to bidi reordering.
148 for (auto runIndex = start.runIndex(); runIndex <= end.runIndex(); ++runIndex) {
149 auto& run = fOwner->run(runIndex);
150 runLevels[runLevelsIndex++] = run.fBidiLevel;
151 fMaxRunMetrics.add(
152 InternalLineMetrics(run.correctAscent(), run.correctDescent(), run.fFontMetrics.fLeading));
153 if (run.isPlaceholder()) {
154 placeholdersInOriginalOrder.push_back(runIndex);
155 }
156 }
157 SkASSERT(runLevelsIndex == numRuns);
158
159 SkAutoSTArray<kPreallocCount, int32_t> logicalOrder(numRuns);
160
161 // TODO: hide all these logic in SkUnicode?
162 fOwner->getUnicode()->reorderVisual(runLevels.data(), numRuns, logicalOrder.data());
163 auto firstRunIndex = start.runIndex();
164 auto placeholderIter = placeholdersInOriginalOrder.begin();
165 for (auto index : logicalOrder) {
166 auto runIndex = firstRunIndex + index;
167 if (fOwner->run(runIndex).isPlaceholder()) {
168 fRunsInVisualOrder.push_back(*placeholderIter++);
169 } else {
170 fRunsInVisualOrder.push_back(runIndex);
171 }
172 }
173
174 // TODO: This is the fix for flutter. Must be removed...
175 for (auto cluster = &start; cluster <= &end; ++cluster) {
176 if (!cluster->run().isPlaceholder()) {
177 fShift += cluster->getHalfLetterSpacing();
178 break;
179 }
180 }
181 }
182
paint(ParagraphPainter * painter,SkScalar x,SkScalar y)183 void TextLine::paint(ParagraphPainter* painter, SkScalar x, SkScalar y) {
184 prepareRoundRect();
185
186 this->iterateThroughVisualRuns(false,
187 [painter, x, y, this]
188 (const Run* run, SkScalar runOffsetInLine, TextRange textRange, SkScalar* runWidthInLine) {
189 *runWidthInLine = this->iterateThroughSingleRunByStyles(
190 TextAdjustment::GlyphCluster, run, runOffsetInLine, textRange, StyleType::kBackground,
191 [painter, x, y, run, this](TextRange textRange, const TextStyle& style, const ClipContext& context) {
192 if (fHasBackground) {
193 this->paintBackground(painter, x, y, textRange, style, context);
194 }
195 paintRoundRect(painter, x, y, run);
196 });
197 return true;
198 });
199
200 if (fHasShadows) {
201 this->iterateThroughVisualRuns(false,
202 [painter, x, y, this]
203 (const Run* run, SkScalar runOffsetInLine, TextRange textRange, SkScalar* runWidthInLine) {
204 *runWidthInLine = this->iterateThroughSingleRunByStyles(
205 TextAdjustment::GlyphCluster, run, runOffsetInLine, textRange, StyleType::kShadow,
206 [painter, x, y, this]
207 (TextRange textRange, const TextStyle& style, const ClipContext& context) {
208 this->paintShadow(painter, x, y, textRange, style, context);
209 });
210 return true;
211 });
212 }
213
214 this->ensureTextBlobCachePopulated();
215
216 for (auto& record : fTextBlobCache) {
217 record.paint(painter, x, y);
218 }
219
220 if (fHasDecorations) {
221 this->iterateThroughVisualRuns(true,
222 [painter, x, y, this]
223 (const Run* run, SkScalar runOffsetInLine, TextRange textRange, SkScalar* runWidthInLine) {
224 *runWidthInLine = this->iterateThroughSingleRunByStyles(
225 TextAdjustment::GlyphCluster, run, runOffsetInLine, textRange, StyleType::kDecorations,
226 [painter, x, y, this]
227 (TextRange textRange, const TextStyle& style, const ClipContext& context) {
228 this->paintDecorations(painter, x, y, textRange, style, context);
229 });
230 return true;
231 });
232 }
233 }
234
hasBackgroundRect(const RoundRectAttr & attr)235 bool TextLine::hasBackgroundRect(const RoundRectAttr& attr) {
236 return attr.roundRectStyle.color != 0 && attr.rect.width() > 0;
237 }
238
computeRoundRect(int & index,int & preIndex,std::vector<Run * > & groupRuns,Run * run)239 void TextLine::computeRoundRect(int& index, int& preIndex, std::vector<Run*>& groupRuns, Run* run) {
240 int runCount = roundRectAttrs.size();
241 if (index >= runCount) {
242 return;
243 }
244
245 bool leftRound = false;
246 bool rightRound = false;
247 if (hasBackgroundRect(roundRectAttrs[index])) {
248 int styleId = roundRectAttrs[index].styleId;
249 // index - 1 is previous index, -1 is the invalid styleId
250 int preStyleId = index == 0 ? -1 : roundRectAttrs[index - 1].styleId;
251 // runCount - 1 is the last run index, index + 1 is next run index, -1 is the invalid styleId
252 int nextStyleId = index == runCount - 1 ? -1 : roundRectAttrs[index + 1].styleId;
253 // index - preIndex > 1 means the left run has no background rect
254 leftRound = (preIndex < 0 || index - preIndex > 1 || preStyleId != styleId);
255 // runCount - 1 is the last run index
256 rightRound = (index == runCount - 1 || !hasBackgroundRect(roundRectAttrs[index + 1]) ||
257 nextStyleId != styleId);
258 preIndex = index;
259 groupRuns.push_back(run);
260 } else if (!groupRuns.empty()) {
261 groupRuns.erase(groupRuns.begin(), groupRuns.end());
262 }
263 if (leftRound && rightRound) {
264 run->setRoundRectType(RoundRectType::ALL);
265 } else if (leftRound) {
266 run->setRoundRectType(RoundRectType::LEFT_ONLY);
267 } else if (rightRound) {
268 run->setRoundRectType(RoundRectType::RIGHT_ONLY);
269 } else {
270 run->setRoundRectType(RoundRectType::NONE);
271 }
272
273 if (rightRound && !groupRuns.empty()) {
274 double maxRoundRectRadius = MAX_INT_VALUE;
275 double minTop = MAX_INT_VALUE;
276 double maxBottom = 0;
277 for (auto &gRun : groupRuns) {
278 RoundRectAttr& attr = roundRectAttrs[gRun->getIndexInLine()];
279 maxRoundRectRadius = std::fmin(std::fmin(attr.rect.width(), attr.rect.height()), maxRoundRectRadius);
280 minTop = std::fmin(minTop, attr.rect.top());
281 maxBottom = std::fmax(maxBottom, attr.rect.bottom());
282 }
283 for (auto &gRun : groupRuns) {
284 gRun->setMaxRoundRectRadius(maxRoundRectRadius);
285 gRun->setTopInGroup(minTop - gRun->offset().y());
286 gRun->setBottomInGroup(maxBottom - gRun->offset().y());
287 }
288 groupRuns.erase(groupRuns.begin(), groupRuns.end());
289 }
290 index++;
291 }
292
prepareRoundRect()293 void TextLine::prepareRoundRect() {
294 roundRectAttrs.clear();
295 this->iterateThroughVisualRuns(false,
296 [this](const Run* run, SkScalar runOffsetInLine, TextRange textRange, SkScalar* runWidthInLine) {
297 *runWidthInLine = this->iterateThroughSingleRunByStyles(
298 TextAdjustment::GlyphCluster, run, runOffsetInLine, textRange, StyleType::kBackground,
299 [run, this](TextRange textRange, const TextStyle& style, const ClipContext& context) {
300 roundRectAttrs.push_back({style.getStyleId(), style.getBackgroundRect(), context.clip});
301 });
302 return true;
303 });
304
305 std::vector<Run*> groupRuns;
306 int index = 0;
307 int preIndex = -1;
308 for (auto& runIndex : fRunsInVisualOrder) {
309 auto run = &this->fOwner->run(runIndex);
310 run->setIndexInLine(static_cast<size_t>(index));
311 computeRoundRect(index, preIndex, groupRuns, run);
312 }
313 }
314
ensureTextBlobCachePopulated()315 void TextLine::ensureTextBlobCachePopulated() {
316 if (fTextBlobCachePopulated) {
317 return;
318 }
319 if (fBlockRange.width() == 1 &&
320 fRunsInVisualOrder.size() == 1 &&
321 fEllipsis == nullptr &&
322 fOwner->run(fRunsInVisualOrder[0]).placeholderStyle() == nullptr) {
323 if (fClusterRange.width() == 0) {
324 return;
325 }
326 // Most common and most simple case
327 const auto& style = fOwner->block(fBlockRange.start).fStyle;
328 const auto& run = fOwner->run(fRunsInVisualOrder[0]);
329 auto clip = SkRect::MakeXYWH(0.0f, this->sizes().runTop(&run, this->fAscentStyle),
330 fAdvance.fX,
331 run.calculateHeight(this->fAscentStyle, this->fDescentStyle));
332
333 auto& start = fOwner->cluster(fClusterRange.start);
334 auto& end = fOwner->cluster(fClusterRange.end - 1);
335 SkASSERT(start.runIndex() == end.runIndex());
336 GlyphRange glyphs;
337 if (run.leftToRight()) {
338 glyphs = GlyphRange(start.startPos(),
339 end.isHardBreak() ? end.startPos() : end.endPos());
340 } else {
341 glyphs = GlyphRange(end.startPos(),
342 start.isHardBreak() ? start.startPos() : start.endPos());
343 }
344 ClipContext context = {/*run=*/&run,
345 /*pos=*/glyphs.start,
346 /*size=*/glyphs.width(),
347 /*fTextShift=*/-run.positionX(glyphs.start), // starting position
348 /*clip=*/clip, // entire line
349 /*fExcludedTrailingSpaces=*/0.0f, // no need for that
350 /*clippingNeeded=*/false}; // no need for that
351 this->buildTextBlob(fTextExcludingSpaces, style, context);
352 } else {
353 this->iterateThroughVisualRuns(false,
354 [this](const Run* run,
355 SkScalar runOffsetInLine,
356 TextRange textRange,
357 SkScalar* runWidthInLine) {
358 if (run->placeholderStyle() != nullptr) {
359 *runWidthInLine = run->advance().fX;
360 return true;
361 }
362 *runWidthInLine = this->iterateThroughSingleRunByStyles(
363 TextAdjustment::GlyphCluster,
364 run,
365 runOffsetInLine,
366 textRange,
367 StyleType::kForeground,
368 [this](TextRange textRange, const TextStyle& style, const ClipContext& context) {
369 this->buildTextBlob(textRange, style, context);
370 });
371 return true;
372 });
373 }
374 fTextBlobCachePopulated = true;
375 }
376
format(TextAlign align,SkScalar maxWidth)377 void TextLine::format(TextAlign align, SkScalar maxWidth) {
378 SkScalar delta = maxWidth - this->width();
379 if (delta <= 0) {
380 return;
381 }
382
383 // We do nothing for left align
384 if (align == TextAlign::kJustify) {
385 if (!this->endsWithHardLineBreak()) {
386 this->justify(maxWidth);
387 } else if (fOwner->paragraphStyle().getTextDirection() == TextDirection::kRtl) {
388 // Justify -> Right align
389 fShift = delta;
390 }
391 } else if (align == TextAlign::kRight) {
392 fShift = delta;
393 } else if (align == TextAlign::kCenter) {
394 fShift = delta / 2;
395 }
396 }
397
scanStyles(StyleType styleType,const RunStyleVisitor & visitor)398 void TextLine::scanStyles(StyleType styleType, const RunStyleVisitor& visitor) {
399 if (this->empty()) {
400 return;
401 }
402
403 this->iterateThroughVisualRuns(
404 false,
405 [this, visitor, styleType](
406 const Run* run, SkScalar runOffset, TextRange textRange, SkScalar* width) {
407 *width = this->iterateThroughSingleRunByStyles(
408 TextAdjustment::GlyphCluster,
409 run,
410 runOffset,
411 textRange,
412 styleType,
413 [visitor](TextRange textRange,
414 const TextStyle& style,
415 const ClipContext& context) {
416 visitor(textRange, style, context);
417 });
418 return true;
419 });
420 }
421
extendHeight(const ClipContext & context) const422 SkRect TextLine::extendHeight(const ClipContext& context) const {
423 SkRect result = context.clip;
424 result.fBottom += std::max(this->fMaxRunMetrics.height() - this->height(), 0.0f);
425 return result;
426 }
427
buildTextBlob(TextRange textRange,const TextStyle & style,const ClipContext & context)428 void TextLine::buildTextBlob(TextRange textRange, const TextStyle& style, const ClipContext& context) {
429 if (context.run->placeholderStyle() != nullptr) {
430 return;
431 }
432
433 fTextBlobCache.emplace_back();
434 TextBlobRecord& record = fTextBlobCache.back();
435
436 if (style.hasForeground()) {
437 record.fPaint = style.getForegroundPaintOrID();
438 } else {
439 std::get<SkPaint>(record.fPaint).setColor(style.getColor());
440 }
441 record.fVisitor_Run = context.run;
442 record.fVisitor_Pos = context.pos;
443
444 // TODO: This is the change for flutter, must be removed later
445 SkTextBlobBuilder builder;
446 context.run->copyTo(builder, SkToU32(context.pos), context.size);
447 record.fClippingNeeded = context.clippingNeeded;
448 if (context.clippingNeeded) {
449 record.fClipRect = extendHeight(context).makeOffset(this->offset());
450 } else {
451 record.fClipRect = context.clip.makeOffset(this->offset());
452 }
453
454 SkASSERT(nearlyEqual(context.run->baselineShift(), style.getBaselineShift()));
455 SkScalar correctedBaseline = SkScalarFloorToScalar(this->baseline() + style.getBaselineShift() + 0.5);
456 record.fBlob = builder.make();
457 if (record.fBlob != nullptr) {
458 record.fBounds.joinPossiblyEmptyRect(record.fBlob->bounds());
459 }
460
461 record.fOffset = SkPoint::Make(this->offset().fX + context.fTextShift,
462 this->offset().fY + correctedBaseline);
463 }
464
paint(ParagraphPainter * painter,SkScalar x,SkScalar y)465 void TextLine::TextBlobRecord::paint(ParagraphPainter* painter, SkScalar x, SkScalar y) {
466 if (fClippingNeeded) {
467 painter->save();
468 painter->clipRect(fClipRect.makeOffset(x, y));
469 }
470 painter->drawTextBlob(fBlob, x + fOffset.x(), y + fOffset.y(), fPaint);
471 if (fClippingNeeded) {
472 painter->restore();
473 }
474 }
475
paintBackground(ParagraphPainter * painter,SkScalar x,SkScalar y,TextRange textRange,const TextStyle & style,const ClipContext & context) const476 void TextLine::paintBackground(ParagraphPainter* painter,
477 SkScalar x,
478 SkScalar y,
479 TextRange textRange,
480 const TextStyle& style,
481 const ClipContext& context) const {
482 if (style.hasBackground()) {
483 painter->drawRect(context.clip.makeOffset(this->offset() + SkPoint::Make(x, y)),
484 style.getBackgroundPaintOrID());
485 }
486 }
487
paintRoundRect(ParagraphPainter * painter,SkScalar x,SkScalar y,const Run * run) const488 void TextLine::paintRoundRect(ParagraphPainter* painter, SkScalar x, SkScalar y, const Run* run) const {
489 size_t index = run->getIndexInLine();
490 if (index >= roundRectAttrs.size()) {
491 return;
492 }
493
494 const RoundRectAttr& attr = roundRectAttrs[index];
495 if (attr.roundRectStyle.color == 0) {
496 return;
497 }
498
499 SkScalar ltRadius = 0.0f;
500 SkScalar rtRadius = 0.0f;
501 SkScalar rbRadius = 0.0f;
502 SkScalar lbRadius = 0.0f;
503 RoundRectType rType = run->getRoundRectType();
504 if (rType == RoundRectType::ALL || rType == RoundRectType::LEFT_ONLY) {
505 ltRadius = std::fmin(attr.roundRectStyle.leftTopRadius, run->getMaxRoundRectRadius());
506 lbRadius = std::fmin(attr.roundRectStyle.leftBottomRadius, run->getMaxRoundRectRadius());
507 }
508 if (rType == RoundRectType::ALL || rType == RoundRectType::RIGHT_ONLY) {
509 rtRadius = std::fmin(attr.roundRectStyle.rightTopRadius, run->getMaxRoundRectRadius());
510 rbRadius = std::fmin(attr.roundRectStyle.rightBottomRadius, run->getMaxRoundRectRadius());
511 }
512 const SkVector radii[4] = {{ltRadius, ltRadius}, {rtRadius, rtRadius}, {rbRadius, rbRadius}, {lbRadius, lbRadius}};
513 SkRect skRect(SkRect::MakeLTRB(attr.rect.left(), run->getTopInGroup(), attr.rect.right(),
514 run->getBottomInGroup()));
515 SkRRect skRRect;
516 skRRect.setRectRadii(skRect, radii);
517 skRRect.offset(x + fOffset.x(), y + fOffset.y());
518 painter->drawRRect(skRRect, attr.roundRectStyle.color);
519 }
520
paintShadow(ParagraphPainter * painter,SkScalar x,SkScalar y,TextRange textRange,const TextStyle & style,const ClipContext & context) const521 void TextLine::paintShadow(ParagraphPainter* painter,
522 SkScalar x,
523 SkScalar y,
524 TextRange textRange,
525 const TextStyle& style,
526 const ClipContext& context) const {
527 SkScalar correctedBaseline = SkScalarFloorToScalar(this->baseline() + style.getBaselineShift() + 0.5);
528
529 for (TextShadow shadow : style.getShadows()) {
530 if (!shadow.hasShadow()) continue;
531
532 SkTextBlobBuilder builder;
533 context.run->copyTo(builder, context.pos, context.size);
534
535 if (context.clippingNeeded) {
536 painter->save();
537 SkRect clip = extendHeight(context);
538 clip.offset(x, y);
539 clip.offset(this->offset());
540 painter->clipRect(clip);
541 }
542 auto blob = builder.make();
543 painter->drawTextShadow(blob,
544 x + this->offset().fX + shadow.fOffset.x() + context.fTextShift,
545 y + this->offset().fY + shadow.fOffset.y() + correctedBaseline,
546 shadow.fColor,
547 SkDoubleToScalar(shadow.fBlurSigma));
548 if (context.clippingNeeded) {
549 painter->restore();
550 }
551 }
552 }
553
paintDecorations(ParagraphPainter * painter,SkScalar x,SkScalar y,TextRange textRange,const TextStyle & style,const ClipContext & context) const554 void TextLine::paintDecorations(ParagraphPainter* painter, SkScalar x, SkScalar y, TextRange textRange, const TextStyle& style, const ClipContext& context) const {
555 ParagraphPainterAutoRestore ppar(painter);
556 painter->translate(x + this->offset().fX, y + this->offset().fY + style.getBaselineShift());
557 Decorations decorations;
558 SkScalar correctedBaseline = SkScalarFloorToScalar(-this->sizes().rawAscent() + style.getBaselineShift() + 0.5);
559 decorations.paint(painter, style, context, correctedBaseline);
560 }
561
justify(SkScalar maxWidth)562 void TextLine::justify(SkScalar maxWidth) {
563 // Count words and the extra spaces to spread across the line
564 // TODO: do it at the line breaking?..
565 size_t whitespacePatches = 0;
566 SkScalar textLen = 0;
567 bool whitespacePatch = false;
568 this->iterateThroughClustersInGlyphsOrder(false, false,
569 [&whitespacePatches, &textLen, &whitespacePatch](const Cluster* cluster, bool ghost) {
570 if (cluster->isWhitespaceBreak()) {
571 if (!whitespacePatch) {
572 whitespacePatch = true;
573 ++whitespacePatches;
574 }
575 } else {
576 whitespacePatch = false;
577 }
578 textLen += cluster->width();
579 return true;
580 });
581
582 if (whitespacePatches == 0) {
583 return;
584 }
585
586 SkScalar step = (maxWidth - textLen) / whitespacePatches;
587 SkScalar shift = 0;
588
589 // Deal with the ghost spaces
590 auto ghostShift = maxWidth - this->fAdvance.fX;
591 // Spread the extra whitespaces
592 whitespacePatch = false;
593 this->iterateThroughClustersInGlyphsOrder(false, true, [&](const Cluster* cluster, bool ghost) {
594
595 if (ghost) {
596 if (cluster->run().leftToRight()) {
597 this->shiftCluster(cluster, ghostShift, ghostShift);
598 }
599 return true;
600 }
601
602 auto prevShift = shift;
603 if (cluster->isWhitespaceBreak()) {
604 if (!whitespacePatch) {
605 shift += step;
606 whitespacePatch = true;
607 --whitespacePatches;
608 }
609 } else {
610 whitespacePatch = false;
611 }
612 shiftCluster(cluster, shift, prevShift);
613 return true;
614 });
615
616 SkAssertResult(nearlyEqual(shift, maxWidth - textLen));
617 SkASSERT(whitespacePatches == 0);
618
619 this->fWidthWithSpaces += ghostShift;
620 this->fAdvance.fX = maxWidth;
621 }
622
shiftCluster(const Cluster * cluster,SkScalar shift,SkScalar prevShift)623 void TextLine::shiftCluster(const Cluster* cluster, SkScalar shift, SkScalar prevShift) {
624
625 auto& run = cluster->run();
626 auto start = cluster->startPos();
627 auto end = cluster->endPos();
628
629 if (end == run.size()) {
630 // Set the same shift for the fake last glyph (to avoid all extra checks)
631 ++end;
632 }
633
634 if (run.fJustificationShifts.empty()) {
635 // Do not fill this array until needed
636 run.fJustificationShifts.push_back_n(run.size() + 1, { 0, 0 });
637 }
638
639 for (size_t pos = start; pos < end; ++pos) {
640 run.fJustificationShifts[pos] = { shift, prevShift };
641 }
642 }
643
createEllipsis(SkScalar maxWidth,const SkString & ellipsis,bool ltr,WordBreakType wordBreakType)644 void TextLine::createEllipsis(SkScalar maxWidth, const SkString& ellipsis, bool ltr, WordBreakType wordBreakType) {
645 // Replace some clusters with the ellipsis
646 // Go through the clusters in the reverse logical order
647 // taking off cluster by cluster until the ellipsis fits
648 SkScalar width = fAdvance.fX;
649 RunIndex lastRun = EMPTY_RUN;
650 std::unique_ptr<Run> ellipsisRun;
651 int wordCount = 0;
652 bool inWord = false;
653
654 for (auto clusterIndex = fGhostClusterRange.start; clusterIndex < fGhostClusterRange.end; ++clusterIndex) {
655 auto& cluster = fOwner->cluster(clusterIndex);
656 if (cluster.isWordBreak()) {
657 inWord = false;
658 } else if (!inWord) {
659 ++wordCount;
660 inWord = true;
661 }
662 }
663
664 bool iterForWord = false;
665 for (auto clusterIndex = fGhostClusterRange.end; clusterIndex > fGhostClusterRange.start; --clusterIndex) {
666 auto& cluster = fOwner->cluster(clusterIndex - 1);
667 // Shape the ellipsis if the run has changed
668 if (lastRun != cluster.runIndex()) {
669 ellipsisRun = this->shapeEllipsis(ellipsis, &cluster);
670 if (ellipsisRun->advance().fX > maxWidth) {
671 // Ellipsis is bigger than the entire line; no way we can add it at all
672 // BUT! We can keep scanning in case the next run will give us better results
673 lastRun = EMPTY_RUN;
674 continue;
675 } else {
676 // We may need to continue
677 lastRun = cluster.runIndex();
678 }
679 }
680
681 if (!cluster.isWordBreak()) {
682 inWord = true;
683 } else if (inWord) {
684 --wordCount;
685 inWord = false;
686 }
687 // See if it fits
688 if (width + ellipsisRun->advance().fX > maxWidth) {
689 width -= cluster.width();
690 // Continue if the ellipsis does not fit
691 iterForWord = (wordCount != 1 && wordBreakType != WordBreakType::BREAK_ALL && !cluster.isWordBreak());
692 continue;
693 }
694
695 if (iterForWord && !cluster.isWordBreak()) {
696 width -= cluster.width();
697 continue;
698 }
699
700 // We found enough room for the ellipsis
701 fAdvance.fX = width;
702 fEllipsis = std::move(ellipsisRun);
703 fEllipsis->setOwner(fOwner);
704
705 // Let's update the line
706 fClusterRange.end = clusterIndex;
707 fGhostClusterRange.end = fClusterRange.end;
708 fEllipsis->fClusterStart = cluster.textRange().start;
709 fText.end = cluster.textRange().end;
710 fTextIncludingNewlines.end = cluster.textRange().end;
711 fTextExcludingSpaces.end = cluster.textRange().end;
712 break;
713 }
714
715 if (!fEllipsis) {
716 // Weird situation: ellipsis does not fit; no ellipsis then
717 fClusterRange.end = fClusterRange.start;
718 fGhostClusterRange.end = fClusterRange.start;
719 fText.end = fText.start;
720 fTextIncludingNewlines.end = fTextIncludingNewlines.start;
721 fTextExcludingSpaces.end = fTextExcludingSpaces.start;
722 fAdvance.fX = 0;
723 }
724 }
725
createHeadEllipsis(SkScalar maxWidth,const SkString & ellipsis,bool)726 void TextLine::createHeadEllipsis(SkScalar maxWidth, const SkString& ellipsis, bool) {
727 SkScalar width = fAdvance.fX;
728 std::unique_ptr<Run> ellipsisRun;
729 RunIndex lastRun = EMPTY_RUN;
730 for (auto clusterIndex = fGhostClusterRange.start; clusterIndex < fGhostClusterRange.end; ++clusterIndex) {
731 auto& cluster = fOwner->cluster(clusterIndex);
732 // Shape the ellipsis if the run has changed
733 if (lastRun != cluster.runIndex()) {
734 ellipsisRun = this->shapeEllipsis(ellipsis, &cluster);
735 if (ellipsisRun->advance().fX > maxWidth) {
736 lastRun = EMPTY_RUN;
737 continue;
738 } else {
739 // We may need to continue
740 lastRun = cluster.runIndex();
741 }
742 }
743 // See if it fits
744 if (width + ellipsisRun->advance().fX > maxWidth) {
745 width -= cluster.width();
746 // Continue if the ellipsis does not fit
747 continue;
748 }
749 // We found enough room for the ellipsis
750 fAdvance.fX += ellipsisRun->advance().fX;
751 fEllipsis = std::move(ellipsisRun);
752 fEllipsis->setOwner(fOwner);
753 fClusterRange.start = clusterIndex;
754 fClusterRange.end = fGhostClusterRange.end;
755 if (fOwner->paragraphStyle().getTextDirection() == TextDirection::kRtl) {
756 fEllipsis->fClusterStart = fText.start;
757 } else {
758 fEllipsis->fClusterStart = 0;
759 }
760 fText.start = cluster.textRange().start;
761 fTextIncludingNewlines.start = cluster.textRange().start;
762 fTextExcludingSpaces.start = cluster.textRange().start;
763 break;
764 }
765
766 if (!fEllipsis) {
767 // Weird situation: ellipsis does not fit; no ellipsis then
768 fClusterRange.start = fClusterRange.start;
769 fGhostClusterRange.start = fClusterRange.start;
770 fText.end = fText.start;
771 fTextIncludingNewlines.start = fTextIncludingNewlines.start;
772 fTextExcludingSpaces.start = fTextExcludingSpaces.start;
773 fAdvance.fX = 0;
774 }
775 }
776
nextUtf8Unit(const char ** ptr,const char * end)777 static inline SkUnichar nextUtf8Unit(const char** ptr, const char* end) {
778 SkUnichar val = SkUTF::NextUTF8(ptr, end);
779 return val < 0 ? 0xFFFD : val;
780 }
781
shapeEllipsis(const SkString & ellipsis,const Cluster * cluster)782 std::unique_ptr<Run> TextLine::shapeEllipsis(const SkString& ellipsis, const Cluster* cluster) {
783
784 class ShapeHandler final : public SkShaper::RunHandler {
785 public:
786 ShapeHandler(SkScalar lineHeight, bool useHalfLeading, SkScalar baselineShift, const SkString& ellipsis)
787 : fRun(nullptr), fLineHeight(lineHeight), fUseHalfLeading(useHalfLeading), fBaselineShift(baselineShift), fEllipsis(ellipsis) {}
788 std::unique_ptr<Run> run() & { return std::move(fRun); }
789
790 private:
791 void beginLine() override {}
792
793 void runInfo(const RunInfo&) override {}
794
795 void commitRunInfo() override {}
796
797 Buffer runBuffer(const RunInfo& info) override {
798 SkASSERT(!fRun);
799 fRun = std::make_unique<Run>(nullptr, info, 0, fLineHeight, fUseHalfLeading, fBaselineShift, 0, 0);
800 return fRun->newRunBuffer();
801 }
802
803 void commitRunBuffer(const RunInfo& info) override {
804 fRun->fAdvance.fX = info.fAdvance.fX;
805 fRun->fAdvance.fY = fRun->advance().fY;
806 fRun->fPlaceholderIndex = std::numeric_limits<size_t>::max();
807 fRun->fEllipsis = true;
808 }
809
810 void commitLine() override {}
811
812 std::unique_ptr<Run> fRun;
813 SkScalar fLineHeight;
814 bool fUseHalfLeading;
815 SkScalar fBaselineShift;
816 SkString fEllipsis;
817 };
818
819 const Run& run = cluster->run();
820 TextStyle textStyle = fOwner->paragraphStyle().getTextStyle();
821 for (auto i = fBlockRange.start; i < fBlockRange.end; ++i) {
822 auto& block = fOwner->block(i);
823 if (run.leftToRight() && cluster->textRange().end <= block.fRange.end) {
824 textStyle = block.fStyle;
825 break;
826 } else if (!run.leftToRight() && cluster->textRange().start <= block.fRange.end) {
827 textStyle = block.fStyle;
828 break;
829 }
830 }
831
832 auto shaped = [&](sk_sp<SkTypeface> typeface, bool fallback) -> std::unique_ptr<Run> {
833 ShapeHandler handler(run.heightMultiplier(), run.useHalfLeading(), run.baselineShift(), ellipsis);
834 SkFont font(typeface, textStyle.getFontSize());
835 font.setEdging(SkFont::Edging::kAntiAlias);
836 font.setHinting(SkFontHinting::kSlight);
837 font.setSubpixel(true);
838
839 std::unique_ptr<SkShaper> shaper = SkShaper::MakeShapeDontWrapOrReorder(
840 fOwner->getUnicode()->copy(),
841 fallback ? SkFontMgr::RefDefault() : SkFontMgr::RefEmpty());
842 shaper->shape(ellipsis.c_str(),
843 ellipsis.size(),
844 font,
845 true,
846 std::numeric_limits<SkScalar>::max(),
847 &handler);
848 auto ellipsisRun = handler.run();
849 ellipsisRun->fTextRange = TextRange(0, ellipsis.size());
850 ellipsisRun->fOwner = fOwner;
851 return ellipsisRun;
852 };
853
854 // Check all allowed fonts
855 std::vector<sk_sp<SkTypeface>> typefaces = fOwner->fontCollection()->findTypefaces(
856 textStyle.getFontFamilies(), textStyle.getFontStyle(), textStyle.getFontArguments());
857 for (const auto& typeface : typefaces) {
858 auto ellipsisRun = shaped(typeface, false);
859 if (ellipsisRun->isResolved()) {
860 return ellipsisRun;
861 }
862 }
863
864 // Try the fallback
865 if (fOwner->fontCollection()->fontFallbackEnabled()) {
866 const char* ch = ellipsis.c_str();
867 SkUnichar unicode = nextUtf8Unit(&ch, ellipsis.c_str() + ellipsis.size());
868
869 auto typeface = fOwner->fontCollection()->defaultFallback(
870 unicode, textStyle.getFontStyle(), textStyle.getLocale());
871 if (typeface) {
872 auto ellipsisRun = shaped(typeface, true);
873 if (ellipsisRun->isResolved()) {
874 return ellipsisRun;
875 }
876 }
877 }
878
879 // Check the current font
880 auto ellipsisRun = shaped(run.fFont.refTypeface(), false);
881 if (ellipsisRun->isResolved()) {
882 return ellipsisRun;
883 }
884 return ellipsisRun;
885 }
886
measureTextInsideOneRun(TextRange textRange,const Run * run,SkScalar runOffsetInLine,SkScalar textOffsetInRunInLine,bool includeGhostSpaces,TextAdjustment textAdjustment) const887 TextLine::ClipContext TextLine::measureTextInsideOneRun(TextRange textRange,
888 const Run* run,
889 SkScalar runOffsetInLine,
890 SkScalar textOffsetInRunInLine,
891 bool includeGhostSpaces,
892 TextAdjustment textAdjustment) const {
893 ClipContext result = { run, 0, run->size(), 0, SkRect::MakeEmpty(), 0, false };
894
895 if (run->fEllipsis) {
896 // Both ellipsis and placeholders can only be measured as one glyph
897 result.fTextShift = runOffsetInLine;
898 result.clip = SkRect::MakeXYWH(runOffsetInLine,
899 sizes().runTop(run, this->fAscentStyle),
900 run->advance().fX,
901 run->calculateHeight(this->fAscentStyle,this->fDescentStyle));
902 return result;
903 } else if (run->isPlaceholder()) {
904 result.fTextShift = runOffsetInLine;
905 if (SkScalarIsFinite(run->fFontMetrics.fAscent)) {
906 result.clip = SkRect::MakeXYWH(runOffsetInLine,
907 sizes().runTop(run, this->fAscentStyle),
908 run->advance().fX,
909 run->calculateHeight(this->fAscentStyle,this->fDescentStyle));
910 } else {
911 result.clip = SkRect::MakeXYWH(runOffsetInLine, run->fFontMetrics.fAscent, run->advance().fX, 0);
912 }
913 return result;
914 } else if (textRange.empty()) {
915 return result;
916 }
917
918 TextRange originalTextRange(textRange); // We need it for proportional measurement
919 // Find [start:end] clusters for the text
920 while (true) {
921 // Update textRange by cluster edges (shift start up to the edge of the cluster)
922 // TODO: remove this limitation?
923 TextRange updatedTextRange;
924 bool found;
925 std::tie(found, updatedTextRange.start, updatedTextRange.end) =
926 run->findLimitingGlyphClusters(textRange);
927 if (!found) {
928 return result;
929 }
930
931 if ((textAdjustment & TextAdjustment::Grapheme) == 0) {
932 textRange = updatedTextRange;
933 break;
934 }
935
936 // Update text range by grapheme edges (shift start up to the edge of the grapheme)
937 std::tie(found, updatedTextRange.start, updatedTextRange.end) =
938 run->findLimitingGraphemes(updatedTextRange);
939 if (updatedTextRange == textRange) {
940 break;
941 }
942
943 // Some clusters are inside graphemes and we need to adjust them
944 //SkDebugf("Correct range: [%d:%d) -> [%d:%d)\n", textRange.start, textRange.end, startIndex, endIndex);
945 textRange = updatedTextRange;
946
947 // Move the start until it's on the grapheme edge (and glypheme, too)
948 }
949 Cluster* start = &fOwner->cluster(fOwner->clusterIndex(textRange.start));
950 Cluster* end = &fOwner->cluster(fOwner->clusterIndex(textRange.end - (textRange.width() == 0 ? 0 : 1)));
951
952 if (!run->leftToRight()) {
953 std::swap(start, end);
954 }
955 result.pos = start->startPos();
956 result.size = (end->isHardBreak() ? end->startPos() : end->endPos()) - start->startPos();
957 auto textStartInRun = run->positionX(start->startPos());
958 auto textStartInLine = runOffsetInLine + textOffsetInRunInLine;
959 if (!run->leftToRight()) {
960 std::swap(start, end);
961 }
962 /*
963 if (!run->fJustificationShifts.empty()) {
964 SkDebugf("Justification for [%d:%d)\n", textRange.start, textRange.end);
965 for (auto i = result.pos; i < result.pos + result.size; ++i) {
966 auto j = run->fJustificationShifts[i];
967 SkDebugf("[%d] = %f %f\n", i, j.fX, j.fY);
968 }
969 }
970 */
971 // Calculate the clipping rectangle for the text with cluster edges
972 // There are 2 cases:
973 // EOL (when we expect the last cluster clipped without any spaces)
974 // Anything else (when we want the cluster width contain all the spaces -
975 // coming from letter spacing or word spacing or justification)
976 result.clip =
977 SkRect::MakeXYWH(0,
978 sizes().runTop(run, this->fAscentStyle),
979 run->calculateWidth(result.pos, result.pos + result.size, false),
980 run->calculateHeight(this->fAscentStyle,this->fDescentStyle));
981
982 // Correct the width in case the text edges don't match clusters
983 // TODO: This is where we get smart about selecting a part of a cluster
984 // by shaping each grapheme separately and then use the result sizes
985 // to calculate the proportions
986 auto leftCorrection = start->sizeToChar(originalTextRange.start);
987 auto rightCorrection = end->sizeFromChar(originalTextRange.end - 1);
988 /*
989 SkDebugf("[%d: %d) => [%d: %d), @%d, %d: [%f:%f) + [%f:%f) = ", // جَآَهُ
990 originalTextRange.start, originalTextRange.end, textRange.start, textRange.end,
991 result.pos, result.size,
992 result.clip.fLeft, result.clip.fRight, leftCorrection, rightCorrection);
993 */
994 result.clippingNeeded = leftCorrection != 0 || rightCorrection != 0;
995 if (run->leftToRight()) {
996 result.clip.fLeft += leftCorrection;
997 result.clip.fRight -= rightCorrection;
998 textStartInLine -= leftCorrection;
999 } else {
1000 result.clip.fRight -= leftCorrection;
1001 result.clip.fLeft += rightCorrection;
1002 textStartInLine -= rightCorrection;
1003 }
1004
1005 result.clip.offset(textStartInLine, 0);
1006 //SkDebugf("@%f[%f:%f)\n", textStartInLine, result.clip.fLeft, result.clip.fRight);
1007
1008 if (compareRound(result.clip.fRight, fAdvance.fX, fOwner->getApplyRoundingHack()) > 0 && !includeGhostSpaces) {
1009 // There are few cases when we need it.
1010 // The most important one: we measure the text with spaces at the end (or at the beginning in RTL)
1011 // and we should ignore these spaces
1012 if (fOwner->paragraphStyle().getTextDirection() == TextDirection::kLtr) {
1013 // We only use this member for LTR
1014 result.fExcludedTrailingSpaces = std::max(result.clip.fRight - fAdvance.fX, 0.0f);
1015 result.clippingNeeded = true;
1016 result.clip.fRight = fAdvance.fX;
1017 }
1018 }
1019
1020 if (result.clip.width() < 0) {
1021 // Weird situation when glyph offsets move the glyph to the left
1022 // (happens with zalgo texts, for instance)
1023 result.clip.fRight = result.clip.fLeft;
1024 }
1025
1026 // The text must be aligned with the lineOffset
1027 result.fTextShift = textStartInLine - textStartInRun;
1028
1029 return result;
1030 }
1031
iterateThroughClustersInGlyphsOrder(bool reversed,bool includeGhosts,const ClustersVisitor & visitor) const1032 void TextLine::iterateThroughClustersInGlyphsOrder(bool reversed,
1033 bool includeGhosts,
1034 const ClustersVisitor& visitor) const {
1035 // Walk through the clusters in the logical order (or reverse)
1036 SkSpan<const size_t> runs(fRunsInVisualOrder.data(), fRunsInVisualOrder.size());
1037 bool ignore = false;
1038 directional_for_each(runs, !reversed, [&](decltype(runs[0]) r) {
1039 if (ignore) return;
1040 auto run = this->fOwner->run(r);
1041 auto trimmedRange = fClusterRange.intersection(run.clusterRange());
1042 auto trailedRange = fGhostClusterRange.intersection(run.clusterRange());
1043 SkASSERT(trimmedRange.start == trailedRange.start);
1044
1045 auto trailed = fOwner->clusters(trailedRange);
1046 auto trimmed = fOwner->clusters(trimmedRange);
1047 directional_for_each(trailed, reversed != run.leftToRight(), [&](Cluster& cluster) {
1048 if (ignore) return;
1049 bool ghost = &cluster >= trimmed.end();
1050 if (!includeGhosts && ghost) {
1051 return;
1052 }
1053 if (!visitor(&cluster, ghost)) {
1054 ignore = true;
1055 return;
1056 }
1057 });
1058 });
1059 }
1060
iterateThroughSingleRunByStyles(TextAdjustment textAdjustment,const Run * run,SkScalar runOffset,TextRange textRange,StyleType styleType,const RunStyleVisitor & visitor) const1061 SkScalar TextLine::iterateThroughSingleRunByStyles(TextAdjustment textAdjustment,
1062 const Run* run,
1063 SkScalar runOffset,
1064 TextRange textRange,
1065 StyleType styleType,
1066 const RunStyleVisitor& visitor) const {
1067 auto correctContext = [&](TextRange textRange, SkScalar textOffsetInRun) -> ClipContext {
1068 auto result = this->measureTextInsideOneRun(
1069 textRange, run, runOffset, textOffsetInRun, styleType == StyleType::kDecorations, textAdjustment);
1070 if (styleType == StyleType::kDecorations) {
1071 // Decorations are drawn based on the real font metrics (regardless of styles and strut)
1072 result.clip.fTop = this->sizes().runTop(run, LineMetricStyle::CSS);
1073 result.clip.fBottom = result.clip.fTop +
1074 run->calculateHeight(LineMetricStyle::CSS, LineMetricStyle::CSS);
1075 }
1076 return result;
1077 };
1078
1079 if (run->fEllipsis) {
1080 // Extra efforts to get the ellipsis text style
1081 ClipContext clipContext = correctContext(run->textRange(), 0.0f);
1082 TextRange testRange(run->fClusterStart, run->fClusterStart + run->textRange().width());
1083 for (BlockIndex index = fBlockRange.start; index < fBlockRange.end; ++index) {
1084 auto block = fOwner->styles().begin() + index;
1085 auto intersect = intersected(block->fRange, testRange);
1086 if (intersect.width() > 0) {
1087 visitor(testRange, block->fStyle, clipContext);
1088 return run->advance().fX;
1089 }
1090 }
1091 SkASSERT(false);
1092 }
1093
1094 if (styleType == StyleType::kNone) {
1095 ClipContext clipContext = correctContext(textRange, 0.0f);
1096 if (clipContext.clip.height() > 0) {
1097 visitor(textRange, TextStyle(), clipContext);
1098 return clipContext.clip.width();
1099 } else {
1100 return 0;
1101 }
1102 }
1103
1104 TextIndex start = EMPTY_INDEX;
1105 size_t size = 0;
1106 const TextStyle* prevStyle = nullptr;
1107 SkScalar textOffsetInRun = 0;
1108
1109 const BlockIndex blockRangeSize = fBlockRange.end - fBlockRange.start;
1110 for (BlockIndex index = 0; index <= blockRangeSize; ++index) {
1111
1112 TextRange intersect;
1113 TextStyle* style = nullptr;
1114 if (index < blockRangeSize) {
1115 auto block = fOwner->styles().begin() +
1116 (run->leftToRight() ? fBlockRange.start + index : fBlockRange.end - index - 1);
1117
1118 // Get the text
1119 intersect = intersected(block->fRange, textRange);
1120 if (intersect.width() == 0) {
1121 if (start == EMPTY_INDEX) {
1122 // This style is not applicable to the text yet
1123 continue;
1124 } else {
1125 // We have found all the good styles already
1126 // but we need to process the last one of them
1127 intersect = TextRange(start, start + size);
1128 index = fBlockRange.end;
1129 }
1130 } else {
1131 // Get the style
1132 style = &block->fStyle;
1133 if (start != EMPTY_INDEX && style->matchOneAttribute(styleType, *prevStyle)) {
1134 size += intersect.width();
1135 // RTL text intervals move backward
1136 start = std::min(intersect.start, start);
1137 continue;
1138 } else if (start == EMPTY_INDEX ) {
1139 // First time only
1140 prevStyle = style;
1141 size = intersect.width();
1142 start = intersect.start;
1143 continue;
1144 }
1145 }
1146 } else if (prevStyle != nullptr) {
1147 // This is the last style
1148 } else {
1149 break;
1150 }
1151
1152 // We have the style and the text
1153 auto runStyleTextRange = TextRange(start, start + size);
1154 ClipContext clipContext = correctContext(runStyleTextRange, textOffsetInRun);
1155 textOffsetInRun += clipContext.clip.width();
1156 if (clipContext.clip.height() == 0) {
1157 continue;
1158 }
1159 visitor(runStyleTextRange, *prevStyle, clipContext);
1160
1161 // Start all over again
1162 prevStyle = style;
1163 start = intersect.start;
1164 size = intersect.width();
1165 }
1166 return textOffsetInRun;
1167 }
1168
iterateThroughVisualRuns(bool includingGhostSpaces,const RunVisitor & visitor) const1169 void TextLine::iterateThroughVisualRuns(bool includingGhostSpaces, const RunVisitor& visitor) const {
1170
1171 // Walk through all the runs that intersect with the line in visual order
1172 SkScalar width = 0;
1173 SkScalar runOffset = 0;
1174 SkScalar totalWidth = 0;
1175 bool ellipsisModeIsHead = fOwner->paragraphStyle().getEllipsisMod() == EllipsisModal::HEAD;
1176 bool isAlreadyUseEllipsis = false;
1177 auto textRange = includingGhostSpaces ? this->textWithNewlines() : this->trimmedText();
1178
1179 if (fOwner->paragraphStyle().getTextDirection() == TextDirection::kRtl &&
1180 this->ellipsis() != nullptr && !ellipsisModeIsHead) {
1181 isAlreadyUseEllipsis = true;
1182 runOffset = this->ellipsis()->offset().fX;
1183 if (visitor(ellipsis(), runOffset, ellipsis()->textRange(), &width)) {
1184 }
1185 } else if (ellipsisModeIsHead && this->ellipsis() != nullptr &&
1186 fOwner->paragraphStyle().getTextDirection() == TextDirection::kLtr) {
1187 isAlreadyUseEllipsis = true;
1188 runOffset = this->ellipsis()->offset().fX;
1189 if (visitor(ellipsis(), runOffset, ellipsis()->textRange(), &width)) {
1190 }
1191 }
1192
1193 for (auto& runIndex : fRunsInVisualOrder) {
1194
1195 const auto run = &this->fOwner->run(runIndex);
1196 auto lineIntersection = intersected(run->textRange(), textRange);
1197 if (lineIntersection.width() == 0 && this->width() != 0) {
1198 // TODO: deal with empty runs in a better way
1199 continue;
1200 }
1201 if (!run->leftToRight() && runOffset == 0 && includingGhostSpaces) {
1202 // runOffset does not take in account a possibility
1203 // that RTL run could start before the line (trailing spaces)
1204 // so we need to do runOffset -= "trailing whitespaces length"
1205 TextRange whitespaces = intersected(
1206 TextRange(fTextExcludingSpaces.end, fTextIncludingNewlines.end), run->fTextRange);
1207 if (whitespaces.width() > 0) {
1208 auto whitespacesLen = measureTextInsideOneRun(whitespaces, run, runOffset, 0, true, TextAdjustment::GlyphCluster).clip.width();
1209 runOffset -= whitespacesLen;
1210 }
1211 }
1212 runOffset += width;
1213 totalWidth += width;
1214 if (!visitor(run, runOffset, lineIntersection, &width)) {
1215 return;
1216 }
1217 }
1218
1219 runOffset += width;
1220 totalWidth += width;
1221
1222 if (this->ellipsis() != nullptr && !isAlreadyUseEllipsis) {
1223 if (visitor(ellipsis(), runOffset, ellipsis()->textRange(), &width)) {
1224 totalWidth += width;
1225 }
1226 }
1227
1228 if (!includingGhostSpaces && compareRound(totalWidth, this->width(), fOwner->getApplyRoundingHack()) != 0) {
1229 // This is a very important assert!
1230 // It asserts that 2 different ways of calculation come with the same results
1231 SkDebugf("ASSERT: %f != %f\n", totalWidth, this->width());
1232 SkASSERT(false);
1233 }
1234 }
1235
offset() const1236 SkVector TextLine::offset() const {
1237 return fOffset + SkVector::Make(fShift, 0);
1238 }
1239
getMetrics() const1240 LineMetrics TextLine::getMetrics() const {
1241 LineMetrics result;
1242
1243 // Fill out the metrics
1244 fOwner->ensureUTF16Mapping();
1245 result.fStartIndex = fOwner->getUTF16Index(fTextExcludingSpaces.start);
1246 result.fEndExcludingWhitespaces = fOwner->getUTF16Index(fTextExcludingSpaces.end);
1247 result.fEndIndex = fOwner->getUTF16Index(fText.end);
1248 result.fEndIncludingNewline = fOwner->getUTF16Index(fTextIncludingNewlines.end);
1249 result.fHardBreak = endsWithHardLineBreak();
1250 result.fAscent = - fMaxRunMetrics.ascent();
1251 result.fDescent = fMaxRunMetrics.descent();
1252 result.fUnscaledAscent = - fMaxRunMetrics.ascent(); // TODO: implement
1253 result.fHeight = fAdvance.fY;
1254 result.fWidth = fAdvance.fX;
1255 if (fOwner->getApplyRoundingHack()) {
1256 result.fHeight = littleRound(result.fHeight);
1257 result.fWidth = littleRound(result.fWidth);
1258 }
1259 result.fLeft = this->offset().fX;
1260 // This is Flutter definition of a baseline
1261 result.fBaseline = this->offset().fY + this->height() - this->sizes().descent();
1262 result.fLineNumber = this - fOwner->lines().begin();
1263
1264 // Fill out the style parts
1265 this->iterateThroughVisualRuns(false,
1266 [this, &result]
1267 (const Run* run, SkScalar runOffsetInLine, TextRange textRange, SkScalar* runWidthInLine) {
1268 if (run->placeholderStyle() != nullptr) {
1269 *runWidthInLine = run->advance().fX;
1270 return true;
1271 }
1272 *runWidthInLine = this->iterateThroughSingleRunByStyles(
1273 TextAdjustment::GlyphCluster, run, runOffsetInLine, textRange, StyleType::kForeground,
1274 [&result, &run](TextRange textRange, const TextStyle& style, const ClipContext& context) {
1275 SkFontMetrics fontMetrics;
1276 run->fFont.getMetrics(&fontMetrics);
1277 StyleMetrics styleMetrics(&style, fontMetrics);
1278 result.fLineMetrics.emplace(textRange.start, styleMetrics);
1279 });
1280 return true;
1281 });
1282
1283 return result;
1284 }
1285
isFirstLine() const1286 bool TextLine::isFirstLine() const {
1287 return this == &fOwner->lines().front();
1288 }
1289
isLastLine() const1290 bool TextLine::isLastLine() const {
1291 return this == &fOwner->lines().back();
1292 }
1293
endsWithHardLineBreak() const1294 bool TextLine::endsWithHardLineBreak() const {
1295 // TODO: For some reason Flutter imagines a hard line break at the end of the last line.
1296 // To be removed...
1297 return (fGhostClusterRange.width() > 0 && fOwner->cluster(fGhostClusterRange.end - 1).isHardBreak()) ||
1298 fEllipsis != nullptr ||
1299 fGhostClusterRange.end == fOwner->clusters().size() - 1;
1300 }
1301
getRectsForRange(TextRange textRange0,RectHeightStyle rectHeightStyle,RectWidthStyle rectWidthStyle,std::vector<TextBox> & boxes) const1302 void TextLine::getRectsForRange(TextRange textRange0,
1303 RectHeightStyle rectHeightStyle,
1304 RectWidthStyle rectWidthStyle,
1305 std::vector<TextBox>& boxes) const
1306 {
1307 const Run* lastRun = nullptr;
1308 auto startBox = boxes.size();
1309 this->iterateThroughVisualRuns(true,
1310 [textRange0, rectHeightStyle, rectWidthStyle, &boxes, &lastRun, startBox, this]
1311 (const Run* run, SkScalar runOffsetInLine, TextRange textRange, SkScalar* runWidthInLine) {
1312 *runWidthInLine = this->iterateThroughSingleRunByStyles(
1313 TextAdjustment::GraphemeGluster, run, runOffsetInLine, textRange, StyleType::kNone,
1314 [run, runOffsetInLine, textRange0, rectHeightStyle, rectWidthStyle, &boxes, &lastRun, startBox, this]
1315 (TextRange textRange, const TextStyle& style, const TextLine::ClipContext& lineContext) {
1316
1317 auto intersect = textRange * textRange0;
1318 if (intersect.empty()) {
1319 return true;
1320 }
1321
1322 auto paragraphStyle = fOwner->paragraphStyle();
1323
1324 // Found a run that intersects with the text
1325 auto context = this->measureTextInsideOneRun(
1326 intersect, run, runOffsetInLine, 0, true, TextAdjustment::GraphemeGluster);
1327 SkRect clip = context.clip;
1328 clip.offset(lineContext.fTextShift - context.fTextShift, 0);
1329
1330 switch (rectHeightStyle) {
1331 case RectHeightStyle::kMax:
1332 // TODO: Change it once flutter rolls into google3
1333 // (probably will break things if changed before)
1334 clip.fBottom = this->height();
1335 clip.fTop = this->sizes().delta();
1336 break;
1337 case RectHeightStyle::kIncludeLineSpacingTop: {
1338 clip.fBottom = this->height();
1339 clip.fTop = this->sizes().delta();
1340 auto verticalShift = this->sizes().rawAscent() - this->sizes().ascent();
1341 if (isFirstLine()) {
1342 clip.fTop += verticalShift;
1343 }
1344 break;
1345 }
1346 case RectHeightStyle::kIncludeLineSpacingMiddle: {
1347 clip.fBottom = this->height();
1348 clip.fTop = this->sizes().delta();
1349 auto verticalShift = this->sizes().rawAscent() - this->sizes().ascent();
1350 clip.offset(0, verticalShift / 2.0);
1351 if (isFirstLine()) {
1352 clip.fTop += verticalShift / 2.0;
1353 }
1354 if (isLastLine()) {
1355 clip.fBottom -= verticalShift / 2.0;
1356 }
1357 break;
1358 }
1359 case RectHeightStyle::kIncludeLineSpacingBottom: {
1360 clip.fBottom = this->height();
1361 clip.fTop = this->sizes().delta();
1362 auto verticalShift = this->sizes().rawAscent() - this->sizes().ascent();
1363 clip.offset(0, verticalShift);
1364 if (isLastLine()) {
1365 clip.fBottom -= verticalShift;
1366 }
1367 break;
1368 }
1369 case RectHeightStyle::kStrut: {
1370 const auto& strutStyle = paragraphStyle.getStrutStyle();
1371 if (strutStyle.getStrutEnabled()
1372 && strutStyle.getFontSize() > 0) {
1373 auto strutMetrics = fOwner->strutMetrics();
1374 auto top = this->baseline();
1375 clip.fTop = top + strutMetrics.ascent();
1376 clip.fBottom = top + strutMetrics.descent();
1377 }
1378 }
1379 break;
1380 case RectHeightStyle::kTight: {
1381 if (run->fHeightMultiplier <= 0) {
1382 break;
1383 }
1384 const auto effectiveBaseline = this->baseline() + this->sizes().delta();
1385 clip.fTop = effectiveBaseline + run->ascent();
1386 clip.fBottom = effectiveBaseline + run->descent();
1387 }
1388 break;
1389 default:
1390 SkASSERT(false);
1391 break;
1392 }
1393
1394 // Separate trailing spaces and move them in the default order of the paragraph
1395 // in case the run order and the paragraph order don't match
1396 SkRect trailingSpaces = SkRect::MakeEmpty();
1397 if (this->trimmedText().end <this->textWithNewlines().end && // Line has trailing space
1398 this->textWithNewlines().end == intersect.end && // Range is at the end of the line
1399 this->trimmedText().end > intersect.start) // Range has more than just spaces
1400 {
1401 auto delta = this->spacesWidth();
1402 trailingSpaces = SkRect::MakeXYWH(0, 0, 0, 0);
1403 // There are trailing spaces in this run
1404 if (paragraphStyle.getTextAlign() == TextAlign::kJustify && isLastLine())
1405 {
1406 // TODO: this is just a patch. Make it right later (when it's clear what and how)
1407 trailingSpaces = clip;
1408 if(run->leftToRight()) {
1409 trailingSpaces.fLeft = this->width();
1410 clip.fRight = this->width();
1411 } else {
1412 trailingSpaces.fRight = 0;
1413 clip.fLeft = 0;
1414 }
1415 } else if (paragraphStyle.getTextDirection() == TextDirection::kRtl &&
1416 !run->leftToRight())
1417 {
1418 // Split
1419 trailingSpaces = clip;
1420 trailingSpaces.fLeft = - delta;
1421 trailingSpaces.fRight = 0;
1422 clip.fLeft += delta;
1423 } else if (paragraphStyle.getTextDirection() == TextDirection::kLtr &&
1424 run->leftToRight())
1425 {
1426 // Split
1427 trailingSpaces = clip;
1428 trailingSpaces.fLeft = this->width();
1429 trailingSpaces.fRight = trailingSpaces.fLeft + delta;
1430 clip.fRight -= delta;
1431 }
1432 }
1433
1434 clip.offset(this->offset());
1435 if (trailingSpaces.width() > 0) {
1436 trailingSpaces.offset(this->offset());
1437 }
1438
1439 // Check if we can merge two boxes instead of adding a new one
1440 auto merge = [&lastRun, &context, &boxes](SkRect clip) {
1441 bool mergedBoxes = false;
1442 if (!boxes.empty() &&
1443 lastRun != nullptr &&
1444 context.run->leftToRight() == lastRun->leftToRight() &&
1445 lastRun->placeholderStyle() == nullptr &&
1446 context.run->placeholderStyle() == nullptr &&
1447 nearlyEqual(lastRun->heightMultiplier(),
1448 context.run->heightMultiplier()) &&
1449 lastRun->font() == context.run->font())
1450 {
1451 auto& lastBox = boxes.back();
1452 if (nearlyEqual(lastBox.rect.fTop, clip.fTop) &&
1453 nearlyEqual(lastBox.rect.fBottom, clip.fBottom) &&
1454 (nearlyEqual(lastBox.rect.fLeft, clip.fRight) ||
1455 nearlyEqual(lastBox.rect.fRight, clip.fLeft)))
1456 {
1457 lastBox.rect.fLeft = std::min(lastBox.rect.fLeft, clip.fLeft);
1458 lastBox.rect.fRight = std::max(lastBox.rect.fRight, clip.fRight);
1459 mergedBoxes = true;
1460 }
1461 }
1462 lastRun = context.run;
1463 return mergedBoxes;
1464 };
1465
1466 if (!merge(clip)) {
1467 boxes.emplace_back(clip, context.run->getTextDirection());
1468 }
1469 if (!nearlyZero(trailingSpaces.width()) && !merge(trailingSpaces)) {
1470 boxes.emplace_back(trailingSpaces, paragraphStyle.getTextDirection());
1471 }
1472
1473 if (rectWidthStyle == RectWidthStyle::kMax && !isLastLine()) {
1474 // Align the very left/right box horizontally
1475 auto lineStart = this->offset().fX;
1476 auto lineEnd = this->offset().fX + this->width();
1477 auto left = boxes[startBox];
1478 auto right = boxes.back();
1479 if (left.rect.fLeft > lineStart && left.direction == TextDirection::kRtl) {
1480 left.rect.fRight = left.rect.fLeft;
1481 left.rect.fLeft = 0;
1482 boxes.insert(boxes.begin() + startBox + 1, left);
1483 }
1484 if (right.direction == TextDirection::kLtr &&
1485 right.rect.fRight >= lineEnd &&
1486 right.rect.fRight < fOwner->widthWithTrailingSpaces()) {
1487 right.rect.fLeft = right.rect.fRight;
1488 right.rect.fRight = fOwner->widthWithTrailingSpaces();
1489 boxes.emplace_back(right);
1490 }
1491 }
1492
1493 return true;
1494 });
1495 return true;
1496 });
1497 if (fOwner->getApplyRoundingHack()) {
1498 for (auto& r : boxes) {
1499 r.rect.fLeft = littleRound(r.rect.fLeft);
1500 r.rect.fRight = littleRound(r.rect.fRight);
1501 r.rect.fTop = littleRound(r.rect.fTop);
1502 r.rect.fBottom = littleRound(r.rect.fBottom);
1503 }
1504 }
1505 }
1506
getGlyphPositionAtCoordinate(SkScalar dx)1507 PositionWithAffinity TextLine::getGlyphPositionAtCoordinate(SkScalar dx) {
1508
1509 if (SkScalarNearlyZero(this->width()) && SkScalarNearlyZero(this->spacesWidth())) {
1510 // TODO: this is one of the flutter changes that have to go away eventually
1511 // Empty line is a special case in txtlib (but only when there are no spaces, too)
1512 auto utf16Index = fOwner->getUTF16Index(this->fTextExcludingSpaces.end);
1513 return { SkToS32(utf16Index) , kDownstream };
1514 }
1515
1516 PositionWithAffinity result(0, Affinity::kDownstream);
1517 this->iterateThroughVisualRuns(true,
1518 [this, dx, &result]
1519 (const Run* run, SkScalar runOffsetInLine, TextRange textRange, SkScalar* runWidthInLine) {
1520 bool keepLooking = true;
1521 *runWidthInLine = this->iterateThroughSingleRunByStyles(
1522 TextAdjustment::GraphemeGluster, run, runOffsetInLine, textRange, StyleType::kNone,
1523 [this, run, dx, &result, &keepLooking]
1524 (TextRange textRange, const TextStyle& style, const TextLine::ClipContext& context0) {
1525
1526 SkScalar offsetX = this->offset().fX;
1527 ClipContext context = context0;
1528
1529 // Correct the clip size because libtxt counts trailing spaces
1530 if (run->leftToRight()) {
1531 context.clip.fRight += context.fExcludedTrailingSpaces; // extending clip to the right
1532 } else {
1533 // Clip starts from 0; we cannot extend it to the left from that
1534 }
1535 // However, we need to offset the clip
1536 context.clip.offset(offsetX, 0.0f);
1537
1538 // This patch will help us to avoid a floating point error
1539 if (SkScalarNearlyEqual(context.clip.fRight, dx, 0.01f)) {
1540 context.clip.fRight = dx;
1541 }
1542
1543 if (dx <= context.clip.fLeft) {
1544 // All the other runs are placed right of this one
1545 auto utf16Index = fOwner->getUTF16Index(context.run->globalClusterIndex(context.pos));
1546 if (run->leftToRight()) {
1547 result = { SkToS32(utf16Index), kDownstream};
1548 keepLooking = false;
1549 } else {
1550 result = { SkToS32(utf16Index + 1), kUpstream};
1551 // If we haven't reached the end of the run we need to keep looking
1552 keepLooking = context.pos != 0;
1553 }
1554 // For RTL we go another way
1555 return !run->leftToRight();
1556 }
1557
1558 if (dx >= context.clip.fRight) {
1559 // We have to keep looking ; just in case keep the last one as the closest
1560 auto utf16Index = fOwner->getUTF16Index(context.run->globalClusterIndex(context.pos + context.size));
1561 if (run->leftToRight()) {
1562 result = {SkToS32(utf16Index), kUpstream};
1563 } else {
1564 result = {SkToS32(utf16Index), kDownstream};
1565 }
1566 // For RTL we go another way
1567 return run->leftToRight();
1568 }
1569
1570 // So we found the run that contains our coordinates
1571 // Find the glyph position in the run that is the closest left of our point
1572 // TODO: binary search
1573 size_t found = context.pos;
1574 for (size_t index = context.pos; index < context.pos + context.size; ++index) {
1575 // TODO: this rounding is done to match Flutter tests. Must be removed..
1576 auto end = context.run->positionX(index) + context.fTextShift + offsetX;
1577 if (fOwner->getApplyRoundingHack()) {
1578 end = littleRound(end);
1579 }
1580 if (end > dx) {
1581 break;
1582 } else if (end == dx && !context.run->leftToRight()) {
1583 // When we move RTL variable end points to the beginning of the code point which is included
1584 found = index;
1585 break;
1586 }
1587 found = index;
1588 }
1589
1590 SkScalar glyphemePosLeft = context.run->positionX(found) + context.fTextShift + offsetX;
1591 SkScalar glyphemesWidth = context.run->positionX(found + 1) - context.run->positionX(found);
1592
1593 // Find the grapheme range that contains the point
1594 auto clusterIndex8 = context.run->globalClusterIndex(found);
1595 auto clusterEnd8 = context.run->globalClusterIndex(found + 1);
1596 auto graphemes = fOwner->countSurroundingGraphemes({clusterIndex8, clusterEnd8});
1597
1598 SkScalar center = glyphemePosLeft + glyphemesWidth * fOwner->getTextSplitRatio();
1599 if (graphemes.size() > 1) {
1600 // Calculate the position proportionally based on grapheme count
1601 SkScalar averageGraphemeWidth = glyphemesWidth / graphemes.size();
1602 SkScalar delta = dx - glyphemePosLeft;
1603 int graphemeIndex = SkScalarNearlyZero(averageGraphemeWidth)
1604 ? 0
1605 : SkScalarFloorToInt(delta / averageGraphemeWidth);
1606 auto graphemeCenter = glyphemePosLeft + graphemeIndex * averageGraphemeWidth +
1607 averageGraphemeWidth * fOwner->getTextSplitRatio();
1608 auto graphemeUtf8Index = graphemes[graphemeIndex];
1609 if ((dx < graphemeCenter) == context.run->leftToRight()) {
1610 size_t utf16Index = fOwner->getUTF16Index(graphemeUtf8Index);
1611 result = { SkToS32(utf16Index), kDownstream };
1612 } else {
1613 size_t utf16Index = fOwner->getUTF16Index(graphemeUtf8Index + 1);
1614 result = { SkToS32(utf16Index), kUpstream };
1615 }
1616 // Keep UTF16 index as is
1617 } else if ((dx < center) == context.run->leftToRight()) {
1618 size_t utf16Index = fOwner->getUTF16Index(clusterIndex8);
1619 result = { SkToS32(utf16Index), kDownstream };
1620 } else {
1621 size_t utf16Index = context.run->leftToRight()
1622 ? fOwner->getUTF16Index(clusterEnd8)
1623 : fOwner->getUTF16Index(clusterIndex8) + 1;
1624 result = { SkToS32(utf16Index), kUpstream };
1625 }
1626
1627 return keepLooking = false;
1628
1629 });
1630 return keepLooking;
1631 }
1632 );
1633 return result;
1634 }
1635
getRectsForPlaceholders(std::vector<TextBox> & boxes)1636 void TextLine::getRectsForPlaceholders(std::vector<TextBox>& boxes) {
1637 this->iterateThroughVisualRuns(
1638 true,
1639 [&boxes, this](const Run* run, SkScalar runOffset, TextRange textRange,
1640 SkScalar* width) {
1641 auto context = this->measureTextInsideOneRun(
1642 textRange, run, runOffset, 0, true, TextAdjustment::GraphemeGluster);
1643 *width = context.clip.width();
1644
1645 if (textRange.width() == 0) {
1646 return true;
1647 }
1648 if (!run->isPlaceholder()) {
1649 return true;
1650 }
1651
1652 SkRect clip = context.clip;
1653 clip.offset(this->offset());
1654
1655 if (fOwner->getApplyRoundingHack()) {
1656 clip.fLeft = littleRound(clip.fLeft);
1657 clip.fRight = littleRound(clip.fRight);
1658 clip.fTop = littleRound(clip.fTop);
1659 clip.fBottom = littleRound(clip.fBottom);
1660 }
1661 boxes.emplace_back(clip, run->getTextDirection());
1662 return true;
1663 });
1664 }
1665 } // namespace textlayout
1666 } // namespace skia
1667