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