Completed
Push — master ( 7a405c...245614 )
by Ben
02:22
created

ObjectWriter   A

Complexity

Total Complexity 27

Size/Duplication

Total Lines 236
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 0

Importance

Changes 5
Bugs 1 Features 1
Metric Value
wmc 27
c 5
b 1
f 1
lcom 1
cbo 0
dl 0
loc 236
rs 10

18 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 4 1
A getCurrentOffset() 0 4 1
A writeRawLine() 0 4 1
A getObjectOffsets() 0 4 1
A allocateObjectId() 0 4 1
A startObject() 0 11 2
A endObject() 0 4 1
A writeIndirectReference() 0 10 2
A startDictionary() 0 5 1
A endDictionary() 0 5 1
A startArray() 0 5 1
A endArray() 0 5 1
A writeNull() 0 10 2
A writeBoolean() 0 10 4
A writeNumber() 0 10 4
A writeName() 0 5 1
A writeLiteralString() 0 5 1
A writeHexadecimalString() 0 5 1
1
<?php
2
/**
3
 * BaconPdf
4
 *
5
 * @link      http://github.com/Bacon/BaconPdf For the canonical source repository
6
 * @copyright 2015 Ben Scholzen (DASPRiD)
7
 * @license   http://opensource.org/licenses/BSD-2-Clause Simplified BSD License
8
 */
9
10
namespace Bacon\Pdf\Writer;
11
12
use Bacon\Pdf\Exception\InvalidArgumentException;
13
use SplFileObject;
14
15
/**
16
 * Writer responsible for writing objects to a stream.
17
 *
18
 * While the PDF specification tells that there is a line limit of 255 characters, not even Adobe's own PDF library
19
 * respects this limit. We ignore it as well, as it imposes a huge impact on the performance of the writer.
20
 *
21
 * {@internal This is a very performance sensitive class, which is why some code may look duplicated. Before thinking
22
 * about refactoring these parts, take a good look and the supplied benchmarks and verify that your changes
23
 * do not affect the performance in a bad way. Keep in mind that the methods in this writer are called quite
24
 * often.}}
25
 */
26
class ObjectWriter
27
{
28
    /**
29
     * @var SplFileObject
30
     */
31
    private $fileObject;
32
33
    /**
34
     * @var bool
35
     */
36
    private $requiresWhitespace = false;
37
38
    /**
39
     * @var int
40
     */
41
    private $lastAllocatedObjectId = 0;
42
43
    /**
44
     * @var int[]
45
     */
46
    private $objectOffsets = [];
47
48
    /**
49
     * @param SplFileObject $fileObject
50
     */
51
    public function __construct(SplFileObject $fileObject)
52
    {
53
        $this->fileObject = $fileObject;
54
    }
55
56
    /**
57
     * Returns the current position in the file.
58
     *
59
     * @return int
60
     */
61
    public function getCurrentOffset()
62
    {
63
        return $this->fileObject->ftell();
64
    }
65
66
    /**
67
     * Writes a raw data line to the stream.
68
     *
69
     * A newline character is appended after the data. Keep in mind that you may still be after a token which requires
70
     * a following whitespace, depending on the context you are in.
71
     *
72
     * @param string $data
73
     */
74
    public function writeRawLine($data)
75
    {
76
        $this->fileObject->fwrite($data . "\n");
77
    }
78
79
    /**
80
     * Returns all object offsets.
81
     *
82
     * @return int
83
     */
84
    public function getObjectOffsets()
85
    {
86
        return $this->objectOffsets;
87
    }
88
89
    /**
90
     * Allocates a new ID for an object.
91
     *
92
     * @return int
93
     */
94
    public function allocateObjectId()
95
    {
96
        return ++$this->lastAllocatedObjectId;
97
    }
98
99
    /**
100
     * Starts an object.
101
     *
102
     * If the object ID is omitted, a new one is allocated.
103
     *
104
     * @param  int|null $objectId
105
     * @return int
106
     */
107
    public function startObject($objectId = null)
108
    {
109
        if (null === $objectId) {
110
            $objectId = ++$this->lastAllocatedObjectId;
111
        }
112
113
        $this->objectOffsets[$objectId] = $this->fileObject->ftell();
114
        $this->fileObject->fwrite(sprintf("%d 0 obj\n", $objectId));
115
116
        return $objectId;
117
    }
118
119
    /**
120
     * Ends an object.
121
     */
122
    public function endObject()
123
    {
124
        $this->fileObject->fwrite("\nendobj\n");
125
    }
126
127
    /**
128
     * Writes an indirect reference
129
     *
130
     * @param int $objectId
131
     */
132
    public function writeIndirectReference($objectId)
133
    {
134
        if ($this->requiresWhitespace) {
135
            $this->fileObject->fwrite(sprintf(' %d 0 R', $objectId));
136
        } else {
137
            $this->fileObject->fwrite(sprintf('%d 0 R', $objectId));
138
        }
139
140
        $this->requiresWhitespace = true;
141
    }
142
143
    /**
144
     * Starts a dictionary.
145
     */
146
    public function startDictionary()
147
    {
148
        $this->fileObject->fwrite('<<');
149
        $this->requiresWhitespace = false;
150
    }
151
152
    /**
153
     * Ends a dictionary.
154
     */
155
    public function endDictionary()
156
    {
157
        $this->fileObject->fwrite('>>');
158
        $this->requiresWhitespace = false;
159
    }
160
161
    /**
162
     * Starts an array.
163
     */
164
    public function startArray()
165
    {
166
        $this->fileObject->fwrite('[');
167
        $this->requiresWhitespace = false;
168
    }
169
170
    /**
171
     * Ends an array.
172
     */
173
    public function endArray()
174
    {
175
        $this->fileObject->fwrite(']');
176
        $this->requiresWhitespace = false;
177
    }
178
179
    /**
180
     * Writes a null value.
181
     */
182
    public function writeNull()
183
    {
184
        if ($this->requiresWhitespace) {
185
            $this->fileObject->fwrite(' null');
186
        } else {
187
            $this->fileObject->fwrite('null');
188
        }
189
190
        $this->requiresWhitespace = true;
191
    }
192
193
    /**
194
     * Writes a boolean.
195
     *
196
     * @param bool $boolean
197
     */
198
    public function writeBoolean($boolean)
199
    {
200
        if ($this->requiresWhitespace) {
201
            $this->fileObject->fwrite($boolean ? ' true' : ' false');
202
        } else {
203
            $this->fileObject->fwrite($boolean ? 'true' : 'false');
204
        }
205
206
        $this->requiresWhitespace = true;
207
    }
208
209
    /**
210
     * Writes a number.
211
     *
212
     * @param  int|float $number
213
     * @throws InvalidArgumentException
214
     */
215
    public function writeNumber($number)
216
    {
217
        if ($this->requiresWhitespace) {
218
            $this->fileObject->fwrite(' ' . (rtrim(sprintf('%.6F', $number), '0.') ?: '0'));
219
        } else {
220
            $this->fileObject->fwrite(rtrim(sprintf('%.6F', $number), '0.') ?: '0');
221
        }
222
223
        $this->requiresWhitespace = true;
224
    }
225
226
    /**
227
     * Writes a name.
228
     *
229
     * @param string $name
230
     */
231
    public function writeName($name)
232
    {
233
        $this->fileObject->fwrite('/' . $name);
234
        $this->requiresWhitespace = true;
235
    }
236
237
    /**
238
     * Writes a literal string.
239
     *
240
     * The string itself is splitted into multiple lines after 248 characters. We chose that specific limit to avoid
241
     * splitting mutli-byte characters in half.
242
     *
243
     * @param string $string
244
     */
245
    public function writeLiteralString($string)
246
    {
247
        $this->fileObject->fwrite('(' . strtr($string, ['(' => '\\(', ')' => '\\)', '\\' => '\\\\']) . ')');
248
        $this->requiresWhitespace = false;
249
    }
250
251
    /**
252
     * Writes a hexadecimal string.
253
     *
254
     * @param string $string
255
     */
256
    public function writeHexadecimalString($string)
257
    {
258
        $this->fileObject->fwrite('<' . bin2hex($string) . '>');
259
        $this->requiresWhitespace = false;
260
    }
261
}
262