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

DocumentWriter::writeTrailer()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 21
Code Lines 14

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 0
loc 21
rs 9.3143
cc 1
eloc 14
nc 1
nop 3
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\DocumentInformation;
13
use Bacon\Pdf\Encryption\EncryptionInterface;
14
use Bacon\Pdf\Options\PdfWriterOptions;
15
16
class DocumentWriter
17
{
18
    /**
19
     * @var ObjectWriter
20
     */
21
    private $objectWriter;
22
23
    /**
24
     * @var PdfWriterOptions
25
     */
26
    private $options;
27
28
    /**
29
     * @var string
30
     */
31
    private $permanentFileIdentifier;
32
33
    /**
34
     * @var string
35
     */
36
    private $changingFileIdentifier;
37
38
    /**
39
     * @var int
40
     */
41
    private $pageTreeId;
42
43
    /**
44
     * @var PageWriter[]
45
     */
46
    private $pageWriters = [];
47
48
    /**
49
     * @var int[]
50
     */
51
    private $pageIds = [];
52
53
    /**
54
     * @var DocumentInformation
55
     */
56
    private $documentInformation;
57
58
    /**
59
     * @param ObjectWriter $objectWriter
60
     * @param string       $fileIdentifier
61
     */
62
    public function __construct(ObjectWriter $objectWriter, PdfWriterOptions $options, $fileIdentifier)
63
    {
64
        $this->objectWriter = $objectWriter;
65
        $this->options = $options;
66
67
        $this->objectWriter->writeRawLine(sprintf("%%PDF-%s", $this->options->getPdfVersion()));
68
        $this->objectWriter->writeRawLine("%\xff\xff\xff\xff");
69
70
        $this->permanentFileIdentifier = $this->changingFileIdentifier = $fileIdentifier;
71
        $this->pageTreeId = $this->objectWriter->allocateObjectId();
72
        $this->documentInformation = new DocumentInformation();
73
    }
74
75
    /**
76
     * Returns the document information object.
77
     *
78
     * @return DocumentInformation
79
     */
80
    public function getDocumentInformation()
81
    {
82
        return $this->documentInformation;
83
    }
84
85
    /**
86
     * Adds a page writer for the page tree.
87
     *
88
     * @param PageWriter $pageWriter
89
     */
90
    public function addPageWriter(PageWriter $pageWriter)
91
    {
92
        $this->pageWriters[] = $pageWriter;
93
    }
94
95
    /**
96
     * Ends the document.
97
     *
98
     * @param EncryptionInterface $encryption
99
     */
100
    public function endDocument(EncryptionInterface $encryption)
101
    {
102
        $this->closeRemainingPages();
103
        $this->writePageTree();
104
        $documentInformationId = $this->writeDocumentInformation();
105
        $documentCatalogId = $this->writeDocumentCatalog();
106
107
        $xrefOffset = $this->writeCrossReferenceTable();
108
        $this->writeTrailer($documentInformationId, $documentCatalogId, $encryption);
109
        $this->writeFooter($xrefOffset);
110
    }
111
112
    /**
113
     * Closes pages which haven't been explicitly closed yet.
114
     */
115
    private function closeRemainingPages()
116
    {
117
        foreach ($this->pageWriters as $key => $pageWriter) {
118
            $this->pageIds[] = $pageWriter->writePage($this->objectWriter, $this->pageTreeId);
119
            unset($this->pageWriters[$key]);
120
        }
121
    }
122
123
    /**
124
     * Writes the page tree.
125
     */
126
    private function writePageTree()
127
    {
128
        $this->objectWriter->startObject($this->pageTreeId);
129
        $this->objectWriter->startDictionary();
130
131
        $this->objectWriter->writeName('Type');
132
        $this->objectWriter->writeName('Pages');
133
134
        $this->objectWriter->writeName('Kids');
135
        $this->objectWriter->startArray();
136
137
        sort($this->pageIds, SORT_NUMERIC);
138
        foreach ($this->pageIds as $pageId) {
139
            $this->objectWriter->writeIndirectReference($pageId);
140
        }
141
142
        $this->objectWriter->endArray();
143
144
        $this->objectWriter->writeName('Count');
145
        $this->objectWriter->writeNumber(count($this->pageIds));
146
147
        $this->objectWriter->endDictionary();
148
        $this->objectWriter->endObject();
149
    }
150
151
    /**
152
     * Writes the document information.
153
     *
154
     * @return int
155
     */
156
    private function writeDocumentInformation()
157
    {
158
        $id = $this->objectWriter->startObject();
159
        $this->documentInformation->writeInfoDictionary($this->objectWriter);
160
        $this->objectWriter->endObject();
161
        return $id;
162
    }
163
164
    /**
165
     * Writes the document catalog.
166
     *
167
     * @return int
168
     */
169
    private function writeDocumentCatalog()
170
    {
171
        $id = $this->objectWriter->startObject();
172
        $this->objectWriter->startDictionary();
173
174
        $this->objectWriter->writeName('Type');
175
        $this->objectWriter->writeName('Catalog');
176
177
        $this->objectWriter->writeName('Pages');
178
        $this->objectWriter->writeIndirectReference($this->pageTreeId);
179
180
        $this->objectWriter->endDictionary();
181
        $this->objectWriter->endObject();
182
        return $id;
183
    }
184
185
    /**
186
     * Writes the cross-reference table.
187
     *
188
     * @return int
189
     */
190
    private function writeCrossReferenceTable()
191
    {
192
        $xrefOffset = $this->objectWriter->getCurrentOffset();
193
        $objectOffsets = $this->objectWriter->getObjectOffsets();
194
        ksort($objectOffsets, SORT_NUMERIC);
195
196
        $this->objectWriter->writeRawLine('xref');
197
        $this->objectWriter->writeRawLine(sprintf('0 %d', count($objectOffsets) + 1));
198
        $this->objectWriter->writeRawLine(sprintf('%010d %05d f ', 0, 65535));
199
200
        foreach ($objectOffsets as $offset) {
201
            $this->objectWriter->writeRawLine(sprintf('%010d %05d n ', $offset, 0));
202
        }
203
204
        return $xrefOffset;
205
    }
206
207
    /**
208
     * Writes the trailer.
209
     *
210
     * @param int                 $documentInformationId
211
     * @param int                 $documentCatalogId
212
     * @param EncryptionInterface $encryption
213
     */
214
    private function writeTrailer($documentInformationId, $documentCatalogId, EncryptionInterface $encryption)
215
    {
216
        $this->objectWriter->writeRawLine('trailer');
217
        $this->objectWriter->startDictionary();
218
219
        $this->objectWriter->writeName('Id');
220
        $this->objectWriter->startArray();
221
        $this->objectWriter->writeHexadecimalString($this->permanentFileIdentifier);
222
        $this->objectWriter->writeHexadecimalString($this->changingFileIdentifier);
223
        $this->objectWriter->endArray();
224
225
        $this->objectWriter->writeName('Info');
226
        $this->objectWriter->writeIndirectReference($documentInformationId);
227
228
        $this->objectWriter->writeName('Root');
229
        $this->objectWriter->writeIndirectReference($documentCatalogId);
230
231
        $encryption->writeEncryptEntry($this->objectWriter);
232
233
        $this->objectWriter->endDictionary();
234
    }
235
236
    /**
237
     * Writes the footer.
238
     *
239
     * @param int $xrefOffset
240
     */
241
    private function writeFooter($xrefOffset)
242
    {
243
        $this->objectWriter->writeRawLine('');
244
        $this->objectWriter->writeRawLine('startxref');
245
        $this->objectWriter->writeRawLine((string) $xrefOffset);
246
        $this->objectWriter->writeRawLine("%%%EOF");
247
    }
248
}
249