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