blob: 87a9835ac9190bb4d19a8ee6d86361a28b5c7008 [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()) {
17 break;
18 }
19 if (cluster->width() > maxWidth) {
20 // Break the cluster into parts by glyph position
21 auto delta = maxWidth - (fWords.width() + fClusters.width());
22 fClip.extend(cluster, cluster->roundPos(delta));
23 fTooLongCluster = true;
24 fTooLongWord = true;
25 break;
26 }
27
28 // Walk further to see if there is a too long word, cluster or glyph
29 SkScalar nextWordLength = fClusters.width();
30 for (auto further = cluster; further != endOfClusters; ++further) {
31 if (further->isSoftBreak() || further->isHardBreak()) {
32 break;
33 }
34 nextWordLength += further->width();
35 }
36 if (nextWordLength > maxWidth) {
37 // If the word is too long we can break it right now and hope it's enough
38 fTooLongWord = true;
39 }
40
41 // TODO: this is the place when we use hyphenation
42 fMinIntrinsicWidth = SkTMax(fMinIntrinsicWidth, nextWordLength);
43 break;
44 }
45
46 fClusters.extend(cluster);
47
48 // Keep adding clusters/words
49 if (fClusters.endOfWord()) {
50 fMinIntrinsicWidth = SkTMax(fMinIntrinsicWidth, getClustersTrimmedWidth());
51 fWords.extend(fClusters);
52 }
53
54 if ((fHardLineBreak = cluster->isHardBreak())) {
55 // Stop at the hard line break
56 break;
57 }
58 }
59}
60
61void TextWrapper::moveForward() {
62 do {
63 if (fWords.width() > 0) {
64 fEndLine.extend(fWords);
65 } else if (fClusters.width() > 0) {
66 fEndLine.extend(fClusters);
67 fTooLongWord = false;
68 } else if (fClip.width() > 0) {
69 fEndLine.extend(fClip);
70 fTooLongWord = false;
71 fTooLongCluster = false;
72 } else {
73 break;
74 }
75 } while (fTooLongWord || fTooLongCluster);
76}
77
78// Special case for start/end cluster since they can be clipped
79void TextWrapper::trimEndSpaces(bool includingClusters) {
80 // Remember the breaking position
81 fEndLine.saveBreak();
82 if (includingClusters) {
83 // Move the end of the line to the left
84 for (auto cluster = fEndLine.endCluster();
85 cluster >= fEndLine.startCluster() && cluster->isWhitespaces();
86 --cluster) {
87 fEndLine.trim(cluster);
88 }
89 }
90 fEndLine.trim();
91}
92
93SkScalar TextWrapper::getClustersTrimmedWidth() {
94 // Move the end of the line to the left
95 SkScalar width = fClusters.width();
96 auto cluster = fClusters.endCluster();
97 for (; cluster > fClusters.startCluster() && cluster->isWhitespaces(); --cluster) {
98 width -= cluster->width();
99 }
100 if (cluster >= fClusters.startCluster()) {
101 width -= (cluster->width() - cluster->trimmedWidth(cluster->endPos()));
102 }
103 return width;
104}
105
106// Trim the beginning spaces in case of soft line break
107void TextWrapper::trimStartSpaces(Cluster* endOfClusters) {
108 // Restore the breaking position
109 fEndLine.restoreBreak();
110 fEndLine.nextPos();
111 if (fHardLineBreak) {
112 // End of line is always end of cluster, but need to skip \n
113 fEndLine.startFrom(fEndLine.endCluster(), 0);
114 return;
115 }
116 if (fEndLine.endPos() != 0) {
117 // Clipping
118 fEndLine.startFrom(fEndLine.endCluster(), fEndLine.endPos());
119 return;
120 }
121
122 auto cluster = fEndLine.endCluster();
123 while (cluster < endOfClusters && cluster->isWhitespaces()) {
124 ++cluster;
125 }
126 fEndLine.startFrom(cluster, 0);
127}
128
129void TextWrapper::breakTextIntoLines(ParagraphImpl* parent,
130 SkScalar maxWidth,
131 const AddLineToParagraph& addLine) {
132 auto span = parent->clusters();
133 auto maxLines = parent->paragraphStyle().getMaxLines();
134 auto ellipsisStr = parent->paragraphStyle().getEllipsis();
135 auto textAlign = parent->paragraphStyle().effective_align();
136
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
149 trimEndSpaces(textAlign != TextAlign::kLeft || fEndLine.endCluster() < end - 1);
150
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
167 SkSpan<const char> text(fEndLine.startCluster()->text().begin(),
168 fEndLine.endCluster()->text().end() - fEndLine.startCluster()->text().begin());
169 SkSpan<const char> textWithSpaces(fEndLine.startCluster()->text().begin(),
170 fEndLine.breakCluster()->text().end() - fEndLine.startCluster()->text().begin());
171 addLine(text, textWithSpaces,
172 fEndLine.startCluster(),
173 fEndLine.endCluster(),
174 fEndLine.startPos(),
175 fEndLine.endPos(),
176 SkVector::Make(0, fHeight),
177 SkVector::Make(fEndLine.width(), fEndLine.metrics().height()),
178 fEndLine.metrics(),
179 needEllipsis);
180
181 // Start a new line
182 fHeight += fEndLine.metrics().height();
183
184 trimStartSpaces(end);
185
186 if (needEllipsis || fLineNumber >= maxLines) {
187 break;
188 }
189 ++fLineNumber;
190 }
191
192 if (fHardLineBreak) {
193 // Last character is a line break
194 if (parent->strutEnabled()) {
195 // Make sure font metrics are not less than the strut
196 parent->strutMetrics().updateLineMetrics(fEndLine.metrics(),
197 parent->strutForceHeight());
198 }
199 SkSpan<const char> empty(fEndLine.breakCluster()->text().begin(), 0);
200 addLine(empty, empty,
201 fEndLine.breakCluster(),
202 fEndLine.breakCluster(),
203 0,
204 0,
205 SkVector::Make(0, fHeight),
206 SkVector::Make(0, fEndLine.metrics().height()),
207 fEndLine.metrics(),
208 false);
209 }
210}
211
212} // namespace textlayout
213} // namespace skia