addKid($page); $catalog = new DocumentCatalog($pages); // 4. Choose objects that will be members of the /ObjStm stream. // Numbers 40 to 43 are explicitly reserved; the writer avoids collisions // when assigning other numbers. Every member has generation zero. // $contents, itself a stream, remains an indirect object outside the container. // The second FlateDecode compresses the whole container independently of content. $objects = (new ObjectStream( new ObjectStreamEntry(new ObjectNumber(40), $catalog), new ObjectStreamEntry(new ObjectNumber(41), $pages), new ObjectStreamEntry(new ObjectNumber(42), $page), new ObjectStreamEntry(new ObjectNumber(43), $font), ))->withFilters(new FlateDecode()); // 5. Select a cross-reference stream (/Type /XRef). // Its type 2 entries identify the container and compressed-object indices. // /Root still targets the catalog even when it is inside /ObjStm. // Both representations have existed since PDF 1.5; our header is 1.7. $file = new FileStructure( new FileHeader('1.7'), new FileBody($objects), new IndirectReference($catalog), null, CrossReferenceFormat::Stream, ); // 6. Write the PDF. Page rendering depends on content and resources, // while /ObjStm and /XRef organize objects within the file's bytes. $path = $argv[1] ?? __DIR__ . '/object-stream.pdf'; $file->writeTo($path); echo $path . PHP_EOL;