Flutter Engine
The Flutter Engine
TextWrapper.cpp
Go to the documentation of this file.
1// Copyright 2019 Google LLC.
4
5namespace skia {
6namespace textlayout {
7
8namespace {
9struct LineBreakerWithLittleRounding {
10 LineBreakerWithLittleRounding(SkScalar maxWidth, bool applyRoundingHack)
11 : fLower(maxWidth - 0.25f)
12 , fMaxWidth(maxWidth)
13 , fUpper(maxWidth + 0.25f)
14 , fApplyRoundingHack(applyRoundingHack) {}
15
16 bool breakLine(SkScalar width) const {
17 if (width < fLower) {
18 return false;
19 } else if (width > fUpper) {
20 return true;
21 }
22
23 auto val = std::fabs(width);
24 SkScalar roundedWidth;
26 if (val < 10000) {
27 roundedWidth = SkScalarRoundToScalar(width * 100) * (1.0f/100);
28 } else if (val < 100000) {
29 roundedWidth = SkScalarRoundToScalar(width * 10) * (1.0f/10);
30 } else {
31 roundedWidth = SkScalarFloorToScalar(width);
32 }
33 } else {
34 if (val < 10000) {
35 roundedWidth = SkScalarFloorToScalar(width * 100) * (1.0f/100);
36 } else if (val < 100000) {
37 roundedWidth = SkScalarFloorToScalar(width * 10) * (1.0f/10);
38 } else {
39 roundedWidth = SkScalarFloorToScalar(width);
40 }
41 }
42 return roundedWidth > fMaxWidth;
43 }
44
47};
48} // namespace
49
50// Since we allow cluster clipping when they don't fit
51// we have to work with stretches - parts of clusters
52void TextWrapper::lookAhead(SkScalar maxWidth, Cluster* endOfClusters, bool applyRoundingHack) {
53
54 reset();
55 fEndLine.metrics().clean();
56 fWords.startFrom(fEndLine.startCluster(), fEndLine.startPos());
57 fClusters.startFrom(fEndLine.startCluster(), fEndLine.startPos());
58 fClip.startFrom(fEndLine.startCluster(), fEndLine.startPos());
59
60 LineBreakerWithLittleRounding breaker(maxWidth, applyRoundingHack);
61 Cluster* nextNonBreakingSpace = nullptr;
62 for (auto cluster = fEndLine.endCluster(); cluster < endOfClusters; ++cluster) {
63 if (cluster->isHardBreak()) {
64 } else if (
65 // TODO: Trying to deal with flutter rounding problem. Must be removed...
66 SkScalar width = fWords.width() + fClusters.width() + cluster->width();
67 breaker.breakLine(width)) {
68 if (cluster->isWhitespaceBreak()) {
69 // It's the end of the word
70 fClusters.extend(cluster);
71 fMinIntrinsicWidth = std::max(fMinIntrinsicWidth, this->getClustersTrimmedWidth());
72 fWords.extend(fClusters);
73 continue;
74 } else if (cluster->run().isPlaceholder()) {
75 if (!fClusters.empty()) {
76 // Placeholder ends the previous word
77 fMinIntrinsicWidth = std::max(fMinIntrinsicWidth, this->getClustersTrimmedWidth());
78 fWords.extend(fClusters);
79 }
80
81 if (cluster->width() > maxWidth && fWords.empty()) {
82 // Placeholder is the only text and it's longer than the line;
83 // it does not count in fMinIntrinsicWidth
84 fClusters.extend(cluster);
85 fTooLongCluster = true;
86 fTooLongWord = true;
87 } else {
88 // Placeholder does not fit the line; it will be considered again on the next line
89 }
90 break;
91 }
92
93 // Walk further to see if there is a too long word, cluster or glyph
94 SkScalar nextWordLength = fClusters.width();
95 SkScalar nextShortWordLength = nextWordLength;
96 for (auto further = cluster; further != endOfClusters; ++further) {
97 if (further->isSoftBreak() || further->isHardBreak() || further->isWhitespaceBreak()) {
98 break;
99 }
100 if (further->run().isPlaceholder()) {
101 // Placeholder ends the word
102 break;
103 }
104
105 if (nextWordLength > 0 && nextWordLength <= maxWidth && further->isIntraWordBreak()) {
106 // The cluster is spaces but not the end of the word in a normal sense
107 nextNonBreakingSpace = further;
108 nextShortWordLength = nextWordLength;
109 }
110
111 if (maxWidth == 0) {
112 // This is a tricky flutter case: layout(width:0) places 1 cluster on each line
113 nextWordLength = std::max(nextWordLength, further->width());
114 } else {
115 nextWordLength += further->width();
116 }
117 }
118 if (nextWordLength > maxWidth) {
119 if (nextNonBreakingSpace != nullptr) {
120 // We only get here if the non-breaking space improves our situation
121 // (allows us to break the text to fit the word)
122 if (SkScalar shortLength = fWords.width() + nextShortWordLength;
123 !breaker.breakLine(shortLength)) {
124 // We can add the short word to the existing line
125 fClusters = TextStretch(fClusters.startCluster(), nextNonBreakingSpace, fClusters.metrics().getForceStrut());
126 fMinIntrinsicWidth = std::max(fMinIntrinsicWidth, nextShortWordLength);
127 fWords.extend(fClusters);
128 } else {
129 // We can place the short word on the next line
130 fClusters.clean();
131 }
132 // Either way we are not in "word is too long" situation anymore
133 break;
134 }
135 // If the word is too long we can break it right now and hope it's enough
136 fMinIntrinsicWidth = std::max(fMinIntrinsicWidth, nextWordLength);
137 if (fClusters.endPos() - fClusters.startPos() > 1 ||
138 fWords.empty()) {
139 fTooLongWord = true;
140 } else {
141 // Even if the word is too long there is a very little space on this line.
142 // let's deal with it on the next line.
143 }
144 }
145
146 if (cluster->width() > maxWidth) {
147 fClusters.extend(cluster);
148 fTooLongCluster = true;
149 fTooLongWord = true;
150 }
151 break;
152 }
153
154 if (cluster->run().isPlaceholder()) {
155 if (!fClusters.empty()) {
156 // Placeholder ends the previous word (placeholders are ignored in trimming)
157 fMinIntrinsicWidth = std::max(fMinIntrinsicWidth, getClustersTrimmedWidth());
158 fWords.extend(fClusters);
159 }
160
161 // Placeholder is separate word and its width now is counted in minIntrinsicWidth
162 fMinIntrinsicWidth = std::max(fMinIntrinsicWidth, cluster->width());
163 fWords.extend(cluster);
164 } else {
165 fClusters.extend(cluster);
166
167 // Keep adding clusters/words
168 if (fClusters.endOfWord()) {
169 fMinIntrinsicWidth = std::max(fMinIntrinsicWidth, getClustersTrimmedWidth());
170 fWords.extend(fClusters);
171 }
172 }
173
174 if ((fHardLineBreak = cluster->isHardBreak())) {
175 // Stop at the hard line break
176 break;
177 }
178 }
179}
180
181void TextWrapper::moveForward(bool hasEllipsis) {
182
183 // We normally break lines by words.
184 // The only way we may go to clusters is if the word is too long or
185 // it's the first word and it has an ellipsis attached to it.
186 // If nothing fits we show the clipping.
187 if (!fWords.empty()) {
188 fEndLine.extend(fWords);
189#ifdef SK_IGNORE_SKPARAGRAPH_ELLIPSIS_FIX
190 if (!fTooLongWord || hasEllipsis) { // Ellipsis added to a word
191#else
192 if (!fTooLongWord && !hasEllipsis) { // Ellipsis added to a grapheme
193#endif
194 return;
195 }
196 }
197 if (!fClusters.empty()) {
198 fEndLine.extend(fClusters);
199 if (!fTooLongCluster) {
200 return;
201 }
202 }
203
204 if (!fClip.empty()) {
205 // Flutter: forget the clipped cluster but keep the metrics
206 fEndLine.metrics().add(fClip.metrics());
207 }
208}
209
210// Special case for start/end cluster since they can be clipped
211void TextWrapper::trimEndSpaces(TextAlign align) {
212 // Remember the breaking position
213 fEndLine.saveBreak();
214 // Skip all space cluster at the end
215 for (auto cluster = fEndLine.endCluster();
216 cluster >= fEndLine.startCluster() && cluster->isWhitespaceBreak();
217 --cluster) {
218 fEndLine.trim(cluster);
219 }
220 fEndLine.trim();
221}
222
223SkScalar TextWrapper::getClustersTrimmedWidth() {
224 // Move the end of the line to the left
225 SkScalar width = 0;
226 bool trailingSpaces = true;
227 for (auto cluster = fClusters.endCluster(); cluster >= fClusters.startCluster(); --cluster) {
228 if (cluster->run().isPlaceholder()) {
229 continue;
230 }
231 if (trailingSpaces) {
232 if (!cluster->isWhitespaceBreak()) {
233 width += cluster->trimmedWidth(cluster->endPos());
234 trailingSpaces = false;
235 }
236 continue;
237 }
238 width += cluster->width();
239 }
240 return width;
241}
242
243// Trim the beginning spaces in case of soft line break
244std::tuple<Cluster*, size_t, SkScalar> TextWrapper::trimStartSpaces(Cluster* endOfClusters) {
245
246 if (fHardLineBreak) {
247 // End of line is always end of cluster, but need to skip \n
248 auto width = fEndLine.width();
249 auto cluster = fEndLine.endCluster() + 1;
250 while (cluster < fEndLine.breakCluster() && cluster->isWhitespaceBreak()) {
251 width += cluster->width();
252 ++cluster;
253 }
254 return std::make_tuple(fEndLine.breakCluster() + 1, 0, width);
255 }
256
257 // breakCluster points to the end of the line;
258 // It's a soft line break so we need to move lineStart forward skipping all the spaces
259 auto width = fEndLine.widthWithGhostSpaces();
260 auto cluster = fEndLine.breakCluster() + 1;
261 while (cluster < endOfClusters && cluster->isWhitespaceBreak()) {
262 width += cluster->width();
263 ++cluster;
264 }
265
266 if (fEndLine.breakCluster()->isWhitespaceBreak() && fEndLine.breakCluster() < endOfClusters) {
267 // In case of a soft line break by the whitespace
268 // fBreak should point to the beginning of the next line
269 // (it only matters when there are trailing spaces)
270 fEndLine.shiftBreak();
271 }
272
273 return std::make_tuple(cluster, 0, width);
274}
275
276// TODO: refactor the code for line ending (with/without ellipsis)
278 SkScalar maxWidth,
279 const AddLineToParagraph& addLine) {
280 fHeight = 0;
281 fMinIntrinsicWidth = std::numeric_limits<SkScalar>::min();
282 fMaxIntrinsicWidth = std::numeric_limits<SkScalar>::min();
283
284 auto span = parent->clusters();
285 if (span.empty()) {
286 return;
287 }
288 auto maxLines = parent->paragraphStyle().getMaxLines();
289 auto align = parent->paragraphStyle().effective_align();
290 auto unlimitedLines = maxLines == std::numeric_limits<size_t>::max();
291 auto endlessLine = !SkIsFinite(maxWidth);
292 auto hasEllipsis = parent->paragraphStyle().ellipsized();
293
296 bool firstLine = true; // We only interested in fist line if we have to disable the first ascent
297
298 SkScalar softLineMaxIntrinsicWidth = 0;
299 fEndLine = TextStretch(span.begin(), span.begin(), parent->strutForceHeight());
300 auto end = span.end() - 1;
301 auto start = span.begin();
302 InternalLineMetrics maxRunMetrics;
303 bool needEllipsis = false;
304 while (fEndLine.endCluster() != end) {
305
306 this->lookAhead(maxWidth, end, parent->getApplyRoundingHack());
307
308 auto lastLine = (hasEllipsis && unlimitedLines) || fLineNumber >= maxLines;
309 needEllipsis = hasEllipsis && !endlessLine && lastLine;
310
311 this->moveForward(needEllipsis);
312 needEllipsis &= fEndLine.endCluster() < end - 1; // Only if we have some text to ellipsize
313
314 // Do not trim end spaces on the naturally last line of the left aligned text
315 this->trimEndSpaces(align);
316
317 // For soft line breaks add to the line all the spaces next to it
318 Cluster* startLine;
319 size_t pos;
320 SkScalar widthWithSpaces;
321 std::tie(startLine, pos, widthWithSpaces) = this->trimStartSpaces(end);
322
323 if (needEllipsis && !fHardLineBreak) {
324 // This is what we need to do to preserve a space before the ellipsis
325 fEndLine.restoreBreak();
326 widthWithSpaces = fEndLine.widthWithGhostSpaces();
327 }
328
329 // If the line is empty with the hard line break, let's take the paragraph font (flutter???)
330 if (fEndLine.metrics().isClean()) {
331 fEndLine.setMetrics(parent->getEmptyMetrics());
332 }
333
334 // Deal with placeholder clusters == runs[@size==1]
335 Run* lastRun = nullptr;
336 for (auto cluster = fEndLine.startCluster(); cluster <= fEndLine.endCluster(); ++cluster) {
337 auto r = cluster->runOrNull();
338 if (r == lastRun) {
339 continue;
340 }
341 lastRun = r;
342 if (lastRun->placeholderStyle() != nullptr) {
343 SkASSERT(lastRun->size() == 1);
344 // Update the placeholder metrics so we can get the placeholder positions later
345 // and the line metrics (to make sure the placeholder fits)
346 lastRun->updateMetrics(&fEndLine.metrics());
347 }
348 }
349
350 // Before we update the line metrics with struts,
351 // let's save it for GetRectsForRange(RectHeightStyle::kMax)
352 maxRunMetrics = fEndLine.metrics();
353 maxRunMetrics.fForceStrut = false;
354
355 // TODO: keep start/end/break info for text and runs but in a better way that below
356 TextRange textExcludingSpaces(fEndLine.startCluster()->textRange().start, fEndLine.endCluster()->textRange().end);
357 TextRange text(fEndLine.startCluster()->textRange().start, fEndLine.breakCluster()->textRange().start);
358 TextRange textIncludingNewlines(fEndLine.startCluster()->textRange().start, startLine->textRange().start);
359 if (startLine == end) {
360 textIncludingNewlines.end = parent->text().size();
361 text.end = parent->text().size();
362 }
363 ClusterRange clusters(fEndLine.startCluster() - start, fEndLine.endCluster() - start + 1);
364 ClusterRange clustersWithGhosts(fEndLine.startCluster() - start, startLine - start);
365
366 if (disableFirstAscent && firstLine) {
367 fEndLine.metrics().fAscent = fEndLine.metrics().fRawAscent;
368 }
369 if (disableLastDescent && (lastLine || (startLine == end && !fHardLineBreak ))) {
370 fEndLine.metrics().fDescent = fEndLine.metrics().fRawDescent;
371 }
372
373 if (parent->strutEnabled()) {
374 // Make sure font metrics are not less than the strut
375 parent->strutMetrics().updateLineMetrics(fEndLine.metrics());
376 }
377
378 SkScalar lineHeight = fEndLine.metrics().height();
379 firstLine = false;
380
381 if (fEndLine.empty()) {
382 // Correct text and clusters (make it empty for an empty line)
383 textExcludingSpaces.end = textExcludingSpaces.start;
384 clusters.end = clusters.start;
385 }
386
387 // In case of a force wrapping we don't have a break cluster and have to use the end cluster
388 text.end = std::max(text.end, textExcludingSpaces.end);
389
390 addLine(textExcludingSpaces,
391 text,
392 textIncludingNewlines, clusters, clustersWithGhosts, widthWithSpaces,
393 fEndLine.startPos(),
394 fEndLine.endPos(),
395 SkVector::Make(0, fHeight),
396 SkVector::Make(fEndLine.width(), lineHeight),
397 fEndLine.metrics(),
398 needEllipsis && !fHardLineBreak);
399
400 softLineMaxIntrinsicWidth += widthWithSpaces;
401
402 fMaxIntrinsicWidth = std::max(fMaxIntrinsicWidth, softLineMaxIntrinsicWidth);
403 if (fHardLineBreak) {
404 softLineMaxIntrinsicWidth = 0;
405 }
406 // Start a new line
407 fHeight += lineHeight;
408 if (!fHardLineBreak || startLine != end) {
409 fEndLine.clean();
410 }
411 fEndLine.startFrom(startLine, pos);
412 parent->fMaxWidthWithTrailingSpaces = std::max(parent->fMaxWidthWithTrailingSpaces, widthWithSpaces);
413
414 if (hasEllipsis && unlimitedLines) {
415 // There is one case when we need an ellipsis on a separate line
416 // after a line break when width is infinite
417 if (!fHardLineBreak) {
418 break;
419 }
420 } else if (lastLine) {
421 // There is nothing more to draw
422 fHardLineBreak = false;
423 break;
424 }
425
426 ++fLineNumber;
427 }
428
429 // We finished formatting the text but we need to scan the rest for some numbers
430 // TODO: make it a case of a normal flow
431 if (fEndLine.endCluster() != nullptr) {
432 auto lastWordLength = 0.0f;
433 auto cluster = fEndLine.endCluster();
434 while (cluster != end || cluster->endPos() < end->endPos()) {
435 fExceededMaxLines = true;
436 if (cluster->isHardBreak()) {
437 // Hard line break ends the word and the line
438 fMaxIntrinsicWidth = std::max(fMaxIntrinsicWidth, softLineMaxIntrinsicWidth);
439 softLineMaxIntrinsicWidth = 0;
440 fMinIntrinsicWidth = std::max(fMinIntrinsicWidth, lastWordLength);
441 lastWordLength = 0;
442 } else if (cluster->isWhitespaceBreak()) {
443 // Whitespaces end the word
444 softLineMaxIntrinsicWidth += cluster->width();
445 fMinIntrinsicWidth = std::max(fMinIntrinsicWidth, lastWordLength);
446 lastWordLength = 0;
447 } else if (cluster->run().isPlaceholder()) {
448 // Placeholder ends the previous word and creates a separate one
449 fMinIntrinsicWidth = std::max(fMinIntrinsicWidth, lastWordLength);
450 // Placeholder width now counts in fMinIntrinsicWidth
451 softLineMaxIntrinsicWidth += cluster->width();
452 fMinIntrinsicWidth = std::max(fMinIntrinsicWidth, cluster->width());
453 lastWordLength = 0;
454 } else {
455 // Nothing out of ordinary - just add this cluster to the word and to the line
456 softLineMaxIntrinsicWidth += cluster->width();
457 lastWordLength += cluster->width();
458 }
459 ++cluster;
460 }
461 fMinIntrinsicWidth = std::max(fMinIntrinsicWidth, lastWordLength);
462 fMaxIntrinsicWidth = std::max(fMaxIntrinsicWidth, softLineMaxIntrinsicWidth);
463
464 if (parent->lines().empty()) {
465 // In case we could not place even a single cluster on the line
466 if (disableFirstAscent) {
467 fEndLine.metrics().fAscent = fEndLine.metrics().fRawAscent;
468 }
469 if (disableLastDescent && !fHardLineBreak) {
470 fEndLine.metrics().fDescent = fEndLine.metrics().fRawDescent;
471 }
472 fHeight = std::max(fHeight, fEndLine.metrics().height());
473 }
474 }
475
476 if (fHardLineBreak) {
477 if (disableLastDescent) {
478 fEndLine.metrics().fDescent = fEndLine.metrics().fRawDescent;
479 }
480
481 // Last character is a line break
482 if (parent->strutEnabled()) {
483 // Make sure font metrics are not less than the strut
484 parent->strutMetrics().updateLineMetrics(fEndLine.metrics());
485 }
486
487 ClusterRange clusters(fEndLine.breakCluster() - start, fEndLine.endCluster() - start);
488 addLine(fEndLine.breakCluster()->textRange(),
489 fEndLine.breakCluster()->textRange(),
490 fEndLine.endCluster()->textRange(),
491 clusters,
492 clusters,
493 0,
494 0,
495 0,
496 SkVector::Make(0, fHeight),
497 SkVector::Make(0, fEndLine.metrics().height()),
498 fEndLine.metrics(),
499 needEllipsis);
500 fHeight += fEndLine.metrics().height();
501 parent->lines().back().setMaxRunMetrics(maxRunMetrics);
502 }
503
504 if (parent->lines().empty()) {
505 return;
506 }
507 // Correct line metric styles for the first and for the last lines if needed
508 if (disableFirstAscent) {
509 parent->lines().front().setAscentStyle(LineMetricStyle::Typographic);
510 }
511 if (disableLastDescent) {
512 parent->lines().back().setDescentStyle(LineMetricStyle::Typographic);
513 }
514}
515
516} // namespace textlayout
517} // namespace skia
SkPoint pos
#define SkASSERT(cond)
Definition: SkAssert.h:116
static bool SkIsFinite(T x, Pack... values)
#define SkScalarFloorToScalar(x)
Definition: SkScalar.h:30
#define SkScalarRoundToScalar(x)
Definition: SkScalar.h:32
const SkScalar fMaxWidth
Definition: TextWrapper.cpp:45
const SkScalar fLower
Definition: TextWrapper.cpp:45
const bool fApplyRoundingHack
Definition: TextWrapper.cpp:46
const SkScalar fUpper
Definition: TextWrapper.cpp:45
constexpr size_t size() const
Definition: SkSpan_impl.h:95
TextRange textRange() const
Definition: Run.h:325
void updateLineMetrics(InternalLineMetrics &metrics)
Definition: Run.h:454
SkSpan< TextLine > lines()
InternalLineMetrics getEmptyMetrics() const
InternalLineMetrics strutMetrics() const
SkSpan< Cluster > clusters()
SkSpan< const char > text() const
const ParagraphStyle & paragraphStyle() const
void updateMetrics(InternalLineMetrics *endlineMetrics)
Definition: Run.cpp:220
PlaceholderStyle * placeholderStyle() const
Definition: Run.cpp:312
size_t size() const
Definition: Run.h:78
std::function< void(TextRange textExcludingSpaces, TextRange text, TextRange textIncludingNewlines, ClusterRange clusters, ClusterRange clustersWithGhosts, SkScalar AddLineToParagraph, size_t startClip, size_t endClip, SkVector offset, SkVector advance, InternalLineMetrics metrics, bool addEllipsis)> AddLineToParagraph
Definition: TextWrapper.h:181
void breakTextIntoLines(ParagraphImpl *parent, SkScalar maxWidth, const AddLineToParagraph &addLine)
float SkScalar
Definition: extension.cpp:12
glong glong end
static float max(float r, float g, float b)
Definition: hsl.cpp:49
static float min(float r, float g, float b)
Definition: hsl.cpp:48
std::u16string text
Definition: DartTypes.h:13
int32_t width
static constexpr SkPoint Make(float x, float y)
Definition: SkPoint_impl.h:173
TextHeightBehavior getTextHeightBehavior() const