Files
cs249r_book/books/shared/scripts/frontmatter_float_labels.py
Vijay Janapa Reddi 1b92400e13 books: number front-matter figures and tables P.1, P.2, ...
Floats before Chapter 1 (each preface's dependency map, vol4's reading-track
table) have no chapter number and printed as 0.1. The shared LaTeX header
now numbers them P.1 when the chapter counter is 0, so every volume gets it;
chapters and appendices are unchanged. Quarto offers no supported prefix for
HTML, so a post-render script (frontmatter_float_labels.py) relabels
captions, lightbox titles, and in-page references on pages under any
frontmatter/ folder to match.

Also aligns the vol2-vol4 map captions with vol1: "builds on its results".
2026-10-13 20:40:56 +02:00

64 lines
2.2 KiB
Python

#!/usr/bin/env python3
"""
Post-render step: number front-matter figures and tables P.1, P.2, ...
Figures and tables in front matter (preface, prerequisites) sit before
Chapter 1 and have no chapter number. The PDF numbers them P.1, P.2, ...
(shared/tex/header-includes.tex). Quarto's HTML output numbers them 1, 2, ...
and offers no supported prefix option (2026-10-13: its crossref schema accepts
only a numbering style, a label list fails at render time, and user filters
cannot reach its number formatter). This script rewrites the rendered HTML of
every page under a frontmatter/ folder so captions and in-page references
match the PDF. It is idempotent: an already-prefixed number does not match.
"""
import os
import re
import sys
from pathlib import Path
# Caption prefix ("Figure 1:"), also repeated in the lightbox title
# attribute, and cross-reference link text ("figure 1").
CAPTION = re.compile(r'(>\s*|title=")(Figure|Table) (\d+)(:)')
XREF = re.compile(r'(class="quarto-xref">)(Figure|figure|Table|table)&nbsp;(\d+)(<)')
def relabel(html_path: Path) -> int:
text = html_path.read_text(encoding="utf-8")
count = 0
def sub(m: re.Match) -> str:
nonlocal count
count += 1
return f"{m.group(1)}{m.group(2)}&nbsp;P.{m.group(3)}{m.group(4)}"
new_text = XREF.sub(sub, CAPTION.sub(sub, text))
if count:
html_path.write_text(new_text, encoding="utf-8")
return count
def main() -> None:
if len(sys.argv) > 1:
build_dir = Path(sys.argv[1])
elif os.environ.get("QUARTO_PROJECT_OUTPUT_DIR"):
build_dir = Path(os.environ["QUARTO_PROJECT_OUTPUT_DIR"])
else:
build_dir = Path(__file__).resolve().parents[2] / "_build"
if not build_dir.is_dir():
sys.exit(f"[frontmatter-labels] build directory not found: {build_dir}")
total = files = 0
for html_file in sorted(build_dir.rglob("*.html")):
if "frontmatter" not in html_file.parts:
continue
n = relabel(html_file)
if n:
total += n
files += 1
print(f"[frontmatter-labels] Relabeled {total} front-matter float number(s) in {files} file(s).")
if __name__ == "__main__":
main()