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::__anon05aeccc80111::LineBreakerWithLittleRounding10 LineBreakerWithLittleRounding(SkScalar maxWidth)
11 : fLower(maxWidth - 0.25f)
12 , fMaxWidth(maxWidth)
13 , fUpper(maxWidth + 0.25f) {}
14
breakLineskia::textlayout::__anon05aeccc80111::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 break;
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 return std::make_tuple(cluster, 0, width);
251 }
252
253 // TODO: refactor the code for line ending (with/without ellipsis)
breakTextIntoLines(ParagraphImpl * parent,SkScalar maxWidth,const AddLineToParagraph & addLine)254 void TextWrapper::breakTextIntoLines(ParagraphImpl* parent,
255 SkScalar maxWidth,
256 const AddLineToParagraph& addLine) {
257 fHeight = 0;
258 fMinIntrinsicWidth = std::numeric_limits<SkScalar>::min();
259 fMaxIntrinsicWidth = std::numeric_limits<SkScalar>::min();
260
261 auto span = parent->clusters();
262 if (span.size() == 0) {
263 return;
264 }
265 auto maxLines = parent->paragraphStyle().getMaxLines();
266 auto align = parent->paragraphStyle().effective_align();
267 auto unlimitedLines = maxLines == std::numeric_limits<size_t>::max();
268 auto endlessLine = !SkScalarIsFinite(maxWidth);
269 auto hasEllipsis = parent->paragraphStyle().ellipsized();
270
271 auto disableFirstAscent = parent->paragraphStyle().getTextHeightBehavior() & TextHeightBehavior::kDisableFirstAscent;
272 auto disableLastDescent = parent->paragraphStyle().getTextHeightBehavior() & TextHeightBehavior::kDisableLastDescent;
273 bool firstLine = true; // We only interested in fist line if we have to disable the first ascent
274
275 SkScalar softLineMaxIntrinsicWidth = 0;
276 fEndLine = TextStretch(span.begin(), span.begin(), parent->strutForceHeight());
277 auto end = span.end() - 1;
278 auto start = span.begin();
279 InternalLineMetrics maxRunMetrics;
280 bool needEllipsis = false;
281 while (fEndLine.endCluster() != end) {
282
283 lookAhead(maxWidth, end);
284
285 auto lastLine = (hasEllipsis && unlimitedLines) || fLineNumber >= maxLines;
286 needEllipsis = hasEllipsis && !endlessLine && lastLine;
287
288 moveForward(needEllipsis);
289 needEllipsis &= fEndLine.endCluster() < end - 1; // Only if we have some text to ellipsize
290
291 // Do not trim end spaces on the naturally last line of the left aligned text
292 trimEndSpaces(align);
293
294 // For soft line breaks add to the line all the spaces next to it
295 Cluster* startLine;
296 size_t pos;
297 SkScalar widthWithSpaces;
298 std::tie(startLine, pos, widthWithSpaces) = trimStartSpaces(end);
299
300 if (needEllipsis && !fHardLineBreak) {
301 // This is what we need to do to preserve a space before the ellipsis
302 fEndLine.restoreBreak();
303 widthWithSpaces = fEndLine.widthWithGhostSpaces();
304 }
305
306 // If the line is empty with the hard line break, let's take the paragraph font (flutter???)
307 if (fHardLineBreak && fEndLine.width() == 0) {
308 fEndLine.setMetrics(parent->getEmptyMetrics());
309 }
310
311 // Deal with placeholder clusters == runs[@size==1]
312 Run* lastRun = nullptr;
313 for (auto cluster = fEndLine.startCluster(); cluster <= fEndLine.endCluster(); ++cluster) {
314 auto r = cluster->runOrNull();
315 if (r == lastRun) {
316 continue;
317 }
318 lastRun = r;
319 if (lastRun->placeholderStyle() != nullptr) {
320 SkASSERT(lastRun->size() == 1);
321 // Update the placeholder metrics so we can get the placeholder positions later
322 // and the line metrics (to make sure the placeholder fits)
323 lastRun->updateMetrics(&fEndLine.metrics());
324 }
325 }
326
327 // Before we update the line metrics with struts,
328 // let's save it for GetRectsForRange(RectHeightStyle::kMax)
329 maxRunMetrics = fEndLine.metrics();
330 maxRunMetrics.fForceStrut = false;
331
332 if (parent->strutEnabled()) {
333 // Make sure font metrics are not less than the strut
334 parent->strutMetrics().updateLineMetrics(fEndLine.metrics());
335 }
336
337 // TODO: keep start/end/break info for text and runs but in a better way that below
338 TextRange text(fEndLine.startCluster()->textRange().start, fEndLine.endCluster()->textRange().end);
339 TextRange textWithSpaces(fEndLine.startCluster()->textRange().start, startLine->textRange().start);
340 if (startLine == end) {
341 textWithSpaces.end = parent->text().size();
342 }
343 ClusterRange clusters(fEndLine.startCluster() - start, fEndLine.endCluster() - start + 1);
344 ClusterRange clustersWithGhosts(fEndLine.startCluster() - start, startLine - start);
345
346 if (disableFirstAscent && firstLine) {
347 fEndLine.metrics().fAscent = fEndLine.metrics().fRawAscent;
348 }
349 if (disableLastDescent && (lastLine || (startLine == end && !fHardLineBreak ))) {
350 fEndLine.metrics().fDescent = fEndLine.metrics().fRawDescent;
351 }
352
353 SkScalar lineHeight = fEndLine.metrics().height();
354 firstLine = false;
355
356 if (fEndLine.empty()) {
357 // Correct text and clusters (make it empty for an empty line)
358 text.end = text.start;
359 clusters.end = clusters.start;
360 }
361
362 addLine(text, textWithSpaces, clusters, clustersWithGhosts, widthWithSpaces,
363 fEndLine.startPos(),
364 fEndLine.endPos(),
365 SkVector::Make(0, fHeight),
366 SkVector::Make(fEndLine.width(), lineHeight),
367 fEndLine.metrics(),
368 needEllipsis && !fHardLineBreak);
369
370 softLineMaxIntrinsicWidth += widthWithSpaces;
371
372 fMaxIntrinsicWidth = std::max(fMaxIntrinsicWidth, softLineMaxIntrinsicWidth);
373 if (fHardLineBreak) {
374 softLineMaxIntrinsicWidth = 0;
375 }
376 // Start a new line
377 fHeight += lineHeight;
378 if (!fHardLineBreak || startLine != end) {
379 fEndLine.clean();
380 }
381 fEndLine.startFrom(startLine, pos);
382 parent->fMaxWidthWithTrailingSpaces = std::max(parent->fMaxWidthWithTrailingSpaces, widthWithSpaces);
383
384 if (hasEllipsis && unlimitedLines) {
385 // There is one case when we need an ellipsis on a separate line
386 // after a line break when width is infinite
387 if (!fHardLineBreak) {
388 break;
389 }
390 } else if (lastLine) {
391 // There is nothing more to draw
392 fHardLineBreak = false;
393 break;
394 }
395
396 ++fLineNumber;
397 }
398
399 // We finished formatting the text but we need to scan the rest for some numbers
400 // TODO: make it a case of a normal flow
401 if (fEndLine.endCluster() != nullptr) {
402 auto lastWordLength = 0.0f;
403 auto cluster = fEndLine.endCluster();
404 while (cluster != end || cluster->endPos() < end->endPos()) {
405 fExceededMaxLines = true;
406 if (cluster->isHardBreak()) {
407 // Hard line break ends the word and the line
408 fMaxIntrinsicWidth = std::max(fMaxIntrinsicWidth, softLineMaxIntrinsicWidth);
409 softLineMaxIntrinsicWidth = 0;
410 fMinIntrinsicWidth = std::max(fMinIntrinsicWidth, lastWordLength);
411 lastWordLength = 0;
412 } else if (cluster->isWhitespaceBreak()) {
413 // Whitespaces end the word
414 softLineMaxIntrinsicWidth += cluster->width();
415 fMinIntrinsicWidth = std::max(fMinIntrinsicWidth, lastWordLength);
416 lastWordLength = 0;
417 } else if (cluster->run().isPlaceholder()) {
418 // Placeholder ends the previous word and creates a separate one
419 fMinIntrinsicWidth = std::max(fMinIntrinsicWidth, lastWordLength);
420 // Placeholder width now counts in fMinIntrinsicWidth
421 softLineMaxIntrinsicWidth += cluster->width();
422 fMinIntrinsicWidth = std::max(fMinIntrinsicWidth, cluster->width());
423 lastWordLength = 0;
424 } else {
425 // Nothing out of ordinary - just add this cluster to the word and to the line
426 softLineMaxIntrinsicWidth += cluster->width();
427 lastWordLength += cluster->width();
428 }
429 ++cluster;
430 }
431 fMinIntrinsicWidth = std::max(fMinIntrinsicWidth, lastWordLength);
432 fMaxIntrinsicWidth = std::max(fMaxIntrinsicWidth, softLineMaxIntrinsicWidth);
433
434 if (parent->lines().empty()) {
435 // In case we could not place even a single cluster on the line
436 if (disableFirstAscent) {
437 fEndLine.metrics().fAscent = fEndLine.metrics().fRawAscent;
438 }
439 if (disableLastDescent && !fHardLineBreak) {
440 fEndLine.metrics().fDescent = fEndLine.metrics().fRawDescent;
441 }
442 fHeight = std::max(fHeight, fEndLine.metrics().height());
443 }
444 }
445
446 if (fHardLineBreak) {
447 // Last character is a line break
448 if (parent->strutEnabled()) {
449 // Make sure font metrics are not less than the strut
450 parent->strutMetrics().updateLineMetrics(fEndLine.metrics());
451 }
452
453 if (disableLastDescent) {
454 fEndLine.metrics().fDescent = fEndLine.metrics().fRawDescent;
455 }
456
457 ClusterRange clusters(fEndLine.breakCluster() - start, fEndLine.endCluster() - start);
458 addLine(fEndLine.breakCluster()->textRange(),
459 fEndLine.endCluster()->textRange(),
460 clusters,
461 clusters,
462 0,
463 0,
464 0,
465 SkVector::Make(0, fHeight),
466 SkVector::Make(0, fEndLine.metrics().height()),
467 fEndLine.metrics(),
468 needEllipsis);
469 fHeight += fEndLine.metrics().height();
470 parent->lines().back().setMaxRunMetrics(maxRunMetrics);
471 }
472 }
473
474 } // namespace textlayout
475 } // namespace skia
476