27
27
import com .openhtmltopdf .css .style .CalculatedStyle ;
28
28
import com .openhtmltopdf .css .style .CssContext ;
29
29
import com .openhtmltopdf .extend .FSTextBreaker ;
30
+ import com .openhtmltopdf .layout .LineBreakContext .LineBreakResult ;
30
31
import com .openhtmltopdf .render .FSFont ;
31
32
32
33
/**
@@ -71,7 +72,7 @@ private static int getFirstLetterEnd(String text, int start) {
71
72
return end ;
72
73
}
73
74
74
- public static void breakText (LayoutContext c ,
75
+ public static LineBreakResult breakText (LayoutContext c ,
75
76
LineBreakContext context , int avail ,
76
77
CalculatedStyle style , boolean tryToBreakAnywhere , int lineWidth ) {
77
78
@@ -84,7 +85,7 @@ public static void breakText(LayoutContext c,
84
85
if (whitespace == IdentValue .NOWRAP ) {
85
86
context .setEnd (context .getLast ());
86
87
context .setWidth (Breaker .getTextWidthWithLetterSpacing (c , font , context .getCalculatedSubstring (), letterSpacing ));
87
- return ;
88
+ return LineBreakResult . WORD_BREAKING_FINISHED ;
88
89
}
89
90
90
91
//check if we should break on the next newline
@@ -106,54 +107,75 @@ public static void breakText(LayoutContext c,
106
107
//check if we may wrap
107
108
if (whitespace == IdentValue .PRE ||
108
109
(context .isNeedsNewLine () && context .getWidth () <= avail )) {
109
- return ;
110
+ return context .isNeedsNewLine () ?
111
+ LineBreakResult .WORD_BREAKING_NEED_NEW_LINE :
112
+ LineBreakResult .WORD_BREAKING_FINISHED ;
110
113
}
111
114
112
115
context .setEndsOnNL (false );
113
116
114
117
if (style .getWordWrap () != IdentValue .BREAK_WORD ) {
115
118
// Ordinary old word wrap which will overflow too long unbreakable words.
116
- doBreakText (c , context , avail , style , tryToBreakAnywhere );
119
+ return doBreakText (c , context , avail , style , tryToBreakAnywhere );
117
120
} else {
118
121
int originalStart = context .getStart ();
119
-
120
122
// The idea is we only break a word if it will not fit on a line by itself.
121
123
124
+ LineBreakResult result ;
125
+ LOOP :
122
126
while (true ) {
123
- doBreakText (c , context , avail , style , tryToBreakAnywhere );
127
+ result = doBreakText (c , context , avail , style , tryToBreakAnywhere );
128
+
129
+ switch (result ) {
130
+ case WORD_BREAKING_FINISHED :
131
+ case CHAR_BREAKING_FINISHED :
132
+ case CHAR_BREAKING_UNBREAKABLE :
133
+ case CHAR_BREAKING_NEED_NEW_LINE :
134
+ break LOOP ;
124
135
125
- if (context .isFinished ()) {
126
- break ;
127
- } else if (tryToBreakAnywhere && context .isEndsOnWordBreak ()) {
128
- // We were in char breaking mode, but have found a line breaking opportunity.
136
+ case CHAR_BREAKING_FOUND_WORD_BREAK :
129
137
tryToBreakAnywhere = false ;
130
- } else if (!tryToBreakAnywhere && context .isNeedsNewLine () && context .getNextWidth () >= lineWidth ) {
131
- // The next word will not fit on a line by itself so turn on char breaking mode.
132
- tryToBreakAnywhere = true ;
133
- } else if (!tryToBreakAnywhere && context .isUnbreakable ()) {
134
- // Safety valve: Not sure we need it.
135
- break ;
136
- } else if (context .isNeedsNewLine ()) {
137
- // Stop, we're at the end of the line.
138
138
break ;
139
+
140
+ case WORD_BREAKING_NEED_NEW_LINE : {
141
+ if (context .getNextWidth () >= lineWidth ) {
142
+ tryToBreakAnywhere = true ;
143
+ break ;
144
+ } else {
145
+ break LOOP ;
146
+ }
147
+ }
148
+ case WORD_BREAKING_UNBREAKABLE : {
149
+ if (context .getWidth () >= lineWidth ) {
150
+ tryToBreakAnywhere = true ;
151
+ context .resetEnd ();
152
+ continue LOOP ;
153
+ } else {
154
+ break LOOP ;
155
+ }
156
+ }
157
+
158
+ default :
159
+ break LOOP ;
139
160
}
140
161
141
- avail -= context .getWidth ();
142
162
context .setStart (context .getEnd ());
163
+ avail -= context .getWidth ();
143
164
}
144
165
145
166
context .setStart (originalStart );
146
167
147
168
// We need to know this for the next line.
148
169
context .setFinishedInCharBreakingMode (tryToBreakAnywhere );
170
+ return result ;
149
171
}
150
172
}
151
173
152
- private static void doBreakText (LayoutContext c ,
174
+ private static LineBreakResult doBreakText (LayoutContext c ,
153
175
LineBreakContext context , int avail , CalculatedStyle style ,
154
176
boolean tryToBreakAnywhere ) {
155
177
if (!tryToBreakAnywhere ) {
156
- doBreakText (c , context , avail , style , STANDARD_LINE_BREAKER );
178
+ return doBreakText (c , context , avail , style , STANDARD_LINE_BREAKER );
157
179
} else {
158
180
FSFont font = style .getFSFont (c );
159
181
@@ -168,15 +190,15 @@ private static void doBreakText(LayoutContext c,
168
190
FSTextBreaker lineIterator = STANDARD_LINE_BREAKER .getBreaker (currentString , c .getSharedContext ());
169
191
FSTextBreaker charIterator = STANDARD_CHARACTER_BREAKER .getBreaker (currentString , c .getSharedContext ());
170
192
171
- doBreakCharacters (currentString , lineIterator , charIterator , context , avail , letterSpacing , measurer );
193
+ return doBreakCharacters (currentString , lineIterator , charIterator , context , avail , letterSpacing , measurer );
172
194
}
173
195
}
174
196
175
197
/**
176
198
* Breaks at most one word (until the next word break) going character by character to see
177
199
* what will fit in.
178
200
*/
179
- static void doBreakCharacters (
201
+ static LineBreakResult doBreakCharacters (
180
202
String currentString ,
181
203
FSTextBreaker lineIterator ,
182
204
FSTextBreaker charIterator ,
@@ -237,10 +259,19 @@ static void doBreakCharacters(
237
259
238
260
if (graphicsLength == avail ) {
239
261
// Exact fit..
240
- context .setNeedsNewLine (currentString .length () > left );
241
- context .setEnd (left );
262
+ boolean needNewLine = currentString .length () > left ;
263
+
264
+ context .setNeedsNewLine (needNewLine );
265
+ context .setEnd (left + context .getStart ());
242
266
context .setWidth (graphicsLength );
243
- return ;
267
+
268
+ if (left >= currentString .length ()) {
269
+ return LineBreakResult .CHAR_BREAKING_FINISHED ;
270
+ } else if (left >= nextWordBreak ) {
271
+ return LineBreakResult .CHAR_BREAKING_FOUND_WORD_BREAK ;
272
+ } else {
273
+ return LineBreakResult .CHAR_BREAKING_NEED_NEW_LINE ;
274
+ }
244
275
}
245
276
246
277
if (nextCharBreak < 0 ) {
@@ -252,7 +283,7 @@ static void doBreakCharacters(
252
283
lastGoodWrap = nextCharBreak ;
253
284
lastGoodGraphicsLength = graphicsLength ;
254
285
255
- nextCharBreak = Math . min ( currentString . length (), nextWordBreak ) ;
286
+ nextCharBreak = nextWordBreak ;
256
287
257
288
float extraSpacing = (nextCharBreak - left ) * letterSpacing ;
258
289
int splitWidth = (int ) (measurer .applyAsInt (currentString .substring (left , nextCharBreak )) + extraSpacing );
@@ -265,7 +296,14 @@ static void doBreakCharacters(
265
296
context .setWidth (graphicsLength );
266
297
context .setEnd (nextCharBreak + context .getStart ());
267
298
context .setEndsOnWordBreak (nextCharBreak == nextWordBreak );
268
- return ;
299
+
300
+ if (nextCharBreak >= currentString .length ()) {
301
+ return LineBreakResult .CHAR_BREAKING_FINISHED ;
302
+ } else if (nextCharBreak >= nextWordBreak ) {
303
+ return LineBreakResult .CHAR_BREAKING_FOUND_WORD_BREAK ;
304
+ } else {
305
+ return LineBreakResult .CHAR_BREAKING_NEED_NEW_LINE ;
306
+ }
269
307
}
270
308
271
309
// We need a newline for this word.
@@ -276,7 +314,14 @@ static void doBreakCharacters(
276
314
context .setWidth (lastGoodGraphicsLength );
277
315
context .setEnd (lastGoodWrap + context .getStart ());
278
316
context .setEndsOnWordBreak (lastGoodWrap == nextWordBreak );
279
- return ;
317
+
318
+ if (lastGoodWrap >= currentString .length ()) {
319
+ return LineBreakResult .CHAR_BREAKING_FINISHED ;
320
+ } else if (lastGoodWrap >= nextWordBreak ) {
321
+ return LineBreakResult .CHAR_BREAKING_FOUND_WORD_BREAK ;
322
+ } else {
323
+ return LineBreakResult .CHAR_BREAKING_NEED_NEW_LINE ;
324
+ }
280
325
} else {
281
326
// One character word, so we didn't find a wrap point.
282
327
float extraSpacing = nextWordBreak * letterSpacing ;
@@ -286,7 +331,8 @@ static void doBreakCharacters(
286
331
context .setEnd (nextWordBreak + context .getStart ());
287
332
context .setEndsOnWordBreak (true );
288
333
context .setWidth (splitWidth );
289
- return ;
334
+
335
+ return LineBreakResult .CHAR_BREAKING_UNBREAKABLE ;
290
336
}
291
337
}
292
338
@@ -308,7 +354,7 @@ void copyTo(AppBreakOpportunity other) {
308
354
}
309
355
}
310
356
311
- public static void doBreakText (
357
+ public static LineBreakResult doBreakText (
312
358
LayoutContext c ,
313
359
LineBreakContext context ,
314
360
int avail ,
@@ -387,7 +433,7 @@ public static void doBreakText(
387
433
context .setWidth (current .graphicsLength );
388
434
context .setEnd (context .getMaster ().length ());
389
435
// It all fit!
390
- return ;
436
+ return LineBreakResult . WORD_BREAKING_FINISHED ;
391
437
}
392
438
393
439
context .setNeedsNewLine (true );
@@ -403,6 +449,8 @@ public static void doBreakText(
403
449
404
450
context .setNextWidth (nextUnfittableSplitWidth );
405
451
context .setEnd (context .getStart () + lastWrap );
452
+
453
+ return LineBreakResult .WORD_BREAKING_NEED_NEW_LINE ;
406
454
} else {
407
455
// Unbreakable string
408
456
if (current .left == 0 ) {
@@ -420,6 +468,8 @@ public static void doBreakText(
420
468
} else {
421
469
context .setWidth (current .graphicsLength );
422
470
}
471
+
472
+ return LineBreakResult .WORD_BREAKING_UNBREAKABLE ;
423
473
}
424
474
}
425
475
0 commit comments