138 lines
5.1 KiB
Python
138 lines
5.1 KiB
Python
#!/usr/bin/env python3
|
|||
|
|
"""Render the 'wikis that were not ours' markdown essay into a PDF.
|
||
|
|
|
||
|
|
Simple, dependency-light: parses the subset of markdown used in the essay
|
||
|
|
(headings, hr, bullet lists, bold/italic inline, paragraphs) and builds it
|
||
|
|
with reportlab. A4, bookish serif (Times), generous leading.
|
||
|
|
"""
|
||
|
|
|
||
|
|
import re
|
||
|
|
import os
|
||
|
|
from reportlab.lib.pagesizes import A4
|
||
|
|
from reportlab.lib.units import mm
|
||
|
|
from reportlab.lib.styles import ParagraphStyle
|
||
|
|
from reportlab.lib.enums import TA_LEFT, TA_CENTER, TA_JUSTIFY
|
||
|
|
from reportlab.lib.colors import black, HexColor
|
||
|
|
from reportlab.platypus import (
|
||
|
|
SimpleDocTemplate, Paragraph, Spacer, HRFlowable,
|
||
|
|
)
|
||
|
|
|
||
|
|
HERE = os.path.dirname(os.path.abspath(__file__))
|
||
|
|
INPUT = os.path.join(HERE, "README.md")
|
||
|
|
OUTPUT = os.path.join(HERE, "the-wikis-that-were-not-ours.pdf")
|
||
|
|
|
||
|
|
MARGIN = 22 * mm
|
||
|
|
WIDTH = A4[0] - 2 * MARGIN
|
||
|
|
|
||
|
|
INK = black
|
||
|
|
ACCENT = HexColor("#5b4636") # faded brown, like old book covers
|
||
|
|
|
||
|
|
STYLES = {
|
||
|
|
"title": ParagraphStyle(
|
||
|
|
"Title", fontName="Times-Bold", fontSize=22, leading=28,
|
||
|
|
alignment=TA_LEFT, spaceBefore=4 * mm, spaceAfter=2 * mm, textColor=INK),
|
||
|
|
"subtitle": ParagraphStyle(
|
||
|
|
"Subtitle", fontName="Times-Italic", fontSize=11, leading=16,
|
||
|
|
alignment=TA_LEFT, spaceAfter=6 * mm, textColor=HexColor("#444444")),
|
||
|
|
"h2": ParagraphStyle(
|
||
|
|
"H2", fontName="Times-Bold", fontSize=15, leading=20,
|
||
|
|
spaceBefore=9 * mm, spaceAfter=3 * mm, textColor=INK),
|
||
|
|
"h3": ParagraphStyle(
|
||
|
|
"H3", fontName="Times-Bold", fontSize=12.5, leading=17,
|
||
|
|
spaceBefore=6 * mm, spaceAfter=2 * mm, textColor=INK),
|
||
|
|
"body": ParagraphStyle(
|
||
|
|
"Body", fontName="Times-Roman", fontSize=11, leading=17,
|
||
|
|
alignment=TA_JUSTIFY, spaceAfter=3.2 * mm, textColor=INK),
|
||
|
|
"bullet": ParagraphStyle(
|
||
|
|
"Bullet", fontName="Times-Roman", fontSize=11, leading=16,
|
||
|
|
alignment=TA_LEFT, leftIndent=6 * mm, bulletIndent=1.5 * mm,
|
||
|
|
spaceAfter=1.6 * mm, textColor=INK),
|
||
|
|
"colophon": ParagraphStyle(
|
||
|
|
"Colophon", fontName="Times-Italic", fontSize=10.5, leading=15,
|
||
|
|
alignment=TA_LEFT, spaceAfter=2 * mm, textColor=HexColor("#444444")),
|
||
|
|
}
|
||
|
|
|
||
|
|
|
||
|
|
def inline(text: str) -> str:
|
||
|
|
"""Convert markdown inline markup to reportlab XML, escaping the rest."""
|
||
|
|
text = text.replace("&", "&").replace("<", "<").replace(">", ">")
|
||
|
|
text = re.sub(r"\[([^\]]+)\]\(([^)]+)\)", r'<link href="\2" color="#3a5a8c">\1</link>', text)
|
||
|
|
text = re.sub(r"\*\*([^*]+)\*\*", r"<b>\1</b>", text)
|
||
|
|
text = re.sub(r"\*([^*]+)\*", r"<i>\1</i>", text)
|
||
|
|
return text
|
||
|
|
|
||
|
|
|
||
|
|
def build():
|
||
|
|
with open(INPUT, "r", encoding="utf-8") as f:
|
||
|
|
lines = f.read().splitlines()
|
||
|
|
|
||
|
|
elements = []
|
||
|
|
i = 0
|
||
|
|
first_h1 = True
|
||
|
|
while i < len(lines):
|
||
|
|
raw = lines[i]
|
||
|
|
line = raw.strip()
|
||
|
|
i += 1
|
||
|
|
|
||
|
|
if not line:
|
||
|
|
continue
|
||
|
|
if line == "---":
|
||
|
|
elements.append(Spacer(1, 2 * mm))
|
||
|
|
elements.append(HRFlowable(width="100%", thickness=0.6,
|
||
|
|
color=ACCENT, spaceBefore=2 * mm,
|
||
|
|
spaceAfter=2 * mm))
|
||
|
|
continue
|
||
|
|
|
||
|
|
if line.startswith("# "):
|
||
|
|
style = STYLES["title"] if first_h1 else STYLES["h2"]
|
||
|
|
elements.append(Paragraph(inline(line[2:]), style))
|
||
|
|
first_h1 = False
|
||
|
|
continue
|
||
|
|
if line.startswith("## "):
|
||
|
|
elements.append(Paragraph(inline(line[3:]), STYLES["h2"]))
|
||
|
|
continue
|
||
|
|
if line.startswith("### "):
|
||
|
|
elements.append(Paragraph(inline(line[4:]), STYLES["h3"]))
|
||
|
|
continue
|
||
|
|
|
||
|
|
if re.match(r"^- ", line):
|
||
|
|
items = []
|
||
|
|
while i < len(lines) and lines[i].strip().startswith("- "):
|
||
|
|
items.append(lines[i].strip()[2:])
|
||
|
|
i += 1
|
||
|
|
for item in items:
|
||
|
|
elements.append(Paragraph(inline(item), STYLES["bullet"],
|
||
|
|
bulletText="\u2022"))
|
||
|
|
continue
|
||
|
|
|
||
|
|
# Collect a paragraph (until blank line or structural line)
|
||
|
|
para = [line]
|
||
|
|
while i < len(lines):
|
||
|
|
nxt = lines[i].strip()
|
||
|
|
if (not nxt or nxt == "---" or nxt.startswith("#")
|
||
|
|
or nxt.startswith("- ")):
|
||
|
|
break
|
||
|
|
para.append(nxt)
|
||
|
|
i += 1
|
||
|
|
text = " ".join(para)
|
||
|
|
# Italic-only lines (e.g. the source line, the closing — K.H.)
|
||
|
|
if re.fullmatch(r"\*[^*].*\*", text) and not text.startswith("*An"):
|
||
|
|
elements.append(Paragraph(inline(text), STYLES["colophon"]))
|
||
|
|
else:
|
||
|
|
elements.append(Paragraph(inline(text), STYLES["body"]))
|
||
|
|
|
||
|
|
# Drop the README H1 duplicate? No — the README's H1 is the essay title; keep it.
|
||
|
|
doc = SimpleDocTemplate(
|
||
|
|
OUTPUT, pagesize=A4,
|
||
|
|
leftMargin=MARGIN, rightMargin=MARGIN,
|
||
|
|
topMargin=24 * mm, bottomMargin=24 * mm,
|
||
|
|
title="The Wikis That Were Not Ours",
|
||
|
|
author="Kevin Hermes",
|
||
|
|
subject="What the AI agents at collusion.wiki actually did",
|
||
|
|
)
|
||
|
|
doc.build(elements)
|
||
|
|
print(f"PDF written: {OUTPUT} ({os.path.getsize(OUTPUT)} bytes)")
|
||
|
|
|
||
|
|
|
||
|
|
if __name__ == "__main__":
|
||
|
|
build()
|