• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // Copyright 2019 Google LLC.
2 #include "modules/skparagraph/src/ParagraphImpl.h"
3 #include <unicode/brkiter.h>
4 #include <unicode/ubidi.h>
5 #include <unicode/unistr.h>
6 #include <unicode/urename.h>
7 #include "include/core/SkBlurTypes.h"
8 #include "include/core/SkCanvas.h"
9 #include "include/core/SkFontMgr.h"
10 #include "include/core/SkPictureRecorder.h"
11 #include "modules/skparagraph/src/Iterators.h"
12 #include "modules/skparagraph/src/Run.h"
13 #include "modules/skparagraph/src/TextWrapper.h"
14 #include "src/core/SkSpan.h"
15 #include "src/utils/SkUTF.h"
16 #include <algorithm>
17 
18 namespace {
19 
20 class TextBreaker {
21 public:
TextBreaker()22     TextBreaker() : fPos(-1) {}
23 
initialize(SkSpan<const char> text,UBreakIteratorType type)24     bool initialize(SkSpan<const char> text, UBreakIteratorType type) {
25         UErrorCode status = U_ZERO_ERROR;
26         fIterator = nullptr;
27         fSize = text.size();
28         UText utf8UText = UTEXT_INITIALIZER;
29         utext_openUTF8(&utf8UText, text.begin(), text.size(), &status);
30         fAutoClose =
31                 std::unique_ptr<UText, SkFunctionWrapper<UText*, UText, utext_close>>(&utf8UText);
32         if (U_FAILURE(status)) {
33             SkDebugf("Could not create utf8UText: %s", u_errorName(status));
34             return false;
35         }
36         fIterator.reset(ubrk_open(type, "en", nullptr, 0, &status));
37         if (U_FAILURE(status)) {
38             SkDebugf("Could not create line break iterator: %s", u_errorName(status));
39             SK_ABORT("");
40         }
41 
42         ubrk_setUText(fIterator.get(), &utf8UText, &status);
43         if (U_FAILURE(status)) {
44             SkDebugf("Could not setText on break iterator: %s", u_errorName(status));
45             return false;
46         }
47 
48         fPos = 0;
49         return true;
50     }
51 
first()52     size_t first() {
53         fPos = ubrk_first(fIterator.get());
54         return eof() ? fSize : fPos;
55     }
56 
next()57     size_t next() {
58         fPos = ubrk_next(fIterator.get());
59         return eof() ? fSize : fPos;
60     }
61 
preceding(size_t offset)62     size_t preceding(size_t offset) {
63         auto pos = ubrk_preceding(fIterator.get(), offset);
64         return eof() ? 0 : pos;
65     }
66 
following(size_t offset)67     size_t following(size_t offset) {
68         auto pos = ubrk_following(fIterator.get(), offset);
69         return eof() ? fSize : pos;
70     }
71 
status()72     int32_t status() { return ubrk_getRuleStatus(fIterator.get()); }
73 
eof()74     bool eof() { return fPos == icu::BreakIterator::DONE; }
75 
76 private:
77     std::unique_ptr<UText, SkFunctionWrapper<UText*, UText, utext_close>> fAutoClose;
78     std::unique_ptr<UBreakIterator, SkFunctionWrapper<void, UBreakIterator, ubrk_close>> fIterator;
79     int32_t fPos;
80     size_t fSize;
81 };
82 }  // namespace
83 
84 namespace skia {
85 namespace textlayout {
86 
operator *(const TextRange & a,const TextRange & b)87 TextRange operator*(const TextRange& a, const TextRange& b) {
88     if (a.start == b.start && a.end == b.end) return a;
89     auto begin = SkTMax(a.start, b.start);
90     auto end = SkTMin(a.end, b.end);
91     return end > begin ? TextRange(begin, end) : EMPTY_TEXT;
92 }
93 
ParagraphImpl(const SkString & text,ParagraphStyle style,SkTArray<Block,true> blocks,sk_sp<FontCollection> fonts)94 ParagraphImpl::ParagraphImpl(const SkString& text,
95                              ParagraphStyle style,
96                              SkTArray<Block, true> blocks,
97                              sk_sp<FontCollection> fonts)
98         : Paragraph(std::move(style), std::move(fonts))
99         , fTextStyles(std::move(blocks))
100         , fText(text)
101         , fTextSpan(fText.c_str(), fText.size())
102         , fState(kUnknown)
103         , fPicture(nullptr)
104         , fStrutMetrics(false)
105         , fOldWidth(0)
106         , fOldHeight(0) {
107     // TODO: extractStyles();
108 }
109 
ParagraphImpl(const std::u16string & utf16text,ParagraphStyle style,SkTArray<Block,true> blocks,sk_sp<FontCollection> fonts)110 ParagraphImpl::ParagraphImpl(const std::u16string& utf16text,
111                              ParagraphStyle style,
112                              SkTArray<Block, true> blocks,
113                              sk_sp<FontCollection> fonts)
114         : Paragraph(std::move(style), std::move(fonts))
115         , fTextStyles(std::move(blocks))
116         , fState(kUnknown)
117         , fPicture(nullptr)
118         , fStrutMetrics(false)
119         , fOldWidth(0)
120         , fOldHeight(0) {
121     icu::UnicodeString unicode((UChar*)utf16text.data(), SkToS32(utf16text.size()));
122     std::string str;
123     unicode.toUTF8String(str);
124     fText = SkString(str.data(), str.size());
125     fTextSpan = SkSpan<const char>(fText.c_str(), fText.size());
126     // TODO: extractStyles();
127 }
128 
129 ParagraphImpl::~ParagraphImpl() = default;
130 
layout(SkScalar width)131 void ParagraphImpl::layout(SkScalar width) {
132 
133     if (fState < kShaped) {
134         // Layout marked as dirty for performance/testing reasons
135         this->fRuns.reset();
136         this->fClusters.reset();
137     } else if (fState >= kLineBroken && (fOldWidth != width || fOldHeight != fHeight)) {
138         // We can use the results from SkShaper but have to break lines again
139         fState = kShaped;
140     }
141 
142     if (fState < kShaped) {
143         fClusters.reset();
144 
145         if (!this->shapeTextIntoEndlessLine()) {
146             // Apply the last style to the empty text
147             FontIterator font(SkMakeSpan(" "), &fFontResolver);
148             // Get the font metrics
149             font.consume();
150             LineMetrics lineMetrics(font.currentFont(), paragraphStyle().getStrutStyle().getForceStrutHeight());
151             // Set the important values that are not zero
152             fHeight = lineMetrics.height();
153             fAlphabeticBaseline = lineMetrics.alphabeticBaseline();
154             fIdeographicBaseline = lineMetrics.ideographicBaseline();
155         }
156         if (fState < kShaped) {
157             fState = kShaped;
158         } else {
159             layout(width);
160             return;
161         }
162 
163         if (fState < kMarked) {
164             this->buildClusterTable();
165             fState = kClusterized;
166             this->markLineBreaks();
167             fState = kMarked;
168 
169             // Add the paragraph to the cache
170             fFontCollection->getParagraphCache()->updateParagraph(this);
171         }
172     }
173 
174     if (fState >= kLineBroken)  {
175         if (fOldWidth != width || fOldHeight != fHeight) {
176             fState = kMarked;
177         }
178     }
179 
180     if (fState < kLineBroken) {
181         this->resetContext();
182         this->resolveStrut();
183         this->fLines.reset();
184         this->breakShapedTextIntoLines(width);
185         fState = kLineBroken;
186 
187     }
188 
189     if (fState < kFormatted) {
190         // Build the picture lazily not until we actually have to paint (or never)
191         this->formatLines(fWidth);
192         fState = kFormatted;
193     }
194 
195     this->fOldWidth = width;
196     this->fOldHeight = this->fHeight;
197 }
198 
paint(SkCanvas * canvas,SkScalar x,SkScalar y)199 void ParagraphImpl::paint(SkCanvas* canvas, SkScalar x, SkScalar y) {
200 
201     if (fState < kDrawn) {
202         // Record the picture anyway (but if we have some pieces in the cache they will be used)
203         this->paintLinesIntoPicture();
204         fState = kDrawn;
205     }
206 
207     SkMatrix matrix = SkMatrix::MakeTrans(x, y);
208     canvas->drawPicture(fPicture, &matrix, nullptr);
209 }
210 
resetContext()211 void ParagraphImpl::resetContext() {
212     fAlphabeticBaseline = 0;
213     fHeight = 0;
214     fWidth = 0;
215     fIdeographicBaseline = 0;
216     fMaxIntrinsicWidth = 0;
217     fMinIntrinsicWidth = 0;
218 }
219 
220 // Clusters in the order of the input text
buildClusterTable()221 void ParagraphImpl::buildClusterTable() {
222 
223     // Walk through all the run in the direction of input text
224     for (RunIndex runIndex = 0; runIndex < fRuns.size(); ++runIndex) {
225         auto& run = fRuns[runIndex];
226         auto runStart = fClusters.size();
227         fClusters.reserve(fClusters.size() + fRuns.size());
228         // Walk through the glyph in the direction of input text
229         run.iterateThroughClustersInTextOrder([runIndex, this](
230                                                       size_t glyphStart,
231                                                       size_t glyphEnd,
232                                                       size_t charStart,
233                                                       size_t charEnd,
234                                                       SkScalar width,
235                                                       SkScalar height) {
236             SkASSERT(charEnd >= charStart);
237             SkSpan<const char> text(fTextSpan.begin() + charStart, charEnd - charStart);
238 
239             auto& cluster = fClusters.emplace_back(this, runIndex, glyphStart, glyphEnd, text, width, height);
240             cluster.setIsWhiteSpaces();
241         });
242 
243         run.setClusterRange(runStart, fClusters.size());
244         fMaxIntrinsicWidth += run.advance().fX;
245     }
246 }
247 
248 // TODO: we need soft line breaks before for word spacing
markLineBreaks()249 void ParagraphImpl::markLineBreaks() {
250 
251     // Find all possible (soft) line breaks
252     TextBreaker breaker;
253     if (!breaker.initialize(fTextSpan, UBRK_LINE)) {
254         return;
255     }
256 
257     Cluster* current = fClusters.begin();
258     while (!breaker.eof() && current < fClusters.end()) {
259         size_t currentPos = breaker.next();
260         while (current < fClusters.end()) {
261             if (current->textRange().end > currentPos) {
262                 break;
263             } else if (current->textRange().end == currentPos) {
264                 current->setBreakType(breaker.status() == UBRK_LINE_HARD
265                                       ? Cluster::BreakType::HardLineBreak
266                                       : Cluster::BreakType::SoftLineBreak);
267                 ++current;
268                 break;
269             }
270             ++current;
271         }
272     }
273 
274 
275     // Walk through all the clusters in the direction of shaped text
276     // (we have to walk through the styles in the same order, too)
277     SkScalar shift = 0;
278     for (auto& run : fRuns) {
279 
280         bool soFarWhitespacesOnly = true;
281         for (size_t index = 0; index != run.clusterRange().width(); ++index) {
282             auto correctIndex = run.leftToRight()
283                     ? index + run.clusterRange().start
284                     : run.clusterRange().end - index - 1;
285             const auto cluster = &this->cluster(correctIndex);
286 
287             // Shift the cluster (shift collected from the previous clusters)
288             run.shift(cluster, shift);
289 
290             // Synchronize styles (one cluster can be covered by few styles)
291             Block* currentStyle = this->fTextStyles.begin();
292             while (!cluster->startsIn(currentStyle->fRange)) {
293                 currentStyle++;
294                 SkASSERT(currentStyle != this->fTextStyles.end());
295             }
296 
297             // Process word spacing
298             if (currentStyle->fStyle.getWordSpacing() != 0) {
299                 if (cluster->isWhitespaces() && cluster->isSoftBreak()) {
300                     if (!soFarWhitespacesOnly) {
301                         shift += run.addSpacesAtTheEnd(currentStyle->fStyle.getWordSpacing(), cluster);
302                     }
303                 }
304             }
305             // Process letter spacing
306             if (currentStyle->fStyle.getLetterSpacing() != 0) {
307                 shift += run.addSpacesEvenly(currentStyle->fStyle.getLetterSpacing(), cluster);
308             }
309 
310             if (soFarWhitespacesOnly && !cluster->isWhitespaces()) {
311                 soFarWhitespacesOnly = false;
312             }
313         }
314     }
315 
316     fClusters.emplace_back(this, EMPTY_RUN, 0, 0, SkSpan<const char>(), 0, 0);
317 }
318 
shapeTextIntoEndlessLine()319 bool ParagraphImpl::shapeTextIntoEndlessLine() {
320 
321     class ShapeHandler final : public SkShaper::RunHandler {
322     public:
323         explicit ShapeHandler(ParagraphImpl& paragraph, FontIterator* fontIterator)
324                 : fParagraph(&paragraph)
325                 , fFontIterator(fontIterator)
326                 , fAdvance(SkVector::Make(0, 0)) {}
327 
328         SkVector advance() const { return fAdvance; }
329 
330     private:
331         void beginLine() override {}
332 
333         void runInfo(const RunInfo&) override {}
334 
335         void commitRunInfo() override {}
336 
337         Buffer runBuffer(const RunInfo& info) override {
338             auto& run = fParagraph->fRuns.emplace_back(fParagraph,
339                                                        info,
340                                                        fFontIterator->currentLineHeight(),
341                                                        fParagraph->fRuns.count(),
342                                                        fAdvance.fX);
343             return run.newRunBuffer();
344         }
345 
346         void commitRunBuffer(const RunInfo&) override {
347             auto& run = fParagraph->fRuns.back();
348             if (run.size() == 0) {
349                 fParagraph->fRuns.pop_back();
350                 return;
351             }
352             // Carve out the line text out of the entire run text
353             fAdvance.fX += run.advance().fX;
354             fAdvance.fY = SkMaxScalar(fAdvance.fY, run.advance().fY);
355         }
356 
357         void commitLine() override {}
358 
359         ParagraphImpl* fParagraph;
360         FontIterator* fFontIterator;
361         SkVector fAdvance;
362     };
363 
364     if (fTextSpan.empty()) {
365         return false;
366     }
367 
368     // This is a pretty big step - resolving all characters against all given fonts
369     fFontResolver.findAllFontsForAllStyledBlocks(this);
370 
371     // Check the font-resolved text against the cache
372     if (!fFontCollection->getParagraphCache()->findParagraph(this)) {
373         LangIterator lang(fTextSpan, styles(), paragraphStyle().getTextStyle());
374         FontIterator font(fTextSpan, &fFontResolver);
375         ShapeHandler handler(*this, &font);
376         std::unique_ptr<SkShaper> shaper = SkShaper::MakeShapeDontWrapOrReorder();
377         SkASSERT_RELEASE(shaper != nullptr);
378         auto bidi = SkShaper::MakeIcuBiDiRunIterator(
379                 fTextSpan.begin(), fTextSpan.size(),
380                 fParagraphStyle.getTextDirection() == TextDirection::kLtr ? (uint8_t)2
381                                                                           : (uint8_t)1);
382         if (bidi == nullptr) {
383             return false;
384         }
385         auto script = SkShaper::MakeHbIcuScriptRunIterator(fTextSpan.begin(), fTextSpan.size());
386 
387         shaper->shape(fTextSpan.begin(), fTextSpan.size(), font, *bidi, *script, lang,
388                       std::numeric_limits<SkScalar>::max(), &handler);
389     }
390 
391     if (fParagraphStyle.getTextAlign() == TextAlign::kJustify) {
392         fRunShifts.reset();
393         fRunShifts.push_back_n(fRuns.size(), RunShifts());
394         for (size_t i = 0; i < fRuns.size(); ++i) {
395             fRunShifts[i].fShifts.push_back_n(fRuns[i].size() + 1, 0.0);
396         }
397     }
398 
399     return true;
400 }
401 
breakShapedTextIntoLines(SkScalar maxWidth)402 void ParagraphImpl::breakShapedTextIntoLines(SkScalar maxWidth) {
403 
404     TextWrapper textWrapper;
405     textWrapper.breakTextIntoLines(
406             this,
407             maxWidth,
408             [&](TextRange text,
409                 TextRange textWithSpaces,
410                 ClusterRange clusters,
411                 ClusterRange clustersWithGhosts,
412                 SkScalar widthWithSpaces,
413                 size_t startPos,
414                 size_t endPos,
415                 SkVector offset,
416                 SkVector advance,
417                 LineMetrics metrics,
418                 bool addEllipsis) {
419                 // Add the line
420                 // TODO: Take in account clipped edges
421                 auto& line = this->addLine(offset, advance, text, textWithSpaces, clusters, clustersWithGhosts, widthWithSpaces, metrics);
422                 if (addEllipsis) {
423                     line.createEllipsis(maxWidth, fParagraphStyle.getEllipsis(), true);
424                 }
425             });
426     fHeight = textWrapper.height();
427     fWidth = maxWidth;  // fTextWrapper.width();
428     fMinIntrinsicWidth = textWrapper.minIntrinsicWidth();
429     fMaxIntrinsicWidth = textWrapper.maxIntrinsicWidth();
430     fAlphabeticBaseline = fLines.empty() ? 0 : fLines.front().alphabeticBaseline();
431     fIdeographicBaseline = fLines.empty() ? 0 : fLines.front().ideographicBaseline();
432 }
433 
formatLines(SkScalar maxWidth)434 void ParagraphImpl::formatLines(SkScalar maxWidth) {
435     auto effectiveAlign = fParagraphStyle.effective_align();
436     for (auto& line : fLines) {
437         if (&line == &fLines.back() && effectiveAlign == TextAlign::kJustify) {
438             effectiveAlign = line.assumedTextAlign();
439         }
440         line.format(effectiveAlign, maxWidth);
441     }
442 }
443 
paintLinesIntoPicture()444 void ParagraphImpl::paintLinesIntoPicture() {
445     SkPictureRecorder recorder;
446     SkCanvas* textCanvas = recorder.beginRecording(fWidth, fHeight, nullptr, 0);
447 
448     for (auto& line : fLines) {
449         line.paint(textCanvas);
450     }
451 
452     fPicture = recorder.finishRecordingAsPicture();
453 }
454 
resolveStrut()455 void ParagraphImpl::resolveStrut() {
456     auto strutStyle = this->paragraphStyle().getStrutStyle();
457     if (!strutStyle.getStrutEnabled() || strutStyle.getFontSize() < 0) {
458         return;
459     }
460 
461     sk_sp<SkTypeface> typeface;
462     for (auto& fontFamily : strutStyle.getFontFamilies()) {
463         typeface = fFontCollection->matchTypeface(fontFamily.c_str(), strutStyle.getFontStyle());
464         if (typeface.get() != nullptr) {
465             break;
466         }
467     }
468     if (typeface.get() == nullptr) {
469         return;
470     }
471 
472     SkFont font(typeface, strutStyle.getFontSize());
473     SkFontMetrics metrics;
474     font.getMetrics(&metrics);
475 
476     if (strutStyle.getHeightOverride()) {
477         auto strutHeight = metrics.fDescent - metrics.fAscent + metrics.fLeading;
478         auto strutMultiplier = strutStyle.getHeight() * strutStyle.getFontSize();
479         fStrutMetrics = LineMetrics(
480                 metrics.fAscent / strutHeight * strutMultiplier,
481                 metrics.fDescent / strutHeight * strutMultiplier,
482                 strutStyle.getLeading() < 0 ? 0 : strutStyle.getLeading() * strutStyle.getFontSize());
483     } else {
484         fStrutMetrics = LineMetrics(
485                 metrics.fAscent,
486                 metrics.fDescent,
487                 strutStyle.getLeading() < 0 ? 0 : strutStyle.getLeading() * strutStyle.getFontSize());
488     }
489 }
490 
findAllBlocks(TextRange textRange)491 BlockRange ParagraphImpl::findAllBlocks(TextRange textRange) {
492     BlockIndex begin = EMPTY_BLOCK;
493     BlockIndex end = EMPTY_BLOCK;
494     for (size_t index = 0; index < fTextStyles.size(); ++index) {
495         auto& block = fTextStyles[index];
496         if (block.fRange.end <= textRange.start) {
497             continue;
498         }
499         if (block.fRange.start >= textRange.end) {
500             break;
501         }
502         if (begin == EMPTY_BLOCK) {
503             begin = index;
504         }
505         end = index;
506     }
507 
508     return { begin, end + 1 };
509 }
510 
addLine(SkVector offset,SkVector advance,TextRange text,TextRange textWithSpaces,ClusterRange clusters,ClusterRange clustersWithGhosts,SkScalar widthWithSpaces,LineMetrics sizes)511 TextLine& ParagraphImpl::addLine(SkVector offset,
512                                  SkVector advance,
513                                  TextRange text,
514                                  TextRange textWithSpaces,
515                                  ClusterRange clusters,
516                                  ClusterRange clustersWithGhosts,
517                                  SkScalar widthWithSpaces,
518                                  LineMetrics sizes) {
519     // Define a list of styles that covers the line
520     auto blocks = findAllBlocks(text);
521 
522     return fLines.emplace_back(this, offset, advance, blocks, text, textWithSpaces, clusters, clustersWithGhosts, widthWithSpaces, sizes);
523 }
524 
markGraphemes()525 void ParagraphImpl::markGraphemes() {
526 
527     if (!fGraphemes.empty()) {
528         return;
529     }
530 
531     TextBreaker breaker;
532     if (!breaker.initialize(fTextSpan, UBRK_CHARACTER)) {
533         return;
534     }
535 
536     auto ptr = fTextSpan.begin();
537     while (ptr < fTextSpan.end()) {
538 
539         size_t index = ptr - fTextSpan.begin();
540         SkUnichar u = SkUTF::NextUTF8(&ptr, fTextSpan.end());
541         uint16_t buffer[2];
542         size_t count = SkUTF::ToUTF16(u, buffer);
543         fCodePoints.emplace_back(EMPTY_INDEX, index);
544         if (count > 1) {
545             fCodePoints.emplace_back(EMPTY_INDEX, index);
546         }
547     }
548 
549     CodepointRange codepoints(0ul, 0ul);
550 
551     size_t endPos = 0;
552     while (!breaker.eof()) {
553         auto startPos = endPos;
554         endPos = breaker.next();
555 
556         // Collect all the codepoints that belong to the grapheme
557         while (codepoints.end < fCodePoints.size() && fCodePoints[codepoints.end].fTextIndex < endPos) {
558             ++codepoints.end;
559         }
560 
561         // Update all the codepoints that belong to this grapheme
562         for (auto i = codepoints.start; i < codepoints.end; ++i) {
563             fCodePoints[i].fGrapeme = fGraphemes.size();
564         }
565 
566         fGraphemes.emplace_back(codepoints, TextRange(startPos, endPos));
567         codepoints.start = codepoints.end;
568     }
569 }
570 
571 // Returns a vector of bounding boxes that enclose all text between
572 // start and end glyph indexes, including start and excluding end
getRectsForRange(unsigned start,unsigned end,RectHeightStyle rectHeightStyle,RectWidthStyle rectWidthStyle)573 std::vector<TextBox> ParagraphImpl::getRectsForRange(unsigned start,
574                                                      unsigned end,
575                                                      RectHeightStyle rectHeightStyle,
576                                                      RectWidthStyle rectWidthStyle) {
577     markGraphemes();
578     std::vector<TextBox> results;
579     if (start >= end || start > fCodePoints.size() || end == 0) {
580         return results;
581     }
582 
583     // Make sure the edges are set on the glyph edges
584     TextRange text;
585     text.end = end >= fCodePoints.size()
586                         ? fTextSpan.size()
587                         : fGraphemes[fCodePoints[end].fGrapeme].fTextRange.start;
588     text.start = start >=  fCodePoints.size()
589                         ? fTextSpan.size()
590                         : fGraphemes[fCodePoints[start].fGrapeme].fTextRange.start;
591 
592     for (auto& line : fLines) {
593         auto lineText = line.textWithSpaces();
594         auto intersect = lineText * text;
595         if (intersect.empty() && lineText.start != text.start) {
596             continue;
597         }
598 
599         SkScalar runOffset = line.calculateLeftVisualOffset(intersect);
600 
601         auto firstBoxOnTheLine = results.size();
602         auto paragraphTextDirection = paragraphStyle().getTextDirection();
603         auto lineTextAlign = line.assumedTextAlign();
604         Run* lastRun = nullptr;
605         line.iterateThroughRuns(
606             intersect,
607             runOffset,
608             true,
609             [&results, &line, rectHeightStyle, this, paragraphTextDirection, lineTextAlign, &lastRun]
610             (Run* run, size_t pos, size_t size, TextRange text, SkRect clip, SkScalar shift, bool clippingNeeded) {
611 
612                 SkRect trailingSpaces = SkRect::MakeEmpty();
613 
614                 SkScalar ghostSpacesRight = run->leftToRight() ? clip.right() - line.width() : 0;
615                 SkScalar ghostSpacesLeft = !run->leftToRight() ? clip.right() - line.width() : 0;
616 
617                 if (ghostSpacesRight + ghostSpacesLeft > 0) {
618                     if (lineTextAlign == TextAlign::kLeft && ghostSpacesLeft > 0) {
619                         clip.offset(-ghostSpacesLeft, 0);
620                     } else if (lineTextAlign == TextAlign::kRight && ghostSpacesLeft > 0) {
621                         clip.offset(-ghostSpacesLeft, 0);
622                     } else if (lineTextAlign == TextAlign::kCenter) {
623                         // TODO: What do we do for centering?
624                     }
625                 }
626 
627                 clip.offset(line.offset());
628 
629                 if (rectHeightStyle == RectHeightStyle::kMax) {
630                     // TODO: Sort it out with Flutter people
631                     // Mimicking TxtLib: clip.fTop = line.offset().fY + line.roundingDelta();
632                     clip.fBottom = line.offset().fY + line.height();
633 
634                 } else if (rectHeightStyle == RectHeightStyle::kIncludeLineSpacingTop) {
635                     if (&line != &fLines.front()) {
636                         clip.fTop -= line.sizes().runTop(run);
637                     }
638                     clip.fBottom -= line.sizes().runTop(run);
639                 } else if (rectHeightStyle == RectHeightStyle::kIncludeLineSpacingMiddle) {
640                     if (&line != &fLines.front()) {
641                         clip.fTop -= line.sizes().runTop(run) / 2;
642                     }
643                     if (&line == &fLines.back()) {
644                         clip.fBottom -= line.sizes().runTop(run);
645                     } else {
646                         clip.fBottom -= line.sizes().runTop(run) / 2;
647                     }
648                 } else if (rectHeightStyle == RectHeightStyle::kIncludeLineSpacingBottom) {
649                     if (&line == &fLines.back()) {
650                         clip.fBottom -= line.sizes().runTop(run);
651                     }
652                 } else if (rectHeightStyle == RectHeightStyle::kStrut) {
653                     auto strutStyle = this->paragraphStyle().getStrutStyle();
654                     if (strutStyle.getStrutEnabled() && strutStyle.getFontSize() > 0) {
655                         auto top = line.baseline() ; //+ line.sizes().runTop(run);
656                         clip.fTop = top + fStrutMetrics.ascent();
657                         clip.fBottom = top + fStrutMetrics.descent();
658                         clip.offset(line.offset());
659                     }
660                 }
661 
662                 // Check if we can merge two boxes
663                 bool mergedBoxes = false;
664                 if (!results.empty() &&
665                     lastRun != nullptr &&
666                     lastRun->lineHeight() == run->lineHeight() &&
667                     lastRun->font() == run->font()) {
668                     auto& lastBox = results.back();
669                     if (lastBox.rect.fTop == clip.fTop && lastBox.rect.fBottom == clip.fBottom &&
670                             (lastBox.rect.fLeft == clip.fRight || lastBox.rect.fRight == clip.fLeft)) {
671                         lastBox.rect.fLeft = SkTMin(lastBox.rect.fLeft, clip.fLeft);
672                         lastBox.rect.fRight = SkTMax(lastBox.rect.fRight, clip.fRight);
673                         mergedBoxes = true;
674                     }
675                 }
676                 lastRun = run;
677 
678                 if (!mergedBoxes) {
679                     results.emplace_back(
680                         clip, run->leftToRight() ? TextDirection::kLtr : TextDirection::kRtl);
681                 }
682 
683                 if (trailingSpaces.width() > 0) {
684                     results.emplace_back(trailingSpaces, paragraphTextDirection);
685                 }
686 
687                 return true;
688             });
689 
690         if (rectWidthStyle == RectWidthStyle::kMax) {
691             // Align the very left/right box horizontally
692             auto lineStart = line.offset().fX;
693             auto lineEnd = line.offset().fX + line.width();
694             auto left = results.front();
695             auto right = results.back();
696             if (left.rect.fLeft > lineStart && left.direction == TextDirection::kRtl) {
697                 left.rect.fRight = left.rect.fLeft;
698                 left.rect.fLeft = 0;
699                 results.insert(results.begin() + firstBoxOnTheLine + 1, left);
700             }
701             if (right.direction == TextDirection::kLtr &&
702                 right.rect.fRight >= lineEnd &&  right.rect.fRight < this->fMaxWidthWithTrailingSpaces) {
703                 right.rect.fLeft = right.rect.fRight;
704                 right.rect.fRight = this->fMaxWidthWithTrailingSpaces;
705                 results.emplace_back(right);
706             }
707         }
708     }
709 
710     return results;
711 }
712 // TODO: Deal with RTL here
getGlyphPositionAtCoordinate(SkScalar dx,SkScalar dy)713 PositionWithAffinity ParagraphImpl::getGlyphPositionAtCoordinate(SkScalar dx, SkScalar dy) {
714 
715     markGraphemes();
716     PositionWithAffinity result(0, Affinity::kDownstream);
717     for (auto& line : fLines) {
718         // Let's figure out if we can stop looking
719         auto offsetY = line.offset().fY;
720         if (dy > offsetY + line.height() && &line != &fLines.back()) {
721             // This line is not good enough
722             continue;
723         }
724 
725         // This is so far the the line vertically closest to our coordinates
726         // (or the first one, or the only one - all the same)
727         line.iterateThroughRuns(
728             line.textWithSpaces(),
729             0,
730             true,
731             [this, dx, &result]
732             (Run* run, size_t pos, size_t size, TextRange, SkRect clip, SkScalar shift, bool clippingNeeded) {
733 
734                 if (dx < clip.fLeft) {
735                     // All the other runs are placed right of this one
736                     result = { SkToS32(run->fClusterIndexes[pos]), kDownstream };
737                     return false;
738                 }
739 
740                 if (dx >= clip.fRight) {
741                     // We have to keep looking but just in case keep the last one as the closes
742                     // so far
743                     result = { SkToS32(run->fClusterIndexes[pos + size - 1]) + 1, kUpstream };
744                     return true;
745                 }
746 
747                 // So we found the run that contains our coordinates
748                 // Find the glyph position in the run that is the closest left of our point
749                 // TODO: binary search
750                 size_t found = pos;
751                 for (size_t i = pos; i < pos + size; ++i) {
752                     if (run->positionX(i) + shift > dx) {
753                         break;
754                     }
755                     found = i;
756                 }
757                 auto glyphStart = run->positionX(found);
758                 auto glyphWidth = run->positionX(found + 1) - run->positionX(found);
759                 auto clusterIndex8 = run->fClusterIndexes[found];
760 
761                 // Find the grapheme positions in codepoints that contains the point
762                 auto codepoint = std::lower_bound(
763                     fCodePoints.begin(), fCodePoints.end(),
764                     clusterIndex8,
765                     [](const Codepoint& lhs,size_t rhs) -> bool { return lhs.fTextIndex < rhs; });
766 
767                 auto codepointIndex = codepoint - fCodePoints.begin();
768                 auto codepoints = fGraphemes[codepoint->fGrapeme].fCodepointRange;
769                 auto graphemeSize = codepoints.width();
770 
771                 // We only need to inspect one glyph (maybe not even the entire glyph)
772                 SkScalar center;
773                 if (graphemeSize > 1) {
774                     auto averageCodepoint = glyphWidth / graphemeSize;
775                     auto codepointStart = glyphStart + averageCodepoint * (codepointIndex - codepoints.start);
776                     auto codepointEnd = codepointStart + averageCodepoint;
777                     center = (codepointStart + codepointEnd) / 2 + shift;
778                 } else {
779                     SkASSERT(graphemeSize == 1);
780                     auto codepointStart = glyphStart;
781                     auto codepointEnd = codepointStart + glyphWidth;
782                     center = (codepointStart + codepointEnd) / 2 + shift;
783                 }
784 
785                 if ((dx <= center) == run->leftToRight()) {
786                     result = { SkToS32(codepointIndex), kDownstream };
787                 } else {
788                     result = { SkToS32(codepointIndex + 1), kUpstream };
789                 }
790                 // No need to continue
791                 return false;
792             });
793 
794         if (dy < offsetY + line.height()) {
795             // The closest position on this line; next line is going to be even lower
796             break;
797         }
798     }
799 
800     // SkDebugf("getGlyphPositionAtCoordinate(%f,%f) = %d\n", dx, dy, result.position);
801     return result;
802 }
803 
804 // Finds the first and last glyphs that define a word containing
805 // the glyph at index offset.
806 // By "glyph" they mean a character index - indicated by Minikin's code
807 // TODO: make the breaker a member of ParagraphImpl
getWordBoundary(unsigned offset)808 SkRange<size_t> ParagraphImpl::getWordBoundary(unsigned offset) {
809     TextBreaker breaker;
810     if (!breaker.initialize(fTextSpan, UBRK_WORD)) {
811         return {0, 0};
812     }
813 
814     auto start = breaker.preceding(offset + 1);
815     auto end = breaker.following(start);
816 
817     return { start, end };
818 }
819 
text(TextRange textRange)820 SkSpan<const char> ParagraphImpl::text(TextRange textRange) {
821     SkASSERT(textRange.start < fText.size() && textRange.end <= fText.size());
822     return SkSpan<const char>(&fText[textRange.start], textRange.width());
823 }
824 
clusters(ClusterRange clusterRange)825 SkSpan<Cluster> ParagraphImpl::clusters(ClusterRange clusterRange) {
826     SkASSERT(clusterRange.start < fClusters.size() && clusterRange.end <= fClusters.size());
827     return SkSpan<Cluster>(&fClusters[clusterRange.start], clusterRange.width());
828 }
829 
cluster(ClusterIndex clusterIndex)830 Cluster& ParagraphImpl::cluster(ClusterIndex clusterIndex) {
831     SkASSERT(clusterIndex < fClusters.size());
832     return fClusters[clusterIndex];
833 }
834 
run(RunIndex runIndex)835 Run& ParagraphImpl::run(RunIndex runIndex) {
836     SkASSERT(runIndex < fRuns.size());
837     return fRuns[runIndex];
838 }
839 
blocks(BlockRange blockRange)840 SkSpan<Block> ParagraphImpl::blocks(BlockRange blockRange) {
841     SkASSERT(blockRange.start < fTextStyles.size() && blockRange.end <= fTextStyles.size());
842     return SkSpan<Block>(&fTextStyles[blockRange.start], blockRange.width());
843 }
844 
block(BlockIndex blockIndex)845 Block& ParagraphImpl::block(BlockIndex blockIndex) {
846     SkASSERT(blockIndex < fTextStyles.size());
847     return fTextStyles[blockIndex];
848 }
849 
resetRunShifts()850 void ParagraphImpl::resetRunShifts() {
851     fRunShifts.reset();
852     fRunShifts.push_back_n(fRuns.size(), RunShifts());
853     for (size_t i = 0; i < fRuns.size(); ++i) {
854         fRunShifts[i].fShifts.push_back_n(fRuns[i].size() + 1, 0.0);
855     }
856 }
857 
setState(InternalState state)858 void ParagraphImpl::setState(InternalState state) {
859     if (fState <= state) {
860         fState = state;
861         return;
862     }
863 
864     fState = state;
865     switch (fState) {
866         case kUnknown:
867             fRuns.reset();
868         case kShaped:
869             fClusters.reset();
870         case kClusterized:
871         case kMarked:
872         case kLineBroken:
873             this->resetContext();
874             this->resolveStrut();
875             this->resetRunShifts();
876             fLines.reset();
877         case kFormatted:
878             fPicture = nullptr;
879         case kDrawn:
880             break;
881     default:
882         break;
883     }
884 
885 }
886 
887 }  // namespace textlayout
888 }  // namespace skia
889