blob: f5e611b321588b67b5c0180ec9555a647b698b76 [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
8// Since we allow cluster clipping when they don't fit
9// we have to work with stretches - parts of clusters
10void TextWrapper::lookAhead(SkScalar maxWidth, Cluster* endOfClusters) {
11 fWords.startFrom(fEndLine.startCluster(), fEndLine.startPos());
12 fClusters.startFrom(fEndLine.startCluster(), fEndLine.startPos());
13 fClip.startFrom(fEndLine.startCluster(), fEndLine.startPos());
14 for (auto cluster = fEndLine.endCluster(); cluster < endOfClusters; ++cluster) {
15 if (fWords.width() + fClusters.width() + cluster->width() > maxWidth) {
16 if (cluster->isWhitespaces()) {
Julia Lavrova6e6333f2019-06-17 10:34:10 -040017 // It's the end of the word
18 fMinIntrinsicWidth = SkTMax(fMinIntrinsicWidth, getClustersTrimmedWidth());
19 fWords.extend(fClusters);
Julia Lavrovaa3552c52019-05-30 16:12:56 -040020 break;
21 }
22 if (cluster->width() > maxWidth) {
23 // Break the cluster into parts by glyph position
24 auto delta = maxWidth - (fWords.width() + fClusters.width());
25 fClip.extend(cluster, cluster->roundPos(delta));
26 fTooLongCluster = true;
27 fTooLongWord = true;
28 break;
29 }
30
31 // Walk further to see if there is a too long word, cluster or glyph
32 SkScalar nextWordLength = fClusters.width();
33 for (auto further = cluster; further != endOfClusters; ++further) {
34 if (further->isSoftBreak() || further->isHardBreak()) {
35 break;
36 }
37 nextWordLength += further->width();
38 }
39 if (nextWordLength > maxWidth) {
40 // If the word is too long we can break it right now and hope it's enough
41 fTooLongWord = true;
42 }
43
44 // TODO: this is the place when we use hyphenation
45 fMinIntrinsicWidth = SkTMax(fMinIntrinsicWidth, nextWordLength);
46 break;
47 }
48
49 fClusters.extend(cluster);
50
51 // Keep adding clusters/words
52 if (fClusters.endOfWord()) {
53 fMinIntrinsicWidth = SkTMax(fMinIntrinsicWidth, getClustersTrimmedWidth());
54 fWords.extend(fClusters);
55 }
56
57 if ((fHardLineBreak = cluster->isHardBreak())) {
58 // Stop at the hard line break
59 break;
60 }
61 }
62}
63
64void TextWrapper::moveForward() {
65 do {
66 if (fWords.width() > 0) {
67 fEndLine.extend(fWords);
68 } else if (fClusters.width() > 0) {
69 fEndLine.extend(fClusters);
70 fTooLongWord = false;
71 } else if (fClip.width() > 0) {
72 fEndLine.extend(fClip);
73 fTooLongWord = false;
74 fTooLongCluster = false;
75 } else {
76 break;
77 }
78 } while (fTooLongWord || fTooLongCluster);
79}
80
81// Special case for start/end cluster since they can be clipped
Julia Lavrovadb9f6692019-08-01 16:02:17 -040082void TextWrapper::trimEndSpaces(TextAlign align) {
Julia Lavrovaa3552c52019-05-30 16:12:56 -040083 // Remember the breaking position
84 fEndLine.saveBreak();
Julia Lavrovadb9f6692019-08-01 16:02:17 -040085 // Skip all space cluster at the end
86 //bool left = align == TextAlign::kStart || align == TextAlign::kLeft;
87 bool right = align == TextAlign::kRight || align == TextAlign::kEnd;
Julia Lavrova6e6333f2019-06-17 10:34:10 -040088 for (auto cluster = fEndLine.endCluster();
89 cluster >= fEndLine.startCluster() && cluster->isWhitespaces();
90 --cluster) {
Julia Lavrova526df262019-08-21 17:49:44 -040091 if ((cluster->run()->leftToRight()) ||
Julia Lavrovadb9f6692019-08-01 16:02:17 -040092 (right && !cluster->run()->leftToRight()) ||
93 align == TextAlign::kJustify || align == TextAlign::kCenter) {
94 fEndLine.trim(cluster);
95 continue;
96 } else {
97 break;
98 }
Julia Lavrovaa3552c52019-05-30 16:12:56 -040099 }
Julia Lavrova526df262019-08-21 17:49:44 -0400100 if (!right || true) {
Julia Lavrovadb9f6692019-08-01 16:02:17 -0400101 fEndLine.trim();
102 }
Julia Lavrovaa3552c52019-05-30 16:12:56 -0400103}
104
105SkScalar TextWrapper::getClustersTrimmedWidth() {
106 // Move the end of the line to the left
107 SkScalar width = fClusters.width();
108 auto cluster = fClusters.endCluster();
109 for (; cluster > fClusters.startCluster() && cluster->isWhitespaces(); --cluster) {
110 width -= cluster->width();
111 }
112 if (cluster >= fClusters.startCluster()) {
113 width -= (cluster->width() - cluster->trimmedWidth(cluster->endPos()));
114 }
115 return width;
116}
117
118// Trim the beginning spaces in case of soft line break
Julia Lavrovadb9f6692019-08-01 16:02:17 -0400119std::tuple<Cluster*, size_t, SkScalar> TextWrapper::trimStartSpaces(Cluster* endOfClusters) {
120
Julia Lavrovaa3552c52019-05-30 16:12:56 -0400121 if (fHardLineBreak) {
122 // End of line is always end of cluster, but need to skip \n
Julia Lavrovadb9f6692019-08-01 16:02:17 -0400123 auto width = fEndLine.width();
124 auto cluster = fEndLine.endCluster() + 1;
125 while (cluster < fEndLine.breakCluster() && cluster->isWhitespaces()) {
126 width += cluster->width();
127 ++cluster;
128 }
Julia Lavrovaaf89d392019-08-09 15:19:26 -0400129 return std::make_tuple(fEndLine.breakCluster() + 1, 0, width);
Julia Lavrovaa3552c52019-05-30 16:12:56 -0400130 }
131
Julia Lavrovac2228562019-08-08 16:51:27 -0400132 auto width = fEndLine.withWithGhostSpaces(); //fEndLine.width();
Julia Lavrovadb9f6692019-08-01 16:02:17 -0400133 auto cluster = fEndLine.breakCluster() + 1;
Julia Lavrovaa3552c52019-05-30 16:12:56 -0400134 while (cluster < endOfClusters && cluster->isWhitespaces()) {
Julia Lavrovadb9f6692019-08-01 16:02:17 -0400135 width += cluster->width();
Julia Lavrovaa3552c52019-05-30 16:12:56 -0400136 ++cluster;
137 }
Julia Lavrovaaf89d392019-08-09 15:19:26 -0400138 return std::make_tuple(cluster, 0, width);
Julia Lavrovaa3552c52019-05-30 16:12:56 -0400139}
140
141void TextWrapper::breakTextIntoLines(ParagraphImpl* parent,
142 SkScalar maxWidth,
143 const AddLineToParagraph& addLine) {
144 auto span = parent->clusters();
Julia Lavrova526df262019-08-21 17:49:44 -0400145 if (span.size() == 0) {
146 return;
147 }
Julia Lavrovaa3552c52019-05-30 16:12:56 -0400148 auto maxLines = parent->paragraphStyle().getMaxLines();
Julia Lavrovadb9f6692019-08-01 16:02:17 -0400149 auto& ellipsisStr = parent->paragraphStyle().getEllipsis();
150 auto align = parent->paragraphStyle().getTextAlign();
Julia Lavrovaa3552c52019-05-30 16:12:56 -0400151
152 fHeight = 0;
153 fMinIntrinsicWidth = 0;
154 fMaxIntrinsicWidth = 0;
Julia Lavrovadb9f6692019-08-01 16:02:17 -0400155 fEndLine = TextStretch(span.begin(), span.begin(), parent->strutForceHeight());
156 auto end = span.end() - 1;
157 auto start = span.begin();
Julia Lavrova916a9042019-08-08 16:51:27 -0400158 LineMetrics maxRunMetrics;
Julia Lavrovaa3552c52019-05-30 16:12:56 -0400159 while (fEndLine.endCluster() != end) {
160 reset();
161
162 lookAhead(maxWidth, end);
163 moveForward();
164
165 // Do not trim end spaces on the naturally last line of the left aligned text
Julia Lavrovadb9f6692019-08-01 16:02:17 -0400166 trimEndSpaces(align);
167
168 // For soft line breaks add to the line all the spaces next to it
169 Cluster* startLine;
170 size_t pos;
171 SkScalar widthWithSpaces;
172 std::tie(startLine, pos, widthWithSpaces) = trimStartSpaces(end);
Julia Lavrovaa3552c52019-05-30 16:12:56 -0400173
174 auto lastLine = maxLines == std::numeric_limits<size_t>::max() ||
175 fLineNumber >= maxLines;
176 auto needEllipsis =
177 lastLine &&
178 !fHardLineBreak &&
179 fEndLine.endCluster() < end - 1 &&
180 maxWidth != std::numeric_limits<SkScalar>::max() &&
181 !ellipsisStr.isEmpty();
182 // TODO: perform ellipsis work here
Julia Lavrova916a9042019-08-08 16:51:27 -0400183
184 // Deal with placeholder clusters == runs[@size==1]
185 for (auto cluster = fEndLine.startCluster(); cluster <= fEndLine.endCluster(); ++cluster) {
186 if (cluster->run()->placeholder() != nullptr) {
187 SkASSERT(cluster->run()->size() == 1);
188 // Update the placeholder metrics so we can get the placeholder positions later
189 // and the line metrics (to make sure the placeholder fits)
190 cluster->run()->updateMetrics(&fEndLine.metrics());
191 }
192 }
193
194 // Before we update the line metrics with struts,
195 // let's save it for GetRectsForRange(RectHeightStyle::kMax)
196 maxRunMetrics = fEndLine.metrics();
197
Julia Lavrovaa3552c52019-05-30 16:12:56 -0400198 if (parent->strutEnabled()) {
199 // Make sure font metrics are not less than the strut
Julia Lavrovadb9f6692019-08-01 16:02:17 -0400200 parent->strutMetrics().updateLineMetrics(fEndLine.metrics());
Julia Lavrovaa3552c52019-05-30 16:12:56 -0400201 }
Julia Lavrova916a9042019-08-08 16:51:27 -0400202
Julia Lavrovaa3552c52019-05-30 16:12:56 -0400203 fMaxIntrinsicWidth = SkMaxScalar(fMaxIntrinsicWidth, fEndLine.width());
204 // TODO: keep start/end/break info for text and runs but in a better way that below
Julia Lavrova526df262019-08-21 17:49:44 -0400205 TextRange text(fEndLine.startCluster()->textRange().start, fEndLine.endCluster()->textRange().end);
Julia Lavrovadb9f6692019-08-01 16:02:17 -0400206 TextRange textWithSpaces(fEndLine.startCluster()->textRange().start, startLine->textRange().start);
207 if (fEndLine.breakCluster()->isHardBreak()) {
208 textWithSpaces.end = fEndLine.breakCluster()->textRange().start;
209 } else if (startLine == end) {
210 textWithSpaces.end = fEndLine.breakCluster()->textRange().end;
211 }
Julia Lavrovac2228562019-08-08 16:51:27 -0400212 ClusterRange clusters(fEndLine.startCluster() - start, fEndLine.endCluster() - start + 1);
Julia Lavrovadb9f6692019-08-01 16:02:17 -0400213 ClusterRange clustersWithGhosts(fEndLine.startCluster() - start, startLine - start);
214 addLine(text, textWithSpaces, clusters, clustersWithGhosts, widthWithSpaces,
Julia Lavrovaa3552c52019-05-30 16:12:56 -0400215 fEndLine.startPos(),
216 fEndLine.endPos(),
217 SkVector::Make(0, fHeight),
218 SkVector::Make(fEndLine.width(), fEndLine.metrics().height()),
219 fEndLine.metrics(),
220 needEllipsis);
221
Julia Lavrova916a9042019-08-08 16:51:27 -0400222 parent->lines().back().setMaxRunMetrics(maxRunMetrics);
223
Julia Lavrovaa3552c52019-05-30 16:12:56 -0400224 // Start a new line
225 fHeight += fEndLine.metrics().height();
226
Julia Lavrova916a9042019-08-08 16:51:27 -0400227 if (!fHardLineBreak || startLine != end) {
Julia Lavrovadb9f6692019-08-01 16:02:17 -0400228 fEndLine.clean();
229 }
230 fEndLine.startFrom(startLine, pos);
231 parent->fMaxWidthWithTrailingSpaces = SkMaxScalar(parent->fMaxWidthWithTrailingSpaces, widthWithSpaces);
Julia Lavrovaa3552c52019-05-30 16:12:56 -0400232
233 if (needEllipsis || fLineNumber >= maxLines) {
234 break;
235 }
236 ++fLineNumber;
237 }
238
239 if (fHardLineBreak) {
Julia Lavrova916a9042019-08-08 16:51:27 -0400240
Julia Lavrovaa3552c52019-05-30 16:12:56 -0400241 // Last character is a line break
242 if (parent->strutEnabled()) {
243 // Make sure font metrics are not less than the strut
Julia Lavrovadb9f6692019-08-01 16:02:17 -0400244 parent->strutMetrics().updateLineMetrics(fEndLine.metrics());
Julia Lavrovaa3552c52019-05-30 16:12:56 -0400245 }
Julia Lavrova5207f352019-06-21 12:22:32 -0400246 TextRange empty(fEndLine.breakCluster()->textRange().start, fEndLine.breakCluster()->textRange().start);
Julia Lavrovadb9f6692019-08-01 16:02:17 -0400247 TextRange hardBreak(fEndLine.breakCluster()->textRange().end, fEndLine.breakCluster()->textRange().end);
Julia Lavrova526df262019-08-21 17:49:44 -0400248 ClusterRange clusters(fEndLine.breakCluster() - start, fEndLine.endCluster() - start);
Julia Lavrovadb9f6692019-08-01 16:02:17 -0400249 addLine(empty, hardBreak, clusters, clusters,
250 0,
Julia Lavrovaa3552c52019-05-30 16:12:56 -0400251 0,
252 0,
253 SkVector::Make(0, fHeight),
254 SkVector::Make(0, fEndLine.metrics().height()),
255 fEndLine.metrics(),
256 false);
Julia Lavrova916a9042019-08-08 16:51:27 -0400257 parent->lines().back().setMaxRunMetrics(maxRunMetrics);
Julia Lavrovaa3552c52019-05-30 16:12:56 -0400258 }
259}
260
261} // namespace textlayout
262} // namespace skia