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(¶graph)
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