#!/usr/bin/env python3 """Generate DocView's small synthetic PDF corpus (no third-party content). The coordinates, tags, appearances and security actions are intentional and are asserted by test_pdf. qpdf is needed only to regenerate the encrypted fixture. """ from pathlib import Path import subprocess root = Path(__file__).parent def stream(data, extra=''): data = data.encode('ascii') return f'<< /Length {len(data)} {extra} >>\nstream\n'.encode() + data + b'\nendstream' def write_pdf(name, objects): result = bytearray(b'%PDF-1.7\n%\xe2\xe3\xcf\xd3\n') offsets = [0] for index, obj in enumerate(objects, 1): offsets.append(len(result)) if isinstance(obj, str): obj = obj.encode('ascii') result += f'{index} 0 obj\n'.encode() + obj + b'\nendobj\n' xref = len(result) result += f'xref\n0 {len(offsets)}\n0000000000 65535 f \n'.encode() for offset in offsets[1:]: result += f'{offset:010d} 00000 n \n'.encode() result += f'trailer\n<< /Size {len(offsets)} /Root 1 0 R /Info 19 0 R >>\nstartxref\n{xref}\n%%EOF\n'.encode() (root / name).write_bytes(result) objects = [ '<< /Type /Catalog /Pages 2 0 R /Outlines 8 0 R /PageLabels << /Nums [0 << /S /r >> 1 << /S /D /St 1 >>] >> /StructTreeRoot 11 0 R /MarkInfo << /Marked true >> /AcroForm << /Fields [16 0 R] /DR << /Font << /F1 7 0 R >> >> /DA (/F1 12 Tf 0 g) >> /Names << /Dests << /Names [(second) [4 0 R /XYZ 40 200 null]] >> >> >>', '<< /Type /Pages /Kids [3 0 R 4 0 R] /Count 2 >>', '<< /Type /Page /Parent 2 0 R /MediaBox [0 0 240 260] /CropBox [20 30 220 230] /Resources << /Font << /F1 7 0 R >> >> /Contents 5 0 R /StructParents 0 /Annots [14 0 R 15 0 R 16 0 R 18 0 R] >>', '<< /Type /Page /Parent 2 0 R /MediaBox [0 0 300 200] /Rotate 90 /Resources << /Font << /F1 7 0 R >> >> /Contents 6 0 R >>', stream('0 g 40 50 40 40 re f\n/Chapter <> BDC BT /F1 16 Tf 60 180 Td (Heading One) Tj ET EMC\n/H2 <> BDC BT /F1 12 Tf 60 120 Td (Needle) Tj ET EMC\n/H2 <> BDC BT /F1 12 Tf 60 100 Td (Second MCID) Tj ET EMC'), stream('BT /F1 20 Tf 40 100 Td (Second Page Needle) Tj ET'), '<< /Type /Font /Subtype /Type1 /BaseFont /Helvetica >>', '<< /Type /Outlines /First 9 0 R /Last 10 0 R /Count 2 >>', '<< /Title (First heading) /Parent 8 0 R /Dest [3 0 R /XYZ 60 180 null] /Next 10 0 R >>', '<< /Title (Named second) /Parent 8 0 R /Dest (second) /Prev 9 0 R >>', '<< /Type /StructTreeRoot /K [12 0 R 13 0 R] /RoleMap << /Chapter /H1 >> /ParentTree << /Nums [0 [12 0 R 13 0 R 13 0 R]] >> >>', '<< /Type /StructElem /S /Chapter /P 11 0 R /Pg 3 0 R /K 0 >>', '<< /Type /StructElem /S /H2 /P 11 0 R /Pg 3 0 R /K [1 2] >>', '<< /Type /Annot /Subtype /Link /Rect [30 30 90 45] /A << /S /URI /URI (https://example.org/) >> >>', '<< /Type /Annot /Subtype /Link /Rect [100 30 160 45] /A << /S /Launch /F (/tmp/never-execute) >> >>', '<< /Type /Annot /Subtype /Widget /FT /Tx /T (ReadonlyField) /V (Saved) /Ff 1 /F 4 /Rect [120 60 160 90] /P 3 0 R /AP << /N 17 0 R >> >>', stream('1 0 0 rg 0 0 40 30 re f', '/Type /XObject /Subtype /Form /BBox [0 0 40 30] /Resources << >>'), '<< /Type /Annot /Subtype /Text /Rect [180 200 200 220] /Contents (Read-only comment) /F 4 >>', '<< /Title (DocView PDF fixture) /Author (DocView tests) >>' ] write_pdf('navigation.pdf', objects) subprocess.run(['qpdf', '--encrypt', 'reader', 'owner-secret', '256', '--', str(root / 'navigation.pdf'), str(root / 'password.pdf')], check=True) (root / 'corrupt.pdf').write_bytes(b'%PDF-1.7\nThis is deliberately corrupt.\n') missing = list(objects) missing[15] = missing[15].replace(' /AP << /N 17 0 R >>', '') write_pdf('missing-appearance.pdf', missing) reused = list(objects) reused[2] = reused[2].replace('/Resources <<', '/Resources << /XObject << /Fm 20 0 R >>', 1) reused[4] = stream('0 g /Chapter <> BDC BT /F1 16 Tf 60 180 Td (Heading One) Tj ET EMC /Fm Do') reused[11] = reused[11].replace('/S /Chapter', '/S /Chapter /T (Original Heading)') reused.append(stream('/H1 <> BDC BT /F1 12 Tf 50 50 Td (Different Form Heading) Tj ET EMC', '/Type /XObject /Subtype /Form /BBox [0 0 240 260] /Resources << /Font << /F1 7 0 R >> >>')) write_pdf('reused-form-mcid.pdf', reused) broken_pages = list(objects) broken_pages[1] = broken_pages[1].replace('/Kids [3 0 R 4 0 R]', '/Kids [null 4 0 R]') write_pdf('missing-first-page.pdf', broken_pages)