Skip to content

Commit d9f7ccd

Browse files
committed
Apply text chunk's characteristics to a line only if the chunk is going to be drawn inside that line.
Add some new tests to demonstrate the issue. DEVSIX-2552
1 parent ef19e7b commit d9f7ccd

File tree

5 files changed

+129
-40
lines changed

5 files changed

+129
-40
lines changed

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

Lines changed: 41 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -80,15 +80,12 @@ This file is part of the iText (R) project.
8080

8181
public class LineRenderer extends AbstractRenderer {
8282

83+
// AbstractRenderer.EPS is not enough here
84+
private static final float MIN_MAX_WIDTH_CORRECTION_EPS = 0.001f;
8385
protected float maxAscent;
8486
protected float maxDescent;
85-
8687
// bidi levels
8788
protected byte[] levels;
88-
89-
// AbstractRenderer.EPS is not enough here
90-
private static final float MIN_MAX_WIDTH_CORRECTION_EPS = 0.001f;
91-
9289
private float maxTextAscent;
9390
private float maxTextDescent;
9491
private float maxBlockAscent;
@@ -387,23 +384,40 @@ public LayoutResult layout(LayoutContext layoutContext) {
387384
}
388385
}
389386

390-
maxAscent = Math.max(maxAscent, childAscent);
391-
if (childRenderer instanceof TextRenderer) {
392-
maxTextAscent = Math.max(maxTextAscent, childAscent);
393-
} else if (!isChildFloating) {
394-
maxBlockAscent = Math.max(maxBlockAscent, childAscent);
387+
boolean newLineOccurred = (childResult instanceof TextLayoutResult && ((TextLayoutResult) childResult).isSplitForcedByNewline());
388+
boolean shouldBreakLayouting = childResult.getStatus() != LayoutResult.FULL || newLineOccurred;
389+
390+
boolean wordWasSplitAndItWillFitOntoNextLine = false;
391+
392+
if (shouldBreakLayouting && childResult instanceof TextLayoutResult && ((TextLayoutResult) childResult).isWordHasBeenSplit()) {
393+
if (wasXOverflowChanged) {
394+
setProperty(Property.OVERFLOW_X, oldXOverflow);
395+
}
396+
LayoutResult newLayoutResult = childRenderer.layout(new LayoutContext(new LayoutArea(layoutContext.getArea().getPageNumber(), layoutBox), wasParentsHeightClipped));
397+
if (wasXOverflowChanged) {
398+
setProperty(Property.OVERFLOW_X, OverflowPropertyValue.FIT);
399+
}
400+
if (newLayoutResult instanceof TextLayoutResult && !((TextLayoutResult) newLayoutResult).isWordHasBeenSplit()) {
401+
wordWasSplitAndItWillFitOntoNextLine = true;
402+
}
395403
}
396-
maxDescent = Math.min(maxDescent, childDescent);
397-
if (childRenderer instanceof TextRenderer) {
398-
maxTextDescent = Math.min(maxTextDescent, childDescent);
399-
} else if (!isChildFloating) {
400-
maxBlockDescent = Math.min(maxBlockDescent, childDescent);
404+
405+
if (!wordWasSplitAndItWillFitOntoNextLine) {
406+
maxAscent = Math.max(maxAscent, childAscent);
407+
if (childRenderer instanceof TextRenderer) {
408+
maxTextAscent = Math.max(maxTextAscent, childAscent);
409+
} else if (!isChildFloating) {
410+
maxBlockAscent = Math.max(maxBlockAscent, childAscent);
411+
}
412+
maxDescent = Math.min(maxDescent, childDescent);
413+
if (childRenderer instanceof TextRenderer) {
414+
maxTextDescent = Math.min(maxTextDescent, childDescent);
415+
} else if (!isChildFloating) {
416+
maxBlockDescent = Math.min(maxBlockDescent, childDescent);
417+
}
401418
}
402419
float maxHeight = maxAscent - maxDescent;
403420

404-
boolean newLineOccurred = (childResult instanceof TextLayoutResult && ((TextLayoutResult) childResult).isSplitForcedByNewline());
405-
boolean shouldBreakLayouting = childResult.getStatus() != LayoutResult.FULL || newLineOccurred;
406-
407421
float currChildTextIndent = anythingPlaced ? 0 : lineLayoutContext.getTextIndent();
408422
if (hangingTabStop != null
409423
&& (TabAlignment.LEFT == hangingTabStop.getTabAlignment() || shouldBreakLayouting || childRenderers.size() - 1 == childPos || childRenderers.get(childPos + 1) instanceof TabRenderer)) {
@@ -421,7 +435,6 @@ public LayoutResult layout(LayoutContext layoutContext) {
421435
if (childResult.getSplitRenderer() != null) {
422436
childResult.getSplitRenderer().move(tabWidth + sumOfAffectedRendererWidths - childResult.getSplitRenderer().getOccupiedArea().getBBox().getWidth(), 0);
423437
}
424-
425438
float tabAndNextElemWidth = tabWidth + childResult.getOccupiedArea().getBBox().getWidth();
426439
if (hangingTabStop.getTabAlignment() == TabAlignment.RIGHT && curWidth + tabAndNextElemWidth < hangingTabStop.getTabPosition()) {
427440
curWidth = hangingTabStop.getTabPosition();
@@ -438,26 +451,14 @@ public LayoutResult layout(LayoutContext layoutContext) {
438451
widthHandler.updateMinChildWidth(minChildWidth + currChildTextIndent);
439452
widthHandler.updateMaxChildWidth(maxChildWidth + currChildTextIndent);
440453
}
441-
occupiedArea.setBBox(new Rectangle(layoutBox.getX(), layoutBox.getY() + layoutBox.getHeight() - maxHeight, curWidth, maxHeight));
442-
454+
if (!wordWasSplitAndItWillFitOntoNextLine) {
455+
occupiedArea.setBBox(new Rectangle(layoutBox.getX(), layoutBox.getY() + layoutBox.getHeight() - maxHeight, curWidth, maxHeight));
456+
}
443457

444458
if (shouldBreakLayouting) {
445459
LineRenderer[] split = split();
446460
split[0].childRenderers = new ArrayList<>(childRenderers.subList(0, childPos));
447461

448-
boolean wordWasSplitAndItWillFitOntoNextLine = false;
449-
if (childResult instanceof TextLayoutResult && ((TextLayoutResult) childResult).isWordHasBeenSplit()) {
450-
if (wasXOverflowChanged) {
451-
setProperty(Property.OVERFLOW_X, oldXOverflow);
452-
}
453-
LayoutResult newLayoutResult = childRenderer.layout(new LayoutContext(new LayoutArea(layoutContext.getArea().getPageNumber(), layoutBox), wasParentsHeightClipped));
454-
if (wasXOverflowChanged) {
455-
setProperty(Property.OVERFLOW_X, OverflowPropertyValue.FIT);
456-
}
457-
if (newLayoutResult instanceof TextLayoutResult && !((TextLayoutResult) newLayoutResult).isWordHasBeenSplit()) {
458-
wordWasSplitAndItWillFitOntoNextLine = true;
459-
}
460-
}
461462

462463
if (wordWasSplitAndItWillFitOntoNextLine) {
463464
split[1].childRenderers.add(childRenderer);
@@ -779,8 +780,8 @@ public void justify(float width) {
779780
childX = lastRightPos;
780781
if (child instanceof TextRenderer) {
781782
float childHSCale = (float) ((TextRenderer) child).getPropertyAsFloat(Property.HORIZONTAL_SCALING, 1f);
782-
Float oldCharacterSpacing = ((TextRenderer)child).getPropertyAsFloat(Property.CHARACTER_SPACING);
783-
Float oldWordSpacing = ((TextRenderer)child).getPropertyAsFloat(Property.WORD_SPACING);
783+
Float oldCharacterSpacing = ((TextRenderer) child).getPropertyAsFloat(Property.CHARACTER_SPACING);
784+
Float oldWordSpacing = ((TextRenderer) child).getPropertyAsFloat(Property.WORD_SPACING);
784785
child.setProperty(Property.CHARACTER_SPACING, (null == oldCharacterSpacing ? 0 : (float) oldCharacterSpacing) + characterSpacing / childHSCale);
785786
child.setProperty(Property.WORD_SPACING, (null == oldWordSpacing ? 0 : (float) oldWordSpacing) + wordSpacing / childHSCale);
786787
boolean isLastTextRenderer = child == lastChildRenderer;
@@ -810,7 +811,7 @@ protected int getNumberOfSpaces() {
810811
*/
811812
protected int length() {
812813
int length = 0;
813-
for (IRenderer child : childRenderers ) {
814+
for (IRenderer child : childRenderers) {
814815
if (child instanceof TextRenderer && !FloatingHelper.isRendererFloating(child)) {
815816
length += ((TextRenderer) child).lineLength();
816817
}
@@ -1255,12 +1256,12 @@ private boolean isInlineBlockChild(IRenderer child) {
12551256
}
12561257

12571258
static class RendererGlyph {
1259+
public Glyph glyph;
1260+
public TextRenderer renderer;
1261+
12581262
public RendererGlyph(Glyph glyph, TextRenderer textRenderer) {
12591263
this.glyph = glyph;
12601264
this.renderer = textRenderer;
12611265
}
1262-
1263-
public Glyph glyph;
1264-
public TextRenderer renderer;
12651266
}
12661267
}
Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
package com.itextpdf.layout;
2+
3+
import com.itextpdf.io.font.constants.StandardFonts;
4+
import com.itextpdf.kernel.colors.ColorConstants;
5+
import com.itextpdf.kernel.font.PdfFontFactory;
6+
import com.itextpdf.kernel.pdf.PdfDocument;
7+
import com.itextpdf.kernel.pdf.PdfWriter;
8+
import com.itextpdf.kernel.utils.CompareTool;
9+
import com.itextpdf.layout.borders.SolidBorder;
10+
import com.itextpdf.layout.element.Paragraph;
11+
import com.itextpdf.layout.element.Text;
12+
import com.itextpdf.layout.property.TabAlignment;
13+
import com.itextpdf.layout.property.TextAlignment;
14+
import com.itextpdf.test.ExtendedITextTest;
15+
import org.junit.Assert;
16+
import org.junit.BeforeClass;
17+
import org.junit.Test;
18+
19+
import java.io.IOException;
20+
21+
public class ParagraphTest extends ExtendedITextTest {
22+
23+
public static final String destinationFolder = "./target/test/com/itextpdf/layout/ParagraphTest/";
24+
public static final String sourceFolder = "./src/test/resources/com/itextpdf/layout/ParagraphTest/";
25+
26+
@BeforeClass
27+
public static void beforeClass() {
28+
createDestinationFolder(destinationFolder);
29+
}
30+
31+
@Test
32+
public void cannotPlaceABigChunkOnALineTest01() throws IOException, InterruptedException {
33+
String outFileName = destinationFolder + "cannotPlaceABigChunkOnALineTest01.pdf";
34+
String cmpFileName = sourceFolder + "cmp_cannotPlaceABigChunkOnALineTest01.pdf";
35+
PdfDocument pdfDocument = new PdfDocument(new PdfWriter(outFileName));
36+
37+
Document doc = new Document(pdfDocument);
38+
39+
Paragraph p = new Paragraph().setBorder(new SolidBorder(ColorConstants.YELLOW, 0));
40+
41+
p.add(new Text("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa").setBorder(new SolidBorder(ColorConstants.RED, 0)));
42+
p.add(new Text("b").setFontSize(100).setBorder(new SolidBorder(ColorConstants.BLUE, 0)));
43+
doc.add(p);
44+
45+
doc.close();
46+
47+
Assert.assertNull(new CompareTool().compareByContent(outFileName, cmpFileName, destinationFolder, "diff"));
48+
}
49+
50+
@Test
51+
public void cannotPlaceABigChunkOnALineTest02() throws IOException, InterruptedException {
52+
String outFileName = destinationFolder + "cannotPlaceABigChunkOnALineTest02.pdf";
53+
String cmpFileName = sourceFolder + "cmp_cannotPlaceABigChunkOnALineTest02.pdf";
54+
PdfDocument pdfDocument = new PdfDocument(new PdfWriter(outFileName));
55+
56+
Document doc = new Document(pdfDocument);
57+
58+
Paragraph p = new Paragraph().setBorder(new SolidBorder(ColorConstants.YELLOW, 0));
59+
p.add(new Text("smaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaall").setFontSize(5).setBorder(new SolidBorder(ColorConstants.RED, 0)));
60+
p.add(new Text("biiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiig").setFontSize(20).setBorder(new SolidBorder(ColorConstants.BLUE, 0)));
61+
62+
doc.add(p);
63+
64+
doc.close();
65+
66+
Assert.assertNull(new CompareTool().compareByContent(outFileName, cmpFileName, destinationFolder, "diff"));
67+
}
68+
69+
@Test
70+
public void wordWasSplitAndItWillFitOntoNextLineTest01() throws IOException, InterruptedException {
71+
String outFileName = destinationFolder + "wordWasSplitAndItWillFitOntoNextLineTest01.pdf";
72+
String cmpFileName = sourceFolder + "cmp_wordWasSplitAndItWillFitOntoNextLineTest01.pdf";
73+
PdfDocument pdfDocument = new PdfDocument(new PdfWriter(outFileName));
74+
75+
Document doc = new Document(pdfDocument);
76+
77+
Paragraph p = new Paragraph().setBorder(new SolidBorder(ColorConstants.YELLOW, 0)).setTextAlignment(TextAlignment.RIGHT);
78+
for (int i = 0; i < 5; i++) {
79+
p.add(new Text("aaaaaaaaaaaaaaaaaaaaa" + i).setBorder(new SolidBorder(ColorConstants.BLUE, 0)));
80+
}
81+
82+
doc.add(p);
83+
84+
doc.close();
85+
86+
Assert.assertNull(new CompareTool().compareByContent(outFileName, cmpFileName, destinationFolder, "diff"));
87+
}
88+
}

0 commit comments

Comments
 (0)