Author: tilman
Date: Mon Mar 23 05:52:27 2026
New Revision: 1932473
Log:
PDFBOX-6178, PDFBOX-4076: use byte[] instead of String for internal storage in
COSName, by Maruan Sahyoun (backported 3.0 changes)
Added:
pdfbox/branches/2.0/pdfbox/src/test/java/org/apache/pdfbox/pdfparser/TestCOSParser.java
- copied, changed from r1932462,
pdfbox/trunk/pdfbox/src/test/java/org/apache/pdfbox/pdfparser/TestCOSParser.java
Modified:
pdfbox/branches/2.0/pdfbox/pom.xml
pdfbox/branches/2.0/pdfbox/src/main/java/org/apache/pdfbox/cos/COSName.java
pdfbox/branches/2.0/pdfbox/src/main/java/org/apache/pdfbox/pdfparser/BaseParser.java
pdfbox/branches/2.0/pdfbox/src/test/java/org/apache/pdfbox/cos/TestCOSName.java
Modified: pdfbox/branches/2.0/pdfbox/pom.xml
==============================================================================
--- pdfbox/branches/2.0/pdfbox/pom.xml Mon Mar 23 04:20:05 2026
(r1932472)
+++ pdfbox/branches/2.0/pdfbox/pom.xml Mon Mar 23 05:52:27 2026
(r1932473)
@@ -844,6 +844,32 @@
<sha512>60e7b46f11a655083a57f4a627edf75bee477cd1ebfb06fbaefeb8ba16a01be37039cfd51c7c181a3ea368b53d22e327d480a7b0699c0edb2e06b4faaae7790f</sha512>
</configuration>
</execution>
+ <execution>
+ <id>PDFBOX-6178</id>
+ <phase>generate-test-resources</phase>
+ <goals>
+ <goal>wget</goal>
+ </goals>
+ <configuration>
+
<url>https://issues.apache.org/jira/secure/attachment/13081297/form_empty.pdf</url>
+
<outputDirectory>${project.build.directory}/pdfs</outputDirectory>
+ <outputFileName>PDFBOX-6178.pdf</outputFileName>
+
<sha512>d39486af0614bd099167a6adaab833aed41a0ebec7b85b13b382a2fdb6fddbcaaea9ab26ed0a81b72822258c8dd66dd535fa5c76afd1e5a8b1bff7d81e890274</sha512>
+ </configuration>
+ </execution>
+ <execution>
+ <id>PDFBOX-6178-1</id>
+ <phase>generate-test-resources</phase>
+ <goals>
+ <goal>wget</goal>
+ </goals>
+ <configuration>
+
<url>https://issues.apache.org/jira/secure/attachment/13081311/form_selected_ASCII_NUL_acrobat.pdf</url>
+
<outputDirectory>${project.build.directory}/pdfs</outputDirectory>
+ <outputFileName>PDFBOX-6178-1.pdf</outputFileName>
+
<sha512>83bc557e6f7d3e98de6e81168b2e2fb3def5025cc5fab1ddb3ef658505351c615253587f09b93503bb73a6225f3d3898be894e9d05dba8d464f4dd9c54514bc3</sha512>
+ </configuration>
+ </execution>
</executions>
</plugin>
</plugins>
Modified:
pdfbox/branches/2.0/pdfbox/src/main/java/org/apache/pdfbox/cos/COSName.java
==============================================================================
--- pdfbox/branches/2.0/pdfbox/src/main/java/org/apache/pdfbox/cos/COSName.java
Mon Mar 23 04:20:05 2026 (r1932472)
+++ pdfbox/branches/2.0/pdfbox/src/main/java/org/apache/pdfbox/cos/COSName.java
Mon Mar 23 05:52:27 2026 (r1932473)
@@ -18,6 +18,8 @@ package org.apache.pdfbox.cos;
import java.io.IOException;
import java.io.OutputStream;
+import java.nio.ByteBuffer;
+import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
@@ -33,12 +35,12 @@ import org.apache.pdfbox.util.Hex;
public final class COSName extends COSBase implements Comparable<COSName>
{
// using ConcurrentHashMap because this can be accessed by multiple threads
- private static final Map<String, COSName> nameMap = new
ConcurrentHashMap<String, COSName>(
+ private static final Map<ByteBuffer, COSName> nameMap = new
ConcurrentHashMap<ByteBuffer, COSName>(
8192);
// all common COSName values are stored in this HashMap
// they are already defined as static constants and don't need to be
synchronized
- private static final Map<String, COSName> commonNameMap = new
HashMap<String, COSName>(768);
+ private static final Map<ByteBuffer, COSName> commonNameMap = new
HashMap<ByteBuffer, COSName>(768);
//
// IMPORTANT: this list is *alphabetized* and does not need any JavaDoc
@@ -642,98 +644,171 @@ public final class COSName extends COSBa
public static final COSName ZA_DB = new COSName("ZaDb");
// fields
- private final String name;
/**
- * This will get a COSName object with that name.
- *
- * @param aName The name of the object.
- *
- * @return A COSName with the specified name.
+ * <p>Per PDF 32000-1:2008 §7.3.5: Beginning with PDF 1.2 a name object is
an atomic symbol
+ * uniquely defined by a sequence of any characters (8-bit values) except
null
+ * (character code 0).</p>
+ */
+ private final byte[] nameBytes;
+
+ /**
+ * Returns a {@code COSName} whose byte sequence is the UTF-8 encoding of
{@code aName}.
+ *
+ * <p>This is the standard factory for names defined in Java source code
(e.g. the static
+ * constants above). All well-formed PDF names defined by the spec are
ASCII, so the UTF-8
+ * encoding is a transparent identity transform for those cases.</p>
+ *
+ * @param aName the name string; must not be {@code null}
+ * @return a canonicalised {@code COSName} instance
*/
public static COSName getPDFName(String aName)
{
- COSName name = null;
- if (aName != null)
+ return getPDFName(aName.getBytes(Charsets.UTF_8));
+ }
+
+ /**
+ * Returns a {@code COSName} whose byte sequence is exactly {@code bytes}.
+ *
+ * <p>This is the preferred factory when constructing a name directly from
a PDF byte stream
+ * (i.e. after the parser has stripped the leading {@code /} and expanded
all {@code #XX}
+ * escape sequences). Using this method preserves the spec-correct,
byte-level identity of the
+ * name even when the bytes are not valid UTF-8.</p>
+ *
+ * <p>Null bytes (0x00) are rejected; the spec explicitly excludes
them.</p>
+ *
+ * @param bytes the raw decoded byte sequence; must not be {@code null}
and must not contain 0x00
+ * @return a canonicalised {@code COSName} instance
+ * @throws IllegalArgumentException if {@code bytes} contains a null byte
+ */
+ public static COSName getPDFName(byte[] bytes)
+ {
+ if (bytes == null)
+ {
+ return null;
+ }
+
+ // Wrap for lookup only to avoid unnecessary copying of the byte array
for the key.
+ ByteBuffer lookupKey = ByteBuffer.wrap(bytes);
+
+ // Is it a common COSName ??
+ COSName name = commonNameMap.get(lookupKey);
+ if (name == null)
{
- // Is it a common COSName ??
- name = commonNameMap.get(aName);
+ // It seems to be a document specific COSName
+ name = nameMap.get(lookupKey);
if (name == null)
{
- // It seems to be a document specific COSName
- name = nameMap.get(aName);
- if (name == null)
- {
- // name is added to the synchronized map in the constructor
- name = new COSName(aName, false);
- }
+ // name is added to the synchronized map in the constructor
+ name = new COSName(bytes, false);
}
}
+
return name;
}
/**
- * Private constructor. This will limit the number of COSName objects.
that are created.
- *
- * @param aName The name of the COSName object.
+ * Private constructor. This will limit the number of COSName objects that
are created.
+ *
+ * @param storedBytes The name of the COSName object.
* @param staticValue Indicates if the COSName object is static so that it
can be stored in the HashMap without
* synchronizing.
*/
- private COSName(String aName, boolean staticValue)
+ private COSName(byte[] storedBytes, boolean staticValue)
{
- name = aName;
+ this.nameBytes = storedBytes;
+ ByteBuffer storedKey = ByteBuffer.wrap(storedBytes);
if (staticValue)
{
- commonNameMap.put(aName, this);
+ commonNameMap.put(storedKey, this);
}
else
{
- nameMap.put(aName, this);
+ nameMap.put(storedKey, this);
}
}
/**
- * Private constructor. This will limit the number of COSName objects.
that are created.
+ * Private constructor. This will limit the number of COSName objects that
are created.
*
* @param aName The name of the COSName object.
*/
private COSName(String aName)
{
- this(aName, true);
+ this(aName.getBytes(Charsets.UTF_8), true);
+ }
+
+ /**
+ * Returns the raw byte sequence that defines this name.
+ *
+ * <p>This is the atomic content/identity of the name. Prefer this over
+ * {@link #getName()} whenever you need to write name bytes to an output
stream, compare names
+ * parsed from a PDF, or otherwise operate at the byte level.</p>
+ *
+ * @return a defensive copy of the internal byte array; never {@code null}
+ */
+ public byte[] getBytes()
+ {
+ return Arrays.copyOf(nameBytes, nameBytes.length);
}
/**
- * This will get the name of this COSName object.
+ * Returns the name decoded as a UTF-8 {@code String}.
+ *
+ * <p>This method exists primarily for backward compatibility and for
cases where the
+ * readable value needs to be stored.</p>
+ *
+ * <p>Per PDF 32000-1:2008 §7.3.5, ... However, occasionally the need
arises to treat a name object
+ * as text, such as one that represents a font ... </p>
+ *
+ * <p>... In such situations, the sequence of bytes (after expansion of
NUMBER SIGN sequences, if any)
+ * should be interpreted according to UTF-8... </p>
+ *
+ * <p>Use {@link #getBytes()} when byte-level fidelity is required.</p>
*
* @return The name of the object.
*/
public String getName()
{
- return name;
+ String utf8String = new String(nameBytes, Charsets.UTF_8);
+
+ //check for lossy decoding, which can happen if the name contains
+ // bytes that are not valid UTF-8
+ if (utf8String.indexOf('\uFFFD') >= 0)
+ {
+ // fall back to ISO-8859-1, which is a single-byte encoding that
can decode any
+ // byte sequence without loss
+ return new String(nameBytes, Charsets.ISO_8859_1);
+ }
+ return utf8String;
}
@Override
public String toString()
{
- return "COSName{" + name + "}";
+ return "COSName{" + getName() + "}";
}
@Override
public boolean equals(Object object)
{
- return object instanceof COSName && name.equals(((COSName)
object).name);
+ return object instanceof COSName && Arrays.equals(nameBytes,
((COSName) object).nameBytes);
}
@Override
public int hashCode()
{
- return name.hashCode();
+ return Arrays.hashCode(nameBytes);
}
@Override
public int compareTo(COSName other)
{
- return name.compareTo(other.name);
+ if (this == other)
+ {
+ return 0;
+ }
+ return getName().compareTo(other.getName()); // maybe poor
implementation compared to 3.0?
}
/**
@@ -742,7 +817,7 @@ public final class COSName extends COSBa
*/
public boolean isEmpty()
{
- return name.isEmpty();
+ return nameBytes.length == 0;
}
@Override
@@ -760,7 +835,7 @@ public final class COSName extends COSBa
public void writePDF(OutputStream output) throws IOException
{
output.write('/');
- byte[] bytes = getName().getBytes(Charsets.UTF_8);
+ byte[] bytes = getBytes();
for (byte b : bytes)
{
int current = b & 0xFF;
Modified:
pdfbox/branches/2.0/pdfbox/src/main/java/org/apache/pdfbox/pdfparser/BaseParser.java
==============================================================================
---
pdfbox/branches/2.0/pdfbox/src/main/java/org/apache/pdfbox/pdfparser/BaseParser.java
Mon Mar 23 04:20:05 2026 (r1932472)
+++
pdfbox/branches/2.0/pdfbox/src/main/java/org/apache/pdfbox/pdfparser/BaseParser.java
Mon Mar 23 05:52:27 2026 (r1932473)
@@ -887,7 +887,7 @@ public abstract class BaseParser
seqSource.unread(c);
}
- return COSName.getPDFName(decodeBuffer(buffer));
+ return COSName.getPDFName(buffer.toByteArray());
}
/**
Modified:
pdfbox/branches/2.0/pdfbox/src/test/java/org/apache/pdfbox/cos/TestCOSName.java
==============================================================================
---
pdfbox/branches/2.0/pdfbox/src/test/java/org/apache/pdfbox/cos/TestCOSName.java
Mon Mar 23 04:20:05 2026 (r1932472)
+++
pdfbox/branches/2.0/pdfbox/src/test/java/org/apache/pdfbox/cos/TestCOSName.java
Mon Mar 23 05:52:27 2026 (r1932473)
@@ -17,14 +17,18 @@
package org.apache.pdfbox.cos;
import java.io.ByteArrayOutputStream;
+import java.io.File;
import java.io.IOException;
import org.apache.pdfbox.pdmodel.PDDocument;
import org.apache.pdfbox.pdmodel.PDPage;
+import org.apache.pdfbox.pdmodel.interactive.form.PDField;
import org.junit.Assert;
import org.junit.Test;
public class TestCOSName
{
+ private static final File TARGETPDFDIR = new File("target/pdfs");
+
/**
* PDFBOX-4076: Check that characters outside of US_ASCII are not replaced
with "?".
*
@@ -48,4 +52,71 @@ public class TestCOSName
document.close();
}
+ /**
+ * PDFBOX-6178: Ensure that names with escape sequences #xx are written as
is.
+ *
+ * @throws IOException
+ */
+ @Test
+ public void PDFBox6178() throws IOException
+ {
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+
+ PDDocument document = PDDocument.load(new
File(TARGETPDFDIR,"PDFBOX-6178.pdf"));
+ PDField field = document.getDocumentCatalog()
+ .getAcroForm(null)
+ .getField("Geschlecht");
+
+ field.setValue("männlich");
+
+ COSDictionary dict = (COSDictionary) field.getWidgets()
+ .get(0).getAppearance().getNormalAppearance().getCOSObject();
+ for (COSName k : dict.keySet())
+ {
+ try
+ {
+ k.writePDF(baos);
+ }
+ catch (IOException e)
+ {
+ // ignored
+ }
+ }
+ String writtenKeys = new String(baos.toByteArray(), "UTF-8");
+ Assert.assertTrue("Output should be /m#e4nnlich (with 0xE4 as hex
escape)", writtenKeys.contains("/m#E4nnlich"));
+ document.close();
+ }
+
+ /**
+ * PDFBOX-6178: Ensure that names with escape sequences #xx are written as
is.
+ *
+ * @throws IOException
+ */
+ @Test
+ public void NameWithASCII_NUL() throws IOException
+ {
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+
+ PDDocument document = PDDocument.load(new
File(TARGETPDFDIR,"PDFBOX-6178-1.pdf"));
+ PDField field = document.getDocumentCatalog()
+ .getAcroForm(null)
+ .getField("Geschlecht");
+
+ COSDictionary dict = (COSDictionary) field.getWidgets()
+ .get(0).getAppearance().getNormalAppearance().getCOSObject();
+ for (COSName k : dict.keySet())
+ {
+ try
+ {
+ k.writePDF(baos);
+ }
+ catch (IOException e)
+ {
+ // ignored
+ }
+ }
+ String writtenKeys = new String(baos.toByteArray(), "UTF-8");
+ Assert.assertTrue("Output should be /m#00nnlich (with 0xE4 as hex
escape)", writtenKeys.contains("/m#00nnlich"));
+ document.close();
+ }
}
Copied and modified:
pdfbox/branches/2.0/pdfbox/src/test/java/org/apache/pdfbox/pdfparser/TestCOSParser.java
(from r1932462,
pdfbox/trunk/pdfbox/src/test/java/org/apache/pdfbox/pdfparser/TestCOSParser.java)
==============================================================================
---
pdfbox/trunk/pdfbox/src/test/java/org/apache/pdfbox/pdfparser/TestCOSParser.java
Sun Mar 22 12:45:03 2026 (r1932462, copy source)
+++
pdfbox/branches/2.0/pdfbox/src/test/java/org/apache/pdfbox/pdfparser/TestCOSParser.java
Mon Mar 23 05:52:27 2026 (r1932473)
@@ -17,232 +17,170 @@
package org.apache.pdfbox.pdfparser;
-import static org.junit.jupiter.api.Assertions.assertEquals;
import java.io.IOException;
-import java.nio.charset.StandardCharsets;
import org.apache.pdfbox.cos.COSName;
-import org.apache.pdfbox.cos.COSString;
-import org.apache.pdfbox.io.RandomAccessReadBuffer;
-import org.junit.jupiter.api.Test;
+import org.apache.pdfbox.io.RandomAccessBuffer;
+import org.apache.pdfbox.util.Charsets;
+import static org.junit.Assert.assertEquals;
+import org.junit.Test;
-class TestCOSParser
+public class TestCOSParser
{
- @Test
- void testCheckForEndOfString() throws IOException
- {
- // (Test)
- byte[] inputBytes = { 40, 84, 101, 115, 116, 41 };
-
- RandomAccessReadBuffer buffer = new RandomAccessReadBuffer(inputBytes);
- COSParser cosParser = new COSParser(buffer);
- COSString cosString = cosParser.parseCOSLiteralString();
- assertEquals("Test", cosString.getString());
-
- String output = "(Test";
- // ((Test) + LF + "/ "
- inputBytes = new byte[] { '(', '(', 'T', 'e', 's', 't', ')', 10, '/',
' ' };
-
- buffer = new RandomAccessReadBuffer(inputBytes);
- cosParser = new COSParser(buffer);
- cosString = cosParser.parseCOSLiteralString();
- assertEquals(output, cosString.getString());
-
- // ((Test) + CR + "/ "
- inputBytes = new byte[] { '(', '(', 'T', 'e', 's', 't', ')', 13, '/',
' ' };
-
- buffer = new RandomAccessReadBuffer(inputBytes);
- cosParser = new COSParser(buffer);
- cosString = cosParser.parseCOSLiteralString();
- assertEquals(output, cosString.getString());
-
- // ((Test) + CR + LF + "/ "
- inputBytes = new byte[] { '(', '(', 'T', 'e', 's', 't', ')', 13, 10,
'/' };
-
- buffer = new RandomAccessReadBuffer(inputBytes);
- cosParser = new COSParser(buffer);
- cosString = cosParser.parseCOSLiteralString();
- assertEquals(output, cosString.getString());
-
- // ((Test) + LF + "> "
- inputBytes = new byte[] { '(', '(', 'T', 'e', 's', 't', ')', 10, '>',
' ' };
-
- buffer = new RandomAccessReadBuffer(inputBytes);
- cosParser = new COSParser(buffer);
- cosString = cosParser.parseCOSLiteralString();
- assertEquals(output, cosString.getString());
-
- // ((Test) + CR + "> "
- inputBytes = new byte[] { '(', '(', 'T', 'e', 's', 't', ')', 13, '>',
' ' };
-
- buffer = new RandomAccessReadBuffer(inputBytes);
- cosParser = new COSParser(buffer);
- cosString = cosParser.parseCOSLiteralString();
- assertEquals(output, cosString.getString());
-
- // ((Test) + CR + LF + "> "
- inputBytes = new byte[] { '(', '(', 'T', 'e', 's', 't', ')', 13, 10,
'>' };
-
- buffer = new RandomAccessReadBuffer(inputBytes);
- cosParser = new COSParser(buffer);
- cosString = cosParser.parseCOSLiteralString();
- assertEquals(output, cosString.getString());
- }
-
// COSName parsing tests based on examples from PDF 32000-1:2008, Table 4,
Section 7.3.5
@Test
- void testTable4Example_Name1() throws IOException
+ public void testTable4Example_Name1() throws IOException
{
// /Name1 → "Name1"
- byte[] inputBytes = "/Name1 ".getBytes(StandardCharsets.US_ASCII);
- RandomAccessReadBuffer buffer = new RandomAccessReadBuffer(inputBytes);
+ byte[] inputBytes = "/Name1 ".getBytes(Charsets.US_ASCII);
+ RandomAccessBuffer buffer = new RandomAccessBuffer(inputBytes);
COSParser cosParser = new COSParser(buffer);
COSName name = cosParser.parseCOSName();
assertEquals("Name1", name.getName());
}
@Test
- void testTable4Example_ASomewhatLongerName() throws IOException
+ public void testTable4Example_ASomewhatLongerName() throws IOException
{
// /ASomewhatLongerName → "ASomewhatLongerName"
- byte[] inputBytes = "/ASomewhatLongerName
".getBytes(StandardCharsets.US_ASCII);
- RandomAccessReadBuffer buffer = new RandomAccessReadBuffer(inputBytes);
+ byte[] inputBytes = "/ASomewhatLongerName
".getBytes(Charsets.US_ASCII);
+ RandomAccessBuffer buffer = new RandomAccessBuffer(inputBytes);
COSParser cosParser = new COSParser(buffer);
COSName name = cosParser.parseCOSName();
assertEquals("ASomewhatLongerName", name.getName());
}
@Test
- void testTable4Example_WithSpecialCharacters() throws IOException
+ public void testTable4Example_WithSpecialCharacters() throws IOException
{
// /A;Name_With-Various***Characters? →
"A;Name_With-Various***Characters?"
- byte[] inputBytes = "/A;Name_With-Various***Characters?
".getBytes(StandardCharsets.US_ASCII);
- RandomAccessReadBuffer buffer = new RandomAccessReadBuffer(inputBytes);
+ byte[] inputBytes = "/A;Name_With-Various***Characters?
".getBytes(Charsets.US_ASCII);
+ RandomAccessBuffer buffer = new RandomAccessBuffer(inputBytes);
COSParser cosParser = new COSParser(buffer);
COSName name = cosParser.parseCOSName();
assertEquals("A;Name_With-Various***Characters?", name.getName());
}
@Test
- void testTable4Example_Numeric() throws IOException
+ public void testTable4Example_Numeric() throws IOException
{
// /1.2 → "1.2"
- byte[] inputBytes = "/1.2 ".getBytes(StandardCharsets.US_ASCII);
- RandomAccessReadBuffer buffer = new RandomAccessReadBuffer(inputBytes);
+ byte[] inputBytes = "/1.2 ".getBytes(Charsets.US_ASCII);
+ RandomAccessBuffer buffer = new RandomAccessBuffer(inputBytes);
COSParser cosParser = new COSParser(buffer);
COSName name = cosParser.parseCOSName();
assertEquals("1.2", name.getName());
}
@Test
- void testTable4Example_DollarSigns() throws IOException
+ public void testTable4Example_DollarSigns() throws IOException
{
// /$$ → "$$"
- byte[] inputBytes = "/$$ ".getBytes(StandardCharsets.US_ASCII);
- RandomAccessReadBuffer buffer = new RandomAccessReadBuffer(inputBytes);
+ byte[] inputBytes = "/$$ ".getBytes(Charsets.US_ASCII);
+ RandomAccessBuffer buffer = new RandomAccessBuffer(inputBytes);
COSParser cosParser = new COSParser(buffer);
COSName name = cosParser.parseCOSName();
assertEquals("$$", name.getName());
}
@Test
- void testTable4Example_AtPattern() throws IOException
+ public void testTable4Example_AtPattern() throws IOException
{
// /@pattern → "@pattern"
- byte[] inputBytes = "/@pattern ".getBytes(StandardCharsets.US_ASCII);
- RandomAccessReadBuffer buffer = new RandomAccessReadBuffer(inputBytes);
+ byte[] inputBytes = "/@pattern ".getBytes(Charsets.US_ASCII);
+ RandomAccessBuffer buffer = new RandomAccessBuffer(inputBytes);
COSParser cosParser = new COSParser(buffer);
COSName name = cosParser.parseCOSName();
assertEquals("@pattern", name.getName());
}
@Test
- void testTable4Example_DotNotdef() throws IOException
+ public void testTable4Example_DotNotdef() throws IOException
{
// /.notdef → ".notdef" (space is 0x20, hex-encoded as #20)
- byte[] inputBytes = "/#2Enotdef ".getBytes(StandardCharsets.US_ASCII);
- RandomAccessReadBuffer buffer = new RandomAccessReadBuffer(inputBytes);
+ byte[] inputBytes = "/#2Enotdef ".getBytes(Charsets.US_ASCII);
+ RandomAccessBuffer buffer = new RandomAccessBuffer(inputBytes);
COSParser cosParser = new COSParser(buffer);
COSName name = cosParser.parseCOSName();
assertEquals(".notdef", name.getName());
}
@Test
- void testTable4Example_HexEncodedSpace() throws IOException
+ public void testTable4Example_HexEncodedSpace() throws IOException
{
// /lime#20Green → "lime Green"
- byte[] inputBytes = "/lime#20Green
".getBytes(StandardCharsets.US_ASCII);
- RandomAccessReadBuffer buffer = new RandomAccessReadBuffer(inputBytes);
+ byte[] inputBytes = "/lime#20Green ".getBytes(Charsets.US_ASCII);
+ RandomAccessBuffer buffer = new RandomAccessBuffer(inputBytes);
COSParser cosParser = new COSParser(buffer);
COSName name = cosParser.parseCOSName();
assertEquals("lime Green", name.getName());
}
@Test
- void testTable4Example_HexEncodedParentheses() throws IOException
+ public void testTable4Example_HexEncodedParentheses() throws IOException
{
// /paired#28#29parentheses → "paired()parentheses"
// (#28 = '(', #29 = ')')
- byte[] inputBytes = "/paired#28#29parentheses
".getBytes(StandardCharsets.US_ASCII);
- RandomAccessReadBuffer buffer = new RandomAccessReadBuffer(inputBytes);
+ byte[] inputBytes = "/paired#28#29parentheses
".getBytes(Charsets.US_ASCII);
+ RandomAccessBuffer buffer = new RandomAccessBuffer(inputBytes);
COSParser cosParser = new COSParser(buffer);
COSName name = cosParser.parseCOSName();
assertEquals("paired()parentheses", name.getName());
}
@Test
- void testTable4Example_HexEncodedNumberSign() throws IOException
+ public void testTable4Example_HexEncodedNumberSign() throws IOException
{
// /The_Key_of_F#23_Minor → "The_Key_of_F#_Minor"
// (#23 = '#')
- byte[] inputBytes = "/The_Key_of_F#23_Minor
".getBytes(StandardCharsets.US_ASCII);
- RandomAccessReadBuffer buffer = new RandomAccessReadBuffer(inputBytes);
+ byte[] inputBytes = "/The_Key_of_F#23_Minor
".getBytes(Charsets.US_ASCII);
+ RandomAccessBuffer buffer = new RandomAccessBuffer(inputBytes);
COSParser cosParser = new COSParser(buffer);
COSName name = cosParser.parseCOSName();
assertEquals("The_Key_of_F#_Minor", name.getName());
}
@Test
- void testTable4Example_HexEncodedLetter() throws IOException
+ public void testTable4Example_HexEncodedLetter() throws IOException
{
// /A#42 → "AB" (note #42 = 'B')
- byte[] inputBytes = "/A#42 ".getBytes(StandardCharsets.US_ASCII);
- RandomAccessReadBuffer buffer = new RandomAccessReadBuffer(inputBytes);
+ byte[] inputBytes = "/A#42 ".getBytes(Charsets.US_ASCII);
+ RandomAccessBuffer buffer = new RandomAccessBuffer(inputBytes);
COSParser cosParser = new COSParser(buffer);
COSName name = cosParser.parseCOSName();
assertEquals("AB", name.getName());
}
@Test
- void testTable4Example_EmptyName() throws IOException
+ public void testTable4Example_EmptyName() throws IOException
{
// / → "" (empty name is valid per spec)
- byte[] inputBytes = "/ ".getBytes(StandardCharsets.US_ASCII);
- RandomAccessReadBuffer buffer = new RandomAccessReadBuffer(inputBytes);
+ byte[] inputBytes = "/ ".getBytes(Charsets.US_ASCII);
+ RandomAccessBuffer buffer = new RandomAccessBuffer(inputBytes);
COSParser cosParser = new COSParser(buffer);
COSName name = cosParser.parseCOSName();
assertEquals("", name.getName());
}
@Test
- void testNullCharacterTermination() throws IOException
+ public void testNullCharacterTermination() throws IOException
{
// /Name\0Extra should parse as "Name" and stop at null
byte[] inputBytes = new byte[] { '/', 'N', 'a', 'm', 'e', 0, 'E', 'x',
't', 'r', 'a', ' ' };
- RandomAccessReadBuffer buffer = new RandomAccessReadBuffer(inputBytes);
+ RandomAccessBuffer buffer = new RandomAccessBuffer(inputBytes);
COSParser cosParser = new COSParser(buffer);
COSName name = cosParser.parseCOSName();
assertEquals("Name", name.getName());
}
@Test
- void testInvalidHexSequence() throws IOException
+ public void testInvalidHexSequence() throws IOException
{
// /Name#GG should keep #G literally since G is not a valid hex digit
- byte[] inputBytes = "/Name#GG ".getBytes(StandardCharsets.US_ASCII);
- RandomAccessReadBuffer buffer = new RandomAccessReadBuffer(inputBytes);
+ byte[] inputBytes = "/Name#GG ".getBytes(Charsets.US_ASCII);
+ RandomAccessBuffer buffer = new RandomAccessBuffer(inputBytes);
COSParser cosParser = new COSParser(buffer);
COSName name = cosParser.parseCOSName();
// When # is not followed by two hex digits, both chars are kept
literally
@@ -250,94 +188,94 @@ class TestCOSParser
}
@Test
- void testHexEscapeLowercase() throws IOException
+ public void testHexEscapeLowercase() throws IOException
{
// /Name#2fTest (lowercase hex #2f = '/')
- byte[] inputBytes = "/Name#2fTest
".getBytes(StandardCharsets.US_ASCII);
- RandomAccessReadBuffer buffer = new RandomAccessReadBuffer(inputBytes);
+ byte[] inputBytes = "/Name#2fTest ".getBytes(Charsets.US_ASCII);
+ RandomAccessBuffer buffer = new RandomAccessBuffer(inputBytes);
COSParser cosParser = new COSParser(buffer);
COSName name = cosParser.parseCOSName();
assertEquals("Name/Test", name.getName());
}
@Test
- void testHexEscapeUppercase() throws IOException
+ public void testHexEscapeUppercase() throws IOException
{
// /Name#2FTest (uppercase hex #2F = '/')
- byte[] inputBytes = "/Name#2FTest
".getBytes(StandardCharsets.US_ASCII);
- RandomAccessReadBuffer buffer = new RandomAccessReadBuffer(inputBytes);
+ byte[] inputBytes = "/Name#2FTest ".getBytes(Charsets.US_ASCII);
+ RandomAccessBuffer buffer = new RandomAccessBuffer(inputBytes);
COSParser cosParser = new COSParser(buffer);
COSName name = cosParser.parseCOSName();
assertEquals("Name/Test", name.getName());
}
@Test
- void testNameTerminationByDelimiters() throws IOException
+ public void testNameTerminationByDelimiters() throws IOException
{
// Test termination by '>'
- byte[] inputBytes = "/Name1>".getBytes(StandardCharsets.US_ASCII);
- RandomAccessReadBuffer buffer = new RandomAccessReadBuffer(inputBytes);
+ byte[] inputBytes = "/Name1>".getBytes(Charsets.US_ASCII);
+ RandomAccessBuffer buffer = new RandomAccessBuffer(inputBytes);
COSParser cosParser = new COSParser(buffer);
COSName name = cosParser.parseCOSName();
assertEquals("Name1", name.getName());
// Test termination by '<'
- inputBytes = "/Name2<".getBytes(StandardCharsets.US_ASCII);
- buffer = new RandomAccessReadBuffer(inputBytes);
+ inputBytes = "/Name2<".getBytes(Charsets.US_ASCII);
+ buffer = new RandomAccessBuffer(inputBytes);
cosParser = new COSParser(buffer);
name = cosParser.parseCOSName();
assertEquals("Name2", name.getName());
// Test termination by '['
- inputBytes = "/Name3[".getBytes(StandardCharsets.US_ASCII);
- buffer = new RandomAccessReadBuffer(inputBytes);
+ inputBytes = "/Name3[".getBytes(Charsets.US_ASCII);
+ buffer = new RandomAccessBuffer(inputBytes);
cosParser = new COSParser(buffer);
name = cosParser.parseCOSName();
assertEquals("Name3", name.getName());
// Test termination by ']'
- inputBytes = "/Name4]".getBytes(StandardCharsets.US_ASCII);
- buffer = new RandomAccessReadBuffer(inputBytes);
+ inputBytes = "/Name4]".getBytes(Charsets.US_ASCII);
+ buffer = new RandomAccessBuffer(inputBytes);
cosParser = new COSParser(buffer);
name = cosParser.parseCOSName();
assertEquals("Name4", name.getName());
// Test termination by '('
- inputBytes = "/Name5(".getBytes(StandardCharsets.US_ASCII);
- buffer = new RandomAccessReadBuffer(inputBytes);
+ inputBytes = "/Name5(".getBytes(Charsets.US_ASCII);
+ buffer = new RandomAccessBuffer(inputBytes);
cosParser = new COSParser(buffer);
name = cosParser.parseCOSName();
assertEquals("Name5", name.getName());
// Test termination by ')'
- inputBytes = "/Name6)".getBytes(StandardCharsets.US_ASCII);
- buffer = new RandomAccessReadBuffer(inputBytes);
+ inputBytes = "/Name6)".getBytes(Charsets.US_ASCII);
+ buffer = new RandomAccessBuffer(inputBytes);
cosParser = new COSParser(buffer);
name = cosParser.parseCOSName();
assertEquals("Name6", name.getName());
// Test termination by '/'
- inputBytes = "/Name7/".getBytes(StandardCharsets.US_ASCII);
- buffer = new RandomAccessReadBuffer(inputBytes);
+ inputBytes = "/Name7/".getBytes(Charsets.US_ASCII);
+ buffer = new RandomAccessBuffer(inputBytes);
cosParser = new COSParser(buffer);
name = cosParser.parseCOSName();
assertEquals("Name7", name.getName());
// Test termination by '%'
- inputBytes = "/Name8%".getBytes(StandardCharsets.US_ASCII);
- buffer = new RandomAccessReadBuffer(inputBytes);
+ inputBytes = "/Name8%".getBytes(Charsets.US_ASCII);
+ buffer = new RandomAccessBuffer(inputBytes);
cosParser = new COSParser(buffer);
name = cosParser.parseCOSName();
assertEquals("Name8", name.getName());
}
@Test
- void testASCIIRegularCharacters() throws IOException
+ public void testASCIIRegularCharacters() throws IOException
{
// Test a range of ASCII characters that are not delimiters
// PDF delimiters that terminate name parsing: whitespace, <, >, [, ],
{, }, /, %, (, )
- byte[] inputBytes =
"/!\"$'*+-._:;=@~^`|\\".getBytes(StandardCharsets.US_ASCII);
- RandomAccessReadBuffer buffer = new RandomAccessReadBuffer(inputBytes);
+ byte[] inputBytes =
"/!\"$'*+-._:;=@~^`|\\".getBytes(Charsets.US_ASCII);
+ RandomAccessBuffer buffer = new RandomAccessBuffer(inputBytes);
COSParser cosParser = new COSParser(buffer);
COSName name = cosParser.parseCOSName();
// All these non-delimiter characters should be preserved
@@ -345,25 +283,25 @@ class TestCOSParser
}
@Test
- void testUTF8InNames()
+ public void testUTF8InNames()
{
// Create a name with UTF-8 encoded characters
String nameStr = "Test中国";
- byte[] nameBytes = nameStr.getBytes(StandardCharsets.UTF_8);
+ byte[] nameBytes = nameStr.getBytes(Charsets.UTF_8);
COSName name = COSName.getPDFName(nameBytes);
// The name should preserve the UTF-8 bytes
byte[] retrievedBytes = name.getBytes();
// Verify by recreating the string
- String retrievedStr = new String(retrievedBytes,
StandardCharsets.UTF_8);
+ String retrievedStr = new String(retrievedBytes, Charsets.UTF_8);
assertEquals(nameStr, retrievedStr);
}
@Test
- void testNameCanonicaliation()
+ public void testNameCanonicaliation()
{
- byte[] bytes1 = "TestName".getBytes(StandardCharsets.US_ASCII);
- byte[] bytes2 = "TestName".getBytes(StandardCharsets.US_ASCII);
+ byte[] bytes1 = "TestName".getBytes(Charsets.US_ASCII);
+ byte[] bytes2 = "TestName".getBytes(Charsets.US_ASCII);
COSName name1 = COSName.getPDFName(bytes1);
COSName name2 = COSName.getPDFName(bytes2);