buildXrefStreamPdf function
Builds a PDF in the "modern" 1.5+ layout: an uncompressed cross-reference stream, with the catalog and page tree packed into an object stream.
Implementation
Uint8List buildXrefStreamPdf() {
// objects 1 (catalog), 2 (pages), 3 (page) live inside object stream 4
final inner = <(int, String)>[
(1, '<< /Type /Catalog /Pages 2 0 R >>'),
(2, '<< /Type /Pages /Kids [3 0 R] /Count 1 >>'),
(3, '<< /Type /Page /Parent 2 0 R /MediaBox [0 0 612 792] >>'),
];
var header = '';
var payload = '';
for (final (number, source) in inner) {
header += '$number ${payload.length} ';
payload += '$source ';
}
final objStmData = header + payload;
final first = header.length;
final out = StringBuffer('%PDF-1.5\n');
final objStmOffset = out.length;
out.write('4 0 obj\n<< /Type /ObjStm /N ${inner.length} /First $first '
'/Length ${objStmData.length} >>\nstream\n$objStmData\nendstream\n'
'endobj\n');
final xrefOffset = out.length;
// /W [1 4 2]: 1-byte type, 4-byte offset, 2-byte generation/index
final rows = <List<int>>[
[0, 0, 0xFFFF], // 0: head of the free list
[2, 4, 0], // 1: in object stream 4, index 0
[2, 4, 1],
[2, 4, 2],
[1, objStmOffset, 0], // 4: the object stream itself
[1, xrefOffset, 0], // 5: this xref stream
];
final xrefData = <int>[];
for (final row in rows) {
xrefData
..add(row[0])
..addAll([
(row[1] >> 24) & 0xFF,
(row[1] >> 16) & 0xFF,
(row[1] >> 8) & 0xFF,
row[1] & 0xFF,
])
..addAll([(row[2] >> 8) & 0xFF, row[2] & 0xFF]);
}
out.write('5 0 obj\n<< /Type /XRef /Size 6 /W [1 4 2] /Root 1 0 R '
'/Length ${xrefData.length} >>\nstream\n');
return (BytesBuilder()
..add(ascii(out.toString()))
..add(xrefData)
..add(ascii('\nendstream\nendobj\nstartxref\n$xrefOffset\n%%EOF\n')))
.takeBytes();
}