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