1*c8dee2aaSAndroid Build Coastguard Worker // Copyright 2019 Google LLC.
2*c8dee2aaSAndroid Build Coastguard Worker #include "modules/skparagraph/src/ParagraphImpl.h"
3*c8dee2aaSAndroid Build Coastguard Worker #include "modules/skparagraph/src/TextWrapper.h"
4*c8dee2aaSAndroid Build Coastguard Worker
5*c8dee2aaSAndroid Build Coastguard Worker namespace skia {
6*c8dee2aaSAndroid Build Coastguard Worker namespace textlayout {
7*c8dee2aaSAndroid Build Coastguard Worker
8*c8dee2aaSAndroid Build Coastguard Worker namespace {
9*c8dee2aaSAndroid Build Coastguard Worker struct LineBreakerWithLittleRounding {
LineBreakerWithLittleRoundingskia::textlayout::__anonb82136770111::LineBreakerWithLittleRounding10*c8dee2aaSAndroid Build Coastguard Worker LineBreakerWithLittleRounding(SkScalar maxWidth, bool applyRoundingHack)
11*c8dee2aaSAndroid Build Coastguard Worker : fLower(maxWidth - 0.25f)
12*c8dee2aaSAndroid Build Coastguard Worker , fMaxWidth(maxWidth)
13*c8dee2aaSAndroid Build Coastguard Worker , fUpper(maxWidth + 0.25f)
14*c8dee2aaSAndroid Build Coastguard Worker , fApplyRoundingHack(applyRoundingHack) {}
15*c8dee2aaSAndroid Build Coastguard Worker
breakLineskia::textlayout::__anonb82136770111::LineBreakerWithLittleRounding16*c8dee2aaSAndroid Build Coastguard Worker bool breakLine(SkScalar width) const {
17*c8dee2aaSAndroid Build Coastguard Worker if (width < fLower) {
18*c8dee2aaSAndroid Build Coastguard Worker return false;
19*c8dee2aaSAndroid Build Coastguard Worker } else if (width > fUpper) {
20*c8dee2aaSAndroid Build Coastguard Worker return true;
21*c8dee2aaSAndroid Build Coastguard Worker }
22*c8dee2aaSAndroid Build Coastguard Worker
23*c8dee2aaSAndroid Build Coastguard Worker auto val = std::fabs(width);
24*c8dee2aaSAndroid Build Coastguard Worker SkScalar roundedWidth;
25*c8dee2aaSAndroid Build Coastguard Worker if (fApplyRoundingHack) {
26*c8dee2aaSAndroid Build Coastguard Worker if (val < 10000) {
27*c8dee2aaSAndroid Build Coastguard Worker roundedWidth = SkScalarRoundToScalar(width * 100) * (1.0f/100);
28*c8dee2aaSAndroid Build Coastguard Worker } else if (val < 100000) {
29*c8dee2aaSAndroid Build Coastguard Worker roundedWidth = SkScalarRoundToScalar(width * 10) * (1.0f/10);
30*c8dee2aaSAndroid Build Coastguard Worker } else {
31*c8dee2aaSAndroid Build Coastguard Worker roundedWidth = SkScalarFloorToScalar(width);
32*c8dee2aaSAndroid Build Coastguard Worker }
33*c8dee2aaSAndroid Build Coastguard Worker } else {
34*c8dee2aaSAndroid Build Coastguard Worker if (val < 10000) {
35*c8dee2aaSAndroid Build Coastguard Worker roundedWidth = SkScalarFloorToScalar(width * 100) * (1.0f/100);
36*c8dee2aaSAndroid Build Coastguard Worker } else if (val < 100000) {
37*c8dee2aaSAndroid Build Coastguard Worker roundedWidth = SkScalarFloorToScalar(width * 10) * (1.0f/10);
38*c8dee2aaSAndroid Build Coastguard Worker } else {
39*c8dee2aaSAndroid Build Coastguard Worker roundedWidth = SkScalarFloorToScalar(width);
40*c8dee2aaSAndroid Build Coastguard Worker }
41*c8dee2aaSAndroid Build Coastguard Worker }
42*c8dee2aaSAndroid Build Coastguard Worker return roundedWidth > fMaxWidth;
43*c8dee2aaSAndroid Build Coastguard Worker }
44*c8dee2aaSAndroid Build Coastguard Worker
45*c8dee2aaSAndroid Build Coastguard Worker const SkScalar fLower, fMaxWidth, fUpper;
46*c8dee2aaSAndroid Build Coastguard Worker const bool fApplyRoundingHack;
47*c8dee2aaSAndroid Build Coastguard Worker };
48*c8dee2aaSAndroid Build Coastguard Worker } // namespace
49*c8dee2aaSAndroid Build Coastguard Worker
50*c8dee2aaSAndroid Build Coastguard Worker // Since we allow cluster clipping when they don't fit
51*c8dee2aaSAndroid Build Coastguard Worker // we have to work with stretches - parts of clusters
lookAhead(SkScalar maxWidth,Cluster * endOfClusters,bool applyRoundingHack)52*c8dee2aaSAndroid Build Coastguard Worker void TextWrapper::lookAhead(SkScalar maxWidth, Cluster* endOfClusters, bool applyRoundingHack) {
53*c8dee2aaSAndroid Build Coastguard Worker
54*c8dee2aaSAndroid Build Coastguard Worker reset();
55*c8dee2aaSAndroid Build Coastguard Worker fEndLine.metrics().clean();
56*c8dee2aaSAndroid Build Coastguard Worker fWords.startFrom(fEndLine.startCluster(), fEndLine.startPos());
57*c8dee2aaSAndroid Build Coastguard Worker fClusters.startFrom(fEndLine.startCluster(), fEndLine.startPos());
58*c8dee2aaSAndroid Build Coastguard Worker fClip.startFrom(fEndLine.startCluster(), fEndLine.startPos());
59*c8dee2aaSAndroid Build Coastguard Worker
60*c8dee2aaSAndroid Build Coastguard Worker LineBreakerWithLittleRounding breaker(maxWidth, applyRoundingHack);
61*c8dee2aaSAndroid Build Coastguard Worker Cluster* nextNonBreakingSpace = nullptr;
62*c8dee2aaSAndroid Build Coastguard Worker for (auto cluster = fEndLine.endCluster(); cluster < endOfClusters; ++cluster) {
63*c8dee2aaSAndroid Build Coastguard Worker if (cluster->isHardBreak()) {
64*c8dee2aaSAndroid Build Coastguard Worker } else if (
65*c8dee2aaSAndroid Build Coastguard Worker // TODO: Trying to deal with flutter rounding problem. Must be removed...
66*c8dee2aaSAndroid Build Coastguard Worker SkScalar width = fWords.width() + fClusters.width() + cluster->width();
67*c8dee2aaSAndroid Build Coastguard Worker breaker.breakLine(width)) {
68*c8dee2aaSAndroid Build Coastguard Worker if (cluster->isWhitespaceBreak()) {
69*c8dee2aaSAndroid Build Coastguard Worker // It's the end of the word
70*c8dee2aaSAndroid Build Coastguard Worker fClusters.extend(cluster);
71*c8dee2aaSAndroid Build Coastguard Worker fMinIntrinsicWidth = std::max(fMinIntrinsicWidth, this->getClustersTrimmedWidth());
72*c8dee2aaSAndroid Build Coastguard Worker fWords.extend(fClusters);
73*c8dee2aaSAndroid Build Coastguard Worker continue;
74*c8dee2aaSAndroid Build Coastguard Worker } else if (cluster->run().isPlaceholder()) {
75*c8dee2aaSAndroid Build Coastguard Worker if (!fClusters.empty()) {
76*c8dee2aaSAndroid Build Coastguard Worker // Placeholder ends the previous word
77*c8dee2aaSAndroid Build Coastguard Worker fMinIntrinsicWidth = std::max(fMinIntrinsicWidth, this->getClustersTrimmedWidth());
78*c8dee2aaSAndroid Build Coastguard Worker fWords.extend(fClusters);
79*c8dee2aaSAndroid Build Coastguard Worker }
80*c8dee2aaSAndroid Build Coastguard Worker
81*c8dee2aaSAndroid Build Coastguard Worker if (cluster->width() > maxWidth && fWords.empty()) {
82*c8dee2aaSAndroid Build Coastguard Worker // Placeholder is the only text and it's longer than the line;
83*c8dee2aaSAndroid Build Coastguard Worker // it does not count in fMinIntrinsicWidth
84*c8dee2aaSAndroid Build Coastguard Worker fClusters.extend(cluster);
85*c8dee2aaSAndroid Build Coastguard Worker fTooLongCluster = true;
86*c8dee2aaSAndroid Build Coastguard Worker fTooLongWord = true;
87*c8dee2aaSAndroid Build Coastguard Worker } else {
88*c8dee2aaSAndroid Build Coastguard Worker // Placeholder does not fit the line; it will be considered again on the next line
89*c8dee2aaSAndroid Build Coastguard Worker }
90*c8dee2aaSAndroid Build Coastguard Worker break;
91*c8dee2aaSAndroid Build Coastguard Worker }
92*c8dee2aaSAndroid Build Coastguard Worker
93*c8dee2aaSAndroid Build Coastguard Worker // Walk further to see if there is a too long word, cluster or glyph
94*c8dee2aaSAndroid Build Coastguard Worker SkScalar nextWordLength = fClusters.width();
95*c8dee2aaSAndroid Build Coastguard Worker SkScalar nextShortWordLength = nextWordLength;
96*c8dee2aaSAndroid Build Coastguard Worker for (auto further = cluster; further != endOfClusters; ++further) {
97*c8dee2aaSAndroid Build Coastguard Worker if (further->isSoftBreak() || further->isHardBreak() || further->isWhitespaceBreak()) {
98*c8dee2aaSAndroid Build Coastguard Worker break;
99*c8dee2aaSAndroid Build Coastguard Worker }
100*c8dee2aaSAndroid Build Coastguard Worker if (further->run().isPlaceholder()) {
101*c8dee2aaSAndroid Build Coastguard Worker // Placeholder ends the word
102*c8dee2aaSAndroid Build Coastguard Worker break;
103*c8dee2aaSAndroid Build Coastguard Worker }
104*c8dee2aaSAndroid Build Coastguard Worker
105*c8dee2aaSAndroid Build Coastguard Worker if (nextWordLength > 0 && nextWordLength <= maxWidth && further->isIntraWordBreak()) {
106*c8dee2aaSAndroid Build Coastguard Worker // The cluster is spaces but not the end of the word in a normal sense
107*c8dee2aaSAndroid Build Coastguard Worker nextNonBreakingSpace = further;
108*c8dee2aaSAndroid Build Coastguard Worker nextShortWordLength = nextWordLength;
109*c8dee2aaSAndroid Build Coastguard Worker }
110*c8dee2aaSAndroid Build Coastguard Worker
111*c8dee2aaSAndroid Build Coastguard Worker if (maxWidth == 0) {
112*c8dee2aaSAndroid Build Coastguard Worker // This is a tricky flutter case: layout(width:0) places 1 cluster on each line
113*c8dee2aaSAndroid Build Coastguard Worker nextWordLength = std::max(nextWordLength, further->width());
114*c8dee2aaSAndroid Build Coastguard Worker } else {
115*c8dee2aaSAndroid Build Coastguard Worker nextWordLength += further->width();
116*c8dee2aaSAndroid Build Coastguard Worker }
117*c8dee2aaSAndroid Build Coastguard Worker }
118*c8dee2aaSAndroid Build Coastguard Worker if (nextWordLength > maxWidth) {
119*c8dee2aaSAndroid Build Coastguard Worker if (nextNonBreakingSpace != nullptr) {
120*c8dee2aaSAndroid Build Coastguard Worker // We only get here if the non-breaking space improves our situation
121*c8dee2aaSAndroid Build Coastguard Worker // (allows us to break the text to fit the word)
122*c8dee2aaSAndroid Build Coastguard Worker if (SkScalar shortLength = fWords.width() + nextShortWordLength;
123*c8dee2aaSAndroid Build Coastguard Worker !breaker.breakLine(shortLength)) {
124*c8dee2aaSAndroid Build Coastguard Worker // We can add the short word to the existing line
125*c8dee2aaSAndroid Build Coastguard Worker fClusters = TextStretch(fClusters.startCluster(), nextNonBreakingSpace, fClusters.metrics().getForceStrut());
126*c8dee2aaSAndroid Build Coastguard Worker fMinIntrinsicWidth = std::max(fMinIntrinsicWidth, nextShortWordLength);
127*c8dee2aaSAndroid Build Coastguard Worker fWords.extend(fClusters);
128*c8dee2aaSAndroid Build Coastguard Worker } else {
129*c8dee2aaSAndroid Build Coastguard Worker // We can place the short word on the next line
130*c8dee2aaSAndroid Build Coastguard Worker fClusters.clean();
131*c8dee2aaSAndroid Build Coastguard Worker }
132*c8dee2aaSAndroid Build Coastguard Worker // Either way we are not in "word is too long" situation anymore
133*c8dee2aaSAndroid Build Coastguard Worker break;
134*c8dee2aaSAndroid Build Coastguard Worker }
135*c8dee2aaSAndroid Build Coastguard Worker // If the word is too long we can break it right now and hope it's enough
136*c8dee2aaSAndroid Build Coastguard Worker fMinIntrinsicWidth = std::max(fMinIntrinsicWidth, nextWordLength);
137*c8dee2aaSAndroid Build Coastguard Worker if (fClusters.endPos() - fClusters.startPos() > 1 ||
138*c8dee2aaSAndroid Build Coastguard Worker fWords.empty()) {
139*c8dee2aaSAndroid Build Coastguard Worker fTooLongWord = true;
140*c8dee2aaSAndroid Build Coastguard Worker } else {
141*c8dee2aaSAndroid Build Coastguard Worker // Even if the word is too long there is a very little space on this line.
142*c8dee2aaSAndroid Build Coastguard Worker // let's deal with it on the next line.
143*c8dee2aaSAndroid Build Coastguard Worker }
144*c8dee2aaSAndroid Build Coastguard Worker }
145*c8dee2aaSAndroid Build Coastguard Worker
146*c8dee2aaSAndroid Build Coastguard Worker if (cluster->width() > maxWidth) {
147*c8dee2aaSAndroid Build Coastguard Worker fClusters.extend(cluster);
148*c8dee2aaSAndroid Build Coastguard Worker fTooLongCluster = true;
149*c8dee2aaSAndroid Build Coastguard Worker fTooLongWord = true;
150*c8dee2aaSAndroid Build Coastguard Worker }
151*c8dee2aaSAndroid Build Coastguard Worker break;
152*c8dee2aaSAndroid Build Coastguard Worker }
153*c8dee2aaSAndroid Build Coastguard Worker
154*c8dee2aaSAndroid Build Coastguard Worker if (cluster->run().isPlaceholder()) {
155*c8dee2aaSAndroid Build Coastguard Worker if (!fClusters.empty()) {
156*c8dee2aaSAndroid Build Coastguard Worker // Placeholder ends the previous word (placeholders are ignored in trimming)
157*c8dee2aaSAndroid Build Coastguard Worker fMinIntrinsicWidth = std::max(fMinIntrinsicWidth, getClustersTrimmedWidth());
158*c8dee2aaSAndroid Build Coastguard Worker fWords.extend(fClusters);
159*c8dee2aaSAndroid Build Coastguard Worker }
160*c8dee2aaSAndroid Build Coastguard Worker
161*c8dee2aaSAndroid Build Coastguard Worker // Placeholder is separate word and its width now is counted in minIntrinsicWidth
162*c8dee2aaSAndroid Build Coastguard Worker fMinIntrinsicWidth = std::max(fMinIntrinsicWidth, cluster->width());
163*c8dee2aaSAndroid Build Coastguard Worker fWords.extend(cluster);
164*c8dee2aaSAndroid Build Coastguard Worker } else {
165*c8dee2aaSAndroid Build Coastguard Worker fClusters.extend(cluster);
166*c8dee2aaSAndroid Build Coastguard Worker
167*c8dee2aaSAndroid Build Coastguard Worker // Keep adding clusters/words
168*c8dee2aaSAndroid Build Coastguard Worker if (fClusters.endOfWord()) {
169*c8dee2aaSAndroid Build Coastguard Worker fMinIntrinsicWidth = std::max(fMinIntrinsicWidth, getClustersTrimmedWidth());
170*c8dee2aaSAndroid Build Coastguard Worker fWords.extend(fClusters);
171*c8dee2aaSAndroid Build Coastguard Worker }
172*c8dee2aaSAndroid Build Coastguard Worker }
173*c8dee2aaSAndroid Build Coastguard Worker
174*c8dee2aaSAndroid Build Coastguard Worker if ((fHardLineBreak = cluster->isHardBreak())) {
175*c8dee2aaSAndroid Build Coastguard Worker // Stop at the hard line break
176*c8dee2aaSAndroid Build Coastguard Worker break;
177*c8dee2aaSAndroid Build Coastguard Worker }
178*c8dee2aaSAndroid Build Coastguard Worker }
179*c8dee2aaSAndroid Build Coastguard Worker }
180*c8dee2aaSAndroid Build Coastguard Worker
moveForward(bool hasEllipsis)181*c8dee2aaSAndroid Build Coastguard Worker void TextWrapper::moveForward(bool hasEllipsis) {
182*c8dee2aaSAndroid Build Coastguard Worker
183*c8dee2aaSAndroid Build Coastguard Worker // We normally break lines by words.
184*c8dee2aaSAndroid Build Coastguard Worker // The only way we may go to clusters is if the word is too long or
185*c8dee2aaSAndroid Build Coastguard Worker // it's the first word and it has an ellipsis attached to it.
186*c8dee2aaSAndroid Build Coastguard Worker // If nothing fits we show the clipping.
187*c8dee2aaSAndroid Build Coastguard Worker if (!fWords.empty()) {
188*c8dee2aaSAndroid Build Coastguard Worker fEndLine.extend(fWords);
189*c8dee2aaSAndroid Build Coastguard Worker #ifdef SK_IGNORE_SKPARAGRAPH_ELLIPSIS_FIX
190*c8dee2aaSAndroid Build Coastguard Worker if (!fTooLongWord || hasEllipsis) { // Ellipsis added to a word
191*c8dee2aaSAndroid Build Coastguard Worker #else
192*c8dee2aaSAndroid Build Coastguard Worker if (!fTooLongWord && !hasEllipsis) { // Ellipsis added to a grapheme
193*c8dee2aaSAndroid Build Coastguard Worker #endif
194*c8dee2aaSAndroid Build Coastguard Worker return;
195*c8dee2aaSAndroid Build Coastguard Worker }
196*c8dee2aaSAndroid Build Coastguard Worker }
197*c8dee2aaSAndroid Build Coastguard Worker if (!fClusters.empty()) {
198*c8dee2aaSAndroid Build Coastguard Worker fEndLine.extend(fClusters);
199*c8dee2aaSAndroid Build Coastguard Worker if (!fTooLongCluster) {
200*c8dee2aaSAndroid Build Coastguard Worker return;
201*c8dee2aaSAndroid Build Coastguard Worker }
202*c8dee2aaSAndroid Build Coastguard Worker }
203*c8dee2aaSAndroid Build Coastguard Worker
204*c8dee2aaSAndroid Build Coastguard Worker if (!fClip.empty()) {
205*c8dee2aaSAndroid Build Coastguard Worker // Flutter: forget the clipped cluster but keep the metrics
206*c8dee2aaSAndroid Build Coastguard Worker fEndLine.metrics().add(fClip.metrics());
207*c8dee2aaSAndroid Build Coastguard Worker }
208*c8dee2aaSAndroid Build Coastguard Worker }
209*c8dee2aaSAndroid Build Coastguard Worker
210*c8dee2aaSAndroid Build Coastguard Worker // Special case for start/end cluster since they can be clipped
211*c8dee2aaSAndroid Build Coastguard Worker void TextWrapper::trimEndSpaces(TextAlign align) {
212*c8dee2aaSAndroid Build Coastguard Worker // Remember the breaking position
213*c8dee2aaSAndroid Build Coastguard Worker fEndLine.saveBreak();
214*c8dee2aaSAndroid Build Coastguard Worker // Skip all space cluster at the end
215*c8dee2aaSAndroid Build Coastguard Worker for (auto cluster = fEndLine.endCluster();
216*c8dee2aaSAndroid Build Coastguard Worker cluster >= fEndLine.startCluster() && cluster->isWhitespaceBreak();
217*c8dee2aaSAndroid Build Coastguard Worker --cluster) {
218*c8dee2aaSAndroid Build Coastguard Worker fEndLine.trim(cluster);
219*c8dee2aaSAndroid Build Coastguard Worker }
220*c8dee2aaSAndroid Build Coastguard Worker fEndLine.trim();
221*c8dee2aaSAndroid Build Coastguard Worker }
222*c8dee2aaSAndroid Build Coastguard Worker
223*c8dee2aaSAndroid Build Coastguard Worker SkScalar TextWrapper::getClustersTrimmedWidth() {
224*c8dee2aaSAndroid Build Coastguard Worker // Move the end of the line to the left
225*c8dee2aaSAndroid Build Coastguard Worker SkScalar width = 0;
226*c8dee2aaSAndroid Build Coastguard Worker bool trailingSpaces = true;
227*c8dee2aaSAndroid Build Coastguard Worker for (auto cluster = fClusters.endCluster(); cluster >= fClusters.startCluster(); --cluster) {
228*c8dee2aaSAndroid Build Coastguard Worker if (cluster->run().isPlaceholder()) {
229*c8dee2aaSAndroid Build Coastguard Worker continue;
230*c8dee2aaSAndroid Build Coastguard Worker }
231*c8dee2aaSAndroid Build Coastguard Worker if (trailingSpaces) {
232*c8dee2aaSAndroid Build Coastguard Worker if (!cluster->isWhitespaceBreak()) {
233*c8dee2aaSAndroid Build Coastguard Worker width += cluster->trimmedWidth(cluster->endPos());
234*c8dee2aaSAndroid Build Coastguard Worker trailingSpaces = false;
235*c8dee2aaSAndroid Build Coastguard Worker }
236*c8dee2aaSAndroid Build Coastguard Worker continue;
237*c8dee2aaSAndroid Build Coastguard Worker }
238*c8dee2aaSAndroid Build Coastguard Worker width += cluster->width();
239*c8dee2aaSAndroid Build Coastguard Worker }
240*c8dee2aaSAndroid Build Coastguard Worker return width;
241*c8dee2aaSAndroid Build Coastguard Worker }
242*c8dee2aaSAndroid Build Coastguard Worker
243*c8dee2aaSAndroid Build Coastguard Worker // Trim the beginning spaces in case of soft line break
244*c8dee2aaSAndroid Build Coastguard Worker std::tuple<Cluster*, size_t, SkScalar> TextWrapper::trimStartSpaces(Cluster* endOfClusters) {
245*c8dee2aaSAndroid Build Coastguard Worker
246*c8dee2aaSAndroid Build Coastguard Worker if (fHardLineBreak) {
247*c8dee2aaSAndroid Build Coastguard Worker // End of line is always end of cluster, but need to skip \n
248*c8dee2aaSAndroid Build Coastguard Worker auto width = fEndLine.width();
249*c8dee2aaSAndroid Build Coastguard Worker auto cluster = fEndLine.endCluster() + 1;
250*c8dee2aaSAndroid Build Coastguard Worker while (cluster < fEndLine.breakCluster() && cluster->isWhitespaceBreak()) {
251*c8dee2aaSAndroid Build Coastguard Worker width += cluster->width();
252*c8dee2aaSAndroid Build Coastguard Worker ++cluster;
253*c8dee2aaSAndroid Build Coastguard Worker }
254*c8dee2aaSAndroid Build Coastguard Worker return std::make_tuple(fEndLine.breakCluster() + 1, 0, width);
255*c8dee2aaSAndroid Build Coastguard Worker }
256*c8dee2aaSAndroid Build Coastguard Worker
257*c8dee2aaSAndroid Build Coastguard Worker // breakCluster points to the end of the line;
258*c8dee2aaSAndroid Build Coastguard Worker // It's a soft line break so we need to move lineStart forward skipping all the spaces
259*c8dee2aaSAndroid Build Coastguard Worker auto width = fEndLine.widthWithGhostSpaces();
260*c8dee2aaSAndroid Build Coastguard Worker auto cluster = fEndLine.breakCluster() + 1;
261*c8dee2aaSAndroid Build Coastguard Worker while (cluster < endOfClusters && cluster->isWhitespaceBreak()) {
262*c8dee2aaSAndroid Build Coastguard Worker width += cluster->width();
263*c8dee2aaSAndroid Build Coastguard Worker ++cluster;
264*c8dee2aaSAndroid Build Coastguard Worker }
265*c8dee2aaSAndroid Build Coastguard Worker
266*c8dee2aaSAndroid Build Coastguard Worker if (fEndLine.breakCluster()->isWhitespaceBreak() && fEndLine.breakCluster() < endOfClusters) {
267*c8dee2aaSAndroid Build Coastguard Worker // In case of a soft line break by the whitespace
268*c8dee2aaSAndroid Build Coastguard Worker // fBreak should point to the beginning of the next line
269*c8dee2aaSAndroid Build Coastguard Worker // (it only matters when there are trailing spaces)
270*c8dee2aaSAndroid Build Coastguard Worker fEndLine.shiftBreak();
271*c8dee2aaSAndroid Build Coastguard Worker }
272*c8dee2aaSAndroid Build Coastguard Worker
273*c8dee2aaSAndroid Build Coastguard Worker return std::make_tuple(cluster, 0, width);
274*c8dee2aaSAndroid Build Coastguard Worker }
275*c8dee2aaSAndroid Build Coastguard Worker
276*c8dee2aaSAndroid Build Coastguard Worker // TODO: refactor the code for line ending (with/without ellipsis)
277*c8dee2aaSAndroid Build Coastguard Worker void TextWrapper::breakTextIntoLines(ParagraphImpl* parent,
278*c8dee2aaSAndroid Build Coastguard Worker SkScalar maxWidth,
279*c8dee2aaSAndroid Build Coastguard Worker const AddLineToParagraph& addLine) {
280*c8dee2aaSAndroid Build Coastguard Worker fHeight = 0;
281*c8dee2aaSAndroid Build Coastguard Worker fMinIntrinsicWidth = std::numeric_limits<SkScalar>::min();
282*c8dee2aaSAndroid Build Coastguard Worker fMaxIntrinsicWidth = std::numeric_limits<SkScalar>::min();
283*c8dee2aaSAndroid Build Coastguard Worker
284*c8dee2aaSAndroid Build Coastguard Worker auto span = parent->clusters();
285*c8dee2aaSAndroid Build Coastguard Worker if (span.empty()) {
286*c8dee2aaSAndroid Build Coastguard Worker return;
287*c8dee2aaSAndroid Build Coastguard Worker }
288*c8dee2aaSAndroid Build Coastguard Worker auto maxLines = parent->paragraphStyle().getMaxLines();
289*c8dee2aaSAndroid Build Coastguard Worker auto align = parent->paragraphStyle().effective_align();
290*c8dee2aaSAndroid Build Coastguard Worker auto unlimitedLines = maxLines == std::numeric_limits<size_t>::max();
291*c8dee2aaSAndroid Build Coastguard Worker auto endlessLine = !SkIsFinite(maxWidth);
292*c8dee2aaSAndroid Build Coastguard Worker auto hasEllipsis = parent->paragraphStyle().ellipsized();
293*c8dee2aaSAndroid Build Coastguard Worker
294*c8dee2aaSAndroid Build Coastguard Worker auto disableFirstAscent = parent->paragraphStyle().getTextHeightBehavior() & TextHeightBehavior::kDisableFirstAscent;
295*c8dee2aaSAndroid Build Coastguard Worker auto disableLastDescent = parent->paragraphStyle().getTextHeightBehavior() & TextHeightBehavior::kDisableLastDescent;
296*c8dee2aaSAndroid Build Coastguard Worker bool firstLine = true; // We only interested in fist line if we have to disable the first ascent
297*c8dee2aaSAndroid Build Coastguard Worker
298*c8dee2aaSAndroid Build Coastguard Worker SkScalar softLineMaxIntrinsicWidth = 0;
299*c8dee2aaSAndroid Build Coastguard Worker fEndLine = TextStretch(span.begin(), span.begin(), parent->strutForceHeight());
300*c8dee2aaSAndroid Build Coastguard Worker auto end = span.end() - 1;
301*c8dee2aaSAndroid Build Coastguard Worker auto start = span.begin();
302*c8dee2aaSAndroid Build Coastguard Worker InternalLineMetrics maxRunMetrics;
303*c8dee2aaSAndroid Build Coastguard Worker bool needEllipsis = false;
304*c8dee2aaSAndroid Build Coastguard Worker while (fEndLine.endCluster() != end) {
305*c8dee2aaSAndroid Build Coastguard Worker
306*c8dee2aaSAndroid Build Coastguard Worker this->lookAhead(maxWidth, end, parent->getApplyRoundingHack());
307*c8dee2aaSAndroid Build Coastguard Worker
308*c8dee2aaSAndroid Build Coastguard Worker auto lastLine = (hasEllipsis && unlimitedLines) || fLineNumber >= maxLines;
309*c8dee2aaSAndroid Build Coastguard Worker needEllipsis = hasEllipsis && !endlessLine && lastLine;
310*c8dee2aaSAndroid Build Coastguard Worker
311*c8dee2aaSAndroid Build Coastguard Worker this->moveForward(needEllipsis);
312*c8dee2aaSAndroid Build Coastguard Worker needEllipsis &= fEndLine.endCluster() < end - 1; // Only if we have some text to ellipsize
313*c8dee2aaSAndroid Build Coastguard Worker
314*c8dee2aaSAndroid Build Coastguard Worker // Do not trim end spaces on the naturally last line of the left aligned text
315*c8dee2aaSAndroid Build Coastguard Worker this->trimEndSpaces(align);
316*c8dee2aaSAndroid Build Coastguard Worker
317*c8dee2aaSAndroid Build Coastguard Worker // For soft line breaks add to the line all the spaces next to it
318*c8dee2aaSAndroid Build Coastguard Worker Cluster* startLine;
319*c8dee2aaSAndroid Build Coastguard Worker size_t pos;
320*c8dee2aaSAndroid Build Coastguard Worker SkScalar widthWithSpaces;
321*c8dee2aaSAndroid Build Coastguard Worker std::tie(startLine, pos, widthWithSpaces) = this->trimStartSpaces(end);
322*c8dee2aaSAndroid Build Coastguard Worker
323*c8dee2aaSAndroid Build Coastguard Worker if (needEllipsis && !fHardLineBreak) {
324*c8dee2aaSAndroid Build Coastguard Worker // This is what we need to do to preserve a space before the ellipsis
325*c8dee2aaSAndroid Build Coastguard Worker fEndLine.restoreBreak();
326*c8dee2aaSAndroid Build Coastguard Worker widthWithSpaces = fEndLine.widthWithGhostSpaces();
327*c8dee2aaSAndroid Build Coastguard Worker }
328*c8dee2aaSAndroid Build Coastguard Worker
329*c8dee2aaSAndroid Build Coastguard Worker // If the line is empty with the hard line break, let's take the paragraph font (flutter???)
330*c8dee2aaSAndroid Build Coastguard Worker if (fEndLine.metrics().isClean()) {
331*c8dee2aaSAndroid Build Coastguard Worker fEndLine.setMetrics(parent->getEmptyMetrics());
332*c8dee2aaSAndroid Build Coastguard Worker }
333*c8dee2aaSAndroid Build Coastguard Worker
334*c8dee2aaSAndroid Build Coastguard Worker // Deal with placeholder clusters == runs[@size==1]
335*c8dee2aaSAndroid Build Coastguard Worker Run* lastRun = nullptr;
336*c8dee2aaSAndroid Build Coastguard Worker for (auto cluster = fEndLine.startCluster(); cluster <= fEndLine.endCluster(); ++cluster) {
337*c8dee2aaSAndroid Build Coastguard Worker auto r = cluster->runOrNull();
338*c8dee2aaSAndroid Build Coastguard Worker if (r == lastRun) {
339*c8dee2aaSAndroid Build Coastguard Worker continue;
340*c8dee2aaSAndroid Build Coastguard Worker }
341*c8dee2aaSAndroid Build Coastguard Worker lastRun = r;
342*c8dee2aaSAndroid Build Coastguard Worker if (lastRun->placeholderStyle() != nullptr) {
343*c8dee2aaSAndroid Build Coastguard Worker SkASSERT(lastRun->size() == 1);
344*c8dee2aaSAndroid Build Coastguard Worker // Update the placeholder metrics so we can get the placeholder positions later
345*c8dee2aaSAndroid Build Coastguard Worker // and the line metrics (to make sure the placeholder fits)
346*c8dee2aaSAndroid Build Coastguard Worker lastRun->updateMetrics(&fEndLine.metrics());
347*c8dee2aaSAndroid Build Coastguard Worker }
348*c8dee2aaSAndroid Build Coastguard Worker }
349*c8dee2aaSAndroid Build Coastguard Worker
350*c8dee2aaSAndroid Build Coastguard Worker // Before we update the line metrics with struts,
351*c8dee2aaSAndroid Build Coastguard Worker // let's save it for GetRectsForRange(RectHeightStyle::kMax)
352*c8dee2aaSAndroid Build Coastguard Worker maxRunMetrics = fEndLine.metrics();
353*c8dee2aaSAndroid Build Coastguard Worker maxRunMetrics.fForceStrut = false;
354*c8dee2aaSAndroid Build Coastguard Worker
355*c8dee2aaSAndroid Build Coastguard Worker // TODO: keep start/end/break info for text and runs but in a better way that below
356*c8dee2aaSAndroid Build Coastguard Worker TextRange textExcludingSpaces(fEndLine.startCluster()->textRange().start, fEndLine.endCluster()->textRange().end);
357*c8dee2aaSAndroid Build Coastguard Worker TextRange text(fEndLine.startCluster()->textRange().start, fEndLine.breakCluster()->textRange().start);
358*c8dee2aaSAndroid Build Coastguard Worker TextRange textIncludingNewlines(fEndLine.startCluster()->textRange().start, startLine->textRange().start);
359*c8dee2aaSAndroid Build Coastguard Worker if (startLine == end) {
360*c8dee2aaSAndroid Build Coastguard Worker textIncludingNewlines.end = parent->text().size();
361*c8dee2aaSAndroid Build Coastguard Worker text.end = parent->text().size();
362*c8dee2aaSAndroid Build Coastguard Worker }
363*c8dee2aaSAndroid Build Coastguard Worker ClusterRange clusters(fEndLine.startCluster() - start, fEndLine.endCluster() - start + 1);
364*c8dee2aaSAndroid Build Coastguard Worker ClusterRange clustersWithGhosts(fEndLine.startCluster() - start, startLine - start);
365*c8dee2aaSAndroid Build Coastguard Worker
366*c8dee2aaSAndroid Build Coastguard Worker if (disableFirstAscent && firstLine) {
367*c8dee2aaSAndroid Build Coastguard Worker fEndLine.metrics().fAscent = fEndLine.metrics().fRawAscent;
368*c8dee2aaSAndroid Build Coastguard Worker }
369*c8dee2aaSAndroid Build Coastguard Worker if (disableLastDescent && (lastLine || (startLine == end && !fHardLineBreak ))) {
370*c8dee2aaSAndroid Build Coastguard Worker fEndLine.metrics().fDescent = fEndLine.metrics().fRawDescent;
371*c8dee2aaSAndroid Build Coastguard Worker }
372*c8dee2aaSAndroid Build Coastguard Worker
373*c8dee2aaSAndroid Build Coastguard Worker if (parent->strutEnabled()) {
374*c8dee2aaSAndroid Build Coastguard Worker // Make sure font metrics are not less than the strut
375*c8dee2aaSAndroid Build Coastguard Worker parent->strutMetrics().updateLineMetrics(fEndLine.metrics());
376*c8dee2aaSAndroid Build Coastguard Worker }
377*c8dee2aaSAndroid Build Coastguard Worker
378*c8dee2aaSAndroid Build Coastguard Worker SkScalar lineHeight = fEndLine.metrics().height();
379*c8dee2aaSAndroid Build Coastguard Worker firstLine = false;
380*c8dee2aaSAndroid Build Coastguard Worker
381*c8dee2aaSAndroid Build Coastguard Worker if (fEndLine.empty()) {
382*c8dee2aaSAndroid Build Coastguard Worker // Correct text and clusters (make it empty for an empty line)
383*c8dee2aaSAndroid Build Coastguard Worker textExcludingSpaces.end = textExcludingSpaces.start;
384*c8dee2aaSAndroid Build Coastguard Worker clusters.end = clusters.start;
385*c8dee2aaSAndroid Build Coastguard Worker }
386*c8dee2aaSAndroid Build Coastguard Worker
387*c8dee2aaSAndroid Build Coastguard Worker // In case of a force wrapping we don't have a break cluster and have to use the end cluster
388*c8dee2aaSAndroid Build Coastguard Worker text.end = std::max(text.end, textExcludingSpaces.end);
389*c8dee2aaSAndroid Build Coastguard Worker
390*c8dee2aaSAndroid Build Coastguard Worker addLine(textExcludingSpaces,
391*c8dee2aaSAndroid Build Coastguard Worker text,
392*c8dee2aaSAndroid Build Coastguard Worker textIncludingNewlines, clusters, clustersWithGhosts, widthWithSpaces,
393*c8dee2aaSAndroid Build Coastguard Worker fEndLine.startPos(),
394*c8dee2aaSAndroid Build Coastguard Worker fEndLine.endPos(),
395*c8dee2aaSAndroid Build Coastguard Worker SkVector::Make(0, fHeight),
396*c8dee2aaSAndroid Build Coastguard Worker SkVector::Make(fEndLine.width(), lineHeight),
397*c8dee2aaSAndroid Build Coastguard Worker fEndLine.metrics(),
398*c8dee2aaSAndroid Build Coastguard Worker needEllipsis && !fHardLineBreak);
399*c8dee2aaSAndroid Build Coastguard Worker
400*c8dee2aaSAndroid Build Coastguard Worker softLineMaxIntrinsicWidth += widthWithSpaces;
401*c8dee2aaSAndroid Build Coastguard Worker
402*c8dee2aaSAndroid Build Coastguard Worker fMaxIntrinsicWidth = std::max(fMaxIntrinsicWidth, softLineMaxIntrinsicWidth);
403*c8dee2aaSAndroid Build Coastguard Worker if (fHardLineBreak) {
404*c8dee2aaSAndroid Build Coastguard Worker softLineMaxIntrinsicWidth = 0;
405*c8dee2aaSAndroid Build Coastguard Worker }
406*c8dee2aaSAndroid Build Coastguard Worker // Start a new line
407*c8dee2aaSAndroid Build Coastguard Worker fHeight += lineHeight;
408*c8dee2aaSAndroid Build Coastguard Worker if (!fHardLineBreak || startLine != end) {
409*c8dee2aaSAndroid Build Coastguard Worker fEndLine.clean();
410*c8dee2aaSAndroid Build Coastguard Worker }
411*c8dee2aaSAndroid Build Coastguard Worker fEndLine.startFrom(startLine, pos);
412*c8dee2aaSAndroid Build Coastguard Worker parent->fMaxWidthWithTrailingSpaces = std::max(parent->fMaxWidthWithTrailingSpaces, widthWithSpaces);
413*c8dee2aaSAndroid Build Coastguard Worker
414*c8dee2aaSAndroid Build Coastguard Worker if (hasEllipsis && unlimitedLines) {
415*c8dee2aaSAndroid Build Coastguard Worker // There is one case when we need an ellipsis on a separate line
416*c8dee2aaSAndroid Build Coastguard Worker // after a line break when width is infinite
417*c8dee2aaSAndroid Build Coastguard Worker if (!fHardLineBreak) {
418*c8dee2aaSAndroid Build Coastguard Worker break;
419*c8dee2aaSAndroid Build Coastguard Worker }
420*c8dee2aaSAndroid Build Coastguard Worker } else if (lastLine) {
421*c8dee2aaSAndroid Build Coastguard Worker // There is nothing more to draw
422*c8dee2aaSAndroid Build Coastguard Worker fHardLineBreak = false;
423*c8dee2aaSAndroid Build Coastguard Worker break;
424*c8dee2aaSAndroid Build Coastguard Worker }
425*c8dee2aaSAndroid Build Coastguard Worker
426*c8dee2aaSAndroid Build Coastguard Worker ++fLineNumber;
427*c8dee2aaSAndroid Build Coastguard Worker }
428*c8dee2aaSAndroid Build Coastguard Worker
429*c8dee2aaSAndroid Build Coastguard Worker // We finished formatting the text but we need to scan the rest for some numbers
430*c8dee2aaSAndroid Build Coastguard Worker // TODO: make it a case of a normal flow
431*c8dee2aaSAndroid Build Coastguard Worker if (fEndLine.endCluster() != nullptr) {
432*c8dee2aaSAndroid Build Coastguard Worker auto lastWordLength = 0.0f;
433*c8dee2aaSAndroid Build Coastguard Worker auto cluster = fEndLine.endCluster();
434*c8dee2aaSAndroid Build Coastguard Worker while (cluster != end || cluster->endPos() < end->endPos()) {
435*c8dee2aaSAndroid Build Coastguard Worker fExceededMaxLines = true;
436*c8dee2aaSAndroid Build Coastguard Worker if (cluster->isHardBreak()) {
437*c8dee2aaSAndroid Build Coastguard Worker // Hard line break ends the word and the line
438*c8dee2aaSAndroid Build Coastguard Worker fMaxIntrinsicWidth = std::max(fMaxIntrinsicWidth, softLineMaxIntrinsicWidth);
439*c8dee2aaSAndroid Build Coastguard Worker softLineMaxIntrinsicWidth = 0;
440*c8dee2aaSAndroid Build Coastguard Worker fMinIntrinsicWidth = std::max(fMinIntrinsicWidth, lastWordLength);
441*c8dee2aaSAndroid Build Coastguard Worker lastWordLength = 0;
442*c8dee2aaSAndroid Build Coastguard Worker } else if (cluster->isWhitespaceBreak()) {
443*c8dee2aaSAndroid Build Coastguard Worker // Whitespaces end the word
444*c8dee2aaSAndroid Build Coastguard Worker softLineMaxIntrinsicWidth += cluster->width();
445*c8dee2aaSAndroid Build Coastguard Worker fMinIntrinsicWidth = std::max(fMinIntrinsicWidth, lastWordLength);
446*c8dee2aaSAndroid Build Coastguard Worker lastWordLength = 0;
447*c8dee2aaSAndroid Build Coastguard Worker } else if (cluster->run().isPlaceholder()) {
448*c8dee2aaSAndroid Build Coastguard Worker // Placeholder ends the previous word and creates a separate one
449*c8dee2aaSAndroid Build Coastguard Worker fMinIntrinsicWidth = std::max(fMinIntrinsicWidth, lastWordLength);
450*c8dee2aaSAndroid Build Coastguard Worker // Placeholder width now counts in fMinIntrinsicWidth
451*c8dee2aaSAndroid Build Coastguard Worker softLineMaxIntrinsicWidth += cluster->width();
452*c8dee2aaSAndroid Build Coastguard Worker fMinIntrinsicWidth = std::max(fMinIntrinsicWidth, cluster->width());
453*c8dee2aaSAndroid Build Coastguard Worker lastWordLength = 0;
454*c8dee2aaSAndroid Build Coastguard Worker } else {
455*c8dee2aaSAndroid Build Coastguard Worker // Nothing out of ordinary - just add this cluster to the word and to the line
456*c8dee2aaSAndroid Build Coastguard Worker softLineMaxIntrinsicWidth += cluster->width();
457*c8dee2aaSAndroid Build Coastguard Worker lastWordLength += cluster->width();
458*c8dee2aaSAndroid Build Coastguard Worker }
459*c8dee2aaSAndroid Build Coastguard Worker ++cluster;
460*c8dee2aaSAndroid Build Coastguard Worker }
461*c8dee2aaSAndroid Build Coastguard Worker fMinIntrinsicWidth = std::max(fMinIntrinsicWidth, lastWordLength);
462*c8dee2aaSAndroid Build Coastguard Worker fMaxIntrinsicWidth = std::max(fMaxIntrinsicWidth, softLineMaxIntrinsicWidth);
463*c8dee2aaSAndroid Build Coastguard Worker
464*c8dee2aaSAndroid Build Coastguard Worker if (parent->lines().empty()) {
465*c8dee2aaSAndroid Build Coastguard Worker // In case we could not place even a single cluster on the line
466*c8dee2aaSAndroid Build Coastguard Worker if (disableFirstAscent) {
467*c8dee2aaSAndroid Build Coastguard Worker fEndLine.metrics().fAscent = fEndLine.metrics().fRawAscent;
468*c8dee2aaSAndroid Build Coastguard Worker }
469*c8dee2aaSAndroid Build Coastguard Worker if (disableLastDescent && !fHardLineBreak) {
470*c8dee2aaSAndroid Build Coastguard Worker fEndLine.metrics().fDescent = fEndLine.metrics().fRawDescent;
471*c8dee2aaSAndroid Build Coastguard Worker }
472*c8dee2aaSAndroid Build Coastguard Worker fHeight = std::max(fHeight, fEndLine.metrics().height());
473*c8dee2aaSAndroid Build Coastguard Worker }
474*c8dee2aaSAndroid Build Coastguard Worker }
475*c8dee2aaSAndroid Build Coastguard Worker
476*c8dee2aaSAndroid Build Coastguard Worker if (fHardLineBreak) {
477*c8dee2aaSAndroid Build Coastguard Worker if (disableLastDescent) {
478*c8dee2aaSAndroid Build Coastguard Worker fEndLine.metrics().fDescent = fEndLine.metrics().fRawDescent;
479*c8dee2aaSAndroid Build Coastguard Worker }
480*c8dee2aaSAndroid Build Coastguard Worker
481*c8dee2aaSAndroid Build Coastguard Worker // Last character is a line break
482*c8dee2aaSAndroid Build Coastguard Worker if (parent->strutEnabled()) {
483*c8dee2aaSAndroid Build Coastguard Worker // Make sure font metrics are not less than the strut
484*c8dee2aaSAndroid Build Coastguard Worker parent->strutMetrics().updateLineMetrics(fEndLine.metrics());
485*c8dee2aaSAndroid Build Coastguard Worker }
486*c8dee2aaSAndroid Build Coastguard Worker
487*c8dee2aaSAndroid Build Coastguard Worker ClusterRange clusters(fEndLine.breakCluster() - start, fEndLine.endCluster() - start);
488*c8dee2aaSAndroid Build Coastguard Worker addLine(fEndLine.breakCluster()->textRange(),
489*c8dee2aaSAndroid Build Coastguard Worker fEndLine.breakCluster()->textRange(),
490*c8dee2aaSAndroid Build Coastguard Worker fEndLine.endCluster()->textRange(),
491*c8dee2aaSAndroid Build Coastguard Worker clusters,
492*c8dee2aaSAndroid Build Coastguard Worker clusters,
493*c8dee2aaSAndroid Build Coastguard Worker 0,
494*c8dee2aaSAndroid Build Coastguard Worker 0,
495*c8dee2aaSAndroid Build Coastguard Worker 0,
496*c8dee2aaSAndroid Build Coastguard Worker SkVector::Make(0, fHeight),
497*c8dee2aaSAndroid Build Coastguard Worker SkVector::Make(0, fEndLine.metrics().height()),
498*c8dee2aaSAndroid Build Coastguard Worker fEndLine.metrics(),
499*c8dee2aaSAndroid Build Coastguard Worker needEllipsis);
500*c8dee2aaSAndroid Build Coastguard Worker fHeight += fEndLine.metrics().height();
501*c8dee2aaSAndroid Build Coastguard Worker parent->lines().back().setMaxRunMetrics(maxRunMetrics);
502*c8dee2aaSAndroid Build Coastguard Worker }
503*c8dee2aaSAndroid Build Coastguard Worker
504*c8dee2aaSAndroid Build Coastguard Worker if (parent->lines().empty()) {
505*c8dee2aaSAndroid Build Coastguard Worker return;
506*c8dee2aaSAndroid Build Coastguard Worker }
507*c8dee2aaSAndroid Build Coastguard Worker // Correct line metric styles for the first and for the last lines if needed
508*c8dee2aaSAndroid Build Coastguard Worker if (disableFirstAscent) {
509*c8dee2aaSAndroid Build Coastguard Worker parent->lines().front().setAscentStyle(LineMetricStyle::Typographic);
510*c8dee2aaSAndroid Build Coastguard Worker }
511*c8dee2aaSAndroid Build Coastguard Worker if (disableLastDescent) {
512*c8dee2aaSAndroid Build Coastguard Worker parent->lines().back().setDescentStyle(LineMetricStyle::Typographic);
513*c8dee2aaSAndroid Build Coastguard Worker }
514*c8dee2aaSAndroid Build Coastguard Worker }
515*c8dee2aaSAndroid Build Coastguard Worker
516*c8dee2aaSAndroid Build Coastguard Worker } // namespace textlayout
517*c8dee2aaSAndroid Build Coastguard Worker } // namespace skia
518