#!/usr/bin/env python3 """Maakt een notitieversie van de presentatie: per dia een A4 met het beeld bovenaan en de sprekersnotities eronder. Om te printen of op een tweede scherm te openen terwijl je presenteert. Gebruik: python3 bouw_notities_pdf.py Uitvoer: presentatie-met-notities.pdf (staat niet in git; de begeleidersnotities zijn privé) Staat notities-begeleider.json naast dit script, dan gebruikt hij die uitgebreide notities. Anders de korte notities die in de dia's zelf zitten. Na afloop worden de dia-bestanden teruggezet op de publieke versie, zodat bouw_pdf.py en de repo ongewijzigd blijven. """ import html import json import re import subprocess import sys import tempfile from pathlib import Path HERE = Path(__file__).resolve().parent P = HERE / "project" PRIVE = (HERE / "notities-begeleider.json").exists() # 1. dia's bouwen, met de privénotities als die er zijn subprocess.run([sys.executable, str(HERE / "maak_dias.py")] + (["--notities"] if PRIVE else []), check=True) deck = json.loads((P / "deck.json").read_text(encoding="utf-8")) faces = "".join(f'' for f in deck["faces"].values()) # 2. eerst een tijdelijke dia-PDF, dan per dia een afbeelding tmp = Path(tempfile.mkdtemp(prefix="notities-")) css_dia = """ @page { size: 1920px 1080px; margin: 0; } html, body { margin: 0; padding: 0; } * { box-sizing: border-box; margin: 0; padding: 0; } section { position: relative; width: 1920px; height: 1080px; overflow: hidden; page-break-after: always; break-after: page; } section:last-child { page-break-after: auto; } aside { display: none; } ul { padding-left: 1.1em; } table { border-collapse: collapse; width: 100%; } th, td { border: 1px solid #D8D2C4; padding: 12px 18px; text-align: left; vertical-align: top; } img { max-width: 100%; } """ slides_html = "".join((P / "slides" / f"{sid}.html").read_text(encoding="utf-8") for sid in deck["order"]) (tmp / "dias.html").write_text( f'{faces}' f"{slides_html}", encoding="utf-8") subprocess.run(["google-chrome", "--headless=new", "--disable-gpu", "--no-pdf-header-footer", "--virtual-time-budget=8000", f"--print-to-pdf={tmp / 'dias.pdf'}", (tmp / "dias.html").as_uri()], check=True, capture_output=True) subprocess.run(["pdftoppm", "-r", "80", "-jpeg", str(tmp / "dias.pdf"), str(tmp / "dia")], check=True) beelden = sorted(tmp.glob("dia-*.jpg")) def notities(sid): tekst = re.search(r"", (P / "slides" / f"{sid}.html").read_text(encoding="utf-8"), re.S) tekst = tekst.group(1).strip() if tekst else "" if not tekst: return "

Geen notities bij deze dia.

" regels = [] for deel in [d.strip() for d in tekst.split(" | ") if d.strip()]: m = re.match(r"^([A-Z]{4,10}):\s*(.*)$", deel, re.S) if m: regels.append(f"

{html.escape(m.group(1))} {html.escape(m.group(2))}

") else: regels.append(f"

{html.escape(deel)}

") return "".join(regels) # 3. per dia een A4 met beeld en notities css = """ @page { size: A4 portrait; margin: 14mm 14mm 12mm; } html, body { margin: 0; padding: 0; } * { box-sizing: border-box; } body { font-family: "Nunito Sans", Verdana, sans-serif; color: #1E2A3A; } .kaart { page-break-after: always; break-after: page; } .kaart:last-child { page-break-after: auto; } .kop { display: flex; justify-content: space-between; align-items: baseline; font-size: 10pt; color: #8A8F98; border-bottom: 1px solid #D8D2C4; padding-bottom: 4px; margin-bottom: 8px; } .kop b { font-family: "Rubik", Arial, sans-serif; font-size: 12pt; color: #1E2A3A; } img { width: 100%; border: 1px solid #D8D2C4; display: block; } .notities { margin-top: 10px; font-size: 11pt; line-height: 1.45; } .notities p { margin: 0 0 7px; } .notities b { font-family: "Rubik", Arial, sans-serif; color: #D9480F; letter-spacing: .5px; font-size: 10pt; } .leeg { color: #8A8F98; font-style: italic; } """ kaarten = [] for n, sid in enumerate(deck["order"], 1): kaarten.append( f'
Dia {n} · {html.escape(sid)}' f'{html.escape(deck["title"])} · notitieversie
' f'Dia {n}' f'
{notities(sid)}
') pagina = tmp / "notities.html" pagina.write_text(f'{faces}' f'{"".join(kaarten)}', encoding="utf-8") pdf = HERE / "presentatie-met-notities.pdf" subprocess.run(["google-chrome", "--headless=new", "--disable-gpu", "--no-pdf-header-footer", "--virtual-time-budget=8000", f"--print-to-pdf={pdf}", pagina.as_uri()], check=True, capture_output=True) # 4. de dia-bestanden terugzetten op de publieke versie subprocess.run([sys.executable, str(HERE / "maak_dias.py")], check=True, capture_output=True) soort = "uitgebreide begeleidersnotities" if PRIVE else "korte notities uit de dia's" print(f"{pdf.name}: {pdf.stat().st_size // 1024} kB, {len(deck['order'])} dia's ({soort})")