blob: a0fb5813bf05c89ab0d8772f1884b770c22966b6 [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()) {
27 } else if (maxWidth == 0.0f) {
28 // Do nothing
29 } else if (roundedWidth > maxWidth) {
Julia Lavrovaa3552c52019-05-30 16:12:56 -040030 if (cluster->isWhitespaces()) {
Julia Lavrova6e6333f2019-06-17 10:34:10 -040031 // It's the end of the word
Julia Lavrovaf3ed2732019-09-05 14:35:17 -040032 fClusters.extend(cluster);
Julia Lavrova6e6333f2019-06-17 10:34:10 -040033 fMinIntrinsicWidth = SkTMax(fMinIntrinsicWidth, getClustersTrimmedWidth());
34 fWords.extend(fClusters);
Julia Lavrovaa3552c52019-05-30 16:12:56 -040035 break;
36 }
37 if (cluster->width() > maxWidth) {
38 // Break the cluster into parts by glyph position
39 auto delta = maxWidth - (fWords.width() + fClusters.width());
40 fClip.extend(cluster, cluster->roundPos(delta));
41 fTooLongCluster = true;
42 fTooLongWord = true;
43 break;
44 }
45
46 // Walk further to see if there is a too long word, cluster or glyph
47 SkScalar nextWordLength = fClusters.width();
48 for (auto further = cluster; further != endOfClusters; ++further) {
Julia Lavrovaf3ed2732019-09-05 14:35:17 -040049 if (further->isSoftBreak() || further->isHardBreak() || further->isWhitespaces()) {
Julia Lavrovaa3552c52019-05-30 16:12:56 -040050 break;
51 }
52 nextWordLength += further->width();
53 }
54 if (nextWordLength > maxWidth) {
55 // If the word is too long we can break it right now and hope it's enough
56 fTooLongWord = true;
57 }
58
59 // TODO: this is the place when we use hyphenation
Julia Lavrovaf3ed2732019-09-05 14:35:17 -040060 fMinIntrinsicWidth = SkTMax(fMinIntrinsicWidth, fTooLongWord ? maxWidth : nextWordLength);
Julia Lavrovaa3552c52019-05-30 16:12:56 -040061 break;
62 }
63
64 fClusters.extend(cluster);
65
66 // Keep adding clusters/words
67 if (fClusters.endOfWord()) {
68 fMinIntrinsicWidth = SkTMax(fMinIntrinsicWidth, getClustersTrimmedWidth());
69 fWords.extend(fClusters);
70 }
71
72 if ((fHardLineBreak = cluster->isHardBreak())) {
73 // Stop at the hard line break
74 break;
75 }
76 }
77}
78
79void TextWrapper::moveForward() {
80 do {
81 if (fWords.width() > 0) {
82 fEndLine.extend(fWords);
83 } else if (fClusters.width() > 0) {
84 fEndLine.extend(fClusters);
85 fTooLongWord = false;
Julia Lavrova2e30fde2019-10-09 09:43:02 -040086 } else if (fClip.width() > 0 || (fTooLongWord && fTooLongCluster)) {
Julia Lavrovaa3552c52019-05-30 16:12:56 -040087 fEndLine.extend(fClip);
88 fTooLongWord = false;
89 fTooLongCluster = false;
90 } else {
91 break;
92 }
93 } while (fTooLongWord || fTooLongCluster);
94}
95
96// Special case for start/end cluster since they can be clipped
Julia Lavrovadb9f6692019-08-01 16:02:17 -040097void TextWrapper::trimEndSpaces(TextAlign align) {
Julia Lavrovaa3552c52019-05-30 16:12:56 -040098 // Remember the breaking position
99 fEndLine.saveBreak();
Julia Lavrovadb9f6692019-08-01 16:02:17 -0400100 // Skip all space cluster at the end
101 //bool left = align == TextAlign::kStart || align == TextAlign::kLeft;
102 bool right = align == TextAlign::kRight || align == TextAlign::kEnd;
Julia Lavrova6e6333f2019-06-17 10:34:10 -0400103 for (auto cluster = fEndLine.endCluster();
104 cluster >= fEndLine.startCluster() && cluster->isWhitespaces();
105 --cluster) {
Julia Lavrova526df262019-08-21 17:49:44 -0400106 if ((cluster->run()->leftToRight()) ||
Julia Lavrovadb9f6692019-08-01 16:02:17 -0400107 (right && !cluster->run()->leftToRight()) ||
108 align == TextAlign::kJustify || align == TextAlign::kCenter) {
109 fEndLine.trim(cluster);
110 continue;
111 } else {
112 break;
113 }
Julia Lavrovaa3552c52019-05-30 16:12:56 -0400114 }
Julia Lavrova2e30fde2019-10-09 09:43:02 -0400115 fEndLine.trim();
Julia Lavrovaa3552c52019-05-30 16:12:56 -0400116}
117
118SkScalar TextWrapper::getClustersTrimmedWidth() {
119 // Move the end of the line to the left
120 SkScalar width = fClusters.width();
121 auto cluster = fClusters.endCluster();
122 for (; cluster > fClusters.startCluster() && cluster->isWhitespaces(); --cluster) {
123 width -= cluster->width();
124 }
125 if (cluster >= fClusters.startCluster()) {
Julia Lavrovaf3ed2732019-09-05 14:35:17 -0400126 if (cluster->isWhitespaces()) {
127 width -= cluster->width();
128 } else {
129 width -= (cluster->width() - cluster->trimmedWidth(cluster->endPos()));
130 }
Julia Lavrovaa3552c52019-05-30 16:12:56 -0400131 }
132 return width;
133}
134
135// Trim the beginning spaces in case of soft line break
Julia Lavrovadb9f6692019-08-01 16:02:17 -0400136std::tuple<Cluster*, size_t, SkScalar> TextWrapper::trimStartSpaces(Cluster* endOfClusters) {
137
Julia Lavrovaa3552c52019-05-30 16:12:56 -0400138 if (fHardLineBreak) {
139 // End of line is always end of cluster, but need to skip \n
Julia Lavrovadb9f6692019-08-01 16:02:17 -0400140 auto width = fEndLine.width();
141 auto cluster = fEndLine.endCluster() + 1;
142 while (cluster < fEndLine.breakCluster() && cluster->isWhitespaces()) {
143 width += cluster->width();
144 ++cluster;
145 }
Julia Lavrovaaf89d392019-08-09 15:19:26 -0400146 return std::make_tuple(fEndLine.breakCluster() + 1, 0, width);
Julia Lavrovaa3552c52019-05-30 16:12:56 -0400147 }
148
Julia Lavrovac2228562019-08-08 16:51:27 -0400149 auto width = fEndLine.withWithGhostSpaces(); //fEndLine.width();
Julia Lavrovadb9f6692019-08-01 16:02:17 -0400150 auto cluster = fEndLine.breakCluster() + 1;
Julia Lavrovaa3552c52019-05-30 16:12:56 -0400151 while (cluster < endOfClusters && cluster->isWhitespaces()) {
Julia Lavrovadb9f6692019-08-01 16:02:17 -0400152 width += cluster->width();
Julia Lavrovaa3552c52019-05-30 16:12:56 -0400153 ++cluster;
154 }
Julia Lavrovaaf89d392019-08-09 15:19:26 -0400155 return std::make_tuple(cluster, 0, width);
Julia Lavrovaa3552c52019-05-30 16:12:56 -0400156}
157
158void TextWrapper::breakTextIntoLines(ParagraphImpl* parent,
159 SkScalar maxWidth,
160 const AddLineToParagraph& addLine) {
Julia Lavrovaf3ed2732019-09-05 14:35:17 -0400161 fHeight = 0;
162 fMinIntrinsicWidth = 0;
163 fMaxIntrinsicWidth = 0;
164
Julia Lavrovaa3552c52019-05-30 16:12:56 -0400165 auto span = parent->clusters();
Julia Lavrova526df262019-08-21 17:49:44 -0400166 if (span.size() == 0) {
167 return;
168 }
Julia Lavrovaa3552c52019-05-30 16:12:56 -0400169 auto maxLines = parent->paragraphStyle().getMaxLines();
Julia Lavrovadb9f6692019-08-01 16:02:17 -0400170 auto& ellipsisStr = parent->paragraphStyle().getEllipsis();
Julia Lavrovaf3ed2732019-09-05 14:35:17 -0400171 auto align = parent->paragraphStyle().effective_align();
Julia Lavrovaa3552c52019-05-30 16:12:56 -0400172
Julia Lavrovaf3ed2732019-09-05 14:35:17 -0400173 SkScalar softLineMaxIntrinsicWidth = 0;
Julia Lavrovadb9f6692019-08-01 16:02:17 -0400174 fEndLine = TextStretch(span.begin(), span.begin(), parent->strutForceHeight());
175 auto end = span.end() - 1;
176 auto start = span.begin();
Julia Lavrovaf3ed2732019-09-05 14:35:17 -0400177 InternalLineMetrics maxRunMetrics;
178 auto needEllipsis = false;
179 auto endlessLine = maxLines == std::numeric_limits<size_t>::max();
Julia Lavrovaa3552c52019-05-30 16:12:56 -0400180 while (fEndLine.endCluster() != end) {
Julia Lavrovaf3ed2732019-09-05 14:35:17 -0400181
Julia Lavrovaa3552c52019-05-30 16:12:56 -0400182 reset();
183
Julia Lavrovaf3ed2732019-09-05 14:35:17 -0400184 fEndLine.metrics().clean();
Julia Lavrovaa3552c52019-05-30 16:12:56 -0400185 lookAhead(maxWidth, end);
186 moveForward();
187
188 // Do not trim end spaces on the naturally last line of the left aligned text
Julia Lavrovadb9f6692019-08-01 16:02:17 -0400189 trimEndSpaces(align);
190
191 // For soft line breaks add to the line all the spaces next to it
192 Cluster* startLine;
193 size_t pos;
194 SkScalar widthWithSpaces;
195 std::tie(startLine, pos, widthWithSpaces) = trimStartSpaces(end);
Julia Lavrovaa3552c52019-05-30 16:12:56 -0400196
Julia Lavrovaf3ed2732019-09-05 14:35:17 -0400197 needEllipsis =
198 fEndLine.endCluster() < end - 1 &&
199 SkScalarIsFinite(maxWidth) &&
200 !ellipsisStr.isEmpty();
201
202 auto exceededLines = !endlessLine && fLineNumber >= maxLines;
203
Julia Lavrovaa3552c52019-05-30 16:12:56 -0400204 // TODO: perform ellipsis work here
Julia Lavrova916a9042019-08-08 16:51:27 -0400205
Julia Lavrovaf3ed2732019-09-05 14:35:17 -0400206 // If the line is empty with the hard line break, let's take the paragraph font (flutter???)
207 if (fHardLineBreak && fEndLine.width() == 0) {
208 auto emptyMetrics = parent->computeEmptyMetrics();
209 fEndLine.setMetrics(emptyMetrics);
210 }
211
Julia Lavrova916a9042019-08-08 16:51:27 -0400212 // Deal with placeholder clusters == runs[@size==1]
213 for (auto cluster = fEndLine.startCluster(); cluster <= fEndLine.endCluster(); ++cluster) {
214 if (cluster->run()->placeholder() != nullptr) {
215 SkASSERT(cluster->run()->size() == 1);
216 // Update the placeholder metrics so we can get the placeholder positions later
217 // and the line metrics (to make sure the placeholder fits)
218 cluster->run()->updateMetrics(&fEndLine.metrics());
219 }
220 }
221
222 // Before we update the line metrics with struts,
223 // let's save it for GetRectsForRange(RectHeightStyle::kMax)
224 maxRunMetrics = fEndLine.metrics();
Julia Lavrovaf3ed2732019-09-05 14:35:17 -0400225 maxRunMetrics.fForceStrut = false;
Julia Lavrova916a9042019-08-08 16:51:27 -0400226
Julia Lavrovaa3552c52019-05-30 16:12:56 -0400227 if (parent->strutEnabled()) {
228 // Make sure font metrics are not less than the strut
Julia Lavrovadb9f6692019-08-01 16:02:17 -0400229 parent->strutMetrics().updateLineMetrics(fEndLine.metrics());
Julia Lavrovaa3552c52019-05-30 16:12:56 -0400230 }
Julia Lavrova916a9042019-08-08 16:51:27 -0400231
Julia Lavrovaa3552c52019-05-30 16:12:56 -0400232 // TODO: keep start/end/break info for text and runs but in a better way that below
Julia Lavrova526df262019-08-21 17:49:44 -0400233 TextRange text(fEndLine.startCluster()->textRange().start, fEndLine.endCluster()->textRange().end);
Julia Lavrovadb9f6692019-08-01 16:02:17 -0400234 TextRange textWithSpaces(fEndLine.startCluster()->textRange().start, startLine->textRange().start);
Julia Lavrova2e30fde2019-10-09 09:43:02 -0400235 if (startLine == end) {
Julia Lavrovaf3ed2732019-09-05 14:35:17 -0400236 textWithSpaces.end = parent->text().size();
Julia Lavrovadb9f6692019-08-01 16:02:17 -0400237 }
Julia Lavrovac2228562019-08-08 16:51:27 -0400238 ClusterRange clusters(fEndLine.startCluster() - start, fEndLine.endCluster() - start + 1);
Julia Lavrovadb9f6692019-08-01 16:02:17 -0400239 ClusterRange clustersWithGhosts(fEndLine.startCluster() - start, startLine - start);
240 addLine(text, textWithSpaces, clusters, clustersWithGhosts, widthWithSpaces,
Julia Lavrovaa3552c52019-05-30 16:12:56 -0400241 fEndLine.startPos(),
242 fEndLine.endPos(),
243 SkVector::Make(0, fHeight),
244 SkVector::Make(fEndLine.width(), fEndLine.metrics().height()),
245 fEndLine.metrics(),
Julia Lavrovaf3ed2732019-09-05 14:35:17 -0400246 needEllipsis && exceededLines && !fHardLineBreak);
Julia Lavrovaa3552c52019-05-30 16:12:56 -0400247
Julia Lavrova916a9042019-08-08 16:51:27 -0400248 parent->lines().back().setMaxRunMetrics(maxRunMetrics);
249
Julia Lavrovaf3ed2732019-09-05 14:35:17 -0400250 softLineMaxIntrinsicWidth += widthWithSpaces;
251 fMaxIntrinsicWidth = SkMaxScalar(fMaxIntrinsicWidth, softLineMaxIntrinsicWidth);
252 if (fHardLineBreak) {
253 softLineMaxIntrinsicWidth = 0;
254 }
Julia Lavrovaa3552c52019-05-30 16:12:56 -0400255 // Start a new line
256 fHeight += fEndLine.metrics().height();
Julia Lavrova916a9042019-08-08 16:51:27 -0400257 if (!fHardLineBreak || startLine != end) {
Julia Lavrovadb9f6692019-08-01 16:02:17 -0400258 fEndLine.clean();
259 }
260 fEndLine.startFrom(startLine, pos);
261 parent->fMaxWidthWithTrailingSpaces = SkMaxScalar(parent->fMaxWidthWithTrailingSpaces, widthWithSpaces);
Julia Lavrovaa3552c52019-05-30 16:12:56 -0400262
Julia Lavrovaf3ed2732019-09-05 14:35:17 -0400263 if (exceededLines || (needEllipsis && endlessLine && !fHardLineBreak)) {
264 fHardLineBreak = false;
Julia Lavrovaa3552c52019-05-30 16:12:56 -0400265 break;
266 }
Julia Lavrovaf3ed2732019-09-05 14:35:17 -0400267
Julia Lavrovaa3552c52019-05-30 16:12:56 -0400268 ++fLineNumber;
269 }
270
Julia Lavrovaf3ed2732019-09-05 14:35:17 -0400271 // We finished formatting the text but we need to scan the rest for some numbers
272 auto cluster = fEndLine.endCluster();
273 while (cluster != end) {
274 fExceededMaxLines = true;
275 if (cluster->isHardBreak()) {
276 softLineMaxIntrinsicWidth = 0;
277 } else {
278 softLineMaxIntrinsicWidth += cluster->width();
279 fMaxIntrinsicWidth = SkTMax(fMaxIntrinsicWidth, softLineMaxIntrinsicWidth);
280 }
281 ++cluster;
282 }
283
Julia Lavrovaa3552c52019-05-30 16:12:56 -0400284 if (fHardLineBreak) {
Julia Lavrova916a9042019-08-08 16:51:27 -0400285
Julia Lavrovaa3552c52019-05-30 16:12:56 -0400286 // Last character is a line break
287 if (parent->strutEnabled()) {
288 // Make sure font metrics are not less than the strut
Julia Lavrovadb9f6692019-08-01 16:02:17 -0400289 parent->strutMetrics().updateLineMetrics(fEndLine.metrics());
Julia Lavrovaa3552c52019-05-30 16:12:56 -0400290 }
Julia Lavrova5207f352019-06-21 12:22:32 -0400291 TextRange empty(fEndLine.breakCluster()->textRange().start, fEndLine.breakCluster()->textRange().start);
Julia Lavrovadb9f6692019-08-01 16:02:17 -0400292 TextRange hardBreak(fEndLine.breakCluster()->textRange().end, fEndLine.breakCluster()->textRange().end);
Julia Lavrova526df262019-08-21 17:49:44 -0400293 ClusterRange clusters(fEndLine.breakCluster() - start, fEndLine.endCluster() - start);
Julia Lavrovadb9f6692019-08-01 16:02:17 -0400294 addLine(empty, hardBreak, clusters, clusters,
295 0,
Julia Lavrovaa3552c52019-05-30 16:12:56 -0400296 0,
297 0,
298 SkVector::Make(0, fHeight),
299 SkVector::Make(0, fEndLine.metrics().height()),
300 fEndLine.metrics(),
Julia Lavrovaf3ed2732019-09-05 14:35:17 -0400301 needEllipsis);
302 fHeight += fEndLine.metrics().height();
Julia Lavrova916a9042019-08-08 16:51:27 -0400303 parent->lines().back().setMaxRunMetrics(maxRunMetrics);
Julia Lavrovaa3552c52019-05-30 16:12:56 -0400304 }
305}
306
307} // namespace textlayout
308} // namespace skia