• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // Copyright 2019 Google LLC.
2 
3 #include "include/core/SkCanvas.h"
4 #include "include/core/SkFontMetrics.h"
5 #include "include/core/SkMatrix.h"
6 #include "include/core/SkPictureRecorder.h"
7 #include "include/core/SkSpan.h"
8 #include "include/core/SkString.h"
9 #include "include/core/SkTypes.h"
10 #include "include/core/SkTypeface.h"
11 #include "include/private/SkTFitsIn.h"
12 #include "include/private/SkTo.h"
13 #include "modules/skparagraph/include/Metrics.h"
14 #include "modules/skparagraph/include/Paragraph.h"
15 #include "modules/skparagraph/include/ParagraphPainter.h"
16 #include "modules/skparagraph/include/ParagraphStyle.h"
17 #include "modules/skparagraph/include/TextStyle.h"
18 #include "modules/skparagraph/src/OneLineShaper.h"
19 #include "modules/skparagraph/src/ParagraphImpl.h"
20 #include "modules/skparagraph/src/ParagraphPainterImpl.h"
21 #include "modules/skparagraph/src/Run.h"
22 #include "modules/skparagraph/src/TextLine.h"
23 #include "modules/skparagraph/src/TextWrapper.h"
24 #include "src/utils/SkUTF.h"
25 #include <math.h>
26 #include <algorithm>
27 #include <utility>
28 
29 namespace skia {
30 namespace textlayout {
31 
32 namespace {
33 constexpr int PARAM_DOUBLE = 2;
34 
littleRound(SkScalar a)35 SkScalar littleRound(SkScalar a) {
36     // This rounding is done to match Flutter tests. Must be removed..
37     auto val = std::fabs(a);
38     if (val < 10000) {
39         return SkScalarRoundToScalar(a * 100.0)/100.0;
40     } else if (val < 100000) {
41         return SkScalarRoundToScalar(a * 10.0)/10.0;
42     } else {
43         return SkScalarFloorToScalar(a);
44     }
45 }
46 }  // namespace
47 
operator *(const TextRange & a,const TextRange & b)48 TextRange operator*(const TextRange& a, const TextRange& b) {
49     if (a.start == b.start && a.end == b.end) return a;
50     auto begin = std::max(a.start, b.start);
51     auto end = std::min(a.end, b.end);
52     return end > begin ? TextRange(begin, end) : EMPTY_TEXT;
53 }
54 
Paragraph(ParagraphStyle style,sk_sp<FontCollection> fonts)55 Paragraph::Paragraph(ParagraphStyle style, sk_sp<FontCollection> fonts)
56             : fFontCollection(std::move(fonts))
57             , fParagraphStyle(std::move(style))
58             , fAlphabeticBaseline(0)
59             , fIdeographicBaseline(0)
60             , fHeight(0)
61             , fWidth(0)
62             , fMaxIntrinsicWidth(0)
63             , fMinIntrinsicWidth(0)
64             , fLongestLine(0)
65             , fExceededMaxLines(0)
66 { }
67 
ParagraphImpl(const SkString & text,ParagraphStyle style,SkTArray<Block,true> blocks,SkTArray<Placeholder,true> placeholders,sk_sp<FontCollection> fonts,std::shared_ptr<SkUnicode> unicode)68 ParagraphImpl::ParagraphImpl(const SkString& text,
69                              ParagraphStyle style,
70                              SkTArray<Block, true> blocks,
71                              SkTArray<Placeholder, true> placeholders,
72                              sk_sp<FontCollection> fonts,
73                              std::shared_ptr<SkUnicode> unicode)
74         : Paragraph(std::move(style), std::move(fonts))
75         , fTextStyles(std::move(blocks))
76         , fPlaceholders(std::move(placeholders))
77         , fText(text)
78         , fState(kUnknown)
79         , fUnresolvedGlyphs(0)
80         , fPicture(nullptr)
81         , fStrutMetrics(false)
82         , fOldWidth(0)
83         , fOldHeight(0)
84         , fUnicode(std::move(unicode))
85         , fHasLineBreaks(false)
86         , fHasWhitespacesInside(false)
87         , fTrailingSpaces(0)
88 {
89     SkASSERT(fUnicode);
90 }
91 
ParagraphImpl(const std::u16string & utf16text,ParagraphStyle style,SkTArray<Block,true> blocks,SkTArray<Placeholder,true> placeholders,sk_sp<FontCollection> fonts,std::shared_ptr<SkUnicode> unicode)92 ParagraphImpl::ParagraphImpl(const std::u16string& utf16text,
93                              ParagraphStyle style,
94                              SkTArray<Block, true> blocks,
95                              SkTArray<Placeholder, true> placeholders,
96                              sk_sp<FontCollection> fonts,
97                              std::shared_ptr<SkUnicode> unicode)
98         : ParagraphImpl(SkString(),
99                         std::move(style),
100                         std::move(blocks),
101                         std::move(placeholders),
102                         std::move(fonts),
103                         std::move(unicode))
104 {
105     SkASSERT(fUnicode);
106     fText =  SkUnicode::convertUtf16ToUtf8(utf16text);
107 }
108 
109 ParagraphImpl::~ParagraphImpl() = default;
110 
unresolvedGlyphs()111 int32_t ParagraphImpl::unresolvedGlyphs() {
112     if (fState < kShaped) {
113         return -1;
114     }
115 
116     return fUnresolvedGlyphs;
117 }
118 
unresolvedCodepoints()119 std::unordered_set<SkUnichar> ParagraphImpl::unresolvedCodepoints() {
120     return fUnresolvedCodepoints;
121 }
122 
addUnresolvedCodepoints(TextRange textRange)123 void ParagraphImpl::addUnresolvedCodepoints(TextRange textRange) {
124     fUnicode->forEachCodepoint(
125         &fText[textRange.start], textRange.width(),
126         [&](SkUnichar unichar, int32_t start, int32_t end, int32_t count) {
127             fUnresolvedCodepoints.emplace(unichar);
128         }
129     );
130 }
131 
middleEllipsisDeal()132 void ParagraphImpl::middleEllipsisDeal()
133 {
134     isMiddleEllipsis = false;
135     const SkString& ell = this->getEllipsis();
136     const char *ellStr = ell.c_str();
137     size_t start = 0;
138     size_t end = 0;
139     if (fRuns.begin()->leftToRight()) {
140         if (ltrTextSize[0].phraseWidth >= fOldMaxWidth) {
141             fText.reset();
142             fText.set(ellStr);
143             end = 1;
144         } else {
145             scanTextCutPoint(ltrTextSize, start, end);
146             if (end) {
147                 fText.remove(ltrTextSize[start].charbegin, ltrTextSize[end].charOver - ltrTextSize[start].charbegin);
148                 fText.insert(ltrTextSize[start].charbegin, ellStr);
149             }
150         }
151         ltrTextSize.clear();
152     } else {
153         scanTextCutPoint(rtlTextSize, start, end);
154         if (end) {
155             fText.remove(rtlTextSize[start - 1].charbegin,
156                 rtlTextSize[end + PARAM_DOUBLE].charbegin - rtlTextSize[start - 1].charbegin);
157             fText.insert(rtlTextSize[start - 1].charbegin, ellStr);
158             rtlTextSize.clear();
159         }
160     }
161 
162     // end = 0 means the text does not exceed the width limit
163     if (end != 0) {
164         this->fBidiRegions.clear();
165         this->fCodeUnitProperties.reset();
166         this->fClustersIndexFromCodeUnit.reset();
167         this->computeCodeUnitProperties();
168         this->fRuns.reset();
169         this->fClusters.reset();
170         this->fClustersIndexFromCodeUnit.reset();
171         this->fClustersIndexFromCodeUnit.push_back_n(fText.size() + 1, EMPTY_INDEX);
172         this->shapeTextIntoEndlessLine();
173     }
174     this->resetContext();
175     this->resolveStrut();
176     this->computeEmptyMetrics();
177     this->fLines.reset();
178     this->breakShapedTextIntoLines(fOldMaxWidth);
179     this->fText.reset();
180 }
181 
scanRTLTextCutPoint(const std::vector<TextCutRecord> & rawTextSize,size_t & start,size_t & end)182 void ParagraphImpl::scanRTLTextCutPoint(const std::vector<TextCutRecord>& rawTextSize, size_t& start, size_t& end)
183 {
184     float measureWidth = runTimeEllipsisWidth;
185     size_t left = 0;
186     size_t right = rawTextSize.size() - 1;
187     while (left < rawTextSize.size() && measureWidth < fOldMaxWidth && left <= right) {
188         measureWidth += rawTextSize[left++].phraseWidth;
189         if (right > left && measureWidth < fOldMaxWidth) {
190             measureWidth += rawTextSize[right--].phraseWidth;
191         }
192     }
193 
194     if (right < left) {
195         right = left;
196     }
197 
198     if (measureWidth >= fOldMaxWidth || fParagraphStyle.getTextOverflower()) {
199         start = left;
200         end = right;
201     } else {
202         start = 0;
203         end = 0;
204     }
205 }
206 
scanLTRTextCutPoint(const std::vector<TextCutRecord> & rawTextSize,size_t & start,size_t & end)207 void ParagraphImpl::scanLTRTextCutPoint(const std::vector<TextCutRecord>& rawTextSize, size_t& start, size_t& end)
208 {
209     float measureWidth = runTimeEllipsisWidth;
210     size_t begin = 0;
211     size_t last = rawTextSize.size() - 1;
212     bool rightExit = false;
213     while (begin < last && !rightExit && measureWidth < fOldMaxWidth) {
214         measureWidth += rawTextSize[begin++].phraseWidth;
215         if (measureWidth > fOldMaxWidth) {
216             --begin;
217             break;
218         }
219         if (last > begin && measureWidth < fOldMaxWidth) {
220             measureWidth += rawTextSize[last--].phraseWidth;
221             if (measureWidth > fOldMaxWidth) {
222                 rightExit = true;
223                 ++last;
224             }
225         }
226     }
227 
228     if (measureWidth >= fOldMaxWidth || fParagraphStyle.getTextOverflower()) {
229         start = begin;
230         end = last;
231     } else {
232         start = 0;
233         end = 0;
234     }
235 }
236 
scanTextCutPoint(const std::vector<TextCutRecord> & rawTextSize,size_t & start,size_t & end)237 void ParagraphImpl::scanTextCutPoint(const std::vector<TextCutRecord>& rawTextSize, size_t& start, size_t& end)
238 {
239     if (allTextWidth <= fOldMaxWidth || !rawTextSize.size()) {
240         allTextWidth = 0;
241         return;
242     }
243 
244     if (fRuns.begin()->leftToRight()) {
245         scanLTRTextCutPoint(rawTextSize, start, end);
246     } else {
247         scanRTLTextCutPoint(rawTextSize, start, end);
248     }
249 }
250 
layout(SkScalar rawWidth)251 void ParagraphImpl::layout(SkScalar rawWidth) {
252     fLineNumber = 1;
253     // TODO: This rounding is done to match Flutter tests. Must be removed...
254     auto floorWidth = rawWidth;
255     LOGD("paragraph layout begin, addr = %{public}p, rawWidth = %{public}f, fText size = %{public}lu",
256         this, rawWidth, static_cast<unsigned long>(fText.size()));
257 
258     if (fParagraphStyle.getMaxLines() == 1 &&
259         fParagraphStyle.getEllipsisMod() == EllipsisModal::MIDDLE) {
260         fOldMaxWidth = rawWidth;
261         isMiddleEllipsis = true;
262     }
263     if (getApplyRoundingHack()) {
264         floorWidth = SkScalarFloorToScalar(floorWidth);
265     }
266 
267     if (fParagraphStyle.getMaxLines() == 0) {
268         fText.reset();
269     }
270 
271     if ((!SkScalarIsFinite(rawWidth) || fLongestLine <= floorWidth) &&
272         fState >= kLineBroken &&
273          fLines.size() == 1 && fLines.front().ellipsis() == nullptr) {
274         // Most common case: one line of text (and one line is never justified, so no cluster shifts)
275         // We cannot mark it as kLineBroken because the new width can be bigger than the old width
276         fWidth = floorWidth;
277         fState = kShaped;
278     } else if (fState >= kLineBroken && fOldWidth != floorWidth) {
279         // We can use the results from SkShaper but have to do EVERYTHING ELSE again
280         fState = kShaped;
281     } else {
282         // Nothing changed case: we can reuse the data from the last layout
283     }
284 
285     if (fState < kShaped) {
286         // Check if we have the text in the cache and don't need to shape it again
287         if (!fFontCollection->getParagraphCache()->findParagraph(this)) {
288             if (fState < kIndexed) {
289                 // This only happens once at the first layout; the text is immutable
290                 // and there is no reason to repeat it
291                 if (this->computeCodeUnitProperties()) {
292                     fState = kIndexed;
293                 }
294             }
295             this->fRuns.reset();
296             this->fClusters.reset();
297             this->fClustersIndexFromCodeUnit.reset();
298             this->fClustersIndexFromCodeUnit.push_back_n(fText.size() + 1, EMPTY_INDEX);
299             if (!this->shapeTextIntoEndlessLine()) {
300                 this->resetContext();
301                 // TODO: merge the two next calls - they always come together
302                 this->resolveStrut();
303                 this->computeEmptyMetrics();
304                 this->fLines.reset();
305 
306                 // Set the important values that are not zero
307                 fWidth = floorWidth;
308                 fHeight = fEmptyMetrics.height();
309                 if (fParagraphStyle.getStrutStyle().getStrutEnabled() &&
310                     fParagraphStyle.getStrutStyle().getForceStrutHeight()) {
311                     fHeight = fStrutMetrics.height();
312                 }
313                 if (fParagraphStyle.getMaxLines() == 0) {
314                     fHeight = 0;
315                 }
316                 fAlphabeticBaseline = fEmptyMetrics.alphabeticBaseline();
317                 fIdeographicBaseline = fEmptyMetrics.ideographicBaseline();
318                 fLongestLine = FLT_MIN - FLT_MAX;  // That is what flutter has
319                 fMinIntrinsicWidth = 0;
320                 fMaxIntrinsicWidth = 0;
321                 this->fOldWidth = floorWidth;
322                 this->fOldHeight = this->fHeight;
323                 LOGD("paragraph layout end, addr = %{public}p, shape failed, fHeight = %{public}f", this, fHeight);
324                 return;
325             } else {
326                 // Add the paragraph to the cache
327                 fFontCollection->getParagraphCache()->updateParagraph(this);
328             }
329         }
330         fState = kShaped;
331     }
332 
333     if (fParagraphStyle.getMaxLines() == 1 &&
334         fParagraphStyle.getEllipsisMod() == EllipsisModal::MIDDLE) {
335         middleEllipsisDeal();
336         fState = kLineBroken;
337     }
338 
339     if (fState == kShaped) {
340         this->resetContext();
341         this->resolveStrut();
342         this->computeEmptyMetrics();
343         this->fLines.reset();
344         this->breakShapedTextIntoLines(floorWidth);
345         fState = kLineBroken;
346     }
347 
348     if (fState == kLineBroken) {
349         // Build the picture lazily not until we actually have to paint (or never)
350         this->resetShifts();
351         this->formatLines(fWidth);
352         fState = kFormatted;
353     }
354 
355     if (fParagraphStyle.getMaxLines() == 0) {
356         fHeight = 0;
357     }
358 
359     this->fOldWidth = floorWidth;
360     this->fOldHeight = this->fHeight;
361 
362     if (getApplyRoundingHack()) {
363         // TODO: This rounding is done to match Flutter tests. Must be removed...
364         fMinIntrinsicWidth = littleRound(fMinIntrinsicWidth);
365         fMaxIntrinsicWidth = littleRound(fMaxIntrinsicWidth);
366     }
367 
368     // TODO: This is strictly Flutter thing. Must be factored out into some flutter code
369     if (fParagraphStyle.getMaxLines() == 1 ||
370         (fParagraphStyle.unlimited_lines() && fParagraphStyle.ellipsized())) {
371         fMinIntrinsicWidth = fMaxIntrinsicWidth;
372     }
373 
374     // TODO: Since min and max are calculated differently it's possible to get a rounding error
375     //  that would make min > max. Sort it out later, make it the same for now
376     if (fMaxIntrinsicWidth < fMinIntrinsicWidth) {
377         fMaxIntrinsicWidth = fMinIntrinsicWidth;
378     }
379 
380     fLineNumber = std::max(size_t(1), fLines.size());
381     //SkDebugf("layout('%s', %f): %f %f\n", fText.c_str(), rawWidth, fMinIntrinsicWidth, fMaxIntrinsicWidth);
382     LOGD("paragraph layout end, addr = %{public}p, fLineNumber = %{public}lu, fHeight = %{public}f",
383         this, static_cast<unsigned long>(fLineNumber), fHeight);
384 }
385 
paint(SkCanvas * canvas,SkScalar x,SkScalar y)386 void ParagraphImpl::paint(SkCanvas* canvas, SkScalar x, SkScalar y) {
387     CanvasParagraphPainter painter(canvas);
388     paint(&painter, x, y);
389 }
390 
paint(ParagraphPainter * painter,SkScalar x,SkScalar y)391 void ParagraphImpl::paint(ParagraphPainter* painter, SkScalar x, SkScalar y) {
392     LOGD("paragraph paint begin, addr = %{public}p", this);
393     for (auto& line : fLines) {
394         line.paint(painter, x, y);
395     }
396     LOGD("paragraph paint end, addr = %{public}p", this);
397 }
398 
resetContext()399 void ParagraphImpl::resetContext() {
400     fAlphabeticBaseline = 0;
401     fHeight = 0;
402     fWidth = 0;
403     fIdeographicBaseline = 0;
404     fMaxIntrinsicWidth = 0;
405     fMinIntrinsicWidth = 0;
406     fLongestLine = 0;
407     fMaxWidthWithTrailingSpaces = 0;
408     fExceededMaxLines = false;
409 }
410 
411 // shapeTextIntoEndlessLine is the thing that calls this method
computeCodeUnitProperties()412 bool ParagraphImpl::computeCodeUnitProperties() {
413 
414     if (nullptr == fUnicode) {
415         return false;
416     }
417 
418     // Get bidi regions
419     auto textDirection = fParagraphStyle.getTextDirection() == TextDirection::kLtr
420                               ? SkUnicode::TextDirection::kLTR
421                               : SkUnicode::TextDirection::kRTL;
422     if (!fUnicode->getBidiRegions(fText.c_str(), fText.size(), textDirection, &fBidiRegions)) {
423         return false;
424     }
425 
426     // Collect all spaces and some extra information
427     // (and also substitute \t with a space while we are at it)
428     if (!fUnicode->computeCodeUnitFlags(&fText[0],
429                                         fText.size(),
430                                         this->paragraphStyle().getReplaceTabCharacters(),
431                                         &fCodeUnitProperties)) {
432         return false;
433     }
434 
435     // Get some information about trailing spaces / hard line breaks
436     fTrailingSpaces = fText.size();
437     TextIndex firstWhitespace = EMPTY_INDEX;
438     for (int i = 0; i < fCodeUnitProperties.size(); ++i) {
439         auto flags = fCodeUnitProperties[i];
440         if (SkUnicode::isPartOfWhiteSpaceBreak(flags)) {
441             if (fTrailingSpaces  == fText.size()) {
442                 fTrailingSpaces = i;
443             }
444             if (firstWhitespace == EMPTY_INDEX) {
445                 firstWhitespace = i;
446             }
447         } else {
448             fTrailingSpaces = fText.size();
449         }
450         if (SkUnicode::isHardLineBreak(flags)) {
451             fHasLineBreaks = true;
452         }
453     }
454 
455     if (firstWhitespace < fTrailingSpaces) {
456         fHasWhitespacesInside = true;
457     }
458 
459     return true;
460 }
461 
is_ascii_7bit_space(int c)462 static bool is_ascii_7bit_space(int c) {
463     SkASSERT(c >= 0 && c <= 127);
464 
465     // Extracted from https://en.wikipedia.org/wiki/Whitespace_character
466     //
467     enum WS {
468         kHT    = 9,
469         kLF    = 10,
470         kVT    = 11,
471         kFF    = 12,
472         kCR    = 13,
473         kSP    = 32,    // too big to use as shift
474     };
475 #define M(shift)    (1 << (shift))
476     constexpr uint32_t kSpaceMask = M(kHT) | M(kLF) | M(kVT) | M(kFF) | M(kCR);
477     // we check for Space (32) explicitly, since it is too large to shift
478     return (c == kSP) || (c <= 31 && (kSpaceMask & M(c)));
479 #undef M
480 }
481 
Cluster(ParagraphImpl * owner,RunIndex runIndex,size_t start,size_t end,SkSpan<const char> text,SkScalar width,SkScalar height)482 Cluster::Cluster(ParagraphImpl* owner,
483                  RunIndex runIndex,
484                  size_t start,
485                  size_t end,
486                  SkSpan<const char> text,
487                  SkScalar width,
488                  SkScalar height)
489         : fOwner(owner)
490         , fRunIndex(runIndex)
491         , fTextRange(text.begin() - fOwner->text().begin(), text.end() - fOwner->text().begin())
492         , fGraphemeRange(EMPTY_RANGE)
493         , fStart(start)
494         , fEnd(end)
495         , fWidth(width)
496         , fHeight(height)
497         , fHalfLetterSpacing(0.0) {
498     size_t whiteSpacesBreakLen = 0;
499     size_t intraWordBreakLen = 0;
500 
501     const char* ch = text.begin();
502     if (text.end() - ch == 1 && *(unsigned char*)ch <= 0x7F) {
503         // I am not even sure it's worth it if we do not save a unicode call
504         if (is_ascii_7bit_space(*ch)) {
505             ++whiteSpacesBreakLen;
506         }
507     } else {
508         for (auto i = fTextRange.start; i < fTextRange.end; ++i) {
509             if (fOwner->codeUnitHasProperty(i, SkUnicode::CodeUnitFlags::kPartOfWhiteSpaceBreak)) {
510                 ++whiteSpacesBreakLen;
511             }
512             if (fOwner->codeUnitHasProperty(i, SkUnicode::CodeUnitFlags::kPartOfIntraWordBreak)) {
513                 ++intraWordBreakLen;
514             }
515         }
516     }
517 
518     fIsWhiteSpaceBreak = whiteSpacesBreakLen == fTextRange.width();
519     fIsIntraWordBreak = intraWordBreakLen == fTextRange.width();
520     fIsHardBreak = fOwner->codeUnitHasProperty(fTextRange.end,
521                                                SkUnicode::CodeUnitFlags::kHardLineBreakBefore);
522 }
523 
calculateWidth(size_t start,size_t end,bool clip) const524 SkScalar Run::calculateWidth(size_t start, size_t end, bool clip) const {
525     SkASSERT(start <= end);
526     // clip |= end == size();  // Clip at the end of the run?
527     auto correction = 0.0f;
528     if (end > start && !fJustificationShifts.empty()) {
529         // This is not a typo: we are using Point as a pair of SkScalars
530         correction = fJustificationShifts[end - 1].fX -
531                      fJustificationShifts[start].fY;
532     }
533     return posX(end) - posX(start) + correction;
534 }
535 
536 // In some cases we apply spacing to glyphs first and then build the cluster table, in some we do
537 // the opposite - just to optimize the most common case.
applySpacingAndBuildClusterTable()538 void ParagraphImpl::applySpacingAndBuildClusterTable() {
539 
540     // Check all text styles to see what we have to do (if anything)
541     size_t letterSpacingStyles = 0;
542     bool hasWordSpacing = false;
543     for (auto& block : fTextStyles) {
544         if (block.fRange.width() > 0) {
545             if (!SkScalarNearlyZero(block.fStyle.getLetterSpacing())) {
546                 ++letterSpacingStyles;
547             }
548             if (!SkScalarNearlyZero(block.fStyle.getWordSpacing())) {
549                 hasWordSpacing = true;
550             }
551         }
552     }
553 
554     if (letterSpacingStyles == 0 && !hasWordSpacing) {
555         // We don't have to do anything about spacing (most common case)
556         this->buildClusterTable();
557         return;
558     }
559 
560     if (letterSpacingStyles == 1 && !hasWordSpacing && fTextStyles.size() == 1 &&
561         fTextStyles[0].fRange.width() == fText.size() && fRuns.size() == 1) {
562         // We have to letter space the entire paragraph (second most common case)
563         auto& run = fRuns[0];
564         auto& style = fTextStyles[0].fStyle;
565         run.addSpacesEvenly(style.getLetterSpacing());
566         this->buildClusterTable();
567         // This is something Flutter requires
568         for (auto& cluster : fClusters) {
569             cluster.setHalfLetterSpacing(style.getLetterSpacing()/2);
570         }
571         return;
572     }
573 
574     // The complex case: many text styles with spacing (possibly not adjusted to glyphs)
575     this->buildClusterTable();
576 
577     // Walk through all the clusters in the direction of shaped text
578     // (we have to walk through the styles in the same order, too)
579     SkScalar shift = 0;
580     for (auto& run : fRuns) {
581 
582         // Skip placeholder runs
583         if (run.isPlaceholder()) {
584             continue;
585         }
586         bool soFarWhitespacesOnly = true;
587         bool wordSpacingPending = false;
588         Cluster* lastSpaceCluster = nullptr;
589         run.iterateThroughClusters([this, &run, &shift, &soFarWhitespacesOnly, &wordSpacingPending, &lastSpaceCluster](Cluster* cluster) {
590             // Shift the cluster (shift collected from the previous clusters)
591             run.shift(cluster, shift);
592 
593             // Synchronize styles (one cluster can be covered by few styles)
594             Block* currentStyle = fTextStyles.begin();
595             while (!cluster->startsIn(currentStyle->fRange)) {
596                 currentStyle++;
597                 SkASSERT(currentStyle != fTextStyles.end());
598             }
599 
600             SkASSERT(!currentStyle->fStyle.isPlaceholder());
601 
602             // Process word spacing
603             if (currentStyle->fStyle.getWordSpacing() != 0) {
604                 if (cluster->isWhitespaceBreak() && cluster->isSoftBreak()) {
605                     if (!soFarWhitespacesOnly) {
606                         lastSpaceCluster = cluster;
607                         wordSpacingPending = true;
608                     }
609                 } else if (wordSpacingPending) {
610                     SkScalar spacing = currentStyle->fStyle.getWordSpacing();
611                     run.addSpacesAtTheEnd(spacing, lastSpaceCluster);
612                     run.shift(cluster, spacing);
613                     shift += spacing;
614                     wordSpacingPending = false;
615                 }
616             }
617             // Process letter spacing
618             if (currentStyle->fStyle.getLetterSpacing() != 0) {
619                 shift += run.addSpacesEvenly(currentStyle->fStyle.getLetterSpacing(), cluster);
620             }
621 
622             if (soFarWhitespacesOnly && !cluster->isWhitespaceBreak()) {
623                 soFarWhitespacesOnly = false;
624             }
625         });
626     }
627 }
628 
middleEllipsisAddText(size_t charStart,size_t charEnd,SkScalar & allTextWidth,SkScalar width,bool isLeftToRight)629 void ParagraphImpl::middleEllipsisAddText(size_t charStart,
630                                           size_t charEnd,
631                                           SkScalar& allTextWidth,
632                                           SkScalar width,
633                                           bool isLeftToRight) {
634     if (isMiddleEllipsis) {
635         TextCutRecord textCount;
636         textCount.charbegin = charStart;
637         textCount.charOver = charEnd;
638         textCount.phraseWidth = width;
639         allTextWidth += width;
640         if (isLeftToRight) {
641             this->ltrTextSize.emplace_back(textCount);
642         } else {
643             this->rtlTextSize.emplace_back(textCount);
644         }
645     }
646 }
647 
setRunTimeEllipsisWidthForMiddleEllipsis()648 void ParagraphImpl::setRunTimeEllipsisWidthForMiddleEllipsis() {
649     if (isMiddleEllipsis && fParagraphStyle.getEllipsisMod() == EllipsisModal::MIDDLE) {
650         float ellipsisWidth = 0.0;
651         TextLine textLine;
652         const SkString& ellipsis = this->getEllipsis();
653         std::unique_ptr<Run> ellipsisRun;
654         textLine.setParagraphImpl(this);
655         ellipsisRun = textLine.shapeEllipsis(ellipsis, &this->cluster(0));
656         runTimeEllipsisWidth = ellipsisRun->fAdvanceX();
657         ellipsisWidth = ellipsisRun->fAdvanceX();
658         ellipsisRun.reset();
659     }
660 }
661 
662 // Clusters in the order of the input text
buildClusterTable()663 void ParagraphImpl::buildClusterTable() {
664     // It's possible that one grapheme includes few runs; we cannot handle it
665     // so we break graphemes by the runs instead
666     // It's not the ideal solution and has to be revisited later
667     int cluster_count = 1;
668     for (auto& run : fRuns) {
669         cluster_count += run.isPlaceholder() ? 1 : run.size();
670         fCodeUnitProperties[run.fTextRange.start] |= SkUnicode::CodeUnitFlags::kGraphemeStart;
671         fCodeUnitProperties[run.fTextRange.start] |= SkUnicode::CodeUnitFlags::kGlyphClusterStart;
672     }
673     if (!fRuns.empty()) {
674         fCodeUnitProperties[fRuns.back().textRange().end] |= SkUnicode::CodeUnitFlags::kGraphemeStart;
675         fCodeUnitProperties[fRuns.back().textRange().end] |= SkUnicode::CodeUnitFlags::kGlyphClusterStart;
676     }
677     fClusters.reserve_back(fClusters.size() + cluster_count);
678 
679     // Walk through all the run in the direction of input text
680     for (auto& run : fRuns) {
681         auto runIndex = run.index();
682         auto runStart = fClusters.size();
683         if (run.isPlaceholder()) {
684             // Add info to cluster indexes table (text -> cluster)
685             for (auto i = run.textRange().start; i < run.textRange().end; ++i) {
686               fClustersIndexFromCodeUnit[i] = fClusters.size();
687             }
688             // There are no glyphs but we want to have one cluster
689             fClusters.emplace_back(this, runIndex, 0ul, 1ul, this->text(run.textRange()), run.advance().fX, run.advance().fY);
690             fCodeUnitProperties[run.textRange().start] |= SkUnicode::CodeUnitFlags::kSoftLineBreakBefore;
691             fCodeUnitProperties[run.textRange().end] |= SkUnicode::CodeUnitFlags::kSoftLineBreakBefore;
692         } else {
693             // Walk through the glyph in the direction of input text
694             run.iterateThroughClustersInTextOrder([&run, runIndex, this](size_t glyphStart,
695                                                                    size_t glyphEnd,
696                                                                    size_t charStart,
697                                                                    size_t charEnd,
698                                                                    SkScalar width,
699                                                                    SkScalar height) {
700                 SkASSERT(charEnd >= charStart);
701                 // Add info to cluster indexes table (text -> cluster)
702                 for (auto i = charStart; i < charEnd; ++i) {
703                   fClustersIndexFromCodeUnit[i] = fClusters.size();
704                 }
705 
706                 middleEllipsisAddText(charStart, charEnd, allTextWidth, width, run.leftToRight());
707                 SkSpan<const char> text(fText.c_str() + charStart, charEnd - charStart);
708                 fClusters.emplace_back(this, runIndex, glyphStart, glyphEnd, text, width, height);
709                 fCodeUnitProperties[charStart] |= SkUnicode::CodeUnitFlags::kGlyphClusterStart;
710             });
711         }
712         fCodeUnitProperties[run.textRange().start] |= SkUnicode::CodeUnitFlags::kGlyphClusterStart;
713 
714         run.setClusterRange(runStart, fClusters.size());
715         fMaxIntrinsicWidth += run.advance().fX;
716     }
717     fClustersIndexFromCodeUnit[fText.size()] = fClusters.size();
718     fClusters.emplace_back(this, EMPTY_RUN, 0, 0, this->text({fText.size(), fText.size()}), 0, 0);
719     setRunTimeEllipsisWidthForMiddleEllipsis();
720 }
721 
shapeTextIntoEndlessLine()722 bool ParagraphImpl::shapeTextIntoEndlessLine() {
723 
724     if (fText.size() == 0) {
725         return false;
726     }
727 
728     fUnresolvedCodepoints.clear();
729     fFontSwitches.reset();
730 
731     OneLineShaper oneLineShaper(this);
732     auto result = oneLineShaper.shape();
733     fUnresolvedGlyphs = oneLineShaper.unresolvedGlyphs();
734 
735     this->applySpacingAndBuildClusterTable();
736 
737     return result;
738 }
739 
setIndents(const std::vector<SkScalar> & indents)740 void ParagraphImpl::setIndents(const std::vector<SkScalar>& indents)
741 {
742     fIndents = indents;
743 }
744 
detectIndents(size_t index)745 SkScalar ParagraphImpl::detectIndents(size_t index)
746 {
747     SkScalar indent = 0.0;
748     if (fIndents.size() > 0 && index < fIndents.size()) {
749         indent = fIndents[index];
750     } else {
751         indent = fIndents.size() > 0 ? fIndents.back() : 0.0;
752     }
753 
754     return indent;
755 }
756 
breakShapedTextIntoLines(SkScalar maxWidth)757 void ParagraphImpl::breakShapedTextIntoLines(SkScalar maxWidth) {
758 
759     if (!fHasLineBreaks &&
760         !fHasWhitespacesInside &&
761         fPlaceholders.size() == 1 &&
762         fRuns.size() == 1 && fRuns[0].fAdvance.fX <= maxWidth - (this->detectIndents(std::numeric_limits<size_t>::max()))) {
763         // This is a short version of a line breaking when we know that:
764         // 1. We have only one line of text
765         // 2. It's shaped into a single run
766         // 3. There are no placeholders
767         // 4. There are no linebreaks (which will format text into multiple lines)
768         // 5. There are no whitespaces so the minIntrinsicWidth=maxIntrinsicWidth
769         // (To think about that, the last condition is not quite right;
770         // we should calculate minIntrinsicWidth by soft line breaks.
771         // However, it's how it's done in Flutter now)
772         auto& run = this->fRuns[0];
773         auto advance = run.advance();
774         auto textRange = TextRange(0, this->text().size());
775         auto textExcludingSpaces = TextRange(0, fTrailingSpaces);
776         InternalLineMetrics metrics(this->strutForceHeight());
777         metrics.add(&run);
778         auto disableFirstAscent = this->paragraphStyle().getTextHeightBehavior() &
779                                   TextHeightBehavior::kDisableFirstAscent;
780         auto disableLastDescent = this->paragraphStyle().getTextHeightBehavior() &
781                                   TextHeightBehavior::kDisableLastDescent;
782         if (disableFirstAscent) {
783             metrics.fAscent = metrics.fRawAscent;
784         }
785         if (disableLastDescent) {
786             metrics.fDescent = metrics.fRawDescent;
787         }
788         if (this->strutEnabled()) {
789             this->strutMetrics().updateLineMetrics(metrics);
790         }
791         ClusterIndex trailingSpaces = fClusters.size();
792         do {
793             --trailingSpaces;
794             auto& cluster = fClusters[trailingSpaces];
795             if (!cluster.isWhitespaceBreak()) {
796                 ++trailingSpaces;
797                 break;
798             }
799             advance.fX -= cluster.width();
800         } while (trailingSpaces != 0);
801 
802         advance.fY = metrics.height();
803         auto clusterRange = ClusterRange(0, trailingSpaces);
804         auto clusterRangeWithGhosts = ClusterRange(0, this->clusters().size() - 1);
805 
806         TextAlign align = fParagraphStyle.effective_align();
807         SkScalar offsetX = (align == TextAlign::kLeft || align == TextAlign::kJustify) ?
808             this->detectIndents(0) : 0.0f;
809         this->addLine(SkPoint::Make(offsetX, 0), advance,
810                       textExcludingSpaces, textRange, textRange,
811                       clusterRange, clusterRangeWithGhosts, run.advance().x(),
812                       metrics);
813 
814         fLongestLine = std::max(run.advance().fX, advance.fX);
815         fHeight = advance.fY;
816         fWidth = maxWidth;
817         fMaxIntrinsicWidth = run.advance().fX;
818         fMinIntrinsicWidth = advance.fX;
819         fAlphabeticBaseline = fLines.empty() ? fEmptyMetrics.alphabeticBaseline() : fLines.front().alphabeticBaseline();
820         fIdeographicBaseline = fLines.empty() ? fEmptyMetrics.ideographicBaseline() : fLines.front().ideographicBaseline();
821         fExceededMaxLines = false;
822         return;
823     }
824 
825     TextWrapper textWrapper;
826     textWrapper.breakTextIntoLines(
827             this,
828             maxWidth,
829             [&](TextRange textExcludingSpaces,
830                 TextRange text,
831                 TextRange textWithNewlines,
832                 ClusterRange clusters,
833                 ClusterRange clustersWithGhosts,
834                 SkScalar widthWithSpaces,
835                 size_t startPos,
836                 size_t endPos,
837                 SkVector offset,
838                 SkVector advance,
839                 InternalLineMetrics metrics,
840                 bool addEllipsis) {
841                 // TODO: Take in account clipped edges
842                 auto& line = this->addLine(offset, advance, textExcludingSpaces, text, textWithNewlines, clusters, clustersWithGhosts, widthWithSpaces, metrics);
843                 if (addEllipsis && this->paragraphStyle().getEllipsisMod() == EllipsisModal::TAIL) {
844                     line.createEllipsis(maxWidth, this->getEllipsis(), true, this->getWordBreakType());
845                 } else if (addEllipsis && this->paragraphStyle().getEllipsisMod() == EllipsisModal::HEAD) {
846                     line.createHeadEllipsis(maxWidth, this->getEllipsis(), true);
847                 }
848                 fLongestLine = std::max(fLongestLine, std::max(advance.fX, widthWithSpaces));
849             });
850 
851     fHeight = textWrapper.height();
852     fWidth = maxWidth;
853     fMaxIntrinsicWidth = textWrapper.maxIntrinsicWidth();
854     fMinIntrinsicWidth = textWrapper.minIntrinsicWidth();
855     fAlphabeticBaseline = fLines.empty() ? fEmptyMetrics.alphabeticBaseline() : fLines.front().alphabeticBaseline();
856     fIdeographicBaseline = fLines.empty() ? fEmptyMetrics.ideographicBaseline() : fLines.front().ideographicBaseline();
857     fExceededMaxLines = textWrapper.exceededMaxLines();
858 }
859 
formatLines(SkScalar maxWidth)860 void ParagraphImpl::formatLines(SkScalar maxWidth) {
861     auto effectiveAlign = fParagraphStyle.effective_align();
862     const bool isLeftAligned = effectiveAlign == TextAlign::kLeft
863         || (effectiveAlign == TextAlign::kJustify && fParagraphStyle.getTextDirection() == TextDirection::kLtr);
864 
865     if (!SkScalarIsFinite(maxWidth) && !isLeftAligned) {
866         // Special case: clean all text in case of maxWidth == INF & align != left
867         // We had to go through shaping though because we need all the measurement numbers
868         fLines.reset();
869         return;
870     }
871 
872     for (auto& line : fLines) {
873         line.format(effectiveAlign, maxWidth);
874     }
875 }
876 
resolveStrut()877 void ParagraphImpl::resolveStrut() {
878     auto strutStyle = this->paragraphStyle().getStrutStyle();
879     if (!strutStyle.getStrutEnabled() || strutStyle.getFontSize() < 0) {
880         return;
881     }
882 
883     std::vector<sk_sp<SkTypeface>> typefaces = fFontCollection->findTypefaces(strutStyle.getFontFamilies(), strutStyle.getFontStyle(), std::nullopt);
884     if (typefaces.empty()) {
885         SkDEBUGF("Could not resolve strut font\n");
886         return;
887     }
888 
889     SkFont font(typefaces.front(), strutStyle.getFontSize());
890     SkFontMetrics metrics;
891     font.getMetrics(&metrics);
892 
893     if (strutStyle.getHeightOverride()) {
894         auto strutHeight = metrics.fDescent - metrics.fAscent;
895         auto strutMultiplier = strutStyle.getHeight() * strutStyle.getFontSize();
896         fStrutMetrics = InternalLineMetrics(
897             (metrics.fAscent / strutHeight) * strutMultiplier,
898             (metrics.fDescent / strutHeight) * strutMultiplier,
899                 strutStyle.getLeading() < 0 ? 0 : strutStyle.getLeading() * strutStyle.getFontSize(),
900             metrics.fAscent, metrics.fDescent, metrics.fLeading);
901     } else {
902         fStrutMetrics = InternalLineMetrics(
903                 metrics.fAscent,
904                 metrics.fDescent,
905                 strutStyle.getLeading() < 0 ? 0 : strutStyle.getLeading() * strutStyle.getFontSize());
906     }
907     fStrutMetrics.setForceStrut(this->paragraphStyle().getStrutStyle().getForceStrutHeight());
908 }
909 
findAllBlocks(TextRange textRange)910 BlockRange ParagraphImpl::findAllBlocks(TextRange textRange) {
911     BlockIndex begin = EMPTY_BLOCK;
912     BlockIndex end = EMPTY_BLOCK;
913     for (int index = 0; index < fTextStyles.size(); ++index) {
914         auto& block = fTextStyles[index];
915         if (block.fRange.end <= textRange.start) {
916             continue;
917         }
918         if (block.fRange.start >= textRange.end) {
919             break;
920         }
921         if (begin == EMPTY_BLOCK) {
922             begin = index;
923         }
924         end = index;
925     }
926 
927     if (begin == EMPTY_INDEX || end == EMPTY_INDEX) {
928         // It's possible if some text is not covered with any text style
929         // Not in Flutter but in direct use of SkParagraph
930         return EMPTY_RANGE;
931     }
932 
933     return { begin, end + 1 };
934 }
935 
addLine(SkVector offset,SkVector advance,TextRange textExcludingSpaces,TextRange text,TextRange textIncludingNewLines,ClusterRange clusters,ClusterRange clustersWithGhosts,SkScalar widthWithSpaces,InternalLineMetrics sizes)936 TextLine& ParagraphImpl::addLine(SkVector offset,
937                                  SkVector advance,
938                                  TextRange textExcludingSpaces,
939                                  TextRange text,
940                                  TextRange textIncludingNewLines,
941                                  ClusterRange clusters,
942                                  ClusterRange clustersWithGhosts,
943                                  SkScalar widthWithSpaces,
944                                  InternalLineMetrics sizes) {
945     // Define a list of styles that covers the line
946     auto blocks = findAllBlocks(textExcludingSpaces);
947     return fLines.emplace_back(this, offset, advance, blocks,
948                                textExcludingSpaces, text, textIncludingNewLines,
949                                clusters, clustersWithGhosts, widthWithSpaces, sizes);
950 }
951 
952 // Returns a vector of bounding boxes that enclose all text between
953 // start and end glyph indexes, including start and excluding end
getRectsForRange(unsigned start,unsigned end,RectHeightStyle rectHeightStyle,RectWidthStyle rectWidthStyle)954 std::vector<TextBox> ParagraphImpl::getRectsForRange(unsigned start,
955                                                      unsigned end,
956                                                      RectHeightStyle rectHeightStyle,
957                                                      RectWidthStyle rectWidthStyle) {
958     std::vector<TextBox> results;
959     if (fText.isEmpty()) {
960         if (start == 0 && end > 0) {
961             // On account of implied "\n" that is always at the end of the text
962             //SkDebugf("getRectsForRange(%d, %d): %f\n", start, end, fHeight);
963             results.emplace_back(SkRect::MakeXYWH(0, 0, 0, fHeight), fParagraphStyle.getTextDirection());
964         }
965         return results;
966     }
967 
968     this->ensureUTF16Mapping();
969 
970     if (start >= end || start > SkToSizeT(fUTF8IndexForUTF16Index.size()) || end == 0) {
971         return results;
972     }
973 
974     // Adjust the text to grapheme edges
975     // Apparently, text editor CAN move inside graphemes but CANNOT select a part of it.
976     // I don't know why - the solution I have here returns an empty box for every query that
977     // does not contain an end of a grapheme.
978     // Once a cursor is inside a complex grapheme I can press backspace and cause trouble.
979     // To avoid any problems, I will not allow any selection of a part of a grapheme.
980     // One flutter test fails because of it but the editing experience is correct
981     // (although you have to press the cursor many times before it moves to the next grapheme).
982     TextRange text(fText.size(), fText.size());
983     // TODO: This is probably a temp change that makes SkParagraph work as TxtLib
984     //  (so we can compare the results). We now include in the selection box only the graphemes
985     //  that belongs to the given [start:end) range entirely (not the ones that intersect with it)
986     if (start < SkToSizeT(fUTF8IndexForUTF16Index.size())) {
987         auto utf8 = fUTF8IndexForUTF16Index[start];
988         // If start points to a trailing surrogate, skip it
989         if (start > 0 && fUTF8IndexForUTF16Index[start - 1] == utf8) {
990             utf8 = fUTF8IndexForUTF16Index[start + 1];
991         }
992         text.start = this->findNextGraphemeBoundary(utf8);
993     }
994     if (end < SkToSizeT(fUTF8IndexForUTF16Index.size())) {
995         auto utf8 = this->findPreviousGraphemeBoundary(fUTF8IndexForUTF16Index[end]);
996         text.end = utf8;
997     }
998     //SkDebugf("getRectsForRange(%d,%d) -> (%d:%d)\n", start, end, text.start, text.end);
999     for (auto& line : fLines) {
1000         auto lineText = line.textWithNewlines();
1001         auto intersect = lineText * text;
1002         if (intersect.empty() && lineText.start != text.start) {
1003             continue;
1004         }
1005 
1006         line.getRectsForRange(intersect, rectHeightStyle, rectWidthStyle, results);
1007     }
1008 /*
1009     SkDebugf("getRectsForRange(%d, %d)\n", start, end);
1010     for (auto& r : results) {
1011         r.rect.fLeft = littleRound(r.rect.fLeft);
1012         r.rect.fRight = littleRound(r.rect.fRight);
1013         r.rect.fTop = littleRound(r.rect.fTop);
1014         r.rect.fBottom = littleRound(r.rect.fBottom);
1015         SkDebugf("[%f:%f * %f:%f]\n", r.rect.fLeft, r.rect.fRight, r.rect.fTop, r.rect.fBottom);
1016     }
1017 */
1018     return results;
1019 }
1020 
getRectsForPlaceholders()1021 std::vector<TextBox> ParagraphImpl::getRectsForPlaceholders() {
1022   std::vector<TextBox> boxes;
1023   if (fText.isEmpty()) {
1024        return boxes;
1025   }
1026   if (fPlaceholders.size() == 1) {
1027        // We always have one fake placeholder
1028        return boxes;
1029   }
1030   for (auto& line : fLines) {
1031       line.getRectsForPlaceholders(boxes);
1032   }
1033   /*
1034   SkDebugf("getRectsForPlaceholders('%s'): %d\n", fText.c_str(), boxes.size());
1035   for (auto& r : boxes) {
1036       r.rect.fLeft = littleRound(r.rect.fLeft);
1037       r.rect.fRight = littleRound(r.rect.fRight);
1038       r.rect.fTop = littleRound(r.rect.fTop);
1039       r.rect.fBottom = littleRound(r.rect.fBottom);
1040       SkDebugf("[%f:%f * %f:%f] %s\n", r.rect.fLeft, r.rect.fRight, r.rect.fTop, r.rect.fBottom,
1041                (r.direction == TextDirection::kLtr ? "left" : "right"));
1042   }
1043   */
1044   return boxes;
1045 }
1046 
1047 // TODO: Optimize (save cluster <-> codepoint connection)
getGlyphPositionAtCoordinate(SkScalar dx,SkScalar dy)1048 PositionWithAffinity ParagraphImpl::getGlyphPositionAtCoordinate(SkScalar dx, SkScalar dy) {
1049 
1050     if (fText.isEmpty()) {
1051         return {0, Affinity::kDownstream};
1052     }
1053 
1054     this->ensureUTF16Mapping();
1055 
1056     for (auto& line : fLines) {
1057         // Let's figure out if we can stop looking
1058         auto offsetY = line.offset().fY;
1059         if (dy >= offsetY + line.height() && &line != &fLines.back()) {
1060             // This line is not good enough
1061             continue;
1062         }
1063 
1064         // This is so far the the line vertically closest to our coordinates
1065         // (or the first one, or the only one - all the same)
1066 
1067         auto result = line.getGlyphPositionAtCoordinate(dx);
1068         //SkDebugf("getGlyphPositionAtCoordinate(%f, %f): %d %s\n", dx, dy, result.position,
1069         //   result.affinity == Affinity::kUpstream ? "up" : "down");
1070         return result;
1071     }
1072 
1073     return {0, Affinity::kDownstream};
1074 }
1075 
1076 // Finds the first and last glyphs that define a word containing
1077 // the glyph at index offset.
1078 // By "glyph" they mean a character index - indicated by Minikin's code
getWordBoundary(unsigned offset)1079 SkRange<size_t> ParagraphImpl::getWordBoundary(unsigned offset) {
1080 
1081     if (fWords.empty()) {
1082         if (!fUnicode->getWords(fText.c_str(), fText.size(), nullptr, &fWords)) {
1083             return {0, 0 };
1084         }
1085     }
1086 
1087     int32_t start = 0;
1088     int32_t end = 0;
1089     for (size_t i = 0; i < fWords.size(); ++i) {
1090         auto word = fWords[i];
1091         if (word <= offset) {
1092             start = word;
1093             end = word;
1094         } else if (word > offset) {
1095             end = word;
1096             break;
1097         }
1098     }
1099 
1100     //SkDebugf("getWordBoundary(%d): %d - %d\n", offset, start, end);
1101     return { SkToU32(start), SkToU32(end) };
1102 }
1103 
getLineMetrics(std::vector<LineMetrics> & metrics)1104 void ParagraphImpl::getLineMetrics(std::vector<LineMetrics>& metrics) {
1105     metrics.clear();
1106     for (auto& line : fLines) {
1107         metrics.emplace_back(line.getMetrics());
1108     }
1109 }
1110 
text(TextRange textRange)1111 SkSpan<const char> ParagraphImpl::text(TextRange textRange) {
1112     SkASSERT(textRange.start <= fText.size() && textRange.end <= fText.size());
1113     auto start = fText.c_str() + textRange.start;
1114     return SkSpan<const char>(start, textRange.width());
1115 }
1116 
clusters(ClusterRange clusterRange)1117 SkSpan<Cluster> ParagraphImpl::clusters(ClusterRange clusterRange) {
1118     SkASSERT(clusterRange.start < SkToSizeT(fClusters.size()) &&
1119              clusterRange.end <= SkToSizeT(fClusters.size()));
1120     return SkSpan<Cluster>(&fClusters[clusterRange.start], clusterRange.width());
1121 }
1122 
cluster(ClusterIndex clusterIndex)1123 Cluster& ParagraphImpl::cluster(ClusterIndex clusterIndex) {
1124     SkASSERT(clusterIndex < SkToSizeT(fClusters.size()));
1125     return fClusters[clusterIndex];
1126 }
1127 
runByCluster(ClusterIndex clusterIndex)1128 Run& ParagraphImpl::runByCluster(ClusterIndex clusterIndex) {
1129     auto start = cluster(clusterIndex);
1130     return this->run(start.fRunIndex);
1131 }
1132 
blocks(BlockRange blockRange)1133 SkSpan<Block> ParagraphImpl::blocks(BlockRange blockRange) {
1134     SkASSERT(blockRange.start < SkToSizeT(fTextStyles.size()) &&
1135              blockRange.end <= SkToSizeT(fTextStyles.size()));
1136     return SkSpan<Block>(&fTextStyles[blockRange.start], blockRange.width());
1137 }
1138 
block(BlockIndex blockIndex)1139 Block& ParagraphImpl::block(BlockIndex blockIndex) {
1140     SkASSERT(blockIndex < SkToSizeT(fTextStyles.size()));
1141     return fTextStyles[blockIndex];
1142 }
1143 
setState(InternalState state)1144 void ParagraphImpl::setState(InternalState state) {
1145     if (fState <= state) {
1146         fState = state;
1147         return;
1148     }
1149 
1150     fState = state;
1151     switch (fState) {
1152         case kUnknown:
1153             SkASSERT(false);
1154             /*
1155             // The text is immutable and so are all the text indexing properties
1156             // taken from SkUnicode
1157             fCodeUnitProperties.reset();
1158             fWords.clear();
1159             fBidiRegions.clear();
1160             fUTF8IndexForUTF16Index.reset();
1161             fUTF16IndexForUTF8Index.reset();
1162             */
1163             [[fallthrough]];
1164 
1165         case kIndexed:
1166             fRuns.reset();
1167             fClusters.reset();
1168             [[fallthrough]];
1169 
1170         case kShaped:
1171             fLines.reset();
1172             [[fallthrough]];
1173 
1174         case kLineBroken:
1175             fPicture = nullptr;
1176             [[fallthrough]];
1177 
1178         default:
1179             break;
1180     }
1181 }
1182 
computeEmptyMetrics()1183 void ParagraphImpl::computeEmptyMetrics() {
1184 
1185     // The empty metrics is used to define the height of the empty lines
1186     // Unfortunately, Flutter has 2 different cases for that:
1187     // 1. An empty line inside the text
1188     // 2. An empty paragraph
1189     // In the first case SkParagraph takes the metrics from the default paragraph style
1190     // In the second case it should take it from the current text style
1191     bool emptyParagraph = fRuns.empty();
1192     TextStyle textStyle = paragraphStyle().getTextStyle();
1193     if (emptyParagraph && !fTextStyles.empty()) {
1194         textStyle = fTextStyles.back().fStyle;
1195     }
1196 
1197     auto typefaces = fontCollection()->findTypefaces(
1198       textStyle.getFontFamilies(), textStyle.getFontStyle(), textStyle.getFontArguments());
1199     auto typeface = typefaces.empty() ? nullptr : typefaces.front();
1200 
1201     SkFont font(typeface, textStyle.getFontSize());
1202     fEmptyMetrics = InternalLineMetrics(font, paragraphStyle().getStrutStyle().getForceStrutHeight());
1203 
1204     if (!paragraphStyle().getStrutStyle().getForceStrutHeight() &&
1205         textStyle.getHeightOverride()) {
1206         const auto intrinsicHeight = fEmptyMetrics.height();
1207         const auto strutHeight = textStyle.getHeight() * textStyle.getFontSize();
1208         if (paragraphStyle().getStrutStyle().getHalfLeading()) {
1209             fEmptyMetrics.update(
1210                 fEmptyMetrics.ascent(),
1211                 fEmptyMetrics.descent(),
1212                 fEmptyMetrics.leading() + strutHeight - intrinsicHeight);
1213         } else {
1214             const auto multiplier = strutHeight / intrinsicHeight;
1215             fEmptyMetrics.update(
1216                 fEmptyMetrics.ascent() * multiplier,
1217                 fEmptyMetrics.descent() * multiplier,
1218                 fEmptyMetrics.leading() * multiplier);
1219         }
1220     }
1221 
1222     if (emptyParagraph) {
1223         // For an empty text we apply both TextHeightBehaviour flags
1224         // In case of non-empty paragraph TextHeightBehaviour flags will be applied at the appropriate place
1225         // We have to do it here because we skip wrapping for an empty text
1226         auto disableFirstAscent = (paragraphStyle().getTextHeightBehavior() & TextHeightBehavior::kDisableFirstAscent) == TextHeightBehavior::kDisableFirstAscent;
1227         auto disableLastDescent = (paragraphStyle().getTextHeightBehavior() & TextHeightBehavior::kDisableLastDescent) == TextHeightBehavior::kDisableLastDescent;
1228         fEmptyMetrics.update(
1229             disableFirstAscent ? fEmptyMetrics.rawAscent() : fEmptyMetrics.ascent(),
1230             disableLastDescent ? fEmptyMetrics.rawDescent() : fEmptyMetrics.descent(),
1231             fEmptyMetrics.leading());
1232     }
1233 
1234     if (fParagraphStyle.getStrutStyle().getStrutEnabled()) {
1235         fStrutMetrics.updateLineMetrics(fEmptyMetrics);
1236     }
1237 }
1238 
getEllipsis() const1239 SkString ParagraphImpl::getEllipsis() const {
1240 
1241     auto ellipsis8 = fParagraphStyle.getEllipsis();
1242     auto ellipsis16 = fParagraphStyle.getEllipsisUtf16();
1243     if (!ellipsis8.isEmpty()) {
1244         return ellipsis8;
1245     } else {
1246         return SkUnicode::convertUtf16ToUtf8(fParagraphStyle.getEllipsisUtf16());
1247     }
1248 }
1249 
getWordBreakType() const1250 WordBreakType ParagraphImpl::getWordBreakType() const {
1251     return fParagraphStyle.getStrutStyle().getWordBreakType();
1252 }
1253 
updateFontSize(size_t from,size_t to,SkScalar fontSize)1254 void ParagraphImpl::updateFontSize(size_t from, size_t to, SkScalar fontSize) {
1255 
1256   SkASSERT(from == 0 && to == fText.size());
1257   auto defaultStyle = fParagraphStyle.getTextStyle();
1258   defaultStyle.setFontSize(fontSize);
1259   fParagraphStyle.setTextStyle(defaultStyle);
1260 
1261   for (auto& textStyle : fTextStyles) {
1262     textStyle.fStyle.setFontSize(fontSize);
1263   }
1264 
1265   fState = std::min(fState, kIndexed);
1266   fOldWidth = 0;
1267   fOldHeight = 0;
1268 }
1269 
updateTextAlign(TextAlign textAlign)1270 void ParagraphImpl::updateTextAlign(TextAlign textAlign) {
1271     fParagraphStyle.setTextAlign(textAlign);
1272 
1273     if (fState >= kLineBroken) {
1274         fState = kLineBroken;
1275     }
1276 }
1277 
updateForegroundPaint(size_t from,size_t to,SkPaint paint)1278 void ParagraphImpl::updateForegroundPaint(size_t from, size_t to, SkPaint paint) {
1279     SkASSERT(from == 0 && to == fText.size());
1280     auto defaultStyle = fParagraphStyle.getTextStyle();
1281     defaultStyle.setForegroundColor(paint);
1282     fParagraphStyle.setTextStyle(defaultStyle);
1283 
1284     for (auto& textStyle : fTextStyles) {
1285         textStyle.fStyle.setForegroundColor(paint);
1286     }
1287 }
1288 
updateBackgroundPaint(size_t from,size_t to,SkPaint paint)1289 void ParagraphImpl::updateBackgroundPaint(size_t from, size_t to, SkPaint paint) {
1290     SkASSERT(from == 0 && to == fText.size());
1291     auto defaultStyle = fParagraphStyle.getTextStyle();
1292     defaultStyle.setBackgroundColor(paint);
1293     fParagraphStyle.setTextStyle(defaultStyle);
1294 
1295     for (auto& textStyle : fTextStyles) {
1296         textStyle.fStyle.setBackgroundColor(paint);
1297     }
1298 }
1299 
countSurroundingGraphemes(TextRange textRange) const1300 SkTArray<TextIndex> ParagraphImpl::countSurroundingGraphemes(TextRange textRange) const {
1301     textRange = textRange.intersection({0, fText.size()});
1302     SkTArray<TextIndex> graphemes;
1303     if ((fCodeUnitProperties[textRange.start] & SkUnicode::CodeUnitFlags::kGraphemeStart) == 0) {
1304         // Count the previous partial grapheme
1305         graphemes.emplace_back(textRange.start);
1306     }
1307     for (auto index = textRange.start; index < textRange.end; ++index) {
1308         if ((fCodeUnitProperties[index] & SkUnicode::CodeUnitFlags::kGraphemeStart) != 0) {
1309             graphemes.emplace_back(index);
1310         }
1311     }
1312     return graphemes;
1313 }
1314 
findPreviousGraphemeBoundary(TextIndex utf8) const1315 TextIndex ParagraphImpl::findPreviousGraphemeBoundary(TextIndex utf8) const {
1316     while (utf8 > 0 &&
1317           (fCodeUnitProperties[utf8] & SkUnicode::CodeUnitFlags::kGraphemeStart) == 0) {
1318         --utf8;
1319     }
1320     return utf8;
1321 }
1322 
findNextGraphemeBoundary(TextIndex utf8) const1323 TextIndex ParagraphImpl::findNextGraphemeBoundary(TextIndex utf8) const {
1324     while (utf8 < fText.size() &&
1325           (fCodeUnitProperties[utf8] & SkUnicode::CodeUnitFlags::kGraphemeStart) == 0) {
1326         ++utf8;
1327     }
1328     return utf8;
1329 }
1330 
findNextGlyphClusterBoundary(TextIndex utf8) const1331 TextIndex ParagraphImpl::findNextGlyphClusterBoundary(TextIndex utf8) const {
1332     while (utf8 < fText.size() &&
1333           (fCodeUnitProperties[utf8] & SkUnicode::CodeUnitFlags::kGlyphClusterStart) == 0) {
1334         ++utf8;
1335     }
1336     return utf8;
1337 }
1338 
findPreviousGlyphClusterBoundary(TextIndex utf8) const1339 TextIndex ParagraphImpl::findPreviousGlyphClusterBoundary(TextIndex utf8) const {
1340     while (utf8 > 0 &&
1341           (fCodeUnitProperties[utf8] & SkUnicode::CodeUnitFlags::kGlyphClusterStart) == 0) {
1342         --utf8;
1343     }
1344     return utf8;
1345 }
1346 
ensureUTF16Mapping()1347 void ParagraphImpl::ensureUTF16Mapping() {
1348     fillUTF16MappingOnce([&] {
1349         fUnicode->extractUtfConversionMapping(
1350                 this->text(),
1351                 [&](size_t index) { fUTF8IndexForUTF16Index.emplace_back(index); },
1352                 [&](size_t index) { fUTF16IndexForUTF8Index.emplace_back(index); });
1353     });
1354 }
1355 
visit(const Visitor & visitor)1356 void ParagraphImpl::visit(const Visitor& visitor) {
1357     int lineNumber = 0;
1358     for (auto& line : fLines) {
1359         line.ensureTextBlobCachePopulated();
1360         for (auto& rec : line.fTextBlobCache) {
1361             SkTextBlob::Iter iter(*rec.fBlob);
1362             SkTextBlob::Iter::ExperimentalRun run;
1363 
1364             SkSTArray<128, uint32_t> clusterStorage;
1365             const Run* R = rec.fVisitor_Run;
1366             const uint32_t* clusterPtr = &R->fClusterIndexes[0];
1367 
1368             if (R->fClusterStart > 0) {
1369                 int count = R->fClusterIndexes.size();
1370                 clusterStorage.reset(count);
1371                 for (int i = 0; i < count; ++i) {
1372                     clusterStorage[i] = R->fClusterStart + R->fClusterIndexes[i];
1373                 }
1374                 clusterPtr = &clusterStorage[0];
1375             }
1376             clusterPtr += rec.fVisitor_Pos;
1377 
1378             while (iter.experimentalNext(&run)) {
1379                 const Paragraph::VisitorInfo info = {
1380                     run.font,
1381                     rec.fOffset,
1382                     rec.fClipRect.fRight,
1383                     run.count,
1384                     run.glyphs,
1385                     run.positions,
1386                     clusterPtr,
1387                     0,  // flags
1388                 };
1389                 visitor(lineNumber, &info);
1390                 clusterPtr += run.count;
1391             }
1392         }
1393         visitor(lineNumber, nullptr);   // signal end of line
1394         lineNumber += 1;
1395     }
1396 }
1397 
getLineNumberAt(TextIndex codeUnitIndex) const1398 int ParagraphImpl::getLineNumberAt(TextIndex codeUnitIndex) const {
1399     for (auto i = 0; i < fLines.size(); ++i) {
1400         auto& line = fLines[i];
1401         if (line.text().contains({codeUnitIndex, codeUnitIndex + 1})) {
1402             return i;
1403         }
1404     }
1405     return -1;
1406 }
1407 
getLineMetricsAt(int lineNumber,LineMetrics * lineMetrics) const1408 bool ParagraphImpl::getLineMetricsAt(int lineNumber, LineMetrics* lineMetrics) const {
1409     if (lineNumber < 0 || lineNumber >= fLines.size()) {
1410         return false;
1411     }
1412     auto& line = fLines[lineNumber];
1413     if (lineMetrics) {
1414         *lineMetrics = line.getMetrics();
1415     }
1416     return true;
1417 }
1418 
getActualTextRange(int lineNumber,bool includeSpaces) const1419 TextRange ParagraphImpl::getActualTextRange(int lineNumber, bool includeSpaces) const {
1420     if (lineNumber < 0 || lineNumber >= fLines.size()) {
1421         return EMPTY_TEXT;
1422     }
1423     auto& line = fLines[lineNumber];
1424     return includeSpaces ? line.text() : line.trimmedText();
1425 }
1426 
getGlyphClusterAt(TextIndex codeUnitIndex,GlyphClusterInfo * glyphInfo)1427 bool ParagraphImpl::getGlyphClusterAt(TextIndex codeUnitIndex, GlyphClusterInfo* glyphInfo) {
1428     for (auto i = 0; i < fLines.size(); ++i) {
1429         auto& line = fLines[i];
1430         if (!line.text().contains({codeUnitIndex, codeUnitIndex})) {
1431             continue;
1432         }
1433         for (auto c = line.clustersWithSpaces().start; c < line.clustersWithSpaces().end; ++c) {
1434             auto& cluster = fClusters[c];
1435             if (cluster.contains(codeUnitIndex)) {
1436                 std::vector<TextBox> boxes;
1437                 line.getRectsForRange(cluster.textRange(),
1438                                       RectHeightStyle::kTight,
1439                                       RectWidthStyle::kTight,
1440                                       boxes);
1441                 if (boxes.size() > 0) {
1442                     if (glyphInfo) {
1443                         *glyphInfo = {boxes[0].rect, cluster.textRange(), boxes[0].direction};
1444                     }
1445                     return true;
1446                 }
1447             }
1448         }
1449         return false;
1450     }
1451     return false;
1452 }
1453 
getClosestGlyphClusterAt(SkScalar dx,SkScalar dy,GlyphClusterInfo * glyphInfo)1454 bool ParagraphImpl::getClosestGlyphClusterAt(SkScalar dx,
1455                                              SkScalar dy,
1456                                              GlyphClusterInfo* glyphInfo) {
1457     auto res = this->getGlyphPositionAtCoordinate(dx, dy);
1458     auto textIndex = res.position + (res.affinity == Affinity::kDownstream ? 0 : 1);
1459     GlyphClusterInfo gci;
1460     if (this->getGlyphClusterAt(textIndex, glyphInfo ? glyphInfo : &gci)) {
1461         return true;
1462     } else {
1463         return false;
1464     }
1465 }
1466 
getFontAt(TextIndex codeUnitIndex) const1467 SkFont ParagraphImpl::getFontAt(TextIndex codeUnitIndex) const {
1468     for (auto& run : fRuns) {
1469         if (run.textRange().contains({codeUnitIndex, codeUnitIndex})) {
1470             return run.font();
1471         }
1472     }
1473     return SkFont();
1474 }
1475 
getFonts() const1476 std::vector<Paragraph::FontInfo> ParagraphImpl::getFonts() const {
1477     std::vector<FontInfo> results;
1478     for (auto& run : fRuns) {
1479         results.emplace_back(run.font(), run.textRange());
1480     }
1481     return results;
1482 }
1483 
measureText()1484 SkFontMetrics ParagraphImpl::measureText() {
1485     SkFontMetrics metrics;
1486     if (fRuns.empty()) {
1487         return metrics;
1488     }
1489 
1490     const auto& firstFont = fRuns.front().font();
1491     SkRect firstBounds;
1492     auto firstStr = text(fRuns.front().textRange());
1493     firstFont.getMetrics(&metrics);
1494     firstFont.measureText(firstStr.data(), firstStr.size(), SkTextEncoding::kUTF8, &firstBounds, nullptr);
1495     fGlyphsBoundsTop = firstBounds.top();
1496     fGlyphsBoundsBottom = firstBounds.bottom();
1497     fGlyphsBoundsLeft = firstBounds.left();
1498     SkScalar realWidth = 0;
1499     for (size_t i = 0; i < fRuns.size(); ++i) {
1500         auto run = fRuns[i];
1501         const auto& font = run.font();
1502         SkRect bounds;
1503         auto str = text(run.textRange());
1504         auto advance = font.measureText(str.data(), str.size(), SkTextEncoding::kUTF8, &bounds, nullptr);
1505         realWidth += advance;
1506         if (i == 0) {
1507             realWidth -= ((advance - (bounds.right() - bounds.left())) / 2);
1508         }
1509         if (i == (fRuns.size() - 1)) {
1510             realWidth -= ((advance - (bounds.right() - bounds.left())) / 2);
1511         }
1512         fGlyphsBoundsTop = std::min(fGlyphsBoundsTop, bounds.top());
1513         fGlyphsBoundsBottom = std::max(fGlyphsBoundsBottom, bounds.bottom());
1514     }
1515     fGlyphsBoundsRight = realWidth + fGlyphsBoundsLeft;
1516     return metrics;
1517 }
1518 
1519 }  // namespace textlayout
1520 }  // namespace skia
1521