• 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::unique_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::unique_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::unique_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::unique_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) {
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 text,
519                 TextRange textWithSpaces,
520                 ClusterRange clusters,
521                 ClusterRange clustersWithGhosts,
522                 SkScalar widthWithSpaces,
523                 size_t startPos,
524                 size_t endPos,
525                 SkVector offset,
526                 SkVector advance,
527                 InternalLineMetrics metrics,
528                 bool addEllipsis) {
529                 // TODO: Take in account clipped edges
530                 auto& line = this->addLine(offset, advance, text, textWithSpaces, clusters, clustersWithGhosts, widthWithSpaces, metrics);
531                 if (addEllipsis) {
532                     line.createEllipsis(maxWidth, getEllipsis(), true);
533                 }
534 
535                 fLongestLine = std::max(fLongestLine, nearlyZero(advance.fX) ? widthWithSpaces : advance.fX);
536             });
537 
538     fHeight = textWrapper.height();
539     fWidth = maxWidth;
540     fMaxIntrinsicWidth = textWrapper.maxIntrinsicWidth();
541     fMinIntrinsicWidth = textWrapper.minIntrinsicWidth();
542     fAlphabeticBaseline = fLines.empty() ? fEmptyMetrics.alphabeticBaseline() : fLines.front().alphabeticBaseline();
543     fIdeographicBaseline = fLines.empty() ? fEmptyMetrics.ideographicBaseline() : fLines.front().ideographicBaseline();
544     fExceededMaxLines = textWrapper.exceededMaxLines();
545 }
546 
formatLines(SkScalar maxWidth)547 void ParagraphImpl::formatLines(SkScalar maxWidth) {
548     auto effectiveAlign = fParagraphStyle.effective_align();
549 
550     if (!SkScalarIsFinite(maxWidth) && effectiveAlign != TextAlign::kLeft) {
551         // Special case: clean all text in case of maxWidth == INF & align != left
552         // We had to go through shaping though because we need all the measurement numbers
553         fLines.reset();
554         return;
555     }
556 
557     for (auto& line : fLines) {
558         line.format(effectiveAlign, maxWidth);
559     }
560 }
561 
paintLinesIntoPicture(SkScalar x,SkScalar y)562 void ParagraphImpl::paintLinesIntoPicture(SkScalar x, SkScalar y) {
563     SkPictureRecorder recorder;
564     SkCanvas* textCanvas = recorder.beginRecording(this->getMaxWidth(), this->getHeight());
565 
566     auto bounds = SkRect::MakeEmpty();
567     for (auto& line : fLines) {
568         auto boundaries = line.paint(textCanvas, x, y);
569         bounds.joinPossiblyEmptyRect(boundaries);
570     }
571 
572     fPicture = recorder.finishRecordingAsPictureWithCull(bounds);
573 }
574 
paintLines(SkCanvas * canvas,SkScalar x,SkScalar y)575 void ParagraphImpl::paintLines(SkCanvas* canvas, SkScalar x, SkScalar y) {
576     for (auto& line : fLines) {
577         line.paint(canvas, x, y);
578     }
579 }
580 
resolveStrut()581 void ParagraphImpl::resolveStrut() {
582     auto strutStyle = this->paragraphStyle().getStrutStyle();
583     if (!strutStyle.getStrutEnabled() || strutStyle.getFontSize() < 0) {
584         return;
585     }
586 
587     std::vector<sk_sp<SkTypeface>> typefaces = fFontCollection->findTypefaces(strutStyle.getFontFamilies(), strutStyle.getFontStyle());
588     if (typefaces.empty()) {
589         SkDEBUGF("Could not resolve strut font\n");
590         return;
591     }
592 
593     SkFont font(typefaces.front(), strutStyle.getFontSize());
594     SkFontMetrics metrics;
595     font.getMetrics(&metrics);
596 
597     if (strutStyle.getHeightOverride()) {
598         auto strutHeight = metrics.fDescent - metrics.fAscent;
599         auto strutMultiplier = strutStyle.getHeight() * strutStyle.getFontSize();
600         fStrutMetrics = InternalLineMetrics(
601             (metrics.fAscent / strutHeight) * strutMultiplier,
602             (metrics.fDescent / strutHeight) * strutMultiplier,
603                 strutStyle.getLeading() < 0 ? 0 : strutStyle.getLeading() * strutStyle.getFontSize());
604     } else {
605         fStrutMetrics = InternalLineMetrics(
606                 metrics.fAscent,
607                 metrics.fDescent,
608                 strutStyle.getLeading() < 0 ? 0
609                                             : strutStyle.getLeading() * strutStyle.getFontSize());
610     }
611     fStrutMetrics.setForceStrut(this->paragraphStyle().getStrutStyle().getForceStrutHeight());
612 }
613 
findAllBlocks(TextRange textRange)614 BlockRange ParagraphImpl::findAllBlocks(TextRange textRange) {
615     BlockIndex begin = EMPTY_BLOCK;
616     BlockIndex end = EMPTY_BLOCK;
617     for (size_t index = 0; index < fTextStyles.size(); ++index) {
618         auto& block = fTextStyles[index];
619         if (block.fRange.end <= textRange.start) {
620             continue;
621         }
622         if (block.fRange.start >= textRange.end) {
623             break;
624         }
625         if (begin == EMPTY_BLOCK) {
626             begin = index;
627         }
628         end = index;
629     }
630 
631     if (begin == EMPTY_INDEX || end == EMPTY_INDEX) {
632         // It's possible if some text is not covered with any text style
633         // Not in Flutter but in direct use of SkParagraph
634         return EMPTY_RANGE;
635     }
636 
637     return { begin, end + 1 };
638 }
639 
addLine(SkVector offset,SkVector advance,TextRange text,TextRange textWithSpaces,ClusterRange clusters,ClusterRange clustersWithGhosts,SkScalar widthWithSpaces,InternalLineMetrics sizes)640 TextLine& ParagraphImpl::addLine(SkVector offset,
641                                  SkVector advance,
642                                  TextRange text,
643                                  TextRange textWithSpaces,
644                                  ClusterRange clusters,
645                                  ClusterRange clustersWithGhosts,
646                                  SkScalar widthWithSpaces,
647                                  InternalLineMetrics sizes) {
648     // Define a list of styles that covers the line
649     auto blocks = findAllBlocks(text);
650     return fLines.emplace_back(this, offset, advance, blocks, text, textWithSpaces, clusters, clustersWithGhosts, widthWithSpaces, sizes);
651 }
652 
653 // Returns a vector of bounding boxes that enclose all text between
654 // start and end glyph indexes, including start and excluding end
getRectsForRange(unsigned start,unsigned end,RectHeightStyle rectHeightStyle,RectWidthStyle rectWidthStyle)655 std::vector<TextBox> ParagraphImpl::getRectsForRange(unsigned start,
656                                                      unsigned end,
657                                                      RectHeightStyle rectHeightStyle,
658                                                      RectWidthStyle rectWidthStyle) {
659     std::vector<TextBox> results;
660     if (fText.isEmpty()) {
661         if (start == 0 && end > 0) {
662             // On account of implied "\n" that is always at the end of the text
663             //SkDebugf("getRectsForRange(%d, %d): %f\n", start, end, fHeight);
664             results.emplace_back(SkRect::MakeXYWH(0, 0, 0, fHeight), fParagraphStyle.getTextDirection());
665         }
666         return results;
667     }
668 
669     ensureUTF16Mapping();
670 
671     if (start >= end || start > fUTF8IndexForUTF16Index.size() || end == 0) {
672         return results;
673     }
674 
675     // Adjust the text to grapheme edges
676     // Apparently, text editor CAN move inside graphemes but CANNOT select a part of it.
677     // I don't know why - the solution I have here returns an empty box for every query that
678     // does not contain an end of a grapheme.
679     // Once a cursor is inside a complex grapheme I can press backspace and cause trouble.
680     // To avoid any problems, I will not allow any selection of a part of a grapheme.
681     // One flutter test fails because of it but the editing experience is correct
682     // (although you have to press the cursor many times before it moves to the next grapheme).
683     TextRange text(fText.size(), fText.size());
684     // TODO: This is probably a temp change that makes SkParagraph work as TxtLib
685     //  (so we can compare the results). We now include in the selection box only the graphemes
686     //  that belongs to the given [start:end) range entirely (not the ones that intersect with it)
687     if (start < fUTF8IndexForUTF16Index.size()) {
688         auto utf8 = fUTF8IndexForUTF16Index[start];
689         // If start points to a trailing surrogate, skip it
690         if (start > 0 && fUTF8IndexForUTF16Index[start - 1] == utf8) {
691             utf8 = fUTF8IndexForUTF16Index[start + 1];
692         }
693         text.start = findNextGraphemeBoundary(utf8);
694     }
695     if (end < fUTF8IndexForUTF16Index.size()) {
696         auto utf8 = findPreviousGraphemeBoundary(fUTF8IndexForUTF16Index[end]);
697         text.end = utf8;
698     }
699     //SkDebugf("getRectsForRange(%d,%d) -> (%d:%d)\n", start, end, text.start, text.end);
700     for (auto& line : fLines) {
701         auto lineText = line.textWithSpaces();
702         auto intersect = lineText * text;
703         if (intersect.empty() && lineText.start != text.start) {
704             continue;
705         }
706 
707         line.getRectsForRange(intersect, rectHeightStyle, rectWidthStyle, results);
708     }
709 /*
710     SkDebugf("getRectsForRange(%d, %d)\n", start, end);
711     for (auto& r : results) {
712         r.rect.fLeft = littleRound(r.rect.fLeft);
713         r.rect.fRight = littleRound(r.rect.fRight);
714         r.rect.fTop = littleRound(r.rect.fTop);
715         r.rect.fBottom = littleRound(r.rect.fBottom);
716         SkDebugf("[%f:%f * %f:%f]\n", r.rect.fLeft, r.rect.fRight, r.rect.fTop, r.rect.fBottom);
717     }
718 */
719     return results;
720 }
721 
getRectsForPlaceholders()722 std::vector<TextBox> ParagraphImpl::getRectsForPlaceholders() {
723   std::vector<TextBox> boxes;
724   if (fText.isEmpty()) {
725        return boxes;
726   }
727   if (fPlaceholders.size() == 1) {
728        // We always have one fake placeholder
729        return boxes;
730   }
731   for (auto& line : fLines) {
732       line.getRectsForPlaceholders(boxes);
733   }
734   /*
735   SkDebugf("getRectsForPlaceholders('%s'): %d\n", fText.c_str(), boxes.size());
736   for (auto& r : boxes) {
737       r.rect.fLeft = littleRound(r.rect.fLeft);
738       r.rect.fRight = littleRound(r.rect.fRight);
739       r.rect.fTop = littleRound(r.rect.fTop);
740       r.rect.fBottom = littleRound(r.rect.fBottom);
741       SkDebugf("[%f:%f * %f:%f] %s\n", r.rect.fLeft, r.rect.fRight, r.rect.fTop, r.rect.fBottom,
742                (r.direction == TextDirection::kLtr ? "left" : "right"));
743   }
744   */
745   return boxes;
746 }
747 
748 // TODO: Optimize (save cluster <-> codepoint connection)
getGlyphPositionAtCoordinate(SkScalar dx,SkScalar dy)749 PositionWithAffinity ParagraphImpl::getGlyphPositionAtCoordinate(SkScalar dx, SkScalar dy) {
750 
751     if (fText.isEmpty()) {
752         return {0, Affinity::kDownstream};
753     }
754 
755     ensureUTF16Mapping();
756 
757     for (auto& line : fLines) {
758         // Let's figure out if we can stop looking
759         auto offsetY = line.offset().fY;
760         if (dy >= offsetY + line.height() && &line != &fLines.back()) {
761             // This line is not good enough
762             continue;
763         }
764 
765         // This is so far the the line vertically closest to our coordinates
766         // (or the first one, or the only one - all the same)
767 
768         auto result = line.getGlyphPositionAtCoordinate(dx);
769         //SkDebugf("getGlyphPositionAtCoordinate(%f, %f): %d %s\n", dx, dy, result.position,
770         //   result.affinity == Affinity::kUpstream ? "up" : "down");
771         return result;
772     }
773 
774     return {0, Affinity::kDownstream};
775 }
776 
777 // Finds the first and last glyphs that define a word containing
778 // the glyph at index offset.
779 // By "glyph" they mean a character index - indicated by Minikin's code
getWordBoundary(unsigned offset)780 SkRange<size_t> ParagraphImpl::getWordBoundary(unsigned offset) {
781 
782     if (fWords.empty()) {
783         if (!fUnicode->getWords(fText.c_str(), fText.size(), &fWords)) {
784             return {0, 0 };
785         }
786     }
787 
788     int32_t start = 0;
789     int32_t end = 0;
790     for (size_t i = 0; i < fWords.size(); ++i) {
791         auto word = fWords[i];
792         if (word <= offset) {
793             start = word;
794             end = word;
795         } else if (word > offset) {
796             end = word;
797             break;
798         }
799     }
800 
801     //SkDebugf("getWordBoundary(%d): %d - %d\n", offset, start, end);
802     return { SkToU32(start), SkToU32(end) };
803 }
804 
getLineMetrics(std::vector<LineMetrics> & metrics)805 void ParagraphImpl::getLineMetrics(std::vector<LineMetrics>& metrics) {
806     metrics.clear();
807     for (auto& line : fLines) {
808         metrics.emplace_back(line.getMetrics());
809     }
810 }
811 
text(TextRange textRange)812 SkSpan<const char> ParagraphImpl::text(TextRange textRange) {
813     SkASSERT(textRange.start <= fText.size() && textRange.end <= fText.size());
814     auto start = fText.c_str() + textRange.start;
815     return SkSpan<const char>(start, textRange.width());
816 }
817 
clusters(ClusterRange clusterRange)818 SkSpan<Cluster> ParagraphImpl::clusters(ClusterRange clusterRange) {
819     SkASSERT(clusterRange.start < fClusters.size() && clusterRange.end <= fClusters.size());
820     return SkSpan<Cluster>(&fClusters[clusterRange.start], clusterRange.width());
821 }
822 
cluster(ClusterIndex clusterIndex)823 Cluster& ParagraphImpl::cluster(ClusterIndex clusterIndex) {
824     SkASSERT(clusterIndex < fClusters.size());
825     return fClusters[clusterIndex];
826 }
827 
runByCluster(ClusterIndex clusterIndex)828 Run& ParagraphImpl::runByCluster(ClusterIndex clusterIndex) {
829     auto start = cluster(clusterIndex);
830     return this->run(start.fRunIndex);
831 }
832 
blocks(BlockRange blockRange)833 SkSpan<Block> ParagraphImpl::blocks(BlockRange blockRange) {
834     SkASSERT(blockRange.start < fTextStyles.size() && blockRange.end <= fTextStyles.size());
835     return SkSpan<Block>(&fTextStyles[blockRange.start], blockRange.width());
836 }
837 
block(BlockIndex blockIndex)838 Block& ParagraphImpl::block(BlockIndex blockIndex) {
839     SkASSERT(blockIndex < fTextStyles.size());
840     return fTextStyles[blockIndex];
841 }
842 
setState(InternalState state)843 void ParagraphImpl::setState(InternalState state) {
844     if (fState <= state) {
845         fState = state;
846         return;
847     }
848 
849     fState = state;
850     switch (fState) {
851         case kUnknown:
852             fRuns.reset();
853             fCodeUnitProperties.reset();
854             fCodeUnitProperties.push_back_n(fText.size() + 1, kNoCodeUnitFlag);
855             fWords.clear();
856             fBidiRegions.clear();
857             fUTF8IndexForUTF16Index.reset();
858             fUTF16IndexForUTF8Index.reset();
859             [[fallthrough]];
860 
861         case kShaped:
862             fClusters.reset();
863             [[fallthrough]];
864 
865         case kClusterized:
866         case kMarked:
867         case kLineBroken:
868             this->resetContext();
869             this->resolveStrut();
870             this->computeEmptyMetrics();
871             this->resetShifts();
872             fLines.reset();
873             [[fallthrough]];
874 
875         case kFormatted:
876             fPicture = nullptr;
877             [[fallthrough]];
878 
879         case kDrawn:
880         default:
881             break;
882     }
883 }
884 
computeEmptyMetrics()885 void ParagraphImpl::computeEmptyMetrics() {
886 
887     // The empty metrics is used to define the height of the empty lines
888     // Unfortunately, Flutter has 2 different cases for that:
889     // 1. An empty line inside the text
890     // 2. An empty paragraph
891     // In the first case SkParagraph takes the metrics from the default paragraph style
892     // In the second case it should take it from the current text style
893     bool emptyParagraph = fRuns.empty();
894     TextStyle textStyle = paragraphStyle().getTextStyle();
895     if (emptyParagraph && !fTextStyles.empty()) {
896         textStyle = fTextStyles.back().fStyle;
897     }
898 
899     auto typefaces = fontCollection()->findTypefaces(
900       textStyle.getFontFamilies(), textStyle.getFontStyle());
901     auto typeface = typefaces.empty() ? nullptr : typefaces.front();
902 
903     SkFont font(typeface, textStyle.getFontSize());
904     fEmptyMetrics = InternalLineMetrics(font, paragraphStyle().getStrutStyle().getForceStrutHeight());
905 
906     if (!paragraphStyle().getStrutStyle().getForceStrutHeight() &&
907         textStyle.getHeightOverride()) {
908         const auto intrinsicHeight = fEmptyMetrics.height();
909         const auto strutHeight = textStyle.getHeight() * textStyle.getFontSize();
910         if (paragraphStyle().getStrutStyle().getHalfLeading()) {
911             fEmptyMetrics.update(
912                 fEmptyMetrics.ascent(),
913                 fEmptyMetrics.descent(),
914                 fEmptyMetrics.leading() + strutHeight - intrinsicHeight);
915         } else {
916             const auto multiplier = strutHeight / intrinsicHeight;
917             fEmptyMetrics.update(
918                 fEmptyMetrics.ascent() * multiplier,
919                 fEmptyMetrics.descent() * multiplier,
920                 fEmptyMetrics.leading() * multiplier);
921         }
922     }
923 
924     if (emptyParagraph) {
925         // For an empty text we apply both TextHeightBehaviour flags
926         // In case of non-empty paragraph TextHeightBehaviour flags will be applied at the appropriate place
927         // We have to do it here because we skip wrapping for an empty text
928         auto disableFirstAscent = (paragraphStyle().getTextHeightBehavior() & TextHeightBehavior::kDisableFirstAscent) == TextHeightBehavior::kDisableFirstAscent;
929         auto disableLastDescent = (paragraphStyle().getTextHeightBehavior() & TextHeightBehavior::kDisableLastDescent) == TextHeightBehavior::kDisableLastDescent;
930         fEmptyMetrics.update(
931             disableFirstAscent ? fEmptyMetrics.rawAscent() : fEmptyMetrics.ascent(),
932             disableLastDescent ? fEmptyMetrics.rawDescent() : fEmptyMetrics.descent(),
933             fEmptyMetrics.leading());
934     }
935 
936     if (fParagraphStyle.getStrutStyle().getStrutEnabled()) {
937         fStrutMetrics.updateLineMetrics(fEmptyMetrics);
938     }
939 }
940 
getEllipsis() const941 SkString ParagraphImpl::getEllipsis() const {
942 
943     auto ellipsis8 = fParagraphStyle.getEllipsis();
944     auto ellipsis16 = fParagraphStyle.getEllipsisUtf16();
945     if (!ellipsis8.isEmpty()) {
946         return ellipsis8;
947     } else {
948         return fUnicode->convertUtf16ToUtf8(fParagraphStyle.getEllipsisUtf16());
949     }
950 }
951 
updateText(size_t from,SkString text)952 void ParagraphImpl::updateText(size_t from, SkString text) {
953   fText.remove(from, from + text.size());
954   fText.insert(from, text);
955   fState = kUnknown;
956   fOldWidth = 0;
957   fOldHeight = 0;
958 }
959 
updateFontSize(size_t from,size_t to,SkScalar fontSize)960 void ParagraphImpl::updateFontSize(size_t from, size_t to, SkScalar fontSize) {
961 
962   SkASSERT(from == 0 && to == fText.size());
963   auto defaultStyle = fParagraphStyle.getTextStyle();
964   defaultStyle.setFontSize(fontSize);
965   fParagraphStyle.setTextStyle(defaultStyle);
966 
967   for (auto& textStyle : fTextStyles) {
968     textStyle.fStyle.setFontSize(fontSize);
969   }
970 
971   fState = kUnknown;
972   fOldWidth = 0;
973   fOldHeight = 0;
974 }
975 
updateTextAlign(TextAlign textAlign)976 void ParagraphImpl::updateTextAlign(TextAlign textAlign) {
977     fParagraphStyle.setTextAlign(textAlign);
978 
979     if (fState >= kLineBroken) {
980         fState = kLineBroken;
981     }
982 }
983 
updateForegroundPaint(size_t from,size_t to,SkPaint paint)984 void ParagraphImpl::updateForegroundPaint(size_t from, size_t to, SkPaint paint) {
985     SkASSERT(from == 0 && to == fText.size());
986     auto defaultStyle = fParagraphStyle.getTextStyle();
987     defaultStyle.setForegroundColor(paint);
988     fParagraphStyle.setTextStyle(defaultStyle);
989 
990     for (auto& textStyle : fTextStyles) {
991         textStyle.fStyle.setForegroundColor(paint);
992     }
993 }
994 
updateBackgroundPaint(size_t from,size_t to,SkPaint paint)995 void ParagraphImpl::updateBackgroundPaint(size_t from, size_t to, SkPaint paint) {
996     SkASSERT(from == 0 && to == fText.size());
997     auto defaultStyle = fParagraphStyle.getTextStyle();
998     defaultStyle.setBackgroundColor(paint);
999     fParagraphStyle.setTextStyle(defaultStyle);
1000 
1001     for (auto& textStyle : fTextStyles) {
1002         textStyle.fStyle.setBackgroundColor(paint);
1003     }
1004 }
1005 
findPreviousGraphemeBoundary(TextIndex utf8)1006 TextIndex ParagraphImpl::findPreviousGraphemeBoundary(TextIndex utf8) {
1007     while (utf8 > 0 &&
1008           (fCodeUnitProperties[utf8] & CodeUnitFlags::kGraphemeStart) == 0) {
1009         --utf8;
1010     }
1011     return utf8;
1012 }
1013 
findNextGraphemeBoundary(TextIndex utf8)1014 TextIndex ParagraphImpl::findNextGraphemeBoundary(TextIndex utf8) {
1015     while (utf8 < fText.size() &&
1016           (fCodeUnitProperties[utf8] & CodeUnitFlags::kGraphemeStart) == 0) {
1017         ++utf8;
1018     }
1019     return utf8;
1020 }
1021 
ensureUTF16Mapping()1022 void ParagraphImpl::ensureUTF16Mapping() {
1023     if (!fUTF16IndexForUTF8Index.empty()) {
1024         return;
1025     }
1026     // Fill out code points 16
1027     auto ptr = fText.c_str();
1028     auto end = fText.c_str() + fText.size();
1029     while (ptr < end) {
1030 
1031         size_t index = ptr - fText.c_str();
1032         SkUnichar u = SkUTF::NextUTF8(&ptr, end);
1033 
1034         // All utf8 units refer to the same codepoint
1035         size_t next = ptr - fText.c_str();
1036         for (auto i = index; i < next; ++i) {
1037             fUTF16IndexForUTF8Index.emplace_back(fUTF8IndexForUTF16Index.size());
1038         }
1039         SkASSERT(fUTF16IndexForUTF8Index.size() == next);
1040 
1041         // One or two codepoints refer to the same text index
1042         uint16_t buffer[2];
1043         size_t count = SkUTF::ToUTF16(u, buffer);
1044         fUTF8IndexForUTF16Index.emplace_back(index);
1045         if (count > 1) {
1046             fUTF8IndexForUTF16Index.emplace_back(index);
1047         }
1048     }
1049     fUTF16IndexForUTF8Index.emplace_back(fUTF8IndexForUTF16Index.size());
1050     fUTF8IndexForUTF16Index.emplace_back(fText.size());
1051 }
1052 
visit(const Visitor & visitor)1053 void ParagraphImpl::visit(const Visitor& visitor) {
1054     int lineNumber = 0;
1055     for (auto& line : fLines) {
1056         line.ensureTextBlobCachePopulated();
1057         for (auto& rec : line.fTextBlobCache) {
1058             SkTextBlob::Iter iter(*rec.fBlob);
1059             SkTextBlob::Iter::ExperimentalRun run;
1060 
1061             SkSTArray<128, uint32_t> clusterStorage;
1062             const Run* R = rec.fVisitor_Run;
1063             const uint32_t* clusterPtr = &R->fClusterIndexes[0];
1064 
1065             if (R->fClusterStart > 0) {
1066                 int count = R->fClusterIndexes.count();
1067                 clusterStorage.reset(count);
1068                 for (int i = 0; i < count; ++i) {
1069                     clusterStorage[i] = R->fClusterStart + R->fClusterIndexes[i];
1070                 }
1071                 clusterPtr = &clusterStorage[0];
1072             }
1073             clusterPtr += rec.fVisitor_Pos;
1074 
1075             while (iter.experimentalNext(&run)) {
1076                 const Paragraph::VisitorInfo info = {
1077                     run.font,
1078                     rec.fOffset,
1079                     rec.fClipRect.fRight,
1080                     run.count,
1081                     run.glyphs,
1082                     run.positions,
1083                     clusterPtr,
1084                     0,  // flags
1085                 };
1086                 visitor(lineNumber, &info);
1087                 clusterPtr += run.count;
1088             }
1089         }
1090         visitor(lineNumber, nullptr);   // signal end of line
1091         lineNumber += 1;
1092     }
1093 }
1094 
1095 }  // namespace textlayout
1096 }  // namespace skia
1097