Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
100.00% covered (success)
100.00%
9 / 9
100.00% covered (success)
100.00%
3 / 3
CRAP
100.00% covered (success)
100.00%
1 / 1
CrossReferenceTable
100.00% covered (success)
100.00%
9 / 9
100.00% covered (success)
100.00%
3 / 3
4
100.00% covered (success)
100.00%
1 / 1
 add
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getEntries
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 build
100.00% covered (success)
100.00%
7 / 7
100.00% covered (success)
100.00%
1 / 1
2
1<?php
2
3declare(strict_types=1);
4
5namespace Phpdftk\Pdf\Core\File;
6
7/**
8 * Builds the classic PDF cross-reference table (ISO 32000-2 section 7.5.4).
9 *
10 * The fixed 20-byte entry width is mandated by the spec so that readers can
11 * seek directly to any entry by index without parsing the entire table:
12 *
13 *   OOOOOOOOOO GGGGG n\r\n   (in-use object: O = 10-digit byte offset)
14 *   OOOOOOOOOO GGGGG f\r\n   (free object: O = next free object number)
15 *
16 * Per ยง7.5.4 the two-character EOL is one of `SP CR`, `SP LF`, or `CR LF`;
17 * we use `CR LF` (no trailing space) so the entry totals exactly 20 bytes.
18 *
19 * Object 0 is always emitted as the free-list head with generation 65535,
20 * which signals that it can never be reused.
21 */
22class CrossReferenceTable
23{
24    /** @var array<int, int> objectNumber => byte offset */
25    private array $entries = [];
26
27    /**
28     * Record the byte offset for an in-use object.
29     */
30    public function add(int $objNum, int $offset): void
31    {
32        $this->entries[$objNum] = $offset;
33    }
34
35    /**
36     * Return recorded entries (objectNumber => byte offset).
37     *
38     * @return array<int, int>
39     */
40    public function getEntries(): array
41    {
42        return $this->entries;
43    }
44
45    /**
46     * Build and return the complete xref section as a string.
47     * The returned string starts with "xref\n" and ends with the last entry (no trailing newline).
48     */
49    public function build(int $size): string
50    {
51        $xref = "xref\n";
52        $xref .= sprintf("0 %d\n", $size);
53
54        // Object 0: free list head โ€” generation 65535, free.
55        // Layout: 10-digit offset + SP + 5-digit gen + SP + 'f' + CRLF = 20 bytes.
56        $xref .= "0000000000 65535 f\r\n";
57
58        // Objects 1..N โ€” same 20-byte layout as the free-list head.
59        for ($i = 1; $i < $size; $i++) {
60            $offset = $this->entries[$i] ?? 0;
61            $xref .= sprintf("%010d 00000 n\r\n", $offset);
62        }
63
64        return $xref;
65    }
66}