mirror of
https://github.com/Anon-Planet/thgtoa.git
synced 2026-06-11 00:02:29 +02:00
2/8 refactor(pdf): wire dark mode through convert.py
Removes the dead Chromium dark mode path and BeautifulSoup CSS injection code. Dark PDF is now produced by calling convert.py on the finished light PDF. --both builds light then dark; --dark alone works if the light PDF already exists. Signed-off-by: nopeitsnothing <no@anonymousplanet.org>
This commit is contained in:
+66
-96
@@ -1,16 +1,14 @@
|
|||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
"""Experimental dark mode support.
|
"""Build light-mode PDF with MkDocs + Chromium, then produce dark-mode PDF via convert.py.
|
||||||
|
|
||||||
This script builds both light and dark mode MkDocs site, then renders docs/guide/ to single PDFs via Chromium.
|
|
||||||
|
|
||||||
Usage:
|
Usage:
|
||||||
python scripts/build_guide_pdf.py # Generate light mode PDF only
|
python scripts/build_guide_pdf.py # Light PDF only
|
||||||
python scripts/build_guide_pdf.py --dark-mode # Generate dark mode PDF only
|
python scripts/build_guide_pdf.py --dark # Dark PDF only (requires light PDF to exist)
|
||||||
python scripts/build_guide_pdf.py --both # Generate both light and dark mode PDFs
|
python scripts/build_guide_pdf.py --both # Light PDF, then dark PDF
|
||||||
|
|
||||||
Examples:
|
Examples:
|
||||||
python scripts/build_guide_pdf.py --site-dir build/html --pdf-light export/thgtoa.pdf
|
python scripts/build_guide_pdf.py --site-dir build/html --pdf-light export/thgtoa.pdf
|
||||||
python scripts/build_guide_pdf.py --dark-mode --pdf-dark export/thgtoa-dark.pdf
|
python scripts/build_guide_pdf.py --both --pdf-light export/thgtoa.pdf --pdf-dark export/thgtoa-dark.pdf
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
@@ -68,15 +66,11 @@ def run_mkdocs(site_dir: Path) -> None:
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def print_to_pdf(browser: Path, html_file: Path, pdf_out: Path, dark_mode: bool = False) -> Path:
|
def print_to_pdf(browser: Path, html_file: Path, pdf_out: Path) -> Path:
|
||||||
"""Write PDF to ``pdf_out``. Uses a temp file first so an open ``guide.pdf`` on Windows
|
"""Render html_file to pdf_out via headless Chromium.
|
||||||
does not block the build: if the final path is locked, writes ``guide-new.pdf`` instead.
|
|
||||||
|
|
||||||
Args:
|
Uses a temp file first so an open guide.pdf on Windows does not block the
|
||||||
browser: Path to Chromium executable
|
build; if the final path is still locked, writes guide-new.pdf instead.
|
||||||
html_file: Path to HTML file to convert
|
|
||||||
pdf_out: Output PDF path
|
|
||||||
dark_mode: If True, use dark mode color scheme via --prefers-color-scheme flag
|
|
||||||
"""
|
"""
|
||||||
pdf_out.parent.mkdir(parents=True, exist_ok=True)
|
pdf_out.parent.mkdir(parents=True, exist_ok=True)
|
||||||
partial = pdf_out.parent / f".{pdf_out.name}.writing"
|
partial = pdf_out.parent / f".{pdf_out.name}.writing"
|
||||||
@@ -84,32 +78,23 @@ def print_to_pdf(browser: Path, html_file: Path, pdf_out: Path, dark_mode: bool
|
|||||||
|
|
||||||
uri = html_file.resolve().as_uri()
|
uri = html_file.resolve().as_uri()
|
||||||
|
|
||||||
# Chromium headless print; allow time for fonts/images on very large pages.
|
|
||||||
cmd = [str(browser)]
|
cmd = [str(browser)]
|
||||||
if os.environ.get("CI"):
|
if os.environ.get("CI"):
|
||||||
# GitHub Actions / other CI runners often need these for Chromium to start.
|
|
||||||
cmd += [
|
cmd += [
|
||||||
"--no-sandbox",
|
"--no-sandbox",
|
||||||
"--disable-setuid-sandbox",
|
"--disable-setuid-sandbox",
|
||||||
"--disable-dev-shm-usage",
|
"--disable-dev-shm-usage",
|
||||||
]
|
]
|
||||||
|
|
||||||
cmd += [
|
cmd += [
|
||||||
"--headless=new",
|
"--headless=new",
|
||||||
"--disable-gpu",
|
"--disable-gpu",
|
||||||
"--no-pdf-header-footer",
|
"--no-pdf-header-footer",
|
||||||
]
|
|
||||||
|
|
||||||
# Add dark mode preference if requested
|
|
||||||
if dark_mode:
|
|
||||||
cmd.append("--prefers-color-scheme=dark")
|
|
||||||
|
|
||||||
cmd += [
|
|
||||||
f"--print-to-pdf={partial.resolve()}",
|
f"--print-to-pdf={partial.resolve()}",
|
||||||
uri,
|
uri,
|
||||||
]
|
]
|
||||||
|
|
||||||
subprocess.run(cmd, check=True, timeout=600)
|
subprocess.run(cmd, check=True, timeout=600)
|
||||||
|
|
||||||
deadline = time.time() + 120
|
deadline = time.time() + 120
|
||||||
while time.time() < deadline:
|
while time.time() < deadline:
|
||||||
if partial.exists() and partial.stat().st_size > 0:
|
if partial.exists() and partial.stat().st_size > 0:
|
||||||
@@ -131,42 +116,23 @@ def print_to_pdf(browser: Path, html_file: Path, pdf_out: Path, dark_mode: bool
|
|||||||
partial.replace(pdf_out)
|
partial.replace(pdf_out)
|
||||||
return pdf_out
|
return pdf_out
|
||||||
|
|
||||||
|
# Use scripts/convert.py in place of broken dark-mode hack
|
||||||
def generate_dark_mode_html(html_file: Path, output_file: Path, dark_css_path: Path) -> None:
|
def build_dark_pdf(light_pdf: Path, dark_pdf: Path) -> Path:
|
||||||
"""Create a temporary HTML file with dark mode stylesheet applied.
|
"""Convert the light PDF to dark mode using scripts/convert.py."""
|
||||||
|
convert_script = repo_root() / "scripts" / "convert.py"
|
||||||
This is used when we need to force dark mode rendering via CSS rather than browser flags.
|
print(f"Converting {light_pdf.name} → {dark_pdf.name} (dark mode)…")
|
||||||
"""
|
subprocess.run(
|
||||||
try:
|
[sys.executable, str(convert_script), str(light_pdf), str(dark_pdf)],
|
||||||
from bs4 import BeautifulSoup
|
check=True,
|
||||||
|
)
|
||||||
# Read the original HTML
|
return dark_pdf
|
||||||
html_content = html_file.read_text(encoding='utf-8')
|
|
||||||
soup = BeautifulSoup(html_content, 'html.parser')
|
|
||||||
|
|
||||||
# Add dark mode stylesheet link if not present
|
|
||||||
existing_links = [link.get('href', '') for link in soup.find_all('link', rel='stylesheet')]
|
|
||||||
if not any(dark_css_path.name in link for link in existing_links):
|
|
||||||
head = soup.head or soup.new_tag('head')
|
|
||||||
link_tag = soup.new_tag('link', rel='stylesheet', href=str(dark_css_path))
|
|
||||||
if soup.head:
|
|
||||||
soup.head.append(link_tag)
|
|
||||||
else:
|
|
||||||
# Create a new head section
|
|
||||||
new_head = soup.new_tag('head')
|
|
||||||
new_head.append(link_tag)
|
|
||||||
soup.insert(0, new_head)
|
|
||||||
|
|
||||||
# Write the modified HTML
|
|
||||||
output_file.parent.mkdir(parents=True, exist_ok=True)
|
|
||||||
output_file.write_text(str(soup), encoding='utf-8')
|
|
||||||
except ImportError:
|
|
||||||
print("BeautifulSoup not available. Skipping CSS injection.")
|
|
||||||
|
|
||||||
|
|
||||||
def main() -> int:
|
def main() -> int:
|
||||||
root = repo_root()
|
root = repo_root()
|
||||||
ap = argparse.ArgumentParser(description="Build MkDocs + single-page guide PDF (light and/or dark mode).")
|
ap = argparse.ArgumentParser(
|
||||||
|
description="Build MkDocs + single-page guide PDF (light and/or dark mode)."
|
||||||
|
)
|
||||||
ap.add_argument(
|
ap.add_argument(
|
||||||
"--site-dir",
|
"--site-dir",
|
||||||
type=Path,
|
type=Path,
|
||||||
@@ -177,68 +143,72 @@ def main() -> int:
|
|||||||
"--pdf-light",
|
"--pdf-light",
|
||||||
type=Path,
|
type=Path,
|
||||||
default=root / "export" / "thgtoa.pdf",
|
default=root / "export" / "thgtoa.pdf",
|
||||||
help="Output PDF path for light mode (default: ./export/thgtoa.pdf)",
|
help="Output path for light PDF (default: ./export/thgtoa.pdf)",
|
||||||
)
|
)
|
||||||
ap.add_argument(
|
ap.add_argument(
|
||||||
"--pdf-dark",
|
"--pdf-dark",
|
||||||
type=Path,
|
type=Path,
|
||||||
default=root / "export" / "thgtoa-dark.pdf",
|
default=root / "export" / "thgtoa-dark.pdf",
|
||||||
help="Output PDF path for dark mode (default: ./export/thgtoa-dark.pdf)",
|
help="Output path for dark PDF (default: ./export/thgtoa-dark.pdf)",
|
||||||
)
|
)
|
||||||
ap.add_argument("--skip-mkdocs", action="store_true", help="Reuse existing site dir; only run print-to-pdf.")
|
ap.add_argument(
|
||||||
ap.add_argument("--dark-mode", action="store_true", help="Generate dark mode PDF only")
|
"--skip-mkdocs",
|
||||||
ap.add_argument("--both", action="store_true", help="Generate both light and dark mode PDFs")
|
action="store_true",
|
||||||
|
help="Reuse existing site dir; skip MkDocs build.",
|
||||||
|
)
|
||||||
|
|
||||||
|
mode = ap.add_mutually_exclusive_group()
|
||||||
|
mode.add_argument("--dark", action="store_true", help="Dark PDF only (light PDF must already exist)")
|
||||||
|
mode.add_argument("--both", action="store_true", help="Build light PDF, then dark PDF")
|
||||||
|
# default (no flag) = light only
|
||||||
args = ap.parse_args()
|
args = ap.parse_args()
|
||||||
|
|
||||||
# Determine which modes to generate
|
build_light = not args.dark
|
||||||
if args.dark_mode:
|
build_dark = args.dark or args.both
|
||||||
modes = ["dark"]
|
|
||||||
elif args.both:
|
|
||||||
modes = ["light", "dark"]
|
|
||||||
else:
|
|
||||||
modes = ["light"]
|
|
||||||
|
|
||||||
guide_html = args.site_dir / "guide" / "index.html"
|
# --- Light PDF (Chromium) ---
|
||||||
|
if build_light:
|
||||||
|
guide_html = args.site_dir / "guide" / "index.html"
|
||||||
|
|
||||||
if not args.skip_mkdocs or any(mode == "light" for mode in modes):
|
if not args.skip_mkdocs:
|
||||||
run_mkdocs(args.site_dir)
|
run_mkdocs(args.site_dir)
|
||||||
|
|
||||||
if not guide_html.is_file():
|
if not guide_html.is_file():
|
||||||
print(f"Missing {guide_html}; run without --skip-mkdocs first.", file=sys.stderr)
|
print(f"Missing {guide_html}; run without --skip-mkdocs first.", file=sys.stderr)
|
||||||
return 1
|
return 1
|
||||||
|
|
||||||
browser = find_chromium_executable()
|
browser = find_chromium_executable()
|
||||||
if not browser:
|
if not browser:
|
||||||
print(
|
print(
|
||||||
"No Chromium-based browser found (Chrome, Edge, or Chromium). "
|
"No Chromium-based browser found (Chrome, Edge, or Chromium). "
|
||||||
"Install Google Chrome or Microsoft Edge, or add Chromium to PATH.",
|
"Install Google Chrome or Microsoft Edge, or add Chromium to PATH.",
|
||||||
file=sys.stderr,
|
file=sys.stderr,
|
||||||
)
|
)
|
||||||
return 1
|
return 1
|
||||||
|
|
||||||
dark_css_path = root / "docs" / "stylesheets" / "dark-extra.css"
|
out_light = print_to_pdf(browser, guide_html, args.pdf_light)
|
||||||
|
|
||||||
# Generate light mode PDF (default)
|
|
||||||
if "light" in modes:
|
|
||||||
out_light = print_to_pdf(browser, guide_html, args.pdf_light, dark_mode=False)
|
|
||||||
size_kb = out_light.stat().st_size // 1024
|
size_kb = out_light.stat().st_size // 1024
|
||||||
print(f"Wrote {out_light.resolve()} ({size_kb} KiB) [Light Mode]")
|
print(f"Wrote {out_light.resolve()} ({size_kb} KiB) [Light Mode]")
|
||||||
if out_light.resolve() != args.pdf_light.resolve():
|
if out_light.resolve() != args.pdf_light.resolve():
|
||||||
print(
|
print(
|
||||||
f"Note: {args.pdf_light.name} was in use; close it and rename or replace with the file above.",
|
f"Note: {args.pdf_light.name} was in use; "
|
||||||
|
"close it and rename or replace with the file above.",
|
||||||
file=sys.stderr,
|
file=sys.stderr,
|
||||||
)
|
)
|
||||||
|
|
||||||
# Generate dark mode PDF
|
# --- Dark PDF (pixel converter) ---
|
||||||
if "dark" in modes:
|
if build_dark:
|
||||||
out_dark = print_to_pdf(browser, guide_html, args.pdf_dark, dark_mode=True)
|
if not args.pdf_light.exists():
|
||||||
size_kb = out_dark.stat().st_size // 1024
|
|
||||||
print(f"Wrote {out_dark.resolve()} ({size_kb} KiB) [Dark Mode]")
|
|
||||||
if out_dark.resolve() != args.pdf_dark.resolve():
|
|
||||||
print(
|
print(
|
||||||
f"Note: {args.pdf_dark.name} was in use; close it and rename or replace with the file above.",
|
f"Light PDF not found at {args.pdf_light}. "
|
||||||
|
"Run without --dark first, or use --both.",
|
||||||
file=sys.stderr,
|
file=sys.stderr,
|
||||||
)
|
)
|
||||||
|
return 1
|
||||||
|
|
||||||
|
out_dark = build_dark_pdf(args.pdf_light, args.pdf_dark)
|
||||||
|
size_kb = out_dark.stat().st_size // 1024
|
||||||
|
print(f"Wrote {out_dark.resolve()} ({size_kb} KiB) [Dark Mode]")
|
||||||
|
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user