50f95a5403
- 00-workshop/regiekaart: het programma op een kaart, met schrapvolgorde en noodgevallen - 00-presentatie/bouw_notities_pdf.py: per dia een A4 met beeld en notities - de notitieversie zelf blijft buiten git (privenotities) Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
105 lines
5.2 KiB
Python
Vendored
105 lines
5.2 KiB
Python
Vendored
#!/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'<link rel="stylesheet" href="{f["href"]}">' 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'<!DOCTYPE html><html lang="nl"><head><meta charset="utf-8">{faces}<style>{css_dia}</style></head>'
|
|
f"<body>{slides_html}</body></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"<aside>(.*?)</aside>", (P / "slides" / f"{sid}.html").read_text(encoding="utf-8"), re.S)
|
|
tekst = tekst.group(1).strip() if tekst else ""
|
|
if not tekst:
|
|
return "<p class='leeg'>Geen notities bij deze dia.</p>"
|
|
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"<p><b>{html.escape(m.group(1))}</b> {html.escape(m.group(2))}</p>")
|
|
else:
|
|
regels.append(f"<p>{html.escape(deel)}</p>")
|
|
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'<div class="kaart"><div class="kop"><b>Dia {n} · {html.escape(sid)}</b>'
|
|
f'<span>{html.escape(deck["title"])} · notitieversie</span></div>'
|
|
f'<img src="{beelden[n - 1].as_uri()}" alt="Dia {n}">'
|
|
f'<div class="notities">{notities(sid)}</div></div>')
|
|
pagina = tmp / "notities.html"
|
|
pagina.write_text(f'<!DOCTYPE html><html lang="nl"><head><meta charset="utf-8">{faces}<style>{css}</style></head>'
|
|
f'<body>{"".join(kaarten)}</body></html>', 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})")
|