1 // Copyright 2019 Google LLC.
2
3 #include "include/core/SkCanvas.h"
4 #include "include/core/SkFontMetrics.h"
5 #include "include/core/SkMatrix.h"
6 #include "include/core/SkPictureRecorder.h"
7 #include "include/core/SkSpan.h"
8 #include "include/core/SkTypeface.h"
9 #include "include/private/SkTFitsIn.h"
10 #include "include/private/SkTo.h"
11 #include "modules/skparagraph/include/Metrics.h"
12 #include "modules/skparagraph/include/Paragraph.h"
13 #include "modules/skparagraph/include/ParagraphPainter.h"
14 #include "modules/skparagraph/include/ParagraphStyle.h"
15 #include "modules/skparagraph/include/TextStyle.h"
16 #include "modules/skparagraph/src/OneLineShaper.h"
17 #include "modules/skparagraph/src/ParagraphImpl.h"
18 #include "modules/skparagraph/src/ParagraphPainterImpl.h"
19 #include "modules/skparagraph/src/Run.h"
20 #include "modules/skparagraph/src/TextLine.h"
21 #include "modules/skparagraph/src/TextWrapper.h"
22 #ifdef OHOS_SUPPORT
23 #include "trace.h"
24 #endif
25 #include "src/utils/SkUTF.h"
26 #include <math.h>
27 #include <algorithm>
28 #include <utility>
29
30 #ifdef OHOS_SUPPORT
31 #include <sstream>
32 #include "log.h"
33 #include "include/TextGlobalConfig.h"
34 #include "modules/skparagraph/src/TextLineBaseImpl.h"
35 #include "TextParameter.h"
36 #endif
37
38 namespace skia {
39 namespace textlayout {
40
41 namespace {
42 constexpr ParagraphPainter::PaintID INVALID_PAINT_ID = -1;
43
littleRound(SkScalar a)44 SkScalar littleRound(SkScalar a) {
45 // This rounding is done to match Flutter tests. Must be removed..
46 auto val = std::fabs(a);
47 if (val < 10000) {
48 return SkScalarRoundToScalar(a * 100.0)/100.0;
49 } else if (val < 100000) {
50 return SkScalarRoundToScalar(a * 10.0)/10.0;
51 } else {
52 return SkScalarFloorToScalar(a);
53 }
54 }
55 } // namespace
56
operator *(const TextRange & a,const TextRange & b)57 TextRange operator*(const TextRange& a, const TextRange& b) {
58 if (a.start == b.start && a.end == b.end) return a;
59 auto begin = std::max(a.start, b.start);
60 auto end = std::min(a.end, b.end);
61 return end > begin ? TextRange(begin, end) : EMPTY_TEXT;
62 }
63
textRangeMergeBtoA(const TextRange & a,const TextRange & b)64 TextRange textRangeMergeBtoA(const TextRange& a, const TextRange& b) {
65 if (a.width() <= 0 || b.width() <= 0 || a.end < b.start || a.start > b.end) {
66 return a;
67 }
68
69 return TextRange(std::min(a.start, b.start), std::max(a.end, b.end));
70 }
71
72 #ifdef OHOS_SUPPORT
convertUtf8ToUnicode(const SkString & utf8)73 std::vector<SkUnichar> ParagraphImpl::convertUtf8ToUnicode(const SkString& utf8)
74 {
75 fUnicodeIndexForUTF8Index.reset();
76 std::vector<SkUnichar> result;
77 auto p = utf8.c_str();
78 auto end = p + utf8.size();
79 while (p < end) {
80 auto tmp = p;
81 auto unichar = SkUTF::NextUTF8(&p, end);
82 for (auto i = 0; i < p - tmp; ++i) {
83 fUnicodeIndexForUTF8Index.emplace_back(result.size());
84 }
85 result.emplace_back(unichar);
86 }
87 fUnicodeIndexForUTF8Index.emplace_back(result.size());
88 return result;
89 }
90
needCreateMiddleEllipsis()91 bool ParagraphImpl::needCreateMiddleEllipsis()
92 {
93 if (fParagraphStyle.getMaxLines() == 1 && fParagraphStyle.getEllipsisMod() == EllipsisModal::MIDDLE &&
94 fParagraphStyle.ellipsized()) {
95 return true;
96 }
97 return false;
98 }
99
getPlaceholderByIndex(size_t placeholderIndex)100 Placeholder* ParagraphImpl::getPlaceholderByIndex(size_t placeholderIndex)
101 {
102 if (fPlaceholders.size() <= placeholderIndex) {
103 LOGE("Failed to get placeholder");
104 return nullptr;
105 }
106 return &fPlaceholders[placeholderIndex];
107 }
108
IsPlaceholderAlignedFollowParagraph(size_t placeholderIndex)109 bool ParagraphImpl::IsPlaceholderAlignedFollowParagraph(size_t placeholderIndex)
110 {
111 Placeholder* placeholder = getPlaceholderByIndex(placeholderIndex);
112 if (placeholder == nullptr) {
113 return false;
114 }
115 return placeholder->fStyle.fAlignment == PlaceholderAlignment::kFollow;
116 }
117
setPlaceholderAlignment(size_t placeholderIndex,PlaceholderAlignment alignment)118 bool ParagraphImpl::setPlaceholderAlignment(size_t placeholderIndex, PlaceholderAlignment alignment)
119 {
120 Placeholder* placeholder = getPlaceholderByIndex(placeholderIndex);
121 if (placeholder == nullptr) {
122 return false;
123 }
124 placeholder->fStyle.fAlignment = alignment;
125 return true;
126 }
127
getBlockByRun(const Run & run)128 Block& ParagraphImpl::getBlockByRun(const Run& run)
129 {
130 TextRange textRange = run.textRange();
131 BlockRange blocksRange = findAllBlocks(textRange);
132 return block(blocksRange.start);
133 }
134 #endif
135
Paragraph(ParagraphStyle style,sk_sp<FontCollection> fonts)136 Paragraph::Paragraph(ParagraphStyle style, sk_sp<FontCollection> fonts)
137 : fFontCollection(std::move(fonts))
138 , fParagraphStyle(std::move(style))
139 , fAlphabeticBaseline(0)
140 , fIdeographicBaseline(0)
141 , fHeight(0)
142 , fWidth(0)
143 , fMaxIntrinsicWidth(0)
144 , fMinIntrinsicWidth(0)
145 , fLongestLine(0)
146 #ifdef OHOS_SUPPORT
147 , fLongestLineWithIndent(0)
148 #endif
149 , fExceededMaxLines(0)
150 { }
151
ParagraphImpl(const SkString & text,ParagraphStyle style,SkTArray<Block,true> blocks,SkTArray<Placeholder,true> placeholders,sk_sp<FontCollection> fonts,std::shared_ptr<SkUnicode> unicode)152 ParagraphImpl::ParagraphImpl(const SkString& text,
153 ParagraphStyle style,
154 SkTArray<Block, true> blocks,
155 SkTArray<Placeholder, true> placeholders,
156 sk_sp<FontCollection> fonts,
157 std::shared_ptr<SkUnicode> unicode)
158 : Paragraph(std::move(style), std::move(fonts))
159 , fTextStyles(std::move(blocks))
160 , fPlaceholders(std::move(placeholders))
161 , fText(text)
162 , fState(kUnknown)
163 , fUnresolvedGlyphs(0)
164 , fPicture(nullptr)
165 , fStrutMetrics(false)
166 , fOldWidth(0)
167 , fOldHeight(0)
168 , fUnicode(std::move(unicode))
169 , fHasLineBreaks(false)
170 , fHasWhitespacesInside(false)
171 , fTrailingSpaces(0)
172 {
173 SkASSERT(fUnicode);
174 }
175
ParagraphImpl(const std::u16string & utf16text,ParagraphStyle style,SkTArray<Block,true> blocks,SkTArray<Placeholder,true> placeholders,sk_sp<FontCollection> fonts,std::shared_ptr<SkUnicode> unicode)176 ParagraphImpl::ParagraphImpl(const std::u16string& utf16text,
177 ParagraphStyle style,
178 SkTArray<Block, true> blocks,
179 SkTArray<Placeholder, true> placeholders,
180 sk_sp<FontCollection> fonts,
181 std::shared_ptr<SkUnicode> unicode)
182 : ParagraphImpl(SkString(),
183 std::move(style),
184 std::move(blocks),
185 std::move(placeholders),
186 std::move(fonts),
187 std::move(unicode))
188 {
189 SkASSERT(fUnicode);
190 fText = SkUnicode::convertUtf16ToUtf8(utf16text);
191 }
192
193 ParagraphImpl::~ParagraphImpl() = default;
194
unresolvedGlyphs()195 int32_t ParagraphImpl::unresolvedGlyphs() {
196 if (fState < kShaped) {
197 return -1;
198 }
199
200 return fUnresolvedGlyphs;
201 }
202
203 #ifndef USE_SKIA_TXT
GetLineFontMetrics(const size_t lineNumber,size_t & charNumber,std::vector<SkFontMetrics> & fontMetrics)204 bool ParagraphImpl::GetLineFontMetrics(const size_t lineNumber, size_t& charNumber,
205 std::vector<SkFontMetrics>& fontMetrics) {
206 #else
207 bool ParagraphImpl::GetLineFontMetrics(const size_t lineNumber, size_t& charNumber,
208 std::vector<RSFontMetrics>& fontMetrics) {
209 #endif
210 if (lineNumber > fLines.size() || !lineNumber ||
211 !fLines[lineNumber - 1].getLineAllRuns().size()) {
212 return false;
213 }
214
215 size_t textRange = 0;
216 size_t lineCharCount = fLines[lineNumber - 1].clusters().end -
217 fLines[lineNumber - 1].clusters().start;
218
219 for (auto& runIndex : fLines[lineNumber - 1].getLineAllRuns()) {
220 Run& targetRun = this->run(runIndex);
221 size_t runClock = 0;
222 size_t currentRunCharNumber = targetRun.clusterRange().end -
223 targetRun.clusterRange().start;
224 for (;textRange < lineCharCount; textRange++) {
225 if (++runClock > currentRunCharNumber) {
226 break;
227 }
228 #ifndef USE_SKIA_TXT
229 SkFontMetrics newFontMetrics;
230 targetRun.fFont.getMetrics(&newFontMetrics);
231 #else
232 RSFontMetrics newFontMetrics;
233 targetRun.fFont.GetMetrics(&newFontMetrics);
234 #endif
235 #ifdef OHOS_SUPPORT
236 auto decompressFont = targetRun.fFont;
237 scaleFontWithCompressionConfig(decompressFont, ScaleOP::DECOMPRESS);
238 metricsIncludeFontPadding(&newFontMetrics, decompressFont);
239 #endif
240 fontMetrics.emplace_back(newFontMetrics);
241 }
242 }
243
244 charNumber = lineCharCount;
245 return true;
246 }
247
248 std::unordered_set<SkUnichar> ParagraphImpl::unresolvedCodepoints() {
249 return fUnresolvedCodepoints;
250 }
251
252 void ParagraphImpl::addUnresolvedCodepoints(TextRange textRange) {
253 fUnicode->forEachCodepoint(
254 &fText[textRange.start], textRange.width(),
255 [&](SkUnichar unichar, int32_t start, int32_t end, int32_t count) {
256 fUnresolvedCodepoints.emplace(unichar);
257 }
258 );
259 }
260
261 TextRange ParagraphImpl::resetRangeWithDeletedRange(const TextRange& sourceRange,
262 const TextRange& deletedRange, const size_t& ellSize)
263 {
264 if (sourceRange.end <= deletedRange.start) {
265 return sourceRange;
266 }
267 auto changeSize = ellSize - deletedRange.width();
268
269 if (sourceRange.start >= deletedRange.end) {
270 return TextRange(sourceRange.start + changeSize, sourceRange.end + changeSize);
271 }
272
273 TextRange target;
274 target.start = sourceRange.start <= deletedRange.start ? sourceRange.start : deletedRange.start + ellSize;
275 target.end = sourceRange.end <= deletedRange.end ? deletedRange.start + ellSize : sourceRange.end + changeSize;
276 return target.start <= target.end ? target : EMPTY_RANGE;
277 }
278
279 void ParagraphImpl::resetTextStyleRange(const TextRange& deletedRange)
280 {
281 auto tmpTextStyle = fTextStyles;
282 fTextStyles.reset();
283 for (auto fs : tmpTextStyle) {
284 auto newTextRange = resetRangeWithDeletedRange(fs.fRange, deletedRange, this->getEllipsis().size());
285 LOGD("ParagraphImpl::resetTextStyleRange old = [%{public}lu,%{public}lu), new = [%{public}lu,%{public}lu)",
286 static_cast<unsigned long>(fs.fRange.start), static_cast<unsigned long>(fs.fRange.end),
287 static_cast<unsigned long>(newTextRange.start), static_cast<unsigned long>(newTextRange.end));
288 if (newTextRange.width() == 0) {
289 continue;
290 }
291 fs.fRange = newTextRange;
292 fTextStyles.emplace_back(fs);
293 }
294 }
295
296 void ParagraphImpl::resetPlaceholderRange(const TextRange& deletedRange)
297 {
298 // reset fRange && fTextBefore && fBlockBefore
299 auto ellSize = this->getEllipsis().size();
300 auto tmpPlaceholders = fPlaceholders;
301 fPlaceholders.reset();
302 for (auto ph : tmpPlaceholders) {
303 auto newTextRange = resetRangeWithDeletedRange(ph.fRange, deletedRange, ellSize);
304 LOGD("ParagraphImpl::resetPlaceholderRange old = [%{public}lu,%{public}lu), new = [%{public}lu,%{public}lu)",
305 static_cast<unsigned long>(ph.fRange.start), static_cast<unsigned long>(ph.fRange.end),
306 static_cast<unsigned long>(newTextRange.start), static_cast<unsigned long>(newTextRange.end));
307 if (newTextRange.empty()) {
308 continue;
309 }
310 ph.fRange = newTextRange;
311 newTextRange = ph.fTextBefore;
312 newTextRange.start = fPlaceholders.empty() ? 0 : fPlaceholders.back().fRange.end;
313 if (newTextRange.end > deletedRange.start) {
314 newTextRange.end = newTextRange.end <= deletedRange.end ?
315 deletedRange.start + ellSize : newTextRange.end + ellSize - deletedRange.width();
316 }
317 ph.fTextBefore = newTextRange;
318 fPlaceholders.emplace_back(ph);
319 }
320 }
321
322 void ParagraphImpl::layout(SkScalar rawWidth) {
323 #ifdef OHOS_SUPPORT
324 TEXT_TRACE_FUNC();
325 #endif
326 fLineNumber = 1;
327 fLayoutRawWidth = rawWidth;
328 // TODO: This rounding is done to match Flutter tests. Must be removed...
329 auto floorWidth = rawWidth;
330
331 if (getApplyRoundingHack()) {
332 floorWidth = SkScalarFloorToScalar(floorWidth);
333 }
334
335 #ifdef OHOS_SUPPORT
336 fPaintRegion.reset();
337 bool isMaxLinesZero = false;
338 if (fParagraphStyle.getMaxLines() == 0) {
339 if (fText.size() != 0) {
340 isMaxLinesZero = true;
341 }
342 }
343 #endif
344
345 if ((!SkScalarIsFinite(rawWidth) || fLongestLine <= floorWidth) &&
346 fState >= kLineBroken &&
347 fLines.size() == 1 && fLines.front().ellipsis() == nullptr) {
348 // Most common case: one line of text (and one line is never justified, so no cluster shifts)
349 // We cannot mark it as kLineBroken because the new width can be bigger than the old width
350 fWidth = floorWidth;
351 fState = kShaped;
352 } else if (fState >= kLineBroken && fOldWidth != floorWidth) {
353 // We can use the results from SkShaper but have to do EVERYTHING ELSE again
354 fState = kShaped;
355 } else {
356 // Nothing changed case: we can reuse the data from the last layout
357 }
358 #ifdef OHOS_SUPPORT
359 this->fUnicodeText = convertUtf8ToUnicode(fText);
360 #endif
361 auto paragraphCache = fFontCollection->getParagraphCache();
362
363 if (fState < kShaped) {
364 // Check if we have the text in the cache and don't need to shape it again
365 if (!paragraphCache->findParagraph(this)) {
366 if (fState < kIndexed) {
367 // This only happens once at the first layout; the text is immutable
368 // and there is no reason to repeat it
369 if (this->computeCodeUnitProperties()) {
370 fState = kIndexed;
371 }
372 }
373 this->fRuns.reset();
374 this->fClusters.reset();
375 this->fClustersIndexFromCodeUnit.reset();
376 this->fClustersIndexFromCodeUnit.push_back_n(fText.size() + 1, EMPTY_INDEX);
377 if (!this->shapeTextIntoEndlessLine()) {
378 this->resetContext();
379
380 #ifdef OHOS_SUPPORT
381 if (isMaxLinesZero) {
382 fExceededMaxLines = true;
383 }
384 #endif
385 // TODO: merge the two next calls - they always come together
386 this->resolveStrut();
387 this->computeEmptyMetrics();
388 this->fLines.reset();
389
390 // Set the important values that are not zero
391 fWidth = floorWidth;
392 fHeight = fEmptyMetrics.height();
393 if (fParagraphStyle.getStrutStyle().getStrutEnabled() &&
394 fParagraphStyle.getStrutStyle().getForceStrutHeight()) {
395 fHeight = fStrutMetrics.height();
396 }
397 if (fParagraphStyle.getMaxLines() == 0) {
398 fHeight = 0;
399 }
400 fAlphabeticBaseline = fEmptyMetrics.alphabeticBaseline();
401 fIdeographicBaseline = fEmptyMetrics.ideographicBaseline();
402 fLongestLine = FLT_MIN - FLT_MAX; // That is what flutter has
403 fMinIntrinsicWidth = 0;
404 fMaxIntrinsicWidth = 0;
405 this->fOldWidth = floorWidth;
406 this->fOldHeight = this->fHeight;
407
408 return;
409 } else {
410 // Add the paragraph to the cache
411 paragraphCache->updateParagraph(this);
412 }
413 }
414 fState = kShaped;
415 }
416
417 if (fState == kShaped) {
418 this->resetContext();
419 this->resolveStrut();
420 this->computeEmptyMetrics();
421 this->fLines.reset();
422 #ifdef OHOS_SUPPORT
423 // fast path
424 if (!fHasLineBreaks &&
425 !fHasWhitespacesInside &&
426 fPlaceholders.size() == 1 &&
427 (fRuns.size() == 1 && preCalculateSingleRunAutoSpaceWidth(floorWidth))) {
428 positionShapedTextIntoLine(floorWidth);
429 } else if (!paragraphCache->GetStoredLayout(*this)) {
430 breakShapedTextIntoLines(floorWidth);
431 // text breaking did not go to fast path and we did not have cached layout
432 paragraphCache->SetStoredLayout(*this);
433 }
434 #else
435 this->breakShapedTextIntoLines(floorWidth);
436 #endif
437 fState = kLineBroken;
438 }
439
440 if (fState == kLineBroken) {
441 #ifdef OHOS_SUPPORT
442 if (paragraphStyle().getVerticalAlignment() != TextVerticalAlign::BASELINE &&
443 paragraphStyle().getMaxLines() > 1) {
444 splitRuns();
445 }
446 #endif
447 // Build the picture lazily not until we actually have to paint (or never)
448 this->resetShifts();
449 this->formatLines(fWidth);
450 fState = kFormatted;
451 }
452
453 if (fParagraphStyle.getMaxLines() == 0) {
454 fHeight = 0;
455 #ifdef OHOS_SUPPORT
456 fLines.reset();
457 #endif
458 }
459
460 this->fOldWidth = floorWidth;
461 this->fOldHeight = this->fHeight;
462
463 if (getApplyRoundingHack()) {
464 // TODO: This rounding is done to match Flutter tests. Must be removed...
465 fMinIntrinsicWidth = littleRound(fMinIntrinsicWidth);
466 fMaxIntrinsicWidth = littleRound(fMaxIntrinsicWidth);
467 }
468
469 // TODO: This is strictly Flutter thing. Must be factored out into some flutter code
470 if (fParagraphStyle.getMaxLines() == 1 ||
471 (fParagraphStyle.unlimited_lines() && fParagraphStyle.ellipsized())) {
472 fMinIntrinsicWidth = fMaxIntrinsicWidth;
473 }
474
475 // TODO: Since min and max are calculated differently it's possible to get a rounding error
476 // that would make min > max. Sort it out later, make it the same for now
477 if (fMaxIntrinsicWidth < fMinIntrinsicWidth) {
478 fMaxIntrinsicWidth = fMinIntrinsicWidth;
479 }
480 if (fParagraphStyle.getMaxLines() == 0) {
481 fLineNumber = 0;
482 } else {
483 fLineNumber = std::max(size_t(1), fLines.size());
484 }
485 //SkDebugf("layout('%s', %f): %f %f\n", fText.c_str(), rawWidth, fMinIntrinsicWidth, fMaxIntrinsicWidth);
486 }
487
488 #ifdef OHOS_SUPPORT
489 void ParagraphImpl::updateSplitRunClusterInfo(const Run& run, bool isSplitRun) {
490 size_t posOffset = run.leftToRight() ? cluster(run.clusterRange().start).startPos() :
491 cluster(run.clusterRange().end - 1).startPos();
492 for (size_t clusterIndex = run.clusterRange().start; clusterIndex < run.clusterRange().end; ++clusterIndex) {
493 Cluster& updateCluster = this->cluster(clusterIndex);
494 updateCluster.fRunIndex = run.index();
495 // If the run has not been split, it only needs to update the run index
496 if (!isSplitRun) {
497 continue;
498 }
499 size_t width = updateCluster.size();
500 updateCluster.fStart -= posOffset;
501 updateCluster.fEnd = updateCluster.fStart + width;
502 }
503 }
504
505 void ParagraphImpl::refreshLines() {
506 for (TextLine& line : fLines) {
507 line.refresh();
508 }
509 }
510
511 bool ParagraphImpl::isTailOfLineNeedSplit(const Run& lineLastRun, size_t lineEnd, bool hasGenerated) {
512 return !hasGenerated && ((lineLastRun.clusterRange().end != lineEnd) ||
513 // Special case: the line of last run combines a hard break, such as "<\n"
514 (cluster(lineEnd - 1).isHardBreak() &&
515 !cluster(runByCluster(lineEnd - 1).clusterRange().start).isHardBreak()));
516 }
517
518 void ParagraphImpl::generateSplitPoint(
519 std::vector<SplitPoint>& splitPoints, const Run& run, ClusterRange lineRange, size_t lineIndex) {
520 size_t startIndex =
521 std::max(cluster(lineRange.start).textRange().start, cluster(run.clusterRange().start).textRange().start);
522 size_t endIndex =
523 std::min(cluster(lineRange.end - 1).textRange().end, cluster(run.clusterRange().end- 1).textRange().end);
524 // The run cross line
525 splitPoints.push_back({lineIndex, run.index(), startIndex, endIndex});
526 }
527
528 void ParagraphImpl::generateSplitPoints(std::vector<SplitPoint>& splitPoints) {
529 for (size_t lineIndex = 0; lineIndex < fLines.size(); ++lineIndex) {
530 const TextLine& line = fLines[lineIndex];
531 ClusterRange lineClusterRange = line.clustersWithSpaces();
532 // Avoid abnormal split of the last line
533 if (lineIndex == fLines.size() - 1) {
534 size_t lineEnd = line.clusters().end == 0 ? 0 : line.clusters().end - 1;
535 lineClusterRange.end = cluster(lineEnd).run().clusterRange().end;
536 }
537 // Skip blank line
538 if (lineClusterRange.empty()) {
539 continue;
540 }
541 size_t lineStart = lineClusterRange.start;
542 size_t lineEnd = lineClusterRange.end;
543 // The next line's starting cluster index
544 const Run& lineFirstRun = runByCluster(lineStart);
545 const Run& lineLastRun = runByCluster(lineEnd - 1);
546 // Each line may have 0, 1, or 2 Runs need to be split
547 bool onlyGenerateOnce{false};
548 if (lineFirstRun.clusterRange().start != lineStart) {
549 generateSplitPoint(splitPoints, lineFirstRun, lineClusterRange, lineIndex);
550
551 if (lineFirstRun.index() == lineLastRun.index()) {
552 onlyGenerateOnce = true;
553 }
554 }
555
556 if (isTailOfLineNeedSplit(lineLastRun, lineEnd, onlyGenerateOnce)) {
557 generateSplitPoint(splitPoints, lineLastRun, lineClusterRange, lineIndex);
558 }
559 }
560 }
561
562 void ParagraphImpl::generateRunsBySplitPoints(std::vector<SplitPoint>& splitPoints, SkTArray<Run, false>& runs) {
563 std::reverse(splitPoints.begin(), splitPoints.end());
564 size_t newRunGlobalIndex = 0;
565 for (size_t runIndex = 0; runIndex < fRuns.size(); ++runIndex) {
566 Run& run = fRuns[runIndex];
567 if (splitPoints.empty() || splitPoints.back().runIndex != run.fIndex) {
568 // No need to split
569 run.fIndex = newRunGlobalIndex++;
570 updateSplitRunClusterInfo(run, false);
571 runs.push_back(run);
572 continue;
573 }
574
575 while (!splitPoints.empty()) {
576 SplitPoint& splitPoint = splitPoints.back();
577 size_t splitRunIndex = splitPoint.runIndex;
578 if (splitRunIndex != runIndex) {
579 break;
580 }
581
582 Run splitRun{run, newRunGlobalIndex++};
583 run.generateSplitRun(splitRun, splitPoint);
584 updateSplitRunClusterInfo(splitRun, true);
585 runs.push_back(std::move(splitRun));
586 splitPoints.pop_back();
587 }
588 }
589 }
590
591 void ParagraphImpl::splitRuns() {
592 // Collect split info for crossing line's run
593 std::vector<SplitPoint> splitPoints{};
594 generateSplitPoints(splitPoints);
595 if (splitPoints.empty()) {
596 return;
597 }
598 SkTArray<Run, false> newRuns;
599 generateRunsBySplitPoints(splitPoints, newRuns);
600 if (newRuns.empty()) {
601 return;
602 }
603 fRuns = std::move(newRuns);
604 refreshLines();
605 }
606 #endif
607
608 void ParagraphImpl::paint(SkCanvas* canvas, SkScalar x, SkScalar y) {
609 #ifdef OHOS_SUPPORT
610 TEXT_TRACE_FUNC();
611 fState = kDrawn;
612 #endif
613 CanvasParagraphPainter painter(canvas);
614 paint(&painter, x, y);
615 }
616
617 void ParagraphImpl::paint(ParagraphPainter* painter, SkScalar x, SkScalar y) {
618 #ifdef OHOS_SUPPORT
619 TEXT_TRACE_FUNC();
620 fState = kDrawn;
621 // Reset all text style vertical shift
622 for (Block& block : fTextStyles) {
623 block.fStyle.setVerticalAlignShift(0.0);
624 }
625 #endif
626 for (auto& line : fLines) {
627 #ifdef OHOS_SUPPORT
628 line.updateTextLinePaintAttributes();
629 #endif
630 line.paint(painter, x, y);
631 }
632 }
633
634 void ParagraphImpl::paint(ParagraphPainter* painter, RSPath* path, SkScalar hOffset, SkScalar vOffset) {
635 #ifdef OHOS_SUPPORT
636 TEXT_TRACE_FUNC();
637 fState = kDrawn;
638 #endif
639 auto& style = fTextStyles[0].fStyle;
640 float align = 0.0f;
641 switch (paragraphStyle().getTextAlign()) {
642 case TextAlign::kCenter:
643 align = -0.5f;
644 break;
645 case TextAlign::kRight:
646 align = -1.0f;
647 break;
648 default:
649 break;
650 }
651 hOffset += align * (fMaxIntrinsicWidth - style.getLetterSpacing() - path->GetLength(false));
652 for (auto& line : fLines) {
653 line.paint(painter, path, hOffset, vOffset);
654 }
655 }
656
657 #ifdef OHOS_SUPPORT
658 TextRange ParagraphImpl::getEllipsisTextRange() {
659 if (fState < kLineBroken) {
660 return EMPTY_RANGE;
661 }
662 if (!fEllipsisRange.empty()) {
663 return fEllipsisRange;
664 }
665 this->ensureUTF16Mapping();
666 for (const auto& line: fLines) {
667 if (line.getTextRangeReplacedByEllipsis().empty()) {
668 continue;
669 }
670 auto ellipsisClusterRange = line.getTextRangeReplacedByEllipsis();
671 return TextRange(getUTF16Index(ellipsisClusterRange.start),
672 getUTF16Index(ellipsisClusterRange.end));
673 }
674 return EMPTY_RANGE;
675 }
676 #endif
677
678 void ParagraphImpl::resetContext() {
679 fAlphabeticBaseline = 0;
680 fHeight = 0;
681 fWidth = 0;
682 fIdeographicBaseline = 0;
683 fMaxIntrinsicWidth = 0;
684 fMinIntrinsicWidth = 0;
685 fLongestLine = 0;
686 #ifdef OHOS_SUPPORT
687 fLongestLineWithIndent = 0;
688 #endif
689 fMaxWidthWithTrailingSpaces = 0;
690 fExceededMaxLines = false;
691 }
692
693 // shapeTextIntoEndlessLine is the thing that calls this method
694 bool ParagraphImpl::computeCodeUnitProperties() {
695 #ifdef OHOS_SUPPORT
696 TEXT_TRACE_FUNC();
697 #endif
698 if (nullptr == fUnicode) {
699 return false;
700 }
701
702 // Get bidi regions
703 auto textDirection = fParagraphStyle.getTextDirection() == TextDirection::kLtr
704 ? SkUnicode::TextDirection::kLTR
705 : SkUnicode::TextDirection::kRTL;
706 if (!fUnicode->getBidiRegions(fText.c_str(), fText.size(), textDirection, &fBidiRegions)) {
707 return false;
708 }
709
710 const char *locale = fParagraphStyle.getTextStyle().getLocale().c_str();
711 // Collect all spaces and some extra information
712 // (and also substitute \t with a space while we are at it)
713 if (!fUnicode->computeCodeUnitFlags(&fText[0],
714 fText.size(),
715 #ifdef OHOS_SUPPORT
716 this->paragraphStyle().getReplaceTabCharacters() ||
717 (!(this->paragraphStyle().getTextTab().location < 1.0)),
718 locale,
719 #else
720 this->paragraphStyle().getReplaceTabCharacters(),
721 #endif
722 &fCodeUnitProperties)) {
723 return false;
724 }
725
726 // Get some information about trailing spaces / hard line breaks
727 fTrailingSpaces = fText.size();
728 TextIndex firstWhitespace = EMPTY_INDEX;
729 for (size_t i = 0; i < fCodeUnitProperties.size(); ++i) {
730 auto flags = fCodeUnitProperties[i];
731 if (SkUnicode::isPartOfWhiteSpaceBreak(flags)) {
732 if (fTrailingSpaces == fText.size()) {
733 fTrailingSpaces = i;
734 }
735 if (firstWhitespace == EMPTY_INDEX) {
736 firstWhitespace = i;
737 }
738 } else {
739 fTrailingSpaces = fText.size();
740 }
741 if (SkUnicode::isHardLineBreak(flags)) {
742 fHasLineBreaks = true;
743 }
744 }
745
746 if (firstWhitespace < fTrailingSpaces) {
747 fHasWhitespacesInside = true;
748 }
749
750 return true;
751 }
752
753 static bool is_ascii_7bit_space(int c) {
754 SkASSERT(c >= 0 && c <= 127);
755
756 // Extracted from https://en.wikipedia.org/wiki/Whitespace_character
757 //
758 enum WS {
759 kHT = 9,
760 kLF = 10,
761 kVT = 11,
762 kFF = 12,
763 kCR = 13,
764 kSP = 32, // too big to use as shift
765 };
766 #define M(shift) (1 << (shift))
767 constexpr uint32_t kSpaceMask = M(kHT) | M(kLF) | M(kVT) | M(kFF) | M(kCR);
768 // we check for Space (32) explicitly, since it is too large to shift
769 return (c == kSP) || (c <= 31 && (kSpaceMask & M(c)));
770 #undef M
771 }
772
773 #ifdef OHOS_SUPPORT
774 static const std::vector<SkRange<SkUnichar>> CJK_UNICODE_SET = {
775 SkRange<SkUnichar>(0x1100, 0x11FF),
776 SkRange<SkUnichar>(0x2E80, 0x2EFF),
777 // [0x3040, 0x309F](Hiragana) + [0x30A0, 0x30FF](Katakana)
778 SkRange<SkUnichar>(0x3040, 0x30FF),
779 SkRange<SkUnichar>(0x3130, 0x318F),
780 // [0x31C0, 0x31EF](CJK Strokes) + [0x31F0, 0x31FF](Katakana Phonetic Extensions)
781 SkRange<SkUnichar>(0x31C0, 0x31FF),
782 SkRange<SkUnichar>(0x3400, 0x4DBF),
783 SkRange<SkUnichar>(0x4E00, 0x9FFF),
784 SkRange<SkUnichar>(0xAC00, 0xD7AF),
785 SkRange<SkUnichar>(0xF900, 0xFAFF),
786 SkRange<SkUnichar>(0x20000, 0x2A6DF),
787 /*
788 [0x2A700, 0x2B73F](CJK Unified Ideographs Extension C) +
789 [0x2B740, 0x2B81F](CJK Unified Ideographs Extension D) +
790 [0x2B820, 0x2CEAF](CJK Unified Ideographs Extension E) +
791 [0x2CEB0, 0x2EBEF](CJK Unified Ideographs Extension F)
792 */
793 SkRange<SkUnichar>(0x2A700, 0x2EBEF),
794 SkRange<SkUnichar>(0x2F800, 0x2FA1F),
795 SkRange<SkUnichar>(0x30000, 0x3134F),
796 };
797
798 static const std::vector<SkRange<SkUnichar>> WESTERN_UNICODE_SET = {
799 SkRange<SkUnichar>(0x0030, 0x0039), // Number
800 SkRange<SkUnichar>(0x0041, 0x005A), // Base Latin
801 SkRange<SkUnichar>(0x0061, 0x007A),
802 SkRange<SkUnichar>(0x00C0, 0x00FF), // Latin Extended-1: À-ÿ
803 SkRange<SkUnichar>(0x0100, 0x017F), // Latin Extended-A: Ā-ſ
804 SkRange<SkUnichar>(0x018F, 0x0192), // Latin Extended-B (specific ranges)
805 SkRange<SkUnichar>(0x01A0, 0x01A1),
806 SkRange<SkUnichar>(0x01AF, 0x01B0),
807 SkRange<SkUnichar>(0x01CD, 0x01DC),
808 SkRange<SkUnichar>(0x01E5, 0x01E5),
809 SkRange<SkUnichar>(0x01E7, 0x01E7),
810 SkRange<SkUnichar>(0x01E9, 0x01E9),
811 SkRange<SkUnichar>(0x01EF, 0x01F0),
812 SkRange<SkUnichar>(0x01F9, 0x01FF),
813 SkRange<SkUnichar>(0x0218, 0x0219),
814 SkRange<SkUnichar>(0x021A, 0x021B),
815 SkRange<SkUnichar>(0x021F, 0x021F),
816 SkRange<SkUnichar>(0x0237, 0x0237),
817 SkRange<SkUnichar>(0x0386, 0x0386), // Greek and Coptic
818 SkRange<SkUnichar>(0x0388, 0x038A),
819 SkRange<SkUnichar>(0x038C, 0x038C),
820 SkRange<SkUnichar>(0x038E, 0x038F),
821 SkRange<SkUnichar>(0x0390, 0x03A1),
822 SkRange<SkUnichar>(0x03A3, 0x03CE),
823 SkRange<SkUnichar>(0x03D1, 0x03D2),
824 SkRange<SkUnichar>(0x03D6, 0x03D6),
825 SkRange<SkUnichar>(0x0400, 0x045F), // Cyrillic
826 SkRange<SkUnichar>(0x0462, 0x0463),
827 SkRange<SkUnichar>(0x046B, 0x046B),
828 SkRange<SkUnichar>(0x0472, 0x0475),
829 SkRange<SkUnichar>(0x0490, 0x0493),
830 SkRange<SkUnichar>(0x0497, 0x0497),
831 SkRange<SkUnichar>(0x049A, 0x049D),
832 SkRange<SkUnichar>(0x04A2, 0x04A3),
833 SkRange<SkUnichar>(0x04AE, 0x04B3),
834 SkRange<SkUnichar>(0x04B8, 0x04BB),
835 SkRange<SkUnichar>(0x04CA, 0x04CA),
836 SkRange<SkUnichar>(0x04D8, 0x04D9),
837 SkRange<SkUnichar>(0x04E8, 0x04E9),
838 SkRange<SkUnichar>(0x1E00, 0x1E01), // Latin Extended Additional
839 SkRange<SkUnichar>(0x1E3E, 0x1E3F),
840 SkRange<SkUnichar>(0x1E80, 0x1E85),
841 SkRange<SkUnichar>(0x1EA0, 0x1EF9),
842 SkRange<SkUnichar>(0x1F45, 0x1F45), // Greek Extended
843 SkRange<SkUnichar>(0x1F4D, 0x1F4D)
844 };
845
846 constexpr SkUnichar COPYRIGHT_UNICODE = 0x00A9;
847
848 struct UnicodeIdentifier {
849 static bool cmp(SkRange<SkUnichar> a, SkRange<SkUnichar> b) {
850 return a.start < b.start;
851 }
852 const std::vector<SkRange<SkUnichar>>& fUnicodeSet;
853 explicit UnicodeIdentifier(const std::vector<SkRange<SkUnichar>>& unicodeSet) : fUnicodeSet(unicodeSet) {}
854 bool exist(SkUnichar c) const {
855 auto pos = std::upper_bound(fUnicodeSet.begin(), fUnicodeSet.end(), SkRange<SkUnichar>(c, c), cmp);
856 if (pos == fUnicodeSet.begin()) {
857 return false;
858 }
859 --pos;
860 return pos->end >= c;
861 }
862 };
863
864 static const UnicodeIdentifier CJK_IDENTIFIER(CJK_UNICODE_SET);
865 static const UnicodeIdentifier WESTERN_IDENTIFIER(WESTERN_UNICODE_SET);
866
867 static Cluster::AutoSpacingFlag recognizeUnicodeAutoSpacingFlag(ParagraphImpl& paragraph, SkUnichar unicode)
868 {
869 if (!paragraph.isAutoSpaceEnabled()) {
870 return Cluster::AutoSpacingFlag::NoFlag;
871 }
872 if (WESTERN_IDENTIFIER.exist(unicode)) {
873 return Cluster::AutoSpacingFlag::Western;
874 }
875 if (CJK_IDENTIFIER.exist(unicode)) {
876 return Cluster::AutoSpacingFlag::CJK;
877 }
878 if (unicode == COPYRIGHT_UNICODE) {
879 return Cluster::AutoSpacingFlag::Copyright;
880 }
881 return Cluster::AutoSpacingFlag::NoFlag;
882 }
883 #endif
884 Cluster::Cluster(ParagraphImpl* owner,
885 RunIndex runIndex,
886 size_t start,
887 size_t end,
888 SkSpan<const char> text,
889 SkScalar width,
890 SkScalar height)
891 : fOwner(owner)
892 , fRunIndex(runIndex)
893 , fTextRange(text.begin() - fOwner->text().begin(), text.end() - fOwner->text().begin())
894 , fGraphemeRange(EMPTY_RANGE)
895 , fStart(start)
896 , fEnd(end)
897 , fWidth(width)
898 , fHeight(height)
899 , fHalfLetterSpacing(0.0)
900 , fIsIdeographic(false) {
901 size_t whiteSpacesBreakLen = 0;
902 size_t intraWordBreakLen = 0;
903
904 const char* ch = text.begin();
905 if (text.end() - ch == 1 && *(unsigned char*)ch <= 0x7F) {
906 // I am not even sure it's worth it if we do not save a unicode call
907 if (is_ascii_7bit_space(*ch)) {
908 ++whiteSpacesBreakLen;
909 }
910 #ifdef OHOS_SUPPORT
911 fIsPunctuation = fOwner->codeUnitHasProperty(fTextRange.start, SkUnicode::CodeUnitFlags::kPunctuation);
912 fIsEllipsis = fOwner->codeUnitHasProperty(fTextRange.start, SkUnicode::CodeUnitFlags::kEllipsis);
913 #endif
914 } else {
915 for (auto i = fTextRange.start; i < fTextRange.end; ++i) {
916 if (fOwner->codeUnitHasProperty(i, SkUnicode::CodeUnitFlags::kPartOfWhiteSpaceBreak)) {
917 ++whiteSpacesBreakLen;
918 }
919 if (fOwner->codeUnitHasProperty(i, SkUnicode::CodeUnitFlags::kPartOfIntraWordBreak)) {
920 ++intraWordBreakLen;
921 }
922 if (fOwner->codeUnitHasProperty(i, SkUnicode::CodeUnitFlags::kIdeographic)) {
923 fIsIdeographic = true;
924 }
925 #ifdef OHOS_SUPPORT
926 fIsPunctuation = fOwner->codeUnitHasProperty(i, SkUnicode::CodeUnitFlags::kPunctuation) | fIsPunctuation;
927 fIsEllipsis = fOwner->codeUnitHasProperty(i, SkUnicode::CodeUnitFlags::kEllipsis) | fIsEllipsis;
928 #endif
929 }
930 }
931
932 fIsWhiteSpaceBreak = whiteSpacesBreakLen == fTextRange.width();
933 fIsIntraWordBreak = intraWordBreakLen == fTextRange.width();
934 fIsHardBreak = fOwner->codeUnitHasProperty(fTextRange.end,
935 SkUnicode::CodeUnitFlags::kHardLineBreakBefore);
936 #ifdef OHOS_SUPPORT
937 fIsTabulation = fOwner->codeUnitHasProperty(fTextRange.start,
938 SkUnicode::CodeUnitFlags::kTabulation);
939 auto unicodeStart = fOwner->getUnicodeIndex(fTextRange.start);
940 auto unicodeEnd = fOwner->getUnicodeIndex(fTextRange.end);
941 SkUnichar unicode = 0;
942 if (unicodeEnd - unicodeStart == 1 && unicodeStart < fOwner->unicodeText().size()) {
943 unicode = fOwner->unicodeText()[unicodeStart];
944 }
945
946 auto curAutoSpacingFlag = recognizeUnicodeAutoSpacingFlag(*fOwner, unicode);
947 auto lastAutoSpacingFlag = fOwner->getLastAutoSpacingFlag();
948 fNeedAutoSpacing = curAutoSpacingFlag != Cluster::AutoSpacingFlag::NoFlag &&
949 curAutoSpacingFlag != lastAutoSpacingFlag && lastAutoSpacingFlag != Cluster::AutoSpacingFlag::NoFlag;
950 fOwner->setLastAutoSpacingFlag(curAutoSpacingFlag);
951 #endif
952 }
953
954 SkScalar Run::calculateWidth(size_t start, size_t end, bool clip) const {
955 SkASSERT(start <= end);
956 // clip |= end == size(); // Clip at the end of the run?
957 auto correction = 0.0f;
958 if (end > start && !fJustificationShifts.empty()) {
959 // This is not a typo: we are using Point as a pair of SkScalars
960 correction = fJustificationShifts[end - 1].fX -
961 fJustificationShifts[start].fY;
962 }
963 if (end > start && !fAutoSpacings.empty()) {
964 // This is not a tyopo: we are using Point as a pair of SkScalars
965 correction += fAutoSpacings[end - 1].fX - fAutoSpacings[start].fY;
966 }
967 return posX(end) - posX(start) + correction;
968 }
969
970 // In some cases we apply spacing to glyphs first and then build the cluster table, in some we do
971 // the opposite - just to optimize the most common case.
972 void ParagraphImpl::applySpacingAndBuildClusterTable() {
973 #ifdef OHOS_SUPPORT
974 TEXT_TRACE_FUNC();
975 #endif
976 // Check all text styles to see what we have to do (if anything)
977 size_t letterSpacingStyles = 0;
978 bool hasWordSpacing = false;
979 for (auto& block : fTextStyles) {
980 if (block.fRange.width() > 0) {
981 if (!SkScalarNearlyZero(block.fStyle.getLetterSpacing())) {
982 ++letterSpacingStyles;
983 }
984 if (!SkScalarNearlyZero(block.fStyle.getWordSpacing())) {
985 hasWordSpacing = true;
986 }
987 }
988 }
989
990 if (letterSpacingStyles == 0 && !hasWordSpacing) {
991 // We don't have to do anything about spacing (most common case)
992 this->buildClusterTable();
993 return;
994 }
995
996 if (letterSpacingStyles == 1 && !hasWordSpacing && fTextStyles.size() == 1 &&
997 fTextStyles[0].fRange.width() == fText.size() && fRuns.size() == 1) {
998 // We have to letter space the entire paragraph (second most common case)
999 auto& run = fRuns[0];
1000 auto& style = fTextStyles[0].fStyle;
1001 this->buildClusterTable();
1002 SkScalar shift = 0;
1003 run.iterateThroughClusters([this, &run, &shift, &style](Cluster* cluster) {
1004 run.shift(cluster, shift);
1005 shift += run.addSpacesEvenly(style.getLetterSpacing(), cluster);
1006 });
1007 return;
1008 }
1009
1010 // The complex case: many text styles with spacing (possibly not adjusted to glyphs)
1011 this->buildClusterTable();
1012
1013 // Walk through all the clusters in the direction of shaped text
1014 // (we have to walk through the styles in the same order, too)
1015 SkScalar shift = 0;
1016 for (auto& run : fRuns) {
1017
1018 // Skip placeholder runs
1019 if (run.isPlaceholder()) {
1020 continue;
1021 }
1022 bool soFarWhitespacesOnly = true;
1023 bool wordSpacingPending = false;
1024 Cluster* lastSpaceCluster = nullptr;
1025 run.iterateThroughClusters([this, &run, &shift, &soFarWhitespacesOnly, &wordSpacingPending, &lastSpaceCluster](Cluster* cluster) {
1026 // Shift the cluster (shift collected from the previous clusters)
1027 run.shift(cluster, shift);
1028
1029 // Synchronize styles (one cluster can be covered by few styles)
1030 Block* currentStyle = fTextStyles.begin();
1031 while (!cluster->startsIn(currentStyle->fRange)) {
1032 currentStyle++;
1033 SkASSERT(currentStyle != fTextStyles.end());
1034 }
1035
1036 SkASSERT(!currentStyle->fStyle.isPlaceholder());
1037
1038 // Process word spacing
1039 if (currentStyle->fStyle.getWordSpacing() != 0) {
1040 if (cluster->isWhitespaceBreak() && cluster->isSoftBreak()) {
1041 if (!soFarWhitespacesOnly) {
1042 lastSpaceCluster = cluster;
1043 wordSpacingPending = true;
1044 }
1045 } else if (wordSpacingPending) {
1046 SkScalar spacing = currentStyle->fStyle.getWordSpacing();
1047 run.addSpacesAtTheEnd(spacing, lastSpaceCluster);
1048 run.shift(cluster, spacing);
1049 shift += spacing;
1050 wordSpacingPending = false;
1051 }
1052 }
1053 // Process letter spacing
1054 if (currentStyle->fStyle.getLetterSpacing() != 0) {
1055 shift += run.addSpacesEvenly(currentStyle->fStyle.getLetterSpacing(), cluster);
1056 }
1057
1058 if (soFarWhitespacesOnly && !cluster->isWhitespaceBreak()) {
1059 soFarWhitespacesOnly = false;
1060 }
1061 });
1062 }
1063 }
1064
1065 // Clusters in the order of the input text
1066 void ParagraphImpl::buildClusterTable() {
1067 // It's possible that one grapheme includes few runs; we cannot handle it
1068 // so we break graphemes by the runs instead
1069 // It's not the ideal solution and has to be revisited later
1070 size_t cluster_count = 1;
1071 for (auto& run : fRuns) {
1072 cluster_count += run.isPlaceholder() ? 1 : run.size();
1073 fCodeUnitProperties[run.fTextRange.start] |= SkUnicode::CodeUnitFlags::kGraphemeStart;
1074 fCodeUnitProperties[run.fTextRange.start] |= SkUnicode::CodeUnitFlags::kGlyphClusterStart;
1075 }
1076 if (!fRuns.empty()) {
1077 fCodeUnitProperties[fRuns.back().textRange().end] |= SkUnicode::CodeUnitFlags::kGraphemeStart;
1078 fCodeUnitProperties[fRuns.back().textRange().end] |= SkUnicode::CodeUnitFlags::kGlyphClusterStart;
1079 }
1080 fClusters.reserve_back(fClusters.size() + cluster_count);
1081
1082 // Walk through all the run in the direction of input text
1083 for (auto& run : fRuns) {
1084 auto runIndex = run.index();
1085 auto runStart = fClusters.size();
1086 if (run.isPlaceholder()) {
1087 // Add info to cluster indexes table (text -> cluster)
1088 for (auto i = run.textRange().start; i < run.textRange().end; ++i) {
1089 fClustersIndexFromCodeUnit[i] = fClusters.size();
1090 }
1091 // There are no glyphs but we want to have one cluster
1092 fClusters.emplace_back(this, runIndex, 0ul, 1ul, this->text(run.textRange()), run.advance().fX, run.advance().fY);
1093 fCodeUnitProperties[run.textRange().start] |= SkUnicode::CodeUnitFlags::kSoftLineBreakBefore;
1094 fCodeUnitProperties[run.textRange().end] |= SkUnicode::CodeUnitFlags::kSoftLineBreakBefore;
1095 } else {
1096 // Walk through the glyph in the direction of input text
1097 run.iterateThroughClustersInTextOrder([&run, runIndex, this](size_t glyphStart,
1098 size_t glyphEnd,
1099 size_t charStart,
1100 size_t charEnd,
1101 SkScalar width,
1102 SkScalar height) {
1103 SkASSERT(charEnd >= charStart);
1104 // Add info to cluster indexes table (text -> cluster)
1105 for (auto i = charStart; i < charEnd; ++i) {
1106 fClustersIndexFromCodeUnit[i] = fClusters.size();
1107 }
1108 SkSpan<const char> text(fText.c_str() + charStart, charEnd - charStart);
1109 fClusters.emplace_back(this, runIndex, glyphStart, glyphEnd, text, width, height);
1110 fCodeUnitProperties[charStart] |= SkUnicode::CodeUnitFlags::kGlyphClusterStart;
1111 });
1112 }
1113 fCodeUnitProperties[run.textRange().start] |= SkUnicode::CodeUnitFlags::kGlyphClusterStart;
1114
1115 run.setClusterRange(runStart, fClusters.size());
1116 fMaxIntrinsicWidth += run.advance().fX;
1117 }
1118 fClustersIndexFromCodeUnit[fText.size()] = fClusters.size();
1119 fClusters.emplace_back(this, EMPTY_RUN, 0, 0, this->text({fText.size(), fText.size()}), 0, 0);
1120 }
1121
1122 bool ParagraphImpl::shapeTextIntoEndlessLine() {
1123 #ifdef OHOS_SUPPORT
1124 TEXT_TRACE_FUNC();
1125 #endif
1126 if (fText.size() == 0) {
1127 return false;
1128 }
1129
1130 fUnresolvedCodepoints.clear();
1131 fFontSwitches.reset();
1132
1133 OneLineShaper oneLineShaper(this);
1134 auto result = oneLineShaper.shape();
1135 fUnresolvedGlyphs = oneLineShaper.unresolvedGlyphs();
1136
1137 this->applySpacingAndBuildClusterTable();
1138
1139 return result;
1140 }
1141
1142 void ParagraphImpl::setIndents(const std::vector<SkScalar>& indents)
1143 {
1144 fIndents = indents;
1145 }
1146
1147 SkScalar ParagraphImpl::detectIndents(size_t index)
1148 {
1149 SkScalar indent = 0.0;
1150 if (fIndents.size() > 0 && index < fIndents.size()) {
1151 indent = fIndents[index];
1152 } else {
1153 indent = fIndents.size() > 0 ? fIndents.back() : 0.0;
1154 }
1155
1156 return indent;
1157 }
1158
1159 #ifdef OHOS_SUPPORT
1160 void ParagraphImpl::positionShapedTextIntoLine(SkScalar maxWidth) {
1161 resetAutoSpacing();
1162 // This is a short version of a line breaking when we know that:
1163 // 1. We have only one line of text
1164 // 2. It's shaped into a single run
1165 // 3. There are no placeholders
1166 // 4. There are no linebreaks (which will format text into multiple lines)
1167 // 5. There are no whitespaces so the minIntrinsicWidth=maxIntrinsicWidth
1168 // (To think about that, the last condition is not quite right;
1169 // we should calculate minIntrinsicWidth by soft line breaks.
1170 // However, it's how it's done in Flutter now)
1171 auto& run = this->fRuns[0];
1172 auto advance = run.advance();
1173 auto textRange = TextRange(0, this->text().size());
1174 auto textExcludingSpaces = TextRange(0, fTrailingSpaces);
1175 InternalLineMetrics metrics(strutForceHeight() && strutEnabled());
1176 metrics.add(&run);
1177 auto disableFirstAscent = this->paragraphStyle().getTextHeightBehavior() & TextHeightBehavior::kDisableFirstAscent;
1178 auto disableLastDescent = this->paragraphStyle().getTextHeightBehavior() & TextHeightBehavior::kDisableLastDescent;
1179 if (disableFirstAscent) {
1180 metrics.fAscent = metrics.fRawAscent;
1181 }
1182 if (disableLastDescent) {
1183 metrics.fDescent = metrics.fRawDescent;
1184 }
1185 if (this->strutEnabled()) {
1186 this->strutMetrics().updateLineMetrics(metrics);
1187 }
1188 ClusterIndex trailingSpaces = fClusters.size();
1189 do {
1190 --trailingSpaces;
1191 auto& cluster = fClusters[trailingSpaces];
1192 if (!cluster.isWhitespaceBreak()) {
1193 ++trailingSpaces;
1194 break;
1195 }
1196 advance.fX -= cluster.width();
1197 } while (trailingSpaces != 0);
1198
1199 advance.fY = metrics.height();
1200 SkScalar heightWithParagraphSpacing = advance.fY;
1201 if (this->paragraphStyle().getIsEndAddParagraphSpacing() &&
1202 this->paragraphStyle().getParagraphSpacing() > 0) {
1203 heightWithParagraphSpacing += this->paragraphStyle().getParagraphSpacing();
1204 }
1205 auto clusterRange = ClusterRange(0, trailingSpaces);
1206 auto clusterRangeWithGhosts = ClusterRange(0, this->clusters().size() - 1);
1207 SkScalar offsetX = this->detectIndents(0);
1208 auto& line = this->addLine(SkPoint::Make(offsetX, 0), advance, textExcludingSpaces, textRange, textRange,
1209 clusterRange, clusterRangeWithGhosts, run.advance().x(), metrics);
1210 auto spacing = line.autoSpacing();
1211 auto longestLine = std::max(run.advance().fX, advance.fX) + spacing;
1212 setSize(heightWithParagraphSpacing, maxWidth, longestLine);
1213 setLongestLineWithIndent(longestLine + offsetX);
1214 setIntrinsicSize(run.advance().fX, advance.fX,
1215 fLines.empty() ? fEmptyMetrics.alphabeticBaseline() : fLines.front().alphabeticBaseline(),
1216 fLines.empty() ? fEmptyMetrics.ideographicBaseline() : fLines.front().ideographicBaseline(),
1217 false);
1218 }
1219
1220 void ParagraphImpl::breakShapedTextIntoLines(SkScalar maxWidth) {
1221 TEXT_TRACE_FUNC();
1222 resetAutoSpacing();
1223 TextWrapper textWrapper;
1224 textWrapper.breakTextIntoLines(
1225 this,
1226 maxWidth,
1227 [&](TextRange textExcludingSpaces,
1228 TextRange text,
1229 TextRange textWithNewlines,
1230 ClusterRange clusters,
1231 ClusterRange clustersWithGhosts,
1232 SkScalar widthWithSpaces,
1233 size_t startPos,
1234 size_t endPos,
1235 SkVector offset,
1236 SkVector advance,
1237 InternalLineMetrics metrics,
1238 bool addEllipsis,
1239 SkScalar indent,
1240 SkScalar noIndentWidth) {
1241 // TODO: Take in account clipped edges
1242 auto& line = this->addLine(offset, advance, textExcludingSpaces, text, textWithNewlines,
1243 clusters, clustersWithGhosts, widthWithSpaces, metrics);
1244 line.autoSpacing();
1245 if (addEllipsis && this->paragraphStyle().getEllipsisMod() == EllipsisModal::TAIL) {
1246 line.createTailEllipsis(noIndentWidth, this->getEllipsis(), true, this->getWordBreakType());
1247 } else if (addEllipsis && this->paragraphStyle().getEllipsisMod() == EllipsisModal::HEAD) {
1248 line.createHeadEllipsis(noIndentWidth, this->getEllipsis(), true);
1249 }
1250 else if (needCreateMiddleEllipsis()) {
1251 line.createMiddleEllipsis(noIndentWidth, this->getEllipsis());
1252 } else if (textWrapper.brokeLineWithHyphen()
1253 || ((clusters.end == clustersWithGhosts.end) && (clusters.end >= 1)
1254 && (clusters.end < this->fUnicodeText.size())
1255 && (this->fUnicodeText[clusters.end - 1] == 0xad))) { // 0xad represents a soft hyphen
1256 line.setBreakWithHyphen(true);
1257 }
1258 auto longestLine = std::max(line.width(), line.widthWithEllipsisSpaces());
1259 fLongestLine = std::max(fLongestLine, longestLine);
1260 fLongestLineWithIndent = std::max(fLongestLineWithIndent, longestLine + indent);
1261 });
1262 setSize(textWrapper.height(), maxWidth, fLongestLine);
1263 setIntrinsicSize(textWrapper.maxIntrinsicWidth(), textWrapper.minIntrinsicWidth(),
1264 fLines.empty() ? fEmptyMetrics.alphabeticBaseline() : fLines.front().alphabeticBaseline(),
1265 fLines.empty() ? fEmptyMetrics.ideographicBaseline() : fLines.front().ideographicBaseline(),
1266 textWrapper.exceededMaxLines());
1267 }
1268 #else
1269 void ParagraphImpl::breakShapedTextIntoLines(SkScalar maxWidth) {
1270
1271 if (!fHasLineBreaks &&
1272 !fHasWhitespacesInside &&
1273 fPlaceholders.size() == 1 &&
1274 fRuns.size() == 1 && fRuns[0].fAdvance.fX <= maxWidth) {
1275 // This is a short version of a line breaking when we know that:
1276 // 1. We have only one line of text
1277 // 2. It's shaped into a single run
1278 // 3. There are no placeholders
1279 // 4. There are no linebreaks (which will format text into multiple lines)
1280 // 5. There are no whitespaces so the minIntrinsicWidth=maxIntrinsicWidth
1281 // (To think about that, the last condition is not quite right;
1282 // we should calculate minIntrinsicWidth by soft line breaks.
1283 // However, it's how it's done in Flutter now)
1284 auto& run = this->fRuns[0];
1285 auto advance = run.advance();
1286 auto textRange = TextRange(0, this->text().size());
1287 auto textExcludingSpaces = TextRange(0, fTrailingSpaces);
1288 InternalLineMetrics metrics(this->strutForceHeight());
1289 metrics.add(&run);
1290 auto disableFirstAscent = this->paragraphStyle().getTextHeightBehavior() &
1291 TextHeightBehavior::kDisableFirstAscent;
1292 auto disableLastDescent = this->paragraphStyle().getTextHeightBehavior() &
1293 TextHeightBehavior::kDisableLastDescent;
1294 if (disableFirstAscent) {
1295 metrics.fAscent = metrics.fRawAscent;
1296 }
1297 if (disableLastDescent) {
1298 metrics.fDescent = metrics.fRawDescent;
1299 }
1300 if (this->strutEnabled()) {
1301 this->strutMetrics().updateLineMetrics(metrics);
1302 }
1303 ClusterIndex trailingSpaces = fClusters.size();
1304 do {
1305 --trailingSpaces;
1306 auto& cluster = fClusters[trailingSpaces];
1307 if (!cluster.isWhitespaceBreak()) {
1308 ++trailingSpaces;
1309 break;
1310 }
1311 advance.fX -= cluster.width();
1312 } while (trailingSpaces != 0);
1313
1314 advance.fY = metrics.height();
1315 auto clusterRange = ClusterRange(0, trailingSpaces);
1316 auto clusterRangeWithGhosts = ClusterRange(0, this->clusters().size() - 1);
1317 this->addLine(SkPoint::Make(0, 0), advance,
1318 textExcludingSpaces, textRange, textRange,
1319 clusterRange, clusterRangeWithGhosts, run.advance().x(),
1320 metrics);
1321
1322 fLongestLine = nearlyZero(advance.fX) ? run.advance().fX : advance.fX;
1323 fHeight = advance.fY;
1324 fWidth = maxWidth;
1325 fMaxIntrinsicWidth = run.advance().fX;
1326 fMinIntrinsicWidth = advance.fX;
1327 fAlphabeticBaseline = fLines.empty() ? fEmptyMetrics.alphabeticBaseline() : fLines.front().alphabeticBaseline();
1328 fIdeographicBaseline = fLines.empty() ? fEmptyMetrics.ideographicBaseline() : fLines.front().ideographicBaseline();
1329 fExceededMaxLines = false;
1330 return;
1331 }
1332
1333 TextWrapper textWrapper;
1334 textWrapper.breakTextIntoLines(
1335 this,
1336 maxWidth,
1337 [&](TextRange textExcludingSpaces,
1338 TextRange text,
1339 TextRange textWithNewlines,
1340 ClusterRange clusters,
1341 ClusterRange clustersWithGhosts,
1342 SkScalar widthWithSpaces,
1343 size_t startPos,
1344 size_t endPos,
1345 SkVector offset,
1346 SkVector advance,
1347 InternalLineMetrics metrics,
1348 bool addEllipsis) {
1349 // TODO: Take in account clipped edges
1350 auto& line = this->addLine(offset, advance, textExcludingSpaces, text, textWithNewlines, clusters, clustersWithGhosts, widthWithSpaces, metrics);
1351 if (addEllipsis) {
1352 line.createEllipsis(maxWidth, this->getEllipsis(), true);
1353 }
1354 fLongestLine = std::max(fLongestLine, nearlyZero(advance.fX) ? widthWithSpaces : advance.fX);
1355 });
1356
1357 fHeight = textWrapper.height();
1358 fWidth = maxWidth;
1359 fMaxIntrinsicWidth = textWrapper.maxIntrinsicWidth();
1360 fMinIntrinsicWidth = textWrapper.minIntrinsicWidth();
1361 fAlphabeticBaseline = fLines.empty() ? fEmptyMetrics.alphabeticBaseline() : fLines.front().alphabeticBaseline();
1362 fIdeographicBaseline = fLines.empty() ? fEmptyMetrics.ideographicBaseline() : fLines.front().ideographicBaseline();
1363 fExceededMaxLines = textWrapper.exceededMaxLines();
1364 }
1365 #endif
1366
1367 void ParagraphImpl::formatLines(SkScalar maxWidth) {
1368 #ifdef OHOS_SUPPORT
1369 TEXT_TRACE_FUNC();
1370 #endif
1371 auto effectiveAlign = fParagraphStyle.effective_align();
1372 const bool isLeftAligned = effectiveAlign == TextAlign::kLeft
1373 || (effectiveAlign == TextAlign::kJustify && fParagraphStyle.getTextDirection() == TextDirection::kLtr);
1374
1375 if (!SkScalarIsFinite(maxWidth) && !isLeftAligned) {
1376 // Special case: clean all text in case of maxWidth == INF & align != left
1377 // We had to go through shaping though because we need all the measurement numbers
1378 fLines.reset();
1379 return;
1380 }
1381
1382 size_t iLineNumber = 0;
1383 for (auto& line : fLines) {
1384 SkScalar noIndentWidth = maxWidth - detectIndents(iLineNumber++);
1385 if (fParagraphStyle.getTextDirection() == TextDirection::kRtl) {
1386 line.setLineOffsetX(0);
1387 }
1388 line.format(effectiveAlign, noIndentWidth, this->paragraphStyle().getEllipsisMod());
1389
1390 if (paragraphStyle().getVerticalAlignment() != TextVerticalAlign::BASELINE) {
1391 line.applyVerticalShift();
1392 }
1393 }
1394 }
1395
1396 void ParagraphImpl::resolveStrut() {
1397 auto strutStyle = this->paragraphStyle().getStrutStyle();
1398 if (!strutStyle.getStrutEnabled() || strutStyle.getFontSize() < 0) {
1399 return;
1400 }
1401
1402 auto typefaces = fFontCollection->findTypefaces(strutStyle.getFontFamilies(), strutStyle.getFontStyle(), std::nullopt);
1403 if (typefaces.empty()) {
1404 SkDEBUGF("Could not resolve strut font\n");
1405 return;
1406 }
1407
1408 #ifndef USE_SKIA_TXT
1409 SkFont font(typefaces.front(), strutStyle.getFontSize());
1410 SkFontMetrics metrics;
1411 #ifdef OHOS_SUPPORT
1412 SkFont compressFont = font;
1413 scaleFontWithCompressionConfig(compressFont, ScaleOP::COMPRESS);
1414 compressFont.getMetrics(&metrics);
1415 metricsIncludeFontPadding(&metrics, font);
1416 #else
1417 font.getMetrics(&metrics);
1418 #endif
1419 #else
1420 RSFont font(typefaces.front(), strutStyle.getFontSize(), 1, 0);
1421 RSFontMetrics metrics;
1422 #ifdef OHOS_SUPPORT
1423 RSFont compressFont = font;
1424 scaleFontWithCompressionConfig(compressFont, ScaleOP::COMPRESS);
1425 compressFont.GetMetrics(&metrics);
1426 metricsIncludeFontPadding(&metrics, font);
1427 #else
1428 font.GetMetrics(&metrics);
1429 #endif
1430 #endif
1431
1432 if (strutStyle.getHeightOverride()) {
1433 auto strutHeight = metrics.fDescent - metrics.fAscent;
1434 auto strutMultiplier = strutStyle.getHeight() * strutStyle.getFontSize();
1435 fStrutMetrics = InternalLineMetrics(
1436 (metrics.fAscent / strutHeight) * strutMultiplier,
1437 (metrics.fDescent / strutHeight) * strutMultiplier,
1438 strutStyle.getLeading() < 0 ? 0 : strutStyle.getLeading() * strutStyle.getFontSize(),
1439 metrics.fAscent, metrics.fDescent, metrics.fLeading);
1440 } else {
1441 fStrutMetrics = InternalLineMetrics(
1442 metrics.fAscent,
1443 metrics.fDescent,
1444 strutStyle.getLeading() < 0 ? 0 : strutStyle.getLeading() * strutStyle.getFontSize());
1445 }
1446 fStrutMetrics.setForceStrut(this->paragraphStyle().getStrutStyle().getForceStrutHeight());
1447 }
1448
1449 BlockRange ParagraphImpl::findAllBlocks(TextRange textRange) {
1450 BlockIndex begin = EMPTY_BLOCK;
1451 BlockIndex end = EMPTY_BLOCK;
1452 for (BlockIndex index = 0; index < fTextStyles.size(); ++index) {
1453 auto& block = fTextStyles[index];
1454 if (block.fRange.end <= textRange.start) {
1455 continue;
1456 }
1457 if (block.fRange.start >= textRange.end) {
1458 break;
1459 }
1460 if (begin == EMPTY_BLOCK) {
1461 begin = index;
1462 }
1463 end = index;
1464 }
1465
1466 if (begin == EMPTY_INDEX || end == EMPTY_INDEX) {
1467 // It's possible if some text is not covered with any text style
1468 // Not in Flutter but in direct use of SkParagraph
1469 return EMPTY_RANGE;
1470 }
1471
1472 return { begin, end + 1 };
1473 }
1474
1475 TextLine& ParagraphImpl::addLine(SkVector offset,
1476 SkVector advance,
1477 TextRange textExcludingSpaces,
1478 TextRange text,
1479 TextRange textIncludingNewLines,
1480 ClusterRange clusters,
1481 ClusterRange clustersWithGhosts,
1482 SkScalar widthWithSpaces,
1483 InternalLineMetrics sizes) {
1484 // Define a list of styles that covers the line
1485 auto blocks = findAllBlocks(textIncludingNewLines);
1486 return fLines.emplace_back(this, offset, advance, blocks,
1487 textExcludingSpaces, text, textIncludingNewLines,
1488 clusters, clustersWithGhosts, widthWithSpaces, sizes);
1489 }
1490
1491 #ifdef OHOS_SUPPORT
1492 TextBox ParagraphImpl::getEmptyTextRect(RectHeightStyle rectHeightStyle) const
1493 {
1494 // get textStyle to calculate text box when text is empty(reference to ParagraphImpl::computeEmptyMetrics())
1495 bool emptyParagraph = fRuns.empty();
1496 TextStyle textStyle = paragraphStyle().getTextStyle();
1497 if (emptyParagraph && !fTextStyles.empty()) {
1498 textStyle = fTextStyles.back().fStyle;
1499 }
1500
1501 // calculate text box(reference to TextLine::getRectsForRange(), switch(rectHeightStyle))
1502 TextBox box(SkRect::MakeXYWH(0, 0, 0, fHeight), fParagraphStyle.getTextDirection());
1503 auto verticalShift = fEmptyMetrics.rawAscent() - fEmptyMetrics.ascent();
1504 switch (rectHeightStyle) {
1505 case RectHeightStyle::kTight:
1506 if (textStyle.getHeightOverride() && textStyle.getHeight() > 0) {
1507 const auto effectiveBaseline = fEmptyMetrics.baseline() + fEmptyMetrics.delta();
1508 box.rect.fTop = effectiveBaseline + fEmptyMetrics.rawAscent();
1509 box.rect.fBottom = effectiveBaseline + fEmptyMetrics.rawDescent();
1510 }
1511 return box;
1512 case RectHeightStyle::kMax:
1513 box.rect.fBottom = fHeight;
1514 box.rect.fTop = fEmptyMetrics.delta();
1515 return box;
1516 case RectHeightStyle::kIncludeLineSpacingMiddle:
1517 case RectHeightStyle::kIncludeLineSpacingTop:
1518 case RectHeightStyle::kIncludeLineSpacingBottom:
1519 box.rect.fBottom = fHeight;
1520 box.rect.fTop = fEmptyMetrics.delta() + verticalShift;
1521 return box;
1522 case RectHeightStyle::kStrut:
1523 if (fParagraphStyle.getStrutStyle().getStrutEnabled() &&
1524 fParagraphStyle.getStrutStyle().getFontSize() > 0) {
1525 auto baseline = fEmptyMetrics.baseline();
1526 box.rect.fTop = baseline + fStrutMetrics.ascent();
1527 box.rect.fBottom = baseline + fStrutMetrics.descent();
1528 }
1529 return box;
1530 default:
1531 return box;
1532 }
1533 }
1534 #endif
1535
1536 // Returns a vector of bounding boxes that enclose all text between
1537 // start and end glyph indexes, including start and excluding end
1538 std::vector<TextBox> ParagraphImpl::getRectsForRange(unsigned start,
1539 unsigned end,
1540 RectHeightStyle rectHeightStyle,
1541 RectWidthStyle rectWidthStyle) {
1542 std::vector<TextBox> results;
1543 // this method should not be called before kshaped
1544 if (fText.isEmpty() || fState < kShaped) {
1545 if (start == 0 && end > 0) {
1546 // On account of implied "\n" that is always at the end of the text
1547 //SkDebugf("getRectsForRange(%d, %d): %f\n", start, end, fHeight);
1548 #ifdef OHOS_SUPPORT
1549 results.emplace_back(getEmptyTextRect(rectHeightStyle));
1550 #else
1551 results.emplace_back(SkRect::MakeXYWH(0, 0, 0, fHeight), fParagraphStyle.getTextDirection());
1552 #endif
1553 }
1554 return results;
1555 }
1556
1557 this->ensureUTF16Mapping();
1558
1559 if (start >= end || start > SkToSizeT(fUTF8IndexForUTF16Index.size()) || end == 0) {
1560 return results;
1561 }
1562
1563 // Adjust the text to grapheme edges
1564 // Apparently, text editor CAN move inside graphemes but CANNOT select a part of it.
1565 // I don't know why - the solution I have here returns an empty box for every query that
1566 // does not contain an end of a grapheme.
1567 // Once a cursor is inside a complex grapheme I can press backspace and cause trouble.
1568 // To avoid any problems, I will not allow any selection of a part of a grapheme.
1569 // One flutter test fails because of it but the editing experience is correct
1570 // (although you have to press the cursor many times before it moves to the next grapheme).
1571 TextRange text(fText.size(), fText.size());
1572 // TODO: This is probably a temp change that makes SkParagraph work as TxtLib
1573 // (so we can compare the results). We now include in the selection box only the graphemes
1574 // that belongs to the given [start:end) range entirely (not the ones that intersect with it)
1575 if (start < SkToSizeT(fUTF8IndexForUTF16Index.size())) {
1576 auto utf8 = fUTF8IndexForUTF16Index[start];
1577 // If start points to a trailing surrogate, skip it
1578 if (start > 0 && fUTF8IndexForUTF16Index[start - 1] == utf8) {
1579 utf8 = fUTF8IndexForUTF16Index[start + 1];
1580 }
1581 text.start = this->findNextGraphemeBoundary(utf8);
1582 }
1583 if (end < SkToSizeT(fUTF8IndexForUTF16Index.size())) {
1584 auto utf8 = this->findPreviousGraphemeBoundary(fUTF8IndexForUTF16Index[end]);
1585 text.end = utf8;
1586 }
1587 //SkDebugf("getRectsForRange(%d,%d) -> (%d:%d)\n", start, end, text.start, text.end);
1588 for (auto& line : fLines) {
1589 auto lineText = line.textWithNewlines();
1590 lineText = textRangeMergeBtoA(lineText, line.getTextRangeReplacedByEllipsis());
1591 auto intersect = lineText * text;
1592 if (intersect.empty() && lineText.start != text.start) {
1593 continue;
1594 }
1595
1596 line.getRectsForRange(intersect, rectHeightStyle, rectWidthStyle, results);
1597 }
1598 /*
1599 SkDebugf("getRectsForRange(%d, %d)\n", start, end);
1600 for (auto& r : results) {
1601 r.rect.fLeft = littleRound(r.rect.fLeft);
1602 r.rect.fRight = littleRound(r.rect.fRight);
1603 r.rect.fTop = littleRound(r.rect.fTop);
1604 r.rect.fBottom = littleRound(r.rect.fBottom);
1605 SkDebugf("[%f:%f * %f:%f]\n", r.rect.fLeft, r.rect.fRight, r.rect.fTop, r.rect.fBottom);
1606 }
1607 */
1608 return results;
1609 }
1610
1611 std::vector<TextBox> ParagraphImpl::getRectsForPlaceholders() {
1612 std::vector<TextBox> boxes;
1613 // this method should not be called before kshaped
1614 if (fText.isEmpty() || fState < kShaped) {
1615 return boxes;
1616 }
1617 if (fPlaceholders.size() == 1) {
1618 // We always have one fake placeholder
1619 return boxes;
1620 }
1621 for (auto& line : fLines) {
1622 line.getRectsForPlaceholders(boxes);
1623 }
1624 /*
1625 SkDebugf("getRectsForPlaceholders('%s'): %d\n", fText.c_str(), boxes.size());
1626 for (auto& r : boxes) {
1627 r.rect.fLeft = littleRound(r.rect.fLeft);
1628 r.rect.fRight = littleRound(r.rect.fRight);
1629 r.rect.fTop = littleRound(r.rect.fTop);
1630 r.rect.fBottom = littleRound(r.rect.fBottom);
1631 SkDebugf("[%f:%f * %f:%f] %s\n", r.rect.fLeft, r.rect.fRight, r.rect.fTop, r.rect.fBottom,
1632 (r.direction == TextDirection::kLtr ? "left" : "right"));
1633 }
1634 */
1635 return boxes;
1636 }
1637
1638 // TODO: Optimize (save cluster <-> codepoint connection)
1639 PositionWithAffinity ParagraphImpl::getGlyphPositionAtCoordinate(SkScalar dx, SkScalar dy) {
1640
1641 if (fText.isEmpty()) {
1642 return {0, Affinity::kDownstream};
1643 }
1644
1645 this->ensureUTF16Mapping();
1646
1647 for (auto& line : fLines) {
1648 // Let's figure out if we can stop looking
1649 auto offsetY = line.offset().fY;
1650 if (dy >= offsetY + line.height() && &line != &fLines.back()) {
1651 // This line is not good enough
1652 continue;
1653 }
1654
1655 // This is so far the the line vertically closest to our coordinates
1656 // (or the first one, or the only one - all the same)
1657
1658 auto result = line.getGlyphPositionAtCoordinate(dx);
1659 //SkDebugf("getGlyphPositionAtCoordinate(%f, %f): %d %s\n", dx, dy, result.position,
1660 // result.affinity == Affinity::kUpstream ? "up" : "down");
1661 return result;
1662 }
1663
1664 return {0, Affinity::kDownstream};
1665 }
1666
1667 // Finds the first and last glyphs that define a word containing
1668 // the glyph at index offset.
1669 // By "glyph" they mean a character index - indicated by Minikin's code
1670 SkRange<size_t> ParagraphImpl::getWordBoundary(unsigned offset) {
1671
1672 if (fWords.empty()) {
1673 if (!fUnicode->getWords(fText.c_str(), fText.size(), nullptr, &fWords)) {
1674 return {0, 0 };
1675 }
1676 }
1677
1678 int32_t start = 0;
1679 int32_t end = 0;
1680 for (size_t i = 0; i < fWords.size(); ++i) {
1681 auto word = fWords[i];
1682 if (word <= offset) {
1683 start = word;
1684 end = word;
1685 } else if (word > offset) {
1686 end = word;
1687 break;
1688 }
1689 }
1690
1691 //SkDebugf("getWordBoundary(%d): %d - %d\n", offset, start, end);
1692 return { SkToU32(start), SkToU32(end) };
1693 }
1694
1695 void ParagraphImpl::getLineMetrics(std::vector<LineMetrics>& metrics) {
1696 metrics.clear();
1697 for (auto& line : fLines) {
1698 metrics.emplace_back(line.getMetrics());
1699 }
1700 }
1701
1702 SkSpan<const char> ParagraphImpl::text(TextRange textRange) {
1703 SkASSERT(textRange.start <= fText.size() && textRange.end <= fText.size());
1704 auto start = fText.c_str() + textRange.start;
1705 return SkSpan<const char>(start, textRange.width());
1706 }
1707
1708 SkSpan<Cluster> ParagraphImpl::clusters(ClusterRange clusterRange) {
1709 SkASSERT(clusterRange.start < SkToSizeT(fClusters.size()) &&
1710 clusterRange.end <= SkToSizeT(fClusters.size()));
1711 return SkSpan<Cluster>(&fClusters[clusterRange.start], clusterRange.width());
1712 }
1713
1714 Cluster& ParagraphImpl::cluster(ClusterIndex clusterIndex) {
1715 SkASSERT(clusterIndex < SkToSizeT(fClusters.size()));
1716 return fClusters[clusterIndex];
1717 }
1718
1719 Run& ParagraphImpl::runByCluster(ClusterIndex clusterIndex) {
1720 auto start = cluster(clusterIndex);
1721 return this->run(start.fRunIndex);
1722 }
1723
1724 SkSpan<Block> ParagraphImpl::blocks(BlockRange blockRange) {
1725 SkASSERT(blockRange.start < SkToSizeT(fTextStyles.size()) &&
1726 blockRange.end <= SkToSizeT(fTextStyles.size()));
1727 return SkSpan<Block>(&fTextStyles[blockRange.start], blockRange.width());
1728 }
1729
1730 Block& ParagraphImpl::block(BlockIndex blockIndex) {
1731 SkASSERT(blockIndex < SkToSizeT(fTextStyles.size()));
1732 return fTextStyles[blockIndex];
1733 }
1734
1735 void ParagraphImpl::setState(InternalState state) {
1736 if (fState <= state) {
1737 fState = state;
1738 return;
1739 }
1740
1741 fState = state;
1742 switch (fState) {
1743 case kUnknown:
1744 SkASSERT(false);
1745 /*
1746 // The text is immutable and so are all the text indexing properties
1747 // taken from SkUnicode
1748 fCodeUnitProperties.reset();
1749 fWords.clear();
1750 fBidiRegions.clear();
1751 fUTF8IndexForUTF16Index.reset();
1752 fUTF16IndexForUTF8Index.reset();
1753 */
1754 [[fallthrough]];
1755
1756 case kIndexed:
1757 fRuns.reset();
1758 fClusters.reset();
1759 [[fallthrough]];
1760
1761 case kShaped:
1762 fLines.reset();
1763 [[fallthrough]];
1764
1765 case kLineBroken:
1766 fPicture = nullptr;
1767 [[fallthrough]];
1768
1769 default:
1770 break;
1771 }
1772 }
1773
1774 void ParagraphImpl::computeEmptyMetrics() {
1775
1776 // The empty metrics is used to define the height of the empty lines
1777 // Unfortunately, Flutter has 2 different cases for that:
1778 // 1. An empty line inside the text
1779 // 2. An empty paragraph
1780 // In the first case SkParagraph takes the metrics from the default paragraph style
1781 // In the second case it should take it from the current text style
1782 bool emptyParagraph = fRuns.empty();
1783 TextStyle textStyle = paragraphStyle().getTextStyle();
1784 if (emptyParagraph && !fTextStyles.empty()) {
1785 textStyle = fTextStyles.back().fStyle;
1786 }
1787
1788 auto typefaces = fontCollection()->findTypefaces(
1789 textStyle.getFontFamilies(), textStyle.getFontStyle(), textStyle.getFontArguments());
1790 auto typeface = typefaces.empty() ? nullptr : typefaces.front();
1791
1792 #ifndef USE_SKIA_TXT
1793 SkFont font(typeface, textStyle.getFontSize());
1794 #else
1795 RSFont font(typeface, textStyle.getFontSize(), 1, 0);
1796 #endif
1797 fEmptyMetrics = InternalLineMetrics(font, paragraphStyle().getStrutStyle().getForceStrutHeight());
1798
1799 if (!paragraphStyle().getStrutStyle().getForceStrutHeight() &&
1800 textStyle.getHeightOverride()) {
1801 #ifdef OHOS_SUPPORT
1802 const auto intrinsicHeight = fEmptyMetrics.fDescent - fEmptyMetrics.fAscent + fEmptyMetrics.fLeading;
1803 #else
1804 const auto intrinsicHeight = fEmptyMetrics.height();
1805 #endif
1806 const auto strutHeight = textStyle.getHeight() * textStyle.getFontSize();
1807 if (paragraphStyle().getStrutStyle().getHalfLeading()) {
1808 fEmptyMetrics.update(
1809 fEmptyMetrics.ascent(),
1810 fEmptyMetrics.descent(),
1811 fEmptyMetrics.leading() + strutHeight - intrinsicHeight);
1812 } else {
1813 const auto multiplier = strutHeight / intrinsicHeight;
1814 fEmptyMetrics.update(
1815 fEmptyMetrics.ascent() * multiplier,
1816 fEmptyMetrics.descent() * multiplier,
1817 fEmptyMetrics.leading() * multiplier);
1818 }
1819 }
1820
1821 if (emptyParagraph) {
1822 // For an empty text we apply both TextHeightBehaviour flags
1823 // In case of non-empty paragraph TextHeightBehaviour flags will be applied at the appropriate place
1824 // We have to do it here because we skip wrapping for an empty text
1825 auto disableFirstAscent = (paragraphStyle().getTextHeightBehavior() & TextHeightBehavior::kDisableFirstAscent) == TextHeightBehavior::kDisableFirstAscent;
1826 auto disableLastDescent = (paragraphStyle().getTextHeightBehavior() & TextHeightBehavior::kDisableLastDescent) == TextHeightBehavior::kDisableLastDescent;
1827 fEmptyMetrics.update(
1828 disableFirstAscent ? fEmptyMetrics.rawAscent() : fEmptyMetrics.ascent(),
1829 disableLastDescent ? fEmptyMetrics.rawDescent() : fEmptyMetrics.descent(),
1830 fEmptyMetrics.leading());
1831 }
1832
1833 if (fParagraphStyle.getStrutStyle().getStrutEnabled()) {
1834 fStrutMetrics.updateLineMetrics(fEmptyMetrics);
1835 }
1836 }
1837
1838 SkString ParagraphImpl::getEllipsis() const {
1839
1840 auto ellipsis8 = fParagraphStyle.getEllipsis();
1841 auto ellipsis16 = fParagraphStyle.getEllipsisUtf16();
1842 if (!ellipsis8.isEmpty()) {
1843 return ellipsis8;
1844 } else {
1845 return SkUnicode::convertUtf16ToUtf8(fParagraphStyle.getEllipsisUtf16());
1846 }
1847 }
1848
1849 WordBreakType ParagraphImpl::getWordBreakType() const {
1850 return fParagraphStyle.getStrutStyle().getWordBreakType();
1851 }
1852
1853 LineBreakStrategy ParagraphImpl::getLineBreakStrategy() const {
1854 return fParagraphStyle.getStrutStyle().getLineBreakStrategy();
1855 }
1856
1857 void ParagraphImpl::updateFontSize(size_t from, size_t to, SkScalar fontSize) {
1858
1859 SkASSERT(from == 0 && to == fText.size());
1860 auto defaultStyle = fParagraphStyle.getTextStyle();
1861 defaultStyle.setFontSize(fontSize);
1862 fParagraphStyle.setTextStyle(defaultStyle);
1863
1864 for (auto& textStyle : fTextStyles) {
1865 textStyle.fStyle.setFontSize(fontSize);
1866 }
1867
1868 fState = std::min(fState, kIndexed);
1869 fOldWidth = 0;
1870 fOldHeight = 0;
1871 }
1872
1873 void ParagraphImpl::updateTextAlign(TextAlign textAlign) {
1874 fParagraphStyle.setTextAlign(textAlign);
1875
1876 if (fState >= kLineBroken) {
1877 fState = kLineBroken;
1878 }
1879 }
1880
1881 void ParagraphImpl::updateForegroundPaint(size_t from, size_t to, SkPaint paint) {
1882 SkASSERT(from == 0 && to == fText.size());
1883 auto defaultStyle = fParagraphStyle.getTextStyle();
1884 defaultStyle.setForegroundColor(paint);
1885 fParagraphStyle.setTextStyle(defaultStyle);
1886
1887 for (auto& textStyle : fTextStyles) {
1888 textStyle.fStyle.setForegroundColor(paint);
1889 }
1890 }
1891
1892 void ParagraphImpl::updateBackgroundPaint(size_t from, size_t to, SkPaint paint) {
1893 SkASSERT(from == 0 && to == fText.size());
1894 auto defaultStyle = fParagraphStyle.getTextStyle();
1895 defaultStyle.setBackgroundColor(paint);
1896 fParagraphStyle.setTextStyle(defaultStyle);
1897
1898 for (auto& textStyle : fTextStyles) {
1899 textStyle.fStyle.setBackgroundColor(paint);
1900 }
1901 }
1902
1903 #ifdef OHOS_SUPPORT
1904 ParagraphPainter::PaintID ParagraphImpl::updateTextStyleColorAndForeground(TextStyle& textStyle, SkColor color)
1905 {
1906 textStyle.setColor(color);
1907 if (textStyle.hasForeground()) {
1908 auto paintOrID = textStyle.getForegroundPaintOrID();
1909 SkPaint* paint = std::get_if<SkPaint>(&paintOrID);
1910 if (paint) {
1911 paint->setColor(color);
1912 textStyle.setForegroundPaint(*paint);
1913 } else {
1914 auto paintID = std::get_if<ParagraphPainter::PaintID>(&paintOrID);
1915 if (paintID) {
1916 return *paintID;
1917 }
1918 }
1919 }
1920 return INVALID_PAINT_ID;
1921 }
1922
1923 std::vector<ParagraphPainter::PaintID> ParagraphImpl::updateColor(size_t from, size_t to, SkColor color,
1924 UtfEncodeType encodeType) {
1925 std::vector<ParagraphPainter::PaintID> unresolvedPaintID;
1926 if (from >= to) {
1927 return unresolvedPaintID;
1928 }
1929 this->ensureUTF16Mapping();
1930 if (encodeType == UtfEncodeType::kUtf8) {
1931 from = (from < SkToSizeT(fUTF8IndexForUTF16Index.size())) ? fUTF8IndexForUTF16Index[from] : fText.size();
1932 to = (to < SkToSizeT(fUTF8IndexForUTF16Index.size())) ? fUTF8IndexForUTF16Index[to] : fText.size();
1933 }
1934 if (from == 0 && to == fText.size()) {
1935 auto defaultStyle = fParagraphStyle.getTextStyle();
1936 auto paintID = updateTextStyleColorAndForeground(defaultStyle, color);
1937 if (paintID != INVALID_PAINT_ID) {
1938 unresolvedPaintID.emplace_back(paintID);
1939 }
1940 fParagraphStyle.setTextStyle(defaultStyle);
1941 }
1942
1943 for (auto& textStyle : fTextStyles) {
1944 auto& fRange = textStyle.fRange;
1945 if (to < fRange.end) {
1946 break;
1947 }
1948 if (from > fRange.start) {
1949 continue;
1950 }
1951 auto paintID = updateTextStyleColorAndForeground(textStyle.fStyle, color);
1952 if (paintID != INVALID_PAINT_ID) {
1953 unresolvedPaintID.emplace_back(paintID);
1954 }
1955 }
1956 for (auto& line : fLines) {
1957 line.setTextBlobCachePopulated(false);
1958 }
1959 return unresolvedPaintID;
1960 }
1961
1962 bool ParagraphImpl::isAutoSpaceEnabled() const
1963 {
1964 return paragraphStyle().getEnableAutoSpace() || TextParameter::GetAutoSpacingEnable();
1965 }
1966
1967 SkScalar ParagraphImpl::clusterUsingAutoSpaceWidth(const Cluster& cluster) const
1968 {
1969 if(!isAutoSpaceEnabled()){
1970 return cluster.width();
1971 }
1972 Run& run = cluster.run();
1973 size_t start = cluster.startPos();
1974 size_t end = cluster.endPos();
1975 float correction = 0.0f;
1976 if (end > start && !run.getAutoSpacings().empty()) {
1977 correction = run.getAutoSpacings()[end - 1].fX - run.getAutoSpacings()[start].fY;
1978 }
1979
1980 return cluster.width() + std::max(0.0f, correction);
1981 }
1982
1983 bool ParagraphImpl::preCalculateSingleRunAutoSpaceWidth(SkScalar floorWidth)
1984 {
1985 SkScalar singleRunWidth = fRuns[0].fAdvance.fX;
1986 if (!isAutoSpaceEnabled()) {
1987 return singleRunWidth <= floorWidth - this->detectIndents(0);
1988 }
1989 SkScalar totalFakeSpacing = 0.0f;
1990 ClusterIndex endOfClusters = fClusters.size();
1991 for (size_t cluster = 1; cluster < endOfClusters; ++cluster) {
1992 totalFakeSpacing += (fClusters[cluster].needAutoSpacing())
1993 ? fClusters[cluster - 1].getFontSize() / AUTO_SPACING_WIDTH_RATIO : 0;
1994 }
1995 singleRunWidth += totalFakeSpacing;
1996 return singleRunWidth <= floorWidth - this->detectIndents(0);
1997 }
1998
1999 std::vector<TextBlobRecordInfo> ParagraphImpl::getTextBlobRecordInfo()
2000 {
2001 std::vector<TextBlobRecordInfo> textBlobRecordInfos;
2002 for (auto& line : fLines) {
2003 for (auto& block : line.fTextBlobCache) {
2004 TextBlobRecordInfo recordInfo;
2005 recordInfo.fBlob = block.fBlob;
2006 recordInfo.fOffset = block.fOffset;
2007 recordInfo.fPaint = block.fPaint;
2008 textBlobRecordInfos.emplace_back(recordInfo);
2009 }
2010 }
2011 return textBlobRecordInfos;
2012 }
2013
2014 bool ParagraphImpl::canPaintAllText() const
2015 {
2016 for (auto& line : fLines) {
2017 if (line.ellipsis() != nullptr) {
2018 return false;
2019 }
2020 }
2021 return !fExceededMaxLines;
2022 }
2023 #endif
2024
2025 SkTArray<TextIndex> ParagraphImpl::countSurroundingGraphemes(TextRange textRange) const {
2026 textRange = textRange.intersection({0, fText.size()});
2027 SkTArray<TextIndex> graphemes;
2028 if ((fCodeUnitProperties[textRange.start] & SkUnicode::CodeUnitFlags::kGraphemeStart) == 0) {
2029 // Count the previous partial grapheme
2030 graphemes.emplace_back(textRange.start);
2031 }
2032 for (auto index = textRange.start; index < textRange.end; ++index) {
2033 if ((fCodeUnitProperties[index] & SkUnicode::CodeUnitFlags::kGraphemeStart) != 0) {
2034 graphemes.emplace_back(index);
2035 }
2036 }
2037 return graphemes;
2038 }
2039
2040 TextIndex ParagraphImpl::findPreviousGraphemeBoundary(TextIndex utf8) const {
2041 while (utf8 > 0 &&
2042 (fCodeUnitProperties[utf8] & SkUnicode::CodeUnitFlags::kGraphemeStart) == 0) {
2043 --utf8;
2044 }
2045 return utf8;
2046 }
2047
2048 TextIndex ParagraphImpl::findNextGraphemeBoundary(TextIndex utf8) const {
2049 while (utf8 < fText.size() &&
2050 (fCodeUnitProperties[utf8] & SkUnicode::CodeUnitFlags::kGraphemeStart) == 0) {
2051 ++utf8;
2052 }
2053 return utf8;
2054 }
2055
2056 TextIndex ParagraphImpl::findNextGlyphClusterBoundary(TextIndex utf8) const {
2057 while (utf8 < fText.size() &&
2058 (fCodeUnitProperties[utf8] & SkUnicode::CodeUnitFlags::kGlyphClusterStart) == 0) {
2059 ++utf8;
2060 }
2061 return utf8;
2062 }
2063
2064 TextIndex ParagraphImpl::findPreviousGlyphClusterBoundary(TextIndex utf8) const {
2065 while (utf8 > 0 &&
2066 (fCodeUnitProperties[utf8] & SkUnicode::CodeUnitFlags::kGlyphClusterStart) == 0) {
2067 --utf8;
2068 }
2069 return utf8;
2070 }
2071
2072 void ParagraphImpl::ensureUTF16Mapping() {
2073 fillUTF16MappingOnce([&] {
2074 fUnicode->extractUtfConversionMapping(
2075 this->text(),
2076 [&](size_t index) { fUTF8IndexForUTF16Index.emplace_back(index); },
2077 [&](size_t index) { fUTF16IndexForUTF8Index.emplace_back(index); });
2078 });
2079 }
2080
2081 void ParagraphImpl::visit(const Visitor& visitor) {
2082 #ifndef USE_SKIA_TXT
2083 int lineNumber = 0;
2084 for (auto& line : fLines) {
2085 line.ensureTextBlobCachePopulated();
2086 for (auto& rec : line.fTextBlobCache) {
2087 SkTextBlob::Iter iter(*rec.fBlob);
2088 SkTextBlob::Iter::ExperimentalRun run;
2089
2090 SkSTArray<128, uint32_t> clusterStorage;
2091 const Run* R = rec.fVisitor_Run;
2092 const uint32_t* clusterPtr = &R->fClusterIndexes[0];
2093
2094 if (R->fClusterStart > 0) {
2095 int count = R->fClusterIndexes.size();
2096 clusterStorage.reset(count);
2097 for (int i = 0; i < count; ++i) {
2098 clusterStorage[i] = R->fClusterStart + R->fClusterIndexes[i];
2099 }
2100 clusterPtr = &clusterStorage[0];
2101 }
2102 clusterPtr += rec.fVisitor_Pos;
2103
2104 while (iter.experimentalNext(&run)) {
2105 const Paragraph::VisitorInfo info = {
2106 run.font,
2107 rec.fOffset,
2108 rec.fClipRect.fRight,
2109 run.count,
2110 run.glyphs,
2111 run.positions,
2112 clusterPtr,
2113 0, // flags
2114 };
2115 visitor(lineNumber, &info);
2116 clusterPtr += run.count;
2117 }
2118 }
2119 visitor(lineNumber, nullptr); // signal end of line
2120 lineNumber += 1;
2121 }
2122 #endif
2123 }
2124
2125 int ParagraphImpl::getLineNumberAt(TextIndex codeUnitIndex) const {
2126 for (size_t i = 0; i < fLines.size(); ++i) {
2127 auto& line = fLines[i];
2128 if (line.text().contains({codeUnitIndex, codeUnitIndex + 1})) {
2129 return i;
2130 }
2131 }
2132 return -1;
2133 }
2134
2135 bool ParagraphImpl::getLineMetricsAt(int lineNumber, LineMetrics* lineMetrics) const {
2136 if (lineNumber < 0 || static_cast<size_t>(lineNumber) >= fLines.size()) {
2137 return false;
2138 }
2139 auto& line = fLines[lineNumber];
2140 if (lineMetrics) {
2141 *lineMetrics = line.getMetrics();
2142 }
2143 return true;
2144 }
2145
2146 TextRange ParagraphImpl::getActualTextRange(int lineNumber, bool includeSpaces) const {
2147 if (lineNumber < 0 || static_cast<size_t>(lineNumber) >= fLines.size()) {
2148 return EMPTY_TEXT;
2149 }
2150 auto& line = fLines[lineNumber];
2151 return includeSpaces ? line.text() : line.trimmedText();
2152 }
2153
2154 bool ParagraphImpl::getGlyphClusterAt(TextIndex codeUnitIndex, GlyphClusterInfo* glyphInfo) {
2155 for (size_t i = 0; i < fLines.size(); ++i) {
2156 auto& line = fLines[i];
2157 if (!line.text().contains({codeUnitIndex, codeUnitIndex})) {
2158 continue;
2159 }
2160 for (auto c = line.clustersWithSpaces().start; c < line.clustersWithSpaces().end; ++c) {
2161 auto& cluster = fClusters[c];
2162 if (cluster.contains(codeUnitIndex)) {
2163 std::vector<TextBox> boxes;
2164 line.getRectsForRange(cluster.textRange(),
2165 RectHeightStyle::kTight,
2166 RectWidthStyle::kTight,
2167 boxes);
2168 if (boxes.size() > 0) {
2169 if (glyphInfo) {
2170 *glyphInfo = {boxes[0].rect, cluster.textRange(), boxes[0].direction};
2171 }
2172 return true;
2173 }
2174 }
2175 }
2176 return false;
2177 }
2178 return false;
2179 }
2180
2181 bool ParagraphImpl::getClosestGlyphClusterAt(SkScalar dx,
2182 SkScalar dy,
2183 GlyphClusterInfo* glyphInfo) {
2184 auto res = this->getGlyphPositionAtCoordinate(dx, dy);
2185 auto textIndex = res.position + (res.affinity == Affinity::kDownstream ? 0 : 1);
2186 GlyphClusterInfo gci;
2187 if (this->getGlyphClusterAt(textIndex, glyphInfo ? glyphInfo : &gci)) {
2188 return true;
2189 } else {
2190 return false;
2191 }
2192 }
2193
2194 #ifndef USE_SKIA_TXT
2195 SkFont ParagraphImpl::getFontAt(TextIndex codeUnitIndex) const {
2196 for (auto& run : fRuns) {
2197 if (run.textRange().contains({codeUnitIndex, codeUnitIndex})) {
2198 return run.font();
2199 }
2200 }
2201 return SkFont();
2202 }
2203 #else
2204 RSFont ParagraphImpl::getFontAt(TextIndex codeUnitIndex) const
2205 {
2206 for (auto& run : fRuns) {
2207 if (run.textRange().contains({codeUnitIndex, codeUnitIndex})) {
2208 return run.font();
2209 }
2210 }
2211 return RSFont();
2212 }
2213 #endif
2214
2215 std::vector<Paragraph::FontInfo> ParagraphImpl::getFonts() const {
2216 std::vector<FontInfo> results;
2217 for (auto& run : fRuns) {
2218 results.emplace_back(run.font(), run.textRange());
2219 }
2220 return results;
2221 }
2222
2223 #ifndef USE_SKIA_TXT
2224 SkFontMetrics ParagraphImpl::measureText() {
2225 SkFontMetrics metrics;
2226 if (fRuns.empty()) {
2227 return metrics;
2228 }
2229
2230 const auto& firstFont = fRuns.front().font();
2231 SkRect firstBounds;
2232 auto firstStr = text(fRuns.front().textRange());
2233 firstFont.getMetrics(&metrics);
2234 #ifdef OHOS_SUPPORT
2235 auto decompressFont = firstFont;
2236 scaleFontWithCompressionConfig(decompressFont, ScaleOP::DECOMPRESS);
2237 metricsIncludeFontPadding(&metrics, decompressFont);
2238 #endif
2239 firstFont.measureText(firstStr.data(), firstStr.size(), SkTextEncoding::kUTF8, &firstBounds, nullptr);
2240 fGlyphsBoundsTop = firstBounds.top();
2241 fGlyphsBoundsBottom = firstBounds.bottom();
2242 fGlyphsBoundsLeft = firstBounds.left();
2243 SkScalar realWidth = 0;
2244 for (size_t i = 0; i < fRuns.size(); ++i) {
2245 auto run = fRuns[i];
2246 const auto& font = run.font();
2247 SkRect bounds;
2248 auto str = text(run.textRange());
2249 auto advance = font.measureText(str.data(), str.size(), SkTextEncoding::kUTF8, &bounds, nullptr);
2250 realWidth += advance;
2251 if (i == 0) {
2252 realWidth -= ((advance - (bounds.right() - bounds.left())) / 2);
2253 }
2254 if (i == (fRuns.size() - 1)) {
2255 realWidth -= ((advance - (bounds.right() - bounds.left())) / 2);
2256 }
2257 fGlyphsBoundsTop = std::min(fGlyphsBoundsTop, bounds.top());
2258 fGlyphsBoundsBottom = std::max(fGlyphsBoundsBottom, bounds.bottom());
2259 }
2260 fGlyphsBoundsRight = realWidth + fGlyphsBoundsLeft;
2261 return metrics;
2262 }
2263 #else
2264 RSFontMetrics ParagraphImpl::measureText()
2265 {
2266 RSFontMetrics metrics;
2267 if (fRuns.empty()) {
2268 return metrics;
2269 }
2270
2271 auto& firstFont = const_cast<RSFont&>(fRuns.front().font());
2272 RSRect firstBounds;
2273 auto firstStr = text(fRuns.front().textRange());
2274 firstFont.GetMetrics(&metrics);
2275 #ifdef OHOS_SUPPORT
2276 auto decompressFont = firstFont;
2277 scaleFontWithCompressionConfig(decompressFont, ScaleOP::DECOMPRESS);
2278 metricsIncludeFontPadding(&metrics, decompressFont);
2279 #endif
2280 firstFont.MeasureText(firstStr.data(), firstStr.size(), RSDrawing::TextEncoding::UTF8, &firstBounds);
2281 fGlyphsBoundsTop = firstBounds.GetTop();
2282 fGlyphsBoundsBottom = firstBounds.GetBottom();
2283 fGlyphsBoundsLeft = firstBounds.GetLeft();
2284 float realWidth = 0;
2285 for (size_t i = 0; i < fRuns.size(); ++i) {
2286 auto run = fRuns[i];
2287 auto& font = const_cast<RSFont&>(run.font());
2288 RSRect bounds;
2289 auto str = text(run.textRange());
2290 auto advance = font.MeasureText(str.data(), str.size(), RSDrawing::TextEncoding::UTF8, &bounds);
2291 realWidth += advance;
2292 if (i == 0) {
2293 realWidth -= ((advance - (bounds.GetRight() - bounds.GetLeft())) / 2);
2294 }
2295 if (i == (fRuns.size() - 1)) {
2296 realWidth -= ((advance - (bounds.GetRight() - bounds.GetLeft())) / 2);
2297 }
2298 fGlyphsBoundsTop = std::min(fGlyphsBoundsTop, bounds.GetTop());
2299 fGlyphsBoundsBottom = std::max(fGlyphsBoundsBottom, bounds.GetBottom());
2300 }
2301 fGlyphsBoundsRight = realWidth + fGlyphsBoundsLeft;
2302 return metrics;
2303 }
2304 #endif
2305
2306 std::vector<std::unique_ptr<TextLineBase>> ParagraphImpl::GetTextLines() {
2307 std::vector<std::unique_ptr<TextLineBase>> textLineBases;
2308 for (auto& line: fLines) {
2309 #ifdef OHOS_SUPPORT
2310 std::unique_ptr<TextLineBaseImpl> textLineBaseImplPtr =
2311 std::make_unique<TextLineBaseImpl>(std::make_unique<TextLine>(std::move(line)));
2312 #else
2313 std::unique_ptr<TextLineBaseImpl> textLineBaseImplPtr = std::make_unique<TextLineBaseImpl>(&line);
2314 #endif
2315 textLineBases.emplace_back(std::move(textLineBaseImplPtr));
2316 }
2317
2318 return textLineBases;
2319 }
2320
2321 #ifdef OHOS_SUPPORT
2322 size_t ParagraphImpl::prefixByteCountUntilChar(size_t index) {
2323 convertUtf8ToUnicode(fText);
2324 if (fUnicodeIndexForUTF8Index.empty()) {
2325 return std::numeric_limits<size_t>::max();
2326 }
2327 auto it = std::lower_bound(fUnicodeIndexForUTF8Index.begin(), fUnicodeIndexForUTF8Index.end(), index);
2328 if (it != fUnicodeIndexForUTF8Index.end() && *it == index) {
2329 return std::distance(fUnicodeIndexForUTF8Index.begin(), it);
2330 } else {
2331 return std::numeric_limits<size_t>::max();
2332 }
2333 }
2334
2335 void ParagraphImpl::copyProperties(const ParagraphImpl& source) {
2336 fText = source.fText;
2337 fTextStyles = source.fTextStyles;
2338 fPlaceholders = source.fPlaceholders;
2339 fParagraphStyle = source.fParagraphStyle;
2340 fFontCollection = source.fFontCollection;
2341 fUnicode = source.fUnicode;
2342
2343 fState = kUnknown;
2344 fUnresolvedGlyphs = 0;
2345 fPicture = nullptr;
2346 fStrutMetrics = false;
2347 fOldWidth = 0;
2348 fOldHeight = 0;
2349 fHasLineBreaks = false;
2350 fHasWhitespacesInside = false;
2351 fTrailingSpaces = 0;
2352 }
2353
2354 std::unique_ptr<Paragraph> ParagraphImpl::createCroppedCopy(size_t startIndex, size_t count) {
2355 std::unique_ptr<ParagraphImpl> paragraph = std::make_unique<ParagraphImpl>();
2356 paragraph->copyProperties(*this);
2357
2358 // change range
2359 auto validStart = prefixByteCountUntilChar(startIndex);
2360 if (validStart == std::numeric_limits<size_t>::max()) {
2361 return nullptr;
2362 }
2363 // For example, when the clipped string str1 is "123456789"
2364 // startIndex=2, count=std:: numeric_imits<size_t>:: max(), the resulting string str2 is "3456789".
2365 // When startIndex=3 and count=3, crop the generated string str3 to "456"
2366 TextRange firstDeleteRange(0, validStart);
2367 paragraph->fText.remove(0, validStart);
2368 paragraph->resetTextStyleRange(firstDeleteRange);
2369 paragraph->resetPlaceholderRange(firstDeleteRange);
2370
2371 if (count != std::numeric_limits<size_t>::max()) {
2372 auto invalidStart = paragraph->prefixByteCountUntilChar(count);
2373 if (invalidStart == std::numeric_limits<size_t>::max()) {
2374 return nullptr;
2375 }
2376 auto invalidEnd = paragraph->fText.size();
2377 TextRange secodeDeleteRange(invalidStart, invalidEnd);
2378 paragraph->fText.remove(invalidStart, invalidEnd - invalidStart);
2379 paragraph->resetTextStyleRange(secodeDeleteRange);
2380 paragraph->resetPlaceholderRange(secodeDeleteRange);
2381 }
2382 return paragraph;
2383 }
2384
2385 void ParagraphImpl::initUnicodeText() {
2386 this->fUnicodeText = convertUtf8ToUnicode(fText);
2387 }
2388
2389 // Currently, only support to generate text and text shadow paint regions.
2390 // Can't accurately calculate the paint region of italic fonts(including fake italic).
2391 SkIRect ParagraphImpl::generatePaintRegion(SkScalar x, SkScalar y) {
2392 if (fState < kFormatted) {
2393 TEXT_LOGW("Call generatePaintRegion when paragraph is not formatted");
2394 return SkIRect::MakeXYWH(x, y, 0, 0);
2395 }
2396
2397 if (fPaintRegion.has_value()) {
2398 return fPaintRegion.value().makeOffset(x, y).roundOut();
2399 }
2400
2401 fPaintRegion = SkRect::MakeEmpty();
2402 for (auto& line : fLines) {
2403 SkRect linePaintRegion = line.generatePaintRegion(0, 0);
2404 fPaintRegion->join(linePaintRegion);
2405 }
2406 return fPaintRegion.value().makeOffset(x, y).roundOut();
2407 }
2408 #endif
2409
2410 std::unique_ptr<Paragraph> ParagraphImpl::CloneSelf()
2411 {
2412 std::unique_ptr<ParagraphImpl> paragraph = std::make_unique<ParagraphImpl>();
2413
2414 paragraph->fFontCollection = this->fFontCollection;
2415 paragraph->fParagraphStyle = this->fParagraphStyle;
2416 paragraph->fAlphabeticBaseline = this->fAlphabeticBaseline;
2417 paragraph->fIdeographicBaseline = this->fIdeographicBaseline;
2418 paragraph->fGlyphsBoundsTop = this->fGlyphsBoundsTop;
2419 paragraph->fGlyphsBoundsBottom = this->fGlyphsBoundsBottom;
2420 paragraph->fGlyphsBoundsLeft = this->fGlyphsBoundsLeft;
2421 paragraph->fGlyphsBoundsRight = this->fGlyphsBoundsRight;
2422 paragraph->fHeight = this->fHeight;
2423 paragraph->fWidth = this->fWidth;
2424 paragraph->fMaxIntrinsicWidth = this->fMaxIntrinsicWidth;
2425 paragraph->fMinIntrinsicWidth = this->fMinIntrinsicWidth;
2426 paragraph->fLongestLine = this->fLongestLine;
2427 #ifdef OHOS_SUPPORT
2428 paragraph->fLongestLineWithIndent = this->fLongestLineWithIndent;
2429 #endif
2430 paragraph->fExceededMaxLines = this->fExceededMaxLines;
2431
2432 paragraph->fLetterSpaceStyles = this->fLetterSpaceStyles;
2433 paragraph->fWordSpaceStyles = this->fWordSpaceStyles;
2434 paragraph->fBackgroundStyles = this->fBackgroundStyles;
2435 paragraph->fForegroundStyles = this->fForegroundStyles;
2436 paragraph->fShadowStyles = this->fShadowStyles;
2437 paragraph->fDecorationStyles = this->fDecorationStyles;
2438 paragraph->fTextStyles = this->fTextStyles;
2439 paragraph->fPlaceholders = this->fPlaceholders;
2440 paragraph->fText = this->fText;
2441
2442 paragraph->fState = this->fState;
2443 paragraph->fRuns = this->fRuns;
2444 paragraph->fClusters = this->fClusters;
2445 paragraph->fCodeUnitProperties = this->fCodeUnitProperties;
2446 paragraph->fClustersIndexFromCodeUnit = this->fClustersIndexFromCodeUnit;
2447
2448 paragraph->fWords = this->fWords;
2449 paragraph->fIndents = this->fIndents;
2450 paragraph->fBidiRegions = this->fBidiRegions;
2451
2452 paragraph->fUTF8IndexForUTF16Index = this->fUTF8IndexForUTF16Index;
2453 paragraph->fUTF16IndexForUTF8Index = this->fUTF16IndexForUTF8Index;
2454 paragraph->fUnresolvedGlyphs = this->fUnresolvedGlyphs;
2455 paragraph->fUnresolvedCodepoints = this->fUnresolvedCodepoints;
2456
2457 for (auto& line : this->fLines) {
2458 paragraph->fLines.emplace_back(line.CloneSelf());
2459 }
2460
2461 paragraph->fPicture = this->fPicture;
2462 paragraph->fFontSwitches = this->fFontSwitches;
2463 paragraph->fEmptyMetrics = this->fEmptyMetrics;
2464 paragraph->fStrutMetrics = this->fStrutMetrics;
2465
2466 paragraph->fOldWidth = this->fOldWidth;
2467 paragraph->fOldHeight = this->fOldHeight;
2468 paragraph->fMaxWidthWithTrailingSpaces = this->fMaxWidthWithTrailingSpaces;
2469
2470 paragraph->fUnicode = this->fUnicode;
2471 paragraph->fHasLineBreaks = this->fHasLineBreaks;
2472 paragraph->fHasWhitespacesInside = this->fHasWhitespacesInside;
2473 paragraph->fTrailingSpaces = this->fTrailingSpaces;
2474 paragraph->fLineNumber = this->fLineNumber;
2475 paragraph->fEllipsisRange = this->fEllipsisRange;
2476
2477 for (auto& run : paragraph->fRuns) {
2478 run.setOwner(paragraph.get());
2479 }
2480 for (auto& cluster : paragraph->fClusters) {
2481 cluster.setOwner(paragraph.get());
2482 }
2483 for (auto& line : paragraph->fLines) {
2484 line.setParagraphImpl(paragraph.get());
2485 }
2486 return paragraph;
2487 }
2488
2489 #ifdef OHOS_SUPPORT
2490 std::string_view ParagraphImpl::GetState() const
2491 {
2492 static std::unordered_map<InternalState, std::string_view> state = {
2493 {kUnknown, "Unknow"},
2494 {kIndexed, "Indexed"},
2495 {kShaped, "Shaped"},
2496 {kLineBroken, "LineBroken"},
2497 {kFormatted, "Formatted"},
2498 {kDrawn, "Drawn"},
2499 };
2500 return state[fState];
2501 }
2502
2503 std::string ParagraphImpl::GetDumpInfo() const
2504 {
2505 // 由于arkui那边要求dump信息只能一行,这里将应该换行的地方用逗号代替,方便定位时统一用工具替换
2506 std::ostringstream paragraphInfo;
2507 paragraphInfo << "This is paragraph dump info:,";
2508 paragraphInfo << "Text size: " << fText.size() << " fState: " << GetState()
2509 << " fSkipTextBlobDrawing: " << (fSkipTextBlobDrawing ? "true" : "false") << ",";
2510 uint32_t glyphSize = 0;
2511 uint32_t runIndex = 0;
2512 for (auto& run : fRuns) {
2513 paragraphInfo << "Run[" << runIndex << "]" << " glyph size: " << run.size()
2514 << " text range: [" << run.textRange().start << "-" << run.textRange().end << "),";
2515 runIndex++;
2516 glyphSize += run.size();
2517 }
2518 uint32_t blockIndex = 0;
2519 for (auto& block : fTextStyles) {
2520 paragraphInfo << "Block[" << blockIndex << "]"
2521 << " text range[" << block.fRange.start << "-"<< block.fRange.end << ")"
2522 << " font size: " << block.fStyle.getFontSize()
2523 << " font color: " << std::hex << block.fStyle.getColor() << std::dec
2524 << " font height: " << block.fStyle.getHeight()
2525 << " font weight: " << block.fStyle.getFontStyle().GetWeight()
2526 << " font width: " << block.fStyle.getFontStyle().GetWidth()
2527 << " font slant: " << block.fStyle.getFontStyle().GetSlant() << ",";
2528 blockIndex++;
2529 }
2530 paragraphInfo << "Paragraph glyph size: " << glyphSize << ",";
2531 uint32_t lineIndex = 0;
2532 for (auto& line : fLines) {
2533 auto runs = line.getLineAllRuns();
2534 auto runSize = runs.size();
2535 if (runSize !=0 ) {
2536 paragraphInfo << "Line[" << lineIndex << "] run range: [" << runs[0] << "-" << runs[runSize - 1] << "],";
2537 }
2538 lineIndex++;
2539 }
2540 return paragraphInfo.str();
2541 }
2542 #endif
2543
2544 } // namespace textlayout
2545 } // namespace skia
2546