blob: 813152bf265b9526571befe2272c8080a0dc9553 [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) {
Julia Lavrova90bfd1c2019-12-04 11:43:32 -050038 fClusters.extend(cluster);
Julia Lavrovaa3552c52019-05-30 16:12:56 -040039 fTooLongCluster = true;
40 fTooLongWord = true;
41 break;
42 }
43
44 // Walk further to see if there is a too long word, cluster or glyph
45 SkScalar nextWordLength = fClusters.width();
46 for (auto further = cluster; further != endOfClusters; ++further) {
Julia Lavrovaf3ed2732019-09-05 14:35:17 -040047 if (further->isSoftBreak() || further->isHardBreak() || further->isWhitespaces()) {
Julia Lavrovaa3552c52019-05-30 16:12:56 -040048 break;
49 }
50 nextWordLength += further->width();
51 }
52 if (nextWordLength > maxWidth) {
53 // If the word is too long we can break it right now and hope it's enough
Julia Lavrova90bfd1c2019-12-04 11:43:32 -050054 fMinIntrinsicWidth = SkTMax(fMinIntrinsicWidth, nextWordLength);
Julia Lavrovaa3552c52019-05-30 16:12:56 -040055 fTooLongWord = true;
56 }
Julia Lavrovaa3552c52019-05-30 16:12:56 -040057 break;
58 }
59
60 fClusters.extend(cluster);
61
62 // Keep adding clusters/words
63 if (fClusters.endOfWord()) {
64 fMinIntrinsicWidth = SkTMax(fMinIntrinsicWidth, getClustersTrimmedWidth());
65 fWords.extend(fClusters);
66 }
67
68 if ((fHardLineBreak = cluster->isHardBreak())) {
69 // Stop at the hard line break
70 break;
71 }
72 }
73}
74
75void TextWrapper::moveForward() {
76 do {
Julia Lavrova53c14472019-12-18 09:39:55 -050077 if (!fWords.empty()) {
Julia Lavrovaa3552c52019-05-30 16:12:56 -040078 fEndLine.extend(fWords);
Julia Lavrova53c14472019-12-18 09:39:55 -050079 } else if (!fClusters.empty()) {
Julia Lavrovaa3552c52019-05-30 16:12:56 -040080 fEndLine.extend(fClusters);
81 fTooLongWord = false;
Julia Lavrova90bfd1c2019-12-04 11:43:32 -050082 fTooLongCluster = false;
Julia Lavrova53c14472019-12-18 09:39:55 -050083 } else if (!fClip.empty() || (fTooLongWord && fTooLongCluster)) {
Julia Lavrova90bfd1c2019-12-04 11:43:32 -050084 // Flutter: forget the clipped cluster but keep the metrics
85 fEndLine.metrics().add(fClip.metrics());
Julia Lavrovaa3552c52019-05-30 16:12:56 -040086 fTooLongWord = false;
87 fTooLongCluster = false;
88 } else {
89 break;
90 }
91 } while (fTooLongWord || fTooLongCluster);
92}
93
94// Special case for start/end cluster since they can be clipped
Julia Lavrovadb9f6692019-08-01 16:02:17 -040095void TextWrapper::trimEndSpaces(TextAlign align) {
Julia Lavrovaa3552c52019-05-30 16:12:56 -040096 // Remember the breaking position
97 fEndLine.saveBreak();
Julia Lavrovadb9f6692019-08-01 16:02:17 -040098 // Skip all space cluster at the end
99 //bool left = align == TextAlign::kStart || align == TextAlign::kLeft;
100 bool right = align == TextAlign::kRight || align == TextAlign::kEnd;
Julia Lavrova6e6333f2019-06-17 10:34:10 -0400101 for (auto cluster = fEndLine.endCluster();
102 cluster >= fEndLine.startCluster() && cluster->isWhitespaces();
103 --cluster) {
Julia Lavrova526df262019-08-21 17:49:44 -0400104 if ((cluster->run()->leftToRight()) ||
Julia Lavrovadb9f6692019-08-01 16:02:17 -0400105 (right && !cluster->run()->leftToRight()) ||
106 align == TextAlign::kJustify || align == TextAlign::kCenter) {
107 fEndLine.trim(cluster);
108 continue;
109 } else {
110 break;
111 }
Julia Lavrovaa3552c52019-05-30 16:12:56 -0400112 }
Julia Lavrova2e30fde2019-10-09 09:43:02 -0400113 fEndLine.trim();
Julia Lavrovaa3552c52019-05-30 16:12:56 -0400114}
115
116SkScalar TextWrapper::getClustersTrimmedWidth() {
117 // Move the end of the line to the left
118 SkScalar width = fClusters.width();
119 auto cluster = fClusters.endCluster();
120 for (; cluster > fClusters.startCluster() && cluster->isWhitespaces(); --cluster) {
121 width -= cluster->width();
122 }
123 if (cluster >= fClusters.startCluster()) {
Julia Lavrovaf3ed2732019-09-05 14:35:17 -0400124 if (cluster->isWhitespaces()) {
125 width -= cluster->width();
126 } else {
127 width -= (cluster->width() - cluster->trimmedWidth(cluster->endPos()));
128 }
Julia Lavrovaa3552c52019-05-30 16:12:56 -0400129 }
130 return width;
131}
132
133// Trim the beginning spaces in case of soft line break
Julia Lavrovadb9f6692019-08-01 16:02:17 -0400134std::tuple<Cluster*, size_t, SkScalar> TextWrapper::trimStartSpaces(Cluster* endOfClusters) {
135
Julia Lavrovaa3552c52019-05-30 16:12:56 -0400136 if (fHardLineBreak) {
137 // End of line is always end of cluster, but need to skip \n
Julia Lavrovadb9f6692019-08-01 16:02:17 -0400138 auto width = fEndLine.width();
139 auto cluster = fEndLine.endCluster() + 1;
140 while (cluster < fEndLine.breakCluster() && cluster->isWhitespaces()) {
141 width += cluster->width();
142 ++cluster;
143 }
Julia Lavrovaaf89d392019-08-09 15:19:26 -0400144 return std::make_tuple(fEndLine.breakCluster() + 1, 0, width);
Julia Lavrovaa3552c52019-05-30 16:12:56 -0400145 }
146
Julia Lavrova53c14472019-12-18 09:39:55 -0500147 auto width = fEndLine.withWithGhostSpaces();
148 auto cluster = fEndLine.breakCluster();
149 if (fEndLine.endCluster() != fEndLine.startCluster() ||
150 fEndLine.endPos() != fEndLine.startPos()) {
Julia Lavrovaa3552c52019-05-30 16:12:56 -0400151 ++cluster;
Julia Lavrova53c14472019-12-18 09:39:55 -0500152 while (cluster < endOfClusters && cluster->isWhitespaces()) {
153 width += cluster->width();
154 ++cluster;
155 }
156 } else {
157 // Nothing fits the line - no need to check for spaces
Julia Lavrovaa3552c52019-05-30 16:12:56 -0400158 }
Julia Lavrova53c14472019-12-18 09:39:55 -0500159
Julia Lavrovaaf89d392019-08-09 15:19:26 -0400160 return std::make_tuple(cluster, 0, width);
Julia Lavrovaa3552c52019-05-30 16:12:56 -0400161}
162
163void TextWrapper::breakTextIntoLines(ParagraphImpl* parent,
164 SkScalar maxWidth,
165 const AddLineToParagraph& addLine) {
Julia Lavrovaf3ed2732019-09-05 14:35:17 -0400166 fHeight = 0;
Julia Lavrova90bfd1c2019-12-04 11:43:32 -0500167 fMinIntrinsicWidth = std::numeric_limits<SkScalar>::min();
168 fMaxIntrinsicWidth = std::numeric_limits<SkScalar>::min();
Julia Lavrovaf3ed2732019-09-05 14:35:17 -0400169
Julia Lavrovaa3552c52019-05-30 16:12:56 -0400170 auto span = parent->clusters();
Julia Lavrova526df262019-08-21 17:49:44 -0400171 if (span.size() == 0) {
172 return;
173 }
Julia Lavrovaa3552c52019-05-30 16:12:56 -0400174 auto maxLines = parent->paragraphStyle().getMaxLines();
Julia Lavrovadb9f6692019-08-01 16:02:17 -0400175 auto& ellipsisStr = parent->paragraphStyle().getEllipsis();
Julia Lavrovaf3ed2732019-09-05 14:35:17 -0400176 auto align = parent->paragraphStyle().effective_align();
Julia Lavrovaa3552c52019-05-30 16:12:56 -0400177
Julia Lavrovaf3ed2732019-09-05 14:35:17 -0400178 SkScalar softLineMaxIntrinsicWidth = 0;
Julia Lavrovadb9f6692019-08-01 16:02:17 -0400179 fEndLine = TextStretch(span.begin(), span.begin(), parent->strutForceHeight());
180 auto end = span.end() - 1;
181 auto start = span.begin();
Julia Lavrovaf3ed2732019-09-05 14:35:17 -0400182 InternalLineMetrics maxRunMetrics;
183 auto needEllipsis = false;
184 auto endlessLine = maxLines == std::numeric_limits<size_t>::max();
Julia Lavrovaa3552c52019-05-30 16:12:56 -0400185 while (fEndLine.endCluster() != end) {
Julia Lavrovaf3ed2732019-09-05 14:35:17 -0400186
Julia Lavrovaa3552c52019-05-30 16:12:56 -0400187 reset();
188
Julia Lavrovaf3ed2732019-09-05 14:35:17 -0400189 fEndLine.metrics().clean();
Julia Lavrovaa3552c52019-05-30 16:12:56 -0400190 lookAhead(maxWidth, end);
191 moveForward();
192
193 // Do not trim end spaces on the naturally last line of the left aligned text
Julia Lavrovadb9f6692019-08-01 16:02:17 -0400194 trimEndSpaces(align);
195
196 // For soft line breaks add to the line all the spaces next to it
197 Cluster* startLine;
198 size_t pos;
199 SkScalar widthWithSpaces;
200 std::tie(startLine, pos, widthWithSpaces) = trimStartSpaces(end);
Julia Lavrovaa3552c52019-05-30 16:12:56 -0400201
Julia Lavrovaf3ed2732019-09-05 14:35:17 -0400202 needEllipsis =
203 fEndLine.endCluster() < end - 1 &&
204 SkScalarIsFinite(maxWidth) &&
205 !ellipsisStr.isEmpty();
206
207 auto exceededLines = !endlessLine && fLineNumber >= maxLines;
208
Julia Lavrovaa3552c52019-05-30 16:12:56 -0400209 // TODO: perform ellipsis work here
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]
217 for (auto cluster = fEndLine.startCluster(); cluster <= fEndLine.endCluster(); ++cluster) {
218 if (cluster->run()->placeholder() != nullptr) {
219 SkASSERT(cluster->run()->size() == 1);
220 // Update the placeholder metrics so we can get the placeholder positions later
221 // and the line metrics (to make sure the placeholder fits)
222 cluster->run()->updateMetrics(&fEndLine.metrics());
223 }
224 }
225
226 // Before we update the line metrics with struts,
227 // let's save it for GetRectsForRange(RectHeightStyle::kMax)
228 maxRunMetrics = fEndLine.metrics();
Julia Lavrovaf3ed2732019-09-05 14:35:17 -0400229 maxRunMetrics.fForceStrut = false;
Julia Lavrova916a9042019-08-08 16:51:27 -0400230
Julia Lavrovaa3552c52019-05-30 16:12:56 -0400231 if (parent->strutEnabled()) {
232 // Make sure font metrics are not less than the strut
Julia Lavrovadb9f6692019-08-01 16:02:17 -0400233 parent->strutMetrics().updateLineMetrics(fEndLine.metrics());
Julia Lavrovaa3552c52019-05-30 16:12:56 -0400234 }
Julia Lavrova916a9042019-08-08 16:51:27 -0400235
Julia Lavrovaa3552c52019-05-30 16:12:56 -0400236 // TODO: keep start/end/break info for text and runs but in a better way that below
Julia Lavrova526df262019-08-21 17:49:44 -0400237 TextRange text(fEndLine.startCluster()->textRange().start, fEndLine.endCluster()->textRange().end);
Julia Lavrovadb9f6692019-08-01 16:02:17 -0400238 TextRange textWithSpaces(fEndLine.startCluster()->textRange().start, startLine->textRange().start);
Julia Lavrova2e30fde2019-10-09 09:43:02 -0400239 if (startLine == end) {
Julia Lavrovaf3ed2732019-09-05 14:35:17 -0400240 textWithSpaces.end = parent->text().size();
Julia Lavrovadb9f6692019-08-01 16:02:17 -0400241 }
Julia Lavrovac2228562019-08-08 16:51:27 -0400242 ClusterRange clusters(fEndLine.startCluster() - start, fEndLine.endCluster() - start + 1);
Julia Lavrovadb9f6692019-08-01 16:02:17 -0400243 ClusterRange clustersWithGhosts(fEndLine.startCluster() - start, startLine - start);
244 addLine(text, textWithSpaces, clusters, clustersWithGhosts, widthWithSpaces,
Julia Lavrovaa3552c52019-05-30 16:12:56 -0400245 fEndLine.startPos(),
246 fEndLine.endPos(),
247 SkVector::Make(0, fHeight),
248 SkVector::Make(fEndLine.width(), fEndLine.metrics().height()),
249 fEndLine.metrics(),
Julia Lavrovaf3ed2732019-09-05 14:35:17 -0400250 needEllipsis && exceededLines && !fHardLineBreak);
Julia Lavrovaa3552c52019-05-30 16:12:56 -0400251
Julia Lavrova916a9042019-08-08 16:51:27 -0400252 parent->lines().back().setMaxRunMetrics(maxRunMetrics);
253
Julia Lavrovaf3ed2732019-09-05 14:35:17 -0400254 softLineMaxIntrinsicWidth += widthWithSpaces;
255 fMaxIntrinsicWidth = SkMaxScalar(fMaxIntrinsicWidth, softLineMaxIntrinsicWidth);
256 if (fHardLineBreak) {
257 softLineMaxIntrinsicWidth = 0;
258 }
Julia Lavrovaa3552c52019-05-30 16:12:56 -0400259 // Start a new line
260 fHeight += fEndLine.metrics().height();
Julia Lavrova916a9042019-08-08 16:51:27 -0400261 if (!fHardLineBreak || startLine != end) {
Julia Lavrovadb9f6692019-08-01 16:02:17 -0400262 fEndLine.clean();
263 }
264 fEndLine.startFrom(startLine, pos);
265 parent->fMaxWidthWithTrailingSpaces = SkMaxScalar(parent->fMaxWidthWithTrailingSpaces, widthWithSpaces);
Julia Lavrovaa3552c52019-05-30 16:12:56 -0400266
Julia Lavrovaf3ed2732019-09-05 14:35:17 -0400267 if (exceededLines || (needEllipsis && endlessLine && !fHardLineBreak)) {
268 fHardLineBreak = false;
Julia Lavrovaa3552c52019-05-30 16:12:56 -0400269 break;
270 }
Julia Lavrovaf3ed2732019-09-05 14:35:17 -0400271
Julia Lavrovaa3552c52019-05-30 16:12:56 -0400272 ++fLineNumber;
273 }
274
Julia Lavrovaf3ed2732019-09-05 14:35:17 -0400275 // We finished formatting the text but we need to scan the rest for some numbers
Julia Lavrova53c14472019-12-18 09:39:55 -0500276 if (fEndLine.endCluster() != nullptr) {
Julia Lavrova90bfd1c2019-12-04 11:43:32 -0500277 auto lastWordLength = 0.0f;
Julia Lavrova53c14472019-12-18 09:39:55 -0500278 auto cluster = fEndLine.endCluster();
Julia Lavrova90bfd1c2019-12-04 11:43:32 -0500279 while (cluster != end || cluster->endPos() < end->endPos()) {
280 fExceededMaxLines = true;
281 if (cluster->isHardBreak()) {
282 fMaxIntrinsicWidth = SkTMax(fMaxIntrinsicWidth, softLineMaxIntrinsicWidth);
283 softLineMaxIntrinsicWidth = 0;
284
285 fMinIntrinsicWidth = SkTMax(fMinIntrinsicWidth, lastWordLength);
286 lastWordLength = 0;
287 } else if (cluster->isWhitespaces()) {
288 SkASSERT(cluster->isWhitespaces());
289 softLineMaxIntrinsicWidth += cluster->width();
290 fMinIntrinsicWidth = SkTMax(fMinIntrinsicWidth, lastWordLength);
291 lastWordLength = 0;
292 } else {
293 softLineMaxIntrinsicWidth += cluster->width();
294 lastWordLength += cluster->width();
295 }
296 ++cluster;
297 }
298 fMinIntrinsicWidth = SkTMax(fMinIntrinsicWidth, lastWordLength);
299 fMaxIntrinsicWidth = SkTMax(fMaxIntrinsicWidth, softLineMaxIntrinsicWidth);
300 // In case we could not place a single cluster on the line
301 fHeight = SkTMax(fHeight, fEndLine.metrics().height());
Julia Lavrovaf3ed2732019-09-05 14:35:17 -0400302 }
303
Julia Lavrovaa3552c52019-05-30 16:12:56 -0400304 if (fHardLineBreak) {
Julia Lavrova916a9042019-08-08 16:51:27 -0400305
Julia Lavrovaa3552c52019-05-30 16:12:56 -0400306 // Last character is a line break
307 if (parent->strutEnabled()) {
308 // Make sure font metrics are not less than the strut
Julia Lavrovadb9f6692019-08-01 16:02:17 -0400309 parent->strutMetrics().updateLineMetrics(fEndLine.metrics());
Julia Lavrovaa3552c52019-05-30 16:12:56 -0400310 }
Julia Lavrova5207f352019-06-21 12:22:32 -0400311 TextRange empty(fEndLine.breakCluster()->textRange().start, fEndLine.breakCluster()->textRange().start);
Julia Lavrovadb9f6692019-08-01 16:02:17 -0400312 TextRange hardBreak(fEndLine.breakCluster()->textRange().end, fEndLine.breakCluster()->textRange().end);
Julia Lavrova526df262019-08-21 17:49:44 -0400313 ClusterRange clusters(fEndLine.breakCluster() - start, fEndLine.endCluster() - start);
Julia Lavrovadb9f6692019-08-01 16:02:17 -0400314 addLine(empty, hardBreak, clusters, clusters,
315 0,
Julia Lavrovaa3552c52019-05-30 16:12:56 -0400316 0,
317 0,
318 SkVector::Make(0, fHeight),
319 SkVector::Make(0, fEndLine.metrics().height()),
320 fEndLine.metrics(),
Julia Lavrovaf3ed2732019-09-05 14:35:17 -0400321 needEllipsis);
322 fHeight += fEndLine.metrics().height();
Julia Lavrova916a9042019-08-08 16:51:27 -0400323 parent->lines().back().setMaxRunMetrics(maxRunMetrics);
Julia Lavrovaa3552c52019-05-30 16:12:56 -0400324 }
325}
326
327} // namespace textlayout
328} // namespace skia