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