#!/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 = """
Page 1 content
Page 2 content
Page 3 content
""" 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')