Created
April 7, 2026 00:00
-
-
Save teknoraver/ae21c26a4c22d6e9dd40da45a8d1993e to your computer and use it in GitHub Desktop.
Convert SVG files to a single multi-page PDF using headless Chrome
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/usr/bin/env python3 | |
| """Convert SVG files to a single multi-page PDF using headless Chrome.""" | |
| import glob | |
| import os | |
| import re | |
| import subprocess | |
| import sys | |
| import tempfile | |
| from PyPDF2 import PdfMerger | |
| CHROME = "/Applications/Google Chrome.app/Contents/MacOS/Google Chrome" | |
| SVG_DIR = os.path.dirname(os.path.abspath(__file__)) | |
| OUTPUT = os.path.join(SVG_DIR, "AL.pdf") | |
| def get_page_size(svg_content): | |
| """Extract viewBox dimensions and convert to inches at 96 DPI.""" | |
| m = re.search(r'viewBox="0 0 (\d+) (\d+)"', svg_content) | |
| if m: | |
| w = int(m.group(1)) / 96 | |
| h = int(m.group(2)) / 96 | |
| return w, h | |
| return 8.27, 11.69 # fallback to A4 | |
| def create_wrapper(svg_path, html_path): | |
| """Create an HTML wrapper with zero margins that embeds the SVG inline.""" | |
| with open(svg_path, "r") as f: | |
| svg_content = f.read() | |
| w, h = get_page_size(svg_content) | |
| base_url = f"file://{os.path.dirname(os.path.abspath(svg_path))}/" | |
| html = f"""<!DOCTYPE html> | |
| <html> | |
| <head> | |
| <base href="{base_url}"> | |
| <style> | |
| @page {{ size: {w}in {h}in; margin: 0; }} | |
| * {{ margin: 0; padding: 0; }} | |
| html, body {{ width: 100%; height: 100%; overflow: hidden; }} | |
| </style> | |
| </head> | |
| <body> | |
| {svg_content} | |
| </body> | |
| </html>""" | |
| with open(html_path, "w") as f: | |
| f.write(html) | |
| def svg_to_pdf(svg_path, pdf_path, tmpdir): | |
| """Convert a single SVG to PDF using headless Chrome.""" | |
| html_path = os.path.join(tmpdir, "wrapper.html") | |
| create_wrapper(svg_path, html_path) | |
| html_url = f"file://{html_path}" | |
| result = subprocess.run( | |
| [ | |
| CHROME, | |
| "--headless=new", | |
| "--disable-gpu", | |
| "--no-sandbox", | |
| "--disable-extensions", | |
| "--run-all-compositor-stages-before-draw", | |
| f"--print-to-pdf={pdf_path}", | |
| "--print-to-pdf-no-header", | |
| "--no-pdf-header-footer", | |
| html_url, | |
| ], | |
| capture_output=True, | |
| text=True, | |
| ) | |
| if result.returncode != 0: | |
| print(f" WARNING: Chrome exited with code {result.returncode}", file=sys.stderr) | |
| return os.path.exists(pdf_path) | |
| def main(): | |
| svg_files = sorted(glob.glob(os.path.join(SVG_DIR, "page_*.svg"))) | |
| if not svg_files: | |
| print("No page_*.svg files found") | |
| sys.exit(1) | |
| print(f"Found {len(svg_files)} SVG files") | |
| with tempfile.TemporaryDirectory() as tmpdir: | |
| merger = PdfMerger() | |
| for svg_file in svg_files: | |
| basename = os.path.splitext(os.path.basename(svg_file))[0] | |
| pdf_path = os.path.join(tmpdir, f"{basename}.pdf") | |
| print(f"Converting {os.path.basename(svg_file)}...") | |
| if svg_to_pdf(svg_file, pdf_path, tmpdir): | |
| merger.append(pdf_path) | |
| else: | |
| print(f" FAILED: {svg_file}", file=sys.stderr) | |
| print(f"Merging into {OUTPUT}...") | |
| merger.write(OUTPUT) | |
| merger.close() | |
| print(f"Done: {OUTPUT} ({os.path.getsize(OUTPUT) / 1024 / 1024:.1f} MB)") | |
| if __name__ == "__main__": | |
| main() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment