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