msds-tds/scripts/generate-numbered-pdf.py
2026-07-08 13:12:20 +00:00

80 lines
2.5 KiB
Python

#!/usr/bin/env python3
"""Two-pass PDF generator: WeasyPrint + pikepdf page numbering."""
from weasyprint import HTML
import pikepdf
import io, os, re
def generate_sds_with_page_numbers(html, output_path):
"""Generate PDF with 'Page X of Y' numbering on each page."""
# Pass 1: generate PDF, measure pages
buf = io.BytesIO()
HTML(string=html).write_pdf(buf)
buf.seek(0)
pdf = pikepdf.Pdf.open(buf)
total_pages = len(pdf.pages)
if total_pages <= 1:
pdf.save(output_path)
pdf.close()
return total_pages
# Pass 2: add page numbers via PDF annotation objects
# We add a simple text annotation at the bottom center of each page
# Load font info - we need standard fonts
for page_num, page in enumerate(pdf.pages, 1):
# Get page dimensions
mediabox = page.media_box
width = float(mediabox[2])
bottom_y = 56 # ~1.5cm from bottom
text = f"Page {page_num} of {total_pages}"
# Create a content stream for the page number
content = pikepdf.ContentStream(pdf)
# PDF operators for text rendering
content.operations.extend([
pikepdf.StreamOperation('BT', []),
pikepdf.StreamOperation('q', []),
pikepdf.StreamOperation('1 0 0 1 0 0 cm', []),
pikepdf.StreamOperation('0.357 0.400 0.435 RG', []), # #5B6670 RGB
pikepdf.StreamOperation('0.357 0.400 0.435 rg', []),
# No custom fonts - use built-in Helvetica
pikepdf.StreamOperation('/Helvetica 7 Tf', []),
# Position
pikepdf.StreamOperation(f'{width/2:.0f} {bottom_y} Td', []),
pikepdf.StreamOperation(f'({text}) Tj', []),
pikepdf.StreamOperation('Q', []),
pikepdf.StreamOperation('ET', []),
])
page.add_content(content)
pdf.save(output_path)
pdf.close()
return total_pages
if __name__ == '__main__':
# Test
test_html = """<html><body>
<style>
@page { size: A4; margin: 2cm; }
body { font-family: sans-serif; }
</style>
<p>Page 1 content</p>
<p>Page 2 content</p>
<p>Page 3 content</p>
</body></html>"""
n = generate_sds_with_page_numbers(test_html, '/tmp/test_numbered.pdf')
print(f'Generated {n} pages')
# Verify
with pikepdf.Pdf.open('/tmp/test_numbered.pdf') as pdf:
for i, page in enumerate(pdf.pages, 1):
print(f'Page {i}: {len(page.contents)} content streams')