Skip to content

Commit 1d44968

Browse files
authored
Add support for other colorspaces that are not DeviceColorSpaces but are stored as luts in objects (#134)
1 parent 692fb1b commit 1d44968

File tree

9 files changed

+153
-13
lines changed

9 files changed

+153
-13
lines changed

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

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

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

5-
enum CIEColorSpaceNameValue: string implements NameValue {
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 {
616
case CalGray = 'CalGray';
717
case CalRGB = 'CalRGB';
818
case Lab = 'Lab';
919
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+
}
1037
}

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

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,22 @@
22

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

5-
enum DeviceColorSpaceNameValue: string implements NameValue {
5+
use Override;
6+
use PrinsFrank\PdfParser\Document\Image\ColorSpace\Components;
7+
use PrinsFrank\PdfParser\Document\Image\ColorSpace\HasComponents;
8+
use PrinsFrank\PdfParser\Document\Image\ColorSpace\LUT;
9+
10+
enum DeviceColorSpaceNameValue: string implements NameValue, HasComponents {
611
case DeviceGray = 'DeviceGray';
712
case DeviceRGB = 'DeviceRGB';
813
case DeviceCMYK = 'DeviceCMYK';
14+
15+
#[Override]
16+
public function getComponents(?LUT $lut): Components {
17+
return match ($this) {
18+
self::DeviceGray => Components::Gray,
19+
self::DeviceRGB => Components::RGB,
20+
self::DeviceCMYK => Components::CMYK,
21+
};
22+
}
923
}

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

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

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

5-
enum SpecialColorSpaceNameValue: string implements NameValue {
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 {
616
case Pattern = 'Pattern';
717
case Indexed = 'Indexed';
818
case DeviceN = 'DeviceN';
919
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+
}
1037
}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
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+
9+
class ColorSpace {
10+
public function __construct(
11+
public readonly DeviceColorSpaceNameValue|CIEColorSpaceNameValue|SpecialColorSpaceNameValue $nameValue,
12+
public readonly ?LUT $lutObject,
13+
) {
14+
}
15+
16+
public function getComponents(): Components {
17+
return $this->nameValue->getComponents($this->lutObject);
18+
}
19+
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
<?php declare(strict_types=1);
2+
3+
namespace PrinsFrank\PdfParser\Document\Image\ColorSpace;
4+
5+
enum Components: int {
6+
case Gray = 1;
7+
case RGB = 3;
8+
case CMYK = 4;
9+
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
<?php declare(strict_types=1);
2+
3+
namespace PrinsFrank\PdfParser\Document\Image\ColorSpace;
4+
5+
interface HasComponents {
6+
public function getComponents(?LUT $lut): Components;
7+
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
<?php declare(strict_types=1);
2+
3+
namespace PrinsFrank\PdfParser\Document\Image\ColorSpace;
4+
5+
use PrinsFrank\PdfParser\Document\Object\Decorator\DecoratedObject;
6+
7+
class LUT {
8+
public function __construct(
9+
public readonly DecoratedObject $decoratedObject,
10+
) {
11+
}
12+
}

src/Document/Image/RasterizedImage.php

Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,8 @@
22

33
namespace PrinsFrank\PdfParser\Document\Image;
44

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;
5+
use PrinsFrank\PdfParser\Document\Image\ColorSpace\ColorSpace;
6+
use PrinsFrank\PdfParser\Document\Image\ColorSpace\Components;
87
use PrinsFrank\PdfParser\Exception\ParseFailureException;
98

109
class RasterizedImage {
@@ -15,7 +14,7 @@ class RasterizedImage {
1514
* @param int<1, max> $height
1615
* @throws ParseFailureException
1716
*/
18-
public static function toPNG(CIEColorSpaceNameValue|DeviceColorSpaceNameValue|SpecialColorSpaceNameValue $colorSpace, int $width, int $height, int $bitsPerComponent, string $content): string {
17+
public static function toPNG(ColorSpace $colorSpace, int $width, int $height, int $bitsPerComponent, string $content): string {
1918
if ($bitsPerComponent !== 8) {
2019
throw new ParseFailureException('Unsupported BitsPerComponent');
2120
}
@@ -28,10 +27,10 @@ public static function toPNG(CIEColorSpaceNameValue|DeviceColorSpaceNameValue|Sp
2827
$pixelIndex = 0;
2928
for ($y = 0; $y < $height; $y++) {
3029
for ($x = 0; $x < $width; $x++) {
31-
$color = match ($colorSpace) {
32-
DeviceColorSpaceNameValue::DeviceRGB => imagecolorallocate($image, ord($content[$pixelIndex++]), ord($content[$pixelIndex++]), ord($content[$pixelIndex++])),
33-
DeviceColorSpaceNameValue::DeviceGray => imagecolorallocate($image, $value = ord($content[$pixelIndex++]), $value, $value),
34-
default => throw new ParseFailureException('Unsupported colorspace: ' . $colorSpace->value),
30+
$color = match ($colorSpace->getComponents()) {
31+
Components::RGB => imagecolorallocate($image, ord($content[$pixelIndex++]), ord($content[$pixelIndex++]), ord($content[$pixelIndex++])),
32+
Components::Gray => imagecolorallocate($image, $value = ord($content[$pixelIndex++]), $value, $value),
33+
default => throw new ParseFailureException('Unsupported components: ' . $colorSpace->getComponents()->name),
3534
};
3635

3736
if ($color === false) {

src/Document/Object/Decorator/XObject.php

Lines changed: 28 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,16 @@
44

55
use Override;
66
use PrinsFrank\PdfParser\Document\Dictionary\DictionaryKey\DictionaryKey;
7+
use PrinsFrank\PdfParser\Document\Dictionary\DictionaryValue\Array\ArrayValue;
78
use PrinsFrank\PdfParser\Document\Dictionary\DictionaryValue\Integer\IntegerValue;
89
use PrinsFrank\PdfParser\Document\Dictionary\DictionaryValue\Name\CIEColorSpaceNameValue;
910
use PrinsFrank\PdfParser\Document\Dictionary\DictionaryValue\Name\DeviceColorSpaceNameValue;
1011
use PrinsFrank\PdfParser\Document\Dictionary\DictionaryValue\Name\FilterNameValue;
1112
use PrinsFrank\PdfParser\Document\Dictionary\DictionaryValue\Name\SpecialColorSpaceNameValue;
1213
use PrinsFrank\PdfParser\Document\Dictionary\DictionaryValue\Name\SubtypeNameValue;
14+
use PrinsFrank\PdfParser\Document\Dictionary\DictionaryValue\Reference\ReferenceValue;
15+
use PrinsFrank\PdfParser\Document\Image\ColorSpace\ColorSpace;
16+
use PrinsFrank\PdfParser\Document\Image\ColorSpace\LUT;
1317
use PrinsFrank\PdfParser\Document\Image\ImageType;
1418
use PrinsFrank\PdfParser\Document\Image\RasterizedImage;
1519
use PrinsFrank\PdfParser\Exception\ParseFailureException;
@@ -71,13 +75,35 @@ private function getBitsPerComponent(): ?int {
7175
->getValueForKey(DictionaryKey::BITS_PER_COMPONENT, IntegerValue::class)?->value;
7276
}
7377

74-
private function getColorSpace(): DeviceColorSpaceNameValue|CIEColorSpaceNameValue|SpecialColorSpaceNameValue|null {
78+
private function getColorSpace(): ?ColorSpace {
7579
if (($type = $this->getDictionary()->getTypeForKey(DictionaryKey::COLOR_SPACE)) === null) {
7680
return null;
7781
}
7882

7983
if ($type === DeviceColorSpaceNameValue::class || $type === CIEColorSpaceNameValue::class || $type === SpecialColorSpaceNameValue::class) {
80-
return $this->getDictionary()->getValueForKey(DictionaryKey::COLOR_SPACE, $type);
84+
return new ColorSpace($this->getDictionary()->getValueForKey(DictionaryKey::COLOR_SPACE, $type) ?? throw new ParseFailureException(), null);
85+
}
86+
87+
if ($type === ReferenceValue::class) {
88+
$colorSpaceObject = $this->getDictionary()->getObjectForReference($this->document, DictionaryKey::COLOR_SPACE)
89+
?? throw new ParseFailureException('Unable to retrieve colorspace object');
90+
91+
$colorSpaceInfo = ArrayValue::fromValue($colorSpaceObject->getContent());
92+
if (!$colorSpaceInfo instanceof ArrayValue || !array_key_exists(0, $colorSpaceInfo->value) || !is_string($colorSpaceInfo->value[0])) {
93+
throw new ParseFailureException('Expected an array for colorspace info');
94+
}
95+
96+
$colorSpaceName = substr($colorSpaceInfo->value[0], 1);
97+
$colorSpace = CIEColorSpaceNameValue::tryFrom($colorSpaceName) ?? DeviceColorSpaceNameValue::tryFrom($colorSpaceName) ?? SpecialColorSpaceNameValue::tryFrom($colorSpaceName) ?? throw new ParseFailureException(sprintf('Unsupported colorspace "%s"', $colorSpaceName));
98+
if (count($colorSpaceInfo->value) !== 4 || $colorSpaceInfo->value[3] !== 'R') {
99+
throw new ParseFailureException(sprintf('Expected reference value for colorspace info, got "%s"', $colorSpaceObject->getContent()));
100+
}
101+
102+
if (!is_int($objectNumber = $colorSpaceInfo->value[1])) {
103+
throw new ParseFailureException(sprintf('Expected an integer for object number, got "%s"', json_encode($objectNumber)));
104+
}
105+
106+
return new ColorSpace($colorSpace, new LUT($this->document->getObject($objectNumber) ?? throw new ParseFailureException(sprintf('Unable to locate object %d', $colorSpaceInfo->value[1]))));
81107
}
82108

83109
throw new ParseFailureException(sprintf('Unsupported colorspace format %s', $type));

0 commit comments

Comments
 (0)