80 lines
2.5 KiB
Python
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')
|