Skip to content

Commit c739fb7

Browse files
committed
Fix the handling of CRCRLF - DEVSIX-1727
1 parent 7821f55 commit c739fb7

File tree

11 files changed

+176
-13
lines changed

11 files changed

+176
-13
lines changed

io/src/main/java/com/itextpdf/io/util/TextUtil.java

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ This file is part of the iText (R) project.
4444
package com.itextpdf.io.util;
4545

4646
import com.itextpdf.io.font.otf.Glyph;
47+
import com.itextpdf.io.font.otf.GlyphLine;
4748

4849
import java.util.ArrayList;
4950
import java.util.List;
@@ -215,6 +216,13 @@ public static boolean isNewLine(Glyph glyph) {
215216
return unicode == '\n' || unicode == '\r';
216217
}
217218

219+
public static boolean isCarriageReturnFollowedByLineFeed(GlyphLine glyphLine, int carriageReturnPosition) {
220+
return glyphLine.size() > 1
221+
&& carriageReturnPosition <= glyphLine.size() - 2
222+
&& glyphLine.get(carriageReturnPosition).getUnicode() == '\r'
223+
&& glyphLine.get(carriageReturnPosition + 1).getUnicode() == '\n';
224+
}
225+
218226
public static boolean isSpaceOrWhitespace(Glyph glyph) {
219227
//\r, \n, and \t are whitespaces, but not space chars.
220228
//\u00a0 is SpaceChar, but not whitespace.
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
package com.itextpdf.io.util;
2+
3+
import com.itextpdf.io.font.otf.Glyph;
4+
import com.itextpdf.io.font.otf.GlyphLine;
5+
import com.itextpdf.test.annotations.type.UnitTest;
6+
7+
import java.util.Arrays;
8+
9+
import org.junit.Assert;
10+
import org.junit.Before;
11+
import org.junit.Test;
12+
import org.junit.experimental.categories.Category;
13+
14+
@Category(UnitTest.class)
15+
public class TextUtilTest {
16+
17+
private Glyph carriageReturn;
18+
private Glyph lineFeed;
19+
20+
@Before
21+
public void before() {
22+
this.carriageReturn = new Glyph(0, 0, '\r');
23+
this.lineFeed = new Glyph(0, 0, '\n');
24+
}
25+
26+
@Test
27+
public void carriageReturnFollowedByLineFeedTest() {
28+
helper(true, 0,
29+
carriageReturn, lineFeed);
30+
}
31+
32+
@Test
33+
public void carriageReturnFollowedByCarriageReturnAndThenLineFeedTest() {
34+
helper(false, 0,
35+
carriageReturn, carriageReturn, lineFeed);
36+
}
37+
38+
@Test
39+
public void carriageReturnPrecededByCarriageReturnAndFollowedByLineFeedTest() {
40+
helper(true, 1,
41+
carriageReturn, carriageReturn, lineFeed);
42+
}
43+
44+
@Test
45+
public void carriageReturnFollowedByNothingTest() {
46+
helper(false, 0,
47+
carriageReturn);
48+
}
49+
50+
@Test
51+
public void carriageReturnPrecededByLineFeedTest() {
52+
helper(false, 0,
53+
lineFeed, carriageReturn);
54+
}
55+
56+
@Test
57+
public void carriageReturnPrecededByTextFollowedByLineFeedTest() {
58+
helper(true, 1,
59+
new Glyph(0,0, 'a'), carriageReturn, lineFeed);
60+
}
61+
62+
private void helper(boolean expected, int currentCRPosition, Glyph...glyphs) {
63+
GlyphLine glyphLine = new GlyphLine(Arrays.asList(glyphs));
64+
Assert.assertTrue(expected == TextUtil.isCarriageReturnFollowedByLineFeed(glyphLine, currentCRPosition));
65+
}
66+
}

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

Lines changed: 21 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ This file is part of the iText (R) project.
4949
import com.itextpdf.io.font.TrueTypeFont;
5050
import com.itextpdf.io.font.otf.Glyph;
5151
import com.itextpdf.io.font.otf.GlyphLine;
52+
import com.itextpdf.io.util.MessageFormatUtil;
5253
import com.itextpdf.io.util.TextUtil;
5354
import com.itextpdf.kernel.colors.Color;
5455
import com.itextpdf.kernel.font.PdfFont;
@@ -83,11 +84,8 @@ This file is part of the iText (R) project.
8384
import com.itextpdf.layout.property.Underline;
8485
import com.itextpdf.layout.property.UnitValue;
8586
import com.itextpdf.layout.splitting.ISplitCharacters;
86-
import org.slf4j.Logger;
87-
import org.slf4j.LoggerFactory;
88-
89-
import com.itextpdf.io.util.MessageFormatUtil;
9087
import com.itextpdf.layout.tagging.LayoutTaggingHelper;
88+
9189
import java.util.ArrayList;
9290
import java.util.Collection;
9391
import java.util.Collections;
@@ -96,6 +94,9 @@ This file is part of the iText (R) project.
9694
import java.util.List;
9795
import java.util.Map;
9896

97+
import org.slf4j.Logger;
98+
import org.slf4j.LoggerFactory;
99+
99100
/**
100101
* This class represents the {@link IRenderer renderer} object for a {@link Text}
101102
* object. It will draw the glyphs of the textual content on the {@link DrawContext}.
@@ -235,6 +236,8 @@ public LayoutResult layout(LayoutContext layoutContext) {
235236
boolean forcePartialSplitOnFirstChar = false;
236237
// true in situations like "Hello\nWorld"
237238
boolean ignoreNewLineSymbol = false;
239+
// true when \r\n are found
240+
boolean crlf = false;
238241

239242
// For example, if a first character is a RTL mark (U+200F), and the second is a newline, we need to break anyway
240243
int firstPrintPos = currentTextPos;
@@ -271,9 +274,17 @@ public LayoutResult layout(LayoutContext layoutContext) {
271274
// Notice that in that case we do not need to ignore the new line symbol ('\n')
272275
forcePartialSplitOnFirstChar = true;
273276
}
277+
274278
if (line.start == -1) {
275279
line.start = currentTextPos;
276280
}
281+
282+
crlf = TextUtil.isCarriageReturnFollowedByLineFeed(text, currentTextPos);
283+
284+
if ( crlf) {
285+
currentTextPos++;
286+
}
287+
277288
line.end = Math.max(line.end, firstCharacterWhichExceedsAllowedWidth - 1);
278289
break;
279290
}
@@ -417,7 +428,9 @@ public LayoutResult layout(LayoutContext layoutContext) {
417428
if (line.start == -1) {
418429
line.start = currentTextPos;
419430
}
420-
currentTextPos = (forcePartialSplitOnFirstChar || null == overflowX || OverflowPropertyValue.FIT.equals(overflowX)) ? firstCharacterWhichExceedsAllowedWidth : nonBreakablePartEnd+1;
431+
if ( !crlf ) {
432+
currentTextPos = (forcePartialSplitOnFirstChar || null == overflowX || OverflowPropertyValue.FIT.equals(overflowX)) ? firstCharacterWhichExceedsAllowedWidth : nonBreakablePartEnd+1;
433+
}
421434
line.end = Math.max(line.end, currentTextPos);
422435
wordSplit = !forcePartialSplitOnFirstChar && (text.end != currentTextPos);
423436
if (wordSplit || !(forcePartialSplitOnFirstChar || null == overflowX || OverflowPropertyValue.FIT.equals(overflowX))) {
@@ -475,7 +488,7 @@ public LayoutResult layout(LayoutContext layoutContext) {
475488
result = new TextLayoutResult(LayoutResult.FULL, occupiedArea, null, null, isPlacingForcedWhileNothing ? this : null);
476489
} else {
477490
TextRenderer[] split;
478-
if (ignoreNewLineSymbol) {
491+
if (ignoreNewLineSymbol || crlf ) {
479492
// ignore '\n'
480493
split = splitIgnoreFirstNewLine(currentTextPos);
481494
} else {
@@ -984,13 +997,8 @@ static float[] calculateAscenderDescender(PdfFont font) {
984997
}
985998

986999
private TextRenderer[] splitIgnoreFirstNewLine(int currentTextPos) {
987-
if (text.get(currentTextPos).getUnicode() == '\r') {
988-
int next = currentTextPos + 1 < text.end ? text.get(currentTextPos + 1).getUnicode() : -1;
989-
if (next == '\n') {
990-
return split(currentTextPos + 2);
991-
} else {
992-
return split(currentTextPos + 1);
993-
}
1000+
if ( TextUtil.isCarriageReturnFollowedByLineFeed(text, currentTextPos)) {
1001+
return split(currentTextPos + 2);
9941002
} else {
9951003
return split(currentTextPos + 1);
9961004
}
Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
package com.itextpdf.layout;
2+
3+
import com.itextpdf.kernel.pdf.PdfDocument;
4+
import com.itextpdf.kernel.pdf.PdfWriter;
5+
import com.itextpdf.kernel.pdf.WriterProperties;
6+
import com.itextpdf.kernel.utils.CompareTool;
7+
import com.itextpdf.layout.element.Paragraph;
8+
import com.itextpdf.test.ExtendedITextTest;
9+
import com.itextpdf.test.annotations.type.IntegrationTest;
10+
11+
import java.io.FileOutputStream;
12+
import java.io.IOException;
13+
14+
import org.junit.Assert;
15+
import org.junit.BeforeClass;
16+
import org.junit.Test;
17+
import org.junit.experimental.categories.Category;
18+
19+
@Category(IntegrationTest.class)
20+
public class NewLineTest extends ExtendedITextTest {
21+
22+
public static final String sourceFolder = "./src/test/resources/com/itextpdf/layout/NewLineTest/";
23+
public static final String destinationFolder = "./target/test/com/itextpdf/layout/NewLineTest/";
24+
25+
@BeforeClass
26+
public static void beforeClass() {
27+
createOrClearDestinationFolder(destinationFolder);
28+
}
29+
30+
@Test
31+
public void r() throws IOException, InterruptedException {
32+
test("\r", "r.pdf");
33+
}
34+
35+
@Test
36+
public void n() throws IOException, InterruptedException {
37+
test("\n", "n.pdf");
38+
}
39+
40+
@Test
41+
public void rn() throws IOException, InterruptedException {
42+
test("\r\n", "rn.pdf");
43+
}
44+
45+
@Test
46+
public void rrn() throws IOException, InterruptedException {
47+
test("\r\r\n", "rrn.pdf");
48+
}
49+
50+
@Test
51+
public void nn() throws IOException, InterruptedException {
52+
test("\n\n", "nn.pdf");
53+
}
54+
55+
@Test
56+
public void rnn() throws IOException, InterruptedException {
57+
test("\r\n\n", "rnn.pdf");
58+
}
59+
60+
@Test
61+
public void rnrn() throws IOException, InterruptedException {
62+
test("\r\n\r\n", "rnrn.pdf");
63+
}
64+
65+
private void test(String newlineCharacters, String fileName) throws IOException, InterruptedException {
66+
String outFileName = destinationFolder + fileName;
67+
String cmpFileName = sourceFolder + "cmp_" + fileName;
68+
String diffPrefix = "diff_" + fileName + "_";
69+
70+
PdfDocument pdf = new PdfDocument(new PdfWriter(new FileOutputStream(outFileName), new WriterProperties().setCompressionLevel(0)));
71+
Document document = new Document(pdf);
72+
73+
Paragraph paragraph = new Paragraph().add(
74+
"This line is before." + newlineCharacters + "This line is after.");
75+
76+
document.add(paragraph);
77+
document.close();
78+
79+
Assert.assertNull(new CompareTool().compareVisually(outFileName, cmpFileName, destinationFolder, diffPrefix));
80+
}
81+
}
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.

0 commit comments

Comments
 (0)