blob: c4faca0b79c62a8fdcbce4f941027a7a59c6ffac [file] [log] [blame]
Julia Lavrovaa3552c52019-05-30 16:12:56 -04001// Copyright 2019 Google LLC.
Julia Lavrovaa3552c52019-05-30 16:12:56 -04002#include "modules/skparagraph/src/ParagraphImpl.h"
Greg Danielf91aeb22019-06-18 09:58:02 -04003#include "modules/skparagraph/src/TextWrapper.h"
Julia Lavrovaa3552c52019-05-30 16:12:56 -04004
5namespace skia {
6namespace textlayout {
7
Julia Lavrovaf3ed2732019-09-05 14:35:17 -04008namespace {
9SkScalar littleRound(SkScalar a) {
10 // This rounding is done to match Flutter tests. Must be removed..
11 return SkScalarRoundToScalar(a * 100.0)/100.0;
12}
13}
14
Julia Lavrovaa3552c52019-05-30 16:12:56 -040015// Since we allow cluster clipping when they don't fit
16// we have to work with stretches - parts of clusters
17void TextWrapper::lookAhead(SkScalar maxWidth, Cluster* endOfClusters) {
18 fWords.startFrom(fEndLine.startCluster(), fEndLine.startPos());
19 fClusters.startFrom(fEndLine.startCluster(), fEndLine.startPos());
20 fClip.startFrom(fEndLine.startCluster(), fEndLine.startPos());
Julia Lavrovaf3ed2732019-09-05 14:35:17 -040021
Julia Lavrovaa3552c52019-05-30 16:12:56 -040022 for (auto cluster = fEndLine.endCluster(); cluster < endOfClusters; ++cluster) {
Julia Lavrovaf3ed2732019-09-05 14:35:17 -040023 // TODO: Trying to deal with flutter rounding problem. Must be removed...
24 auto width = fWords.width() + fClusters.width() + cluster->width();
25 auto roundedWidth = littleRound(width);
26 if (cluster->isHardBreak()) {
Julia Lavrovaf3ed2732019-09-05 14:35:17 -040027 } else if (roundedWidth > maxWidth) {
Julia Lavrovaa3552c52019-05-30 16:12:56 -040028 if (cluster->isWhitespaces()) {
Julia Lavrova6e6333f2019-06-17 10:34:10 -040029 // It's the end of the word
Julia Lavrovaf3ed2732019-09-05 14:35:17 -040030 fClusters.extend(cluster);
Julia Lavrova6e6333f2019-06-17 10:34:10 -040031 fMinIntrinsicWidth = SkTMax(fMinIntrinsicWidth, getClustersTrimmedWidth());
32 fWords.extend(fClusters);
Julia Lavrovaa3552c52019-05-30 16:12:56 -040033 break;
34 }
Julia Lavrovaa3552c52019-05-30 16:12:56 -040035
36 // Walk further to see if there is a too long word, cluster or glyph
37 SkScalar nextWordLength = fClusters.width();
38 for (auto further = cluster; further != endOfClusters; ++further) {
Julia Lavrovaf3ed2732019-09-05 14:35:17 -040039 if (further->isSoftBreak() || further->isHardBreak() || further->isWhitespaces()) {
Julia Lavrovaa3552c52019-05-30 16:12:56 -040040 break;
41 }
42 nextWordLength += further->width();
43 }
44 if (nextWordLength > maxWidth) {
45 // If the word is too long we can break it right now and hope it's enough
Julia Lavrova90bfd1c2019-12-04 11:43:32 -050046 fMinIntrinsicWidth = SkTMax(fMinIntrinsicWidth, nextWordLength);
Julia Lavrovaa3552c52019-05-30 16:12:56 -040047 fTooLongWord = true;
48 }
Julia Lavrova70e93012020-01-10 14:04:45 -050049
50 if (cluster->width() > maxWidth) {
51 fClusters.extend(cluster);
52 fTooLongCluster = true;
53 fTooLongWord = true;
54 }
Julia Lavrovaa3552c52019-05-30 16:12:56 -040055 break;
56 }
57
58 fClusters.extend(cluster);
59
60 // Keep adding clusters/words
61 if (fClusters.endOfWord()) {
62 fMinIntrinsicWidth = SkTMax(fMinIntrinsicWidth, getClustersTrimmedWidth());
63 fWords.extend(fClusters);
64 }
65
66 if ((fHardLineBreak = cluster->isHardBreak())) {
67 // Stop at the hard line break
68 break;
69 }
70 }
71}
72
Julia Lavrovac48687a2020-01-08 16:53:53 -050073void TextWrapper::moveForward(bool hasEllipsis) {
74
75 // We normally break lines by words.
76 // The only way we may go to clusters is if the word is too long or
77 // it's the first word and it has an ellipsis attached to it.
78 // If nothing fits we show the clipping.
79 if (!fWords.empty()) {
80 fEndLine.extend(fWords);
81 if (!fTooLongWord || hasEllipsis) {
82 return;
Julia Lavrovaa3552c52019-05-30 16:12:56 -040083 }
Julia Lavrovac48687a2020-01-08 16:53:53 -050084 }
85 if (!fClusters.empty()) {
86 fEndLine.extend(fClusters);
87 if (!fTooLongCluster) {
88 return;
89 }
90 }
91
92 if (!fClip.empty()) {
93 // Flutter: forget the clipped cluster but keep the metrics
94 fEndLine.metrics().add(fClip.metrics());
95 }
Julia Lavrovaa3552c52019-05-30 16:12:56 -040096}
97
98// Special case for start/end cluster since they can be clipped
Julia Lavrovadb9f6692019-08-01 16:02:17 -040099void TextWrapper::trimEndSpaces(TextAlign align) {
Julia Lavrovaa3552c52019-05-30 16:12:56 -0400100 // Remember the breaking position
101 fEndLine.saveBreak();
Julia Lavrovadb9f6692019-08-01 16:02:17 -0400102 // Skip all space cluster at the end
103 //bool left = align == TextAlign::kStart || align == TextAlign::kLeft;
104 bool right = align == TextAlign::kRight || align == TextAlign::kEnd;
Julia Lavrova6e6333f2019-06-17 10:34:10 -0400105 for (auto cluster = fEndLine.endCluster();
106 cluster >= fEndLine.startCluster() && cluster->isWhitespaces();
107 --cluster) {
Julia Lavrova526df262019-08-21 17:49:44 -0400108 if ((cluster->run()->leftToRight()) ||
Julia Lavrovadb9f6692019-08-01 16:02:17 -0400109 (right && !cluster->run()->leftToRight()) ||
110 align == TextAlign::kJustify || align == TextAlign::kCenter) {
111 fEndLine.trim(cluster);
112 continue;
113 } else {
114 break;
115 }
Julia Lavrovaa3552c52019-05-30 16:12:56 -0400116 }
Julia Lavrova2e30fde2019-10-09 09:43:02 -0400117 fEndLine.trim();
Julia Lavrovaa3552c52019-05-30 16:12:56 -0400118}
119
120SkScalar TextWrapper::getClustersTrimmedWidth() {
121 // Move the end of the line to the left
122 SkScalar width = fClusters.width();
123 auto cluster = fClusters.endCluster();
124 for (; cluster > fClusters.startCluster() && cluster->isWhitespaces(); --cluster) {
125 width -= cluster->width();
126 }
127 if (cluster >= fClusters.startCluster()) {
Julia Lavrovaf3ed2732019-09-05 14:35:17 -0400128 if (cluster->isWhitespaces()) {
129 width -= cluster->width();
130 } else {
131 width -= (cluster->width() - cluster->trimmedWidth(cluster->endPos()));
132 }
Julia Lavrovaa3552c52019-05-30 16:12:56 -0400133 }
134 return width;
135}
136
137// Trim the beginning spaces in case of soft line break
Julia Lavrovadb9f6692019-08-01 16:02:17 -0400138std::tuple<Cluster*, size_t, SkScalar> TextWrapper::trimStartSpaces(Cluster* endOfClusters) {
139
Julia Lavrovaa3552c52019-05-30 16:12:56 -0400140 if (fHardLineBreak) {
141 // End of line is always end of cluster, but need to skip \n
Julia Lavrovadb9f6692019-08-01 16:02:17 -0400142 auto width = fEndLine.width();
143 auto cluster = fEndLine.endCluster() + 1;
144 while (cluster < fEndLine.breakCluster() && cluster->isWhitespaces()) {
145 width += cluster->width();
146 ++cluster;
147 }
Julia Lavrovaaf89d392019-08-09 15:19:26 -0400148 return std::make_tuple(fEndLine.breakCluster() + 1, 0, width);
Julia Lavrovaa3552c52019-05-30 16:12:56 -0400149 }
150
Julia Lavrova53c14472019-12-18 09:39:55 -0500151 auto width = fEndLine.withWithGhostSpaces();
152 auto cluster = fEndLine.breakCluster();
153 if (fEndLine.endCluster() != fEndLine.startCluster() ||
154 fEndLine.endPos() != fEndLine.startPos()) {
Julia Lavrovaa3552c52019-05-30 16:12:56 -0400155 ++cluster;
Julia Lavrova53c14472019-12-18 09:39:55 -0500156 while (cluster < endOfClusters && cluster->isWhitespaces()) {
157 width += cluster->width();
158 ++cluster;
159 }
160 } else {
161 // Nothing fits the line - no need to check for spaces
Julia Lavrovaa3552c52019-05-30 16:12:56 -0400162 }
Julia Lavrova53c14472019-12-18 09:39:55 -0500163
Julia Lavrovaaf89d392019-08-09 15:19:26 -0400164 return std::make_tuple(cluster, 0, width);
Julia Lavrovaa3552c52019-05-30 16:12:56 -0400165}
166
Julia Lavrovac48687a2020-01-08 16:53:53 -0500167// TODO: refactor the code for line ending (with/without ellipsis)
Julia Lavrovaa3552c52019-05-30 16:12:56 -0400168void TextWrapper::breakTextIntoLines(ParagraphImpl* parent,
169 SkScalar maxWidth,
170 const AddLineToParagraph& addLine) {
Julia Lavrovaf3ed2732019-09-05 14:35:17 -0400171 fHeight = 0;
Julia Lavrova90bfd1c2019-12-04 11:43:32 -0500172 fMinIntrinsicWidth = std::numeric_limits<SkScalar>::min();
173 fMaxIntrinsicWidth = std::numeric_limits<SkScalar>::min();
Julia Lavrovaf3ed2732019-09-05 14:35:17 -0400174
Julia Lavrovaa3552c52019-05-30 16:12:56 -0400175 auto span = parent->clusters();
Julia Lavrova526df262019-08-21 17:49:44 -0400176 if (span.size() == 0) {
Julia Lavrovac48687a2020-01-08 16:53:53 -0500177 return;
Julia Lavrova526df262019-08-21 17:49:44 -0400178 }
Julia Lavrovaa3552c52019-05-30 16:12:56 -0400179 auto maxLines = parent->paragraphStyle().getMaxLines();
Julia Lavrovadb9f6692019-08-01 16:02:17 -0400180 auto& ellipsisStr = parent->paragraphStyle().getEllipsis();
Julia Lavrovaf3ed2732019-09-05 14:35:17 -0400181 auto align = parent->paragraphStyle().effective_align();
Julia Lavrovaa3552c52019-05-30 16:12:56 -0400182
Julia Lavrovaf3ed2732019-09-05 14:35:17 -0400183 SkScalar softLineMaxIntrinsicWidth = 0;
Julia Lavrovadb9f6692019-08-01 16:02:17 -0400184 fEndLine = TextStretch(span.begin(), span.begin(), parent->strutForceHeight());
185 auto end = span.end() - 1;
186 auto start = span.begin();
Julia Lavrovaf3ed2732019-09-05 14:35:17 -0400187 InternalLineMetrics maxRunMetrics;
188 auto needEllipsis = false;
189 auto endlessLine = maxLines == std::numeric_limits<size_t>::max();
Julia Lavrovaa3552c52019-05-30 16:12:56 -0400190 while (fEndLine.endCluster() != end) {
Julia Lavrovaf3ed2732019-09-05 14:35:17 -0400191
Julia Lavrovaa3552c52019-05-30 16:12:56 -0400192 reset();
Julia Lavrovac48687a2020-01-08 16:53:53 -0500193 auto exceededLines = !endlessLine && fLineNumber >= maxLines;
Julia Lavrovaf3ed2732019-09-05 14:35:17 -0400194 fEndLine.metrics().clean();
Julia Lavrovaa3552c52019-05-30 16:12:56 -0400195 lookAhead(maxWidth, end);
Julia Lavrovac48687a2020-01-08 16:53:53 -0500196 moveForward(exceededLines && !ellipsisStr.isEmpty());
Julia Lavrovaa3552c52019-05-30 16:12:56 -0400197
198 // Do not trim end spaces on the naturally last line of the left aligned text
Julia Lavrovadb9f6692019-08-01 16:02:17 -0400199 trimEndSpaces(align);
200
201 // For soft line breaks add to the line all the spaces next to it
202 Cluster* startLine;
203 size_t pos;
204 SkScalar widthWithSpaces;
205 std::tie(startLine, pos, widthWithSpaces) = trimStartSpaces(end);
Julia Lavrovaa3552c52019-05-30 16:12:56 -0400206
Julia Lavrovaf3ed2732019-09-05 14:35:17 -0400207 needEllipsis =
208 fEndLine.endCluster() < end - 1 &&
209 SkScalarIsFinite(maxWidth) &&
210 !ellipsisStr.isEmpty();
211
Julia Lavrovac48687a2020-01-08 16:53:53 -0500212 if (needEllipsis && exceededLines && !fHardLineBreak) {
213 // This is what we need to do to preserve a space before the ellipsis
214 fEndLine.restoreBreak();
215 widthWithSpaces = fEndLine.withWithGhostSpaces();
216 }
Julia Lavrova916a9042019-08-08 16:51:27 -0400217
Julia Lavrovaf3ed2732019-09-05 14:35:17 -0400218 // If the line is empty with the hard line break, let's take the paragraph font (flutter???)
219 if (fHardLineBreak && fEndLine.width() == 0) {
Jason Simmons22bb52e2019-12-05 17:56:59 -0800220 fEndLine.setMetrics(parent->getEmptyMetrics());
Julia Lavrovaf3ed2732019-09-05 14:35:17 -0400221 }
222
Julia Lavrova916a9042019-08-08 16:51:27 -0400223 // Deal with placeholder clusters == runs[@size==1]
224 for (auto cluster = fEndLine.startCluster(); cluster <= fEndLine.endCluster(); ++cluster) {
225 if (cluster->run()->placeholder() != nullptr) {
226 SkASSERT(cluster->run()->size() == 1);
227 // Update the placeholder metrics so we can get the placeholder positions later
228 // and the line metrics (to make sure the placeholder fits)
229 cluster->run()->updateMetrics(&fEndLine.metrics());
230 }
231 }
232
233 // Before we update the line metrics with struts,
234 // let's save it for GetRectsForRange(RectHeightStyle::kMax)
235 maxRunMetrics = fEndLine.metrics();
Julia Lavrovaf3ed2732019-09-05 14:35:17 -0400236 maxRunMetrics.fForceStrut = false;
Julia Lavrova916a9042019-08-08 16:51:27 -0400237
Julia Lavrovaa3552c52019-05-30 16:12:56 -0400238 if (parent->strutEnabled()) {
239 // Make sure font metrics are not less than the strut
Julia Lavrovadb9f6692019-08-01 16:02:17 -0400240 parent->strutMetrics().updateLineMetrics(fEndLine.metrics());
Julia Lavrovaa3552c52019-05-30 16:12:56 -0400241 }
Julia Lavrova916a9042019-08-08 16:51:27 -0400242
Julia Lavrovaa3552c52019-05-30 16:12:56 -0400243 // TODO: keep start/end/break info for text and runs but in a better way that below
Julia Lavrova526df262019-08-21 17:49:44 -0400244 TextRange text(fEndLine.startCluster()->textRange().start, fEndLine.endCluster()->textRange().end);
Julia Lavrovadb9f6692019-08-01 16:02:17 -0400245 TextRange textWithSpaces(fEndLine.startCluster()->textRange().start, startLine->textRange().start);
Julia Lavrova2e30fde2019-10-09 09:43:02 -0400246 if (startLine == end) {
Julia Lavrovaf3ed2732019-09-05 14:35:17 -0400247 textWithSpaces.end = parent->text().size();
Julia Lavrovadb9f6692019-08-01 16:02:17 -0400248 }
Julia Lavrovac2228562019-08-08 16:51:27 -0400249 ClusterRange clusters(fEndLine.startCluster() - start, fEndLine.endCluster() - start + 1);
Julia Lavrovadb9f6692019-08-01 16:02:17 -0400250 ClusterRange clustersWithGhosts(fEndLine.startCluster() - start, startLine - start);
251 addLine(text, textWithSpaces, clusters, clustersWithGhosts, widthWithSpaces,
Julia Lavrovaa3552c52019-05-30 16:12:56 -0400252 fEndLine.startPos(),
253 fEndLine.endPos(),
254 SkVector::Make(0, fHeight),
255 SkVector::Make(fEndLine.width(), fEndLine.metrics().height()),
256 fEndLine.metrics(),
Julia Lavrovaf3ed2732019-09-05 14:35:17 -0400257 needEllipsis && exceededLines && !fHardLineBreak);
Julia Lavrovaa3552c52019-05-30 16:12:56 -0400258
Julia Lavrova916a9042019-08-08 16:51:27 -0400259 parent->lines().back().setMaxRunMetrics(maxRunMetrics);
260
Julia Lavrovaf3ed2732019-09-05 14:35:17 -0400261 softLineMaxIntrinsicWidth += widthWithSpaces;
262 fMaxIntrinsicWidth = SkMaxScalar(fMaxIntrinsicWidth, softLineMaxIntrinsicWidth);
263 if (fHardLineBreak) {
264 softLineMaxIntrinsicWidth = 0;
265 }
Julia Lavrovaa3552c52019-05-30 16:12:56 -0400266 // Start a new line
267 fHeight += fEndLine.metrics().height();
Julia Lavrova916a9042019-08-08 16:51:27 -0400268 if (!fHardLineBreak || startLine != end) {
Julia Lavrovadb9f6692019-08-01 16:02:17 -0400269 fEndLine.clean();
270 }
271 fEndLine.startFrom(startLine, pos);
272 parent->fMaxWidthWithTrailingSpaces = SkMaxScalar(parent->fMaxWidthWithTrailingSpaces, widthWithSpaces);
Julia Lavrovaa3552c52019-05-30 16:12:56 -0400273
Julia Lavrovac48687a2020-01-08 16:53:53 -0500274 if (exceededLines) {
Julia Lavrovaf3ed2732019-09-05 14:35:17 -0400275 fHardLineBreak = false;
Julia Lavrovaa3552c52019-05-30 16:12:56 -0400276 break;
Julia Lavrovac48687a2020-01-08 16:53:53 -0500277 } else if (endlessLine && needEllipsis) {
278 break;
Julia Lavrovaa3552c52019-05-30 16:12:56 -0400279 }
Julia Lavrovaf3ed2732019-09-05 14:35:17 -0400280
Julia Lavrovaa3552c52019-05-30 16:12:56 -0400281 ++fLineNumber;
282 }
283
Julia Lavrovaf3ed2732019-09-05 14:35:17 -0400284 // We finished formatting the text but we need to scan the rest for some numbers
Julia Lavrova53c14472019-12-18 09:39:55 -0500285 if (fEndLine.endCluster() != nullptr) {
Julia Lavrova90bfd1c2019-12-04 11:43:32 -0500286 auto lastWordLength = 0.0f;
Julia Lavrova53c14472019-12-18 09:39:55 -0500287 auto cluster = fEndLine.endCluster();
Julia Lavrova90bfd1c2019-12-04 11:43:32 -0500288 while (cluster != end || cluster->endPos() < end->endPos()) {
289 fExceededMaxLines = true;
290 if (cluster->isHardBreak()) {
291 fMaxIntrinsicWidth = SkTMax(fMaxIntrinsicWidth, softLineMaxIntrinsicWidth);
292 softLineMaxIntrinsicWidth = 0;
293
294 fMinIntrinsicWidth = SkTMax(fMinIntrinsicWidth, lastWordLength);
295 lastWordLength = 0;
296 } else if (cluster->isWhitespaces()) {
297 SkASSERT(cluster->isWhitespaces());
298 softLineMaxIntrinsicWidth += cluster->width();
299 fMinIntrinsicWidth = SkTMax(fMinIntrinsicWidth, lastWordLength);
300 lastWordLength = 0;
301 } else {
302 softLineMaxIntrinsicWidth += cluster->width();
303 lastWordLength += cluster->width();
304 }
305 ++cluster;
306 }
307 fMinIntrinsicWidth = SkTMax(fMinIntrinsicWidth, lastWordLength);
308 fMaxIntrinsicWidth = SkTMax(fMaxIntrinsicWidth, softLineMaxIntrinsicWidth);
309 // In case we could not place a single cluster on the line
310 fHeight = SkTMax(fHeight, fEndLine.metrics().height());
Julia Lavrovaf3ed2732019-09-05 14:35:17 -0400311 }
312
Julia Lavrovaa3552c52019-05-30 16:12:56 -0400313 if (fHardLineBreak) {
314 // Last character is a line break
315 if (parent->strutEnabled()) {
316 // Make sure font metrics are not less than the strut
Julia Lavrovadb9f6692019-08-01 16:02:17 -0400317 parent->strutMetrics().updateLineMetrics(fEndLine.metrics());
Julia Lavrovaa3552c52019-05-30 16:12:56 -0400318 }
Julia Lavrova5207f352019-06-21 12:22:32 -0400319 TextRange empty(fEndLine.breakCluster()->textRange().start, fEndLine.breakCluster()->textRange().start);
Julia Lavrovadb9f6692019-08-01 16:02:17 -0400320 TextRange hardBreak(fEndLine.breakCluster()->textRange().end, fEndLine.breakCluster()->textRange().end);
Julia Lavrova526df262019-08-21 17:49:44 -0400321 ClusterRange clusters(fEndLine.breakCluster() - start, fEndLine.endCluster() - start);
Julia Lavrovadb9f6692019-08-01 16:02:17 -0400322 addLine(empty, hardBreak, clusters, clusters,
323 0,
Julia Lavrovaa3552c52019-05-30 16:12:56 -0400324 0,
325 0,
326 SkVector::Make(0, fHeight),
327 SkVector::Make(0, fEndLine.metrics().height()),
328 fEndLine.metrics(),
Julia Lavrovaf3ed2732019-09-05 14:35:17 -0400329 needEllipsis);
330 fHeight += fEndLine.metrics().height();
Julia Lavrova916a9042019-08-08 16:51:27 -0400331 parent->lines().back().setMaxRunMetrics(maxRunMetrics);
Julia Lavrovaa3552c52019-05-30 16:12:56 -0400332 }
333}
334
335} // namespace textlayout
336} // namespace skia