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