blob: b0cd2ee196f824eebee660eb10b5c67a47c63145 [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) {
Julia Lavrova2ea20ea2020-01-22 10:56:53 -050018
19 reset();
20 fEndLine.metrics().clean();
Julia Lavrovaa3552c52019-05-30 16:12:56 -040021 fWords.startFrom(fEndLine.startCluster(), fEndLine.startPos());
22 fClusters.startFrom(fEndLine.startCluster(), fEndLine.startPos());
23 fClip.startFrom(fEndLine.startCluster(), fEndLine.startPos());
Julia Lavrovaf3ed2732019-09-05 14:35:17 -040024
Julia Lavrovaa3552c52019-05-30 16:12:56 -040025 for (auto cluster = fEndLine.endCluster(); cluster < endOfClusters; ++cluster) {
Julia Lavrovaf3ed2732019-09-05 14:35:17 -040026 // TODO: Trying to deal with flutter rounding problem. Must be removed...
27 auto width = fWords.width() + fClusters.width() + cluster->width();
28 auto roundedWidth = littleRound(width);
29 if (cluster->isHardBreak()) {
Julia Lavrovaf3ed2732019-09-05 14:35:17 -040030 } else if (roundedWidth > maxWidth) {
Julia Lavrovaa3552c52019-05-30 16:12:56 -040031 if (cluster->isWhitespaces()) {
Julia Lavrova6e6333f2019-06-17 10:34:10 -040032 // It's the end of the word
Julia Lavrovaf3ed2732019-09-05 14:35:17 -040033 fClusters.extend(cluster);
Julia Lavrova6e6333f2019-06-17 10:34:10 -040034 fMinIntrinsicWidth = SkTMax(fMinIntrinsicWidth, getClustersTrimmedWidth());
35 fWords.extend(fClusters);
Julia Lavrovaa3552c52019-05-30 16:12:56 -040036 break;
37 }
Julia Lavrovaa3552c52019-05-30 16:12:56 -040038
39 // Walk further to see if there is a too long word, cluster or glyph
40 SkScalar nextWordLength = fClusters.width();
41 for (auto further = cluster; further != endOfClusters; ++further) {
Julia Lavrovaf3ed2732019-09-05 14:35:17 -040042 if (further->isSoftBreak() || further->isHardBreak() || further->isWhitespaces()) {
Julia Lavrovaa3552c52019-05-30 16:12:56 -040043 break;
44 }
45 nextWordLength += further->width();
46 }
47 if (nextWordLength > maxWidth) {
48 // If the word is too long we can break it right now and hope it's enough
Julia Lavrova90bfd1c2019-12-04 11:43:32 -050049 fMinIntrinsicWidth = SkTMax(fMinIntrinsicWidth, nextWordLength);
Julia Lavrovaa3552c52019-05-30 16:12:56 -040050 fTooLongWord = true;
51 }
Julia Lavrova70e93012020-01-10 14:04:45 -050052
53 if (cluster->width() > maxWidth) {
54 fClusters.extend(cluster);
55 fTooLongCluster = true;
56 fTooLongWord = true;
57 }
Julia Lavrovaa3552c52019-05-30 16:12:56 -040058 break;
59 }
60
61 fClusters.extend(cluster);
62
63 // Keep adding clusters/words
64 if (fClusters.endOfWord()) {
65 fMinIntrinsicWidth = SkTMax(fMinIntrinsicWidth, getClustersTrimmedWidth());
66 fWords.extend(fClusters);
67 }
68
69 if ((fHardLineBreak = cluster->isHardBreak())) {
70 // Stop at the hard line break
71 break;
72 }
73 }
74}
75
Julia Lavrovac48687a2020-01-08 16:53:53 -050076void TextWrapper::moveForward(bool hasEllipsis) {
77
78 // We normally break lines by words.
79 // The only way we may go to clusters is if the word is too long or
80 // it's the first word and it has an ellipsis attached to it.
81 // If nothing fits we show the clipping.
82 if (!fWords.empty()) {
83 fEndLine.extend(fWords);
84 if (!fTooLongWord || hasEllipsis) {
85 return;
Julia Lavrovaa3552c52019-05-30 16:12:56 -040086 }
Julia Lavrovac48687a2020-01-08 16:53:53 -050087 }
88 if (!fClusters.empty()) {
89 fEndLine.extend(fClusters);
90 if (!fTooLongCluster) {
91 return;
92 }
93 }
94
95 if (!fClip.empty()) {
96 // Flutter: forget the clipped cluster but keep the metrics
97 fEndLine.metrics().add(fClip.metrics());
98 }
Julia Lavrovaa3552c52019-05-30 16:12:56 -040099}
100
101// Special case for start/end cluster since they can be clipped
Julia Lavrovadb9f6692019-08-01 16:02:17 -0400102void TextWrapper::trimEndSpaces(TextAlign align) {
Julia Lavrovaa3552c52019-05-30 16:12:56 -0400103 // Remember the breaking position
104 fEndLine.saveBreak();
Julia Lavrovadb9f6692019-08-01 16:02:17 -0400105 // Skip all space cluster at the end
Julia Lavrova6e6333f2019-06-17 10:34:10 -0400106 for (auto cluster = fEndLine.endCluster();
107 cluster >= fEndLine.startCluster() && cluster->isWhitespaces();
108 --cluster) {
Julia Lavrovad3a32c52020-02-03 09:43:52 -0500109 fEndLine.trim(cluster);
Julia Lavrovaa3552c52019-05-30 16:12:56 -0400110 }
Julia Lavrova2e30fde2019-10-09 09:43:02 -0400111 fEndLine.trim();
Julia Lavrovaa3552c52019-05-30 16:12:56 -0400112}
113
114SkScalar TextWrapper::getClustersTrimmedWidth() {
115 // Move the end of the line to the left
116 SkScalar width = fClusters.width();
117 auto cluster = fClusters.endCluster();
118 for (; cluster > fClusters.startCluster() && cluster->isWhitespaces(); --cluster) {
119 width -= cluster->width();
120 }
121 if (cluster >= fClusters.startCluster()) {
Julia Lavrovaf3ed2732019-09-05 14:35:17 -0400122 if (cluster->isWhitespaces()) {
123 width -= cluster->width();
124 } else {
125 width -= (cluster->width() - cluster->trimmedWidth(cluster->endPos()));
126 }
Julia Lavrovaa3552c52019-05-30 16:12:56 -0400127 }
128 return width;
129}
130
131// Trim the beginning spaces in case of soft line break
Julia Lavrovadb9f6692019-08-01 16:02:17 -0400132std::tuple<Cluster*, size_t, SkScalar> TextWrapper::trimStartSpaces(Cluster* endOfClusters) {
133
Julia Lavrovaa3552c52019-05-30 16:12:56 -0400134 if (fHardLineBreak) {
135 // End of line is always end of cluster, but need to skip \n
Julia Lavrovadb9f6692019-08-01 16:02:17 -0400136 auto width = fEndLine.width();
137 auto cluster = fEndLine.endCluster() + 1;
138 while (cluster < fEndLine.breakCluster() && cluster->isWhitespaces()) {
139 width += cluster->width();
140 ++cluster;
141 }
Julia Lavrovaaf89d392019-08-09 15:19:26 -0400142 return std::make_tuple(fEndLine.breakCluster() + 1, 0, width);
Julia Lavrovaa3552c52019-05-30 16:12:56 -0400143 }
144
Julia Lavrova53c14472019-12-18 09:39:55 -0500145 auto width = fEndLine.withWithGhostSpaces();
146 auto cluster = fEndLine.breakCluster();
147 if (fEndLine.endCluster() != fEndLine.startCluster() ||
148 fEndLine.endPos() != fEndLine.startPos()) {
Julia Lavrovaa3552c52019-05-30 16:12:56 -0400149 ++cluster;
Julia Lavrova53c14472019-12-18 09:39:55 -0500150 while (cluster < endOfClusters && cluster->isWhitespaces()) {
151 width += cluster->width();
152 ++cluster;
153 }
154 } else {
155 // Nothing fits the line - no need to check for spaces
Julia Lavrovaa3552c52019-05-30 16:12:56 -0400156 }
Julia Lavrova53c14472019-12-18 09:39:55 -0500157
Julia Lavrovaaf89d392019-08-09 15:19:26 -0400158 return std::make_tuple(cluster, 0, width);
Julia Lavrovaa3552c52019-05-30 16:12:56 -0400159}
160
Julia Lavrovac48687a2020-01-08 16:53:53 -0500161// TODO: refactor the code for line ending (with/without ellipsis)
Julia Lavrovaa3552c52019-05-30 16:12:56 -0400162void TextWrapper::breakTextIntoLines(ParagraphImpl* parent,
163 SkScalar maxWidth,
164 const AddLineToParagraph& addLine) {
Julia Lavrovaf3ed2732019-09-05 14:35:17 -0400165 fHeight = 0;
Julia Lavrova90bfd1c2019-12-04 11:43:32 -0500166 fMinIntrinsicWidth = std::numeric_limits<SkScalar>::min();
167 fMaxIntrinsicWidth = std::numeric_limits<SkScalar>::min();
Julia Lavrovaf3ed2732019-09-05 14:35:17 -0400168
Julia Lavrovaa3552c52019-05-30 16:12:56 -0400169 auto span = parent->clusters();
Julia Lavrova526df262019-08-21 17:49:44 -0400170 if (span.size() == 0) {
Julia Lavrovac48687a2020-01-08 16:53:53 -0500171 return;
Julia Lavrova526df262019-08-21 17:49:44 -0400172 }
Julia Lavrovaa3552c52019-05-30 16:12:56 -0400173 auto maxLines = parent->paragraphStyle().getMaxLines();
Julia Lavrovadb9f6692019-08-01 16:02:17 -0400174 auto& ellipsisStr = parent->paragraphStyle().getEllipsis();
Julia Lavrovaf3ed2732019-09-05 14:35:17 -0400175 auto align = parent->paragraphStyle().effective_align();
Julia Lavrova2ea20ea2020-01-22 10:56:53 -0500176 auto unlimitedLines = maxLines == std::numeric_limits<size_t>::max();
177 auto endlessLine = !SkScalarIsFinite(maxWidth);
178 auto hasEllipsis = !ellipsisStr.isEmpty();
Julia Lavrovaa3552c52019-05-30 16:12:56 -0400179
Julia Lavrovaf3ed2732019-09-05 14:35:17 -0400180 SkScalar softLineMaxIntrinsicWidth = 0;
Julia Lavrovadb9f6692019-08-01 16:02:17 -0400181 fEndLine = TextStretch(span.begin(), span.begin(), parent->strutForceHeight());
182 auto end = span.end() - 1;
183 auto start = span.begin();
Julia Lavrovaf3ed2732019-09-05 14:35:17 -0400184 InternalLineMetrics maxRunMetrics;
Julia Lavrova2ea20ea2020-01-22 10:56:53 -0500185 bool needEllipsis = false;
Julia Lavrovaa3552c52019-05-30 16:12:56 -0400186 while (fEndLine.endCluster() != end) {
Julia Lavrovaf3ed2732019-09-05 14:35:17 -0400187
Julia Lavrovaa3552c52019-05-30 16:12:56 -0400188 lookAhead(maxWidth, end);
Julia Lavrova2ea20ea2020-01-22 10:56:53 -0500189
190 auto lastLine = (hasEllipsis && unlimitedLines) || fLineNumber >= maxLines;
191 needEllipsis = hasEllipsis && !endlessLine && lastLine;
192
193 moveForward(needEllipsis);
194 needEllipsis &= fEndLine.endCluster() < end - 1; // Only if we have some text to ellipsize
Julia Lavrovaa3552c52019-05-30 16:12:56 -0400195
196 // Do not trim end spaces on the naturally last line of the left aligned text
Julia Lavrovadb9f6692019-08-01 16:02:17 -0400197 trimEndSpaces(align);
198
199 // For soft line breaks add to the line all the spaces next to it
200 Cluster* startLine;
201 size_t pos;
202 SkScalar widthWithSpaces;
203 std::tie(startLine, pos, widthWithSpaces) = trimStartSpaces(end);
Julia Lavrovaa3552c52019-05-30 16:12:56 -0400204
Julia Lavrova2ea20ea2020-01-22 10:56:53 -0500205 if (needEllipsis && !fHardLineBreak) {
Julia Lavrovac48687a2020-01-08 16:53:53 -0500206 // This is what we need to do to preserve a space before the ellipsis
207 fEndLine.restoreBreak();
208 widthWithSpaces = fEndLine.withWithGhostSpaces();
209 }
Julia Lavrova916a9042019-08-08 16:51:27 -0400210
Julia Lavrovaf3ed2732019-09-05 14:35:17 -0400211 // If the line is empty with the hard line break, let's take the paragraph font (flutter???)
212 if (fHardLineBreak && fEndLine.width() == 0) {
Jason Simmons22bb52e2019-12-05 17:56:59 -0800213 fEndLine.setMetrics(parent->getEmptyMetrics());
Julia Lavrovaf3ed2732019-09-05 14:35:17 -0400214 }
215
Julia Lavrova916a9042019-08-08 16:51:27 -0400216 // Deal with placeholder clusters == runs[@size==1]
Julia Lavrova2ea20ea2020-01-22 10:56:53 -0500217 Run* lastRun = nullptr;
Julia Lavrova916a9042019-08-08 16:51:27 -0400218 for (auto cluster = fEndLine.startCluster(); cluster <= fEndLine.endCluster(); ++cluster) {
Julia Lavrova2ea20ea2020-01-22 10:56:53 -0500219 if (cluster->run() == lastRun) {
220 continue;
221 }
222 lastRun = cluster->run();
223 if (lastRun->placeholder() != nullptr) {
224 SkASSERT(lastRun->size() == 1);
Julia Lavrova916a9042019-08-08 16:51:27 -0400225 // Update the placeholder metrics so we can get the placeholder positions later
226 // and the line metrics (to make sure the placeholder fits)
Julia Lavrova2ea20ea2020-01-22 10:56:53 -0500227 lastRun->updateMetrics(&fEndLine.metrics());
Julia Lavrova916a9042019-08-08 16:51:27 -0400228 }
229 }
230
231 // Before we update the line metrics with struts,
232 // let's save it for GetRectsForRange(RectHeightStyle::kMax)
233 maxRunMetrics = fEndLine.metrics();
Julia Lavrovaf3ed2732019-09-05 14:35:17 -0400234 maxRunMetrics.fForceStrut = false;
Julia Lavrova916a9042019-08-08 16:51:27 -0400235
Julia Lavrovaa3552c52019-05-30 16:12:56 -0400236 if (parent->strutEnabled()) {
237 // Make sure font metrics are not less than the strut
Julia Lavrovadb9f6692019-08-01 16:02:17 -0400238 parent->strutMetrics().updateLineMetrics(fEndLine.metrics());
Julia Lavrovaa3552c52019-05-30 16:12:56 -0400239 }
Julia Lavrova916a9042019-08-08 16:51:27 -0400240
Julia Lavrovaa3552c52019-05-30 16:12:56 -0400241 // TODO: keep start/end/break info for text and runs but in a better way that below
Julia Lavrova526df262019-08-21 17:49:44 -0400242 TextRange text(fEndLine.startCluster()->textRange().start, fEndLine.endCluster()->textRange().end);
Julia Lavrovadb9f6692019-08-01 16:02:17 -0400243 TextRange textWithSpaces(fEndLine.startCluster()->textRange().start, startLine->textRange().start);
Julia Lavrova2e30fde2019-10-09 09:43:02 -0400244 if (startLine == end) {
Julia Lavrovaf3ed2732019-09-05 14:35:17 -0400245 textWithSpaces.end = parent->text().size();
Julia Lavrovadb9f6692019-08-01 16:02:17 -0400246 }
Julia Lavrovac2228562019-08-08 16:51:27 -0400247 ClusterRange clusters(fEndLine.startCluster() - start, fEndLine.endCluster() - start + 1);
Julia Lavrovadb9f6692019-08-01 16:02:17 -0400248 ClusterRange clustersWithGhosts(fEndLine.startCluster() - start, startLine - start);
249 addLine(text, textWithSpaces, clusters, clustersWithGhosts, widthWithSpaces,
Julia Lavrovaa3552c52019-05-30 16:12:56 -0400250 fEndLine.startPos(),
251 fEndLine.endPos(),
252 SkVector::Make(0, fHeight),
253 SkVector::Make(fEndLine.width(), fEndLine.metrics().height()),
254 fEndLine.metrics(),
Julia Lavrova2ea20ea2020-01-22 10:56:53 -0500255 needEllipsis && !fHardLineBreak);
Julia Lavrovaa3552c52019-05-30 16:12:56 -0400256
Julia Lavrovaf3ed2732019-09-05 14:35:17 -0400257 softLineMaxIntrinsicWidth += widthWithSpaces;
258 fMaxIntrinsicWidth = SkMaxScalar(fMaxIntrinsicWidth, softLineMaxIntrinsicWidth);
259 if (fHardLineBreak) {
260 softLineMaxIntrinsicWidth = 0;
261 }
Julia Lavrovaa3552c52019-05-30 16:12:56 -0400262 // Start a new line
263 fHeight += fEndLine.metrics().height();
Julia Lavrova916a9042019-08-08 16:51:27 -0400264 if (!fHardLineBreak || startLine != end) {
Julia Lavrovadb9f6692019-08-01 16:02:17 -0400265 fEndLine.clean();
266 }
267 fEndLine.startFrom(startLine, pos);
268 parent->fMaxWidthWithTrailingSpaces = SkMaxScalar(parent->fMaxWidthWithTrailingSpaces, widthWithSpaces);
Julia Lavrovaa3552c52019-05-30 16:12:56 -0400269
Julia Lavrova2ea20ea2020-01-22 10:56:53 -0500270 if (hasEllipsis && unlimitedLines) {
271 // There is one case when we need an ellipsis on a separate line
272 // after a line break when width is infinite
273 if (!fHardLineBreak) {
274 break;
275 }
276 } else if (lastLine) {
277 // There is nothing more to draw
Julia Lavrovaf3ed2732019-09-05 14:35:17 -0400278 fHardLineBreak = false;
Julia Lavrovaa3552c52019-05-30 16:12:56 -0400279 break;
280 }
Julia Lavrovaf3ed2732019-09-05 14:35:17 -0400281
Julia Lavrovaa3552c52019-05-30 16:12:56 -0400282 ++fLineNumber;
283 }
284
Julia Lavrovaf3ed2732019-09-05 14:35:17 -0400285 // We finished formatting the text but we need to scan the rest for some numbers
Julia Lavrova53c14472019-12-18 09:39:55 -0500286 if (fEndLine.endCluster() != nullptr) {
Julia Lavrova90bfd1c2019-12-04 11:43:32 -0500287 auto lastWordLength = 0.0f;
Julia Lavrova53c14472019-12-18 09:39:55 -0500288 auto cluster = fEndLine.endCluster();
Julia Lavrova90bfd1c2019-12-04 11:43:32 -0500289 while (cluster != end || cluster->endPos() < end->endPos()) {
290 fExceededMaxLines = true;
291 if (cluster->isHardBreak()) {
292 fMaxIntrinsicWidth = SkTMax(fMaxIntrinsicWidth, softLineMaxIntrinsicWidth);
293 softLineMaxIntrinsicWidth = 0;
294
295 fMinIntrinsicWidth = SkTMax(fMinIntrinsicWidth, lastWordLength);
296 lastWordLength = 0;
297 } else if (cluster->isWhitespaces()) {
298 SkASSERT(cluster->isWhitespaces());
299 softLineMaxIntrinsicWidth += cluster->width();
300 fMinIntrinsicWidth = SkTMax(fMinIntrinsicWidth, lastWordLength);
301 lastWordLength = 0;
302 } else {
303 softLineMaxIntrinsicWidth += cluster->width();
304 lastWordLength += cluster->width();
305 }
306 ++cluster;
307 }
308 fMinIntrinsicWidth = SkTMax(fMinIntrinsicWidth, lastWordLength);
309 fMaxIntrinsicWidth = SkTMax(fMaxIntrinsicWidth, softLineMaxIntrinsicWidth);
310 // In case we could not place a single cluster on the line
311 fHeight = SkTMax(fHeight, fEndLine.metrics().height());
Julia Lavrovaf3ed2732019-09-05 14:35:17 -0400312 }
313
Julia Lavrovaa3552c52019-05-30 16:12:56 -0400314 if (fHardLineBreak) {
315 // Last character is a line break
316 if (parent->strutEnabled()) {
317 // Make sure font metrics are not less than the strut
Julia Lavrovadb9f6692019-08-01 16:02:17 -0400318 parent->strutMetrics().updateLineMetrics(fEndLine.metrics());
Julia Lavrovaa3552c52019-05-30 16:12:56 -0400319 }
Julia Lavrova5207f352019-06-21 12:22:32 -0400320 TextRange empty(fEndLine.breakCluster()->textRange().start, fEndLine.breakCluster()->textRange().start);
Julia Lavrovadb9f6692019-08-01 16:02:17 -0400321 TextRange hardBreak(fEndLine.breakCluster()->textRange().end, fEndLine.breakCluster()->textRange().end);
Julia Lavrova526df262019-08-21 17:49:44 -0400322 ClusterRange clusters(fEndLine.breakCluster() - start, fEndLine.endCluster() - start);
Julia Lavrovadb9f6692019-08-01 16:02:17 -0400323 addLine(empty, hardBreak, clusters, clusters,
324 0,
Julia Lavrovaa3552c52019-05-30 16:12:56 -0400325 0,
326 0,
327 SkVector::Make(0, fHeight),
328 SkVector::Make(0, fEndLine.metrics().height()),
329 fEndLine.metrics(),
Julia Lavrovaf3ed2732019-09-05 14:35:17 -0400330 needEllipsis);
331 fHeight += fEndLine.metrics().height();
Julia Lavrova916a9042019-08-08 16:51:27 -0400332 parent->lines().back().setMaxRunMetrics(maxRunMetrics);
Julia Lavrovaa3552c52019-05-30 16:12:56 -0400333 }
334}
335
336} // namespace textlayout
337} // namespace skia