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