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