Skip to content

Commit 7573b3e

Browse files
committed
close #251: Allow softhyphen in form field names
Fix PDFDocEncoding: decode 0xAD to U+00AD (SOFT HYPHEN), not NUL Byte 0xAD was skipped during table init and left unmapped in CODE_TO_UNI, so it decoded to (char) 0 (U+0000) instead of the soft hyphen. This corrupted text strings such as form field names and broke field lookups against producers that decode 0xAD as U+00AD (eg: pdf.js). Stop skipping 0xAD so it maps to its ISO-8859-1 value U+00AD; it now round-trips and encodes back to a single 0xAD byte. Adds round-trip tests for the soft hyphen and the field-name case, plus regression locks for undefined bytes (0x7F/0x9F -> U+FFFD) and NUL (0x00 -> U+0000).
1 parent 6a641e0 commit 7573b3e

2 files changed

Lines changed: 61 additions & 4 deletions

File tree

src/main/java/org/sejda/sambox/cos/PDFDocEncoding.java

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -49,10 +49,6 @@ final class PDFDocEncoding
4949
{
5050
continue;
5151
}
52-
if (i == 0xAD)
53-
{
54-
continue;
55-
}
5652

5753
set(i, (char) i);
5854
}

src/test/java/org/sejda/sambox/cos/PDFDocEncodingTest.java

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717

1818
package org.sejda.sambox.cos;
1919

20+
import static org.junit.Assert.assertArrayEquals;
2021
import static org.junit.Assert.assertEquals;
2122

2223
import java.io.IOException;
@@ -108,4 +109,64 @@ public void testPDFBox3864() throws IOException
108109
assertEquals(cs1.getString(), cs2.getString());
109110
}
110111
}
112+
113+
/**
114+
* 0xAD (SOFT HYPHEN) must decode to U+00AD, not U+0000. ISO 32000-1 leaves it undefined, but
115+
* other producers treat it as its ISO-8859-1 value; decoding it to a
116+
* NUL control character corrupts text strings such as form field names.
117+
*/
118+
@Test
119+
public void softHyphenDecodesToU00AD()
120+
{
121+
assertEquals("\u00AD", PDFDocEncoding.toString(new byte[] { (byte) 0xAD }));
122+
}
123+
124+
@Test
125+
public void softHyphenEncodesTo0xAD()
126+
{
127+
assertArrayEquals(new byte[] { (byte) 0xAD }, PDFDocEncoding.getBytes("\u00AD"));
128+
}
129+
130+
@Test
131+
public void softHyphenRoundTrips()
132+
{
133+
byte[] bytes = { (byte) 0xAD };
134+
assertArrayEquals(bytes, PDFDocEncoding.getBytes(PDFDocEncoding.toString(bytes)));
135+
}
136+
137+
/**
138+
* Reproduces the real-world failure: a German field name with two soft hyphens and an
139+
* a-umlaut, stored as PDFDocEncoding, survives a literal round-trip via COSString and is
140+
* stored single-byte (not UTF-16BE).
141+
*/
142+
@Test
143+
public void fieldNameWithSoftHyphensRoundTrips()
144+
{
145+
String fieldName = "Test\u00ADbesch\u00E4test adas Test\u00ADdsadas";
146+
COSString cosString = COSString.parseLiteral(fieldName);
147+
assertEquals(fieldName, cosString.getString());
148+
// representable in PDFDocEncoding, so single-byte; UTF-16BE would be 2*len + 2 (BOM)
149+
assertEquals(fieldName.length(), cosString.getBytes().length);
150+
}
151+
152+
/**
153+
* Bytes genuinely undefined in PDFDocEncoding (0x7F, 0x9F) must decode to the replacement
154+
* character, never to a silent U+0000.
155+
*/
156+
@Test
157+
public void undefinedBytesDecodeToReplacementCharacter()
158+
{
159+
assertEquals("\uFFFD", PDFDocEncoding.toString(new byte[] { (byte) 0x7F }));
160+
assertEquals("\uFFFD", PDFDocEncoding.toString(new byte[] { (byte) 0x9F }));
161+
}
162+
163+
/**
164+
* Code 0x00 is a legitimate mapping to U+0000 and must not be clobbered by the
165+
* replacement-character pre-fill.
166+
*/
167+
@Test
168+
public void nulByteStillDecodesToU0000()
169+
{
170+
assertEquals("\u0000", PDFDocEncoding.toString(new byte[] { 0x00 }));
171+
}
111172
}

0 commit comments

Comments
 (0)