Skip to content

Commit 032f46e

Browse files
LodrKumquatiText-CI
authored andcommitted
Support css overflow-wrap property
DEVSIX-4421
1 parent 82edb39 commit 032f46e

File tree

12 files changed

+292
-29
lines changed

12 files changed

+292
-29
lines changed

layout/src/main/java/com/itextpdf/layout/layout/TextLayoutResult.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,7 @@ public TextLayoutResult(int status, LayoutArea occupiedArea, IRenderer splitRend
8686
}
8787

8888
/**
89-
* Indicates whether some word in a rendered text was splitted during {@link com.itextpdf.layout.renderer.IRenderer#layout layout}.
89+
* Indicates whether some word in a rendered text was split during {@link com.itextpdf.layout.renderer.IRenderer#layout layout}.
9090
* The value will be set as true if, for example, the rendered words width is bigger than the width of layout area.
9191
*
9292
* @return whether some word was splitted or not.
@@ -97,7 +97,7 @@ public boolean isWordHasBeenSplit() {
9797

9898
/**
9999
* Sets {@link #wordHasBeenSplit}
100-
* @param wordHasBeenSplit indicates that some word was splitted during {@link com.itextpdf.layout.renderer.IRenderer#layout layout}.
100+
* @param wordHasBeenSplit indicates that some word was split during {@link com.itextpdf.layout.renderer.IRenderer#layout layout}.
101101
* @return {@link com.itextpdf.layout.layout.TextLayoutResult this layout result} the setting was applied on
102102
* @see #wordHasBeenSplit
103103
*/
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
/*
2+
This file is part of the iText (R) project.
3+
Copyright (c) 1998-2020 iText Group NV
4+
Authors: iText Software.
5+
6+
This program is offered under a commercial and under the AGPL license.
7+
For commercial licensing, contact us at https://itextpdf.com/sales. For AGPL licensing, see below.
8+
9+
AGPL licensing:
10+
This program is free software: you can redistribute it and/or modify
11+
it under the terms of the GNU Affero General Public License as published by
12+
the Free Software Foundation, either version 3 of the License, or
13+
(at your option) any later version.
14+
15+
This program is distributed in the hope that it will be useful,
16+
but WITHOUT ANY WARRANTY; without even the implied warranty of
17+
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18+
GNU Affero General Public License for more details.
19+
20+
You should have received a copy of the GNU Affero General Public License
21+
along with this program. If not, see <https://www.gnu.org/licenses/>.
22+
*/
23+
24+
package com.itextpdf.layout.property;
25+
26+
public enum OverflowWrapPropertyValue {
27+
ANYWHERE,
28+
BREAK_WORD,
29+
NORMAL
30+
}

layout/src/main/java/com/itextpdf/layout/property/Property.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -166,6 +166,7 @@ public final class Property {
166166
*/
167167
@Deprecated
168168
public static final int OVERFLOW = 102;
169+
public static final int OVERFLOW_WRAP = 127;
169170
public static final int OVERFLOW_X = 103;
170171
public static final int OVERFLOW_Y = 104;
171172
public static final int PADDING_BOTTOM = 47;
@@ -220,7 +221,7 @@ public final class Property {
220221
* related to textual operations. Indicates whether or not this type of property is inheritable.
221222
*/
222223
private static final boolean[] INHERITED_PROPERTIES;
223-
private static final int MAX_INHERITED_PROPERTY_ID = 125;
224+
private static final int MAX_INHERITED_PROPERTY_ID = 127;
224225

225226
static {
226227
INHERITED_PROPERTIES = new boolean[MAX_INHERITED_PROPERTY_ID + 1];
@@ -262,6 +263,7 @@ public final class Property {
262263
INHERITED_PROPERTIES[Property.TYPOGRAPHY_CONFIG] = true;
263264
INHERITED_PROPERTIES[Property.RENDERING_MODE] = true;
264265
INHERITED_PROPERTIES[Property.LINE_HEIGHT] = true;
266+
INHERITED_PROPERTIES[Property.OVERFLOW_WRAP] = true;
265267
}
266268

267269
private Property() {

layout/src/main/java/com/itextpdf/layout/renderer/LineRenderer.java

Lines changed: 22 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -182,6 +182,8 @@ && hasChildRendererInHtmlMode()) {
182182
LayoutResult childResult = null;
183183
Rectangle bbox = new Rectangle(layoutBox.getX() + curWidth, layoutBox.getY(), layoutBox.getWidth() - curWidth, layoutBox.getHeight());
184184

185+
RenderingMode childRenderingMode = childRenderer.<RenderingMode>getProperty(Property.RENDERING_MODE);
186+
185187
if (childRenderer instanceof TextRenderer) {
186188
// Delete these properties in case of relayout. We might have applied them during justify().
187189
childRenderer.deleteOwnProperty(Property.CHARACTER_SPACING);
@@ -400,8 +402,7 @@ && isTextRendererAndRequiresSpecialScriptPreLayoutProcessing(childRenderer)) {
400402
float childDescent = 0;
401403
if (childRenderer instanceof ILeafElementRenderer
402404
&& childResult.getStatus() != LayoutResult.NOTHING) {
403-
if (RenderingMode.HTML_MODE.equals(childRenderer.<RenderingMode>getProperty(Property.RENDERING_MODE))
404-
&& childRenderer instanceof TextRenderer) {
405+
if (RenderingMode.HTML_MODE == childRenderingMode && childRenderer instanceof TextRenderer) {
405406
float[] ascenderDescender = LineHeightHelper.getActualAscenderDescender((TextRenderer) childRenderer);
406407
childAscent = ascenderDescender[0];
407408
childDescent = ascenderDescender[1];
@@ -426,23 +427,29 @@ && isTextRendererAndRequiresSpecialScriptPreLayoutProcessing(childRenderer)) {
426427
boolean newLineOccurred = (childResult instanceof TextLayoutResult && ((TextLayoutResult) childResult).isSplitForcedByNewline());
427428
boolean shouldBreakLayouting = childResult.getStatus() != LayoutResult.FULL || newLineOccurred;
428429

429-
boolean wordWasSplitAndItWillFitOntoNextLine = false;
430+
boolean forceOverflowForTextRendererPartialResult = false;
430431

431432
// if childRenderer contains scripts which require word wrapping,
432433
// we don't need to attempt to relayout it and see if the split word could fit the next line
433434
// word wrapping is handled on shouldBreakLayouting
434435
if (shouldBreakLayouting && childResult instanceof TextLayoutResult
435436
&& ((TextLayoutResult) childResult).isWordHasBeenSplit()
436437
&& !((TextRenderer) childRenderer).textContainsSpecialScriptGlyphs(true)) {
437-
if (wasXOverflowChanged) {
438-
setProperty(Property.OVERFLOW_X, oldXOverflow);
439-
}
440-
LayoutResult newLayoutResult = childRenderer.layout(new LayoutContext(new LayoutArea(layoutContext.getArea().getPageNumber(), layoutBox), wasParentsHeightClipped));
441-
if (wasXOverflowChanged) {
442-
setProperty(Property.OVERFLOW_X, OverflowPropertyValue.FIT);
443-
}
444-
if (newLayoutResult instanceof TextLayoutResult && !((TextLayoutResult) newLayoutResult).isWordHasBeenSplit()) {
445-
wordWasSplitAndItWillFitOntoNextLine = true;
438+
if (RenderingMode.HTML_MODE == childRenderingMode && anythingPlaced) {
439+
// we don't really know if it will fit,
440+
// it's just used as a flag to mark that the entire word should be pushed to the next line
441+
forceOverflowForTextRendererPartialResult = true;
442+
} else {
443+
if (wasXOverflowChanged) {
444+
setProperty(Property.OVERFLOW_X, oldXOverflow);
445+
}
446+
LayoutResult newLayoutResult = childRenderer.layout(new LayoutContext(new LayoutArea(layoutContext.getArea().getPageNumber(), layoutBox), wasParentsHeightClipped));
447+
if (wasXOverflowChanged) {
448+
setProperty(Property.OVERFLOW_X, OverflowPropertyValue.FIT);
449+
}
450+
if (newLayoutResult instanceof TextLayoutResult && !((TextLayoutResult) newLayoutResult).isWordHasBeenSplit()) {
451+
forceOverflowForTextRendererPartialResult = true;
452+
}
446453
}
447454
} else if (shouldBreakLayouting && !newLineOccurred && childRenderers.get(childPos) instanceof TextRenderer
448455
&& ((TextRenderer) childRenderers.get(childPos)).textContainsSpecialScriptGlyphs(true)) {
@@ -457,7 +464,7 @@ && isTextRendererAndRequiresSpecialScriptPreLayoutProcessing(childRenderer)) {
457464
childResult = lastFittingChildRendererData.childLayoutResult;
458465
}
459466

460-
if (!wordWasSplitAndItWillFitOntoNextLine) {
467+
if (!forceOverflowForTextRendererPartialResult) {
461468
maxAscent = Math.max(maxAscent, childAscent);
462469
if (childRenderer instanceof TextRenderer) {
463470
maxTextAscent = Math.max(maxTextAscent, childAscent);
@@ -506,15 +513,15 @@ && isTextRendererAndRequiresSpecialScriptPreLayoutProcessing(childRenderer)) {
506513
widthHandler.updateMinChildWidth(minChildWidth + currChildTextIndent);
507514
widthHandler.updateMaxChildWidth(maxChildWidth + currChildTextIndent);
508515
}
509-
if (!wordWasSplitAndItWillFitOntoNextLine) {
516+
if (!forceOverflowForTextRendererPartialResult) {
510517
occupiedArea.setBBox(new Rectangle(layoutBox.getX(), layoutBox.getY() + layoutBox.getHeight() - maxHeight, curWidth, maxHeight));
511518
}
512519

513520
if (shouldBreakLayouting) {
514521
LineRenderer[] split = split();
515522
split[0].childRenderers = new ArrayList<>(childRenderers.subList(0, childPos));
516523

517-
if (wordWasSplitAndItWillFitOntoNextLine) {
524+
if (forceOverflowForTextRendererPartialResult) {
518525
split[1].childRenderers.add(childRenderer);
519526
split[1].childRenderers.addAll(childRenderers.subList(childPos + 1, childRenderers.size()));
520527
} else {

layout/src/main/java/com/itextpdf/layout/renderer/TextRenderer.java

Lines changed: 48 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,7 @@ This file is part of the iText (R) project.
8181
import com.itextpdf.layout.property.FloatPropertyValue;
8282
import com.itextpdf.layout.property.FontKerning;
8383
import com.itextpdf.layout.property.OverflowPropertyValue;
84+
import com.itextpdf.layout.property.OverflowWrapPropertyValue;
8485
import com.itextpdf.layout.property.Property;
8586
import com.itextpdf.layout.property.RenderingMode;
8687
import com.itextpdf.layout.property.TransparentColor;
@@ -178,6 +179,12 @@ public LayoutResult layout(LayoutContext layoutContext) {
178179

179180
OverflowPropertyValue overflowX = this.parent.<OverflowPropertyValue>getProperty(Property.OVERFLOW_X);
180181

182+
OverflowWrapPropertyValue overflowWrap = this.<OverflowWrapPropertyValue>getProperty(Property.OVERFLOW_WRAP);
183+
if (overflowWrap == OverflowWrapPropertyValue.ANYWHERE
184+
|| overflowWrap == OverflowWrapPropertyValue.BREAK_WORD) {
185+
overflowX = OverflowPropertyValue.FIT;
186+
}
187+
181188
List<Rectangle> floatRendererAreas = layoutContext.getFloatRendererAreas();
182189
FloatPropertyValue floatPropertyValue = this.<FloatPropertyValue>getProperty(Property.FLOAT);
183190

@@ -390,6 +397,12 @@ public LayoutResult layout(LayoutContext layoutContext) {
390397
}
391398
}
392399

400+
if (OverflowWrapPropertyValue.ANYWHERE == overflowWrap) {
401+
widthHandler.updateMinChildWidth((float) ((double) glyphWidth + (double) xAdvance
402+
+ (double) italicSkewAddition + (double) boldSimulationAddition));
403+
widthHandler.updateMaxChildWidth((float) ((double) glyphWidth + (double) xAdvance));
404+
}
405+
393406
boolean endOfWordBelongingToSpecialScripts = textContainsSpecialScriptGlyphs(true)
394407
&& findPossibleBreaksSplitPosition(specialScriptsWordBreakPoints,
395408
ind + 1, true) >= 0;
@@ -412,8 +425,17 @@ && findPossibleBreaksSplitPosition(specialScriptsWordBreakPoints,
412425
currentLineHeight = Math.max(currentLineHeight, nonBreakablePartMaxHeight);
413426
currentTextPos = nonBreakablePartEnd + 1;
414427
currentLineWidth += nonBreakablePartFullWidth;
415-
widthHandler.updateMinChildWidth(nonBreakablePartWidthWhichDoesNotExceedAllowedWidth + italicSkewAddition + boldSimulationAddition);
416-
widthHandler.updateMaxChildWidth(nonBreakablePartWidthWhichDoesNotExceedAllowedWidth + italicSkewAddition + boldSimulationAddition);
428+
if (OverflowWrapPropertyValue.ANYWHERE == overflowWrap) {
429+
widthHandler.updateMaxChildWidth((float) ((double) italicSkewAddition
430+
+ (double) boldSimulationAddition));
431+
} else {
432+
widthHandler.updateMinChildWidth(
433+
(float) ((double) nonBreakablePartWidthWhichDoesNotExceedAllowedWidth
434+
+ (double) italicSkewAddition + (double) boldSimulationAddition));
435+
widthHandler.updateMaxChildWidth(
436+
(float) ((double) nonBreakablePartWidthWhichDoesNotExceedAllowedWidth
437+
+ (double) italicSkewAddition + (double) boldSimulationAddition));
438+
}
417439
anythingPlaced = true;
418440
} else {
419441
// check if line height exceeds the allowed height
@@ -465,9 +487,19 @@ && findPossibleBreaksSplitPosition(specialScriptsWordBreakPoints,
465487
currentLineHeight = Math.max(currentLineHeight, nonBreakablePartMaxHeight);
466488

467489
currentLineWidth += currentHyphenationChoicePreTextWidth;
468-
widthHandler.updateMinChildWidth(currentHyphenationChoicePreTextWidth + italicSkewAddition + boldSimulationAddition);
469-
widthHandler.updateMaxChildWidth(currentHyphenationChoicePreTextWidth + italicSkewAddition + boldSimulationAddition);
470-
490+
if (OverflowWrapPropertyValue.ANYWHERE == overflowWrap) {
491+
widthHandler.updateMaxChildWidth((float) ((double) italicSkewAddition
492+
+ (double) boldSimulationAddition));
493+
} else {
494+
widthHandler.updateMinChildWidth(
495+
(float) ((double) currentHyphenationChoicePreTextWidth
496+
+ (double) italicSkewAddition
497+
+ (double) boldSimulationAddition));
498+
widthHandler.updateMaxChildWidth(
499+
(float) ((double) currentHyphenationChoicePreTextWidth
500+
+ (double) italicSkewAddition
501+
+ (double) boldSimulationAddition));
502+
}
471503
currentTextPos = wordBounds[0] + pre.length();
472504
break;
473505
}
@@ -509,8 +541,17 @@ && findPossibleBreaksSplitPosition(specialScriptsWordBreakPoints,
509541
currentLineDescender = Math.min(currentLineDescender, nonBreakablePartMaxDescender);
510542
currentLineHeight = Math.max(currentLineHeight, nonBreakablePartMaxHeight);
511543
currentLineWidth += nonBreakablePartWidthWhichDoesNotExceedAllowedWidth;
512-
widthHandler.updateMinChildWidth(nonBreakablePartWidthWhichDoesNotExceedAllowedWidth + italicSkewAddition + boldSimulationAddition);
513-
widthHandler.updateMaxChildWidth(nonBreakablePartWidthWhichDoesNotExceedAllowedWidth + italicSkewAddition + boldSimulationAddition);
544+
if (OverflowWrapPropertyValue.ANYWHERE == overflowWrap) {
545+
widthHandler.updateMaxChildWidth((float) ((double) italicSkewAddition
546+
+ (double) boldSimulationAddition));
547+
} else {
548+
widthHandler.updateMinChildWidth(
549+
(float) ((double) nonBreakablePartWidthWhichDoesNotExceedAllowedWidth
550+
+ (double) italicSkewAddition + (double) boldSimulationAddition));
551+
widthHandler.updateMaxChildWidth(
552+
(float) ((double) nonBreakablePartWidthWhichDoesNotExceedAllowedWidth
553+
+ (double) italicSkewAddition + (double) boldSimulationAddition));
554+
}
514555
} else {
515556
// process empty line (e.g. '\n')
516557
currentLineAscender = ascender;

layout/src/test/java/com/itextpdf/layout/ParagraphTest.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -113,9 +113,9 @@ public void cannotPlaceABigChunkOnALineTest02() throws IOException, InterruptedE
113113
}
114114

115115
@Test
116-
public void wordWasSplitAndItWillFitOntoNextLineTest01() throws IOException, InterruptedException {
117-
String outFileName = destinationFolder + "wordWasSplitAndItWillFitOntoNextLineTest01.pdf";
118-
String cmpFileName = sourceFolder + "cmp_wordWasSplitAndItWillFitOntoNextLineTest01.pdf";
116+
public void forceOverflowForTextRendererPartialResult01() throws IOException, InterruptedException {
117+
String outFileName = destinationFolder + "forceOverflowForTextRendererPartialResult01.pdf";
118+
String cmpFileName = sourceFolder + "cmp_forceOverflowForTextRendererPartialResult01.pdf";
119119
PdfDocument pdfDocument = new PdfDocument(new PdfWriter(outFileName));
120120

121121
Document doc = new Document(pdfDocument);

0 commit comments

Comments
 (0)