@@ -72,94 +72,160 @@ private static int getFirstLetterEnd(String text, int start) {
72
72
return end ;
73
73
}
74
74
75
- public static LineBreakResult breakText (LayoutContext c ,
76
- LineBreakContext context , int avail ,
77
- CalculatedStyle style , boolean tryToBreakAnywhere , int lineWidth ) {
78
-
75
+ public enum BreakTextResult {
76
+ /**
77
+ * Has completely consumed the string.
78
+ */
79
+ FINISHED ,
80
+
81
+ /**
82
+ * In char breaking mode, need a newline before continuing.
83
+ * At least one character has been consumed.
84
+ */
85
+ CONTINUE_CHAR_BREAKING_ON_NL ,
86
+
87
+ /**
88
+ * In word breaking mode, need a newline before continuing.
89
+ * At least one word has been consumed.
90
+ */
91
+ CONTINUE_WORD_BREAKING_ON_NL ,
92
+
93
+ /**
94
+ * Not a single char fitted, but we consumed one character anyway.
95
+ */
96
+ CHAR_UNBREAKABLE_BUT_CONSUMED ,
97
+
98
+ /**
99
+ * Not a single word fitted, but we consumed a word anyway.
100
+ * Only returned when word-wrap is not break-word.
101
+ */
102
+ WORD_UNBREAKABLE_BUT_CONSUMED ,
103
+
104
+ /**
105
+ * DANGER: Last char did not fit and we are directing the
106
+ * parent method to reconsume char on a newline.
107
+ * Example, below floats.
108
+ */
109
+ DANGER_RECONSUME_CHAR_ON_NL ,
110
+
111
+ /**
112
+ * DANGER: Last word did not fit and we are directing the
113
+ * parent method to reconsume word on a newline.
114
+ * Example, below floats.
115
+ */
116
+ DANGER_RECONSUME_WORD_ON_NL ;
117
+ }
118
+
119
+ public static BreakTextResult breakText (
120
+ LayoutContext c ,
121
+ LineBreakContext context ,
122
+ int avail ,
123
+ CalculatedStyle style ,
124
+ boolean tryToBreakAnywhere ,
125
+ int lineWidth ,
126
+ boolean forceOutput ) {
127
+
79
128
FSFont font = style .getFSFont (c );
80
129
IdentValue whitespace = style .getWhitespace ();
81
- float letterSpacing = style .hasLetterSpacing () ?
130
+ float letterSpacing = style .hasLetterSpacing () ?
82
131
style .getFloatPropertyProportionalWidth (CSSName .LETTER_SPACING , 0 , c ) : 0f ;
83
132
84
133
// ====== handle nowrap
85
134
if (whitespace == IdentValue .NOWRAP ) {
86
135
int width = Breaker .getTextWidthWithLetterSpacing (c , font , context .getMaster (), letterSpacing );
87
- if (width <= avail ) {
136
+ if (width <= avail || forceOutput ) {
88
137
c .setLineBreakedBecauseOfNoWrap (false );
89
138
context .setEnd (context .getLast ());
90
139
context .setWidth (width );
91
- return LineBreakResult .WORD_BREAKING_FINISHED ;
140
+ context .setNeedsNewLine (false );
141
+ return BreakTextResult .FINISHED ;
92
142
} else if (!c .isLineBreakedBecauseOfNoWrap ()) {
93
143
c .setLineBreakedBecauseOfNoWrap (true );
94
144
context .setEnd (context .getStart ());
95
145
context .setWidth (0 );
96
146
context .setNeedsNewLine (true );
97
- return LineBreakResult .WORD_BREAKING_NEED_NEW_LINE ;
147
+ context .setUnbreakable (true );
148
+ return BreakTextResult .DANGER_RECONSUME_WORD_ON_NL ;
98
149
} else {
99
150
c .setLineBreakedBecauseOfNoWrap (false );
100
151
context .setEnd (context .getLast ());
101
152
context .setWidth (width );
102
- return LineBreakResult .WORD_BREAKING_FINISHED ;
153
+ context .setNeedsNewLine (false );
154
+ return BreakTextResult .FINISHED ;
103
155
}
104
156
}
105
157
106
- //check if we should break on the next newline
158
+ // Check if we should break on the next newline
107
159
if (whitespace == IdentValue .PRE ||
108
- whitespace == IdentValue .PRE_WRAP ||
109
- whitespace == IdentValue .PRE_LINE ) {
160
+ whitespace == IdentValue .PRE_WRAP ||
161
+ whitespace == IdentValue .PRE_LINE ) {
110
162
int n = context .getStartSubstring ().indexOf (WhitespaceStripper .EOL );
111
163
if (n > -1 ) {
112
164
context .setEnd (context .getStart () + n + 1 );
113
165
context .setWidth (Breaker .getTextWidthWithLetterSpacing (c , font , context .getCalculatedSubstring (), letterSpacing ));
114
166
context .setNeedsNewLine (true );
115
167
context .setEndsOnNL (true );
116
168
} else if (whitespace == IdentValue .PRE ) {
117
- context .setEnd (context .getLast ());
169
+ context .setEnd (context .getLast ());
118
170
context .setWidth (Breaker .getTextWidthWithLetterSpacing (c , font , context .getCalculatedSubstring (), letterSpacing ));
171
+ context .setNeedsNewLine (false );
119
172
}
120
173
}
121
174
122
- //check if we may wrap
175
+ // Check if we may wrap
123
176
if (whitespace == IdentValue .PRE ||
124
- (context .isNeedsNewLine () && context .getWidth () <= avail )) {
177
+ (context .isNeedsNewLine () && context .getWidth () <= avail )) {
125
178
return context .isNeedsNewLine () ?
126
- LineBreakResult . WORD_BREAKING_NEED_NEW_LINE :
127
- LineBreakResult . WORD_BREAKING_FINISHED ;
179
+ BreakTextResult . CONTINUE_WORD_BREAKING_ON_NL :
180
+ BreakTextResult . FINISHED ;
128
181
}
129
182
130
183
context .setEndsOnNL (false );
131
-
184
+
132
185
if (style .getWordWrap () != IdentValue .BREAK_WORD ) {
133
186
// Ordinary old word wrap which will overflow too long unbreakable words.
134
- return doBreakText (c , context , avail , style , tryToBreakAnywhere );
187
+ return toBreakTextResult (
188
+ doBreakText (c , context , avail , style , tryToBreakAnywhere ));
135
189
} else {
136
190
int originalStart = context .getStart ();
137
191
int totalWidth = 0 ;
138
192
139
193
// The idea is we only break a word if it will not fit on a line by itself.
140
-
194
+
141
195
LineBreakResult result ;
196
+ BreakTextResult breakResult ;
197
+
142
198
LOOP :
143
199
while (true ) {
144
200
int savedEnd = context .getEnd ();
145
201
result = doBreakText (c , context , avail , style , tryToBreakAnywhere );
146
202
147
203
switch (result ) {
148
- case WORD_BREAKING_FINISHED :
204
+ case WORD_BREAKING_FINISHED : /* Fallthru */
149
205
case CHAR_BREAKING_FINISHED :
206
+ totalWidth += context .getWidth ();
207
+ breakResult = BreakTextResult .FINISHED ;
208
+ break LOOP ;
209
+
150
210
case CHAR_BREAKING_NEED_NEW_LINE :
151
211
totalWidth += context .getWidth ();
212
+ breakResult = BreakTextResult .CONTINUE_CHAR_BREAKING_ON_NL ;
152
213
break LOOP ;
153
214
154
215
case CHAR_BREAKING_UNBREAKABLE :
155
- if (totalWidth == 0 &&
156
- avail == lineWidth ) {
216
+ if (( totalWidth == 0 && avail == lineWidth ) ||
217
+ forceOutput ) {
157
218
// We are at the start of the line but could not fit a single character!
158
219
totalWidth += context .getWidth ();
220
+ breakResult = BreakTextResult .CHAR_UNBREAKABLE_BUT_CONSUMED ;
159
221
break LOOP ;
160
222
} else {
161
223
// We may be at the end of the line, so pick up at next line.
224
+ // FIXME: This is very dangerous and has led to infinite
225
+ // loops. Needs review.
162
226
context .setEnd (savedEnd );
227
+ context .setNeedsNewLine (true );
228
+ breakResult = BreakTextResult .DANGER_RECONSUME_CHAR_ON_NL ;
163
229
break LOOP ;
164
230
}
165
231
@@ -177,12 +243,14 @@ public static LineBreakResult breakText(LayoutContext c,
177
243
} else {
178
244
// Else, finish so it can be put on a new line.
179
245
totalWidth += context .getWidth ();
246
+ breakResult = BreakTextResult .CONTINUE_WORD_BREAKING_ON_NL ;
180
247
break LOOP ;
181
248
}
182
249
}
183
250
case WORD_BREAKING_UNBREAKABLE : {
184
251
if (context .getWidth () >= lineWidth ||
185
- context .isFirstCharInLine ()) {
252
+ context .isFirstCharInLine () ||
253
+ forceOutput ) {
186
254
// If the word is too long to fit on a line by itself or
187
255
// if we are at the start of a line,
188
256
// retry in character breaking mode.
@@ -194,28 +262,57 @@ public static LineBreakResult breakText(LayoutContext c,
194
262
// FIXME: This is very dangerous and has led to infinite
195
263
// loops. Needs review.
196
264
context .setEnd (savedEnd );
265
+ breakResult = BreakTextResult .DANGER_RECONSUME_WORD_ON_NL ;
197
266
break LOOP ;
198
267
}
199
268
}
200
269
}
201
-
270
+
202
271
context .setStart (context .getEnd ());
203
272
avail -= context .getWidth ();
204
273
totalWidth += context .getWidth ();
205
274
}
206
275
207
276
context .setStart (originalStart );
208
277
context .setWidth (totalWidth );
209
-
278
+
210
279
// We need to know this for the next line.
211
280
context .setFinishedInCharBreakingMode (tryToBreakAnywhere );
212
- return result ;
281
+ return breakResult ;
213
282
}
214
283
}
215
-
216
- private static LineBreakResult doBreakText (LayoutContext c ,
217
- LineBreakContext context , int avail , CalculatedStyle style ,
284
+
285
+ /**
286
+ * Converts a LineBreakResult returned from doBreakText in
287
+ * word-wrapping mode to a BreakTextResult.
288
+ *
289
+ * Throws a runtime exception if unexpected result found.
290
+ */
291
+ private static BreakTextResult toBreakTextResult (LineBreakResult res ) {
292
+ switch (res ) {
293
+ case WORD_BREAKING_FINISHED :
294
+ return BreakTextResult .FINISHED ;
295
+ case WORD_BREAKING_NEED_NEW_LINE :
296
+ return BreakTextResult .CONTINUE_WORD_BREAKING_ON_NL ;
297
+ case WORD_BREAKING_UNBREAKABLE :
298
+ return BreakTextResult .WORD_UNBREAKABLE_BUT_CONSUMED ;
299
+
300
+ case CHAR_BREAKING_FINISHED : // Fall-thru
301
+ case CHAR_BREAKING_FOUND_WORD_BREAK : // Fall-thru
302
+ case CHAR_BREAKING_NEED_NEW_LINE : // Fall-thru
303
+ case CHAR_BREAKING_UNBREAKABLE : // Fall-thru
304
+ default :
305
+ throw new RuntimeException ("PROGRAMMER ERROR: Unexpected LineBreakResult from word wrap" );
306
+ }
307
+ }
308
+
309
+ private static LineBreakResult doBreakText (
310
+ LayoutContext c ,
311
+ LineBreakContext context ,
312
+ int avail ,
313
+ CalculatedStyle style ,
218
314
boolean tryToBreakAnywhere ) {
315
+
219
316
if (!tryToBreakAnywhere ) {
220
317
return doBreakText (c , context , avail , style , STANDARD_LINE_BREAKER );
221
318
} else {
@@ -227,15 +324,15 @@ private static LineBreakResult doBreakText(LayoutContext c,
227
324
228
325
ToIntFunction <String > measurer = (str ) ->
229
326
c .getTextRenderer ().getWidth (c .getFontContext (), font , str );
230
-
327
+
231
328
String currentString = context .getStartSubstring ();
232
329
FSTextBreaker lineIterator = STANDARD_LINE_BREAKER .getBreaker (currentString , c .getSharedContext ());
233
- FSTextBreaker charIterator = STANDARD_CHARACTER_BREAKER .getBreaker (currentString , c .getSharedContext ());
234
-
330
+ FSTextBreaker charIterator = STANDARD_CHARACTER_BREAKER .getBreaker (currentString , c .getSharedContext ());
331
+
235
332
return doBreakCharacters (currentString , lineIterator , charIterator , context , avail , letterSpacing , measurer );
236
333
}
237
334
}
238
-
335
+
239
336
/**
240
337
* Breaks at most one word (until the next word break) going character by character to see
241
338
* what will fit in.
@@ -303,11 +400,12 @@ static LineBreakResult doBreakCharacters(
303
400
graphicsLength > 0 ) {
304
401
// Exact fit..
305
402
boolean needNewLine = currentString .length () > left ;
306
-
403
+
307
404
context .setNeedsNewLine (needNewLine );
308
405
context .setEnd (left + context .getStart ());
309
406
context .setWidth (graphicsLength );
310
-
407
+ context .setUnbreakable (false );
408
+
311
409
if (left >= currentString .length ()) {
312
410
return LineBreakResult .CHAR_BREAKING_FINISHED ;
313
411
} else if (left >= nextWordBreak ) {
@@ -340,6 +438,7 @@ static LineBreakResult doBreakCharacters(
340
438
context .setWidth (graphicsLength );
341
439
context .setEnd (nextCharBreak + context .getStart ());
342
440
context .setEndsOnWordBreak (nextCharBreak == nextWordBreak );
441
+ context .setUnbreakable (false );
343
442
344
443
if (nextCharBreak >= currentString .length ()) {
345
444
return LineBreakResult .CHAR_BREAKING_FINISHED ;
@@ -358,8 +457,10 @@ static LineBreakResult doBreakCharacters(
358
457
context .setWidth (lastGoodGraphicsLength );
359
458
context .setEnd (lastGoodWrap + context .getStart ());
360
459
context .setEndsOnWordBreak (lastGoodWrap == nextWordBreak );
460
+ context .setUnbreakable (false );
361
461
362
462
if (lastGoodWrap >= currentString .length ()) {
463
+ context .setNeedsNewLine (false );
363
464
return LineBreakResult .CHAR_BREAKING_FINISHED ;
364
465
} else if (lastGoodWrap >= nextWordBreak ) {
365
466
return LineBreakResult .CHAR_BREAKING_FOUND_WORD_BREAK ;
@@ -376,13 +477,15 @@ static LineBreakResult doBreakCharacters(
376
477
context .setEnd (end + context .getStart ());
377
478
context .setEndsOnWordBreak (end == nextWordBreak );
378
479
context .setWidth (splitWidth );
379
-
480
+ context .setNeedsNewLine (end < currentString .length ());
481
+
380
482
return LineBreakResult .CHAR_BREAKING_UNBREAKABLE ;
381
483
} else {
382
484
// Empty string.
383
485
context .setEnd (context .getStart ());
384
486
context .setWidth (0 );
385
487
context .setNeedsNewLine (false );
488
+ context .setUnbreakable (false );
386
489
387
490
return LineBreakResult .CHAR_BREAKING_FINISHED ;
388
491
}
@@ -498,6 +601,7 @@ static LineBreakResult doBreakTextWords(
498
601
if (current .graphicsLength <= avail ) {
499
602
context .setWidth (current .graphicsLength );
500
603
context .setEnd (context .getMaster ().length ());
604
+ context .setNeedsNewLine (false );
501
605
// It all fit!
502
606
return LineBreakResult .WORD_BREAKING_FINISHED ;
503
607
}
0 commit comments