Skip to content

Commit f5f4c4a

Browse files
authored
Fix colorspace parsing issues (#270)
1 parent 6317c7b commit f5f4c4a

11 files changed

Lines changed: 258 additions & 101 deletions

File tree

src/Document/Dictionary/DictionaryValue/Name/CIEColorSpaceNameValue.php

Lines changed: 1 addition & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -2,36 +2,9 @@
22

33
namespace PrinsFrank\PdfParser\Document\Dictionary\DictionaryValue\Name;
44

5-
use Override;
6-
use PrinsFrank\PdfParser\Document\Dictionary\DictionaryKey\DictionaryKey;
7-
use PrinsFrank\PdfParser\Document\Dictionary\DictionaryValue\Integer\IntegerValue;
8-
use PrinsFrank\PdfParser\Document\Image\ColorSpace\Components;
9-
use PrinsFrank\PdfParser\Document\Image\ColorSpace\HasComponents;
10-
use PrinsFrank\PdfParser\Document\Image\ColorSpace\LUT;
11-
use PrinsFrank\PdfParser\Exception\InvalidArgumentException;
12-
use PrinsFrank\PdfParser\Exception\ParseFailureException;
13-
use PrinsFrank\PdfParser\Exception\RuntimeException;
14-
15-
enum CIEColorSpaceNameValue: string implements NameValue, HasComponents {
5+
enum CIEColorSpaceNameValue: string implements NameValue {
166
case CalGray = 'CalGray';
177
case CalRGB = 'CalRGB';
188
case Lab = 'Lab';
199
case ICCBased = 'ICCBased';
20-
21-
#[Override]
22-
public function getComponents(?LUT $lut): Components {
23-
if ($lut === null) {
24-
throw new InvalidArgumentException('Unable to get components for CIEColorSpaceNameValue without LUT');
25-
}
26-
27-
$type = $lut->decoratedObject->getDictionary()->getTypeForKey(DictionaryKey::N);
28-
if ($type !== IntegerValue::class) {
29-
throw new RuntimeException('Invalid ColorSpace object, missing N key');
30-
}
31-
32-
$integerValue = $lut->decoratedObject->getDictionary()->getValueForKey(DictionaryKey::N, IntegerValue::class)
33-
?? throw new RuntimeException('Invalid ColorSpace object, missing N key');
34-
35-
return Components::tryFrom($integerValue->value) ?? throw new ParseFailureException(sprintf('Invalid number of components %d', $integerValue->value));
36-
}
3710
}

src/Document/Dictionary/DictionaryValue/Name/DeviceColorSpaceNameValue.php

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,18 +2,14 @@
22

33
namespace PrinsFrank\PdfParser\Document\Dictionary\DictionaryValue\Name;
44

5-
use Override;
65
use PrinsFrank\PdfParser\Document\Image\ColorSpace\Components;
7-
use PrinsFrank\PdfParser\Document\Image\ColorSpace\HasComponents;
8-
use PrinsFrank\PdfParser\Document\Image\ColorSpace\LUT;
96

10-
enum DeviceColorSpaceNameValue: string implements NameValue, HasComponents {
7+
enum DeviceColorSpaceNameValue: string implements NameValue {
118
case DeviceGray = 'DeviceGray';
129
case DeviceRGB = 'DeviceRGB';
1310
case DeviceCMYK = 'DeviceCMYK';
1411

15-
#[Override]
16-
public function getComponents(?LUT $lut): Components {
12+
public function getComponents(): Components {
1713
return match ($this) {
1814
self::DeviceGray => Components::Gray,
1915
self::DeviceRGB => Components::RGB,

src/Document/Dictionary/DictionaryValue/Name/SpecialColorSpaceNameValue.php

Lines changed: 1 addition & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -2,36 +2,9 @@
22

33
namespace PrinsFrank\PdfParser\Document\Dictionary\DictionaryValue\Name;
44

5-
use Override;
6-
use PrinsFrank\PdfParser\Document\Dictionary\DictionaryKey\DictionaryKey;
7-
use PrinsFrank\PdfParser\Document\Dictionary\DictionaryValue\Integer\IntegerValue;
8-
use PrinsFrank\PdfParser\Document\Image\ColorSpace\Components;
9-
use PrinsFrank\PdfParser\Document\Image\ColorSpace\HasComponents;
10-
use PrinsFrank\PdfParser\Document\Image\ColorSpace\LUT;
11-
use PrinsFrank\PdfParser\Exception\InvalidArgumentException;
12-
use PrinsFrank\PdfParser\Exception\ParseFailureException;
13-
use PrinsFrank\PdfParser\Exception\RuntimeException;
14-
15-
enum SpecialColorSpaceNameValue: string implements NameValue, HasComponents {
5+
enum SpecialColorSpaceNameValue: string implements NameValue {
166
case Pattern = 'Pattern';
177
case Indexed = 'Indexed';
188
case DeviceN = 'DeviceN';
199
case Separation = 'Separation';
20-
21-
#[Override]
22-
public function getComponents(?LUT $lut): Components {
23-
if ($lut === null) {
24-
throw new InvalidArgumentException('Unable to get components for SpecialColorSpaceNameValue without LUT');
25-
}
26-
27-
$type = $lut->decoratedObject->getDictionary()->getTypeForKey(DictionaryKey::N);
28-
if ($type !== IntegerValue::class) {
29-
throw new RuntimeException('Invalid ColorSpace object, missing N key');
30-
}
31-
32-
$integerValue = $lut->decoratedObject->getDictionary()->getValueForKey(DictionaryKey::N, IntegerValue::class)
33-
?? throw new RuntimeException('Invalid ColorSpace object, missing N key');
34-
35-
return Components::tryFrom($integerValue->value) ?? throw new ParseFailureException(sprintf('Invalid number of components %d', $integerValue->value));
36-
}
3710
}

src/Document/Image/ColorSpace/ColorSpace.php

Lines changed: 29 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,18 +2,45 @@
22

33
namespace PrinsFrank\PdfParser\Document\Image\ColorSpace;
44

5+
use PrinsFrank\PdfParser\Document\Dictionary\DictionaryKey\DictionaryKey;
6+
use PrinsFrank\PdfParser\Document\Dictionary\DictionaryValue\Integer\IntegerValue;
57
use PrinsFrank\PdfParser\Document\Dictionary\DictionaryValue\Name\CIEColorSpaceNameValue;
68
use PrinsFrank\PdfParser\Document\Dictionary\DictionaryValue\Name\DeviceColorSpaceNameValue;
79
use PrinsFrank\PdfParser\Document\Dictionary\DictionaryValue\Name\SpecialColorSpaceNameValue;
10+
use PrinsFrank\PdfParser\Document\Object\Decorator\DecoratedObject;
11+
use PrinsFrank\PdfParser\Exception\ParseFailureException;
12+
use PrinsFrank\PdfParser\Exception\RuntimeException;
813

914
class ColorSpace {
15+
private readonly Components $components;
16+
1017
public function __construct(
18+
public readonly bool $isIndexed,
1119
public readonly DeviceColorSpaceNameValue|CIEColorSpaceNameValue|SpecialColorSpaceNameValue $nameValue,
12-
public readonly ?LUT $lutObject,
20+
public readonly DecoratedObject|null $objOrInlineValueLUT,
21+
public readonly string|null $fallbackLUT,
22+
public readonly ?int $maxIndexLUT,
1323
) {
1424
}
1525

1626
public function getComponents(): Components {
17-
return $this->nameValue->getComponents($this->lutObject);
27+
if (isset($this->components)) {
28+
return $this->components;
29+
}
30+
31+
if ($this->nameValue instanceof DeviceColorSpaceNameValue) {
32+
return $this->components = $this->nameValue->getComponents();
33+
}
34+
35+
if ($this->objOrInlineValueLUT?->getDictionary()->getTypeForKey(DictionaryKey::N) !== null) {
36+
return $this->components = Components::tryFrom(
37+
$this->objOrInlineValueLUT
38+
->getDictionary()
39+
->getValueForKey(DictionaryKey::N, IntegerValue::class)
40+
->value ?? throw new RuntimeException('Unable to determine number of components for color space')
41+
) ?? throw new ParseFailureException('Unable to determine number of components for color space');
42+
}
43+
44+
return $this->components = Components::Gray;
1845
}
1946
}
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
<?php declare(strict_types=1);
2+
3+
namespace PrinsFrank\PdfParser\Document\Image\ColorSpace;
4+
5+
use PrinsFrank\PdfParser\Document\Dictionary\DictionaryValue\Name\CIEColorSpaceNameValue;
6+
use PrinsFrank\PdfParser\Document\Dictionary\DictionaryValue\Name\DeviceColorSpaceNameValue;
7+
use PrinsFrank\PdfParser\Document\Dictionary\DictionaryValue\Name\SpecialColorSpaceNameValue;
8+
use PrinsFrank\PdfParser\Document\Document;
9+
use PrinsFrank\PdfParser\Exception\ParseFailureException;
10+
11+
class ColorSpaceFactory {
12+
public static function fromString(string $string, Document $document): ColorSpace {
13+
if (preg_match('/^\s*\[\s*(?<indexed>\/Indexed)?\s*(?<name>\/[A-Za-z]+|([0-9]+\s+[0-9]+\s+R))(?<lut>(\s+(?<lut_count>[0-9]+))?\s+(?<lut_value><[A-Fa-f0-9]*>|((?<lut_obj_nr>[0-9]+)\s+[0-9]+\s+R)))?\s*]\s*$/', $string, $matches) !== 1) {
14+
throw new ParseFailureException(sprintf('Invalid color space string "%s"', $string));
15+
}
16+
17+
if (preg_match('/^(?<objectNr>[0-9]+)\s+[0-9]+\s+R$/', $matches['name'], $nameObjectMatches) === 1) {
18+
$colorSpaceObject = $document->getObject((int) $nameObjectMatches['objectNr'])
19+
?? throw new ParseFailureException(sprintf('Unable to locate object with number %d', (int) $nameObjectMatches['objectNr']));
20+
if (preg_match('/^\s*\[\s*\/(?<name>[A-Za-z]+)\s+(?<objectNr>[0-9]+)\s+[0-9]+\s+R\s*]\s*$/', $colorSpaceObject->getStream()->toString(), $colorSpaceObjectMatches) !== 1) {
21+
throw new ParseFailureException(sprintf('Invalid color space string "%s" in colorSpaceObject', $colorSpaceObject->getStream()->toString()));
22+
}
23+
24+
$colorSpaceName = DeviceColorSpaceNameValue::tryFrom($colorSpaceObjectMatches['name'])
25+
?? SpecialColorSpaceNameValue::tryFrom($colorSpaceObjectMatches['name'])
26+
?? CIEColorSpaceNameValue::tryFrom($colorSpaceObjectMatches['name'])
27+
?? throw new ParseFailureException(sprintf('Unsupported color space name "%s"', $colorSpaceObjectMatches['name']));
28+
} else {
29+
$colorSpaceName = DeviceColorSpaceNameValue::tryFrom($nameString = substr($matches['name'], 1))
30+
?? SpecialColorSpaceNameValue::tryFrom($nameString)
31+
?? CIEColorSpaceNameValue::tryFrom($nameString)
32+
?? throw new ParseFailureException(sprintf('Unsupported color space name "%s"', $nameString));
33+
}
34+
35+
return new ColorSpace(
36+
$matches['indexed'] !== '' && SpecialColorSpaceNameValue::tryFrom(substr($matches['indexed'], 1)) === SpecialColorSpaceNameValue::Indexed,
37+
$colorSpaceName,
38+
array_key_exists('lut_obj_nr', $matches) ? $document->getObject((int) $matches['lut_obj_nr']) : null,
39+
$matches['lut_value'] !== '' && preg_match('/^(?<objectNr>[0-9]+)\s+[0-9]+\s+R$/', $matches['lut_value']) === 0 ? $matches['lut_value'] : null,
40+
$matches['lut_count'] !== '' ? (int) $matches['lut_count'] : null,
41+
);
42+
}
43+
}

src/Document/Image/ColorSpace/HasComponents.php

Lines changed: 0 additions & 7 deletions
This file was deleted.

src/Document/Image/ColorSpace/LUT.php

Lines changed: 0 additions & 12 deletions
This file was deleted.

src/Document/Object/Decorator/XObject.php

Lines changed: 3 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
use PrinsFrank\PdfParser\Document\Dictionary\DictionaryValue\Name\SubtypeNameValue;
1414
use PrinsFrank\PdfParser\Document\Dictionary\DictionaryValue\Reference\ReferenceValue;
1515
use PrinsFrank\PdfParser\Document\Image\ColorSpace\ColorSpace;
16-
use PrinsFrank\PdfParser\Document\Image\ColorSpace\LUT;
16+
use PrinsFrank\PdfParser\Document\Image\ColorSpace\ColorSpaceFactory;
1717
use PrinsFrank\PdfParser\Document\Image\ImageType;
1818
use PrinsFrank\PdfParser\Document\Image\RasterizedImage;
1919
use PrinsFrank\PdfParser\Exception\ParseFailureException;
@@ -102,29 +102,14 @@ private function getColorSpace(): ?ColorSpace {
102102
}
103103

104104
if ($type === DeviceColorSpaceNameValue::class || $type === CIEColorSpaceNameValue::class || $type === SpecialColorSpaceNameValue::class) {
105-
return new ColorSpace($this->getDictionary()->getValueForKey(DictionaryKey::COLOR_SPACE, $type) ?? throw new ParseFailureException(), null);
105+
return new ColorSpace(false, $this->getDictionary()->getValueForKey(DictionaryKey::COLOR_SPACE, $type) ?? throw new ParseFailureException(), null, null, null);
106106
}
107107

108108
if ($type === ReferenceValue::class) {
109109
$colorSpaceObject = $this->getDictionary()->getObjectForReference($this->document, DictionaryKey::COLOR_SPACE)
110110
?? throw new ParseFailureException('Unable to retrieve colorspace object');
111111

112-
$colorSpaceInfo = ArrayValue::fromValue($colorSpaceObject->getStream()->toString());
113-
if (!$colorSpaceInfo instanceof ArrayValue || !array_key_exists(0, $colorSpaceInfo->value) || !is_string($colorSpaceInfo->value[0])) {
114-
throw new ParseFailureException('Expected an array for colorspace info');
115-
}
116-
117-
$colorSpaceName = substr($colorSpaceInfo->value[0], 1);
118-
$colorSpace = CIEColorSpaceNameValue::tryFrom($colorSpaceName) ?? DeviceColorSpaceNameValue::tryFrom($colorSpaceName) ?? SpecialColorSpaceNameValue::tryFrom($colorSpaceName) ?? throw new ParseFailureException(sprintf('Unsupported colorspace "%s"', $colorSpaceName));
119-
if (count($colorSpaceInfo->value) !== 4 || $colorSpaceInfo->value[3] !== 'R') {
120-
throw new ParseFailureException(sprintf('Expected reference value for colorspace info, got "%s"', $colorSpaceObject->getStream()->toString()));
121-
}
122-
123-
if (!is_int($objectNumber = $colorSpaceInfo->value[1])) {
124-
throw new ParseFailureException(sprintf('Expected an integer for object number, got "%s"', ($jsonEncoded = json_encode($objectNumber)) !== false ? $jsonEncoded : 'Unknown'));
125-
}
126-
127-
return new ColorSpace($colorSpace, new LUT($this->document->getObject($objectNumber) ?? throw new ParseFailureException(sprintf('Unable to locate object %d', $colorSpaceInfo->value[1]))));
112+
return ColorSpaceFactory::fromString($colorSpaceObject->getStream()->toString(), $this->document);
128113
}
129114

130115
throw new ParseFailureException(sprintf('Unsupported colorspace format %s', $type));
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
<?php declare(strict_types=1);
2+
3+
namespace PrinsFrank\PdfParser\Tests\Unit\Document\Dictionary\DictionaryValue\Name;
4+
5+
use PHPUnit\Framework\Attributes\CoversClass;
6+
use PHPUnit\Framework\TestCase;
7+
use PrinsFrank\PdfParser\Document\Dictionary\DictionaryValue\Name\DeviceColorSpaceNameValue;
8+
use PrinsFrank\PdfParser\Document\Image\ColorSpace\Components;
9+
10+
#[CoversClass(DeviceColorSpaceNameValue::class)]
11+
class DeviceColorSpaceNameValueTest extends TestCase {
12+
public function testGetComponents(): void {
13+
static::assertSame(Components::Gray, DeviceColorSpaceNameValue::DeviceGray->getComponents());
14+
static::assertSame(Components::RGB, DeviceColorSpaceNameValue::DeviceRGB->getComponents());
15+
static::assertSame(Components::CMYK, DeviceColorSpaceNameValue::DeviceCMYK->getComponents());
16+
}
17+
}

0 commit comments

Comments
 (0)