Repository: PrinsFrank/pdfparser (php)

Issue:
[Bug]: Fix string content from dictionary values when hex/unicode encoded.
### Is there an existing issue for this?

- [x] I have searched the existing issues and found no similar reports

### Are you using the latest version of this package?

- [x] The issue I'm reporting exists in the latest release

### Can other PDF readers read the file?

- [x] The PDF I'm trying to read opens correctly in at least one other PDF reader

### When running this snippet

```PHP
$document = PdfParser::parse(Stream::openFile('/path/to/file.pdf'));
$document->getTitle();
```

### I run into the following issue/exception (Please attach the pdf)

Some dictionary textStringValues are still hex-encoded, like `<0000>`

### Do you allow attachment files to be used in tests to prevent regressions?

- [ ] Yes, I give permission to use this file as a test file to prevent future regressions (And am authorized to give this permission)

Pull request:
Add support for text string value bytes
Fixes #22 

Patch:
diff --git a/composer.json b/composer.json
index d167f2b..c22da57 100644
--- a/composer.json
+++ b/composer.json
@@ -23,7 +23,7 @@
         "phpstan/phpstan": "^2.0",
         "phpstan/phpstan-strict-rules": "^2",
         "phpunit/phpunit": "^11.5",
-        "prinsfrank/pdf-samples": "0.0.6"
+        "prinsfrank/pdf-samples": "^0.0.7"
     },
     "scripts": {
         "cs": "php-cs-fixer fix --allow-risky=yes --dry-run",
diff --git a/src/Document/Dictionary/DictionaryValue/TextString/TextStringValue.php b/src/Document/Dictionary/DictionaryValue/TextString/TextStringValue.php
index 924447b..1a22b22 100644
--- a/src/Document/Dictionary/DictionaryValue/TextString/TextStringValue.php
+++ b/src/Document/Dictionary/DictionaryValue/TextString/TextStringValue.php
@@ -16,7 +16,26 @@ class TextStringValue implements DictionaryValue {
     /** @throws ParseFailureException */
     public function getText(): string {
         if (str_starts_with($this->textStringValue, '(') && str_ends_with($this->textStringValue, ')')) {
-            return substr($this->textStringValue, 1, -1);
+            return preg_replace_callback(
+                '/\\\\([0-7]{3})/',
+                fn (array $matches) => mb_chr((int) octdec($matches[1])),
+                str_replace(['\(', '\)', '\n', '\r'], ['(', ')', "\n", "\r"], substr($this->textStringValue, 1, -1))
+            ) ?? throw new ParseFailureException();
+        }
+
+        if (str_starts_with($this->textStringValue, '<') && str_ends_with($this->textStringValue, '>')) {
+            $string = substr($this->textStringValue, 1, -1);
+            if (str_starts_with($string, 'FEFF')) {
+                $string = substr($string, 4);
+            }
+
+            return implode(
+                '',
+                array_map(
+                    fn (string $character) => mb_chr((int) hexdec($character)),
+                    str_split($string, 4)
+                )
+            );
         }
 
         throw new ParseFailureException(sprintf('Unrecognized format %s', $this->textStringValue));


Test patch:
diff --git a/tests/Feature/ParsedResultTest.php b/tests/Feature/ParsedResultTest.php
index 2040ea9..2cd8d3a 100644
--- a/tests/Feature/ParsedResultTest.php
+++ b/tests/Feature/ParsedResultTest.php
@@ -34,10 +34,10 @@ class ParsedResultTest extends TestCase {
         $document = (new PdfParser())->parseFile($pdfPath);
 
         static::assertSame($expectedVersion, $document->version);
-        //        static::assertSame($expectedTitle, $document->getInformationDictionary()?->getTitle());
-        //        static::assertSame($expectedProducer, $document->getInformationDictionary()?->getProducer());
-        //        static::assertSame($expectedAuthor, $document->getInformationDictionary()?->getAuthor());
-        //        static::assertSame($expectedCreator, $document->getInformationDictionary()?->getCreator());
+        static::assertSame($expectedTitle, $document->getInformationDictionary()?->getTitle());
+        static::assertSame($expectedProducer, $document->getInformationDictionary()?->getProducer());
+        static::assertSame($expectedAuthor, $document->getInformationDictionary()?->getAuthor());
+        static::assertSame($expectedCreator, $document->getInformationDictionary()?->getCreator());
         static::assertEquals($expectedCreationDate, $document->getInformationDictionary()?->getCreationDate());
         static::assertEquals($expectedModificationDate, $document->getInformationDictionary()?->getModificationDate());
         static::assertSame(count($expectedPages), $document->getNumberOfPages());