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