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 "log.h"
15 #include "modules/skparagraph/include/DartTypes.h"
16 #include "modules/skparagraph/include/Metrics.h"
17 #include "modules/skparagraph/include/ParagraphPainter.h"
18 #include "modules/skparagraph/include/ParagraphStyle.h"
19 #include "modules/skparagraph/include/TextShadow.h"
20 #include "modules/skparagraph/include/TextStyle.h"
21 #include "modules/skparagraph/src/Decorations.h"
22 #include "modules/skparagraph/src/ParagraphImpl.h"
23 #include "modules/skparagraph/src/ParagraphPainterImpl.h"
24 #include "modules/skparagraph/src/RunBaseImpl.h"
25 #include "modules/skparagraph/src/TextLine.h"
26 #include "modules/skshaper/include/SkShaper.h"
27 #include "src/Run.h"
28 #ifdef TXT_USE_PARAMETER
29 #include "parameter.h"
30 #endif
31 #include "log.h"
32
33 #include <algorithm>
34 #include <iterator>
35 #include <limits>
36 #include <map>
37 #include <memory>
38 #include <tuple>
39 #include <type_traits>
40 #include <utility>
41
42 namespace skia {
43 namespace textlayout {
44 #define MAX_INT_VALUE 0x7FFFFFFF
45 #define EMOJI_UNICODE_START 0x1F300
46 #define EMOJI_UNICODE_END 0x1F9EF
47
48 namespace {
49
50 // TODO: deal with all the intersection functionality
intersected(const TextRange & a,const TextRange & b)51 TextRange intersected(const TextRange& a, const TextRange& b) {
52 if (a.start == b.start && a.end == b.end) return a;
53 auto begin = std::max(a.start, b.start);
54 auto end = std::min(a.end, b.end);
55 return end >= begin ? TextRange(begin, end) : EMPTY_TEXT;
56 }
57
littleRound(SkScalar a)58 SkScalar littleRound(SkScalar a) {
59 // This rounding is done to match Flutter tests. Must be removed..
60 return SkScalarRoundToScalar(a * 100.0)/100.0;
61 }
62
operator *(const TextRange & a,const TextRange & b)63 TextRange operator*(const TextRange& a, const TextRange& b) {
64 if (a.start == b.start && a.end == b.end) return a;
65 auto begin = std::max(a.start, b.start);
66 auto end = std::min(a.end, b.end);
67 return end > begin ? TextRange(begin, end) : EMPTY_TEXT;
68 }
69
compareRound(SkScalar a,SkScalar b,bool applyRoundingHack)70 int compareRound(SkScalar a, SkScalar b, bool applyRoundingHack) {
71 // There is a rounding error that gets bigger when maxWidth gets bigger
72 // VERY long zalgo text (> 100000) on a VERY long line (> 10000)
73 // Canvas scaling affects it
74 // Letter spacing affects it
75 // It has to be relative to be useful
76 auto base = std::max(SkScalarAbs(a), SkScalarAbs(b));
77 auto diff = SkScalarAbs(a - b);
78 if (nearlyZero(base) || diff / base < 0.001f) {
79 return 0;
80 }
81
82 auto ra = a;
83 auto rb = b;
84
85 if (applyRoundingHack) {
86 ra = littleRound(a);
87 rb = littleRound(b);
88 }
89 if (ra < rb) {
90 return -1;
91 } else {
92 return 1;
93 }
94 }
95
96 #ifdef USE_SKIA_TXT
IsRSFontEquals(const RSFont & font0,const RSFont & font1)97 bool IsRSFontEquals(const RSFont& font0, const RSFont& font1) {
98 auto f0 = const_cast<RSFont&>(font0);
99 auto f1 = const_cast<RSFont&>(font1);
100 return f0.GetTypeface().get() == f1.GetTypeface().get() &&
101 f0.GetSize() == f1.GetSize() &&
102 f0.GetScaleX() == f1.GetScaleX() &&
103 f0.GetSkewX() == f1.GetSkewX() &&
104 f0.GetEdging() == f1.GetEdging() &&
105 f0.GetHinting() == f1.GetHinting();
106 }
107 #endif
108
109 } // namespace
110
TextLine(ParagraphImpl * owner,SkVector offset,SkVector advance,BlockRange blocks,TextRange textExcludingSpaces,TextRange text,TextRange textIncludingNewlines,ClusterRange clusters,ClusterRange clustersWithGhosts,SkScalar widthWithSpaces,InternalLineMetrics sizes)111 TextLine::TextLine(ParagraphImpl* owner,
112 SkVector offset,
113 SkVector advance,
114 BlockRange blocks,
115 TextRange textExcludingSpaces,
116 TextRange text,
117 TextRange textIncludingNewlines,
118 ClusterRange clusters,
119 ClusterRange clustersWithGhosts,
120 SkScalar widthWithSpaces,
121 InternalLineMetrics sizes)
122 : fOwner(owner)
123 , fBlockRange(blocks)
124 , fTextExcludingSpaces(textExcludingSpaces)
125 , fText(text)
126 , fTextIncludingNewlines(textIncludingNewlines)
127 , fClusterRange(clusters)
128 , fGhostClusterRange(clustersWithGhosts)
129 , fRunsInVisualOrder()
130 , fAdvance(advance)
131 , fOffset(offset)
132 , fShift(0.0)
133 , fWidthWithSpaces(widthWithSpaces)
134 , fEllipsis(nullptr)
135 , fSizes(sizes)
136 , fHasBackground(false)
137 , fHasShadows(false)
138 , fHasDecorations(false)
139 , fIsArcText(false)
140 , fArcTextState(false)
141 , fAscentStyle(LineMetricStyle::CSS)
142 , fDescentStyle(LineMetricStyle::CSS)
143 , fTextBlobCachePopulated(false) {
144 // Reorder visual runs
145 auto& start = owner->cluster(fGhostClusterRange.start);
146 auto& end = owner->cluster(fGhostClusterRange.end - 1);
147 size_t numRuns = end.runIndex() - start.runIndex() + 1;
148
149 for (BlockIndex index = fBlockRange.start; index < fBlockRange.end; ++index) {
150 auto b = fOwner->styles().begin() + index;
151 if (b->fStyle.hasBackground()) {
152 fHasBackground = true;
153 }
154 if (b->fStyle.getDecorationType() != TextDecoration::kNoDecoration) {
155 fHasDecorations = true;
156 }
157 if (b->fStyle.getShadowNumber() > 0) {
158 fHasShadows = true;
159 }
160 }
161
162 // Get the logical order
163
164 // This is just chosen to catch the common/fast cases. Feel free to tweak.
165 constexpr int kPreallocCount = 4;
166 SkAutoSTArray<kPreallocCount, SkUnicode::BidiLevel> runLevels(numRuns);
167 std::vector<RunIndex> placeholdersInOriginalOrder;
168 size_t runLevelsIndex = 0;
169 // Placeholders must be laid out using the original order in which they were added
170 // in the input. The API does not provide a way to indicate that a placeholder
171 // position was moved due to bidi reordering.
172 for (auto runIndex = start.runIndex(); runIndex <= end.runIndex(); ++runIndex) {
173 auto& run = fOwner->run(runIndex);
174 runLevels[runLevelsIndex++] = run.fBidiLevel;
175 fMaxRunMetrics.add(
176 InternalLineMetrics(run.correctAscent(), run.correctDescent(), run.fFontMetrics.fLeading));
177 if (run.isPlaceholder()) {
178 placeholdersInOriginalOrder.push_back(runIndex);
179 }
180 }
181 SkASSERT(runLevelsIndex == numRuns);
182
183 SkAutoSTArray<kPreallocCount, int32_t> logicalOrder(numRuns);
184
185 // TODO: hide all these logic in SkUnicode?
186 fOwner->getUnicode()->reorderVisual(runLevels.data(), numRuns, logicalOrder.data());
187 auto firstRunIndex = start.runIndex();
188 auto placeholderIter = placeholdersInOriginalOrder.begin();
189 for (auto index : logicalOrder) {
190 auto runIndex = firstRunIndex + index;
191 if (fOwner->run(runIndex).isPlaceholder()) {
192 fRunsInVisualOrder.push_back(*placeholderIter++);
193 } else {
194 fRunsInVisualOrder.push_back(runIndex);
195 }
196 }
197
198 fTextRangeReplacedByEllipsis = EMPTY_RANGE;
199 fEllipsisIndex = EMPTY_INDEX;
200 fLastClipRunLtr = false;
201 }
202
paint(ParagraphPainter * painter,const RSPath * path,SkScalar hOffset,SkScalar vOffset)203 void TextLine::paint(ParagraphPainter* painter, const RSPath* path, SkScalar hOffset, SkScalar vOffset) {
204 prepareRoundRect();
205 fIsArcText = true;
206 if (pathParameters.hOffset != hOffset || pathParameters.vOffset != vOffset) {
207 fTextBlobCachePopulated = false;
208 }
209 pathParameters.recordPath = path;
210 pathParameters.hOffset = hOffset;
211 pathParameters.vOffset = vOffset;
212 this->ensureTextBlobCachePopulated();
213 for (auto& record : fTextBlobCache) {
214 record.paint(painter);
215 }
216 }
217
paint(ParagraphPainter * painter,SkScalar x,SkScalar y)218 void TextLine::paint(ParagraphPainter* painter, SkScalar x, SkScalar y) {
219 prepareRoundRect();
220 fIsArcText = false;
221 #ifdef OHOS_SUPPORT
222 this->iterateThroughVisualRuns(EllipsisReadStrategy::READ_REPLACED_WORD, true,
223 #else
224 this->iterateThroughVisualRuns(false,
225 #endif
226 [painter, x, y, this]
227 (const Run* run, SkScalar runOffsetInLine, TextRange textRange, SkScalar* runWidthInLine) {
228 *runWidthInLine = this->iterateThroughSingleRunByStyles(
229 TextAdjustment::GlyphCluster, run, runOffsetInLine, textRange, StyleType::kBackground,
230 [painter, x, y, run, this](TextRange textRange, const TextStyle& style, const ClipContext& context) {
231 if (fHasBackground) {
232 this->paintBackground(painter, x, y, textRange, style, context);
233 }
234 paintRoundRect(painter, x, y, run);
235 });
236 return true;
237 });
238
239 if (fHasShadows) {
240 #ifdef OHOS_SUPPORT
241 this->iterateThroughVisualRuns(EllipsisReadStrategy::READ_REPLACED_WORD, false,
242 #else
243 this->iterateThroughVisualRuns(false,
244 #endif
245 [painter, x, y, this]
246 (const Run* run, SkScalar runOffsetInLine, TextRange textRange, SkScalar* runWidthInLine) {
247 *runWidthInLine = this->iterateThroughSingleRunByStyles(
248 TextAdjustment::GlyphCluster, run, runOffsetInLine, textRange, StyleType::kShadow,
249 [painter, x, y, this]
250 (TextRange textRange, const TextStyle& style, const ClipContext& context) {
251 this->paintShadow(painter, x, y, textRange, style, context);
252 });
253 return true;
254 });
255 }
256
257 this->ensureTextBlobCachePopulated();
258
259 for (auto& record : fTextBlobCache) {
260 record.paint(painter, x, y);
261 }
262
263 if (fHasDecorations) {
264 this->fDecorationContext = {0.0f, 0.0f, 0.0f};
265 #ifdef OHOS_SUPPORT
266 this->iterateThroughVisualRuns(EllipsisReadStrategy::DEFAULT, true,
267 #else
268 this->iterateThroughVisualRuns(false,
269 #endif
270 [painter, x, y, this]
271 (const Run* run, SkScalar runOffsetInLine, TextRange textRange, SkScalar* runWidthInLine) {
272 *runWidthInLine = this->iterateThroughSingleRunByStyles(
273 TextAdjustment::GlyphCluster, run, runOffsetInLine, textRange,
274 StyleType::kDecorations, [painter, x, y, this]
275 (TextRange textRange, const TextStyle& style, const ClipContext& context) {
276 if (style.getDecoration().fType == TextDecoration::kUnderline) {
277 SkScalar tmpThick = this->calculateThickness(style, context);
278 fDecorationContext.thickness = fDecorationContext.thickness > tmpThick ?
279 fDecorationContext.thickness : tmpThick;
280 }
281 });
282 return true;
283 });
284 #ifdef OHOS_SUPPORT
285 this->iterateThroughVisualRuns(EllipsisReadStrategy::DEFAULT, true,
286 #else
287 this->iterateThroughVisualRuns(false,
288 #endif
289 [painter, x, y, this]
290 (const Run* run, SkScalar runOffsetInLine, TextRange textRange, SkScalar* runWidthInLine) {
291 *runWidthInLine = this->iterateThroughSingleRunByStyles(
292 TextAdjustment::GlyphCluster, run, runOffsetInLine, textRange, StyleType::kDecorations,
293 [painter, x, y, this]
294 (TextRange textRange, const TextStyle& style, const ClipContext& context) {
295 // 12% of row height.
296 fDecorationContext.underlinePosition = (fSizes.height() * 0.12 + this->baseline());
297 fDecorationContext.textBlobTop = fSizes.height() * 0.12;
298 this->paintDecorations(painter, x, y, textRange, style, context);
299 });
300 return true;
301 });
302 }
303 }
304
hasBackgroundRect(const RoundRectAttr & attr)305 bool TextLine::hasBackgroundRect(const RoundRectAttr& attr) {
306 return attr.roundRectStyle.color != 0 && attr.rect.width() > 0;
307 }
308
computeRoundRect(int & index,int & preIndex,std::vector<Run * > & groupRuns,Run * run)309 void TextLine::computeRoundRect(int& index, int& preIndex, std::vector<Run*>& groupRuns, Run* run) {
310 int runCount = roundRectAttrs.size();
311 if (index >= runCount) {
312 return;
313 }
314
315 bool leftRound = false;
316 bool rightRound = false;
317 if (hasBackgroundRect(roundRectAttrs[index])) {
318 int styleId = roundRectAttrs[index].styleId;
319 // index - 1 is previous index, -1 is the invalid styleId
320 int preStyleId = index == 0 ? -1 : roundRectAttrs[index - 1].styleId;
321 // runCount - 1 is the last run index, index + 1 is next run index, -1 is the invalid styleId
322 int nextStyleId = index == runCount - 1 ? -1 : roundRectAttrs[index + 1].styleId;
323 // index - preIndex > 1 means the left run has no background rect
324 leftRound = (preIndex < 0 || index - preIndex > 1 || preStyleId != styleId);
325 // runCount - 1 is the last run index
326 rightRound = (index == runCount - 1 || !hasBackgroundRect(roundRectAttrs[index + 1]) ||
327 nextStyleId != styleId);
328 preIndex = index;
329 groupRuns.push_back(run);
330 } else if (!groupRuns.empty()) {
331 groupRuns.erase(groupRuns.begin(), groupRuns.end());
332 }
333 if (leftRound && rightRound) {
334 run->setRoundRectType(RoundRectType::ALL);
335 } else if (leftRound) {
336 run->setRoundRectType(RoundRectType::LEFT_ONLY);
337 } else if (rightRound) {
338 run->setRoundRectType(RoundRectType::RIGHT_ONLY);
339 } else {
340 run->setRoundRectType(RoundRectType::NONE);
341 }
342
343 if (rightRound && !groupRuns.empty()) {
344 double maxRoundRectRadius = MAX_INT_VALUE;
345 double minTop = MAX_INT_VALUE;
346 double maxBottom = 0;
347 for (auto &gRun : groupRuns) {
348 RoundRectAttr& attr = roundRectAttrs[gRun->getIndexInLine()];
349 maxRoundRectRadius = std::fmin(std::fmin(attr.rect.width(), attr.rect.height()), maxRoundRectRadius);
350 minTop = std::fmin(minTop, attr.rect.top());
351 maxBottom = std::fmax(maxBottom, attr.rect.bottom());
352 }
353 for (auto &gRun : groupRuns) {
354 gRun->setMaxRoundRectRadius(maxRoundRectRadius);
355 gRun->setTopInGroup(minTop - gRun->offset().y());
356 gRun->setBottomInGroup(maxBottom - gRun->offset().y());
357 }
358 groupRuns.erase(groupRuns.begin(), groupRuns.end());
359 }
360 index++;
361 }
362
prepareRoundRect()363 void TextLine::prepareRoundRect() {
364 roundRectAttrs.clear();
365 #ifdef OHOS_SUPPORT
366 this->iterateThroughVisualRuns(EllipsisReadStrategy::READ_REPLACED_WORD, true,
367 #else
368 this->iterateThroughVisualRuns(true,
369 #endif
370 [this](const Run* run, SkScalar runOffsetInLine, TextRange textRange, SkScalar* runWidthInLine) {
371 *runWidthInLine = this->iterateThroughSingleRunByStyles(
372 TextAdjustment::GlyphCluster, run, runOffsetInLine, textRange, StyleType::kBackground,
373 [run, this](TextRange textRange, const TextStyle& style, const ClipContext& context) {
374 roundRectAttrs.push_back({style.getStyleId(), style.getBackgroundRect(), context.clip});
375 });
376 return true;
377 });
378
379 std::vector<Run*> groupRuns;
380 int index = 0;
381 int preIndex = -1;
382 for (auto& runIndex : fRunsInVisualOrder) {
383 auto run = &this->fOwner->run(runIndex);
384 run->setIndexInLine(static_cast<size_t>(index));
385 computeRoundRect(index, preIndex, groupRuns, run);
386 }
387 }
388
ensureTextBlobCachePopulated()389 void TextLine::ensureTextBlobCachePopulated() {
390 if (fTextBlobCachePopulated && fArcTextState == fIsArcText) {
391 return;
392 }
393 fTextBlobCache.clear();
394 if (fBlockRange.width() == 1 &&
395 fRunsInVisualOrder.size() == 1 &&
396 fEllipsis == nullptr &&
397 fOwner->run(fRunsInVisualOrder[0]).placeholderStyle() == nullptr) {
398 if (fClusterRange.width() == 0) {
399 return;
400 }
401 // Most common and most simple case
402 const auto& style = fOwner->block(fBlockRange.start).fStyle;
403 const auto& run = fOwner->run(fRunsInVisualOrder[0]);
404 auto clip = SkRect::MakeXYWH(0.0f, this->sizes().runTop(&run, this->fAscentStyle),
405 fAdvance.fX,
406 run.calculateHeight(this->fAscentStyle, this->fDescentStyle));
407
408 auto& start = fOwner->cluster(fClusterRange.start);
409 auto& end = fOwner->cluster(fClusterRange.end - 1);
410 SkASSERT(start.runIndex() == end.runIndex());
411 GlyphRange glyphs;
412 if (run.leftToRight()) {
413 glyphs = GlyphRange(start.startPos(),
414 end.isHardBreak() ? end.startPos() : end.endPos());
415 } else {
416 glyphs = GlyphRange(end.startPos(),
417 start.isHardBreak() ? start.startPos() : start.endPos());
418 }
419 ClipContext context = {/*run=*/&run,
420 /*pos=*/glyphs.start,
421 /*size=*/glyphs.width(),
422 /*fTextShift=*/-run.positionX(glyphs.start), // starting position
423 /*clip=*/clip, // entire line
424 /*fExcludedTrailingSpaces=*/0.0f, // no need for that
425 /*clippingNeeded=*/false}; // no need for that
426 this->buildTextBlob(fTextExcludingSpaces, style, context);
427 } else {
428 #ifdef OHOS_SUPPORT
429 this->iterateThroughVisualRuns(EllipsisReadStrategy::READ_ELLIPSIS_WORD, false,
430 #else
431 this->iterateThroughVisualRuns(false,
432 #endif
433 [this](const Run* run,
434 SkScalar runOffsetInLine,
435 TextRange textRange,
436 SkScalar* runWidthInLine) {
437 if (run->placeholderStyle() != nullptr) {
438 *runWidthInLine = run->advance().fX;
439 return true;
440 }
441 *runWidthInLine = this->iterateThroughSingleRunByStyles(
442 TextAdjustment::GlyphCluster,
443 run,
444 runOffsetInLine,
445 textRange,
446 StyleType::kForeground,
447 [this](TextRange textRange, const TextStyle& style, const ClipContext& context) {
448 this->buildTextBlob(textRange, style, context);
449 });
450 return true;
451 });
452 }
453 fTextBlobCachePopulated = true;
454 fArcTextState = fIsArcText;
455 pathParameters.recordPath = nullptr;
456 }
457
format(TextAlign align,SkScalar maxWidth,EllipsisModal ellipsisModal)458 void TextLine::format(TextAlign align, SkScalar maxWidth, EllipsisModal ellipsisModal) {
459 SkScalar delta = maxWidth - this->widthWithEllipsisSpaces();
460 if (delta <= 0) {
461 return;
462 }
463
464 // We do nothing for left align
465 if (align == TextAlign::kJustify) {
466 if (!this->endsWithHardLineBreak()) {
467 this->justify(maxWidth);
468 } else if (fOwner->paragraphStyle().getTextDirection() == TextDirection::kRtl) {
469 // Justify -> Right align
470 fShift = delta;
471 }
472 } else if (align == TextAlign::kRight) {
473 fShift = delta;
474 } else if (align == TextAlign::kCenter) {
475 fShift = delta / 2;
476 }
477 }
478
calculateSpacing(const Cluster prevCluster,const Cluster curCluster)479 SkScalar TextLine::calculateSpacing(const Cluster prevCluster, const Cluster curCluster)
480 {
481 if (prevCluster.isWhitespaceBreak() || curCluster.isWhitespaceBreak()) {
482 return 0;
483 }
484 if (prevCluster.isHardBreak() || curCluster.isHardBreak()) {
485 return 0;
486 }
487 if (prevCluster.isCopyright() || curCluster.isCopyright()) {
488 return prevCluster.getFontSize() / AUTO_SPACING_WIDTH_RATIO;
489 }
490 if ((curCluster.isCJK() && prevCluster.isWestern()) || (curCluster.isWestern() && prevCluster.isCJK())) {
491 return prevCluster.getFontSize() / AUTO_SPACING_WIDTH_RATIO;
492 }
493 return 0;
494 }
495
496 #ifdef OHOS_SUPPORT
autoSpacing()497 SkScalar TextLine::autoSpacing() {
498 #ifdef TXT_USE_PARAMETER
499 static constexpr int AUTO_SPACING_ENABLE_LENGTH = 10;
500 char autoSpacingEnable[AUTO_SPACING_ENABLE_LENGTH] = {0};
501 GetParameter("persist.sys.text.autospacing.enable", "0", autoSpacingEnable, AUTO_SPACING_ENABLE_LENGTH);
502 if (!std::strcmp(autoSpacingEnable, "0")) {
503 return 0;
504 }
505 #else
506 return 0;
507 #endif
508 SkScalar spacing = 0.0;
509 auto prevCluster = fOwner->cluster(fClusterRange.start);
510 for (auto clusterIndex = fClusterRange.start + 1; clusterIndex < fClusterRange.end; ++clusterIndex) {
511 auto prevSpacing = spacing;
512 auto& cluster = fOwner->cluster(clusterIndex);
513 spacing += calculateSpacing(prevCluster, cluster);
514 spacingCluster(&cluster, spacing, prevSpacing);
515 prevCluster = cluster;
516 }
517 this->fWidthWithSpaces += spacing;
518 this->fAdvance.fX += spacing;
519 return spacing;
520 }
521 #endif
522
scanStyles(StyleType styleType,const RunStyleVisitor & visitor)523 void TextLine::scanStyles(StyleType styleType, const RunStyleVisitor& visitor) {
524 if (this->empty()) {
525 return;
526 }
527
528 #ifdef OHOS_SUPPORT
529 this->iterateThroughVisualRuns(EllipsisReadStrategy::READ_REPLACED_WORD, false,
530 #else
531 this->iterateThroughVisualRuns(false,
532 #endif
533 [this, visitor, styleType](
534 const Run* run, SkScalar runOffset, TextRange textRange, SkScalar* width) {
535 *width = this->iterateThroughSingleRunByStyles(
536 TextAdjustment::GlyphCluster,
537 run,
538 runOffset,
539 textRange,
540 styleType,
541 [visitor](TextRange textRange,
542 const TextStyle& style,
543 const ClipContext& context) {
544 visitor(textRange, style, context);
545 });
546 return true;
547 });
548 }
549
extendHeight(const ClipContext & context) const550 SkRect TextLine::extendHeight(const ClipContext& context) const {
551 SkRect result = context.clip;
552 result.fBottom += std::max(this->fMaxRunMetrics.height() - this->height(), 0.0f);
553 return result;
554 }
555
buildTextBlob(TextRange textRange,const TextStyle & style,const ClipContext & context)556 void TextLine::buildTextBlob(TextRange textRange, const TextStyle& style, const ClipContext& context) {
557 if (context.run->placeholderStyle() != nullptr) {
558 return;
559 }
560
561 fTextBlobCache.emplace_back();
562 TextBlobRecord& record = fTextBlobCache.back();
563
564 if (style.hasForeground()) {
565 record.fPaint = style.getForegroundPaintOrID();
566 } else {
567 std::get<SkPaint>(record.fPaint).setColor(style.getColor());
568 }
569 record.fVisitor_Run = context.run;
570 record.fVisitor_Pos = context.pos;
571 record.fVisitor_Size = context.size;
572
573 // TODO: This is the change for flutter, must be removed later
574 #ifndef USE_SKIA_TXT
575 SkTextBlobBuilder builder;
576 #else
577 RSTextBlobBuilder builder;
578 #endif
579 if (pathParameters.recordPath) {
580 context.run->copyTo(builder,
581 pathParameters.recordPath,
582 pathParameters.hOffset,
583 pathParameters.vOffset,
584 context.fTextShift,
585 SkToU32(context.pos),
586 context.size);
587 } else {
588 context.run->copyTo(builder, SkToU32(context.pos), context.size);
589 }
590 #ifdef OHOS_SUPPORT
591 // when letterspacing < 0, it causes the font is cliped. so the record fClippingNeeded is set false
592 #else
593 record.fClippingNeeded = context.clippingNeeded;
594 #endif
595 if (context.clippingNeeded) {
596 record.fClipRect = extendHeight(context).makeOffset(this->offset());
597 } else {
598 record.fClipRect = context.clip.makeOffset(this->offset());
599 }
600
601 SkASSERT(nearlyEqual(context.run->baselineShift(), style.getBaselineShift()));
602 SkScalar correctedBaseline = SkScalarFloorToScalar(this->baseline() + style.getBaselineShift() + 0.5);
603 #ifndef USE_SKIA_TXT
604 record.fBlob = builder.make();
605 if (record.fBlob != nullptr) {
606 record.fBounds.joinPossiblyEmptyRect(record.fBlob->bounds());
607 }
608 #else
609 record.fBlob = builder.Make();
610 if (record.fBlob != nullptr) {
611 auto bounds = record.fBlob->Bounds();
612 if (bounds) {
613 record.fBounds.joinPossiblyEmptyRect(SkRect::MakeLTRB(
614 bounds->left_, bounds->top_, bounds->right_, bounds->bottom_
615 ));
616 }
617 }
618 #endif
619
620 record.fOffset = SkPoint::Make(this->offset().fX + context.fTextShift,
621 #ifdef OHOS_SUPPORT
622 this->offset().fY + correctedBaseline - (context.run ? context.run->fCompressionBaselineShift : 0));
623 #else
624 this->offset().fY + correctedBaseline);
625 #endif
626 #ifdef OHOS_SUPPORT
627 #ifndef USE_SKIA_TXT
628 SkFont font;
629 #else
630 RSFont font;
631 #endif
632 if (record.fBlob != nullptr && record.fVisitor_Run != nullptr) {
633 font = record.fVisitor_Run->font();
634 if (font.GetTypeface() != nullptr &&
635 (font.GetTypeface()->GetFamilyName().find("Emoji") != std::string::npos ||
636 font.GetTypeface()->GetFamilyName().find("emoji") != std::string::npos)) {
637 record.fBlob->SetEmoji(true);
638 }
639 }
640 #endif
641 }
642
paint(ParagraphPainter * painter,SkScalar x,SkScalar y)643 void TextLine::TextBlobRecord::paint(ParagraphPainter* painter, SkScalar x, SkScalar y) {
644 if (fClippingNeeded) {
645 painter->save();
646 painter->clipRect(fClipRect.makeOffset(x, y));
647 }
648 painter->drawTextBlob(fBlob, x + fOffset.x(), y + fOffset.y(), fPaint);
649 if (fClippingNeeded) {
650 painter->restore();
651 }
652 }
653
paint(ParagraphPainter * painter)654 void TextLine::TextBlobRecord::paint(ParagraphPainter* painter) {
655 if (fClippingNeeded) {
656 painter->save();
657 }
658 painter->drawTextBlob(fBlob, 0, 0, fPaint);
659 if (fClippingNeeded) {
660 painter->restore();
661 }
662 }
663
paintBackground(ParagraphPainter * painter,SkScalar x,SkScalar y,TextRange textRange,const TextStyle & style,const ClipContext & context) const664 void TextLine::paintBackground(ParagraphPainter* painter,
665 SkScalar x,
666 SkScalar y,
667 TextRange textRange,
668 const TextStyle& style,
669 const ClipContext& context) const {
670 if (style.hasBackground()) {
671 painter->drawRect(context.clip.makeOffset(this->offset() + SkPoint::Make(x, y)),
672 style.getBackgroundPaintOrID());
673 }
674 }
675
paintRoundRect(ParagraphPainter * painter,SkScalar x,SkScalar y,const Run * run) const676 void TextLine::paintRoundRect(ParagraphPainter* painter, SkScalar x, SkScalar y, const Run* run) const {
677 size_t index = run->getIndexInLine();
678 if (index >= roundRectAttrs.size()) {
679 return;
680 }
681
682 const RoundRectAttr& attr = roundRectAttrs[index];
683 if (attr.roundRectStyle.color == 0) {
684 return;
685 }
686
687 SkScalar ltRadius = 0.0f;
688 SkScalar rtRadius = 0.0f;
689 SkScalar rbRadius = 0.0f;
690 SkScalar lbRadius = 0.0f;
691 RoundRectType rType = run->getRoundRectType();
692 if (rType == RoundRectType::ALL || rType == RoundRectType::LEFT_ONLY) {
693 ltRadius = std::fmin(attr.roundRectStyle.leftTopRadius, run->getMaxRoundRectRadius());
694 lbRadius = std::fmin(attr.roundRectStyle.leftBottomRadius, run->getMaxRoundRectRadius());
695 }
696 if (rType == RoundRectType::ALL || rType == RoundRectType::RIGHT_ONLY) {
697 rtRadius = std::fmin(attr.roundRectStyle.rightTopRadius, run->getMaxRoundRectRadius());
698 rbRadius = std::fmin(attr.roundRectStyle.rightBottomRadius, run->getMaxRoundRectRadius());
699 }
700 const SkVector radii[4] = {{ltRadius, ltRadius}, {rtRadius, rtRadius}, {rbRadius, rbRadius}, {lbRadius, lbRadius}};
701 SkRect skRect(SkRect::MakeLTRB(attr.rect.left(), run->getTopInGroup(), attr.rect.right(),
702 run->getBottomInGroup()));
703 SkRRect skRRect;
704 skRRect.setRectRadii(skRect, radii);
705 skRRect.offset(x + this->offset().x(), y + this->offset().y());
706 painter->drawRRect(skRRect, attr.roundRectStyle.color);
707 }
708
paintShadow(ParagraphPainter * painter,SkScalar x,SkScalar y,TextRange textRange,const TextStyle & style,const ClipContext & context) const709 void TextLine::paintShadow(ParagraphPainter* painter,
710 SkScalar x,
711 SkScalar y,
712 TextRange textRange,
713 const TextStyle& style,
714 const ClipContext& context) const {
715 SkScalar correctedBaseline = SkScalarFloorToScalar(this->baseline() + style.getBaselineShift() + 0.5);
716
717 for (TextShadow shadow : style.getShadows()) {
718 if (!shadow.hasShadow()) continue;
719
720 #ifndef USE_SKIA_TXT
721 SkTextBlobBuilder builder;
722 #else
723 RSTextBlobBuilder builder;
724 #endif
725 context.run->copyTo(builder, context.pos, context.size);
726
727 if (context.clippingNeeded) {
728 painter->save();
729 SkRect clip = extendHeight(context);
730 clip.offset(x, y);
731 clip.offset(this->offset());
732 painter->clipRect(clip);
733 }
734 #ifndef USE_SKIA_TXT
735 auto blob = builder.make();
736 #else
737 auto blob = builder.Make();
738 #endif
739 painter->drawTextShadow(blob,
740 x + this->offset().fX + shadow.fOffset.x() + context.fTextShift,
741 y + this->offset().fY + shadow.fOffset.y() + correctedBaseline,
742 shadow.fColor,
743 SkDoubleToScalar(shadow.fBlurSigma));
744 if (context.clippingNeeded) {
745 painter->restore();
746 }
747 }
748 }
749
calculateThickness(const TextStyle & style,const ClipContext & content)750 SkScalar TextLine::calculateThickness(const TextStyle& style, const ClipContext& content)
751 {
752 Decorations decoration;
753 return decoration.calculateThickness(style, content);
754 }
755
paintDecorations(ParagraphPainter * painter,SkScalar x,SkScalar y,TextRange textRange,const TextStyle & style,const ClipContext & context) const756 void TextLine::paintDecorations(ParagraphPainter* painter, SkScalar x, SkScalar y, TextRange textRange, const TextStyle& style, const ClipContext& context) const {
757 ParagraphPainterAutoRestore ppar(painter);
758 painter->translate(x + this->offset().fX, y + this->offset().fY + style.getBaselineShift());
759 Decorations decorations;
760 decorations.setDecorationContext(fDecorationContext);
761 SkScalar correctedBaseline = SkScalarFloorToScalar(-this->sizes().rawAscent() + style.getBaselineShift() + 0.5);
762 decorations.paint(painter, style, context, correctedBaseline);
763 }
764
justify(SkScalar maxWidth)765 void TextLine::justify(SkScalar maxWidth) {
766 int whitespacePatches = 0;
767 SkScalar textLen = 0;
768 bool whitespacePatch = false;
769 // Take leading whitespaces width but do not increment a whitespace patch number
770 bool leadingWhitespaces = false;
771 this->iterateThroughClustersInGlyphsOrder(false, false,
772 [&](const Cluster* cluster, ClusterIndex index, bool ghost) {
773 if (cluster->isWhitespaceBreak()) {
774 if (index == 0) {
775 leadingWhitespaces = true;
776 } else if (!whitespacePatch && !leadingWhitespaces) {
777 // We only count patches BETWEEN words, not before
778 ++whitespacePatches;
779 }
780 whitespacePatch = !leadingWhitespaces;
781 } else if (cluster->isIdeographic()) {
782 // Whitespace break before and after
783 if (!whitespacePatch && index != 0) {
784 // We only count patches BETWEEN words, not before
785 ++whitespacePatches; // before
786 }
787 whitespacePatch = true;
788 leadingWhitespaces = false;
789 ++whitespacePatches; // after
790 } else {
791 whitespacePatch = false;
792 leadingWhitespaces = false;
793 }
794 textLen += cluster->width();
795 return true;
796 });
797
798 if (whitespacePatch) {
799 // We only count patches BETWEEN words, not after
800 --whitespacePatches;
801 }
802 if (whitespacePatches == 0) {
803 if (fOwner->paragraphStyle().getTextDirection() == TextDirection::kRtl) {
804 // Justify -> Right align
805 fShift = maxWidth - textLen;
806 }
807 return;
808 }
809
810 SkScalar step = (maxWidth - textLen) / whitespacePatches;
811 SkScalar shift = 0.0f;
812 SkScalar prevShift = 0.0f;
813
814 // Deal with the ghost spaces
815 auto ghostShift = maxWidth - this->fAdvance.fX;
816 // Spread the extra whitespaces
817 whitespacePatch = false;
818 // Do not break on leading whitespaces
819 leadingWhitespaces = false;
820 this->iterateThroughClustersInGlyphsOrder(false, true, [&](const Cluster* cluster, ClusterIndex index, bool ghost) {
821
822 if (ghost) {
823 if (cluster->run().leftToRight()) {
824 this->shiftCluster(cluster, ghostShift, ghostShift);
825 }
826 return true;
827 }
828
829 if (cluster->isWhitespaceBreak()) {
830 if (index == 0) {
831 leadingWhitespaces = true;
832 } else if (!whitespacePatch && !leadingWhitespaces) {
833 shift += step;
834 whitespacePatch = true;
835 --whitespacePatches;
836 }
837 } else if (cluster->isIdeographic()) {
838 if (!whitespacePatch && index != 0) {
839 shift += step;
840 --whitespacePatches;
841 }
842 whitespacePatch = false;
843 leadingWhitespaces = false;
844 } else {
845 whitespacePatch = false;
846 leadingWhitespaces = false;
847 }
848 this->shiftCluster(cluster, shift, prevShift);
849 prevShift = shift;
850 // We skip ideographic whitespaces
851 if (!cluster->isWhitespaceBreak() && cluster->isIdeographic()) {
852 shift += step;
853 whitespacePatch = true;
854 --whitespacePatches;
855 }
856 return true;
857 });
858
859 if (whitespacePatch && whitespacePatches < 0) {
860 whitespacePatches++;
861 shift -= step;
862 }
863
864 SkAssertResult(nearlyEqual(shift, maxWidth - textLen));
865 SkASSERT(whitespacePatches == 0);
866
867 this->fWidthWithSpaces += ghostShift;
868 this->fAdvance.fX = maxWidth;
869 }
870
shiftCluster(const Cluster * cluster,SkScalar shift,SkScalar prevShift)871 void TextLine::shiftCluster(const Cluster* cluster, SkScalar shift, SkScalar prevShift) {
872
873 auto& run = cluster->run();
874 auto start = cluster->startPos();
875 auto end = cluster->endPos();
876
877 if (end == run.size()) {
878 // Set the same shift for the fake last glyph (to avoid all extra checks)
879 ++end;
880 }
881
882 if (run.fJustificationShifts.empty()) {
883 // Do not fill this array until needed
884 run.fJustificationShifts.push_back_n(run.size() + 1, { 0, 0 });
885 }
886
887 for (size_t pos = start; pos < end; ++pos) {
888 run.fJustificationShifts[pos] = { shift, prevShift };
889 }
890 }
891
spacingCluster(const Cluster * cluster,SkScalar spacing,SkScalar prevSpacing)892 void TextLine::spacingCluster(const Cluster* cluster, SkScalar spacing, SkScalar prevSpacing) {
893 auto& run = cluster->run();
894 auto start = cluster->startPos();
895 auto end = cluster->endPos();
896 if (end == run.size()) {
897 // Set the same shift for the fake last glyph (to avoid all extra checks)
898 ++end;
899 }
900
901 if (run.fAutoSpacings.empty()) {
902 // Do not fill this array until needed
903 run.fAutoSpacings.push_back_n(run.size() + 1, { 0, 0 });
904 }
905
906 for (size_t pos = start; pos < end; ++pos) {
907 run.fAutoSpacings[pos] = { spacing, prevSpacing};
908 }
909 }
910
countWord(int & wordCount,bool & inWord)911 void TextLine::countWord(int& wordCount, bool& inWord) {
912 for (auto clusterIndex = fGhostClusterRange.start; clusterIndex < fGhostClusterRange.end; ++clusterIndex) {
913 auto& cluster = fOwner->cluster(clusterIndex);
914 if (cluster.isWordBreak()) {
915 inWord = false;
916 } else if (!inWord) {
917 ++wordCount;
918 inWord = true;
919 }
920 }
921 }
922
ellipsisNotFitProcess(EllipsisModal ellipsisModal)923 void TextLine::ellipsisNotFitProcess(EllipsisModal ellipsisModal) {
924 if (fEllipsis) {
925 return;
926 }
927
928 // Weird situation: ellipsis does not fit; no ellipsis then
929 switch (ellipsisModal) {
930 case EllipsisModal::TAIL:
931 fClusterRange.end = fClusterRange.start;
932 fGhostClusterRange.end = fClusterRange.start;
933 fText.end = fText.start;
934 fTextIncludingNewlines.end = fTextIncludingNewlines.start;
935 fTextExcludingSpaces.end = fTextExcludingSpaces.start;
936 fAdvance.fX = 0;
937 break;
938 case EllipsisModal::HEAD:
939 fClusterRange.start = fClusterRange.end;
940 fGhostClusterRange.start = fClusterRange.end;
941 fText.start = fText.end;
942 fTextIncludingNewlines.start = fTextIncludingNewlines.end;
943 fTextExcludingSpaces.start = fTextExcludingSpaces.end;
944 fAdvance.fX = 0;
945 break;
946 default:
947 return;
948 }
949 }
950
createTailEllipsis(SkScalar maxWidth,const SkString & ellipsis,bool ltr,WordBreakType wordBreakType)951 void TextLine::createTailEllipsis(SkScalar maxWidth, const SkString& ellipsis, bool ltr, WordBreakType wordBreakType) {
952 // Replace some clusters with the ellipsis
953 // Go through the clusters in the reverse logical order
954 // taking off cluster by cluster until the ellipsis fits
955 SkScalar width = fAdvance.fX;
956 RunIndex lastRun = EMPTY_RUN;
957 std::unique_ptr<Run> ellipsisRun;
958 int wordCount = 0;
959 bool inWord = false;
960
961 countWord(wordCount, inWord);
962
963 bool iterForWord = false;
964
965 for (auto clusterIndex = fGhostClusterRange.end; clusterIndex > fGhostClusterRange.start; --clusterIndex) {
966 auto& cluster = fOwner->cluster(clusterIndex - 1);
967 // Shape the ellipsis if the run has changed
968 if (lastRun != cluster.runIndex()) {
969 ellipsisRun = this->shapeEllipsis(ellipsis, &cluster);
970 // We may need to continue
971 lastRun = cluster.runIndex();
972 }
973
974 if (!cluster.isWordBreak()) {
975 inWord = true;
976 } else if (inWord) {
977 --wordCount;
978 inWord = false;
979 }
980 // See if it fits
981 if (ellipsisRun && width + ellipsisRun->advance().fX > maxWidth) {
982 if (!cluster.isHardBreak()) {
983 width -= cluster.width();
984 }
985 // Continue if the ellipsis does not fit
986 iterForWord = (wordCount != 1 && wordBreakType != WordBreakType::BREAK_ALL && !cluster.isWordBreak());
987 if (std::floor(width) > 0) {
988 continue;
989 }
990 }
991
992 if (iterForWord && !cluster.isWordBreak()) {
993 width -= cluster.width();
994 if (std::floor(width) > 0) {
995 continue;
996 }
997 }
998
999 // Get the last run directions after clipping
1000 fEllipsisIndex = cluster.runIndex();
1001 fLastClipRunLtr = fOwner->run(fEllipsisIndex).leftToRight();
1002
1003 // We found enough room for the ellipsis
1004 fAdvance.fX = width;
1005 fEllipsis = std::move(ellipsisRun);
1006 fEllipsis->setOwner(fOwner);
1007 fTextRangeReplacedByEllipsis = TextRange(cluster.textRange().end, fOwner->text().size());
1008
1009 // Let's update the line
1010 fClusterRange.end = clusterIndex;
1011 fGhostClusterRange.end = fClusterRange.end;
1012 #ifdef OHOS_SUPPORT
1013 fEllipsis->fTextRange =
1014 TextRange(cluster.textRange().end, cluster.textRange().end + ellipsis.size());
1015 fEllipsis->fClusterStart = cluster.textRange().end;
1016 #else
1017 fEllipsis->fClusterStart = cluster.textRange().start;
1018 #endif
1019 fText.end = cluster.textRange().end;
1020 fTextIncludingNewlines.end = cluster.textRange().end;
1021 fTextExcludingSpaces.end = cluster.textRange().end;
1022
1023 if (SkScalarNearlyZero(width)) {
1024 fRunsInVisualOrder.reset();
1025 }
1026
1027 break;
1028 }
1029
1030 fWidthWithSpaces = width;
1031
1032 ellipsisNotFitProcess(EllipsisModal::TAIL);
1033 }
1034
1035 #ifdef OHOS_SUPPORT
createHeadEllipsis(SkScalar maxWidth,const SkString & ellipsis,bool)1036 void TextLine::createHeadEllipsis(SkScalar maxWidth, const SkString& ellipsis, bool) {
1037 if (fAdvance.fX <= maxWidth) {
1038 return;
1039 }
1040 SkScalar width = fAdvance.fX;
1041 std::unique_ptr<Run> ellipsisRun;
1042 RunIndex lastRun = EMPTY_RUN;
1043 for (auto clusterIndex = fGhostClusterRange.start; clusterIndex < fGhostClusterRange.end; ++clusterIndex) {
1044 auto& cluster = fOwner->cluster(clusterIndex);
1045 // Shape the ellipsis if the run has changed
1046 if (lastRun != cluster.runIndex()) {
1047 ellipsisRun = this->shapeEllipsis(ellipsis, &cluster);
1048 // We may need to continue
1049 lastRun = cluster.runIndex();
1050 }
1051 // See if it fits
1052 if (ellipsisRun && width + ellipsisRun->advance().fX > maxWidth) {
1053 width -= cluster.width();
1054 // Continue if the ellipsis does not fit
1055 if (std::floor(width) > 0) {
1056 continue;
1057 }
1058 }
1059
1060 // Get the last run directions after clipping
1061 fEllipsisIndex = cluster.runIndex();
1062 fLastClipRunLtr = fOwner->run(fEllipsisIndex).leftToRight();
1063
1064 // We found enough room for the ellipsis
1065 fAdvance.fX = width + ellipsisRun->advance().fX;
1066 fEllipsis = std::move(ellipsisRun);
1067 fEllipsis->setOwner(fOwner);
1068 fTextRangeReplacedByEllipsis = TextRange(0, cluster.textRange().start);
1069 fClusterRange.start = clusterIndex;
1070 fGhostClusterRange.start = fClusterRange.start;
1071 fEllipsis->fClusterStart = 0;
1072 fText.start = cluster.textRange().start;
1073 fTextIncludingNewlines.start = cluster.textRange().start;
1074 fTextExcludingSpaces.start = cluster.textRange().start;
1075 break;
1076 }
1077
1078 fWidthWithSpaces = width;
1079
1080 ellipsisNotFitProcess(EllipsisModal::HEAD);
1081 }
1082 #endif
1083
nextUtf8Unit(const char ** ptr,const char * end)1084 static inline SkUnichar nextUtf8Unit(const char** ptr, const char* end) {
1085 SkUnichar val = SkUTF::NextUTF8(ptr, end);
1086 return val < 0 ? 0xFFFD : val;
1087 }
1088
shapeEllipsis(const SkString & ellipsis,const Cluster * cluster)1089 std::unique_ptr<Run> TextLine::shapeEllipsis(const SkString& ellipsis, const Cluster* cluster) {
1090
1091 class ShapeHandler final : public SkShaper::RunHandler {
1092 public:
1093 ShapeHandler(SkScalar lineHeight, bool useHalfLeading, SkScalar baselineShift, const SkString& ellipsis)
1094 : fRun(nullptr), fLineHeight(lineHeight), fUseHalfLeading(useHalfLeading), fBaselineShift(baselineShift), fEllipsis(ellipsis) {}
1095 std::unique_ptr<Run> run() & { return std::move(fRun); }
1096
1097 private:
1098 void beginLine() override {}
1099
1100 void runInfo(const RunInfo&) override {}
1101
1102 void commitRunInfo() override {}
1103
1104 Buffer runBuffer(const RunInfo& info) override {
1105 SkASSERT(!fRun);
1106 fRun = std::make_unique<Run>(nullptr, info, 0, fLineHeight, fUseHalfLeading, fBaselineShift, 0, 0);
1107 return fRun->newRunBuffer();
1108 }
1109
1110 void commitRunBuffer(const RunInfo& info) override {
1111 fRun->fAdvance.fX = info.fAdvance.fX;
1112 fRun->fAdvance.fY = fRun->advance().fY;
1113 fRun->fPlaceholderIndex = std::numeric_limits<size_t>::max();
1114 fRun->fEllipsis = true;
1115 }
1116
1117 void commitLine() override {}
1118
1119 std::unique_ptr<Run> fRun;
1120 SkScalar fLineHeight;
1121 bool fUseHalfLeading;
1122 SkScalar fBaselineShift;
1123 SkString fEllipsis;
1124 };
1125
1126 const Run& run = cluster->run();
1127 TextStyle textStyle = fOwner->paragraphStyle().getTextStyle();
1128 for (auto i = fBlockRange.start; i < fBlockRange.end; ++i) {
1129 auto& block = fOwner->block(i);
1130 if (run.leftToRight() && cluster->textRange().end <= block.fRange.end) {
1131 textStyle = block.fStyle;
1132 break;
1133 } else if (!run.leftToRight() && cluster->textRange().start <= block.fRange.end) {
1134 textStyle = block.fStyle;
1135 break;
1136 }
1137 }
1138
1139 #ifndef USE_SKIA_TXT
1140 auto shaped = [&](sk_sp<SkTypeface> typeface, bool fallback) -> std::unique_ptr<Run> {
1141 #else
1142 auto shaped = [&](std::shared_ptr<RSTypeface> typeface, bool fallback) -> std::unique_ptr<Run> {
1143 #endif
1144 ShapeHandler handler(run.heightMultiplier(), run.useHalfLeading(), run.baselineShift(), ellipsis);
1145 #ifndef USE_SKIA_TXT
1146 SkFont font(typeface, textStyle.getFontSize());
1147 font.setEdging(SkFont::Edging::kAntiAlias);
1148 font.setHinting(SkFontHinting::kSlight);
1149 font.setSubpixel(true);
1150 #else
1151 RSFont font(typeface, textStyle.getFontSize(), 1, 0);
1152 font.SetEdging(RSDrawing::FontEdging::ANTI_ALIAS);
1153 font.SetHinting(RSDrawing::FontHinting::SLIGHT);
1154 font.SetSubpixel(true);
1155 #endif
1156
1157 #ifndef USE_SKIA_TXT
1158 std::unique_ptr<SkShaper> shaper = SkShaper::MakeShapeDontWrapOrReorder(
1159 fOwner->getUnicode()->copy(),
1160 fallback ? SkFontMgr::RefDefault() : SkFontMgr::RefEmpty());
1161 #else
1162 std::unique_ptr<SkShaper> shaper = SkShaper::MakeShapeDontWrapOrReorder(
1163 fOwner->getUnicode()->copy(),
1164 fallback ? RSFontMgr::CreateDefaultFontMgr() : RSFontMgr::CreateDefaultFontMgr());
1165 #endif
1166 shaper->shape(ellipsis.c_str(),
1167 ellipsis.size(),
1168 font,
1169 true,
1170 std::numeric_limits<SkScalar>::max(),
1171 &handler);
1172 auto ellipsisRun = handler.run();
1173 ellipsisRun->fTextRange = TextRange(0, ellipsis.size());
1174 ellipsisRun->fOwner = fOwner;
1175 return ellipsisRun;
1176 };
1177
1178 // Check all allowed fonts
1179 auto typefaces = fOwner->fontCollection()->findTypefaces(
1180 textStyle.getFontFamilies(), textStyle.getFontStyle(), textStyle.getFontArguments());
1181 for (const auto& typeface : typefaces) {
1182 auto ellipsisRun = shaped(typeface, false);
1183 if (ellipsisRun->isResolved()) {
1184 return ellipsisRun;
1185 }
1186 }
1187
1188 // Try the fallback
1189 if (fOwner->fontCollection()->fontFallbackEnabled()) {
1190 const char* ch = ellipsis.c_str();
1191 SkUnichar unicode = nextUtf8Unit(&ch, ellipsis.c_str() + ellipsis.size());
1192
1193 auto typeface = fOwner->fontCollection()->defaultFallback(
1194 unicode, textStyle.getFontStyle(), textStyle.getLocale());
1195 if (typeface) {
1196 if (textStyle.getFontArguments()) {
1197 typeface = fOwner->fontCollection()->CloneTypeface(typeface, textStyle.getFontArguments());
1198 }
1199 auto ellipsisRun = shaped(typeface, true);
1200 if (ellipsisRun->isResolved()) {
1201 return ellipsisRun;
1202 }
1203 }
1204 }
1205
1206 // Check the current font
1207 #ifndef USE_SKIA_TXT
1208 auto ellipsisRun = shaped(run.fFont.refTypeface(), false);
1209 #else
1210 auto ellipsisRun = shaped(const_cast<RSFont&>(run.fFont).GetTypeface(), false);
1211 #endif
1212 if (ellipsisRun->isResolved()) {
1213 return ellipsisRun;
1214 }
1215 return ellipsisRun;
1216 }
1217
1218 #ifdef OHOS_SUPPORT
1219 void TextLine::measureTextWithSpacesAtTheEnd(ClipContext& context, bool includeGhostSpaces) const
1220 {
1221 if (compareRound(context.clip.fRight, fAdvance.fX, fOwner->getApplyRoundingHack()) > 0 && !includeGhostSpaces &&
1222 fAdvance.fX > 0) {
1223 // There are few cases when we need it.
1224 // The most important one: we measure the text with spaces at the end (or at the beginning in RTL)
1225 // and we should ignore these spaces
1226 if (fOwner->paragraphStyle().getTextDirection() == TextDirection::kLtr) {
1227 // We only use this member for LTR
1228 context.fExcludedTrailingSpaces = std::max(context.clip.fRight - fAdvance.fX, 0.0f);
1229 context.clippingNeeded = true;
1230 context.clip.fRight = fAdvance.fX;
1231 }
1232 }
1233 }
1234 #endif
1235
1236 TextLine::ClipContext TextLine::measureTextInsideOneRun(TextRange textRange,
1237 const Run* run,
1238 SkScalar runOffsetInLine,
1239 SkScalar textOffsetInRunInLine,
1240 bool includeGhostSpaces,
1241 TextAdjustment textAdjustment) const {
1242 ClipContext result = { run, 0, run->size(), 0, SkRect::MakeEmpty(), 0, false };
1243
1244 if (run->fEllipsis) {
1245 // Both ellipsis and placeholders can only be measured as one glyph
1246 result.fTextShift = runOffsetInLine;
1247 result.clip = SkRect::MakeXYWH(runOffsetInLine,
1248 sizes().runTop(run, this->fAscentStyle),
1249 run->advance().fX,
1250 run->calculateHeight(this->fAscentStyle,this->fDescentStyle));
1251 return result;
1252 } else if (run->isPlaceholder()) {
1253 result.fTextShift = runOffsetInLine;
1254 if (SkScalarIsFinite(run->fFontMetrics.fAscent)) {
1255 result.clip = SkRect::MakeXYWH(runOffsetInLine,
1256 sizes().runTop(run, this->fAscentStyle),
1257 run->advance().fX,
1258 run->calculateHeight(this->fAscentStyle,this->fDescentStyle));
1259 } else {
1260 result.clip = SkRect::MakeXYWH(runOffsetInLine, run->fFontMetrics.fAscent, run->advance().fX, 0);
1261 }
1262 return result;
1263 } else if (textRange.empty()) {
1264 return result;
1265 }
1266
1267 TextRange originalTextRange(textRange); // We need it for proportional measurement
1268 // Find [start:end] clusters for the text
1269 while (true) {
1270 // Update textRange by cluster edges (shift start up to the edge of the cluster)
1271 // TODO: remove this limitation?
1272 TextRange updatedTextRange;
1273 bool found;
1274 std::tie(found, updatedTextRange.start, updatedTextRange.end) =
1275 run->findLimitingGlyphClusters(textRange);
1276 if (!found) {
1277 return result;
1278 }
1279
1280 if ((textAdjustment & TextAdjustment::Grapheme) == 0) {
1281 textRange = updatedTextRange;
1282 break;
1283 }
1284
1285 // Update text range by grapheme edges (shift start up to the edge of the grapheme)
1286 std::tie(found, updatedTextRange.start, updatedTextRange.end) =
1287 run->findLimitingGraphemes(updatedTextRange);
1288 if (updatedTextRange == textRange) {
1289 break;
1290 }
1291
1292 // Some clusters are inside graphemes and we need to adjust them
1293 //SkDebugf("Correct range: [%d:%d) -> [%d:%d)\n", textRange.start, textRange.end, startIndex, endIndex);
1294 textRange = updatedTextRange;
1295
1296 // Move the start until it's on the grapheme edge (and glypheme, too)
1297 }
1298 Cluster* start = &fOwner->cluster(fOwner->clusterIndex(textRange.start));
1299 Cluster* end = &fOwner->cluster(fOwner->clusterIndex(textRange.end - (textRange.width() == 0 ? 0 : 1)));
1300
1301 if (!run->leftToRight()) {
1302 std::swap(start, end);
1303 }
1304 result.pos = start->startPos();
1305 result.size = (end->isHardBreak() ? end->startPos() : end->endPos()) - start->startPos();
1306 auto textStartInRun = run->positionX(start->startPos());
1307 auto textStartInLine = runOffsetInLine + textOffsetInRunInLine;
1308 if (!run->leftToRight()) {
1309 std::swap(start, end);
1310 }
1311 /*
1312 if (!run->fJustificationShifts.empty()) {
1313 SkDebugf("Justification for [%d:%d)\n", textRange.start, textRange.end);
1314 for (auto i = result.pos; i < result.pos + result.size; ++i) {
1315 auto j = run->fJustificationShifts[i];
1316 SkDebugf("[%d] = %f %f\n", i, j.fX, j.fY);
1317 }
1318 }
1319 */
1320 // Calculate the clipping rectangle for the text with cluster edges
1321 // There are 2 cases:
1322 // EOL (when we expect the last cluster clipped without any spaces)
1323 // Anything else (when we want the cluster width contain all the spaces -
1324 // coming from letter spacing or word spacing or justification)
1325 result.clip =
1326 SkRect::MakeXYWH(0,
1327 sizes().runTop(run, this->fAscentStyle),
1328 run->calculateWidth(result.pos, result.pos + result.size, false),
1329 run->calculateHeight(this->fAscentStyle,this->fDescentStyle));
1330
1331 // Correct the width in case the text edges don't match clusters
1332 // TODO: This is where we get smart about selecting a part of a cluster
1333 // by shaping each grapheme separately and then use the result sizes
1334 // to calculate the proportions
1335 auto leftCorrection = start->sizeToChar(originalTextRange.start);
1336 auto rightCorrection = end->sizeFromChar(originalTextRange.end - 1);
1337 /*
1338 SkDebugf("[%d: %d) => [%d: %d), @%d, %d: [%f:%f) + [%f:%f) = ", // جَآَهُ
1339 originalTextRange.start, originalTextRange.end, textRange.start, textRange.end,
1340 result.pos, result.size,
1341 result.clip.fLeft, result.clip.fRight, leftCorrection, rightCorrection);
1342 */
1343 result.clippingNeeded = leftCorrection != 0 || rightCorrection != 0;
1344 if (run->leftToRight()) {
1345 result.clip.fLeft += leftCorrection;
1346 result.clip.fRight -= rightCorrection;
1347 textStartInLine -= leftCorrection;
1348 } else {
1349 result.clip.fRight -= leftCorrection;
1350 result.clip.fLeft += rightCorrection;
1351 textStartInLine -= rightCorrection;
1352 }
1353
1354 result.clip.offset(textStartInLine, 0);
1355 //SkDebugf("@%f[%f:%f)\n", textStartInLine, result.clip.fLeft, result.clip.fRight);
1356
1357 #ifdef OHOS_SUPPORT
1358 measureTextWithSpacesAtTheEnd(result, includeGhostSpaces);
1359 #else
1360 if (compareRound(result.clip.fRight, fAdvance.fX, fOwner->getApplyRoundingHack()) > 0 && !includeGhostSpaces) {
1361 // There are few cases when we need it.
1362 // The most important one: we measure the text with spaces at the end (or at the beginning in RTL)
1363 // and we should ignore these spaces
1364 if (fOwner->paragraphStyle().getTextDirection() == TextDirection::kLtr) {
1365 // We only use this member for LTR
1366 result.fExcludedTrailingSpaces = std::max(result.clip.fRight - fAdvance.fX, 0.0f);
1367 result.clippingNeeded = true;
1368 result.clip.fRight = fAdvance.fX;
1369 }
1370 }
1371 #endif
1372
1373 #ifndef OHOS_SUPPORT
1374 if (result.clip.width() < 0) {
1375 // Weird situation when glyph offsets move the glyph to the left
1376 // (happens with zalgo texts, for instance)
1377 result.clip.fRight = result.clip.fLeft;
1378 }
1379 #endif
1380
1381 // The text must be aligned with the lineOffset
1382 result.fTextShift = textStartInLine - textStartInRun;
1383
1384 return result;
1385 }
1386
1387 void TextLine::iterateThroughClustersInGlyphsOrder(bool reversed,
1388 bool includeGhosts,
1389 const ClustersVisitor& visitor) const {
1390 // Walk through the clusters in the logical order (or reverse)
1391 SkSpan<const size_t> runs(fRunsInVisualOrder.data(), fRunsInVisualOrder.size());
1392 bool ignore = false;
1393 ClusterIndex index = 0;
1394 directional_for_each(runs, !reversed, [&](decltype(runs[0]) r) {
1395 if (ignore) return;
1396 auto run = this->fOwner->run(r);
1397 auto trimmedRange = fClusterRange.intersection(run.clusterRange());
1398 auto trailedRange = fGhostClusterRange.intersection(run.clusterRange());
1399 SkASSERT(trimmedRange.start == trailedRange.start);
1400
1401 auto trailed = fOwner->clusters(trailedRange);
1402 auto trimmed = fOwner->clusters(trimmedRange);
1403 directional_for_each(trailed, reversed != run.leftToRight(), [&](Cluster& cluster) {
1404 if (ignore) return;
1405 bool ghost = &cluster >= trimmed.end();
1406 if (!includeGhosts && ghost) {
1407 return;
1408 }
1409 if (!visitor(&cluster, index++, ghost)) {
1410
1411 ignore = true;
1412 return;
1413 }
1414 });
1415 });
1416 }
1417
1418 #ifdef OHOS_SUPPORT
1419 void TextLine::computeNextPaintGlyphRange(ClipContext& context,
1420 const TextRange& lastGlyphRange, StyleType styleType) const
1421 {
1422 if (styleType != StyleType::kForeground) {
1423 return;
1424 }
1425 TextRange curGlyphRange = TextRange(context.pos, context.pos + context.size);
1426 auto intersect = intersected(lastGlyphRange, curGlyphRange);
1427 if (intersect == EMPTY_TEXT || (intersect.start != curGlyphRange.start && intersect.end != curGlyphRange.end)) {
1428 return;
1429 }
1430 if (intersect.start == curGlyphRange.start) {
1431 curGlyphRange = TextRange(intersect.end, curGlyphRange.end);
1432 } else if (intersect.end == curGlyphRange.end) {
1433 curGlyphRange = TextRange(curGlyphRange.start, intersect.start);
1434 }
1435
1436 context.pos = curGlyphRange.start;
1437 context.size = curGlyphRange.width();
1438 }
1439 #endif
1440
1441 SkScalar TextLine::iterateThroughSingleRunByStyles(TextAdjustment textAdjustment,
1442 const Run* run,
1443 SkScalar runOffset,
1444 TextRange textRange,
1445 StyleType styleType,
1446 const RunStyleVisitor& visitor) const {
1447 auto includeGhostSpaces = (styleType == StyleType::kDecorations || styleType == StyleType::kBackground ||
1448 styleType == StyleType::kNone);
1449 auto correctContext = [&](TextRange textRange, SkScalar textOffsetInRun) -> ClipContext {
1450 auto result = this->measureTextInsideOneRun(
1451 textRange, run, runOffset, textOffsetInRun, includeGhostSpaces, textAdjustment);
1452 if (styleType == StyleType::kDecorations) {
1453 // Decorations are drawn based on the real font metrics (regardless of styles and strut)
1454 result.clip.fTop = this->sizes().runTop(run, LineMetricStyle::CSS) - run->baselineShift();
1455 result.clip.fBottom = result.clip.fTop +
1456 run->calculateHeight(LineMetricStyle::CSS, LineMetricStyle::CSS);
1457 }
1458 return result;
1459 };
1460
1461 if (run->fEllipsis) {
1462 // Extra efforts to get the ellipsis text style
1463 ClipContext clipContext = correctContext(run->textRange(), 0.0f);
1464 for (BlockIndex index = fBlockRange.start; index < fBlockRange.end; ++index) {
1465 auto block = fOwner->styles().begin() + index;
1466 #ifdef OHOS_SUPPORT
1467 TextRange intersect = intersected(block->fRange,
1468 TextRange(fEllipsis->textRange().start - 1, fEllipsis->textRange().end));
1469 if (intersect.width() > 0) {
1470 visitor(fTextRangeReplacedByEllipsis, block->fStyle, clipContext);
1471 return run->advance().fX;
1472 }
1473 #else
1474 if (block->fRange.start >= run->fClusterStart && block->fRange.end < run->fClusterStart) {
1475 visitor(fTextRangeReplacedByEllipsis, block->fStyle, clipContext);
1476 return run->advance().fX;
1477 }
1478 #endif
1479 }
1480 SkASSERT(false);
1481 }
1482
1483 if (styleType == StyleType::kNone) {
1484 ClipContext clipContext = correctContext(textRange, 0.0f);
1485 #ifdef OHOS_SUPPORT
1486 if (clipContext.clip.height() > 0 || (run->isPlaceholder() && clipContext.clip.height() == 0)) {
1487 #else
1488 if (clipContext.clip.height() > 0) {
1489 #endif
1490 visitor(textRange, TextStyle(), clipContext);
1491 return clipContext.clip.width();
1492 } else {
1493 return 0;
1494 }
1495 }
1496
1497 TextIndex start = EMPTY_INDEX;
1498 size_t size = 0;
1499 const TextStyle* prevStyle = nullptr;
1500 SkScalar textOffsetInRun = 0;
1501 #ifdef OHOS_SUPPORT
1502 TextRange lastGlyphRange = EMPTY_TEXT;
1503 #endif
1504 const BlockIndex blockRangeSize = fBlockRange.end - fBlockRange.start;
1505 for (BlockIndex index = 0; index <= blockRangeSize; ++index) {
1506
1507 TextRange intersect;
1508 TextStyle* style = nullptr;
1509 if (index < blockRangeSize) {
1510 auto block = fOwner->styles().begin() +
1511 (run->leftToRight() ? fBlockRange.start + index : fBlockRange.end - index - 1);
1512
1513 // Get the text
1514 intersect = intersected(block->fRange, textRange);
1515 if (intersect.width() == 0) {
1516 if (start == EMPTY_INDEX) {
1517 // This style is not applicable to the text yet
1518 continue;
1519 } else {
1520 // We have found all the good styles already
1521 // but we need to process the last one of them
1522 intersect = TextRange(start, start + size);
1523 index = fBlockRange.end;
1524 }
1525 } else {
1526 // Get the style
1527 style = &block->fStyle;
1528 if (start != EMPTY_INDEX && style->matchOneAttribute(styleType, *prevStyle)) {
1529 size += intersect.width();
1530 // RTL text intervals move backward
1531 start = std::min(intersect.start, start);
1532 continue;
1533 } else if (start == EMPTY_INDEX ) {
1534 // First time only
1535 prevStyle = style;
1536 size = intersect.width();
1537 start = intersect.start;
1538 continue;
1539 }
1540 }
1541 } else if (prevStyle != nullptr) {
1542 // This is the last style
1543 } else {
1544 break;
1545 }
1546
1547 // We have the style and the text
1548 auto runStyleTextRange = TextRange(start, start + size);
1549 ClipContext clipContext = correctContext(runStyleTextRange, textOffsetInRun);
1550 textOffsetInRun += clipContext.clip.width();
1551 if (clipContext.clip.height() == 0) {
1552 continue;
1553 }
1554
1555 RectStyle temp;
1556 if (styleType == StyleType::kBackground &&
1557 prevStyle->getBackgroundRect() != temp &&
1558 prevStyle->getHeight() != 0) {
1559 #ifdef OHOS_SUPPORT
1560 clipContext.clip.fTop = run->fFontMetrics.fAscent + this->baseline();
1561 #else
1562 clipContext.clip.fTop = run->fFontMetrics.fAscent - run->fCorrectAscent;
1563 #endif
1564 clipContext.clip.fBottom = clipContext.clip.fTop + run->fFontMetrics.fDescent -
1565 run->fFontMetrics.fAscent;
1566 }
1567 #ifdef OHOS_SUPPORT
1568 computeNextPaintGlyphRange(clipContext, lastGlyphRange, styleType);
1569 if (clipContext.size != 0) {
1570 lastGlyphRange = TextRange(clipContext.pos, clipContext.pos + clipContext.size);
1571 }
1572 #endif
1573 visitor(runStyleTextRange, *prevStyle, clipContext);
1574
1575 // Start all over again
1576 prevStyle = style;
1577 start = intersect.start;
1578 size = intersect.width();
1579 }
1580 return textOffsetInRun;
1581 }
1582
1583 #ifdef OHOS_SUPPORT
1584 bool TextLine::processEllipsisRun(bool& isAlreadyUseEllipsis,
1585 SkScalar& runOffset,
1586 EllipsisReadStrategy ellipsisReadStrategy,
1587 const RunVisitor& visitor,
1588 SkScalar& runWidthInLine) const {
1589 isAlreadyUseEllipsis = true;
1590 runOffset += this->ellipsis()->offset().fX;
1591 if (ellipsisReadStrategy == EllipsisReadStrategy::READ_REPLACED_WORD) {
1592 if (!visitor(ellipsis(), runOffset, fTextRangeReplacedByEllipsis, &runWidthInLine)) {
1593 LOGE("Visitor process ellipsis replace word error!");
1594 return false;
1595 }
1596 } else if (ellipsisReadStrategy == EllipsisReadStrategy::READ_ELLIPSIS_WORD) {
1597 if (!visitor(ellipsis(), runOffset, ellipsis()->textRange(), &runWidthInLine)) {
1598 LOGE("Visitor process ellipsis word error!");
1599 return false;
1600 }
1601 } else {
1602 runWidthInLine = this->ellipsis()->advance().fX;
1603 }
1604 return true;
1605 }
1606 #endif
1607
1608 #ifdef OHOS_SUPPORT
1609 void TextLine::iterateThroughVisualRuns(EllipsisReadStrategy ellipsisReadStrategy,
1610 bool includingGhostSpaces,
1611 const RunVisitor& visitor) const {
1612 // Walk through all the runs that intersect with the line in visual order
1613 SkScalar width = 0;
1614 SkScalar runOffset = 0;
1615 SkScalar totalWidth = 0;
1616 bool ellipsisModeIsHead = fOwner->paragraphStyle().getEllipsisMod() == EllipsisModal::HEAD;
1617 bool isAlreadyUseEllipsis = false;
1618 auto textRange = includingGhostSpaces ? this->textWithNewlines() : this->trimmedText();
1619
1620 if (fRunsInVisualOrder.size() == 0 && fEllipsis != nullptr) {
1621 if (!processEllipsisRun(isAlreadyUseEllipsis, runOffset, ellipsisReadStrategy, visitor, width)) {
1622 return;
1623 }
1624 totalWidth += width;
1625 }
1626
1627 for (auto& runIndex : fRunsInVisualOrder) {
1628 // add the lastClipRun's left ellipsis if necessary
1629 if (!isAlreadyUseEllipsis && fEllipsisIndex == runIndex &&
1630 ((!fLastClipRunLtr && !ellipsisModeIsHead) || (ellipsisModeIsHead && fLastClipRunLtr))) {
1631 if (!processEllipsisRun(isAlreadyUseEllipsis, runOffset, ellipsisReadStrategy, visitor, width)) {
1632 return;
1633 }
1634 runOffset += width;
1635 totalWidth += width;
1636 }
1637
1638 const auto run = &this->fOwner->run(runIndex);
1639 auto lineIntersection = intersected(run->textRange(), textRange);
1640 if (lineIntersection.width() == 0 && this->width() != 0) {
1641 // TODO: deal with empty runs in a better way
1642 continue;
1643 }
1644 if (!run->leftToRight() && runOffset == 0 && includingGhostSpaces) {
1645 // runOffset does not take in account a possibility
1646 // that RTL run could start before the line (trailing spaces)
1647 // so we need to do runOffset -= "trailing whitespaces length"
1648 TextRange whitespaces = intersected(
1649 TextRange(fTextExcludingSpaces.end, fTextIncludingNewlines.end), run->fTextRange);
1650 if (whitespaces.width() > 0) {
1651 auto whitespacesLen = measureTextInsideOneRun(whitespaces, run, runOffset, 0, true,
1652 TextAdjustment::GlyphCluster).clip.width();
1653 runOffset -= whitespacesLen;
1654 }
1655 }
1656
1657 if (!visitor(run, runOffset, lineIntersection, &width)) {
1658 return;
1659 }
1660
1661 runOffset += width;
1662 totalWidth += width;
1663
1664 // add the lastClipRun's right ellipsis if necessary
1665 if (!isAlreadyUseEllipsis && fEllipsisIndex == runIndex) {
1666 if (!processEllipsisRun(isAlreadyUseEllipsis, runOffset, ellipsisReadStrategy, visitor, width)) {
1667 return;
1668 }
1669 runOffset += width;
1670 totalWidth += width;
1671 }
1672 }
1673
1674 if (!includingGhostSpaces && compareRound(totalWidth, this->width(), fOwner->getApplyRoundingHack()) != 0) {
1675 // This is a very important assert!
1676 // It asserts that 2 different ways of calculation come with the same results
1677 SkDebugf("ASSERT: %f != %f\n", totalWidth, this->width());
1678 SkASSERT(false);
1679 }
1680 }
1681 #else
1682 void TextLine::iterateThroughVisualRuns(bool includingGhostSpaces, const RunVisitor& visitor) const {
1683
1684 // Walk through all the runs that intersect with the line in visual order
1685 SkScalar width = 0;
1686 SkScalar runOffset = 0;
1687 SkScalar totalWidth = 0;
1688 auto textRange = includingGhostSpaces ? this->textWithNewlines() : this->trimmedText();
1689 for (auto& runIndex : fRunsInVisualOrder) {
1690
1691 const auto run = &this->fOwner->run(runIndex);
1692 auto lineIntersection = intersected(run->textRange(), textRange);
1693 if (lineIntersection.width() == 0 && this->width() != 0) {
1694 continue;
1695 }
1696 if (!run->leftToRight() && runOffset == 0 && includingGhostSpaces) {
1697 // runOffset does not take in account a possibility
1698 // that RTL run could start before the line (trailing spaces)
1699 // so we need to do runOffset -= "trailing whitespaces length"
1700 TextRange whitespaces = intersected(
1701 TextRange(fTextExcludingSpaces.end, fTextIncludingNewlines.end), run->fTextRange);
1702 if (whitespaces.width() > 0) {
1703 auto whitespacesLen = measureTextInsideOneRun(whitespaces, run, runOffset, 0, true, false).clip.width();
1704 runOffset -= whitespacesLen;
1705 }
1706 }
1707 runOffset += width;
1708 totalWidth += width;
1709 if (!visitor(run, runOffset, lineIntersection, &width)) {
1710 return;
1711 }
1712 }
1713
1714 runOffset += width;
1715 totalWidth += width;
1716
1717 if (this->ellipsis() != nullptr) {
1718 if (visitor(ellipsis(), runOffset, ellipsis()->textRange(), &width)) {
1719 totalWidth += width;
1720 }
1721 }
1722
1723 // This is a very important assert!
1724 // It asserts that 2 different ways of calculation come with the same results
1725 if (!includingGhostSpaces && compareRound(totalWidth, this->width()) != 0) {
1726 SkDebugf("ASSERT: %f != %f\n", totalWidth, this->width());
1727 SkASSERT(false);
1728 }
1729 }
1730 #endif
1731
1732 SkVector TextLine::offset() const {
1733 return fOffset + SkVector::Make(fShift, 0);
1734 }
1735
1736 LineMetrics TextLine::getMetrics() const {
1737 LineMetrics result;
1738
1739 // Fill out the metrics
1740 fOwner->ensureUTF16Mapping();
1741 result.fStartIndex = fOwner->getUTF16Index(fTextExcludingSpaces.start);
1742 result.fEndExcludingWhitespaces = fOwner->getUTF16Index(fTextExcludingSpaces.end);
1743 result.fEndIndex = fOwner->getUTF16Index(fText.end);
1744 result.fEndIncludingNewline = fOwner->getUTF16Index(fTextIncludingNewlines.end);
1745 result.fHardBreak = endsWithHardLineBreak();
1746 result.fAscent = - fMaxRunMetrics.ascent();
1747 result.fDescent = fMaxRunMetrics.descent();
1748 result.fUnscaledAscent = - fMaxRunMetrics.ascent(); // TODO: implement
1749 result.fHeight = fAdvance.fY;
1750 result.fWidth = fAdvance.fX;
1751 if (fOwner->getApplyRoundingHack()) {
1752 result.fHeight = littleRound(result.fHeight);
1753 result.fWidth = littleRound(result.fWidth);
1754 }
1755 result.fLeft = this->offset().fX;
1756 // This is Flutter definition of a baseline
1757 result.fBaseline = this->offset().fY + this->height() - this->sizes().descent();
1758 result.fLineNumber = this - fOwner->lines().begin();
1759 result.fWidthWithSpaces = fWidthWithSpaces;
1760 result.fTopHeight = this->offset().fY;
1761
1762 // Fill out the style parts
1763 #ifdef OHOS_SUPPORT
1764 this->iterateThroughVisualRuns(EllipsisReadStrategy::READ_REPLACED_WORD, false,
1765 #else
1766 this->iterateThroughVisualRuns(false,
1767 #endif
1768 [this, &result]
1769 (const Run* run, SkScalar runOffsetInLine, TextRange textRange, SkScalar* runWidthInLine) {
1770 if (run->placeholderStyle() != nullptr) {
1771 *runWidthInLine = run->advance().fX;
1772 return true;
1773 }
1774 *runWidthInLine = this->iterateThroughSingleRunByStyles(
1775 TextAdjustment::GlyphCluster, run, runOffsetInLine, textRange, StyleType::kForeground,
1776 [&result, &run](TextRange textRange, const TextStyle& style, const ClipContext& context) {
1777 #ifndef USE_SKIA_TXT
1778 SkFontMetrics fontMetrics;
1779 run->fFont.getMetrics(&fontMetrics);
1780 #else
1781 RSFontMetrics fontMetrics;
1782 run->fFont.GetMetrics(&fontMetrics);
1783 #endif
1784 #ifdef OHOS_SUPPORT
1785 auto decompressFont = run->fFont;
1786 scaleFontWithCompressionConfig(decompressFont, ScaleOP::DECOMPRESS);
1787 metricsIncludeFontPadding(&fontMetrics, decompressFont);
1788 #endif
1789 StyleMetrics styleMetrics(&style, fontMetrics);
1790 result.fLineMetrics.emplace(textRange.start, styleMetrics);
1791 });
1792 return true;
1793 });
1794
1795 return result;
1796 }
1797
1798 bool TextLine::isFirstLine() const {
1799 return this == &fOwner->lines().front();
1800 }
1801
1802 bool TextLine::isLastLine() const {
1803 return this == &fOwner->lines().back();
1804 }
1805
1806 bool TextLine::endsWithHardLineBreak() const {
1807 // TODO: For some reason Flutter imagines a hard line break at the end of the last line.
1808 // To be removed...
1809 return (fGhostClusterRange.width() > 0 && fOwner->cluster(fGhostClusterRange.end - 1).isHardBreak()) ||
1810 fEllipsis != nullptr ||
1811 fGhostClusterRange.end == fOwner->clusters().size() - 1;
1812 }
1813
1814 void TextLine::getRectsForRange(TextRange textRange0,
1815 RectHeightStyle rectHeightStyle,
1816 RectWidthStyle rectWidthStyle,
1817 std::vector<TextBox>& boxes) const
1818 {
1819 const Run* lastRun = nullptr;
1820 auto startBox = boxes.size();
1821 #ifdef OHOS_SUPPORT
1822 this->iterateThroughVisualRuns(EllipsisReadStrategy::READ_REPLACED_WORD, true,
1823 #else
1824 this->iterateThroughVisualRuns(true,
1825 #endif
1826 [textRange0, rectHeightStyle, rectWidthStyle, &boxes, &lastRun, startBox, this]
1827 (const Run* run, SkScalar runOffsetInLine, TextRange textRange, SkScalar* runWidthInLine) {
1828 *runWidthInLine = this->iterateThroughSingleRunByStyles(
1829 TextAdjustment::GraphemeGluster, run, runOffsetInLine, textRange, StyleType::kNone,
1830 [run, runOffsetInLine, textRange0, rectHeightStyle, rectWidthStyle, &boxes, &lastRun, startBox, this]
1831 (TextRange textRange, const TextStyle& style, const TextLine::ClipContext& lineContext) {
1832
1833 auto intersect = textRange * textRange0;
1834 if (intersect.empty()) {
1835 return true;
1836 }
1837
1838 auto paragraphStyle = fOwner->paragraphStyle();
1839
1840 // Found a run that intersects with the text
1841 auto context = this->measureTextInsideOneRun(
1842 intersect, run, runOffsetInLine, 0, true, TextAdjustment::GraphemeGluster);
1843 SkRect clip = context.clip;
1844 clip.offset(lineContext.fTextShift - context.fTextShift, 0);
1845
1846 switch (rectHeightStyle) {
1847 case RectHeightStyle::kMax:
1848 // TODO: Change it once flutter rolls into google3
1849 // (probably will break things if changed before)
1850 clip.fBottom = this->height();
1851 clip.fTop = this->sizes().delta();
1852 break;
1853 case RectHeightStyle::kIncludeLineSpacingTop: {
1854 clip.fBottom = this->height();
1855 clip.fTop = this->sizes().delta();
1856 auto verticalShift = this->sizes().rawAscent() - this->sizes().ascent();
1857 if (isFirstLine()) {
1858 clip.fTop += verticalShift;
1859 }
1860 break;
1861 }
1862 case RectHeightStyle::kIncludeLineSpacingMiddle: {
1863 clip.fBottom = this->height();
1864 clip.fTop = this->sizes().delta();
1865 auto verticalShift = this->sizes().rawAscent() - this->sizes().ascent();
1866 clip.offset(0, verticalShift / 2.0);
1867 if (isFirstLine()) {
1868 clip.fTop += verticalShift / 2.0;
1869 }
1870 if (isLastLine()) {
1871 clip.fBottom -= verticalShift / 2.0;
1872 }
1873 break;
1874 }
1875 case RectHeightStyle::kIncludeLineSpacingBottom: {
1876 clip.fBottom = this->height();
1877 clip.fTop = this->sizes().delta();
1878 auto verticalShift = this->sizes().rawAscent() - this->sizes().ascent();
1879 clip.offset(0, verticalShift);
1880 if (isLastLine()) {
1881 clip.fBottom -= verticalShift;
1882 }
1883 break;
1884 }
1885 case RectHeightStyle::kStrut: {
1886 const auto& strutStyle = paragraphStyle.getStrutStyle();
1887 if (strutStyle.getStrutEnabled()
1888 && strutStyle.getFontSize() > 0) {
1889 auto strutMetrics = fOwner->strutMetrics();
1890 auto top = this->baseline();
1891 clip.fTop = top + strutMetrics.ascent();
1892 clip.fBottom = top + strutMetrics.descent();
1893 }
1894 }
1895 break;
1896 case RectHeightStyle::kTight: {
1897 if (run->fHeightMultiplier <= 0) {
1898 break;
1899 }
1900 const auto effectiveBaseline = this->baseline() + this->sizes().delta();
1901 clip.fTop = effectiveBaseline + run->ascent();
1902 clip.fBottom = effectiveBaseline + run->descent();
1903 }
1904 break;
1905 default:
1906 SkASSERT(false);
1907 break;
1908 }
1909
1910 // Separate trailing spaces and move them in the default order of the paragraph
1911 // in case the run order and the paragraph order don't match
1912 SkRect trailingSpaces = SkRect::MakeEmpty();
1913 if (this->trimmedText().end <this->textWithNewlines().end && // Line has trailing space
1914 this->textWithNewlines().end == intersect.end && // Range is at the end of the line
1915 this->trimmedText().end > intersect.start) // Range has more than just spaces
1916 {
1917 auto delta = this->spacesWidth();
1918 trailingSpaces = SkRect::MakeXYWH(0, 0, 0, 0);
1919 // There are trailing spaces in this run
1920 if (paragraphStyle.getTextAlign() == TextAlign::kJustify && isLastLine())
1921 {
1922 // TODO: this is just a patch. Make it right later (when it's clear what and how)
1923 trailingSpaces = clip;
1924 if(run->leftToRight()) {
1925 trailingSpaces.fLeft = this->width();
1926 clip.fRight = this->width();
1927 } else {
1928 trailingSpaces.fRight = 0;
1929 clip.fLeft = 0;
1930 }
1931 } else if (paragraphStyle.getTextDirection() == TextDirection::kRtl &&
1932 !run->leftToRight())
1933 {
1934 // Split
1935 trailingSpaces = clip;
1936 trailingSpaces.fLeft = - delta;
1937 trailingSpaces.fRight = 0;
1938 clip.fLeft += delta;
1939 } else if (paragraphStyle.getTextDirection() == TextDirection::kLtr &&
1940 run->leftToRight())
1941 {
1942 // Split
1943 trailingSpaces = clip;
1944 trailingSpaces.fLeft = this->width();
1945 trailingSpaces.fRight = trailingSpaces.fLeft + delta;
1946 clip.fRight -= delta;
1947 }
1948 }
1949
1950 clip.offset(this->offset());
1951 if (trailingSpaces.width() > 0) {
1952 trailingSpaces.offset(this->offset());
1953 }
1954
1955 // Check if we can merge two boxes instead of adding a new one
1956 auto merge = [&lastRun, &context, &boxes](SkRect clip) {
1957 bool mergedBoxes = false;
1958 if (!boxes.empty() &&
1959 lastRun != nullptr &&
1960 context.run->leftToRight() == lastRun->leftToRight() &&
1961 lastRun->placeholderStyle() == nullptr &&
1962 context.run->placeholderStyle() == nullptr &&
1963 nearlyEqual(lastRun->heightMultiplier(),
1964 context.run->heightMultiplier()) &&
1965 #ifndef USE_SKIA_TXT
1966 lastRun->font() == context.run->font())
1967 #else
1968 IsRSFontEquals(lastRun->font(), context.run->font()))
1969 #endif
1970 {
1971 auto& lastBox = boxes.back();
1972 if (nearlyEqual(lastBox.rect.fTop, clip.fTop) &&
1973 nearlyEqual(lastBox.rect.fBottom, clip.fBottom) &&
1974 (nearlyEqual(lastBox.rect.fLeft, clip.fRight) ||
1975 nearlyEqual(lastBox.rect.fRight, clip.fLeft)))
1976 {
1977 lastBox.rect.fLeft = std::min(lastBox.rect.fLeft, clip.fLeft);
1978 lastBox.rect.fRight = std::max(lastBox.rect.fRight, clip.fRight);
1979 mergedBoxes = true;
1980 }
1981 }
1982 lastRun = context.run;
1983 return mergedBoxes;
1984 };
1985
1986 if (!merge(clip)) {
1987 boxes.emplace_back(clip, context.run->getTextDirection());
1988 }
1989 if (!nearlyZero(trailingSpaces.width()) && !merge(trailingSpaces)) {
1990 boxes.emplace_back(trailingSpaces, paragraphStyle.getTextDirection());
1991 }
1992
1993 if (rectWidthStyle == RectWidthStyle::kMax && !isLastLine()) {
1994 // Align the very left/right box horizontally
1995 auto lineStart = this->offset().fX;
1996 auto lineEnd = this->offset().fX + this->width();
1997 auto left = boxes[startBox];
1998 auto right = boxes.back();
1999 if (left.rect.fLeft > lineStart && left.direction == TextDirection::kRtl) {
2000 left.rect.fRight = left.rect.fLeft;
2001 left.rect.fLeft = 0;
2002 boxes.insert(boxes.begin() + startBox + 1, left);
2003 }
2004 if (right.direction == TextDirection::kLtr &&
2005 right.rect.fRight >= lineEnd &&
2006 right.rect.fRight < fOwner->widthWithTrailingSpaces()) {
2007 right.rect.fLeft = right.rect.fRight;
2008 right.rect.fRight = fOwner->widthWithTrailingSpaces();
2009 boxes.emplace_back(right);
2010 }
2011 }
2012
2013 return true;
2014 });
2015 return true;
2016 });
2017 if (fOwner->getApplyRoundingHack()) {
2018 for (auto& r : boxes) {
2019 r.rect.fLeft = littleRound(r.rect.fLeft);
2020 r.rect.fRight = littleRound(r.rect.fRight);
2021 r.rect.fTop = littleRound(r.rect.fTop);
2022 r.rect.fBottom = littleRound(r.rect.fBottom);
2023 }
2024 }
2025 }
2026
2027 PositionWithAffinity TextLine::getGlyphPositionAtCoordinate(SkScalar dx) {
2028
2029 if (SkScalarNearlyZero(this->width()) && SkScalarNearlyZero(this->spacesWidth())) {
2030 // TODO: this is one of the flutter changes that have to go away eventually
2031 // Empty line is a special case in txtlib (but only when there are no spaces, too)
2032 auto utf16Index = fOwner->getUTF16Index(this->fTextExcludingSpaces.end);
2033 return { SkToS32(utf16Index) , kDownstream };
2034 }
2035
2036 PositionWithAffinity result(0, Affinity::kDownstream);
2037 #ifdef OHOS_SUPPORT
2038 this->iterateThroughVisualRuns(EllipsisReadStrategy::READ_REPLACED_WORD, true,
2039 #else
2040 this->iterateThroughVisualRuns(true,
2041 #endif
2042 [this, dx, &result]
2043 (const Run* run, SkScalar runOffsetInLine, TextRange textRange, SkScalar* runWidthInLine) {
2044 bool keepLooking = true;
2045 *runWidthInLine = this->iterateThroughSingleRunByStyles(
2046 TextAdjustment::GraphemeGluster, run, runOffsetInLine, textRange, StyleType::kNone,
2047 [this, run, dx, &result, &keepLooking]
2048 (TextRange textRange, const TextStyle& style, const TextLine::ClipContext& context0) {
2049
2050 SkScalar offsetX = this->offset().fX;
2051 ClipContext context = context0;
2052
2053 // Correct the clip size because libtxt counts trailing spaces
2054 if (run->leftToRight()) {
2055 context.clip.fRight += context.fExcludedTrailingSpaces; // extending clip to the right
2056 } else {
2057 // Clip starts from 0; we cannot extend it to the left from that
2058 }
2059 // However, we need to offset the clip
2060 context.clip.offset(offsetX, 0.0f);
2061
2062 // This patch will help us to avoid a floating point error
2063 if (SkScalarNearlyEqual(context.clip.fRight, dx, 0.01f)) {
2064 context.clip.fRight = dx;
2065 }
2066
2067 if (dx <= context.clip.fLeft) {
2068 // All the other runs are placed right of this one
2069 auto utf16Index = fOwner->getUTF16Index(context.run->globalClusterIndex(context.pos));
2070 if (run->leftToRight()) {
2071 result = { SkToS32(utf16Index), kDownstream};
2072 keepLooking = false;
2073 } else {
2074 result = { SkToS32(utf16Index + 1), kUpstream};
2075 // If we haven't reached the end of the run we need to keep looking
2076 keepLooking = context.pos != 0;
2077 }
2078 // For RTL we go another way
2079 return !run->leftToRight();
2080 }
2081
2082 if (dx >= context.clip.fRight) {
2083 // We have to keep looking ; just in case keep the last one as the closest
2084 auto utf16Index = fOwner->getUTF16Index(context.run->globalClusterIndex(context.pos + context.size));
2085 if (run->leftToRight()) {
2086 result = {SkToS32(utf16Index), kUpstream};
2087 } else {
2088 result = {SkToS32(utf16Index), kDownstream};
2089 }
2090 // For RTL we go another way
2091 return run->leftToRight();
2092 }
2093
2094 // So we found the run that contains our coordinates
2095 // Find the glyph position in the run that is the closest left of our point
2096 // TODO: binary search
2097 size_t found = context.pos;
2098 for (size_t index = context.pos; index < context.pos + context.size; ++index) {
2099 // TODO: this rounding is done to match Flutter tests. Must be removed..
2100 auto end = context.run->positionX(index) + context.fTextShift + offsetX;
2101 if (fOwner->getApplyRoundingHack()) {
2102 end = littleRound(end);
2103 }
2104 if (end > dx) {
2105 break;
2106 } else if (end == dx && !context.run->leftToRight()) {
2107 // When we move RTL variable end points to the beginning of the code point which is included
2108 found = index;
2109 break;
2110 }
2111 found = index;
2112 }
2113
2114 SkScalar glyphemePosLeft = context.run->positionX(found) + context.fTextShift + offsetX;
2115 SkScalar glyphemesWidth = context.run->positionX(found + 1) - context.run->positionX(found);
2116
2117 // Find the grapheme range that contains the point
2118 auto clusterIndex8 = context.run->globalClusterIndex(found);
2119 auto clusterEnd8 = context.run->globalClusterIndex(found + 1);
2120 auto graphemes = fOwner->countSurroundingGraphemes({clusterIndex8, clusterEnd8});
2121
2122 SkScalar center = glyphemePosLeft + glyphemesWidth * fOwner->getTextSplitRatio();
2123 if (graphemes.size() > 1) {
2124 // Calculate the position proportionally based on grapheme count
2125 SkScalar averageGraphemeWidth = glyphemesWidth / graphemes.size();
2126 SkScalar delta = dx - glyphemePosLeft;
2127 int graphemeIndex = SkScalarNearlyZero(averageGraphemeWidth)
2128 ? 0
2129 : SkScalarFloorToInt(delta / averageGraphemeWidth);
2130 auto graphemeCenter = glyphemePosLeft + graphemeIndex * averageGraphemeWidth +
2131 averageGraphemeWidth * fOwner->getTextSplitRatio();
2132 auto graphemeUtf8Index = graphemes[graphemeIndex];
2133 if ((dx < graphemeCenter) == context.run->leftToRight()) {
2134 size_t utf16Index = fOwner->getUTF16Index(graphemeUtf8Index);
2135 result = { SkToS32(utf16Index), kDownstream };
2136 } else {
2137 size_t utf16Index = fOwner->getUTF16Index(graphemeUtf8Index + 1);
2138 result = { SkToS32(utf16Index), kUpstream };
2139 }
2140 // Keep UTF16 index as is
2141 } else if ((dx < center) == context.run->leftToRight()) {
2142 size_t utf16Index = fOwner->getUTF16Index(clusterIndex8);
2143 result = { SkToS32(utf16Index), kDownstream };
2144 } else {
2145 size_t utf16Index = context.run->leftToRight()
2146 ? fOwner->getUTF16Index(clusterEnd8)
2147 : fOwner->getUTF16Index(clusterIndex8) + 1;
2148 result = { SkToS32(utf16Index), kUpstream };
2149 }
2150
2151 return keepLooking = false;
2152
2153 });
2154 return keepLooking;
2155 }
2156 );
2157 return result;
2158 }
2159
2160 void TextLine::getRectsForPlaceholders(std::vector<TextBox>& boxes) {
2161 #ifdef OHOS_SUPPORT
2162 this->iterateThroughVisualRuns(EllipsisReadStrategy::READ_REPLACED_WORD, true,
2163 #else
2164 this->iterateThroughVisualRuns(true,
2165 #endif
2166 [&boxes, this](const Run* run, SkScalar runOffset, TextRange textRange,
2167 SkScalar* width) {
2168 auto context = this->measureTextInsideOneRun(
2169 textRange, run, runOffset, 0, true, TextAdjustment::GraphemeGluster);
2170 *width = context.clip.width();
2171
2172 if (textRange.width() == 0) {
2173 return true;
2174 }
2175 if (!run->isPlaceholder()) {
2176 return true;
2177 }
2178
2179 SkRect clip = context.clip;
2180 clip.offset(this->offset());
2181
2182 if (fOwner->getApplyRoundingHack()) {
2183 clip.fLeft = littleRound(clip.fLeft);
2184 clip.fRight = littleRound(clip.fRight);
2185 clip.fTop = littleRound(clip.fTop);
2186 clip.fBottom = littleRound(clip.fBottom);
2187 }
2188 boxes.emplace_back(clip, run->getTextDirection());
2189 return true;
2190 });
2191 }
2192
2193 size_t TextLine::getGlyphCount() const
2194 {
2195 size_t glyphCount = 0;
2196 for (auto& blob: fTextBlobCache) {
2197 glyphCount += blob.fVisitor_Size;
2198 }
2199 return glyphCount;
2200 }
2201
2202 std::vector<std::unique_ptr<RunBase>> TextLine::getGlyphRuns() const
2203 {
2204 std::vector<std::unique_ptr<RunBase>> runBases;
2205 for (auto& blob: fTextBlobCache) {
2206 std::unique_ptr<RunBaseImpl> runBaseImplPtr = std::make_unique<RunBaseImpl>(
2207 blob.fBlob, blob.fOffset, blob.fPaint, blob.fClippingNeeded, blob.fClipRect,
2208 blob.fVisitor_Run, blob.fVisitor_Pos, blob.fVisitor_Size);
2209 runBases.emplace_back(std::move(runBaseImplPtr));
2210 }
2211 return runBases;
2212 }
2213
2214 TextLine TextLine::CloneSelf()
2215 {
2216 TextLine textLine;
2217 textLine.fBlockRange = this->fBlockRange;
2218 textLine.fTextExcludingSpaces = this->fTextExcludingSpaces;
2219 textLine.fText = this->fText;
2220 textLine.fTextIncludingNewlines = this->fTextIncludingNewlines;
2221 textLine.fClusterRange = this->fClusterRange;
2222
2223 textLine.fGhostClusterRange = this->fGhostClusterRange;
2224 textLine.fRunsInVisualOrder = this->fRunsInVisualOrder;
2225 textLine.fAdvance = this->fAdvance;
2226 textLine.fOffset = this->fOffset;
2227 textLine.fShift = this->fShift;
2228
2229 textLine.fWidthWithSpaces = this->fWidthWithSpaces;
2230 if (this->fEllipsis) {
2231 textLine.fEllipsis = std::make_unique<Run>(*this->fEllipsis);
2232 }
2233
2234 textLine.fSizes = this->fSizes;
2235 textLine.fMaxRunMetrics = this->fMaxRunMetrics;
2236 textLine.fHasBackground = this->fHasBackground;
2237 textLine.fHasShadows = this->fHasShadows;
2238 textLine.fHasDecorations = this->fHasDecorations;
2239 textLine.fAscentStyle = this->fAscentStyle;
2240 textLine.fDescentStyle = this->fDescentStyle;
2241 textLine.fTextBlobCachePopulated = this->fTextBlobCachePopulated;
2242
2243 textLine.roundRectAttrs = this->roundRectAttrs;
2244 textLine.fTextBlobCache = this->fTextBlobCache;
2245 textLine.fTextRangeReplacedByEllipsis = this->fTextRangeReplacedByEllipsis;
2246 textLine.fEllipsisIndex = this->fEllipsisIndex;
2247 textLine.fLastClipRunLtr = this->fLastClipRunLtr;
2248 return textLine;
2249 }
2250 } // namespace textlayout
2251 } // namespace skia
2252