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