blob: 4b585a4143691e52481200ecc773717d1fb89512 [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 Lavrova6e6333f2019-06-17 10:34:10 -040082void TextWrapper::trimEndSpaces() {
Julia Lavrovaa3552c52019-05-30 16:12:56 -040083 // Remember the breaking position
84 fEndLine.saveBreak();
Julia Lavrova6e6333f2019-06-17 10:34:10 -040085 // Move the end of the line to the left
86 for (auto cluster = fEndLine.endCluster();
87 cluster >= fEndLine.startCluster() && cluster->isWhitespaces();
88 --cluster) {
89 fEndLine.trim(cluster);
Julia Lavrovaa3552c52019-05-30 16:12:56 -040090 }
91 fEndLine.trim();
92}
93
94SkScalar TextWrapper::getClustersTrimmedWidth() {
95 // Move the end of the line to the left
96 SkScalar width = fClusters.width();
97 auto cluster = fClusters.endCluster();
98 for (; cluster > fClusters.startCluster() && cluster->isWhitespaces(); --cluster) {
99 width -= cluster->width();
100 }
101 if (cluster >= fClusters.startCluster()) {
102 width -= (cluster->width() - cluster->trimmedWidth(cluster->endPos()));
103 }
104 return width;
105}
106
107// Trim the beginning spaces in case of soft line break
108void TextWrapper::trimStartSpaces(Cluster* endOfClusters) {
109 // Restore the breaking position
110 fEndLine.restoreBreak();
111 fEndLine.nextPos();
112 if (fHardLineBreak) {
113 // End of line is always end of cluster, but need to skip \n
114 fEndLine.startFrom(fEndLine.endCluster(), 0);
115 return;
116 }
117 if (fEndLine.endPos() != 0) {
118 // Clipping
119 fEndLine.startFrom(fEndLine.endCluster(), fEndLine.endPos());
120 return;
121 }
122
123 auto cluster = fEndLine.endCluster();
124 while (cluster < endOfClusters && cluster->isWhitespaces()) {
125 ++cluster;
126 }
127 fEndLine.startFrom(cluster, 0);
128}
129
130void TextWrapper::breakTextIntoLines(ParagraphImpl* parent,
131 SkScalar maxWidth,
132 const AddLineToParagraph& addLine) {
133 auto span = parent->clusters();
134 auto maxLines = parent->paragraphStyle().getMaxLines();
135 auto ellipsisStr = parent->paragraphStyle().getEllipsis();
Julia Lavrovaa3552c52019-05-30 16:12:56 -0400136
137 fHeight = 0;
138 fMinIntrinsicWidth = 0;
139 fMaxIntrinsicWidth = 0;
140 fEndLine = TextStretch(span.begin(), span.begin());
141 auto end = &span.back();
142 while (fEndLine.endCluster() != end) {
143 reset();
144
145 lookAhead(maxWidth, end);
146 moveForward();
147
148 // Do not trim end spaces on the naturally last line of the left aligned text
Julia Lavrova6e6333f2019-06-17 10:34:10 -0400149 trimEndSpaces();
Julia Lavrovaa3552c52019-05-30 16:12:56 -0400150
151 auto lastLine = maxLines == std::numeric_limits<size_t>::max() ||
152 fLineNumber >= maxLines;
153 auto needEllipsis =
154 lastLine &&
155 !fHardLineBreak &&
156 fEndLine.endCluster() < end - 1 &&
157 maxWidth != std::numeric_limits<SkScalar>::max() &&
158 !ellipsisStr.isEmpty();
159 // TODO: perform ellipsis work here
160 if (parent->strutEnabled()) {
161 // Make sure font metrics are not less than the strut
162 parent->strutMetrics().updateLineMetrics(fEndLine.metrics(),
163 parent->strutForceHeight());
164 }
165 fMaxIntrinsicWidth = SkMaxScalar(fMaxIntrinsicWidth, fEndLine.width());
166 // TODO: keep start/end/break info for text and runs but in a better way that below
Julia Lavrova5207f352019-06-21 12:22:32 -0400167 TextRange text(fEndLine.startCluster()->textRange().start, fEndLine.endCluster()->textRange().end);
168 TextRange textWithSpaces(fEndLine.startCluster()->textRange().start, fEndLine.breakCluster()->textRange().end);
169 ClusterRange clusters(fEndLine.startCluster() - parent->clusters().begin(), fEndLine.endCluster() - parent->clusters().begin() + 1);
170 addLine(text, textWithSpaces, clusters,
Julia Lavrovaa3552c52019-05-30 16:12:56 -0400171 fEndLine.startPos(),
172 fEndLine.endPos(),
173 SkVector::Make(0, fHeight),
174 SkVector::Make(fEndLine.width(), fEndLine.metrics().height()),
175 fEndLine.metrics(),
176 needEllipsis);
177
178 // Start a new line
179 fHeight += fEndLine.metrics().height();
180
181 trimStartSpaces(end);
182
183 if (needEllipsis || fLineNumber >= maxLines) {
184 break;
185 }
186 ++fLineNumber;
187 }
188
189 if (fHardLineBreak) {
190 // Last character is a line break
191 if (parent->strutEnabled()) {
192 // Make sure font metrics are not less than the strut
193 parent->strutMetrics().updateLineMetrics(fEndLine.metrics(),
194 parent->strutForceHeight());
195 }
Julia Lavrova5207f352019-06-21 12:22:32 -0400196 TextRange empty(fEndLine.breakCluster()->textRange().start, fEndLine.breakCluster()->textRange().start);
197 ClusterRange clusters(fEndLine.breakCluster() - parent->clusters().begin(), fEndLine.breakCluster() - parent->clusters().begin());
198 addLine(empty, empty, clusters,
Julia Lavrovaa3552c52019-05-30 16:12:56 -0400199 0,
200 0,
201 SkVector::Make(0, fHeight),
202 SkVector::Make(0, fEndLine.metrics().height()),
203 fEndLine.metrics(),
204 false);
205 }
206}
207
208} // namespace textlayout
209} // namespace skia