• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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