• 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/SkTypeface.h"
9 #include "include/private/SkTFitsIn.h"
10 #include "include/private/SkTo.h"
11 #include "modules/skparagraph/include/Metrics.h"
12 #include "modules/skparagraph/include/Paragraph.h"
13 #include "modules/skparagraph/include/ParagraphStyle.h"
14 #include "modules/skparagraph/include/TextStyle.h"
15 #include "modules/skparagraph/src/OneLineShaper.h"
16 #include "modules/skparagraph/src/ParagraphImpl.h"
17 #include "modules/skparagraph/src/Run.h"
18 #include "modules/skparagraph/src/TextLine.h"
19 #include "modules/skparagraph/src/TextWrapper.h"
20 #include "src/utils/SkUTF.h"
21 #include <math.h>
22 #include <algorithm>
23 #include <utility>
24 
25 
26 namespace skia {
27 namespace textlayout {
28 
29 namespace {
30 
littleRound(SkScalar a)31 SkScalar littleRound(SkScalar a) {
32     // This rounding is done to match Flutter tests. Must be removed..
33     auto val = std::fabs(a);
34     if (val < 10000) {
35         return SkScalarRoundToScalar(a * 100.0)/100.0;
36     } else if (val < 100000) {
37         return SkScalarRoundToScalar(a * 10.0)/10.0;
38     } else {
39         return SkScalarFloorToScalar(a);
40     }
41 }
42 }  // namespace
43 
operator *(const TextRange & a,const TextRange & b)44 TextRange operator*(const TextRange& a, const TextRange& b) {
45     if (a.start == b.start && a.end == b.end) return a;
46     auto begin = std::max(a.start, b.start);
47     auto end = std::min(a.end, b.end);
48     return end > begin ? TextRange(begin, end) : EMPTY_TEXT;
49 }
50 
Paragraph(ParagraphStyle style,sk_sp<FontCollection> fonts)51 Paragraph::Paragraph(ParagraphStyle style, sk_sp<FontCollection> fonts)
52             : fFontCollection(std::move(fonts))
53             , fParagraphStyle(std::move(style))
54             , fAlphabeticBaseline(0)
55             , fIdeographicBaseline(0)
56             , fHeight(0)
57             , fWidth(0)
58             , fMaxIntrinsicWidth(0)
59             , fMinIntrinsicWidth(0)
60             , fLongestLine(0)
61             , fExceededMaxLines(0)
62 { }
63 
ParagraphImpl(const SkString & text,ParagraphStyle style,SkTArray<Block,true> blocks,SkTArray<Placeholder,true> placeholders,sk_sp<FontCollection> fonts,std::shared_ptr<SkUnicode> unicode)64 ParagraphImpl::ParagraphImpl(const SkString& text,
65                              ParagraphStyle style,
66                              SkTArray<Block, true> blocks,
67                              SkTArray<Placeholder, true> placeholders,
68                              sk_sp<FontCollection> fonts,
69                              std::shared_ptr<SkUnicode> unicode)
70         : Paragraph(std::move(style), std::move(fonts))
71         , fTextStyles(std::move(blocks))
72         , fPlaceholders(std::move(placeholders))
73         , fText(text)
74         , fState(kUnknown)
75         , fUnresolvedGlyphs(0)
76         , fPicture(nullptr)
77         , fStrutMetrics(false)
78         , fOldWidth(0)
79         , fOldHeight(0)
80         , fUnicode(std::move(unicode))
81 {
82     SkASSERT(fUnicode);
83 }
84 
ParagraphImpl(const std::u16string & utf16text,ParagraphStyle style,SkTArray<Block,true> blocks,SkTArray<Placeholder,true> placeholders,sk_sp<FontCollection> fonts,std::shared_ptr<SkUnicode> unicode)85 ParagraphImpl::ParagraphImpl(const std::u16string& utf16text,
86                              ParagraphStyle style,
87                              SkTArray<Block, true> blocks,
88                              SkTArray<Placeholder, true> placeholders,
89                              sk_sp<FontCollection> fonts,
90                              std::shared_ptr<SkUnicode> unicode)
91         : ParagraphImpl(SkString(),
92                         std::move(style),
93                         std::move(blocks),
94                         std::move(placeholders),
95                         std::move(fonts),
96                         std::move(unicode))
97 {
98     SkASSERT(fUnicode);
99     fText =  fUnicode->convertUtf16ToUtf8(utf16text);
100 }
101 
102 ParagraphImpl::~ParagraphImpl() = default;
103 
unresolvedGlyphs()104 int32_t ParagraphImpl::unresolvedGlyphs() {
105     if (fState < kShaped) {
106         return -1;
107     }
108 
109     return fUnresolvedGlyphs;
110 }
111 
layout(SkScalar rawWidth)112 void ParagraphImpl::layout(SkScalar rawWidth) {
113 
114     // TODO: This rounding is done to match Flutter tests. Must be removed...
115     auto floorWidth = SkScalarFloorToScalar(rawWidth);
116 
117     if ((!SkScalarIsFinite(rawWidth) || fLongestLine <= floorWidth) &&
118         fState >= kLineBroken &&
119          fLines.size() == 1 && fLines.front().ellipsis() == nullptr) {
120         // Most common case: one line of text (and one line is never justified, so no cluster shifts)
121         // We cannot mark it as kLineBroken because the new width can be bigger than the old width
122         fWidth = floorWidth;
123         fState = kMarked;
124     } else if (fState >= kLineBroken && fOldWidth != floorWidth) {
125         // We can use the results from SkShaper but have to do EVERYTHING ELSE again
126         fState = kShaped;
127     } else {
128         // Nothing changed case: we can reuse the data from the last layout
129     }
130 
131     if (fState < kShaped) {
132         this->fCodeUnitProperties.reset();
133         this->fCodeUnitProperties.push_back_n(fText.size() + 1, CodeUnitFlags::kNoCodeUnitFlag);
134         this->fWords.clear();
135         this->fBidiRegions.clear();
136         this->fUTF8IndexForUTF16Index.reset();
137         this->fUTF16IndexForUTF8Index.reset();
138         this->fRuns.reset();
139         if (!this->shapeTextIntoEndlessLine()) {
140             this->resetContext();
141             // TODO: merge the two next calls - they always come together
142             this->resolveStrut();
143             this->computeEmptyMetrics();
144             this->fLines.reset();
145 
146             // Set the important values that are not zero
147             fWidth = floorWidth;
148             fHeight = fEmptyMetrics.height();
149             if (fParagraphStyle.getStrutStyle().getStrutEnabled() &&
150                 fParagraphStyle.getStrutStyle().getForceStrutHeight()) {
151                 fHeight = fStrutMetrics.height();
152             }
153             fAlphabeticBaseline = fEmptyMetrics.alphabeticBaseline();
154             fIdeographicBaseline = fEmptyMetrics.ideographicBaseline();
155             fLongestLine = FLT_MIN - FLT_MAX; // That is what flutter has
156             fMinIntrinsicWidth = 0;
157             fMaxIntrinsicWidth = 0;
158             this->fOldWidth = floorWidth;
159             this->fOldHeight = this->fHeight;
160 
161             return;
162         }
163         fState = kShaped;
164     }
165 
166     if (fState < kMarked) {
167         this->fClusters.reset();
168         this->resetShifts();
169         this->fClustersIndexFromCodeUnit.reset();
170         this->fClustersIndexFromCodeUnit.push_back_n(fText.size() + 1, EMPTY_INDEX);
171         this->buildClusterTable();
172         fState = kClusterized;
173         this->spaceGlyphs();
174         fState = kMarked;
175     }
176 
177     if (fState < kLineBroken) {
178         this->resetContext();
179         this->resolveStrut();
180         this->computeEmptyMetrics();
181         this->fLines.reset();
182         this->breakShapedTextIntoLines(floorWidth);
183         fState = kLineBroken;
184     }
185 
186     if (fState < kFormatted) {
187         // Build the picture lazily not until we actually have to paint (or never)
188         this->formatLines(fWidth);
189         fState = kFormatted;
190     }
191 
192     this->fOldWidth = floorWidth;
193     this->fOldHeight = this->fHeight;
194 
195     // TODO: This rounding is done to match Flutter tests. Must be removed...
196     fMinIntrinsicWidth = littleRound(fMinIntrinsicWidth);
197     fMaxIntrinsicWidth = littleRound(fMaxIntrinsicWidth);
198 
199     // TODO: This is strictly Flutter thing. Must be factored out into some flutter code
200     if (fParagraphStyle.getMaxLines() == 1 ||
201         (fParagraphStyle.unlimited_lines() && fParagraphStyle.ellipsized())) {
202         fMinIntrinsicWidth = fMaxIntrinsicWidth;
203     }
204 
205     // TODO: Since min and max are calculated differently it's possible to get a rounding error
206     //  that would make min > max. Sort it out later, make it the same for now
207     if (fMaxIntrinsicWidth < fMinIntrinsicWidth) {
208         fMaxIntrinsicWidth = fMinIntrinsicWidth;
209     }
210 
211     //SkDebugf("layout('%s', %f): %f %f\n", fText.c_str(), rawWidth, fMinIntrinsicWidth, fMaxIntrinsicWidth);
212 }
213 
paint(SkCanvas * canvas,SkScalar x,SkScalar y)214 void ParagraphImpl::paint(SkCanvas* canvas, SkScalar x, SkScalar y) {
215 
216     if (fParagraphStyle.getDrawOptions() == DrawOptions::kDirect) {
217         // Paint the text without recording it
218         this->paintLines(canvas, x, y);
219         return;
220     }
221 
222     if (fState < kDrawn) {
223         // Record the picture anyway (but if we have some pieces in the cache they will be used)
224         this->paintLinesIntoPicture(0, 0);
225         fState = kDrawn;
226     }
227 
228     if (fParagraphStyle.getDrawOptions() == DrawOptions::kReplay) {
229         // Replay the recorded picture
230         canvas->save();
231         canvas->translate(x, y);
232         fPicture->playback(canvas);
233         canvas->restore();
234     } else {
235         // Draw the picture
236         SkMatrix matrix = SkMatrix::Translate(x, y);
237         canvas->drawPicture(fPicture, &matrix, nullptr);
238     }
239 }
240 
resetContext()241 void ParagraphImpl::resetContext() {
242     fAlphabeticBaseline = 0;
243     fHeight = 0;
244     fWidth = 0;
245     fIdeographicBaseline = 0;
246     fMaxIntrinsicWidth = 0;
247     fMinIntrinsicWidth = 0;
248     fLongestLine = 0;
249     fMaxWidthWithTrailingSpaces = 0;
250     fExceededMaxLines = false;
251 }
252 
253 // shapeTextIntoEndlessLine is the thing that calls this method
computeCodeUnitProperties()254 bool ParagraphImpl::computeCodeUnitProperties() {
255 
256     if (nullptr == fUnicode) {
257         return false;
258     }
259 
260     // Get bidi regions
261     auto textDirection = fParagraphStyle.getTextDirection() == TextDirection::kLtr
262                               ? SkUnicode::TextDirection::kLTR
263                               : SkUnicode::TextDirection::kRTL;
264     if (!fUnicode->getBidiRegions(fText.c_str(), fText.size(), textDirection, &fBidiRegions)) {
265         return false;
266     }
267 
268     // Get all spaces
269     fUnicode->forEachCodepoint(fText.c_str(), fText.size(),
270        [this](SkUnichar unichar, int32_t start, int32_t end, int32_t count) {
271             if (fUnicode->isWhitespace(unichar)) {
272                 for (auto i = start; i < end; ++i) {
273                     fCodeUnitProperties[i] |=  CodeUnitFlags::kPartOfWhiteSpaceBreak;
274                 }
275             }
276             if (fUnicode->isSpace(unichar)) {
277                 for (auto i = start; i < end; ++i) {
278                     fCodeUnitProperties[i] |=  CodeUnitFlags::kPartOfIntraWordBreak;
279                 }
280             }
281        });
282 
283     // Get line breaks
284     std::vector<SkUnicode::LineBreakBefore> lineBreaks;
285     if (!fUnicode->getLineBreaks(fText.c_str(), fText.size(), &lineBreaks)) {
286         return false;
287     }
288     for (auto& lineBreak : lineBreaks) {
289         fCodeUnitProperties[lineBreak.pos] |= lineBreak.breakType == SkUnicode::LineBreakType::kHardLineBreak
290                                            ? CodeUnitFlags::kHardLineBreakBefore
291                                            : CodeUnitFlags::kSoftLineBreakBefore;
292     }
293 
294     // Get graphemes
295     std::vector<SkUnicode::Position> graphemes;
296     if (!fUnicode->getGraphemes(fText.c_str(), fText.size(), &graphemes)) {
297         return false;
298     }
299     for (auto pos : graphemes) {
300         fCodeUnitProperties[pos] |= CodeUnitFlags::kGraphemeStart;
301     }
302 
303     return true;
304 }
305 
is_ascii_7bit_space(int c)306 static bool is_ascii_7bit_space(int c) {
307     SkASSERT(c >= 0 && c <= 127);
308 
309     // Extracted from https://en.wikipedia.org/wiki/Whitespace_character
310     //
311     enum WS {
312         kHT    = 9,
313         kLF    = 10,
314         kVT    = 11,
315         kFF    = 12,
316         kCR    = 13,
317         kSP    = 32,    // too big to use as shift
318     };
319 #define M(shift)    (1 << (shift))
320     constexpr uint32_t kSpaceMask = M(kHT) | M(kLF) | M(kVT) | M(kFF) | M(kCR);
321     // we check for Space (32) explicitly, since it is too large to shift
322     return (c == kSP) || (c <= 31 && (kSpaceMask & M(c)));
323 #undef M
324 }
325 
Cluster(ParagraphImpl * owner,RunIndex runIndex,size_t start,size_t end,SkSpan<const char> text,SkScalar width,SkScalar height)326 Cluster::Cluster(ParagraphImpl* owner,
327                  RunIndex runIndex,
328                  size_t start,
329                  size_t end,
330                  SkSpan<const char> text,
331                  SkScalar width,
332                  SkScalar height)
333         : fOwner(owner)
334         , fRunIndex(runIndex)
335         , fTextRange(text.begin() - fOwner->text().begin(), text.end() - fOwner->text().begin())
336         , fGraphemeRange(EMPTY_RANGE)
337         , fStart(start)
338         , fEnd(end)
339         , fWidth(width)
340         , fSpacing(0)
341         , fHeight(height)
342         , fHalfLetterSpacing(0.0) {
343     size_t whiteSpacesBreakLen = 0;
344     size_t intraWordBreakLen = 0;
345 
346     const char* ch = text.begin();
347     if (text.end() - ch == 1 && *(unsigned char*)ch <= 0x7F) {
348         // I am not even sure it's worth it if we do not save a unicode call
349         if (is_ascii_7bit_space(*ch)) {
350             ++whiteSpacesBreakLen;
351         }
352     } else {
353         for (auto i = fTextRange.start; i < fTextRange.end; ++i) {
354             if (fOwner->codeUnitHasProperty(i, CodeUnitFlags::kPartOfWhiteSpaceBreak)) {
355                 ++whiteSpacesBreakLen;
356             }
357             if (fOwner->codeUnitHasProperty(i, CodeUnitFlags::kPartOfIntraWordBreak)) {
358                 ++intraWordBreakLen;
359             }
360         }
361     }
362 
363     fIsWhiteSpaceBreak = whiteSpacesBreakLen == fTextRange.width();
364     fIsIntraWordBreak = intraWordBreakLen == fTextRange.width();
365     fIsHardBreak = fOwner->codeUnitHasProperty(fTextRange.end, CodeUnitFlags::kHardLineBreakBefore);
366 }
367 
calculateWidth(size_t start,size_t end,bool clip) const368 SkScalar Run::calculateWidth(size_t start, size_t end, bool clip) const {
369     SkASSERT(start <= end);
370     // clip |= end == size();  // Clip at the end of the run?
371     SkScalar shift = 0;
372     if (fSpaced && end > start) {
373         shift = fShifts[clip ? end - 1 : end] - fShifts[start];
374     }
375     auto correction = 0.0f;
376     if (end > start && !fJustificationShifts.empty()) {
377         // This is not a typo: we are using Point as a pair of SkScalars
378         correction = fJustificationShifts[end - 1].fX -
379                      fJustificationShifts[start].fY;
380     }
381     return posX(end) - posX(start) + shift + correction;
382 }
383 
384 // Clusters in the order of the input text
buildClusterTable()385 void ParagraphImpl::buildClusterTable() {
386     int cluster_count = 1;
387     for (auto& run : fRuns) {
388         cluster_count += run.isPlaceholder() ? 1 : run.size();
389     }
390     fClusters.reserve_back(cluster_count);
391 
392     // Walk through all the run in the direction of input text
393     for (auto& run : fRuns) {
394         auto runIndex = run.index();
395         auto runStart = fClusters.size();
396         if (run.isPlaceholder()) {
397             // Add info to cluster indexes table (text -> cluster)
398             for (auto i = run.textRange().start; i < run.textRange().end; ++i) {
399               fClustersIndexFromCodeUnit[i] = fClusters.size();
400             }
401             // There are no glyphs but we want to have one cluster
402             fClusters.emplace_back(this, runIndex, 0ul, 1ul, this->text(run.textRange()), run.advance().fX, run.advance().fY);
403             fCodeUnitProperties[run.textRange().start] |= CodeUnitFlags::kSoftLineBreakBefore;
404             fCodeUnitProperties[run.textRange().end] |= CodeUnitFlags::kSoftLineBreakBefore;
405         } else {
406             // Walk through the glyph in the direction of input text
407             run.iterateThroughClustersInTextOrder([runIndex, this](size_t glyphStart,
408                                                                    size_t glyphEnd,
409                                                                    size_t charStart,
410                                                                    size_t charEnd,
411                                                                    SkScalar width,
412                                                                    SkScalar height) {
413                 SkASSERT(charEnd >= charStart);
414                 // Add info to cluster indexes table (text -> cluster)
415                 for (auto i = charStart; i < charEnd; ++i) {
416                   fClustersIndexFromCodeUnit[i] = fClusters.size();
417                 }
418                 SkSpan<const char> text(fText.c_str() + charStart, charEnd - charStart);
419                 fClusters.emplace_back(this, runIndex, glyphStart, glyphEnd, text, width, height);
420             });
421         }
422 
423         run.setClusterRange(runStart, fClusters.size());
424         fMaxIntrinsicWidth += run.advance().fX;
425     }
426     fClustersIndexFromCodeUnit[fText.size()] = fClusters.size();
427     fClusters.emplace_back(this, EMPTY_RUN, 0, 0, this->text({fText.size(), fText.size()}), 0, 0);
428 }
429 
spaceGlyphs()430 void ParagraphImpl::spaceGlyphs() {
431 
432     // Walk through all the clusters in the direction of shaped text
433     // (we have to walk through the styles in the same order, too)
434     SkScalar shift = 0;
435     for (auto& run : fRuns) {
436 
437         // Skip placeholder runs
438         if (run.isPlaceholder()) {
439             continue;
440         }
441 
442         bool soFarWhitespacesOnly = true;
443         run.iterateThroughClusters([this, &run, &shift, &soFarWhitespacesOnly](Cluster* cluster) {
444             // Shift the cluster (shift collected from the previous clusters)
445             run.shift(cluster, shift);
446 
447             // Synchronize styles (one cluster can be covered by few styles)
448             Block* currentStyle = this->fTextStyles.begin();
449             while (!cluster->startsIn(currentStyle->fRange)) {
450                 currentStyle++;
451                 SkASSERT(currentStyle != this->fTextStyles.end());
452             }
453 
454             SkASSERT(!currentStyle->fStyle.isPlaceholder());
455 
456             // Process word spacing
457             if (currentStyle->fStyle.getWordSpacing() != 0) {
458                 if (cluster->isWhitespaceBreak() && cluster->isSoftBreak()) {
459                     if (!soFarWhitespacesOnly) {
460                         shift += run.addSpacesAtTheEnd(currentStyle->fStyle.getWordSpacing(), cluster);
461                     }
462                 }
463             }
464             // Process letter spacing
465             if (currentStyle->fStyle.getLetterSpacing() != 0) {
466                 shift += run.addSpacesEvenly(currentStyle->fStyle.getLetterSpacing(), cluster);
467             }
468 
469             if (soFarWhitespacesOnly && !cluster->isWhitespaceBreak()) {
470                 soFarWhitespacesOnly = false;
471             }
472         });
473     }
474 }
475 
shapeTextIntoEndlessLine()476 bool ParagraphImpl::shapeTextIntoEndlessLine() {
477 
478     if (fText.size() == 0) {
479         return false;
480     }
481 
482     // Check the font-resolved text against the cache
483     if (fFontCollection->getParagraphCache()->findParagraph(this)) {
484         return true;
485     }
486 
487     if (!computeCodeUnitProperties()) {
488         return false;
489     }
490 
491     fFontSwitches.reset();
492 
493     OneLineShaper oneLineShaper(this);
494     auto result = oneLineShaper.shape();
495     fUnresolvedGlyphs = oneLineShaper.unresolvedGlyphs();
496 
497     // It's possible that one grapheme includes few runs; we cannot handle it
498     // so we break graphemes by the runs instead
499     // It's not the ideal solution and has to be revisited later
500     for (auto& run : fRuns) {
501         fCodeUnitProperties[run.fTextRange.start] |= CodeUnitFlags::kGraphemeStart;
502     }
503 
504     if (!result) {
505         return false;
506     } else {
507         // Add the paragraph to the cache
508         fFontCollection->getParagraphCache()->updateParagraph(this);
509         return true;
510     }
511 }
512 
breakShapedTextIntoLines(SkScalar maxWidth)513 void ParagraphImpl::breakShapedTextIntoLines(SkScalar maxWidth) {
514     TextWrapper textWrapper;
515     textWrapper.breakTextIntoLines(
516             this,
517             maxWidth,
518             [&](TextRange textExcludingSpaces,
519                 TextRange text,
520                 TextRange textWithNewlines,
521                 ClusterRange clusters,
522                 ClusterRange clustersWithGhosts,
523                 SkScalar widthWithSpaces,
524                 size_t startPos,
525                 size_t endPos,
526                 SkVector offset,
527                 SkVector advance,
528                 InternalLineMetrics metrics,
529                 bool addEllipsis) {
530                 // TODO: Take in account clipped edges
531                 auto& line = this->addLine(offset, advance, textExcludingSpaces, text, textWithNewlines, clusters, clustersWithGhosts, widthWithSpaces, metrics);
532                 if (addEllipsis) {
533                     line.createEllipsis(maxWidth, getEllipsis(), true);
534                 }
535 
536                 fLongestLine = std::max(fLongestLine, nearlyZero(advance.fX) ? widthWithSpaces : advance.fX);
537             });
538 
539     fHeight = textWrapper.height();
540     fWidth = maxWidth;
541     fMaxIntrinsicWidth = textWrapper.maxIntrinsicWidth();
542     fMinIntrinsicWidth = textWrapper.minIntrinsicWidth();
543     fAlphabeticBaseline = fLines.empty() ? fEmptyMetrics.alphabeticBaseline() : fLines.front().alphabeticBaseline();
544     fIdeographicBaseline = fLines.empty() ? fEmptyMetrics.ideographicBaseline() : fLines.front().ideographicBaseline();
545     fExceededMaxLines = textWrapper.exceededMaxLines();
546 }
547 
formatLines(SkScalar maxWidth)548 void ParagraphImpl::formatLines(SkScalar maxWidth) {
549     auto effectiveAlign = fParagraphStyle.effective_align();
550 
551     if (!SkScalarIsFinite(maxWidth) && effectiveAlign != TextAlign::kLeft) {
552         // Special case: clean all text in case of maxWidth == INF & align != left
553         // We had to go through shaping though because we need all the measurement numbers
554         fLines.reset();
555         return;
556     }
557 
558     for (auto& line : fLines) {
559         line.format(effectiveAlign, maxWidth);
560     }
561 }
562 
paintLinesIntoPicture(SkScalar x,SkScalar y)563 void ParagraphImpl::paintLinesIntoPicture(SkScalar x, SkScalar y) {
564     SkPictureRecorder recorder;
565     SkCanvas* textCanvas = recorder.beginRecording(this->getMaxWidth(), this->getHeight());
566 
567     auto bounds = SkRect::MakeEmpty();
568     for (auto& line : fLines) {
569         auto boundaries = line.paint(textCanvas, x, y);
570         bounds.joinPossiblyEmptyRect(boundaries);
571     }
572 
573     fPicture = recorder.finishRecordingAsPictureWithCull(bounds);
574 }
575 
paintLines(SkCanvas * canvas,SkScalar x,SkScalar y)576 void ParagraphImpl::paintLines(SkCanvas* canvas, SkScalar x, SkScalar y) {
577     for (auto& line : fLines) {
578         line.paint(canvas, x, y);
579     }
580 }
581 
resolveStrut()582 void ParagraphImpl::resolveStrut() {
583     auto strutStyle = this->paragraphStyle().getStrutStyle();
584     if (!strutStyle.getStrutEnabled() || strutStyle.getFontSize() < 0) {
585         return;
586     }
587 
588     std::vector<sk_sp<SkTypeface>> typefaces = fFontCollection->findTypefaces(strutStyle.getFontFamilies(), strutStyle.getFontStyle());
589     if (typefaces.empty()) {
590         SkDEBUGF("Could not resolve strut font\n");
591         return;
592     }
593 
594     SkFont font(typefaces.front(), strutStyle.getFontSize());
595     SkFontMetrics metrics;
596     font.getMetrics(&metrics);
597 
598     if (strutStyle.getHeightOverride()) {
599         auto strutHeight = metrics.fDescent - metrics.fAscent;
600         auto strutMultiplier = strutStyle.getHeight() * strutStyle.getFontSize();
601         fStrutMetrics = InternalLineMetrics(
602             (metrics.fAscent / strutHeight) * strutMultiplier,
603             (metrics.fDescent / strutHeight) * strutMultiplier,
604                 strutStyle.getLeading() < 0 ? 0 : strutStyle.getLeading() * strutStyle.getFontSize(),
605             metrics.fAscent, metrics.fDescent, metrics.fLeading);
606     } else {
607         fStrutMetrics = InternalLineMetrics(
608                 metrics.fAscent,
609                 metrics.fDescent,
610                 strutStyle.getLeading() < 0 ? 0 : strutStyle.getLeading() * strutStyle.getFontSize());
611     }
612     fStrutMetrics.setForceStrut(this->paragraphStyle().getStrutStyle().getForceStrutHeight());
613 }
614 
findAllBlocks(TextRange textRange)615 BlockRange ParagraphImpl::findAllBlocks(TextRange textRange) {
616     BlockIndex begin = EMPTY_BLOCK;
617     BlockIndex end = EMPTY_BLOCK;
618     for (size_t index = 0; index < fTextStyles.size(); ++index) {
619         auto& block = fTextStyles[index];
620         if (block.fRange.end <= textRange.start) {
621             continue;
622         }
623         if (block.fRange.start >= textRange.end) {
624             break;
625         }
626         if (begin == EMPTY_BLOCK) {
627             begin = index;
628         }
629         end = index;
630     }
631 
632     if (begin == EMPTY_INDEX || end == EMPTY_INDEX) {
633         // It's possible if some text is not covered with any text style
634         // Not in Flutter but in direct use of SkParagraph
635         return EMPTY_RANGE;
636     }
637 
638     return { begin, end + 1 };
639 }
640 
addLine(SkVector offset,SkVector advance,TextRange textExcludingSpaces,TextRange text,TextRange textIncludingNewLines,ClusterRange clusters,ClusterRange clustersWithGhosts,SkScalar widthWithSpaces,InternalLineMetrics sizes)641 TextLine& ParagraphImpl::addLine(SkVector offset,
642                                  SkVector advance,
643                                  TextRange textExcludingSpaces,
644                                  TextRange text,
645                                  TextRange textIncludingNewLines,
646                                  ClusterRange clusters,
647                                  ClusterRange clustersWithGhosts,
648                                  SkScalar widthWithSpaces,
649                                  InternalLineMetrics sizes) {
650     // Define a list of styles that covers the line
651     auto blocks = findAllBlocks(textExcludingSpaces);
652     return fLines.emplace_back(this, offset, advance, blocks,
653                                textExcludingSpaces, text, textIncludingNewLines,
654                                clusters, clustersWithGhosts, widthWithSpaces, sizes);
655 }
656 
657 // Returns a vector of bounding boxes that enclose all text between
658 // start and end glyph indexes, including start and excluding end
getRectsForRange(unsigned start,unsigned end,RectHeightStyle rectHeightStyle,RectWidthStyle rectWidthStyle)659 std::vector<TextBox> ParagraphImpl::getRectsForRange(unsigned start,
660                                                      unsigned end,
661                                                      RectHeightStyle rectHeightStyle,
662                                                      RectWidthStyle rectWidthStyle) {
663     std::vector<TextBox> results;
664     if (fText.isEmpty()) {
665         if (start == 0 && end > 0) {
666             // On account of implied "\n" that is always at the end of the text
667             //SkDebugf("getRectsForRange(%d, %d): %f\n", start, end, fHeight);
668             results.emplace_back(SkRect::MakeXYWH(0, 0, 0, fHeight), fParagraphStyle.getTextDirection());
669         }
670         return results;
671     }
672 
673     ensureUTF16Mapping();
674 
675     if (start >= end || start > fUTF8IndexForUTF16Index.size() || end == 0) {
676         return results;
677     }
678 
679     // Adjust the text to grapheme edges
680     // Apparently, text editor CAN move inside graphemes but CANNOT select a part of it.
681     // I don't know why - the solution I have here returns an empty box for every query that
682     // does not contain an end of a grapheme.
683     // Once a cursor is inside a complex grapheme I can press backspace and cause trouble.
684     // To avoid any problems, I will not allow any selection of a part of a grapheme.
685     // One flutter test fails because of it but the editing experience is correct
686     // (although you have to press the cursor many times before it moves to the next grapheme).
687     TextRange text(fText.size(), fText.size());
688     // TODO: This is probably a temp change that makes SkParagraph work as TxtLib
689     //  (so we can compare the results). We now include in the selection box only the graphemes
690     //  that belongs to the given [start:end) range entirely (not the ones that intersect with it)
691     if (start < fUTF8IndexForUTF16Index.size()) {
692         auto utf8 = fUTF8IndexForUTF16Index[start];
693         // If start points to a trailing surrogate, skip it
694         if (start > 0 && fUTF8IndexForUTF16Index[start - 1] == utf8) {
695             utf8 = fUTF8IndexForUTF16Index[start + 1];
696         }
697         text.start = findNextGraphemeBoundary(utf8);
698     }
699     if (end < fUTF8IndexForUTF16Index.size()) {
700         auto utf8 = findPreviousGraphemeBoundary(fUTF8IndexForUTF16Index[end]);
701         text.end = utf8;
702     }
703     //SkDebugf("getRectsForRange(%d,%d) -> (%d:%d)\n", start, end, text.start, text.end);
704     for (auto& line : fLines) {
705         auto lineText = line.textWithNewlines();
706         auto intersect = lineText * text;
707         if (intersect.empty() && lineText.start != text.start) {
708             continue;
709         }
710 
711         line.getRectsForRange(intersect, rectHeightStyle, rectWidthStyle, results);
712     }
713 /*
714     SkDebugf("getRectsForRange(%d, %d)\n", start, end);
715     for (auto& r : results) {
716         r.rect.fLeft = littleRound(r.rect.fLeft);
717         r.rect.fRight = littleRound(r.rect.fRight);
718         r.rect.fTop = littleRound(r.rect.fTop);
719         r.rect.fBottom = littleRound(r.rect.fBottom);
720         SkDebugf("[%f:%f * %f:%f]\n", r.rect.fLeft, r.rect.fRight, r.rect.fTop, r.rect.fBottom);
721     }
722 */
723     return results;
724 }
725 
getRectsForPlaceholders()726 std::vector<TextBox> ParagraphImpl::getRectsForPlaceholders() {
727   std::vector<TextBox> boxes;
728   if (fText.isEmpty()) {
729        return boxes;
730   }
731   if (fPlaceholders.size() == 1) {
732        // We always have one fake placeholder
733        return boxes;
734   }
735   for (auto& line : fLines) {
736       line.getRectsForPlaceholders(boxes);
737   }
738   /*
739   SkDebugf("getRectsForPlaceholders('%s'): %d\n", fText.c_str(), boxes.size());
740   for (auto& r : boxes) {
741       r.rect.fLeft = littleRound(r.rect.fLeft);
742       r.rect.fRight = littleRound(r.rect.fRight);
743       r.rect.fTop = littleRound(r.rect.fTop);
744       r.rect.fBottom = littleRound(r.rect.fBottom);
745       SkDebugf("[%f:%f * %f:%f] %s\n", r.rect.fLeft, r.rect.fRight, r.rect.fTop, r.rect.fBottom,
746                (r.direction == TextDirection::kLtr ? "left" : "right"));
747   }
748   */
749   return boxes;
750 }
751 
752 // TODO: Optimize (save cluster <-> codepoint connection)
getGlyphPositionAtCoordinate(SkScalar dx,SkScalar dy)753 PositionWithAffinity ParagraphImpl::getGlyphPositionAtCoordinate(SkScalar dx, SkScalar dy) {
754 
755     if (fText.isEmpty()) {
756         return {0, Affinity::kDownstream};
757     }
758 
759     ensureUTF16Mapping();
760 
761     for (auto& line : fLines) {
762         // Let's figure out if we can stop looking
763         auto offsetY = line.offset().fY;
764         if (dy >= offsetY + line.height() && &line != &fLines.back()) {
765             // This line is not good enough
766             continue;
767         }
768 
769         // This is so far the the line vertically closest to our coordinates
770         // (or the first one, or the only one - all the same)
771 
772         auto result = line.getGlyphPositionAtCoordinate(dx);
773         //SkDebugf("getGlyphPositionAtCoordinate(%f, %f): %d %s\n", dx, dy, result.position,
774         //   result.affinity == Affinity::kUpstream ? "up" : "down");
775         return result;
776     }
777 
778     return {0, Affinity::kDownstream};
779 }
780 
781 // Finds the first and last glyphs that define a word containing
782 // the glyph at index offset.
783 // By "glyph" they mean a character index - indicated by Minikin's code
getWordBoundary(unsigned offset)784 SkRange<size_t> ParagraphImpl::getWordBoundary(unsigned offset) {
785 
786     if (fWords.empty()) {
787         if (!fUnicode->getWords(fText.c_str(), fText.size(), &fWords)) {
788             return {0, 0 };
789         }
790     }
791 
792     int32_t start = 0;
793     int32_t end = 0;
794     for (size_t i = 0; i < fWords.size(); ++i) {
795         auto word = fWords[i];
796         if (word <= offset) {
797             start = word;
798             end = word;
799         } else if (word > offset) {
800             end = word;
801             break;
802         }
803     }
804 
805     //SkDebugf("getWordBoundary(%d): %d - %d\n", offset, start, end);
806     return { SkToU32(start), SkToU32(end) };
807 }
808 
getLineMetrics(std::vector<LineMetrics> & metrics)809 void ParagraphImpl::getLineMetrics(std::vector<LineMetrics>& metrics) {
810     metrics.clear();
811     for (auto& line : fLines) {
812         metrics.emplace_back(line.getMetrics());
813     }
814 }
815 
text(TextRange textRange)816 SkSpan<const char> ParagraphImpl::text(TextRange textRange) {
817     SkASSERT(textRange.start <= fText.size() && textRange.end <= fText.size());
818     auto start = fText.c_str() + textRange.start;
819     return SkSpan<const char>(start, textRange.width());
820 }
821 
clusters(ClusterRange clusterRange)822 SkSpan<Cluster> ParagraphImpl::clusters(ClusterRange clusterRange) {
823     SkASSERT(clusterRange.start < fClusters.size() && clusterRange.end <= fClusters.size());
824     return SkSpan<Cluster>(&fClusters[clusterRange.start], clusterRange.width());
825 }
826 
cluster(ClusterIndex clusterIndex)827 Cluster& ParagraphImpl::cluster(ClusterIndex clusterIndex) {
828     SkASSERT(clusterIndex < fClusters.size());
829     return fClusters[clusterIndex];
830 }
831 
runByCluster(ClusterIndex clusterIndex)832 Run& ParagraphImpl::runByCluster(ClusterIndex clusterIndex) {
833     auto start = cluster(clusterIndex);
834     return this->run(start.fRunIndex);
835 }
836 
blocks(BlockRange blockRange)837 SkSpan<Block> ParagraphImpl::blocks(BlockRange blockRange) {
838     SkASSERT(blockRange.start < fTextStyles.size() && blockRange.end <= fTextStyles.size());
839     return SkSpan<Block>(&fTextStyles[blockRange.start], blockRange.width());
840 }
841 
block(BlockIndex blockIndex)842 Block& ParagraphImpl::block(BlockIndex blockIndex) {
843     SkASSERT(blockIndex < fTextStyles.size());
844     return fTextStyles[blockIndex];
845 }
846 
setState(InternalState state)847 void ParagraphImpl::setState(InternalState state) {
848     if (fState <= state) {
849         fState = state;
850         return;
851     }
852 
853     fState = state;
854     switch (fState) {
855         case kUnknown:
856             fRuns.reset();
857             fCodeUnitProperties.reset();
858             fCodeUnitProperties.push_back_n(fText.size() + 1, kNoCodeUnitFlag);
859             fWords.clear();
860             fBidiRegions.clear();
861             fUTF8IndexForUTF16Index.reset();
862             fUTF16IndexForUTF8Index.reset();
863             [[fallthrough]];
864 
865         case kShaped:
866             fClusters.reset();
867             [[fallthrough]];
868 
869         case kClusterized:
870         case kMarked:
871         case kLineBroken:
872             this->resetContext();
873             this->resolveStrut();
874             this->computeEmptyMetrics();
875             this->resetShifts();
876             fLines.reset();
877             [[fallthrough]];
878 
879         case kFormatted:
880             fPicture = nullptr;
881             [[fallthrough]];
882 
883         case kDrawn:
884         default:
885             break;
886     }
887 }
888 
computeEmptyMetrics()889 void ParagraphImpl::computeEmptyMetrics() {
890 
891     // The empty metrics is used to define the height of the empty lines
892     // Unfortunately, Flutter has 2 different cases for that:
893     // 1. An empty line inside the text
894     // 2. An empty paragraph
895     // In the first case SkParagraph takes the metrics from the default paragraph style
896     // In the second case it should take it from the current text style
897     bool emptyParagraph = fRuns.empty();
898     TextStyle textStyle = paragraphStyle().getTextStyle();
899     if (emptyParagraph && !fTextStyles.empty()) {
900         textStyle = fTextStyles.back().fStyle;
901     }
902 
903     auto typefaces = fontCollection()->findTypefaces(
904       textStyle.getFontFamilies(), textStyle.getFontStyle());
905     auto typeface = typefaces.empty() ? nullptr : typefaces.front();
906 
907     SkFont font(typeface, textStyle.getFontSize());
908     fEmptyMetrics = InternalLineMetrics(font, paragraphStyle().getStrutStyle().getForceStrutHeight());
909 
910     if (!paragraphStyle().getStrutStyle().getForceStrutHeight() &&
911         textStyle.getHeightOverride()) {
912         const auto intrinsicHeight = fEmptyMetrics.height();
913         const auto strutHeight = textStyle.getHeight() * textStyle.getFontSize();
914         if (paragraphStyle().getStrutStyle().getHalfLeading()) {
915             fEmptyMetrics.update(
916                 fEmptyMetrics.ascent(),
917                 fEmptyMetrics.descent(),
918                 fEmptyMetrics.leading() + strutHeight - intrinsicHeight);
919         } else {
920             const auto multiplier = strutHeight / intrinsicHeight;
921             fEmptyMetrics.update(
922                 fEmptyMetrics.ascent() * multiplier,
923                 fEmptyMetrics.descent() * multiplier,
924                 fEmptyMetrics.leading() * multiplier);
925         }
926     }
927 
928     if (emptyParagraph) {
929         // For an empty text we apply both TextHeightBehaviour flags
930         // In case of non-empty paragraph TextHeightBehaviour flags will be applied at the appropriate place
931         // We have to do it here because we skip wrapping for an empty text
932         auto disableFirstAscent = (paragraphStyle().getTextHeightBehavior() & TextHeightBehavior::kDisableFirstAscent) == TextHeightBehavior::kDisableFirstAscent;
933         auto disableLastDescent = (paragraphStyle().getTextHeightBehavior() & TextHeightBehavior::kDisableLastDescent) == TextHeightBehavior::kDisableLastDescent;
934         fEmptyMetrics.update(
935             disableFirstAscent ? fEmptyMetrics.rawAscent() : fEmptyMetrics.ascent(),
936             disableLastDescent ? fEmptyMetrics.rawDescent() : fEmptyMetrics.descent(),
937             fEmptyMetrics.leading());
938     }
939 
940     if (fParagraphStyle.getStrutStyle().getStrutEnabled()) {
941         fStrutMetrics.updateLineMetrics(fEmptyMetrics);
942     }
943 }
944 
getEllipsis() const945 SkString ParagraphImpl::getEllipsis() const {
946 
947     auto ellipsis8 = fParagraphStyle.getEllipsis();
948     auto ellipsis16 = fParagraphStyle.getEllipsisUtf16();
949     if (!ellipsis8.isEmpty()) {
950         return ellipsis8;
951     } else {
952         return fUnicode->convertUtf16ToUtf8(fParagraphStyle.getEllipsisUtf16());
953     }
954 }
955 
updateText(size_t from,SkString text)956 void ParagraphImpl::updateText(size_t from, SkString text) {
957   fText.remove(from, from + text.size());
958   fText.insert(from, text);
959   fState = kUnknown;
960   fOldWidth = 0;
961   fOldHeight = 0;
962 }
963 
updateFontSize(size_t from,size_t to,SkScalar fontSize)964 void ParagraphImpl::updateFontSize(size_t from, size_t to, SkScalar fontSize) {
965 
966   SkASSERT(from == 0 && to == fText.size());
967   auto defaultStyle = fParagraphStyle.getTextStyle();
968   defaultStyle.setFontSize(fontSize);
969   fParagraphStyle.setTextStyle(defaultStyle);
970 
971   for (auto& textStyle : fTextStyles) {
972     textStyle.fStyle.setFontSize(fontSize);
973   }
974 
975   fState = kUnknown;
976   fOldWidth = 0;
977   fOldHeight = 0;
978 }
979 
updateTextAlign(TextAlign textAlign)980 void ParagraphImpl::updateTextAlign(TextAlign textAlign) {
981     fParagraphStyle.setTextAlign(textAlign);
982 
983     if (fState >= kLineBroken) {
984         fState = kLineBroken;
985     }
986 }
987 
updateForegroundPaint(size_t from,size_t to,SkPaint paint)988 void ParagraphImpl::updateForegroundPaint(size_t from, size_t to, SkPaint paint) {
989     SkASSERT(from == 0 && to == fText.size());
990     auto defaultStyle = fParagraphStyle.getTextStyle();
991     defaultStyle.setForegroundColor(paint);
992     fParagraphStyle.setTextStyle(defaultStyle);
993 
994     for (auto& textStyle : fTextStyles) {
995         textStyle.fStyle.setForegroundColor(paint);
996     }
997 }
998 
updateBackgroundPaint(size_t from,size_t to,SkPaint paint)999 void ParagraphImpl::updateBackgroundPaint(size_t from, size_t to, SkPaint paint) {
1000     SkASSERT(from == 0 && to == fText.size());
1001     auto defaultStyle = fParagraphStyle.getTextStyle();
1002     defaultStyle.setBackgroundColor(paint);
1003     fParagraphStyle.setTextStyle(defaultStyle);
1004 
1005     for (auto& textStyle : fTextStyles) {
1006         textStyle.fStyle.setBackgroundColor(paint);
1007     }
1008 }
1009 
findPreviousGraphemeBoundary(TextIndex utf8)1010 TextIndex ParagraphImpl::findPreviousGraphemeBoundary(TextIndex utf8) {
1011     while (utf8 > 0 &&
1012           (fCodeUnitProperties[utf8] & CodeUnitFlags::kGraphemeStart) == 0) {
1013         --utf8;
1014     }
1015     return utf8;
1016 }
1017 
findNextGraphemeBoundary(TextIndex utf8)1018 TextIndex ParagraphImpl::findNextGraphemeBoundary(TextIndex utf8) {
1019     while (utf8 < fText.size() &&
1020           (fCodeUnitProperties[utf8] & CodeUnitFlags::kGraphemeStart) == 0) {
1021         ++utf8;
1022     }
1023     return utf8;
1024 }
1025 
ensureUTF16Mapping()1026 void ParagraphImpl::ensureUTF16Mapping() {
1027     if (!fUTF16IndexForUTF8Index.empty()) {
1028         return;
1029     }
1030     // Fill out code points 16
1031     auto ptr = fText.c_str();
1032     auto end = fText.c_str() + fText.size();
1033     while (ptr < end) {
1034 
1035         size_t index = ptr - fText.c_str();
1036         SkUnichar u = SkUTF::NextUTF8(&ptr, end);
1037 
1038         // All utf8 units refer to the same codepoint
1039         size_t next = ptr - fText.c_str();
1040         for (auto i = index; i < next; ++i) {
1041             fUTF16IndexForUTF8Index.emplace_back(fUTF8IndexForUTF16Index.size());
1042         }
1043         SkASSERT(fUTF16IndexForUTF8Index.size() == next);
1044 
1045         // One or two codepoints refer to the same text index
1046         uint16_t buffer[2];
1047         size_t count = SkUTF::ToUTF16(u, buffer);
1048         fUTF8IndexForUTF16Index.emplace_back(index);
1049         if (count > 1) {
1050             fUTF8IndexForUTF16Index.emplace_back(index);
1051         }
1052     }
1053     fUTF16IndexForUTF8Index.emplace_back(fUTF8IndexForUTF16Index.size());
1054     fUTF8IndexForUTF16Index.emplace_back(fText.size());
1055 }
1056 
visit(const Visitor & visitor)1057 void ParagraphImpl::visit(const Visitor& visitor) {
1058     int lineNumber = 0;
1059     for (auto& line : fLines) {
1060         line.ensureTextBlobCachePopulated();
1061         for (auto& rec : line.fTextBlobCache) {
1062             SkTextBlob::Iter iter(*rec.fBlob);
1063             SkTextBlob::Iter::ExperimentalRun run;
1064 
1065             SkSTArray<128, uint32_t> clusterStorage;
1066             const Run* R = rec.fVisitor_Run;
1067             const uint32_t* clusterPtr = &R->fClusterIndexes[0];
1068 
1069             if (R->fClusterStart > 0) {
1070                 int count = R->fClusterIndexes.count();
1071                 clusterStorage.reset(count);
1072                 for (int i = 0; i < count; ++i) {
1073                     clusterStorage[i] = R->fClusterStart + R->fClusterIndexes[i];
1074                 }
1075                 clusterPtr = &clusterStorage[0];
1076             }
1077             clusterPtr += rec.fVisitor_Pos;
1078 
1079             while (iter.experimentalNext(&run)) {
1080                 const Paragraph::VisitorInfo info = {
1081                     run.font,
1082                     rec.fOffset,
1083                     rec.fClipRect.fRight,
1084                     run.count,
1085                     run.glyphs,
1086                     run.positions,
1087                     clusterPtr,
1088                     0,  // flags
1089                 };
1090                 visitor(lineNumber, &info);
1091                 clusterPtr += run.count;
1092             }
1093         }
1094         visitor(lineNumber, nullptr);   // signal end of line
1095         lineNumber += 1;
1096     }
1097 }
1098 
1099 }  // namespace textlayout
1100 }  // namespace skia
1101