1<?php
2/**
3 * This file is part of FPDI
4 *
5 * @package   setasign\Fpdi
6 * @copyright Copyright (c) 2020 Setasign GmbH & Co. KG (https://www.setasign.com)
7 * @license   http://opensource.org/licenses/mit-license The MIT License
8 */
9
10namespace setasign\Fpdi\PdfParser\Type;
11
12use setasign\Fpdi\PdfParser\PdfParser;
13use setasign\Fpdi\PdfParser\StreamReader;
14use setasign\Fpdi\PdfParser\Tokenizer;
15
16/**
17 * Class representing a PDF dictionary object
18 *
19 * @package setasign\Fpdi\PdfParser\Type
20 */
21class PdfDictionary extends PdfType
22{
23    /**
24     * Parses a dictionary of the passed tokenizer, stream-reader and parser.
25     *
26     * @param Tokenizer $tokenizer
27     * @param StreamReader $streamReader
28     * @param PdfParser $parser
29     * @return bool|self
30     * @throws PdfTypeException
31     */
32    public static function parse(Tokenizer $tokenizer, StreamReader $streamReader, PdfParser $parser)
33    {
34        $entries = [];
35
36        while (true) {
37            $token = $tokenizer->getNextToken();
38            if ($token === '>' && $streamReader->getByte() === '>') {
39                $streamReader->addOffset(1);
40                break;
41            }
42
43            $key = $parser->readValue($token);
44            if ($key === false) {
45                return false;
46            }
47
48            // ensure the first value to be a Name object
49            if (!($key instanceof PdfName)) {
50                $lastToken = null;
51                // ignore all other entries and search for the closing brackets
52                while (($token = $tokenizer->getNextToken()) !== '>' && $token !== false && $lastToken !== '>') {
53                    $lastToken = $token;
54                }
55
56                if ($token === false) {
57                    return false;
58                }
59
60                break;
61            }
62
63
64            $value = $parser->readValue();
65            if ($value === false) {
66                return false;
67            }
68
69            if ($value instanceof PdfNull) {
70                continue;
71            }
72
73            // catch missing value
74            if ($value instanceof PdfToken && $value->value === '>' && $streamReader->getByte() === '>') {
75                $streamReader->addOffset(1);
76                break;
77            }
78
79            $entries[$key->value] = $value;
80        }
81
82        $v = new self;
83        $v->value = $entries;
84
85        return $v;
86    }
87
88    /**
89     * Helper method to create an instance.
90     *
91     * @param PdfType[] $entries The keys are the name entries of the dictionary.
92     * @return self
93     */
94    public static function create(array $entries = [])
95    {
96        $v = new self;
97        $v->value = $entries;
98
99        return $v;
100    }
101
102    /**
103     * Get a value by its key from a dictionary or a default value.
104     *
105     * @param mixed $dictionary
106     * @param string $key
107     * @param PdfType|mixed|null $default
108     * @return PdfNull|PdfType
109     * @throws PdfTypeException
110     */
111    public static function get($dictionary, $key, PdfType $default = null)
112    {
113        $dictionary = self::ensure($dictionary);
114
115        if (isset($dictionary->value[$key])) {
116            return $dictionary->value[$key];
117        }
118
119        return $default === null
120            ? new PdfNull()
121            : $default;
122    }
123
124    /**
125     * Ensures that the passed value is a PdfDictionary instance.
126     *
127     * @param mixed $dictionary
128     * @return self
129     * @throws PdfTypeException
130     */
131    public static function ensure($dictionary)
132    {
133        return PdfType::ensureType(self::class, $dictionary, 'Dictionary value expected.');
134    }
135}
136