• 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::__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