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/SkTypeface.h"
8#include "include/private/SkTFitsIn.h"
9#include "include/private/SkTo.h"
10#include "modules/skparagraph/include/Metrics.h"
11#include "modules/skparagraph/include/Paragraph.h"
12#include "modules/skparagraph/include/ParagraphStyle.h"
13#include "modules/skparagraph/include/TextStyle.h"
14#include "modules/skparagraph/src/OneLineShaper.h"
15#include "modules/skparagraph/src/ParagraphImpl.h"
16#include "modules/skparagraph/src/ParagraphUtil.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/core/SkSpan.h"
21#include "src/utils/SkUTF.h"
22
23#if defined(SK_USING_THIRD_PARTY_ICU)
24#include "third_party/icu/SkLoadICU.h"
25#endif
26
27#include <math.h>
28#include <algorithm>
29#include <utility>
30
31
32namespace skia {
33namespace textlayout {
34
35namespace {
36
37SkScalar littleRound(SkScalar a) {
38 // This rounding is done to match Flutter tests. Must be removed..
39 auto val = std::fabs(a);
40 if (val < 10000) {
41 return SkScalarRoundToScalar(a * 100.0)/100.0;
42 } else if (val < 100000) {
43 return SkScalarRoundToScalar(a * 10.0)/10.0;
44 } else {
45 return SkScalarFloorToScalar(a);
46 }
47}
48} // namespace
49
50TextRange operator*(const TextRange& a, const TextRange& b) {
51 if (a.start == b.start && a.end == b.end) return a;
52 auto begin = std::max(a.start, b.start);
53 auto end = std::min(a.end, b.end);
54 return end > begin ? TextRange(begin, end) : EMPTY_TEXT;
55}
56
57Paragraph::Paragraph(ParagraphStyle style, sk_sp<FontCollection> fonts)
58 : fFontCollection(std::move(fonts))
59 , fParagraphStyle(std::move(style))
60 , fAlphabeticBaseline(0)
61 , fIdeographicBaseline(0)
62 , fHeight(0)
63 , fWidth(0)
64 , fMaxIntrinsicWidth(0)
65 , fMinIntrinsicWidth(0)
66 , fLongestLine(0)
67 , fExceededMaxLines(0)
68{ }
69
70ParagraphImpl::ParagraphImpl(const SkString& text,
71 ParagraphStyle style,
72 SkTArray<Block, true> blocks,
73 SkTArray<Placeholder, true> placeholders,
74 sk_sp<FontCollection> fonts)
75 : Paragraph(std::move(style), std::move(fonts))
76 , fTextStyles(std::move(blocks))
77 , fPlaceholders(std::move(placeholders))
78 , fText(text)
79 , fState(kUnknown)
80 , fUnresolvedGlyphs(0)
81 , fPicture(nullptr)
82 , fStrutMetrics(false)
83 , fOldWidth(0)
84 , fOldHeight(0)
85 , fOrigin(SkRect::MakeEmpty()) {
86 fICU = ::skia::SkUnicode::Make();
87}
88
89ParagraphImpl::ParagraphImpl(const std::u16string& utf16text,
90 ParagraphStyle style,
91 SkTArray<Block, true> blocks,
92 SkTArray<Placeholder, true> placeholders,
93 sk_sp<FontCollection> fonts)
94 : ParagraphImpl(SkStringFromU16String(utf16text),
95 std::move(style),
96 std::move(blocks),
97 std::move(placeholders),
98 std::move(fonts))
99{ }
100
101ParagraphImpl::~ParagraphImpl() = default;
102
103int32_t ParagraphImpl::unresolvedGlyphs() {
104 if (fState < kShaped) {
105 return -1;
106 }
107
108 return fUnresolvedGlyphs;
109}
110
111void ParagraphImpl::layout(SkScalar rawWidth) {
112
113 // TODO: This rounding is done to match Flutter tests. Must be removed...
114 auto floorWidth = SkScalarFloorToScalar(rawWidth);
115
116 if ((!SkScalarIsFinite(rawWidth) || fLongestLine <= floorWidth) &&
117 fState >= kLineBroken &&
118 fLines.size() == 1 && fLines.front().ellipsis() == nullptr) {
119 // Most common case: one line of text (and one line is never justified, so no cluster shifts)
120 fWidth = floorWidth;
121 fState = kLineBroken;
122 } else if (fState >= kLineBroken && fOldWidth != floorWidth) {
123 // We can use the results from SkShaper but have to do EVERYTHING ELSE again
124 fState = kShaped;
125 } else {
126 // Nothing changed case: we can reuse the data from the last layout
127 }
128
129 if (fState < kShaped) {
130 this->fCodeUnitProperties.reset();
131 this->fCodeUnitProperties.push_back_n(fText.size() + 1, CodeUnitFlags::kNoCodeUnitFlag);
132 this->fWords.clear();
133 this->fBidiRegions.clear();
134 this->fUTF8IndexForUTF16Index.reset();
135 this->fUTF16IndexForUTF8Index.reset();
136 this->fRuns.reset();
137 if (!this->shapeTextIntoEndlessLine()) {
138 this->resetContext();
139 // TODO: merge the two next calls - they always come together
140 this->resolveStrut();
141 this->computeEmptyMetrics();
142 this->fLines.reset();
143
144 // Set the important values that are not zero
145 fWidth = floorWidth;
146 fHeight = fEmptyMetrics.height();
147 if (fParagraphStyle.getStrutStyle().getStrutEnabled() &&
148 fParagraphStyle.getStrutStyle().getForceStrutHeight()) {
149 fHeight = fStrutMetrics.height();
150 }
151 fAlphabeticBaseline = fEmptyMetrics.alphabeticBaseline();
152 fIdeographicBaseline = fEmptyMetrics.ideographicBaseline();
153 fLongestLine = FLT_MIN - FLT_MAX; // That is what flutter has
154 fMinIntrinsicWidth = 0;
155 fMaxIntrinsicWidth = 0;
156 this->fOldWidth = floorWidth;
157 this->fOldHeight = this->fHeight;
158
159 return;
160 }
161 fState = kShaped;
162 }
163
164 if (fState < kMarked) {
165 this->fClusters.reset();
166 this->resetShifts();
167 this->fClustersIndexFromCodeUnit.reset();
168 this->fClustersIndexFromCodeUnit.push_back_n(fText.size() + 1, EMPTY_INDEX);
169 this->buildClusterTable();
170 fState = kClusterized;
171 this->spaceGlyphs();
172 fState = kMarked;
173 }
174
175 if (fState < kLineBroken) {
176 this->resetContext();
177 this->resolveStrut();
178 this->computeEmptyMetrics();
179 this->fLines.reset();
180 this->breakShapedTextIntoLines(floorWidth);
181 fState = kLineBroken;
182 }
183
184 if (fState < kFormatted) {
185 // Build the picture lazily not until we actually have to paint (or never)
186 this->formatLines(fWidth);
187 // We have to calculate the paragraph boundaries only after we format the lines
188 this->calculateBoundaries();
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 //SkDebugf("layout('%s', %f): %f %f\n", fText.c_str(), rawWidth, fMinIntrinsicWidth, fMaxIntrinsicWidth);
206}
207
208void ParagraphImpl::paint(SkCanvas* canvas, SkScalar x, SkScalar y) {
209
210 if (fState < kDrawn) {
211 // Record the picture anyway (but if we have some pieces in the cache they will be used)
212 this->paintLinesIntoPicture();
213 fState = kDrawn;
214 }
215
216 SkMatrix matrix = SkMatrix::Translate(x + fOrigin.fLeft, y + fOrigin.fTop);
217 canvas->drawPicture(fPicture, &matrix, nullptr);
218}
219
220void ParagraphImpl::resetContext() {
221 fAlphabeticBaseline = 0;
222 fHeight = 0;
223 fWidth = 0;
224 fIdeographicBaseline = 0;
225 fMaxIntrinsicWidth = 0;
226 fMinIntrinsicWidth = 0;
227 fLongestLine = 0;
228 fMaxWidthWithTrailingSpaces = 0;
229 fExceededMaxLines = false;
230}
231
232// shapeTextIntoEndlessLine is the thing that calls this method
233// (that contains all ICU dependencies except for words)
234bool ParagraphImpl::computeCodeUnitProperties() {
235
236 #if defined(SK_USING_THIRD_PARTY_ICU)
237 if (!SkLoadICU()) {
238 return false;
239 }
240 #endif
241
242 // Get bidi regions
243 Direction textDirection = fParagraphStyle.getTextDirection() == TextDirection::kLtr
244 ? Direction::kLTR
245 : Direction::kRTL;
246 if (!fICU->getBidiRegions(fText.c_str(), fText.size(), textDirection, &fBidiRegions)) {
247 return false;
248 }
249
250 // Get white spaces
251 std::vector<Position> whitespaces;
252 if (!fICU->getWhitespaces(fText.c_str(), fText.size(), &whitespaces)) {
253 return false;
254 }
255 for (auto whitespace : whitespaces) {
256 fCodeUnitProperties[whitespace] |= CodeUnitFlags::kPartOfWhiteSpace;
257 }
258
259 // Get line breaks
260 std::vector<LineBreakBefore> lineBreaks;
261 if (!fICU->getLineBreaks(fText.c_str(), fText.size(), &lineBreaks)) {
262 return false;
263 }
264 for (auto& lineBreak : lineBreaks) {
265 fCodeUnitProperties[lineBreak.pos] |= lineBreak.breakType == LineBreakType::kHardLineBreak
266 ? CodeUnitFlags::kHardLineBreakBefore
267 : CodeUnitFlags::kSoftLineBreakBefore;
268 }
269
270 // Get graphemes
271 std::vector<Position> graphemes;
272 if (!fICU->getGraphemes(fText.c_str(), fText.size(), &graphemes)) {
273 return false;
274 }
275 for (auto pos : graphemes) {
276 fCodeUnitProperties[pos] |= CodeUnitFlags::kGraphemeStart;
277 }
278
279 return true;
280}
281
282// Clusters in the order of the input text
283void ParagraphImpl::buildClusterTable() {
284
285 // Walk through all the run in the direction of input text
286 for (auto& run : fRuns) {
287 auto runIndex = run.index();
288 auto runStart = fClusters.size();
289 if (run.isPlaceholder()) {
290 // Add info to cluster indexes table (text -> cluster)
291 for (auto i = run.textRange().start; i < run.textRange().end; ++i) {
292 fClustersIndexFromCodeUnit[i] = fClusters.size();
293 }
294 // There are no glyphs but we want to have one cluster
295 fClusters.emplace_back(this, runIndex, 0ul, 1ul, this->text(run.textRange()), run.advance().fX, run.advance().fY);
296 fCodeUnitProperties[run.textRange().start] |= CodeUnitFlags::kSoftLineBreakBefore;
297 fCodeUnitProperties[run.textRange().end] |= CodeUnitFlags::kSoftLineBreakBefore;
298 } else {
299 fClusters.reserve(fClusters.size() + run.size());
300 // Walk through the glyph in the direction of input text
301 run.iterateThroughClustersInTextOrder([runIndex, this](size_t glyphStart,
302 size_t glyphEnd,
303 size_t charStart,
304 size_t charEnd,
305 SkScalar width,
306 SkScalar height) {
307 SkASSERT(charEnd >= charStart);
308 // Add info to cluster indexes table (text -> cluster)
309 for (auto i = charStart; i < charEnd; ++i) {
310 fClustersIndexFromCodeUnit[i] = fClusters.size();
311 }
312 SkSpan<const char> text(fText.c_str() + charStart, charEnd - charStart);
313 fClusters.emplace_back(this, runIndex, glyphStart, glyphEnd, text, width, height);
314 });
315 }
316
317 run.setClusterRange(runStart, fClusters.size());
318 fMaxIntrinsicWidth += run.advance().fX;
319 }
320 fClustersIndexFromCodeUnit[fText.size()] = fClusters.size();
321 fClusters.emplace_back(this, EMPTY_RUN, 0, 0, this->text({fText.size(), fText.size()}), 0, 0);
322}
323
324void ParagraphImpl::spaceGlyphs() {
325
326 // Walk through all the clusters in the direction of shaped text
327 // (we have to walk through the styles in the same order, too)
328 SkScalar shift = 0;
329 for (auto& run : fRuns) {
330
331 // Skip placeholder runs
332 if (run.isPlaceholder()) {
333 continue;
334 }
335
336 bool soFarWhitespacesOnly = true;
337 run.iterateThroughClusters([this, &run, &shift, &soFarWhitespacesOnly](Cluster* cluster) {
338 // Shift the cluster (shift collected from the previous clusters)
339 run.shift(cluster, shift);
340
341 // Synchronize styles (one cluster can be covered by few styles)
342 Block* currentStyle = this->fTextStyles.begin();
343 while (!cluster->startsIn(currentStyle->fRange)) {
344 currentStyle++;
345 SkASSERT(currentStyle != this->fTextStyles.end());
346 }
347
348 SkASSERT(!currentStyle->fStyle.isPlaceholder());
349
350 // Process word spacing
351 if (currentStyle->fStyle.getWordSpacing() != 0) {
352 if (cluster->isWhitespaces() && cluster->isSoftBreak()) {
353 if (!soFarWhitespacesOnly) {
354 shift += run.addSpacesAtTheEnd(currentStyle->fStyle.getWordSpacing(), cluster);
355 }
356 }
357 }
358 // Process letter spacing
359 if (currentStyle->fStyle.getLetterSpacing() != 0) {
360 shift += run.addSpacesEvenly(currentStyle->fStyle.getLetterSpacing(), cluster);
361 }
362
363 if (soFarWhitespacesOnly && !cluster->isWhitespaces()) {
364 soFarWhitespacesOnly = false;
365 }
366 });
367 }
368}
369
370bool ParagraphImpl::shapeTextIntoEndlessLine() {
371
372 if (fText.size() == 0) {
373 return false;
374 }
375
376 // Check the font-resolved text against the cache
377 if (fFontCollection->getParagraphCache()->findParagraph(this)) {
378 return true;
379 }
380
381 if (!computeCodeUnitProperties()) {
382 return false;
383 }
384
385 fFontSwitches.reset();
386
387 OneLineShaper oneLineShaper(this);
388 auto result = oneLineShaper.shape();
389 fUnresolvedGlyphs = oneLineShaper.unresolvedGlyphs();
390
391 if (!result) {
392 return false;
393 } else {
394 // Add the paragraph to the cache
395 fFontCollection->getParagraphCache()->updateParagraph(this);
396 return true;
397 }
398}
399
400void ParagraphImpl::breakShapedTextIntoLines(SkScalar maxWidth) {
401 TextWrapper textWrapper;
402 textWrapper.breakTextIntoLines(
403 this,
404 maxWidth,
405 [&](TextRange text,
406 TextRange textWithSpaces,
407 ClusterRange clusters,
408 ClusterRange clustersWithGhosts,
409 SkScalar widthWithSpaces,
410 size_t startPos,
411 size_t endPos,
412 SkVector offset,
413 SkVector advance,
414 InternalLineMetrics metrics,
415 bool addEllipsis) {
416 // TODO: Take in account clipped edges
417 auto& line = this->addLine(offset, advance, text, textWithSpaces, clusters, clustersWithGhosts, widthWithSpaces, metrics);
418 if (addEllipsis) {
419 line.createEllipsis(maxWidth, fParagraphStyle.getEllipsis(), true);
420 if (line.ellipsis() != nullptr) {
421 // Make sure the paragraph boundaries include its ellipsis
422 auto size = line.ellipsis()->advance();
423 auto offset = line.ellipsis()->offset();
424 SkRect boundaries = SkRect::MakeXYWH(offset.fX, offset.fY, size.fX, size.fY);
425 fOrigin.joinPossiblyEmptyRect(boundaries);
426 }
427 }
428
429 fLongestLine = std::max(fLongestLine, nearlyZero(advance.fX) ? widthWithSpaces : advance.fX);
430 });
431
432 fHeight = textWrapper.height();
433 fWidth = maxWidth;
434 fMaxIntrinsicWidth = textWrapper.maxIntrinsicWidth();
435 fMinIntrinsicWidth = textWrapper.minIntrinsicWidth();
436 fAlphabeticBaseline = fLines.empty() ? fEmptyMetrics.alphabeticBaseline() : fLines.front().alphabeticBaseline();
437 fIdeographicBaseline = fLines.empty() ? fEmptyMetrics.ideographicBaseline() : fLines.front().ideographicBaseline();
438 fExceededMaxLines = textWrapper.exceededMaxLines();
439
440 // Correct the first and the last line ascents/descents if required
441 if ((fParagraphStyle.getTextHeightBehavior() & TextHeightBehavior::kDisableFirstAscent) != 0) {
442 auto& firstLine = fLines.front();
443 auto delta = firstLine.metricsWithoutMultiplier(TextHeightBehavior::kDisableFirstAscent);
444 if (!SkScalarNearlyZero(delta)) {
445 fHeight += delta;
446 // Shift all the lines up
447 for (auto& line : fLines) {
448 if (line.isFirstLine()) continue;
449 line.shiftVertically(delta);
450 }
451 }
452 }
453
454 if ((fParagraphStyle.getTextHeightBehavior() & TextHeightBehavior::kDisableLastDescent) != 0) {
455 auto& lastLine = fLines.back();
456 auto delta = lastLine.metricsWithoutMultiplier(TextHeightBehavior::kDisableLastDescent);
457 // It's the last line. There is nothing below to shift
458 fHeight += delta;
459 }
460}
461
462void ParagraphImpl::formatLines(SkScalar maxWidth) {
463 auto effectiveAlign = fParagraphStyle.effective_align();
464
465 if (!SkScalarIsFinite(maxWidth) && effectiveAlign != TextAlign::kLeft) {
466 // Special case: clean all text in case of maxWidth == INF & align != left
467 // We had to go through shaping though because we need all the measurement numbers
468 fLines.reset();
469 return;
470 }
471
472 for (auto& line : fLines) {
473 line.format(effectiveAlign, maxWidth);
474 }
475}
476
477void ParagraphImpl::paintLinesIntoPicture() {
478 SkPictureRecorder recorder;
479 SkCanvas* textCanvas = recorder.beginRecording(fOrigin.width(), fOrigin.height(), nullptr, 0);
480 textCanvas->translate(-fOrigin.fLeft, -fOrigin.fTop);
481
482 for (auto& line : fLines) {
483 line.paint(textCanvas);
484 }
485
486 fPicture = recorder.finishRecordingAsPicture();
487}
488
489void ParagraphImpl::resolveStrut() {
490 auto strutStyle = this->paragraphStyle().getStrutStyle();
491 if (!strutStyle.getStrutEnabled() || strutStyle.getFontSize() < 0) {
492 return;
493 }
494
495 std::vector<sk_sp<SkTypeface>> typefaces = fFontCollection->findTypefaces(strutStyle.getFontFamilies(), strutStyle.getFontStyle());
496 if (typefaces.empty()) {
497 SkDEBUGF("Could not resolve strut font\n");
498 return;
499 }
500
501 SkFont font(typefaces.front(), strutStyle.getFontSize());
502 SkFontMetrics metrics;
503 font.getMetrics(&metrics);
504
505 if (strutStyle.getHeightOverride()) {
506 auto strutHeight = metrics.fDescent - metrics.fAscent;
507 auto strutMultiplier = strutStyle.getHeight() * strutStyle.getFontSize();
508 fStrutMetrics = InternalLineMetrics(
509 (metrics.fAscent / strutHeight) * strutMultiplier,
510 (metrics.fDescent / strutHeight) * strutMultiplier,
511 strutStyle.getLeading() < 0 ? 0 : strutStyle.getLeading() * strutStyle.getFontSize());
512 } else {
513 fStrutMetrics = InternalLineMetrics(
514 metrics.fAscent,
515 metrics.fDescent,
516 strutStyle.getLeading() < 0 ? 0
517 : strutStyle.getLeading() * strutStyle.getFontSize());
518 }
519 fStrutMetrics.setForceStrut(this->paragraphStyle().getStrutStyle().getForceStrutHeight());
520}
521
522BlockRange ParagraphImpl::findAllBlocks(TextRange textRange) {
523 BlockIndex begin = EMPTY_BLOCK;
524 BlockIndex end = EMPTY_BLOCK;
525 for (size_t index = 0; index < fTextStyles.size(); ++index) {
526 auto& block = fTextStyles[index];
527 if (block.fRange.end <= textRange.start) {
528 continue;
529 }
530 if (block.fRange.start >= textRange.end) {
531 break;
532 }
533 if (begin == EMPTY_BLOCK) {
534 begin = index;
535 }
536 end = index;
537 }
538
539 return { begin, end + 1 };
540}
541
542void ParagraphImpl::calculateBoundaries() {
543 for (auto& line : fLines) {
544 fOrigin.joinPossiblyEmptyRect(line.calculateBoundaries());
545 }
546}
547
548TextLine& ParagraphImpl::addLine(SkVector offset,
549 SkVector advance,
550 TextRange text,
551 TextRange textWithSpaces,
552 ClusterRange clusters,
553 ClusterRange clustersWithGhosts,
554 SkScalar widthWithSpaces,
555 InternalLineMetrics sizes) {
556 // Define a list of styles that covers the line
557 auto blocks = findAllBlocks(text);
558 return fLines.emplace_back(this, offset, advance, blocks, text, textWithSpaces, clusters, clustersWithGhosts, widthWithSpaces, sizes);
559}
560
561// Returns a vector of bounding boxes that enclose all text between
562// start and end glyph indexes, including start and excluding end
563std::vector<TextBox> ParagraphImpl::getRectsForRange(unsigned start,
564 unsigned end,
565 RectHeightStyle rectHeightStyle,
566 RectWidthStyle rectWidthStyle) {
567 std::vector<TextBox> results;
568 if (fText.isEmpty()) {
569 if (start == 0 && end > 0) {
570 // On account of implied "\n" that is always at the end of the text
571 //SkDebugf("getRectsForRange(%d, %d): %f\n", start, end, fHeight);
572 results.emplace_back(SkRect::MakeXYWH(0, 0, 0, fHeight), fParagraphStyle.getTextDirection());
573 }
574 return results;
575 }
576
577 ensureUTF16Mapping();
578
579 if (start >= end || start > fUTF8IndexForUTF16Index.size() || end == 0) {
580 return results;
581 }
582
583 // Adjust the text to grapheme edges
584 // Apparently, text editor CAN move inside graphemes but CANNOT select a part of it.
585 // I don't know why - the solution I have here returns an empty box for every query that
586 // does not contain an end of a grapheme.
587 // Once a cursor is inside a complex grapheme I can press backspace and cause trouble.
588 // To avoid any problems, I will not allow any selection of a part of a grapheme.
589 // One flutter test fails because of it but the editing experience is correct
590 // (although you have to press the cursor many times before it moves to the next grapheme).
591 TextRange text(fText.size(), fText.size());
592 if (start < fUTF8IndexForUTF16Index.size()) {
593 text.start = findGraphemeStart(fUTF8IndexForUTF16Index[start]);
594 }
595 if (end < fUTF8IndexForUTF16Index.size()) {
596 text.end = findGraphemeStart(fUTF8IndexForUTF16Index[end]);
597 }
598
599 for (auto& line : fLines) {
600 auto lineText = line.textWithSpaces();
601 auto intersect = lineText * text;
602 if (intersect.empty() && lineText.start != text.start) {
603 continue;
604 }
605
606 line.getRectsForRange(intersect, rectHeightStyle, rectWidthStyle, results);
607 }
608/*
609 SkDebugf("getRectsForRange(%d, %d)\n", start, end);
610 for (auto& r : results) {
611 r.rect.fLeft = littleRound(r.rect.fLeft);
612 r.rect.fRight = littleRound(r.rect.fRight);
613 r.rect.fTop = littleRound(r.rect.fTop);
614 r.rect.fBottom = littleRound(r.rect.fBottom);
615 SkDebugf("[%f:%f * %f:%f]\n", r.rect.fLeft, r.rect.fRight, r.rect.fTop, r.rect.fBottom);
616 }
617*/
618 return results;
619}
620
621std::vector<TextBox> ParagraphImpl::getRectsForPlaceholders() {
622 std::vector<TextBox> boxes;
623 if (fText.isEmpty()) {
624 return boxes;
625 }
626 if (fPlaceholders.size() == 1) {
627 // We always have one fake placeholder
628 return boxes;
629 }
630 for (auto& line : fLines) {
631 line.getRectsForPlaceholders(boxes);
632 }
633 /*
634 SkDebugf("getRectsForPlaceholders('%s'): %d\n", fText.c_str(), boxes.size());
635 for (auto& r : boxes) {
636 r.rect.fLeft = littleRound(r.rect.fLeft);
637 r.rect.fRight = littleRound(r.rect.fRight);
638 r.rect.fTop = littleRound(r.rect.fTop);
639 r.rect.fBottom = littleRound(r.rect.fBottom);
640 SkDebugf("[%f:%f * %f:%f] %s\n", r.rect.fLeft, r.rect.fRight, r.rect.fTop, r.rect.fBottom,
641 (r.direction == TextDirection::kLtr ? "left" : "right"));
642 }
643 */
644 return boxes;
645}
646
647// TODO: Optimize (save cluster <-> codepoint connection)
648PositionWithAffinity ParagraphImpl::getGlyphPositionAtCoordinate(SkScalar dx, SkScalar dy) {
649
650 if (fText.isEmpty()) {
651 return {0, Affinity::kDownstream};
652 }
653
654 ensureUTF16Mapping();
655
656 for (auto& line : fLines) {
657 // Let's figure out if we can stop looking
658 auto offsetY = line.offset().fY;
659 if (dy >= offsetY + line.height() && &line != &fLines.back()) {
660 // This line is not good enough
661 continue;
662 }
663
664 // This is so far the the line vertically closest to our coordinates
665 // (or the first one, or the only one - all the same)
666
667 auto result = line.getGlyphPositionAtCoordinate(dx);
668 //SkDebugf("getGlyphPositionAtCoordinate(%f, %f): %d %s\n", dx, dy, result.position,
669 // result.affinity == Affinity::kUpstream ? "up" : "down");
670 return result;
671 }
672
673 return {0, Affinity::kDownstream};
674}
675
676// Finds the first and last glyphs that define a word containing
677// the glyph at index offset.
678// By "glyph" they mean a character index - indicated by Minikin's code
679SkRange<size_t> ParagraphImpl::getWordBoundary(unsigned offset) {
680
681 if (fWords.empty()) {
682 if (!fICU->getWords(fText.c_str(), fText.size(), &fWords)) {
683 return {0, 0 };
684 }
685 }
686
687 int32_t start = 0;
688 int32_t end = 0;
689 for (size_t i = 0; i < fWords.size(); ++i) {
690 auto word = fWords[i];
691 if (word <= offset) {
692 start = word;
693 end = word;
694 } else if (word > offset) {
695 end = word;
696 break;
697 }
698 }
699
700 //SkDebugf("getWordBoundary(%d): %d - %d\n", offset, start, end);
701 return { SkToU32(start), SkToU32(end) };
702}
703
704void ParagraphImpl::forEachCodeUnitPropertyRange(CodeUnitFlags property, CodeUnitRangeVisitor visitor) {
705
706 size_t first = 0;
707 for (size_t i = 1; i < fText.size(); ++i) {
708 auto properties = fCodeUnitProperties[i];
709 if (properties & property) {
710 visitor({first, i});
711 first = i;
712 }
713
714 }
715 visitor({first, fText.size()});
716}
717
718size_t ParagraphImpl::getWhitespacesLength(TextRange textRange) {
719 size_t len = 0;
720 for (auto i = textRange.start; i < textRange.end; ++i) {
721 auto properties = fCodeUnitProperties[i];
722 if (properties & CodeUnitFlags::kPartOfWhiteSpace) {
723 ++len;
724 }
725 }
726 return len;
727}
728
729void ParagraphImpl::getLineMetrics(std::vector<LineMetrics>& metrics) {
730 metrics.clear();
731 for (auto& line : fLines) {
732 metrics.emplace_back(line.getMetrics());
733 }
734}
735
736SkSpan<const char> ParagraphImpl::text(TextRange textRange) {
737 SkASSERT(textRange.start <= fText.size() && textRange.end <= fText.size());
738 auto start = fText.c_str() + textRange.start;
739 return SkSpan<const char>(start, textRange.width());
740}
741
742SkSpan<Cluster> ParagraphImpl::clusters(ClusterRange clusterRange) {
743 SkASSERT(clusterRange.start < fClusters.size() && clusterRange.end <= fClusters.size());
744 return SkSpan<Cluster>(&fClusters[clusterRange.start], clusterRange.width());
745}
746
747Cluster& ParagraphImpl::cluster(ClusterIndex clusterIndex) {
748 SkASSERT(clusterIndex < fClusters.size());
749 return fClusters[clusterIndex];
750}
751
752Run& ParagraphImpl::runByCluster(ClusterIndex clusterIndex) {
753 auto start = cluster(clusterIndex);
754 return this->run(start.fRunIndex);
755}
756
757SkSpan<Block> ParagraphImpl::blocks(BlockRange blockRange) {
758 SkASSERT(blockRange.start < fTextStyles.size() && blockRange.end <= fTextStyles.size());
759 return SkSpan<Block>(&fTextStyles[blockRange.start], blockRange.width());
760}
761
762Block& ParagraphImpl::block(BlockIndex blockIndex) {
763 SkASSERT(blockIndex < fTextStyles.size());
764 return fTextStyles[blockIndex];
765}
766
767void ParagraphImpl::setState(InternalState state) {
768 if (fState <= state) {
769 fState = state;
770 return;
771 }
772
773 fState = state;
774 switch (fState) {
775 case kUnknown:
776 fRuns.reset();
777 fCodeUnitProperties.reset();
778 fCodeUnitProperties.push_back_n(fText.size() + 1, kNoCodeUnitFlag);
779 fWords.clear();
780 fBidiRegions.clear();
781 fUTF8IndexForUTF16Index.reset();
782 fUTF16IndexForUTF8Index.reset();
783 [[fallthrough]];
784
785 case kShaped:
786 fClusters.reset();
787 [[fallthrough]];
788
789 case kClusterized:
790 case kMarked:
791 case kLineBroken:
792 this->resetContext();
793 this->resolveStrut();
794 this->computeEmptyMetrics();
795 this->resetShifts();
796 fLines.reset();
797 [[fallthrough]];
798
799 case kFormatted:
800 fPicture = nullptr;
801 [[fallthrough]];
802
803 case kDrawn:
804 default:
805 break;
806 }
807}
808
809void ParagraphImpl::computeEmptyMetrics() {
810 auto defaultTextStyle = paragraphStyle().getTextStyle();
811
812 auto typefaces = fontCollection()->findTypefaces(
813 defaultTextStyle.getFontFamilies(), defaultTextStyle.getFontStyle());
814 auto typeface = typefaces.empty() ? nullptr : typefaces.front();
815
816 SkFont font(typeface, defaultTextStyle.getFontSize());
817
818 fEmptyMetrics = InternalLineMetrics(font, paragraphStyle().getStrutStyle().getForceStrutHeight());
819 if (!paragraphStyle().getStrutStyle().getForceStrutHeight() &&
820 defaultTextStyle.getHeightOverride()) {
821 auto multiplier =
822 defaultTextStyle.getHeight() * defaultTextStyle.getFontSize() / fEmptyMetrics.height();
823 fEmptyMetrics = InternalLineMetrics(fEmptyMetrics.ascent() * multiplier,
824 fEmptyMetrics.descent() * multiplier,
825 fEmptyMetrics.leading() * multiplier);
826 }
827
828 if (fParagraphStyle.getStrutStyle().getStrutEnabled()) {
829 fStrutMetrics.updateLineMetrics(fEmptyMetrics);
830 }
831}
832
833void ParagraphImpl::updateText(size_t from, SkString text) {
834 fText.remove(from, from + text.size());
835 fText.insert(from, text);
836 fState = kUnknown;
837 fOldWidth = 0;
838 fOldHeight = 0;
839}
840
841void ParagraphImpl::updateFontSize(size_t from, size_t to, SkScalar fontSize) {
842
843 SkASSERT(from == 0 && to == fText.size());
844 auto defaultStyle = fParagraphStyle.getTextStyle();
845 defaultStyle.setFontSize(fontSize);
846 fParagraphStyle.setTextStyle(defaultStyle);
847
848 for (auto& textStyle : fTextStyles) {
849 textStyle.fStyle.setFontSize(fontSize);
850 }
851
852 fState = kUnknown;
853 fOldWidth = 0;
854 fOldHeight = 0;
855}
856
857void ParagraphImpl::updateTextAlign(TextAlign textAlign) {
858 fParagraphStyle.setTextAlign(textAlign);
859
860 if (fState >= kLineBroken) {
861 fState = kLineBroken;
862 }
863}
864
865void ParagraphImpl::updateForegroundPaint(size_t from, size_t to, SkPaint paint) {
866 SkASSERT(from == 0 && to == fText.size());
867 auto defaultStyle = fParagraphStyle.getTextStyle();
868 defaultStyle.setForegroundColor(paint);
869 fParagraphStyle.setTextStyle(defaultStyle);
870
871 for (auto& textStyle : fTextStyles) {
872 textStyle.fStyle.setForegroundColor(paint);
873 }
874}
875
876void ParagraphImpl::updateBackgroundPaint(size_t from, size_t to, SkPaint paint) {
877 SkASSERT(from == 0 && to == fText.size());
878 auto defaultStyle = fParagraphStyle.getTextStyle();
879 defaultStyle.setBackgroundColor(paint);
880 fParagraphStyle.setTextStyle(defaultStyle);
881
882 for (auto& textStyle : fTextStyles) {
883 textStyle.fStyle.setBackgroundColor(paint);
884 }
885}
886
887TextIndex ParagraphImpl::findGraphemeStart(TextIndex index) {
888 if (index == fText.size()) {
889 return index;
890 }
891 while (index > 0 &&
892 (fCodeUnitProperties[index] & CodeUnitFlags::kGraphemeStart) == 0) {
893 --index;
894 }
895 return index;
896}
897
898void ParagraphImpl::ensureUTF16Mapping() {
899 if (!fUTF16IndexForUTF8Index.empty()) {
900 return;
901 }
902 // Fill out code points 16
903 auto ptr = fText.c_str();
904 auto end = fText.c_str() + fText.size();
905 while (ptr < end) {
906
907 size_t index = ptr - fText.c_str();
908 SkUnichar u = SkUTF::NextUTF8(&ptr, end);
909
910 // All utf8 units refer to the same codepoint
911 size_t next = ptr - fText.c_str();
912 for (auto i = index; i < next; ++i) {
913 fUTF16IndexForUTF8Index.emplace_back(fUTF8IndexForUTF16Index.size());
914 }
915 SkASSERT(fUTF16IndexForUTF8Index.size() == next);
916
917 // One or two codepoints refer to the same text index
918 uint16_t buffer[2];
919 size_t count = SkUTF::ToUTF16(u, buffer);
920 fUTF8IndexForUTF16Index.emplace_back(index);
921 if (count > 1) {
922 fUTF8IndexForUTF16Index.emplace_back(index);
923 }
924 }
925 fUTF16IndexForUTF8Index.emplace_back(fUTF8IndexForUTF16Index.size());
926 fUTF8IndexForUTF16Index.emplace_back(fText.size());
927}
928
929} // namespace textlayout
930} // namespace skia
931