1 // Copyright 2019 Google LLC.
2
3 #include "include/core/SkBlurTypes.h"
4 #include "include/core/SkFont.h"
5 #include "include/core/SkFontMetrics.h"
6 #include "include/core/SkMaskFilter.h"
7 #include "include/core/SkPaint.h"
8 #include "include/core/SkSpan.h"
9 #include "include/core/SkString.h"
10 #include "include/core/SkTextBlob.h"
11 #include "include/core/SkTypes.h"
12 #include "include/private/SkTemplates.h"
13 #include "include/private/SkTo.h"
14 #include "modules/skparagraph/include/DartTypes.h"
15 #include "modules/skparagraph/include/Metrics.h"
16 #include "modules/skparagraph/include/ParagraphPainter.h"
17 #include "modules/skparagraph/include/ParagraphStyle.h"
18 #include "modules/skparagraph/include/TextShadow.h"
19 #include "modules/skparagraph/include/TextStyle.h"
20 #include "modules/skparagraph/src/Decorations.h"
21 #include "modules/skparagraph/src/ParagraphImpl.h"
22 #include "modules/skparagraph/src/ParagraphPainterImpl.h"
23 #include "modules/skparagraph/src/TextLine.h"
24 #include "modules/skshaper/include/SkShaper.h"
25
26 #include <algorithm>
27 #include <iterator>
28 #include <limits>
29 #include <map>
30 #include <memory>
31 #include <tuple>
32 #include <type_traits>
33 #include <utility>
34
35 #ifdef OHOS_SUPPORT
36 #include <cstddef>
37 #include <numeric>
38 #include "log.h"
39 #include "modules/skparagraph/src/RunBaseImpl.h"
40 #include "modules/skparagraph/src/TextLineBaseImpl.h"
41 #include "TextParameter.h"
42 #include "TextLineJustify.h"
43 #endif
44
45 namespace skia {
46 namespace textlayout {
47 #define MAX_INT_VALUE 0x7FFFFFFF
48
49 #ifdef OHOS_SUPPORT
50 #define EMOJI_UNICODE_START 0x1F300
51 #define EMOJI_UNICODE_END 0x1F9EF
52 #define EMOJI_WIDTH 4
53 #endif
54
55 namespace {
56
57 // TODO: deal with all the intersection functionality
intersected(const TextRange & a,const TextRange & b)58 TextRange intersected(const TextRange& a, const TextRange& b) {
59 if (a.start == b.start && a.end == b.end) return a;
60 auto begin = std::max(a.start, b.start);
61 auto end = std::min(a.end, b.end);
62 return end >= begin ? TextRange(begin, end) : EMPTY_TEXT;
63 }
64
littleRound(SkScalar a)65 SkScalar littleRound(SkScalar a) {
66 // This rounding is done to match Flutter tests. Must be removed..
67 return SkScalarRoundToScalar(a * 100.0)/100.0;
68 }
69
operator *(const TextRange & a,const TextRange & b)70 TextRange operator*(const TextRange& a, const TextRange& b) {
71 if (a.start == b.start && a.end == b.end) return a;
72 auto begin = std::max(a.start, b.start);
73 auto end = std::min(a.end, b.end);
74 return end > begin ? TextRange(begin, end) : EMPTY_TEXT;
75 }
76
compareRound(SkScalar a,SkScalar b,bool applyRoundingHack)77 int compareRound(SkScalar a, SkScalar b, bool applyRoundingHack) {
78 // There is a rounding error that gets bigger when maxWidth gets bigger
79 // VERY long zalgo text (> 100000) on a VERY long line (> 10000)
80 // Canvas scaling affects it
81 // Letter spacing affects it
82 // It has to be relative to be useful
83 auto base = std::max(SkScalarAbs(a), SkScalarAbs(b));
84 auto diff = SkScalarAbs(a - b);
85 if (nearlyZero(base) || diff / base < 0.001f) {
86 return 0;
87 }
88
89 auto ra = a;
90 auto rb = b;
91
92 if (applyRoundingHack) {
93 ra = littleRound(a);
94 rb = littleRound(b);
95 }
96 if (ra < rb) {
97 return -1;
98 } else {
99 return 1;
100 }
101 }
102
103 #ifdef USE_SKIA_TXT
IsRSFontEquals(const RSFont & font0,const RSFont & font1)104 bool IsRSFontEquals(const RSFont& font0, const RSFont& font1) {
105 auto f0 = const_cast<RSFont&>(font0);
106 auto f1 = const_cast<RSFont&>(font1);
107 return f0.GetTypeface().get() == f1.GetTypeface().get() &&
108 f0.GetSize() == f1.GetSize() &&
109 f0.GetScaleX() == f1.GetScaleX() &&
110 f0.GetSkewX() == f1.GetSkewX() &&
111 f0.GetEdging() == f1.GetEdging() &&
112 f0.GetHinting() == f1.GetHinting();
113 }
114 #endif
115
116 #ifdef OHOS_SUPPORT
117 #ifdef USE_SKIA_TXT
GetTextBlobSkTightBound(std::shared_ptr<RSTextBlob> blob,float offsetX,float offsetY,const SkRect & clipRect)118 SkRect GetTextBlobSkTightBound(std::shared_ptr<RSTextBlob> blob, float offsetX, float offsetY, const SkRect& clipRect)
119 {
120 if (blob == nullptr || blob->Bounds() == nullptr) {
121 return SkRect::MakeEmpty();
122 }
123
124 RSRect bound = *blob->Bounds();
125 bound.Offset(offsetX, offsetY);
126 if (!clipRect.isEmpty()) {
127 bound.left_ = std::max(bound.left_, clipRect.fLeft);
128 bound.right_ = std::min(bound.right_, clipRect.fRight);
129 }
130 return SkRect::MakeLTRB(bound.left_, bound.top_, bound.right_, bound.bottom_);
131 }
132 #else
GetTextBlobSkTightBound(sk_sp<SkTextBlob> blob,float offsetX,float offsetY,const SkRect & clipRect)133 SkRect GetTextBlobSkTightBound(sk_sp<SkTextBlob> blob, float offsetX, float offsetY, const SkRect& clipRect)
134 {
135 if (blob == nullptr) {
136 return SkRect::MakeEmpty();
137 }
138
139 SkRect bound = blob->bounds();
140 if (!clipRect.isEmpty()) {
141 bound.fLeft = std::max(bound.fLeft, clipRect.fLeft);
142 bound.fRight = std::min(bound.fRight, clipRect.fRight);
143 }
144 bound.offset(offsetX, offsetY);
145 return bound;
146 }
147 #endif
148 #endif
149 } // namespace
150
TextLine(ParagraphImpl * owner,SkVector offset,SkVector advance,BlockRange blocks,TextRange textExcludingSpaces,TextRange text,TextRange textIncludingNewlines,ClusterRange clusters,ClusterRange clustersWithGhosts,SkScalar widthWithSpaces,InternalLineMetrics sizes)151 TextLine::TextLine(ParagraphImpl* owner,
152 SkVector offset,
153 SkVector advance,
154 BlockRange blocks,
155 TextRange textExcludingSpaces,
156 TextRange text,
157 TextRange textIncludingNewlines,
158 ClusterRange clusters,
159 ClusterRange clustersWithGhosts,
160 SkScalar widthWithSpaces,
161 InternalLineMetrics sizes)
162 : fOwner(owner)
163 , fBlockRange(blocks)
164 , fTextExcludingSpaces(textExcludingSpaces)
165 , fText(text)
166 , fTextIncludingNewlines(textIncludingNewlines)
167 , fClusterRange(clusters)
168 , fGhostClusterRange(clustersWithGhosts)
169 , fRunsInVisualOrder()
170 , fAdvance(advance)
171 , fOffset(offset)
172 , fShift(0.0)
173 , fWidthWithSpaces(widthWithSpaces)
174 , fEllipsis(nullptr)
175 , fSizes(sizes)
176 , fHasBackground(false)
177 , fHasShadows(false)
178 , fHasDecorations(false)
179 , fIsArcText(false)
180 , fArcTextState(false)
181 , fAscentStyle(LineMetricStyle::CSS)
182 , fDescentStyle(LineMetricStyle::CSS)
183 , fTextBlobCachePopulated(false) {
184 // Reorder visual runs
185 auto& start = owner->cluster(fGhostClusterRange.start);
186 auto& end = owner->cluster(fGhostClusterRange.end - 1);
187 size_t numRuns = end.runIndex() - start.runIndex() + 1;
188
189 for (BlockIndex index = fBlockRange.start; index < fBlockRange.end; ++index) {
190 auto b = fOwner->styles().begin() + index;
191 if (b->fStyle.hasBackground()) {
192 fHasBackground = true;
193 }
194
195 #ifdef OHOS_SUPPORT
196 if (b->fStyle.getDecorationType() != TextDecoration::kNoDecoration &&
197 b->fStyle.getDecorationThicknessMultiplier() > 0) {
198 #else
199 if (b->fStyle.getDecorationType() != TextDecoration::kNoDecoration) {
200 #endif
201 fHasDecorations = true;
202 }
203 if (b->fStyle.getShadowNumber() > 0) {
204 fHasShadows = true;
205 }
206 }
207
208 // Get the logical order
209
210 // This is just chosen to catch the common/fast cases. Feel free to tweak.
211 constexpr int kPreallocCount = 4;
212 SkAutoSTArray<kPreallocCount, SkUnicode::BidiLevel> runLevels(numRuns);
213 std::vector<RunIndex> placeholdersInOriginalOrder;
214 size_t runLevelsIndex = 0;
215 // Placeholders must be laid out using the original order in which they were added
216 // in the input. The API does not provide a way to indicate that a placeholder
217 // position was moved due to bidi reordering.
218 for (auto runIndex = start.runIndex(); runIndex <= end.runIndex(); ++runIndex) {
219 auto& run = fOwner->run(runIndex);
220 runLevels[runLevelsIndex++] = run.fBidiLevel;
221 fMaxRunMetrics.add(
222 InternalLineMetrics(run.correctAscent(), run.correctDescent(), run.fFontMetrics.fLeading));
223 if (run.isPlaceholder()) {
224 placeholdersInOriginalOrder.push_back(runIndex);
225 }
226 }
227 SkASSERT(runLevelsIndex == numRuns);
228
229 SkAutoSTArray<kPreallocCount, int32_t> logicalOrder(numRuns);
230
231 // TODO: hide all these logic in SkUnicode?
232 fOwner->getUnicode()->reorderVisual(runLevels.data(), numRuns, logicalOrder.data());
233 auto firstRunIndex = start.runIndex();
234 auto placeholderIter = placeholdersInOriginalOrder.begin();
235 for (auto index : logicalOrder) {
236 auto runIndex = firstRunIndex + index;
237 if (fOwner->run(runIndex).isPlaceholder()) {
238 fRunsInVisualOrder.push_back(*placeholderIter++);
239 } else {
240 fRunsInVisualOrder.push_back(runIndex);
241 }
242 }
243
244 fTextRangeReplacedByEllipsis = EMPTY_RANGE;
245 fEllipsisIndex = EMPTY_INDEX;
246 #ifdef OHOS_SUPPORT
247 fHyphenIndex = EMPTY_INDEX;
248 #endif
249 fLastClipRunLtr = false;
250 }
251
252 void TextLine::paint(ParagraphPainter* painter, const RSPath* path, SkScalar hOffset, SkScalar vOffset) {
253 prepareRoundRect();
254 fIsArcText = true;
255 if (pathParameters.hOffset != hOffset || pathParameters.vOffset != vOffset) {
256 fTextBlobCachePopulated = false;
257 }
258 pathParameters.recordPath = path;
259 pathParameters.hOffset = hOffset;
260 pathParameters.vOffset = vOffset;
261 this->ensureTextBlobCachePopulated();
262 for (auto& record : fTextBlobCache) {
263 record.paint(painter);
264 }
265 }
266
267 void TextLine::paint(ParagraphPainter* painter, SkScalar x, SkScalar y) {
268 prepareRoundRect();
269 fIsArcText = false;
270 #ifdef OHOS_SUPPORT
271 this->iterateThroughVisualRuns(EllipsisReadStrategy::READ_REPLACED_WORD, true,
272 #else
273 this->iterateThroughVisualRuns(false,
274 #endif
275 [painter, x, y, this]
276 (const Run* run, SkScalar runOffsetInLine, TextRange textRange, SkScalar* runWidthInLine) {
277 *runWidthInLine = this->iterateThroughSingleRunByStyles(
278 TextAdjustment::GlyphCluster, run, runOffsetInLine, textRange, StyleType::kBackground,
279 [painter, x, y, run, this](TextRange textRange, const TextStyle& style, const ClipContext& context) {
280 if (fHasBackground) {
281 this->paintBackground(painter, x, y, textRange, style, context);
282 }
283 paintRoundRect(painter, x, y, run);
284 });
285 return true;
286 });
287
288 if (fHasShadows) {
289 #ifdef OHOS_SUPPORT
290 this->iterateThroughVisualRuns(EllipsisReadStrategy::READ_REPLACED_WORD, false,
291 #else
292 this->iterateThroughVisualRuns(false,
293 #endif
294 [painter, x, y, this]
295 (const Run* run, SkScalar runOffsetInLine, TextRange textRange, SkScalar* runWidthInLine) {
296 *runWidthInLine = this->iterateThroughSingleRunByStyles(
297 TextAdjustment::GlyphCluster, run, runOffsetInLine, textRange, StyleType::kShadow,
298 [painter, x, y, this]
299 (TextRange textRange, const TextStyle& style, const ClipContext& context) {
300 this->paintShadow(painter, x, y, textRange, style, context);
301 });
302 return true;
303 });
304 }
305
306 this->ensureTextBlobCachePopulated();
307
308 for (auto& record : fTextBlobCache) {
309 record.paint(painter, x, y);
310 }
311
312 if (fHasDecorations) {
313 #ifdef OHOS_SUPPORT
314 this->fDecorationContext = {0.0f, 0.0f, 0.0f};
315 // 16 is default value in placeholder-only scenario, calculated by the fontsize 14.
316 SkScalar maxLineHeightWithoutPlaceholder = 16;
317 this->iterateThroughVisualRuns(EllipsisReadStrategy::DEFAULT, true,
318 [painter, x, y, this, &maxLineHeightWithoutPlaceholder]
319 (const Run* run, SkScalar runOffsetInLine, TextRange textRange, SkScalar* runWidthInLine) {
320 *runWidthInLine = this->iterateThroughSingleRunByStyles(
321 TextAdjustment::GlyphCluster, run, runOffsetInLine, textRange,
322 StyleType::kDecorations, [painter, x, y, this, &maxLineHeightWithoutPlaceholder]
323 (TextRange textRange, const TextStyle& style, const ClipContext& context) {
324 if (style.getDecoration().fType == TextDecoration::kUnderline) {
325 SkScalar tmpThick = this->calculateThickness(style, context);
326 fDecorationContext.thickness = fDecorationContext.thickness > tmpThick ?
327 fDecorationContext.thickness : tmpThick;
328 }
329 auto cur = context.run;
330 if (cur != nullptr && !cur->isPlaceholder()) {
331 SkScalar height = round(cur->correctDescent() - cur->correctAscent() + cur->correctLeading());
332 maxLineHeightWithoutPlaceholder = maxLineHeightWithoutPlaceholder < height ?
333 height : maxLineHeightWithoutPlaceholder;
334 }
335 });
336 return true;
337 });
338 // 16% of row height wihtout placeholder.
339 fDecorationContext.underlinePosition = maxLineHeightWithoutPlaceholder * 0.16 + baseline();
340 fDecorationContext.textBlobTop = maxLineHeightWithoutPlaceholder * 0.16;
341 #endif
342
343 #ifdef OHOS_SUPPORT
344 this->iterateThroughVisualRuns(EllipsisReadStrategy::DEFAULT, true,
345 #else
346 this->iterateThroughVisualRuns(false,
347 #endif
348 [painter, x, y, this]
349 (const Run* run, SkScalar runOffsetInLine, TextRange textRange, SkScalar* runWidthInLine) {
350 *runWidthInLine = this->iterateThroughSingleRunByStyles(
351 TextAdjustment::GlyphCluster, run, runOffsetInLine, textRange, StyleType::kDecorations,
352 [painter, x, y, this]
353 (TextRange textRange, const TextStyle& style, const ClipContext& context) {
354 this->paintDecorations(painter, x, y, textRange, style, context);
355 });
356 return true;
357 });
358 }
359 }
360
361 bool TextLine::hasBackgroundRect(const RoundRectAttr& attr) {
362 return attr.roundRectStyle.color != 0 && attr.rect.width() > 0;
363 }
364
365 void TextLine::computeRoundRect(int& index, int& preIndex, std::vector<Run*>& groupRuns, Run* run) {
366 int runCount = roundRectAttrs.size();
367 if (index >= runCount) {
368 return;
369 }
370
371 bool leftRound = false;
372 bool rightRound = false;
373 if (hasBackgroundRect(roundRectAttrs[index])) {
374 int styleId = roundRectAttrs[index].styleId;
375 // index - 1 is previous index, -1 is the invalid styleId
376 int preStyleId = index == 0 ? -1 : roundRectAttrs[index - 1].styleId;
377 // runCount - 1 is the last run index, index + 1 is next run index, -1 is the invalid styleId
378 int nextStyleId = index == runCount - 1 ? -1 : roundRectAttrs[index + 1].styleId;
379 // index - preIndex > 1 means the left run has no background rect
380 leftRound = (preIndex < 0 || index - preIndex > 1 || preStyleId != styleId);
381 // runCount - 1 is the last run index
382 rightRound = (index == runCount - 1 || !hasBackgroundRect(roundRectAttrs[index + 1]) ||
383 nextStyleId != styleId);
384 preIndex = index;
385 groupRuns.push_back(run);
386 } else if (!groupRuns.empty()) {
387 groupRuns.erase(groupRuns.begin(), groupRuns.end());
388 }
389 if (leftRound && rightRound) {
390 run->setRoundRectType(RoundRectType::ALL);
391 } else if (leftRound) {
392 run->setRoundRectType(RoundRectType::LEFT_ONLY);
393 } else if (rightRound) {
394 run->setRoundRectType(RoundRectType::RIGHT_ONLY);
395 } else {
396 run->setRoundRectType(RoundRectType::NONE);
397 }
398
399 if (rightRound && !groupRuns.empty()) {
400 double maxRoundRectRadius = MAX_INT_VALUE;
401 double minTop = MAX_INT_VALUE;
402 double maxBottom = 0;
403 for (auto &gRun : groupRuns) {
404 RoundRectAttr& attr = roundRectAttrs[gRun->getIndexInLine()];
405 maxRoundRectRadius = std::fmin(std::fmin(attr.rect.width(), attr.rect.height()), maxRoundRectRadius);
406 minTop = std::fmin(minTop, attr.rect.top());
407 maxBottom = std::fmax(maxBottom, attr.rect.bottom());
408 }
409 for (auto &gRun : groupRuns) {
410 gRun->setMaxRoundRectRadius(maxRoundRectRadius);
411 gRun->setTopInGroup(minTop - gRun->offset().y());
412 gRun->setBottomInGroup(maxBottom - gRun->offset().y());
413 }
414 groupRuns.erase(groupRuns.begin(), groupRuns.end());
415 }
416 index++;
417 }
418
419 void TextLine::prepareRoundRect() {
420 roundRectAttrs.clear();
421 #ifdef OHOS_SUPPORT
422 this->iterateThroughVisualRuns(EllipsisReadStrategy::DEFAULT, true,
423 #else
424 this->iterateThroughVisualRuns(true,
425 #endif
426 [this](const Run* run, SkScalar runOffsetInLine, TextRange textRange, SkScalar* runWidthInLine) {
427 *runWidthInLine = this->iterateThroughSingleRunByStyles(
428 TextAdjustment::GlyphCluster, run, runOffsetInLine, textRange, StyleType::kBackground,
429 [run, this](TextRange textRange, const TextStyle& style, const ClipContext& context) {
430 roundRectAttrs.push_back({style.getStyleId(), style.getBackgroundRect(), context.clip});
431 });
432 return true;
433 });
434
435 std::vector<Run*> groupRuns;
436 int index = 0;
437 int preIndex = -1;
438 for (auto& runIndex : fRunsInVisualOrder) {
439 auto run = &this->fOwner->run(runIndex);
440 run->setIndexInLine(static_cast<size_t>(index));
441 computeRoundRect(index, preIndex, groupRuns, run);
442 }
443 }
444
445 void TextLine::ensureTextBlobCachePopulated() {
446 if (fTextBlobCachePopulated && fArcTextState == fIsArcText) {
447 return;
448 }
449 fTextBlobCache.clear();
450 if (fBlockRange.width() == 1 &&
451 fRunsInVisualOrder.size() == 1 &&
452 fEllipsis == nullptr &&
453 #ifdef OHOS_SUPPORT
454 fHyphenRun == nullptr &&
455 #endif
456 fOwner->run(fRunsInVisualOrder[0]).placeholderStyle() == nullptr) {
457 if (fClusterRange.width() == 0) {
458 return;
459 }
460 // Most common and most simple case
461 const auto& style = fOwner->block(fBlockRange.start).fStyle;
462 const auto& run = fOwner->run(fRunsInVisualOrder[0]);
463 auto clip = SkRect::MakeXYWH(0.0f, this->sizes().runTop(&run, this->fAscentStyle),
464 fAdvance.fX,
465 run.calculateHeight(this->fAscentStyle, this->fDescentStyle));
466
467 auto& start = fOwner->cluster(fClusterRange.start);
468 auto& end = fOwner->cluster(fClusterRange.end - 1);
469 SkASSERT(start.runIndex() == end.runIndex());
470 GlyphRange glyphs;
471 if (run.leftToRight()) {
472 glyphs = GlyphRange(start.startPos(),
473 end.isHardBreak() ? end.startPos() : end.endPos());
474 } else {
475 glyphs = GlyphRange(end.startPos(),
476 start.isHardBreak() ? start.startPos() : start.endPos());
477 }
478 ClipContext context = {/*run=*/&run,
479 /*pos=*/glyphs.start,
480 /*size=*/glyphs.width(),
481 /*fTextShift=*/-run.positionX(glyphs.start), // starting position
482 /*clip=*/clip, // entire line
483 /*fExcludedTrailingSpaces=*/0.0f, // no need for that
484 /*clippingNeeded=*/false}; // no need for that
485 this->buildTextBlob(fTextExcludingSpaces, style, context);
486 } else {
487 #ifdef OHOS_SUPPORT
488 this->iterateThroughVisualRuns(EllipsisReadStrategy::READ_ELLIPSIS_WORD, false,
489 #else
490 this->iterateThroughVisualRuns(false,
491 #endif
492 [this](const Run* run,
493 SkScalar runOffsetInLine,
494 TextRange textRange,
495 SkScalar* runWidthInLine) {
496 if (run->placeholderStyle() != nullptr) {
497 *runWidthInLine = run->advance().fX;
498 return true;
499 }
500 *runWidthInLine = this->iterateThroughSingleRunByStyles(
501 TextAdjustment::GlyphCluster,
502 run,
503 runOffsetInLine,
504 textRange,
505 StyleType::kForeground,
506 [this](TextRange textRange, const TextStyle& style, const ClipContext& context) {
507 this->buildTextBlob(textRange, style, context);
508 });
509 return true;
510 });
511 }
512 fTextBlobCachePopulated = true;
513 fArcTextState = fIsArcText;
514 pathParameters.recordPath = nullptr;
515 }
516
517 void TextLine::format(TextAlign align, SkScalar maxWidth, EllipsisModal ellipsisModal) {
518 SkScalar delta = maxWidth - this->widthWithEllipsisSpaces();
519 #ifdef OHOS_SUPPORT
520 delta = (delta < 0) ? 0 : delta;
521 #else
522 if (delta <= 0) {
523 return;
524 }
525 #endif
526 // We do nothing for left align
527 if (align == TextAlign::kJustify) {
528 if (!this->endsWithHardLineBreak()) {
529 this->justify(maxWidth);
530 } else if (fOwner->paragraphStyle().getTextDirection() == TextDirection::kRtl) {
531 // Justify -> Right align
532 fShift = delta;
533 }
534 } else if (align == TextAlign::kRight) {
535 #ifdef OHOS_SUPPORT
536 auto lastCluster = fOwner->clusters()[fGhostClusterRange.end - 1];
537 bool isRTLWhiteSpace = lastCluster.isWhitespaceBreak() && !lastCluster.run().leftToRight();
538 // Only be entered when the text alignment direction is RTL and the last character is an RTL whitespace
539 if (fOwner->paragraphStyle().getTextDirection() == TextDirection::kRtl && isRTLWhiteSpace) {
540 fShift = maxWidth - this->width();
541 } else {
542 fShift = delta;
543 }
544 #else
545 fShift = delta;
546 #endif
547 } else if (align == TextAlign::kCenter) {
548 fShift = delta / 2;
549 }
550 }
551
552 #ifdef OHOS_SUPPORT
553 SkScalar TextLine::autoSpacing() {
554 if (!TextParameter::GetAutoSpacingEnable()) {
555 return 0;
556 }
557 SkScalar spacing = 0.0;
558 auto prevCluster = fOwner->cluster(fClusterRange.start);
559 for (auto clusterIndex = fClusterRange.start + 1; clusterIndex < fClusterRange.end; ++clusterIndex) {
560 auto prevSpacing = spacing;
561 auto& cluster = fOwner->cluster(clusterIndex);
562 spacing += cluster.needAutoSpacing() ? prevCluster.getFontSize() / AUTO_SPACING_WIDTH_RATIO : 0;
563 spacingCluster(&cluster, spacing, prevSpacing);
564 prevCluster = cluster;
565 }
566 this->fWidthWithSpaces += spacing;
567 this->fAdvance.fX += spacing;
568 return spacing;
569 }
570 #endif
571
572 void TextLine::scanStyles(StyleType styleType, const RunStyleVisitor& visitor) {
573 if (this->empty()) {
574 return;
575 }
576
577 #ifdef OHOS_SUPPORT
578 this->iterateThroughVisualRuns(EllipsisReadStrategy::READ_REPLACED_WORD, false,
579 #else
580 this->iterateThroughVisualRuns(false,
581 #endif
582 [this, visitor, styleType](
583 const Run* run, SkScalar runOffset, TextRange textRange, SkScalar* width) {
584 *width = this->iterateThroughSingleRunByStyles(
585 TextAdjustment::GlyphCluster,
586 run,
587 runOffset,
588 textRange,
589 styleType,
590 [visitor](TextRange textRange,
591 const TextStyle& style,
592 const ClipContext& context) {
593 visitor(textRange, style, context);
594 });
595 return true;
596 });
597 }
598
599 SkRect TextLine::extendHeight(const ClipContext& context) const {
600 SkRect result = context.clip;
601 result.fBottom += std::max(this->fMaxRunMetrics.height() - this->height(), 0.0f);
602 return result;
603 }
604
605 void TextLine::buildTextBlob(TextRange textRange, const TextStyle& style, const ClipContext& context) {
606 if (context.run->placeholderStyle() != nullptr) {
607 return;
608 }
609
610 fTextBlobCache.emplace_back();
611 TextBlobRecord& record = fTextBlobCache.back();
612
613 if (style.hasForeground()) {
614 record.fPaint = style.getForegroundPaintOrID();
615 } else {
616 std::get<SkPaint>(record.fPaint).setColor(style.getColor());
617 }
618 record.fVisitor_Run = context.run;
619 record.fVisitor_Pos = context.pos;
620 record.fVisitor_Size = context.size;
621
622 // TODO: This is the change for flutter, must be removed later
623 #ifndef USE_SKIA_TXT
624 SkTextBlobBuilder builder;
625 #else
626 RSTextBlobBuilder builder;
627 #endif
628 if (pathParameters.recordPath) {
629 context.run->copyTo(builder,
630 pathParameters.recordPath,
631 pathParameters.hOffset,
632 pathParameters.vOffset,
633 context.fTextShift,
634 SkToU32(context.pos),
635 context.size);
636 } else {
637 context.run->copyTo(builder, SkToU32(context.pos), context.size);
638 }
639 #ifdef OHOS_SUPPORT
640 // when letterspacing < 0, it causes the font is cliped. so the record fClippingNeeded is set false
641 #else
642 record.fClippingNeeded = context.clippingNeeded;
643 #endif
644 if (context.clippingNeeded) {
645 record.fClipRect = extendHeight(context).makeOffset(this->offset());
646 } else {
647 record.fClipRect = context.clip.makeOffset(this->offset());
648 }
649
650 SkASSERT(nearlyEqual(context.run->baselineShift(), style.getBaselineShift()));
651 SkScalar correctedBaseline = SkScalarFloorToScalar(this->baseline() + style.getBaselineShift() + 0.5);
652 #ifndef USE_SKIA_TXT
653 record.fBlob = builder.make();
654 if (record.fBlob != nullptr) {
655 record.fBounds.joinPossiblyEmptyRect(record.fBlob->bounds());
656 }
657 #else
658 record.fBlob = builder.Make();
659 if (record.fBlob != nullptr) {
660 auto bounds = record.fBlob->Bounds();
661 if (bounds) {
662 record.fBounds.joinPossiblyEmptyRect(SkRect::MakeLTRB(
663 bounds->left_, bounds->top_, bounds->right_, bounds->bottom_
664 ));
665 }
666 }
667 #endif
668
669 record.fOffset = SkPoint::Make(this->offset().fX + context.fTextShift,
670 #ifdef OHOS_SUPPORT
671 this->offset().fY + correctedBaseline - (context.run ? context.run->fCompressionBaselineShift : 0));
672 #else
673 this->offset().fY + correctedBaseline);
674 #endif
675 #ifdef OHOS_SUPPORT
676 #ifndef USE_SKIA_TXT
677 SkFont font;
678 #else
679 RSFont font;
680 #endif
681 if (record.fBlob != nullptr && record.fVisitor_Run != nullptr) {
682 font = record.fVisitor_Run->font();
683 if (font.GetTypeface() != nullptr &&
684 (font.GetTypeface()->GetFamilyName().find("Emoji") != std::string::npos ||
685 font.GetTypeface()->GetFamilyName().find("emoji") != std::string::npos)) {
686 record.fBlob->SetEmoji(true);
687 }
688 }
689 #endif
690 }
691
692 void TextLine::TextBlobRecord::paint(ParagraphPainter* painter, SkScalar x, SkScalar y) {
693 if (fClippingNeeded) {
694 painter->save();
695 painter->clipRect(fClipRect.makeOffset(x, y));
696 }
697 painter->drawTextBlob(fBlob, x + fOffset.x(), y + fOffset.y(), fPaint);
698 if (fClippingNeeded) {
699 painter->restore();
700 }
701 }
702
703 void TextLine::TextBlobRecord::paint(ParagraphPainter* painter) {
704 if (fClippingNeeded) {
705 painter->save();
706 }
707 painter->drawTextBlob(fBlob, 0, 0, fPaint);
708 if (fClippingNeeded) {
709 painter->restore();
710 }
711 }
712
713 void TextLine::paintBackground(ParagraphPainter* painter,
714 SkScalar x,
715 SkScalar y,
716 TextRange textRange,
717 const TextStyle& style,
718 const ClipContext& context) const {
719 if (style.hasBackground()) {
720 painter->drawRect(context.clip.makeOffset(this->offset() + SkPoint::Make(x, y)),
721 style.getBackgroundPaintOrID());
722 }
723 }
724
725 void TextLine::paintRoundRect(ParagraphPainter* painter, SkScalar x, SkScalar y, const Run* run) const {
726 size_t index = run->getIndexInLine();
727 if (index >= roundRectAttrs.size()) {
728 return;
729 }
730
731 const RoundRectAttr& attr = roundRectAttrs[index];
732 if (attr.roundRectStyle.color == 0) {
733 return;
734 }
735
736 SkScalar ltRadius = 0.0f;
737 SkScalar rtRadius = 0.0f;
738 SkScalar rbRadius = 0.0f;
739 SkScalar lbRadius = 0.0f;
740 RoundRectType rType = run->getRoundRectType();
741 if (rType == RoundRectType::ALL || rType == RoundRectType::LEFT_ONLY) {
742 ltRadius = std::fmin(attr.roundRectStyle.leftTopRadius, run->getMaxRoundRectRadius());
743 lbRadius = std::fmin(attr.roundRectStyle.leftBottomRadius, run->getMaxRoundRectRadius());
744 }
745 if (rType == RoundRectType::ALL || rType == RoundRectType::RIGHT_ONLY) {
746 rtRadius = std::fmin(attr.roundRectStyle.rightTopRadius, run->getMaxRoundRectRadius());
747 rbRadius = std::fmin(attr.roundRectStyle.rightBottomRadius, run->getMaxRoundRectRadius());
748 }
749 const SkVector radii[4] = {{ltRadius, ltRadius}, {rtRadius, rtRadius}, {rbRadius, rbRadius}, {lbRadius, lbRadius}};
750 SkRect skRect(SkRect::MakeLTRB(attr.rect.left(), run->getTopInGroup(), attr.rect.right(),
751 run->getBottomInGroup()));
752 SkRRect skRRect;
753 skRRect.setRectRadii(skRect, radii);
754 skRRect.offset(x + this->offset().x(), y + this->offset().y());
755 painter->drawRRect(skRRect, attr.roundRectStyle.color);
756 }
757
758 void TextLine::paintShadow(ParagraphPainter* painter,
759 SkScalar x,
760 SkScalar y,
761 TextRange textRange,
762 const TextStyle& style,
763 const ClipContext& context) const {
764 SkScalar correctedBaseline = SkScalarFloorToScalar(this->baseline() + style.getBaselineShift() + 0.5);
765
766 for (TextShadow shadow : style.getShadows()) {
767 if (!shadow.hasShadow()) continue;
768
769 #ifndef USE_SKIA_TXT
770 SkTextBlobBuilder builder;
771 #else
772 RSTextBlobBuilder builder;
773 #endif
774 context.run->copyTo(builder, context.pos, context.size);
775
776 if (context.clippingNeeded) {
777 painter->save();
778 SkRect clip = extendHeight(context);
779 clip.offset(x, y);
780 clip.offset(this->offset());
781 painter->clipRect(clip);
782 }
783 #ifndef USE_SKIA_TXT
784 auto blob = builder.make();
785 #else
786 auto blob = builder.Make();
787 #endif
788 painter->drawTextShadow(blob,
789 x + this->offset().fX + shadow.fOffset.x() + context.fTextShift,
790 #ifdef OHOS_SUPPORT
791 y + this->offset().fY + shadow.fOffset.y() + correctedBaseline -
792 (context.run ? context.run->fCompressionBaselineShift : 0),
793 #else
794 y + this->offset().fY + shadow.fOffset.y() + correctedBaseline,
795 #endif
796 shadow.fColor,
797 SkDoubleToScalar(shadow.fBlurSigma));
798 if (context.clippingNeeded) {
799 painter->restore();
800 }
801 }
802 }
803
804 SkScalar TextLine::calculateThickness(const TextStyle& style, const ClipContext& content)
805 {
806 Decorations decoration;
807 return decoration.calculateThickness(style, content);
808 }
809
810 void TextLine::paintDecorations(ParagraphPainter* painter, SkScalar x, SkScalar y, TextRange textRange, const TextStyle& style, const ClipContext& context) const {
811 ParagraphPainterAutoRestore ppar(painter);
812 painter->translate(x + this->offset().fX, y + this->offset().fY + style.getBaselineShift());
813 Decorations decorations;
814 decorations.setDecorationContext(fDecorationContext);
815 SkScalar correctedBaseline = SkScalarFloorToScalar(-this->sizes().rawAscent() + style.getBaselineShift() + 0.5);
816 decorations.paint(painter, style, context, correctedBaseline);
817 }
818
819 #ifdef OHOS_SUPPORT
820 void TextLine::justify(SkScalar maxWidth)
821 {
822 TextLineJustify textLineJustify(*this);
823 if (textLineJustify.justify(maxWidth)) {
824 this->fWidthWithSpaces += (maxWidth - this->widthWithoutEllipsis());
825 this->fAdvance.fX = maxWidth;
826 }
827 }
828
829 void TextLine::updateClusterOffsets(const Cluster* cluster, SkScalar shift, SkScalar prevShift)
830 {
831 this->shiftCluster(cluster, shift, prevShift);
832 }
833
834 void TextLine::justifyUpdateRtlWidth(const SkScalar maxWidth, const SkScalar textLen)
835 {
836 if (this->fOwner->paragraphStyle().getTextDirection() == TextDirection::kRtl) {
837 // Justify -> Right align
838 this->fShift = maxWidth - textLen;
839 }
840 }
841 #else
842 void TextLine::justify(SkScalar maxWidth) {
843 int whitespacePatches = 0;
844 SkScalar textLen = 0;
845 bool whitespacePatch = false;
846 // Take leading whitespaces width but do not increment a whitespace patch number
847 bool leadingWhitespaces = false;
848 this->iterateThroughClustersInGlyphsOrder(false, false,
849 [&](const Cluster* cluster, ClusterIndex index, bool ghost) {
850 if (cluster->isWhitespaceBreak()) {
851 if (index == 0) {
852 leadingWhitespaces = true;
853 } else if (!whitespacePatch && !leadingWhitespaces) {
854 // We only count patches BETWEEN words, not before
855 ++whitespacePatches;
856 }
857 whitespacePatch = !leadingWhitespaces;
858 } else if (cluster->isIdeographic()) {
859 // Whitespace break before and after
860 if (!whitespacePatch && index != 0) {
861 // We only count patches BETWEEN words, not before
862 ++whitespacePatches; // before
863 }
864 whitespacePatch = true;
865 leadingWhitespaces = false;
866 ++whitespacePatches; // after
867 } else {
868 whitespacePatch = false;
869 leadingWhitespaces = false;
870 }
871 textLen += cluster->width();
872 return true;
873 });
874
875 if (whitespacePatch) {
876 // We only count patches BETWEEN words, not after
877 --whitespacePatches;
878 }
879 if (whitespacePatches == 0) {
880 if (fOwner->paragraphStyle().getTextDirection() == TextDirection::kRtl) {
881 // Justify -> Right align
882 fShift = maxWidth - textLen;
883 }
884 return;
885 }
886 SkScalar step = (maxWidth - textLen) / whitespacePatches;
887 SkScalar shift = 0.0f;
888 SkScalar prevShift = 0.0f;
889
890 // Deal with the ghost spaces
891 auto ghostShift = maxWidth - this->fAdvance.fX;
892 // Spread the extra whitespaces
893 whitespacePatch = false;
894 // Do not break on leading whitespaces
895 leadingWhitespaces = false;
896 this->iterateThroughClustersInGlyphsOrder(false, true, [&](const Cluster* cluster, ClusterIndex index, bool ghost) {
897
898 if (ghost) {
899 if (cluster->run().leftToRight()) {
900 this->shiftCluster(cluster, ghostShift, ghostShift);
901 }
902 return true;
903 }
904
905 if (cluster->isWhitespaceBreak()) {
906 if (index == 0) {
907 leadingWhitespaces = true;
908 } else if (!whitespacePatch && !leadingWhitespaces) {
909 shift += step;
910 whitespacePatch = true;
911 --whitespacePatches;
912 }
913 } else if (cluster->isIdeographic()) {
914 if (!whitespacePatch && index != 0) {
915 shift += step;
916 --whitespacePatches;
917 }
918 whitespacePatch = false;
919 leadingWhitespaces = false;
920 } else {
921 whitespacePatch = false;
922 leadingWhitespaces = false;
923 }
924 this->shiftCluster(cluster, shift, prevShift);
925 prevShift = shift;
926 // We skip ideographic whitespaces
927 if (!cluster->isWhitespaceBreak() && cluster->isIdeographic()) {
928 shift += step;
929 whitespacePatch = true;
930 --whitespacePatches;
931 }
932 return true;
933 });
934
935 if (whitespacePatch && whitespacePatches < 0) {
936 whitespacePatches++;
937 shift -= step;
938 }
939
940 SkAssertResult(nearlyEqual(shift, maxWidth - textLen));
941 SkASSERT(whitespacePatches == 0);
942
943 this->fWidthWithSpaces += ghostShift;
944 this->fAdvance.fX = maxWidth;
945 }
946 #endif
947
948 void TextLine::shiftCluster(const Cluster* cluster, SkScalar shift, SkScalar prevShift) {
949
950 auto& run = cluster->run();
951 auto start = cluster->startPos();
952 auto end = cluster->endPos();
953
954 if (end == run.size()) {
955 // Set the same shift for the fake last glyph (to avoid all extra checks)
956 ++end;
957 }
958
959 if (run.fJustificationShifts.empty()) {
960 // Do not fill this array until needed
961 run.fJustificationShifts.push_back_n(run.size() + 1, { 0, 0 });
962 }
963
964 for (size_t pos = start; pos < end; ++pos) {
965 run.fJustificationShifts[pos] = { shift, prevShift };
966 }
967 }
968
969 void TextLine::spacingCluster(const Cluster* cluster, SkScalar spacing, SkScalar prevSpacing) {
970 auto& run = cluster->run();
971 auto start = cluster->startPos();
972 auto end = cluster->endPos();
973 if (end == run.size()) {
974 // Set the same shift for the fake last glyph (to avoid all extra checks)
975 ++end;
976 }
977
978 if (run.fAutoSpacings.empty()) {
979 // Do not fill this array until needed
980 run.fAutoSpacings.push_back_n(run.size() + 1, { 0, 0 });
981 }
982
983 for (size_t pos = start; pos < end; ++pos) {
984 run.fAutoSpacings[pos] = { spacing, prevSpacing};
985 }
986 }
987
988 void TextLine::countWord(int& wordCount, bool& inWord) {
989 for (auto clusterIndex = fGhostClusterRange.start; clusterIndex < fGhostClusterRange.end; ++clusterIndex) {
990 auto& cluster = fOwner->cluster(clusterIndex);
991 if (cluster.isWordBreak()) {
992 inWord = false;
993 } else if (!inWord) {
994 ++wordCount;
995 inWord = true;
996 }
997 }
998 }
999
1000 void TextLine::ellipsisNotFitProcess(EllipsisModal ellipsisModal) {
1001 if (fEllipsis) {
1002 return;
1003 }
1004
1005 // Weird situation: ellipsis does not fit; no ellipsis then
1006 switch (ellipsisModal) {
1007 case EllipsisModal::TAIL:
1008 fClusterRange.end = fClusterRange.start;
1009 fGhostClusterRange.end = fClusterRange.start;
1010 fText.end = fText.start;
1011 fTextIncludingNewlines.end = fTextIncludingNewlines.start;
1012 fTextExcludingSpaces.end = fTextExcludingSpaces.start;
1013 fAdvance.fX = 0;
1014 break;
1015 case EllipsisModal::HEAD:
1016 fClusterRange.start = fClusterRange.end;
1017 fGhostClusterRange.start = fClusterRange.end;
1018 fText.start = fText.end;
1019 fTextIncludingNewlines.start = fTextIncludingNewlines.end;
1020 fTextExcludingSpaces.start = fTextExcludingSpaces.end;
1021 fAdvance.fX = 0;
1022 break;
1023 default:
1024 return;
1025 }
1026 }
1027
1028 #ifdef OHOS_SUPPORT
1029 void TextLine::createTailEllipsis(SkScalar maxWidth, const SkString& ellipsis, bool ltr, WordBreakType wordBreakType) {
1030 // Replace some clusters with the ellipsis
1031 // Go through the clusters in the reverse logical order
1032 // taking off cluster by cluster until the ellipsis fits
1033 SkScalar width = fAdvance.fX;
1034 RunIndex lastRun = EMPTY_RUN;
1035 std::unique_ptr<Run> ellipsisRun;
1036 int wordCount = 0;
1037 bool inWord = false;
1038
1039 countWord(wordCount, inWord);
1040
1041 if (fClusterRange.width() == 0 && fGhostClusterRange.width() > 0) {
1042 // Only be entered when line is empty.
1043 handleTailEllipsisInEmptyLine(ellipsisRun, ellipsis, width, wordBreakType);
1044 return;
1045 }
1046
1047 bool iterForWord = false;
1048 for (auto clusterIndex = fClusterRange.end; clusterIndex > fClusterRange.start; --clusterIndex) {
1049 auto& cluster = fOwner->cluster(clusterIndex - 1);
1050 // Shape the ellipsis if the run has changed
1051 if (lastRun != cluster.runIndex()) {
1052 ellipsisRun = this->shapeEllipsis(ellipsis, &cluster);
1053 // We may need to continue
1054 lastRun = cluster.runIndex();
1055 }
1056
1057 if (!cluster.isWordBreak()) {
1058 inWord = true;
1059 } else if (inWord) {
1060 --wordCount;
1061 inWord = false;
1062 }
1063 // See if it fits
1064 if (ellipsisRun != nullptr && width + ellipsisRun->advance().fX > maxWidth) {
1065 if (!cluster.isHardBreak()) {
1066 width -= cluster.width();
1067 }
1068 // Continue if the ellipsis does not fit
1069 iterForWord = (wordCount != 1 && wordBreakType != WordBreakType::BREAK_ALL && !cluster.isWordBreak());
1070 if (std::floor(width) > 0) {
1071 continue;
1072 }
1073 }
1074
1075 if (iterForWord && !cluster.isWordBreak()) {
1076 width -= cluster.width();
1077 if (std::floor(width) > 0) {
1078 continue;
1079 }
1080 }
1081
1082 fEllipsis = std::move(ellipsisRun);
1083 fEllipsis->fTextRange = TextRange(cluster.textRange().end, cluster.textRange().end + ellipsis.size());
1084 TailEllipsisUpdateLine(cluster, width, clusterIndex, wordBreakType);
1085
1086 break;
1087 }
1088
1089 fWidthWithSpaces = width;
1090
1091 ellipsisNotFitProcess(EllipsisModal::TAIL);
1092 }
1093
1094 void TextLine::handleTailEllipsisInEmptyLine(std::unique_ptr<Run>& ellipsisRun, const SkString& ellipsis,
1095 SkScalar width, WordBreakType wordBreakType)
1096 {
1097 auto& cluster = fOwner->cluster(fClusterRange.start);
1098 ellipsisRun = this->shapeEllipsis(ellipsis, &cluster);
1099 fEllipsis = std::move(ellipsisRun);
1100 fEllipsis->fTextRange = TextRange(cluster.textRange().end, cluster.textRange().end + ellipsis.size());
1101 TailEllipsisUpdateLine(cluster, width, fGhostClusterRange.end, wordBreakType);
1102 fWidthWithSpaces = width;
1103 ellipsisNotFitProcess(EllipsisModal::TAIL);
1104 }
1105
1106 void TextLine::TailEllipsisUpdateLine(Cluster& cluster, float width, size_t clusterIndex, WordBreakType wordBreakType)
1107 {
1108 // We found enough room for the ellipsis
1109 fAdvance.fX = width;
1110 fEllipsis->setOwner(fOwner);
1111 fEllipsis->fClusterStart = cluster.textRange().end;
1112
1113 // Let's update the line
1114 if (wordBreakType != WordBreakType::BREAK_HYPHEN) {
1115 fTextRangeReplacedByEllipsis = TextRange(cluster.textRange().end, fOwner->text().size());
1116 }
1117 fClusterRange.end = clusterIndex;
1118 fGhostClusterRange.end = fClusterRange.end;
1119 // Get the last run directions after clipping
1120 fEllipsisIndex = cluster.runIndex();
1121 fLastClipRunLtr = fOwner->run(fEllipsisIndex).leftToRight();
1122 fText.end = cluster.textRange().end;
1123 fTextIncludingNewlines.end = cluster.textRange().end;
1124 fTextExcludingSpaces.end = cluster.textRange().end;
1125
1126 if (SkScalarNearlyZero(width)) {
1127 fRunsInVisualOrder.reset();
1128 }
1129 }
1130 #endif
1131
1132 #ifdef OHOS_SUPPORT
1133 void TextLine::createHeadEllipsis(SkScalar maxWidth, const SkString& ellipsis, bool) {
1134 if (fAdvance.fX <= maxWidth) {
1135 return;
1136 }
1137 SkScalar width = fAdvance.fX;
1138 std::unique_ptr<Run> ellipsisRun;
1139 RunIndex lastRun = EMPTY_RUN;
1140 for (auto clusterIndex = fGhostClusterRange.start; clusterIndex < fGhostClusterRange.end; ++clusterIndex) {
1141 auto& cluster = fOwner->cluster(clusterIndex);
1142 // Shape the ellipsis if the run has changed
1143 if (lastRun != cluster.runIndex()) {
1144 ellipsisRun = this->shapeEllipsis(ellipsis, &cluster);
1145 // We may need to continue
1146 lastRun = cluster.runIndex();
1147 }
1148 // See if it fits
1149 if (ellipsisRun && width + ellipsisRun->advance().fX > maxWidth) {
1150 width -= cluster.width();
1151 // Continue if the ellipsis does not fit
1152 if (std::floor(width) > 0) {
1153 continue;
1154 }
1155 }
1156
1157 // Get the last run directions after clipping
1158 fEllipsisIndex = cluster.runIndex();
1159 fLastClipRunLtr = fOwner->run(fEllipsisIndex).leftToRight();
1160
1161 // We found enough room for the ellipsis
1162 fAdvance.fX = width + ellipsisRun->advance().fX;
1163 fEllipsis = std::move(ellipsisRun);
1164 fEllipsis->setOwner(fOwner);
1165 fTextRangeReplacedByEllipsis = TextRange(0, cluster.textRange().start);
1166 fClusterRange.start = clusterIndex;
1167 fGhostClusterRange.start = fClusterRange.start;
1168 fEllipsis->fClusterStart = 0;
1169 fText.start = cluster.textRange().start;
1170 fTextIncludingNewlines.start = cluster.textRange().start;
1171 fTextExcludingSpaces.start = cluster.textRange().start;
1172 break;
1173 }
1174
1175 fWidthWithSpaces = width;
1176
1177 ellipsisNotFitProcess(EllipsisModal::HEAD);
1178 }
1179 #endif
1180
1181 static inline SkUnichar nextUtf8Unit(const char** ptr, const char* end) {
1182 SkUnichar val = SkUTF::NextUTF8(ptr, end);
1183 return val < 0 ? 0xFFFD : val;
1184 }
1185
1186 std::unique_ptr<Run> TextLine::shapeEllipsis(const SkString& ellipsis, const Cluster* cluster) {
1187 #ifdef OHOS_SUPPORT
1188 fEllipsisString = ellipsis;
1189 #endif
1190 return shapeString(ellipsis, cluster);
1191 }
1192
1193 std::unique_ptr<Run> TextLine::shapeString(const SkString& str, const Cluster* cluster) {
1194 class ShapeHandler final : public SkShaper::RunHandler {
1195 public:
1196 ShapeHandler(SkScalar lineHeight, bool useHalfLeading, SkScalar baselineShift, const SkString& str)
1197 : fRun(nullptr), fLineHeight(lineHeight), fUseHalfLeading(useHalfLeading), fBaselineShift(baselineShift), fStr(str) {}
1198 std::unique_ptr<Run> run() & { return std::move(fRun); }
1199
1200 private:
1201 void beginLine() override {}
1202
1203 void runInfo(const RunInfo&) override {}
1204
1205 void commitRunInfo() override {}
1206
1207 Buffer runBuffer(const RunInfo& info) override {
1208 SkASSERT(!fRun);
1209 fRun = std::make_unique<Run>(nullptr, info, 0, fLineHeight, fUseHalfLeading, fBaselineShift, 0, 0);
1210 return fRun->newRunBuffer();
1211 }
1212
1213 void commitRunBuffer(const RunInfo& info) override {
1214 fRun->fAdvance.fX = info.fAdvance.fX;
1215 fRun->fAdvance.fY = fRun->advance().fY;
1216 fRun->fPlaceholderIndex = std::numeric_limits<size_t>::max();
1217 // this may not be fully accurate, but limiting the changes to the text line
1218 fRun->fEllipsis = true;
1219 }
1220
1221 void commitLine() override {}
1222
1223 std::unique_ptr<Run> fRun;
1224 SkScalar fLineHeight;
1225 bool fUseHalfLeading;
1226 SkScalar fBaselineShift;
1227 SkString fStr;
1228 };
1229
1230 const Run& run = cluster->run();
1231 TextStyle textStyle = fOwner->paragraphStyle().getTextStyle();
1232 for (auto i = fBlockRange.start; i < fBlockRange.end; ++i) {
1233 auto& block = fOwner->block(i);
1234 if (run.leftToRight() && cluster->textRange().end <= block.fRange.end) {
1235 textStyle = block.fStyle;
1236 break;
1237 } else if (!run.leftToRight() && cluster->textRange().start <= block.fRange.end) {
1238 textStyle = block.fStyle;
1239 break;
1240 }
1241 }
1242
1243 #ifndef USE_SKIA_TXT
1244 auto shaped = [&](sk_sp<SkTypeface> typeface, bool fallback) -> std::unique_ptr<Run> {
1245 #else
1246 auto shaped = [&](std::shared_ptr<RSTypeface> typeface, bool fallback) -> std::unique_ptr<Run> {
1247 #endif
1248 ShapeHandler handler(run.heightMultiplier(), run.useHalfLeading(), run.baselineShift(), str);
1249 #ifndef USE_SKIA_TXT
1250 SkFont font(typeface, textStyle.getFontSize());
1251 font.setEdging(SkFont::Edging::kAntiAlias);
1252 font.setHinting(SkFontHinting::kSlight);
1253 font.setSubpixel(true);
1254 #else
1255 RSFont font(typeface, textStyle.getFontSize(), 1, 0);
1256 font.SetEdging(RSDrawing::FontEdging::ANTI_ALIAS);
1257 font.SetHinting(RSDrawing::FontHinting::SLIGHT);
1258 font.SetSubpixel(true);
1259 #endif
1260
1261 #ifndef USE_SKIA_TXT
1262 std::unique_ptr<SkShaper> shaper = SkShaper::MakeShapeDontWrapOrReorder(
1263 fOwner->getUnicode()->copy(),
1264 fallback ? SkFontMgr::RefDefault() : SkFontMgr::RefEmpty());
1265 #else
1266 std::unique_ptr<SkShaper> shaper = SkShaper::MakeShapeDontWrapOrReorder(
1267 fOwner->getUnicode()->copy(),
1268 fallback ? RSFontMgr::CreateDefaultFontMgr() : RSFontMgr::CreateDefaultFontMgr());
1269 #endif
1270 shaper->shape(str.c_str(),
1271 str.size(),
1272 font,
1273 true,
1274 std::numeric_limits<SkScalar>::max(),
1275 &handler);
1276 auto run = handler.run();
1277 run->fTextRange = TextRange(0, str.size());
1278 run->fOwner = fOwner;
1279 return run;
1280 };
1281
1282 // Check all allowed fonts
1283 auto typefaces = fOwner->fontCollection()->findTypefaces(
1284 textStyle.getFontFamilies(), textStyle.getFontStyle(), textStyle.getFontArguments());
1285 for (const auto& typeface : typefaces) {
1286 auto run = shaped(typeface, false);
1287 if (run->isResolved()) {
1288 return run;
1289 }
1290 }
1291
1292 // Try the fallback
1293 if (fOwner->fontCollection()->fontFallbackEnabled()) {
1294 const char* ch = str.c_str();
1295 SkUnichar unicode = nextUtf8Unit(&ch, str.c_str() + str.size());
1296
1297 auto typeface = fOwner->fontCollection()->defaultFallback(
1298 unicode, textStyle.getFontStyle(), textStyle.getLocale());
1299 if (typeface) {
1300 if (textStyle.getFontArguments()) {
1301 typeface = fOwner->fontCollection()->CloneTypeface(typeface, textStyle.getFontArguments());
1302 }
1303 auto run = shaped(typeface, true);
1304 if (run->isResolved()) {
1305 return run;
1306 }
1307 }
1308 }
1309
1310 // Check the current font
1311 #ifndef USE_SKIA_TXT
1312 auto finalRun = shaped(run.fFont.refTypeface(), false);
1313 #else
1314 auto finalRun = shaped(const_cast<RSFont&>(run.fFont).GetTypeface(), false);
1315 #endif
1316 if (finalRun->isResolved()) {
1317 return finalRun;
1318 }
1319 return finalRun;
1320 }
1321
1322 #ifdef OHOS_SUPPORT
1323 void TextLine::measureTextWithSpacesAtTheEnd(ClipContext& context, bool includeGhostSpaces) const
1324 {
1325 if (compareRound(context.clip.fRight, fAdvance.fX, fOwner->getApplyRoundingHack()) > 0 && !includeGhostSpaces &&
1326 fAdvance.fX > 0) {
1327 // There are few cases when we need it.
1328 // The most important one: we measure the text with spaces at the end (or at the beginning in RTL)
1329 // and we should ignore these spaces
1330 if (fOwner->paragraphStyle().getTextDirection() == TextDirection::kLtr) {
1331 // We only use this member for LTR
1332 context.fExcludedTrailingSpaces = std::max(context.clip.fRight - fAdvance.fX, 0.0f);
1333 context.clippingNeeded = true;
1334 context.clip.fRight = fAdvance.fX;
1335 }
1336 }
1337 }
1338 #endif
1339
1340 TextLine::ClipContext TextLine::measureTextInsideOneRun(TextRange textRange,
1341 const Run* run,
1342 SkScalar runOffsetInLine,
1343 SkScalar textOffsetInRunInLine,
1344 bool includeGhostSpaces,
1345 TextAdjustment textAdjustment) const {
1346 ClipContext result = { run, 0, run->size(), 0, SkRect::MakeEmpty(), 0, false };
1347
1348 if (run->fEllipsis) {
1349 // Both ellipsis and placeholders can only be measured as one glyph
1350 result.fTextShift = runOffsetInLine;
1351 result.clip = SkRect::MakeXYWH(runOffsetInLine,
1352 sizes().runTop(run, this->fAscentStyle),
1353 run->advance().fX,
1354 run->calculateHeight(this->fAscentStyle,this->fDescentStyle));
1355 return result;
1356 } else if (run->isPlaceholder()) {
1357 result.fTextShift = runOffsetInLine;
1358 if (SkScalarIsFinite(run->fFontMetrics.fAscent)) {
1359 result.clip = SkRect::MakeXYWH(runOffsetInLine,
1360 sizes().runTop(run, this->fAscentStyle),
1361 run->advance().fX,
1362 run->calculateHeight(this->fAscentStyle,this->fDescentStyle));
1363 } else {
1364 result.clip = SkRect::MakeXYWH(runOffsetInLine, run->fFontMetrics.fAscent, run->advance().fX, 0);
1365 }
1366 return result;
1367 } else if (textRange.empty()) {
1368 return result;
1369 }
1370
1371 TextRange originalTextRange(textRange); // We need it for proportional measurement
1372 // Find [start:end] clusters for the text
1373 while (true) {
1374 // Update textRange by cluster edges (shift start up to the edge of the cluster)
1375 // TODO: remove this limitation?
1376 TextRange updatedTextRange;
1377 bool found;
1378 std::tie(found, updatedTextRange.start, updatedTextRange.end) =
1379 run->findLimitingGlyphClusters(textRange);
1380 if (!found) {
1381 return result;
1382 }
1383
1384 if ((textAdjustment & TextAdjustment::Grapheme) == 0) {
1385 textRange = updatedTextRange;
1386 break;
1387 }
1388
1389 // Update text range by grapheme edges (shift start up to the edge of the grapheme)
1390 std::tie(found, updatedTextRange.start, updatedTextRange.end) =
1391 run->findLimitingGraphemes(updatedTextRange);
1392 if (updatedTextRange == textRange) {
1393 break;
1394 }
1395
1396 // Some clusters are inside graphemes and we need to adjust them
1397 //SkDebugf("Correct range: [%d:%d) -> [%d:%d)\n", textRange.start, textRange.end, startIndex, endIndex);
1398 textRange = updatedTextRange;
1399
1400 // Move the start until it's on the grapheme edge (and glypheme, too)
1401 }
1402 Cluster* start = &fOwner->cluster(fOwner->clusterIndex(textRange.start));
1403 Cluster* end = &fOwner->cluster(fOwner->clusterIndex(textRange.end - (textRange.width() == 0 ? 0 : 1)));
1404
1405 if (!run->leftToRight()) {
1406 std::swap(start, end);
1407 }
1408 result.pos = start->startPos();
1409 result.size = (end->isHardBreak() ? end->startPos() : end->endPos()) - start->startPos();
1410 auto textStartInRun = run->positionX(start->startPos());
1411 auto textStartInLine = runOffsetInLine + textOffsetInRunInLine;
1412 if (!run->leftToRight()) {
1413 std::swap(start, end);
1414 }
1415 /*
1416 if (!run->fJustificationShifts.empty()) {
1417 SkDebugf("Justification for [%d:%d)\n", textRange.start, textRange.end);
1418 for (auto i = result.pos; i < result.pos + result.size; ++i) {
1419 auto j = run->fJustificationShifts[i];
1420 SkDebugf("[%d] = %f %f\n", i, j.fX, j.fY);
1421 }
1422 }
1423 */
1424 // Calculate the clipping rectangle for the text with cluster edges
1425 // There are 2 cases:
1426 // EOL (when we expect the last cluster clipped without any spaces)
1427 // Anything else (when we want the cluster width contain all the spaces -
1428 // coming from letter spacing or word spacing or justification)
1429 result.clip =
1430 SkRect::MakeXYWH(0,
1431 sizes().runTop(run, this->fAscentStyle),
1432 run->calculateWidth(result.pos, result.pos + result.size, false),
1433 run->calculateHeight(this->fAscentStyle,this->fDescentStyle));
1434
1435 // Correct the width in case the text edges don't match clusters
1436 // TODO: This is where we get smart about selecting a part of a cluster
1437 // by shaping each grapheme separately and then use the result sizes
1438 // to calculate the proportions
1439 auto leftCorrection = start->sizeToChar(originalTextRange.start);
1440 auto rightCorrection = end->sizeFromChar(originalTextRange.end - 1);
1441 /*
1442 SkDebugf("[%d: %d) => [%d: %d), @%d, %d: [%f:%f) + [%f:%f) = ", // جَآَهُ
1443 originalTextRange.start, originalTextRange.end, textRange.start, textRange.end,
1444 result.pos, result.size,
1445 result.clip.fLeft, result.clip.fRight, leftCorrection, rightCorrection);
1446 */
1447 result.clippingNeeded = leftCorrection != 0 || rightCorrection != 0;
1448 if (run->leftToRight()) {
1449 result.clip.fLeft += leftCorrection;
1450 result.clip.fRight -= rightCorrection;
1451 textStartInLine -= leftCorrection;
1452 } else {
1453 result.clip.fRight -= leftCorrection;
1454 result.clip.fLeft += rightCorrection;
1455 textStartInLine -= rightCorrection;
1456 }
1457
1458 result.clip.offset(textStartInLine, 0);
1459 //SkDebugf("@%f[%f:%f)\n", textStartInLine, result.clip.fLeft, result.clip.fRight);
1460
1461 #ifdef OHOS_SUPPORT
1462 measureTextWithSpacesAtTheEnd(result, includeGhostSpaces);
1463 #else
1464 if (compareRound(result.clip.fRight, fAdvance.fX, fOwner->getApplyRoundingHack()) > 0 && !includeGhostSpaces) {
1465 // There are few cases when we need it.
1466 // The most important one: we measure the text with spaces at the end (or at the beginning in RTL)
1467 // and we should ignore these spaces
1468 if (fOwner->paragraphStyle().getTextDirection() == TextDirection::kLtr) {
1469 // We only use this member for LTR
1470 result.fExcludedTrailingSpaces = std::max(result.clip.fRight - fAdvance.fX, 0.0f);
1471 result.clippingNeeded = true;
1472 result.clip.fRight = fAdvance.fX;
1473 }
1474 }
1475
1476 if (result.clip.width() < 0) {
1477 // Weird situation when glyph offsets move the glyph to the left
1478 // (happens with zalgo texts, for instance)
1479 result.clip.fRight = result.clip.fLeft;
1480 }
1481 #endif
1482
1483 // The text must be aligned with the lineOffset
1484 result.fTextShift = textStartInLine - textStartInRun;
1485
1486 return result;
1487 }
1488
1489 void TextLine::iterateThroughClustersInGlyphsOrder(bool reversed,
1490 bool includeGhosts,
1491 const ClustersVisitor& visitor) const {
1492 // Walk through the clusters in the logical order (or reverse)
1493 SkSpan<const size_t> runs(fRunsInVisualOrder.data(), fRunsInVisualOrder.size());
1494 bool ignore = false;
1495 ClusterIndex index = 0;
1496 directional_for_each(runs, !reversed, [&](decltype(runs[0]) r) {
1497 if (ignore) return;
1498 auto run = this->fOwner->run(r);
1499 auto trimmedRange = fClusterRange.intersection(run.clusterRange());
1500 auto trailedRange = fGhostClusterRange.intersection(run.clusterRange());
1501 SkASSERT(trimmedRange.start == trailedRange.start);
1502
1503 auto trailed = fOwner->clusters(trailedRange);
1504 auto trimmed = fOwner->clusters(trimmedRange);
1505 directional_for_each(trailed, reversed != run.leftToRight(), [&](Cluster& cluster) {
1506 if (ignore) return;
1507 bool ghost = &cluster >= trimmed.end();
1508 if (!includeGhosts && ghost) {
1509 return;
1510 }
1511 if (!visitor(&cluster, index++, ghost)) {
1512
1513 ignore = true;
1514 return;
1515 }
1516 });
1517 });
1518 }
1519
1520 #ifdef OHOS_SUPPORT
1521 void TextLine::computeNextPaintGlyphRange(ClipContext& context,
1522 const TextRange& lastGlyphRange, StyleType styleType) const
1523 {
1524 if (styleType != StyleType::kForeground) {
1525 return;
1526 }
1527 TextRange curGlyphRange = TextRange(context.pos, context.pos + context.size);
1528 auto intersect = intersected(lastGlyphRange, curGlyphRange);
1529 if (intersect == EMPTY_TEXT || (intersect.start != curGlyphRange.start && intersect.end != curGlyphRange.end)) {
1530 return;
1531 }
1532 if (intersect.start == curGlyphRange.start) {
1533 curGlyphRange = TextRange(intersect.end, curGlyphRange.end);
1534 } else if (intersect.end == curGlyphRange.end) {
1535 curGlyphRange = TextRange(curGlyphRange.start, intersect.start);
1536 }
1537
1538 context.pos = curGlyphRange.start;
1539 context.size = curGlyphRange.width();
1540 }
1541 #endif
1542
1543 SkScalar TextLine::iterateThroughSingleRunByStyles(TextAdjustment textAdjustment,
1544 const Run* run,
1545 SkScalar runOffset,
1546 TextRange textRange,
1547 StyleType styleType,
1548 const RunStyleVisitor& visitor) const {
1549 auto includeGhostSpaces = (styleType == StyleType::kDecorations || styleType == StyleType::kBackground ||
1550 styleType == StyleType::kNone);
1551 auto correctContext = [&](TextRange textRange, SkScalar textOffsetInRun) -> ClipContext {
1552 auto result = this->measureTextInsideOneRun(
1553 textRange, run, runOffset, textOffsetInRun, includeGhostSpaces, textAdjustment);
1554 if (styleType == StyleType::kDecorations) {
1555 // Decorations are drawn based on the real font metrics (regardless of styles and strut)
1556 result.clip.fTop = this->sizes().runTop(run, LineMetricStyle::CSS) - run->baselineShift();
1557 result.clip.fBottom = result.clip.fTop +
1558 run->calculateHeight(LineMetricStyle::CSS, LineMetricStyle::CSS);
1559 }
1560 return result;
1561 };
1562
1563 if (run->fEllipsis) {
1564 // Extra efforts to get the ellipsis text style
1565 ClipContext clipContext = correctContext(run->textRange(), 0.0f);
1566 for (BlockIndex index = fBlockRange.start; index < fBlockRange.end; ++index) {
1567 auto block = fOwner->styles().begin() + index;
1568 #ifdef OHOS_SUPPORT
1569 TextRange intersect = intersected(block->fRange,
1570 TextRange(run->textRange().start - 1, run->textRange().end));
1571 if (intersect.width() > 0) {
1572 visitor(fTextRangeReplacedByEllipsis, block->fStyle, clipContext);
1573 return run->advance().fX;
1574 }
1575 #else
1576 if (block->fRange.start >= run->fClusterStart && block->fRange.end < run->fClusterStart) {
1577 visitor(fTextRangeReplacedByEllipsis, block->fStyle, clipContext);
1578 return run->advance().fX;
1579 }
1580 #endif
1581 }
1582 SkASSERT(false);
1583 }
1584
1585 if (styleType == StyleType::kNone) {
1586 ClipContext clipContext = correctContext(textRange, 0.0f);
1587 #ifdef OHOS_SUPPORT
1588 if (clipContext.clip.height() > 0 ||
1589 (run->isPlaceholder() && SkScalarNearlyZero(clipContext.clip.height()))) {
1590 #else
1591 if (clipContext.clip.height() > 0) {
1592 #endif
1593 visitor(textRange, TextStyle(), clipContext);
1594 return clipContext.clip.width();
1595 } else {
1596 return 0;
1597 }
1598 }
1599
1600 TextIndex start = EMPTY_INDEX;
1601 size_t size = 0;
1602 const TextStyle* prevStyle = nullptr;
1603 SkScalar textOffsetInRun = 0;
1604 #ifdef OHOS_SUPPORT
1605 TextRange lastGlyphRange = EMPTY_TEXT;
1606 #endif
1607 const BlockIndex blockRangeSize = fBlockRange.end - fBlockRange.start;
1608 for (BlockIndex index = 0; index <= blockRangeSize; ++index) {
1609
1610 TextRange intersect;
1611 TextStyle* style = nullptr;
1612 if (index < blockRangeSize) {
1613 auto block = fOwner->styles().begin() +
1614 (run->leftToRight() ? fBlockRange.start + index : fBlockRange.end - index - 1);
1615
1616 // Get the text
1617 intersect = intersected(block->fRange, textRange);
1618 if (intersect.width() == 0) {
1619 if (start == EMPTY_INDEX) {
1620 // This style is not applicable to the text yet
1621 continue;
1622 } else {
1623 // We have found all the good styles already
1624 // but we need to process the last one of them
1625 intersect = TextRange(start, start + size);
1626 index = fBlockRange.end;
1627 }
1628 } else {
1629 // Get the style
1630 style = &block->fStyle;
1631 if (start != EMPTY_INDEX && style->matchOneAttribute(styleType, *prevStyle)) {
1632 size += intersect.width();
1633 // RTL text intervals move backward
1634 start = std::min(intersect.start, start);
1635 continue;
1636 } else if (start == EMPTY_INDEX ) {
1637 // First time only
1638 prevStyle = style;
1639 size = intersect.width();
1640 start = intersect.start;
1641 continue;
1642 }
1643 }
1644 } else if (prevStyle != nullptr) {
1645 // This is the last style
1646 } else {
1647 break;
1648 }
1649
1650 // We have the style and the text
1651 auto runStyleTextRange = TextRange(start, start + size);
1652 ClipContext clipContext = correctContext(runStyleTextRange, textOffsetInRun);
1653 textOffsetInRun += clipContext.clip.width();
1654 if (clipContext.clip.height() == 0) {
1655 continue;
1656 }
1657
1658 RectStyle temp;
1659 if (styleType == StyleType::kBackground &&
1660 prevStyle->getBackgroundRect() != temp &&
1661 prevStyle->getHeight() != 0) {
1662 #ifdef OHOS_SUPPORT
1663 clipContext.clip.fTop = run->fFontMetrics.fAscent + this->baseline() + run->fBaselineShift;
1664 #else
1665 clipContext.clip.fTop = run->fFontMetrics.fAscent - run->fCorrectAscent;
1666 #endif
1667 clipContext.clip.fBottom = clipContext.clip.fTop + run->fFontMetrics.fDescent -
1668 run->fFontMetrics.fAscent;
1669 }
1670 #ifdef OHOS_SUPPORT
1671 computeNextPaintGlyphRange(clipContext, lastGlyphRange, styleType);
1672 if (clipContext.size != 0) {
1673 lastGlyphRange = TextRange(clipContext.pos, clipContext.pos + clipContext.size);
1674 }
1675 #endif
1676 visitor(runStyleTextRange, *prevStyle, clipContext);
1677
1678 // Start all over again
1679 prevStyle = style;
1680 start = intersect.start;
1681 size = intersect.width();
1682 }
1683 return textOffsetInRun;
1684 }
1685
1686 #ifdef OHOS_SUPPORT
1687 bool TextLine::processEllipsisRun(bool& isAlreadyUseEllipsis,
1688 SkScalar& runOffset,
1689 EllipsisReadStrategy ellipsisReadStrategy,
1690 const RunVisitor& visitor,
1691 SkScalar& runWidthInLine) const {
1692 isAlreadyUseEllipsis = true;
1693 return processInsertedRun(fEllipsis.get(), runOffset, ellipsisReadStrategy,
1694 visitor, runWidthInLine);
1695 }
1696
1697 bool TextLine::processInsertedRun(const Run* extra,
1698 SkScalar& runOffset,
1699 EllipsisReadStrategy ellipsisReadStrategy,
1700 const RunVisitor& visitor,
1701 SkScalar& runWidthInLine) const {
1702 runOffset += extra->offset().fX;
1703 if (ellipsisReadStrategy == EllipsisReadStrategy::READ_REPLACED_WORD) {
1704 if (!visitor(extra, runOffset, fTextRangeReplacedByEllipsis, &runWidthInLine)) {
1705 LOGE("Visitor process ellipsis replace word error!");
1706 return false;
1707 }
1708 } else if (ellipsisReadStrategy == EllipsisReadStrategy::READ_ELLIPSIS_WORD) {
1709 if (!visitor(extra, runOffset, extra->textRange(), &runWidthInLine)) {
1710 LOGE("Visitor process ellipsis word error!");
1711 return false;
1712 }
1713 } else {
1714 runWidthInLine = extra->advance().fX;
1715 }
1716 return true;
1717 }
1718 #endif
1719
1720 #ifdef OHOS_SUPPORT
1721 void TextLine::iterateThroughVisualRuns(EllipsisReadStrategy ellipsisReadStrategy,
1722 bool includingGhostSpaces,
1723 const RunVisitor& visitor) const {
1724 // Walk through all the runs that intersect with the line in visual order
1725 SkScalar width = 0;
1726 SkScalar runOffset = 0;
1727 SkScalar totalWidth = 0;
1728 #ifdef OHOS_SUPPORT
1729 bool ellipsisModeIsHead = fIsTextLineEllipsisHeadModal ? true :
1730 fOwner->paragraphStyle().getEllipsisMod() == EllipsisModal::HEAD;
1731 #else
1732 bool ellipsisModeIsHead = fOwner->paragraphStyle().getEllipsisMod() == EllipsisModal::HEAD;
1733 #endif
1734 bool isAlreadyUseEllipsis = false;
1735 auto textRange = includingGhostSpaces ? this->textWithNewlines() : this->trimmedText();
1736
1737 if (fRunsInVisualOrder.size() == 0) {
1738 if (fEllipsis != nullptr) {
1739 if (!processEllipsisRun(isAlreadyUseEllipsis, runOffset, ellipsisReadStrategy, visitor, width)) {
1740 return;
1741 }
1742 totalWidth += width;
1743 }
1744 if (fHyphenRun != nullptr) { // not sure if this is basically valid in real life
1745 if (!processInsertedRun(fHyphenRun.get(), runOffset, ellipsisReadStrategy, visitor, width)) {
1746 return;
1747 }
1748 totalWidth += width;
1749 }
1750 }
1751
1752 for (auto& runIndex : fRunsInVisualOrder) {
1753 // add the lastClipRun's left ellipsis if necessary
1754 if (!isAlreadyUseEllipsis && fEllipsisIndex == runIndex &&
1755 ((!fLastClipRunLtr && !ellipsisModeIsHead) || (ellipsisModeIsHead && fLastClipRunLtr))) {
1756 if (!processEllipsisRun(isAlreadyUseEllipsis, runOffset, ellipsisReadStrategy, visitor, width)) {
1757 return;
1758 }
1759 runOffset += width;
1760 totalWidth += width;
1761 }
1762
1763 const auto run = &this->fOwner->run(runIndex);
1764 auto lineIntersection = intersected(run->textRange(), textRange);
1765 if (lineIntersection.width() == 0 && this->width() != 0) {
1766 // TODO: deal with empty runs in a better way
1767 continue;
1768 }
1769 if (!run->leftToRight() && runOffset == 0 && includingGhostSpaces) {
1770 // runOffset does not take in account a possibility
1771 // that RTL run could start before the line (trailing spaces)
1772 // so we need to do runOffset -= "trailing whitespaces length"
1773 TextRange whitespaces = intersected(
1774 TextRange(fTextExcludingSpaces.end, fTextIncludingNewlines.end), run->fTextRange);
1775 if (whitespaces.width() > 0) {
1776 auto whitespacesLen = measureTextInsideOneRun(whitespaces, run, runOffset, 0, true,
1777 TextAdjustment::GlyphCluster).clip.width();
1778 runOffset -= whitespacesLen;
1779 }
1780 }
1781
1782 if (!visitor(run, runOffset, lineIntersection, &width)) {
1783 return;
1784 }
1785
1786 runOffset += width;
1787 totalWidth += width;
1788
1789 // add the lastClipRun's right ellipsis if necessary
1790 if (!isAlreadyUseEllipsis && fEllipsisIndex == runIndex) {
1791 if (!processEllipsisRun(isAlreadyUseEllipsis, runOffset, ellipsisReadStrategy, visitor, width)) {
1792 return;
1793 }
1794 runOffset += width;
1795 totalWidth += width;
1796 }
1797 if (runIndex == fHyphenIndex) {
1798 if (!processInsertedRun(fHyphenRun.get(), runOffset, ellipsisReadStrategy, visitor, width)) {
1799 return;
1800 }
1801 runOffset += width;
1802 totalWidth += width;
1803 }
1804 }
1805
1806 if (!includingGhostSpaces && compareRound(totalWidth, this->width(), fOwner->getApplyRoundingHack()) != 0) {
1807 // This is a very important assert!
1808 // It asserts that 2 different ways of calculation come with the same results
1809 SkDebugf("ASSERT: %f != %f\n", totalWidth, this->width());
1810 SkASSERT(false);
1811 }
1812 }
1813 #else
1814 void TextLine::iterateThroughVisualRuns(bool includingGhostSpaces, const RunVisitor& visitor) const {
1815
1816 // Walk through all the runs that intersect with the line in visual order
1817 SkScalar width = 0;
1818 SkScalar runOffset = 0;
1819 SkScalar totalWidth = 0;
1820 auto textRange = includingGhostSpaces ? this->textWithNewlines() : this->trimmedText();
1821 for (auto& runIndex : fRunsInVisualOrder) {
1822
1823 const auto run = &this->fOwner->run(runIndex);
1824 auto lineIntersection = intersected(run->textRange(), textRange);
1825 if (lineIntersection.width() == 0 && this->width() != 0) {
1826 // TODO: deal with empty runs in a better way
1827 continue;
1828 }
1829 if (!run->leftToRight() && runOffset == 0 && includingGhostSpaces) {
1830 // runOffset does not take in account a possibility
1831 // that RTL run could start before the line (trailing spaces)
1832 // so we need to do runOffset -= "trailing whitespaces length"
1833 TextRange whitespaces = intersected(
1834 TextRange(fTextExcludingSpaces.end, fTextIncludingNewlines.end), run->fTextRange);
1835 if (whitespaces.width() > 0) {
1836 auto whitespacesLen = measureTextInsideOneRun(whitespaces, run, runOffset, 0, true, false).clip.width();
1837 runOffset -= whitespacesLen;
1838 }
1839 }
1840 runOffset += width;
1841 totalWidth += width;
1842 if (!visitor(run, runOffset, lineIntersection, &width)) {
1843 return;
1844 }
1845 }
1846
1847 runOffset += width;
1848 totalWidth += width;
1849
1850 if (fEllipsis != nullptr) {
1851 if (visitor(fEllipsis.get(), runOffset, fEllipsis->textRange(), &width)) {
1852 totalWidth += width;
1853 }
1854 }
1855
1856 // This is a very important assert!
1857 // It asserts that 2 different ways of calculation come with the same results
1858 if (!includingGhostSpaces && compareRound(totalWidth, this->width()) != 0) {
1859 SkDebugf("ASSERT: %f != %f\n", totalWidth, this->width());
1860 SkASSERT(false);
1861 }
1862 }
1863 #endif
1864
1865 SkVector TextLine::offset() const {
1866 return fOffset + SkVector::Make(fShift, 0);
1867 }
1868
1869 LineMetrics TextLine::getMetrics() const {
1870 LineMetrics result;
1871
1872 // Fill out the metrics
1873 fOwner->ensureUTF16Mapping();
1874 result.fStartIndex = fOwner->getUTF16Index(fTextExcludingSpaces.start);
1875 result.fEndExcludingWhitespaces = fOwner->getUTF16Index(fTextExcludingSpaces.end);
1876 result.fEndIndex = fOwner->getUTF16Index(fText.end);
1877 result.fEndIncludingNewline = fOwner->getUTF16Index(fTextIncludingNewlines.end);
1878 result.fHardBreak = endsWithHardLineBreak();
1879 result.fAscent = - fMaxRunMetrics.ascent();
1880 result.fDescent = fMaxRunMetrics.descent();
1881 result.fUnscaledAscent = - fMaxRunMetrics.ascent(); // TODO: implement
1882 result.fHeight = fAdvance.fY;
1883 result.fWidth = fAdvance.fX;
1884 if (fOwner->getApplyRoundingHack()) {
1885 result.fHeight = littleRound(result.fHeight);
1886 result.fWidth = littleRound(result.fWidth);
1887 }
1888 result.fLeft = this->offset().fX;
1889 // This is Flutter definition of a baseline
1890 result.fBaseline = this->offset().fY + this->height() - this->sizes().descent();
1891 result.fLineNumber = this - fOwner->lines().begin();
1892 result.fWidthWithSpaces = fWidthWithSpaces;
1893 result.fTopHeight = this->offset().fY;
1894
1895 // Fill out the style parts
1896 #ifdef OHOS_SUPPORT
1897 this->iterateThroughVisualRuns(EllipsisReadStrategy::READ_REPLACED_WORD, false,
1898 #else
1899 this->iterateThroughVisualRuns(false,
1900 #endif
1901 [this, &result]
1902 (const Run* run, SkScalar runOffsetInLine, TextRange textRange, SkScalar* runWidthInLine) {
1903 if (run->placeholderStyle() != nullptr) {
1904 *runWidthInLine = run->advance().fX;
1905 return true;
1906 }
1907 *runWidthInLine = this->iterateThroughSingleRunByStyles(
1908 TextAdjustment::GlyphCluster, run, runOffsetInLine, textRange, StyleType::kForeground,
1909 [&result, &run](TextRange textRange, const TextStyle& style, const ClipContext& context) {
1910 #ifndef USE_SKIA_TXT
1911 SkFontMetrics fontMetrics;
1912 run->fFont.getMetrics(&fontMetrics);
1913 #else
1914 RSFontMetrics fontMetrics;
1915 run->fFont.GetMetrics(&fontMetrics);
1916 #endif
1917 #ifdef OHOS_SUPPORT
1918 auto decompressFont = run->fFont;
1919 scaleFontWithCompressionConfig(decompressFont, ScaleOP::DECOMPRESS);
1920 metricsIncludeFontPadding(&fontMetrics, decompressFont);
1921 #endif
1922 StyleMetrics styleMetrics(&style, fontMetrics);
1923 result.fLineMetrics.emplace(textRange.start, styleMetrics);
1924 });
1925 return true;
1926 });
1927
1928 return result;
1929 }
1930
1931 bool TextLine::isFirstLine() const {
1932 return this == &fOwner->lines().front();
1933 }
1934
1935 bool TextLine::isLastLine() const {
1936 return this == &fOwner->lines().back();
1937 }
1938
1939 bool TextLine::endsWithHardLineBreak() const {
1940 // TODO: For some reason Flutter imagines a hard line break at the end of the last line.
1941 // To be removed...
1942 return (fGhostClusterRange.width() > 0 && fOwner->cluster(fGhostClusterRange.end - 1).isHardBreak()) ||
1943 fEllipsis != nullptr ||
1944 fGhostClusterRange.end == fOwner->clusters().size() - 1;
1945 }
1946 #ifdef OHOS_SUPPORT
1947 bool TextLine::endsWithOnlyHardBreak() const
1948 {
1949 return (fGhostClusterRange.width() > 0 && fOwner->cluster(fGhostClusterRange.end - 1).isHardBreak());
1950 }
1951 #endif
1952
1953 void TextLine::getRectsForRange(TextRange textRange0,
1954 RectHeightStyle rectHeightStyle,
1955 RectWidthStyle rectWidthStyle,
1956 std::vector<TextBox>& boxes) const
1957 {
1958 const Run* lastRun = nullptr;
1959 auto startBox = boxes.size();
1960 #ifdef OHOS_SUPPORT
1961 this->iterateThroughVisualRuns(EllipsisReadStrategy::READ_REPLACED_WORD, true,
1962 #else
1963 this->iterateThroughVisualRuns(true,
1964 #endif
1965 [textRange0, rectHeightStyle, rectWidthStyle, &boxes, &lastRun, startBox, this]
1966 (const Run* run, SkScalar runOffsetInLine, TextRange textRange, SkScalar* runWidthInLine) {
1967 *runWidthInLine = this->iterateThroughSingleRunByStyles(
1968 TextAdjustment::GraphemeGluster, run, runOffsetInLine, textRange, StyleType::kNone,
1969 [run, runOffsetInLine, textRange0, rectHeightStyle, rectWidthStyle, &boxes, &lastRun, startBox, this]
1970 (TextRange textRange, const TextStyle& style, const TextLine::ClipContext& lineContext) {
1971
1972 auto intersect = textRange * textRange0;
1973 #ifdef OHOS_SUPPORT
1974 if (intersect.empty() && !this->fBreakWithHyphen) {
1975 #else
1976 if (intersect.empty()) {
1977 #endif
1978 return true;
1979 }
1980
1981 auto paragraphStyle = fOwner->paragraphStyle();
1982
1983 // Found a run that intersects with the text
1984 auto context = this->measureTextInsideOneRun(
1985 intersect, run, runOffsetInLine, 0, true, TextAdjustment::GraphemeGluster);
1986 SkRect clip = context.clip;
1987 clip.offset(lineContext.fTextShift - context.fTextShift, 0);
1988
1989 switch (rectHeightStyle) {
1990 case RectHeightStyle::kMax:
1991 // TODO: Change it once flutter rolls into google3
1992 // (probably will break things if changed before)
1993 #ifdef OHOS_SUPPORT
1994 if (endsWithOnlyHardBreak() && fOwner->paragraphStyle().getParagraphSpacing() > 0) {
1995 clip.fBottom = this->height() - fOwner->paragraphStyle().getParagraphSpacing();
1996 } else {
1997 clip.fBottom = this->height();
1998 }
1999 #else
2000 clip.fBottom = this->height();
2001 #endif
2002 clip.fTop = this->sizes().delta();
2003 break;
2004 case RectHeightStyle::kIncludeLineSpacingTop: {
2005 clip.fBottom = this->height();
2006 clip.fTop = this->sizes().delta();
2007 auto verticalShift = this->sizes().rawAscent() - this->sizes().ascent();
2008 if (isFirstLine()) {
2009 clip.fTop += verticalShift;
2010 }
2011 break;
2012 }
2013 case RectHeightStyle::kIncludeLineSpacingMiddle: {
2014 clip.fBottom = this->height();
2015 clip.fTop = this->sizes().delta();
2016 auto verticalShift = this->sizes().rawAscent() - this->sizes().ascent();
2017 clip.offset(0, verticalShift / 2.0);
2018 if (isFirstLine()) {
2019 clip.fTop += verticalShift / 2.0;
2020 }
2021 if (isLastLine()) {
2022 clip.fBottom -= verticalShift / 2.0;
2023 }
2024 break;
2025 }
2026 case RectHeightStyle::kIncludeLineSpacingBottom: {
2027 clip.fBottom = this->height();
2028 clip.fTop = this->sizes().delta();
2029 auto verticalShift = this->sizes().rawAscent() - this->sizes().ascent();
2030 clip.offset(0, verticalShift);
2031 if (isLastLine()) {
2032 clip.fBottom -= verticalShift;
2033 }
2034 break;
2035 }
2036 case RectHeightStyle::kStrut: {
2037 const auto& strutStyle = paragraphStyle.getStrutStyle();
2038 if (strutStyle.getStrutEnabled()
2039 && strutStyle.getFontSize() > 0) {
2040 auto strutMetrics = fOwner->strutMetrics();
2041 auto top = this->baseline();
2042 clip.fTop = top + strutMetrics.ascent();
2043 clip.fBottom = top + strutMetrics.descent();
2044 }
2045 }
2046 break;
2047 case RectHeightStyle::kTight: {
2048 if (run->fHeightMultiplier <= 0) {
2049 break;
2050 }
2051 const auto effectiveBaseline = this->baseline() + this->sizes().delta();
2052 clip.fTop = effectiveBaseline + run->ascent();
2053 clip.fBottom = effectiveBaseline + run->descent();
2054 }
2055 break;
2056 default:
2057 SkASSERT(false);
2058 break;
2059 }
2060
2061 // Separate trailing spaces and move them in the default order of the paragraph
2062 // in case the run order and the paragraph order don't match
2063 SkRect trailingSpaces = SkRect::MakeEmpty();
2064 if (this->trimmedText().end <this->textWithNewlines().end && // Line has trailing space
2065 this->textWithNewlines().end == intersect.end && // Range is at the end of the line
2066 this->trimmedText().end > intersect.start) // Range has more than just spaces
2067 {
2068 auto delta = this->spacesWidth();
2069 trailingSpaces = SkRect::MakeXYWH(0, 0, 0, 0);
2070 // There are trailing spaces in this run
2071 if (paragraphStyle.getTextAlign() == TextAlign::kJustify && isLastLine())
2072 {
2073 // TODO: this is just a patch. Make it right later (when it's clear what and how)
2074 trailingSpaces = clip;
2075 if(run->leftToRight()) {
2076 trailingSpaces.fLeft = this->width();
2077 clip.fRight = this->width();
2078 } else {
2079 trailingSpaces.fRight = 0;
2080 clip.fLeft = 0;
2081 }
2082 } else if (paragraphStyle.getTextDirection() == TextDirection::kRtl &&
2083 !run->leftToRight())
2084 {
2085 // Split
2086 trailingSpaces = clip;
2087 trailingSpaces.fLeft = - delta;
2088 trailingSpaces.fRight = 0;
2089 clip.fLeft += delta;
2090 } else if (paragraphStyle.getTextDirection() == TextDirection::kLtr &&
2091 run->leftToRight())
2092 {
2093 // Split
2094 trailingSpaces = clip;
2095 trailingSpaces.fLeft = this->width();
2096 trailingSpaces.fRight = trailingSpaces.fLeft + delta;
2097 clip.fRight -= delta;
2098 }
2099 }
2100
2101 clip.offset(this->offset());
2102 if (trailingSpaces.width() > 0) {
2103 trailingSpaces.offset(this->offset());
2104 }
2105
2106 // Check if we can merge two boxes instead of adding a new one
2107 auto merge = [&lastRun, &context, &boxes](SkRect clip) {
2108 bool mergedBoxes = false;
2109 if (!boxes.empty() &&
2110 lastRun != nullptr &&
2111 context.run->leftToRight() == lastRun->leftToRight() &&
2112 lastRun->placeholderStyle() == nullptr &&
2113 context.run->placeholderStyle() == nullptr &&
2114 nearlyEqual(lastRun->heightMultiplier(),
2115 context.run->heightMultiplier()) &&
2116 #ifndef USE_SKIA_TXT
2117 lastRun->font() == context.run->font())
2118 #else
2119 IsRSFontEquals(lastRun->font(), context.run->font()))
2120 #endif
2121 {
2122 auto& lastBox = boxes.back();
2123 if (nearlyEqual(lastBox.rect.fTop, clip.fTop) &&
2124 nearlyEqual(lastBox.rect.fBottom, clip.fBottom) &&
2125 (nearlyEqual(lastBox.rect.fLeft, clip.fRight) ||
2126 nearlyEqual(lastBox.rect.fRight, clip.fLeft)))
2127 {
2128 lastBox.rect.fLeft = std::min(lastBox.rect.fLeft, clip.fLeft);
2129 lastBox.rect.fRight = std::max(lastBox.rect.fRight, clip.fRight);
2130 mergedBoxes = true;
2131 }
2132 }
2133 lastRun = context.run;
2134 return mergedBoxes;
2135 };
2136
2137 if (!merge(clip)) {
2138 boxes.emplace_back(clip, context.run->getTextDirection());
2139 }
2140 if (!nearlyZero(trailingSpaces.width()) && !merge(trailingSpaces)) {
2141 boxes.emplace_back(trailingSpaces, paragraphStyle.getTextDirection());
2142 }
2143
2144 if (rectWidthStyle == RectWidthStyle::kMax && !isLastLine()) {
2145 // Align the very left/right box horizontally
2146 auto lineStart = this->offset().fX;
2147 auto lineEnd = this->offset().fX + this->width();
2148 auto left = boxes[startBox];
2149 auto right = boxes.back();
2150 if (left.rect.fLeft > lineStart && left.direction == TextDirection::kRtl) {
2151 left.rect.fRight = left.rect.fLeft;
2152 left.rect.fLeft = 0;
2153 boxes.insert(boxes.begin() + startBox + 1, left);
2154 }
2155 if (right.direction == TextDirection::kLtr &&
2156 right.rect.fRight >= lineEnd &&
2157 right.rect.fRight < fOwner->widthWithTrailingSpaces()) {
2158 right.rect.fLeft = right.rect.fRight;
2159 right.rect.fRight = fOwner->widthWithTrailingSpaces();
2160 boxes.emplace_back(right);
2161 }
2162 }
2163
2164 return true;
2165 });
2166 return true;
2167 });
2168 if (fOwner->getApplyRoundingHack()) {
2169 for (auto& r : boxes) {
2170 r.rect.fLeft = littleRound(r.rect.fLeft);
2171 r.rect.fRight = littleRound(r.rect.fRight);
2172 r.rect.fTop = littleRound(r.rect.fTop);
2173 r.rect.fBottom = littleRound(r.rect.fBottom);
2174 }
2175 }
2176 }
2177
2178 #ifdef OHOS_SUPPORT
2179 void TextLine::extendCoordinateRange(PositionWithAffinity& positionWithAffinity) {
2180 if (fEllipsis == nullptr) {
2181 return;
2182 }
2183 // Extending coordinate index if the ellipsis's run is selected.
2184 EllipsisModal ellipsisModal = fOwner->paragraphStyle().getEllipsisMod();
2185 if (ellipsisModal == EllipsisModal::TAIL) {
2186 if (static_cast<size_t>(positionWithAffinity.position) > fOwner->getEllipsisTextRange().start &&
2187 static_cast<size_t>(positionWithAffinity.position) <= fOwner->getEllipsisTextRange().end) {
2188 positionWithAffinity.position = static_cast<int32_t>(fOwner->getEllipsisTextRange().end);
2189 }
2190 } else if (ellipsisModal == EllipsisModal::HEAD) {
2191 if (static_cast<size_t>(positionWithAffinity.position) >= fOwner->getEllipsisTextRange().start &&
2192 static_cast<size_t>(positionWithAffinity.position) < fOwner->getEllipsisTextRange().end) {
2193 positionWithAffinity.position = static_cast<int32_t>(fOwner->getEllipsisTextRange().start);
2194 }
2195 }
2196 }
2197 #endif
2198
2199 PositionWithAffinity TextLine::getGlyphPositionAtCoordinate(SkScalar dx) {
2200
2201 if (SkScalarNearlyZero(this->width()) && SkScalarNearlyZero(this->spacesWidth())) {
2202 // TODO: this is one of the flutter changes that have to go away eventually
2203 // Empty line is a special case in txtlib (but only when there are no spaces, too)
2204 auto utf16Index = fOwner->getUTF16Index(this->fTextExcludingSpaces.end);
2205 return { SkToS32(utf16Index) , kDownstream };
2206 }
2207
2208 PositionWithAffinity result(0, Affinity::kDownstream);
2209 #ifdef OHOS_SUPPORT
2210 this->iterateThroughVisualRuns(EllipsisReadStrategy::READ_REPLACED_WORD, true,
2211 #else
2212 this->iterateThroughVisualRuns(true,
2213 #endif
2214 [this, dx, &result]
2215 (const Run* run, SkScalar runOffsetInLine, TextRange textRange, SkScalar* runWidthInLine) {
2216 bool keepLooking = true;
2217 if (fHyphenRun.get() == run) {
2218 return keepLooking;
2219 }
2220 *runWidthInLine = this->iterateThroughSingleRunByStyles(
2221 TextAdjustment::GraphemeGluster, run, runOffsetInLine, textRange, StyleType::kNone,
2222 [this, run, dx, &result, &keepLooking]
2223 (TextRange textRange, const TextStyle& style, const TextLine::ClipContext& context0) {
2224
2225 SkScalar offsetX = this->offset().fX;
2226 ClipContext context = context0;
2227
2228 // Correct the clip size because libtxt counts trailing spaces
2229 if (run->leftToRight()) {
2230 context.clip.fRight += context.fExcludedTrailingSpaces; // extending clip to the right
2231 } else {
2232 // Clip starts from 0; we cannot extend it to the left from that
2233 }
2234 // However, we need to offset the clip
2235 context.clip.offset(offsetX, 0.0f);
2236
2237 // This patch will help us to avoid a floating point error
2238 if (SkScalarNearlyEqual(context.clip.fRight, dx, 0.01f)) {
2239 context.clip.fRight = dx;
2240 }
2241
2242 if (dx <= context.clip.fLeft) {
2243 // All the other runs are placed right of this one
2244 auto utf16Index = fOwner->getUTF16Index(context.run->globalClusterIndex(context.pos));
2245 if (run->leftToRight()) {
2246 result = { SkToS32(utf16Index), kDownstream };
2247 keepLooking = false;
2248 } else {
2249 #ifdef OHOS_SUPPORT
2250 result = { SkToS32(utf16Index + 1), kUpstream };
2251 size_t glyphCnt = context.run->glyphs().size();
2252 if ((glyphCnt != 0) && ((context.run->fUtf8Range.size() / glyphCnt) == EMOJI_WIDTH)) {
2253 result = { SkToS32(utf16Index + 2), kUpstream };
2254 }
2255 #else
2256 result = { SkToS32(utf16Index + 1), kUpstream};
2257 #endif
2258 // If we haven't reached the end of the run we need to keep looking
2259 keepLooking = context.pos != 0;
2260 }
2261 // For RTL we go another way
2262 return !run->leftToRight();
2263 }
2264
2265 if (dx >= context.clip.fRight) {
2266 // We have to keep looking ; just in case keep the last one as the closest
2267 #ifdef OHOS_SUPPORT
2268 auto utf16Index = fOwner->getUTF16IndexWithOverflowCheck(context.run->globalClusterIndex(context.pos + context.size));
2269 #else
2270 auto utf16Index = fOwner->getUTF16Index(context.run->globalClusterIndex(context.pos + context.size));
2271 #endif
2272 if (run->leftToRight()) {
2273 result = {SkToS32(utf16Index), kUpstream};
2274 } else {
2275 result = {SkToS32(utf16Index), kDownstream};
2276 }
2277 // For RTL we go another way
2278 return run->leftToRight();
2279 }
2280
2281 // So we found the run that contains our coordinates
2282 // Find the glyph position in the run that is the closest left of our point
2283 // TODO: binary search
2284 size_t found = context.pos;
2285 for (size_t index = context.pos; index < context.pos + context.size; ++index) {
2286 // TODO: this rounding is done to match Flutter tests. Must be removed..
2287 auto end = context.run->positionX(index) + context.fTextShift + offsetX;
2288 if (fOwner->getApplyRoundingHack()) {
2289 end = littleRound(end);
2290 }
2291 if (end > dx) {
2292 break;
2293 } else if (end == dx && !context.run->leftToRight()) {
2294 // When we move RTL variable end points to the beginning of the code point which is included
2295 found = index;
2296 break;
2297 }
2298 found = index;
2299 }
2300
2301 SkScalar glyphemePosLeft = context.run->positionX(found) + context.fTextShift + offsetX;
2302 SkScalar glyphemesWidth = context.run->positionX(found + 1) - context.run->positionX(found);
2303
2304 // Find the grapheme range that contains the point
2305 auto clusterIndex8 = context.run->globalClusterIndex(found);
2306 auto clusterEnd8 = context.run->globalClusterIndex(found + 1);
2307 auto graphemes = fOwner->countSurroundingGraphemes({clusterIndex8, clusterEnd8});
2308
2309 SkScalar center = (context.clip.right() + context.clip.left()) / 2;
2310 if (graphemes.size() > 1) {
2311 // Calculate the position proportionally based on grapheme count
2312 SkScalar averageGraphemeWidth = glyphemesWidth / graphemes.size();
2313 SkScalar delta = dx - glyphemePosLeft;
2314 int graphemeIndex = SkScalarNearlyZero(averageGraphemeWidth)
2315 ? 0
2316 : SkScalarFloorToInt(delta / averageGraphemeWidth);
2317 auto graphemeCenter = glyphemePosLeft + graphemeIndex * averageGraphemeWidth +
2318 averageGraphemeWidth * fOwner->getTextSplitRatio();
2319 auto graphemeUtf8Index = graphemes[graphemeIndex];
2320 if ((dx < graphemeCenter) == context.run->leftToRight()) {
2321 size_t utf16Index = fOwner->getUTF16Index(graphemeUtf8Index);
2322 result = { SkToS32(utf16Index), kDownstream };
2323 } else {
2324 size_t utf16Index = fOwner->getUTF16Index(graphemeUtf8Index + 1);
2325 result = { SkToS32(utf16Index), kUpstream };
2326 }
2327 // Keep UTF16 index as is
2328 } else if ((dx < center) == context.run->leftToRight()) {
2329 #ifdef OHOS_SUPPORT
2330 size_t utf16Index = fOwner->getUTF16IndexWithOverflowCheck(clusterIndex8);
2331 #else
2332 size_t utf16Index = fOwner->getUTF16Index(clusterIndex8);
2333 #endif
2334 result = { SkToS32(utf16Index), kDownstream };
2335 } else {
2336 #ifdef OHOS_SUPPORT
2337 size_t utf16Index = 0;
2338 size_t glyphCnt = context.run->glyphs().size();
2339 if ((glyphCnt != 0) && !context.run->leftToRight() && ((
2340 context.run->fUtf8Range.size() / glyphCnt) == EMOJI_WIDTH)) {
2341 utf16Index = fOwner->getUTF16Index(clusterIndex8) + 2;
2342 } else if (!context.run->leftToRight()) {
2343 utf16Index = fOwner->getUTF16Index(clusterIndex8) + 1;
2344 } else {
2345 utf16Index = fOwner->getUTF16IndexWithOverflowCheck(clusterEnd8);
2346 }
2347 #else
2348 size_t utf16Index = context.run->leftToRight()
2349 ? fOwner->getUTF16Index(clusterEnd8)
2350 : fOwner->getUTF16Index(clusterIndex8) + 1;
2351 #endif
2352 result = { SkToS32(utf16Index), kUpstream };
2353 }
2354
2355 return keepLooking = false;
2356
2357 });
2358 return keepLooking;
2359 }
2360 );
2361
2362 #ifdef OHOS_SUPPORT
2363 extendCoordinateRange(result);
2364 #endif
2365
2366 return result;
2367 }
2368
2369 void TextLine::getRectsForPlaceholders(std::vector<TextBox>& boxes) {
2370 #ifdef OHOS_SUPPORT
2371 this->iterateThroughVisualRuns(EllipsisReadStrategy::READ_REPLACED_WORD, true,
2372 #else
2373 this->iterateThroughVisualRuns(true,
2374 #endif
2375 [&boxes, this](const Run* run, SkScalar runOffset, TextRange textRange,
2376 SkScalar* width) {
2377 auto context = this->measureTextInsideOneRun(
2378 textRange, run, runOffset, 0, true, TextAdjustment::GraphemeGluster);
2379 *width = context.clip.width();
2380
2381 if (textRange.width() == 0) {
2382 return true;
2383 }
2384 if (!run->isPlaceholder()) {
2385 return true;
2386 }
2387
2388 SkRect clip = context.clip;
2389 clip.offset(this->offset());
2390
2391 if (fOwner->getApplyRoundingHack()) {
2392 clip.fLeft = littleRound(clip.fLeft);
2393 clip.fRight = littleRound(clip.fRight);
2394 clip.fTop = littleRound(clip.fTop);
2395 clip.fBottom = littleRound(clip.fBottom);
2396 }
2397 boxes.emplace_back(clip, run->getTextDirection());
2398 return true;
2399 });
2400 }
2401
2402 size_t TextLine::getGlyphCount() const
2403 {
2404 size_t glyphCount = 0;
2405 for (auto& blob: fTextBlobCache) {
2406 glyphCount += blob.fVisitor_Size;
2407 }
2408 return glyphCount;
2409 }
2410
2411 #ifdef OHOS_SUPPORT
2412 std::vector<std::unique_ptr<RunBase>> TextLine::getGlyphRuns() const
2413 {
2414 std::vector<std::unique_ptr<RunBase>> runBases;
2415 size_t num = 0;
2416 // Gets the offset position of the current line across the paragraph
2417 size_t pos = fClusterRange.start;
2418 size_t trailSpaces = 0;
2419 for (auto& blob: fTextBlobCache) {
2420 ++num;
2421 if (blob.fVisitor_Size == 0) {
2422 continue;
2423 }
2424 if (num == fTextBlobCache.size()) {
2425 // Counts how many tabs have been removed from the end of the current line
2426 trailSpaces = fGhostClusterRange.width() - fClusterRange.width();
2427 }
2428 std::unique_ptr<RunBaseImpl> runBaseImplPtr = std::make_unique<RunBaseImpl>(
2429 blob.fBlob, blob.fOffset, blob.fPaint, blob.fClippingNeeded, blob.fClipRect,
2430 blob.fVisitor_Run, blob.fVisitor_Pos, pos, trailSpaces, blob.fVisitor_Size);
2431
2432 // Calculate the position of each blob, relative to the entire paragraph
2433 pos += blob.fVisitor_Size;
2434 runBases.emplace_back(std::move(runBaseImplPtr));
2435 }
2436 return runBases;
2437 }
2438 #else
2439 std::vector<std::unique_ptr<RunBase>> TextLine::getGlyphRuns() const
2440 {
2441 std::vector<std::unique_ptr<RunBase>> runBases;
2442 for (auto& blob: fTextBlobCache) {
2443 std::unique_ptr<RunBaseImpl> runBaseImplPtr = std::make_unique<RunBaseImpl>(
2444 blob.fBlob, blob.fOffset, blob.fPaint, blob.fClippingNeeded, blob.fClipRect,
2445 blob.fVisitor_Run, blob.fVisitor_Pos, blob.fVisitor_Size);
2446 runBases.emplace_back(std::move(runBaseImplPtr));
2447 }
2448 return runBases;
2449 }
2450 #endif
2451
2452 #ifdef OHOS_SUPPORT
2453 int getEndWhitespaceCount(const ClusterRange& range, ParagraphImpl* owner)
2454 {
2455 if (owner == nullptr) {
2456 return 0;
2457 }
2458
2459 int endWhitespaceCount = 0;
2460 for (auto clusterIndex = range.end - 1; clusterIndex >= range.start; clusterIndex--) {
2461 if (!owner->cluster(clusterIndex).isWhitespaceBreak()) {
2462 break;
2463 }
2464
2465 endWhitespaceCount++;
2466 if (clusterIndex == range.start) {
2467 break;
2468 }
2469 }
2470
2471 return endWhitespaceCount;
2472 }
2473
2474 std::unique_ptr<TextLineBase> TextLine::createTruncatedLine(double width, EllipsisModal ellipsisMode,
2475 const std::string& ellipsisStr)
2476 {
2477 if (width > 0 && (ellipsisMode == EllipsisModal::HEAD || ellipsisMode == EllipsisModal::TAIL)) {
2478 TextLine textLine = CloneSelf();
2479 SkScalar widthVal = static_cast<SkScalar>(width);
2480 if (widthVal < widthWithEllipsisSpaces() && !ellipsisStr.empty()) {
2481 if (ellipsisMode == EllipsisModal::HEAD) {
2482 textLine.fIsTextLineEllipsisHeadModal = true;
2483 textLine.setTextBlobCachePopulated(false);
2484 textLine.createHeadEllipsis(widthVal, SkString(ellipsisStr), true);
2485 } else if (ellipsisMode == EllipsisModal::TAIL) {
2486 textLine.fIsTextLineEllipsisHeadModal = false;
2487 textLine.setTextBlobCachePopulated(false);
2488 int endWhitespaceCount = getEndWhitespaceCount(fGhostClusterRange, fOwner);
2489 textLine.fGhostClusterRange.end -= static_cast<size_t>(endWhitespaceCount);
2490 textLine.createTailEllipsis(widthVal, SkString(ellipsisStr), true, fOwner->getWordBreakType());
2491 }
2492 }
2493 return std::make_unique<TextLineBaseImpl>(std::make_unique<TextLine>(std::move(textLine)));
2494 }
2495
2496 return nullptr;
2497 }
2498
2499 double TextLine::getTypographicBounds(double* ascent, double* descent, double* leading) const
2500 {
2501 if (ascent == nullptr || descent == nullptr || leading == nullptr) {
2502 return 0.0;
2503 }
2504
2505 *ascent = fMaxRunMetrics.ascent();
2506 *descent = fMaxRunMetrics.descent();
2507 *leading = fMaxRunMetrics.leading();
2508 return widthWithEllipsisSpaces();
2509 }
2510
2511 size_t getPrevGlyphsIndex(const ClusterRange& range, ParagraphImpl* owner, RunIndex& prevRunIndex)
2512 {
2513 if (owner == nullptr) {
2514 return 0;
2515 }
2516
2517 size_t glyphsIndex = 0;
2518 auto clusterIndex = range.start - 1;
2519 prevRunIndex = owner->cluster(clusterIndex).runIndex();
2520 if (prevRunIndex != owner->cluster(range.start).runIndex()) {
2521 // Belongs to a different run.
2522 return 0;
2523 }
2524
2525 for (; clusterIndex >= 0; clusterIndex--) {
2526 RunIndex runIndex = owner->cluster(clusterIndex).runIndex();
2527 if (prevRunIndex != runIndex) {
2528 // Found a different run.
2529 break;
2530 }
2531
2532 glyphsIndex++;
2533
2534 if (clusterIndex == 0) {
2535 // All belong to the first run.
2536 break;
2537 }
2538 }
2539
2540 return glyphsIndex;
2541 }
2542
2543 #ifndef USE_SKIA_TXT
2544 std::vector<SkRect> getAllRectInfo(const ClusterRange& range, ParagraphImpl* owner)
2545 {
2546 std::vector<SkRect> rectVec;
2547 #else
2548 std::vector<RSRect> getAllRectInfo(const ClusterRange& range, ParagraphImpl* owner)
2549 {
2550 std::vector<RSRect> rectVec;
2551 #endif
2552 if (owner == nullptr) {
2553 return rectVec;
2554 }
2555
2556 // If it is not the first line, you need to get the GlyphsIndex of the first character.
2557 size_t glyphsIndex = 0;
2558 RunIndex prevRunIndex = 0;
2559 if (range.start > 0) {
2560 glyphsIndex = getPrevGlyphsIndex(range, owner, prevRunIndex);
2561 }
2562
2563 for (auto clusterIndex = range.start; clusterIndex < range.end; clusterIndex++) {
2564 RunIndex runIndex = owner->cluster(clusterIndex).runIndex();
2565 if (prevRunIndex != runIndex) {
2566 glyphsIndex = 0;
2567 }
2568
2569 auto run = owner->cluster(clusterIndex).runOrNull();
2570 if (run == nullptr) {
2571 break;
2572 }
2573
2574 SkGlyphID glyphId = run->glyphs()[glyphsIndex];
2575 #ifndef USE_SKIA_TXT
2576 SkRect glyphBounds;
2577 run->font().getBounds(&glyphId, 1, &glyphBounds, nullptr);
2578 #else
2579 RSRect glyphBounds;
2580 run->font().GetWidths(&glyphId, 1, nullptr, &glyphBounds);
2581 #endif
2582 rectVec.push_back(glyphBounds);
2583 glyphsIndex++;
2584 prevRunIndex = runIndex;
2585 }
2586
2587 return rectVec;
2588 }
2589
2590 RSRect TextLine::getImageBounds() const
2591 {
2592 // Look for the first non-space character from the end and get its advance and index
2593 // to calculate the final image bounds.
2594 SkRect rect = {0.0, 0.0, 0.0, 0.0};
2595 int endWhitespaceCount = getEndWhitespaceCount(fGhostClusterRange, fOwner);
2596 size_t endWhitespaceCountVal = static_cast<size_t>(endWhitespaceCount);
2597 if (endWhitespaceCountVal == (fGhostClusterRange.end - fGhostClusterRange.start)) {
2598 // Full of Spaces.
2599 return {};
2600 }
2601 SkScalar endAdvance = fOwner->cluster(fGhostClusterRange.end - endWhitespaceCountVal - 1).width();
2602
2603 // The first space width of the line needs to be added to the x value.
2604 SkScalar startWhitespaceAdvance = 0.0;
2605 size_t startWhitespaceCount = 0;
2606 for (auto clusterIndex = fGhostClusterRange.start; clusterIndex < fGhostClusterRange.end; clusterIndex++) {
2607 if (fOwner->cluster(clusterIndex).isWhitespaceBreak()) {
2608 startWhitespaceAdvance += fOwner->cluster(clusterIndex).width();
2609 startWhitespaceCount++;
2610 } else {
2611 break;
2612 }
2613 }
2614
2615 // Gets rect information for all characters in line.
2616 auto rectVec = getAllRectInfo(fGhostClusterRange, fOwner);
2617 // Calculate the final y and height.
2618 auto joinRect = rectVec[startWhitespaceCount];
2619 for (size_t i = startWhitespaceCount + 1; i < rectVec.size() - endWhitespaceCountVal; ++i) {
2620 joinRect.Join(rectVec[i]);
2621 }
2622
2623 SkScalar lineWidth = width();
2624 auto endRect = rectVec[rectVec.size() - endWhitespaceCountVal - 1];
2625 #ifndef USE_SKIA_TXT
2626 SkScalar x = rectVec[startWhitespaceCount].x() + startWhitespaceAdvance;
2627 SkScalar y = joinRect.bottom();
2628 SkScalar width = lineWidth - (endAdvance - endRect.x() - endRect.width()) - x;
2629 SkScalar height = joinRect.height();
2630 #else
2631 SkScalar x = rectVec[startWhitespaceCount].GetLeft() + startWhitespaceAdvance;
2632 SkScalar y = joinRect.GetBottom();
2633 SkScalar width = lineWidth - (endAdvance - endRect.GetLeft() - endRect.GetWidth()) - x;
2634 SkScalar height = joinRect.GetHeight();
2635 #endif
2636
2637 rect.setXYWH(x, y, width, height);
2638 return {rect.fLeft, rect.fTop, rect.fRight, rect.fBottom};
2639 }
2640
2641 double TextLine::getTrailingSpaceWidth() const
2642 {
2643 return spacesWidth();
2644 }
2645
2646 int32_t TextLine::getStringIndexForPosition(SkPoint point) const
2647 {
2648 int32_t index = fGhostClusterRange.start;
2649 double offset = point.x();
2650 if (offset >= widthWithEllipsisSpaces()) {
2651 index = fGhostClusterRange.end;
2652 } else if (offset > 0) {
2653 double curOffset = 0.0;
2654 for (auto clusterIndex = fGhostClusterRange.start; clusterIndex < fGhostClusterRange.end; ++clusterIndex) {
2655 double characterWidth = fOwner->cluster(clusterIndex).width();
2656 if (offset <= curOffset + characterWidth / 2) {
2657 return index;
2658 }
2659 index++;
2660 curOffset += characterWidth;
2661 }
2662 }
2663
2664 return index;
2665 }
2666
2667 double TextLine::getOffsetForStringIndex(int32_t index) const
2668 {
2669 double offset = 0.0;
2670 if (index <= 0) {
2671 return offset;
2672 }
2673
2674 size_t indexVal = static_cast<size_t>(index);
2675 if (indexVal >= fGhostClusterRange.end) {
2676 offset = widthWithEllipsisSpaces();
2677 } else if (indexVal > fGhostClusterRange.start) {
2678 size_t clusterIndex = fGhostClusterRange.start;
2679 while (clusterIndex < fGhostClusterRange.end) {
2680 offset += fOwner->cluster(clusterIndex).width();
2681 if (++clusterIndex == indexVal) {
2682 break;
2683 }
2684 }
2685 }
2686
2687 return offset;
2688 }
2689
2690 std::map<int32_t, double> TextLine::getIndexAndOffsets(bool& isHardBreak) const
2691 {
2692 std::map<int32_t, double> offsetMap;
2693 double offset = 0.0;
2694 for (auto clusterIndex = fGhostClusterRange.start; clusterIndex < fGhostClusterRange.end; ++clusterIndex) {
2695 auto& cluster = fOwner->cluster(clusterIndex);
2696 offset += cluster.width();
2697 isHardBreak = cluster.isHardBreak();
2698 if (!isHardBreak) {
2699 offsetMap[clusterIndex] = offset;
2700 }
2701 }
2702 return offsetMap;
2703 }
2704
2705 double TextLine::getAlignmentOffset(double alignmentFactor, double alignmentWidth) const
2706 {
2707 double lineWidth = width();
2708 if (alignmentWidth <= lineWidth) {
2709 return 0.0;
2710 }
2711
2712 double offset = 0.0;
2713 TextDirection textDirection = fOwner->paragraphStyle().getTextDirection();
2714 if (alignmentFactor <= 0) {
2715 // Flush left.
2716 if (textDirection == TextDirection::kRtl) {
2717 offset = lineWidth - alignmentWidth;
2718 }
2719 } else if (alignmentFactor < 1) {
2720 // Align according to the alignmentFactor.
2721 if (textDirection == TextDirection::kLtr) {
2722 offset = (alignmentWidth - lineWidth) * alignmentFactor;
2723 } else {
2724 offset = (lineWidth - alignmentWidth) * (1 - alignmentFactor);
2725 }
2726 } else {
2727 // Flush right.
2728 if (textDirection == TextDirection::kLtr) {
2729 offset = alignmentWidth - lineWidth;
2730 }
2731 }
2732
2733 return offset;
2734 }
2735
2736 SkRect TextLine::computeShadowRect(SkScalar x, SkScalar y, const TextStyle& style, const ClipContext& context) const
2737 {
2738 SkScalar offsetX = x + this->fOffset.fX;
2739 SkScalar offsetY = y + this->fOffset.fY -
2740 (context.run ? context.run->fCompressionBaselineShift : 0);
2741 SkRect shadowRect = SkRect::MakeEmpty();
2742
2743 for (const TextShadow& shadow : style.getShadows()) {
2744 if (!shadow.hasShadow()) {
2745 continue;
2746 }
2747
2748 SkScalar blurSigma = SkDoubleToScalar(shadow.fBlurSigma);
2749 SkRect rect = context.clip
2750 .makeOffset(offsetX + shadow.fOffset.fX, offsetY + shadow.fOffset.fY)
2751 .makeOutset(blurSigma, blurSigma);
2752 shadowRect.join(rect);
2753 }
2754 return shadowRect;
2755 }
2756
2757 SkRect TextLine::getAllShadowsRect(SkScalar x, SkScalar y) const
2758 {
2759 if (!fHasShadows) {
2760 return SkRect::MakeEmpty();
2761 }
2762 SkRect paintRegion = SkRect::MakeEmpty();
2763 this->iterateThroughVisualRuns(EllipsisReadStrategy::READ_REPLACED_WORD, false,
2764 [&paintRegion, x, y, this]
2765 (const Run* run, SkScalar runOffsetInLine, TextRange textRange, SkScalar* runWidthInLine) {
2766 if (runWidthInLine == nullptr) {
2767 return true;
2768 }
2769 *runWidthInLine = this->iterateThroughSingleRunByStyles(
2770 TextAdjustment::GlyphCluster, run, runOffsetInLine, textRange, StyleType::kShadow,
2771 [&paintRegion, x, y, this]
2772 (TextRange textRange, const TextStyle& style, const ClipContext& context) {
2773 SkRect rect = computeShadowRect(x, y, style, context);
2774 paintRegion.join(rect);
2775 });
2776 return true;
2777 });
2778 return paintRegion;
2779 }
2780
2781 SkRect TextLine::generatePaintRegion(SkScalar x, SkScalar y)
2782 {
2783 SkRect paintRegion = SkRect::MakeXYWH(x, y, 0, 0);
2784 fIsArcText = false;
2785
2786 SkRect rect = getAllShadowsRect(x, y);
2787 paintRegion.join(rect);
2788
2789 // textblob
2790 this->ensureTextBlobCachePopulated();
2791 for (auto& record : fTextBlobCache) {
2792 rect = GetTextBlobSkTightBound(record.fBlob, x + record.fOffset.fX, y + record.fOffset.fY, record.fClipRect);
2793 paintRegion.join(rect);
2794 }
2795
2796 return paintRegion;
2797 }
2798 #endif
2799
2800 TextLine TextLine::CloneSelf()
2801 {
2802 TextLine textLine;
2803 textLine.fBlockRange = this->fBlockRange;
2804 textLine.fTextExcludingSpaces = this->fTextExcludingSpaces;
2805 textLine.fText = this->fText;
2806 textLine.fTextIncludingNewlines = this->fTextIncludingNewlines;
2807 textLine.fClusterRange = this->fClusterRange;
2808
2809 textLine.fGhostClusterRange = this->fGhostClusterRange;
2810 textLine.fRunsInVisualOrder = this->fRunsInVisualOrder;
2811 textLine.fAdvance = this->fAdvance;
2812 textLine.fOffset = this->fOffset;
2813 textLine.fShift = this->fShift;
2814
2815 textLine.fWidthWithSpaces = this->fWidthWithSpaces;
2816 if (this->fEllipsis) {
2817 textLine.fEllipsis = std::make_unique<Run>(*this->fEllipsis);
2818 }
2819
2820 textLine.fSizes = this->fSizes;
2821 textLine.fMaxRunMetrics = this->fMaxRunMetrics;
2822 textLine.fHasBackground = this->fHasBackground;
2823 textLine.fHasShadows = this->fHasShadows;
2824 textLine.fHasDecorations = this->fHasDecorations;
2825 textLine.fAscentStyle = this->fAscentStyle;
2826 textLine.fDescentStyle = this->fDescentStyle;
2827 textLine.fTextBlobCachePopulated = this->fTextBlobCachePopulated;
2828 #ifdef OHOS_SUPPORT
2829 textLine.fOwner = this->fOwner;
2830 textLine.fIsTextLineEllipsisHeadModal = this->fIsTextLineEllipsisHeadModal;
2831 textLine.fEllipsisString = this->fEllipsisString;
2832 textLine.fBreakWithHyphen = this->fBreakWithHyphen;
2833 if (this->fHyphenRun) {
2834 textLine.fHyphenRun = std::make_unique<Run>(*this->fHyphenRun);
2835 }
2836 textLine.fHyphenIndex = this->fHyphenIndex;
2837 #endif
2838
2839 textLine.roundRectAttrs = this->roundRectAttrs;
2840 textLine.fTextBlobCache = this->fTextBlobCache;
2841 textLine.fTextRangeReplacedByEllipsis = this->fTextRangeReplacedByEllipsis;
2842 textLine.fEllipsisIndex = this->fEllipsisIndex;
2843 textLine.fLastClipRunLtr = this->fLastClipRunLtr;
2844 return textLine;
2845 }
2846
2847 #ifdef OHOS_SUPPORT
2848 void TextLine::setBreakWithHyphen(bool breakWithHyphen)
2849 {
2850 fBreakWithHyphen = breakWithHyphen;
2851 if (!breakWithHyphen) {
2852 if (fHyphenRun != nullptr) {
2853 fWidthWithSpaces -= fHyphenRun->fAdvance.fX;
2854 }
2855 fHyphenRun.reset();
2856 fHyphenIndex = EMPTY_INDEX;
2857 } else {
2858 auto endIx = fClusterRange.end - 1;
2859 // if we don't have hyphen run, shape it
2860 auto& cluster = fOwner->cluster(endIx);
2861 SkString dash("-");
2862 if (fHyphenRun == nullptr) {
2863 fHyphenRun = shapeString(dash, &cluster);
2864 fHyphenRun->setOwner(fOwner);
2865 }
2866
2867 fHyphenRun->fTextRange = TextRange(cluster.textRange().end, cluster.textRange().end + 1);
2868 fHyphenRun->fClusterStart = cluster.textRange().end;
2869
2870 fAdvance.fX += fHyphenRun->fAdvance.fX;
2871 fWidthWithSpaces = fAdvance.fX;
2872 fGhostClusterRange.end = fClusterRange.end;
2873 fHyphenIndex = cluster.runIndex();
2874 fText.end = cluster.textRange().end;
2875 fTextIncludingNewlines.end = cluster.textRange().end;
2876 fTextExcludingSpaces.end = cluster.textRange().end;
2877 }
2878 }
2879
2880 bool TextLine::getBreakWithHyphen() const
2881 {
2882 return fBreakWithHyphen;
2883 }
2884
2885 void TextLine::updateTextLinePaintAttributes() {
2886 fHasBackground = false;
2887 fHasDecorations = false;
2888 fHasShadows = false;
2889 for (BlockIndex index = fBlockRange.start; index < fBlockRange.end; ++index) {
2890 auto textStyleBlock = fOwner->styles().begin() + index;
2891 if (textStyleBlock->fStyle.hasBackground()) {
2892 fHasBackground = true;
2893 }
2894 if (textStyleBlock->fStyle.getDecorationType() != TextDecoration::kNoDecoration &&
2895 textStyleBlock->fStyle.getDecorationThicknessMultiplier() > 0) {
2896 fHasDecorations = true;
2897 }
2898 if (textStyleBlock->fStyle.getShadowNumber() > 0) {
2899 fHasShadows = true;
2900 }
2901 }
2902 }
2903 #endif
2904
2905 } // namespace textlayout
2906 } // namespace skia
2907