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