buildXrefStreamPdf function

Uint8List buildXrefStreamPdf()

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();
}