Files
Vijay Janapa Reddi ce4018e168 notation: make Volume I consistent with its notation table; split the series core
Volume I is in print, so the goal was consistency with the fewest changes.
A 17-auditor read of vol1 against the notation table found 284 distinct
findings; one fixer per chapter applied the regressions (23, from the
earlier pattern-based passes), mechanical fixes (208), and the approved
conventions, matching the surrounding prose.

Notation table (prints in vol1):
- Errata: the subscript sentence (the subscript is upright, not the
  root); the decimal-units rule now states the nameplate-capacity
  exception the assumptions appendix already used; notes no longer
  point at symbols the table never defines.
- Rows only where chapters improvised for want of one: d_in/d_out,
  y-hat, T_distill, T_memory/T_compute, N_GPU, L_lat,total, s_sample,
  s_loss; sentences for GEMM M,N,K, \mathcal{O}, l_p norms, mu, power
  subscripts, and upright p99.

Structure for four volumes:
- The shared body is split into a series core (head + tail, printed in
  every volume) and a vol1-only degradation extension, so vol2-4 no
  longer print tau/lambda meanings their extensions use differently.
  Vol1's assembled text differs only by the intended edits.
- New `binder check notation --scope vol1-print-pin`: fails when vol1's
  printed notation text changes; re-pin only for an approved erratum
  (binder/tools/audit/repin_vol1_notation.py).
- Tooling updated for the new file names (notation, currency-style,
  layout, math-render audits).

Also restores footnote markers and index tags dropped by the figure
reference rewrites (5 footnotes, 6 index entries), fixes the MIT Press
spellings and abbreviation expansions those rewrites introduced, a
residual-block Jacobian evaluated at the wrong layer, and the Kaplan
exponent arithmetic in Appendix B. Margin SVGs regenerated from the
generator for p99, r_pipe, and nameplate GB labels; TikZ labels updated
to the same symbols. Vol1 PDF builds cleanly with all validations.

Pre-commit: skipped book-check-footnotes, -code, -prose, -punctuation,
-numbers, -notation. Every remaining failure in them is pre-existing on
dev (vol2-4, plus unchanged vol1 percent and literal-number items); the
vol1 notation and pin scopes pass.
2026-10-01 11:30:53 -04:00

738 lines
28 KiB
Python

"""Mapped single-chapter PDF builds for layout reconstruction.
The full-volume LaTeX ``.aux`` file is the numbering authority. External
cross-references in a generated copy of the chapter are replaced with their
resolved display text, while the chapter and folio counters are restored to
their full-book values. The source QMD is never modified.
"""
from __future__ import annotations
import fcntl
import json
import os
import re
import shutil
import subprocess
from contextlib import contextmanager
from pathlib import Path
from typing import Any, Iterator
import yaml
XREF_RE = re.compile(
r"@(?P<kind>Sec|sec|Fig|fig|Tbl|tbl|Eq|eq|Lst|lst|Alg|alg)-"
r"(?P<rest>[A-Za-z0-9_-]+)"
)
ID_RE = re.compile(r"#([A-Za-z][A-Za-z0-9_-]+)")
CELL_LABEL_RE = re.compile(
r"^\s*#\|\s*label:\s*([A-Za-z][A-Za-z0-9_-]+)\s*$", re.MULTILINE
)
H1_RE = re.compile(
r"^#\s+.+?\{[^}\n]*#([A-Za-z][A-Za-z0-9_-]+)(?:\s+[^}\n]*)?\}\s*$",
re.MULTILINE,
)
H1_LINE_RE = re.compile(
r"^#\s+(?P<title>.+?)(?:[ \t]+\{(?P<attrs>[^}\n]*)\})?[ \t]*$", re.MULTILINE
)
FENCE_RE = re.compile(r"^\s*(`{3,}|~{3,})")
CUSTOM_HEADING_RE = re.compile(
r"(\\begin\{fbxSimple\}\{callout-[^}]+\}\{[^{}\n]*? )"
r"(?P<number>(?:(?:\d+|[A-Z]|[IVXLCM]+)\.)?\d+)(?P<suffix>:\})"
)
CUSTOM_TARGET_RE = re.compile(
r"\\label\{(?P<id>(?:dfn|exmp|lhs|psp|nbk|thrm|chk|pri|cs|ws)-[^}]+)\}"
r"\s*\\begin\{fbxSimple\}\{callout-[^}]+\}"
r"\{[^{}\n]*? (?P<number>(?:(?:\d+|[A-Z]|[IVXLCM]+)\.)?\d+):\}"
)
CUSTOM_REF_RE = re.compile(
r"\\hyperref\[(?P<id>(?:dfn|exmp|lhs|psp|nbk|thrm|chk|pri|cs|ws)-[^]]+)\]"
r"\{(?P<number>(?:(?:\d+|[A-Z]|[IVXLCM]+)\.)?\d+)\}"
)
def _local_render_env(config_manager: Any) -> dict[str, str]:
"""Return an environment that imports Python sources from this worktree."""
from ..core.process import local_render_env
return local_render_env(config_manager.root_dir)
def _braced_fields(payload: str) -> list[str]:
"""Return top-level braced fields from a LaTeX aux payload."""
fields: list[str] = []
i = 0
while i < len(payload):
if payload[i].isspace():
i += 1
continue
if payload[i] != "{":
break
depth = 0
start = i + 1
for j in range(i, len(payload)):
char = payload[j]
if char == "{":
depth += 1
elif char == "}":
depth -= 1
if depth == 0:
fields.append(payload[start:j])
i = j + 1
break
else:
break
return fields
def parse_aux(aux_path: Path) -> dict[str, dict[str, str]]:
"""Parse Quarto/Pandoc label number, page, and anchor data from ``.aux``."""
labels: dict[str, dict[str, str]] = {}
prefix = r"\newlabel{"
for line in aux_path.read_text(encoding="utf-8", errors="replace").splitlines():
if not line.startswith(prefix):
continue
label_end = line.find("}", len(prefix))
if label_end < 0:
continue
label = line[len(prefix) : label_end]
payload = line[label_end + 1 :].strip()
if not (payload.startswith("{") and payload.endswith("}")):
continue
fields = _braced_fields(payload[1:-1])
if len(fields) >= 4:
number, page, _, anchor = fields[:4]
labels[label] = {"number": number, "page": page, "anchor": anchor}
return labels
def parse_toc_chapters(aux_path: Path) -> list[dict[str, str]]:
"""Extract chapter-level title, folio, and anchor records from an AUX file."""
records: list[dict[str, str]] = []
marker = r"\contentsline {chapter}"
for line in aux_path.read_text(encoding="utf-8", errors="replace").splitlines():
offset = line.find(marker)
if offset < 0:
continue
fields = _braced_fields(line[offset + len(marker) :])
if len(fields) >= 3:
records.append(
{"title": fields[0], "page": fields[1], "anchor": fields[2]}
)
return records
def roman_to_int(value: str) -> int:
"""Convert a positive Roman-numeral folio to an integer page counter."""
numerals = {"I": 1, "V": 5, "X": 10, "L": 50, "C": 100, "D": 500, "M": 1000}
text = value.strip().upper()
if not text or any(char not in numerals for char in text):
raise ValueError(f"Invalid Roman folio: {value}")
total = 0
previous = 0
for char in reversed(text):
current = numerals[char]
total += -current if current < previous else current
previous = max(previous, current)
return total
def _inject_folio_after_h1(source: str, start_page: int, *, roman: bool) -> str:
"""Set the visible folio after Quarto emits an unnumbered chapter heading."""
numbering = "roman" if roman else "arabic"
block = (
"\n\n```{=latex}\n"
f"\\pagenumbering{{{numbering}}}\n"
f"\\setcounter{{page}}{{{start_page}}}\n"
"```"
)
return H1_LINE_RE.sub(lambda match: match.group(0) + block, source, count=1)
def _configured_sources(config: dict[str, Any], key: str) -> list[str]:
"""Return the plain-string entries under ``book.<key>`` in a Quarto config."""
values = config.get("book", {}).get(key, []) or []
return [value for value in values if isinstance(value, str)]
def _fragment_owner(source_path: Path, volume_root: Path) -> Path | None:
"""Return the front-matter wrapper that includes a known fragment file, or None."""
owners = {
"_conventions.qmd": volume_root / "frontmatter" / "about.qmd",
"_notation_core_head.qmd": volume_root / "frontmatter" / "notation.qmd",
"_notation_core_tail.qmd": volume_root / "frontmatter" / "notation.qmd",
"_notation_degradation.qmd": volume_root / "frontmatter" / "notation.qmd",
"_notation_scaling.qmd": volume_root / "frontmatter" / "notation.qmd",
"_notation_agentic.qmd": volume_root / "frontmatter" / "notation.qmd",
"_notation_physical.qmd": volume_root / "frontmatter" / "notation.qmd",
}
return owners.get(source_path.name)
def _volume_citekeys(quarto_dir: Path, configured: list[str]) -> list[str]:
"""Collect bibliography citekeys used by the selected volume's QMD sources."""
keys: set[str] = set()
visited: set[Path] = set()
citation = re.compile(r"(?<![A-Za-z0-9_])@([A-Za-z][A-Za-z0-9_:.+-]*)")
include = re.compile(r"\{\{<\s*include\s+([^ >]+)\s*>\}\}")
xref_prefixes = ("sec-", "fig-", "tbl-", "eq-", "lst-", "alg-", "vid-")
def collect(path: Path) -> None:
"""Add non-xref citekeys from a QMD file and recurse into its includes once."""
path = path.resolve()
if path in visited or not path.is_file() or path.suffix != ".qmd":
return
visited.add(path)
text = path.read_text(encoding="utf-8")
for key in citation.findall(text):
if not key.lower().startswith(xref_prefixes):
keys.add(key)
for relative in include.findall(text):
collect(path.parent / relative)
for relative in configured:
collect(quarto_dir / relative)
return sorted(keys)
def _plain_toc_title(value: str) -> str:
"""Remove the numberline wrapper used by numbered AUX chapter titles."""
return re.sub(r"^\\numberline\s*\{[^}]+\}", "", value).strip()
def _h1_metadata(source: str) -> tuple[str, str]:
"""Return the first level-one heading's title and raw attribute string.
Raises ValueError when the source has no level-one heading.
"""
match = H1_LINE_RE.search(source)
if match is None:
raise ValueError("Could not find a level-one heading")
return match.group("title").strip(), (match.group("attrs") or "")
def _toc_record_for_title(
records: list[dict[str, str]], title: str
) -> dict[str, str]:
"""Return the single AUX chapter TOC record whose plain title equals ``title``.
Raises ValueError when no record or more than one record matches.
"""
matches = [record for record in records if _plain_toc_title(record["title"]) == title]
if len(matches) != 1:
detail = "none" if not matches else ", ".join(record["anchor"] for record in matches)
raise ValueError(f"Full-build AUX title {title!r} did not resolve uniquely: {detail}")
return matches[0]
def _display_text(kind_token: str, record: dict[str, str]) -> str:
"""Return the resolved reference text, such as ``figure 3.2``, joined by a no-break space.
``sec`` references become "chapter" when the AUX anchor is a chapter anchor.
The noun is capitalized when the source token was (``@Fig-`` vs ``@fig-``).
"""
kind = kind_token.lower()
if kind == "sec":
noun = "chapter" if record["anchor"].startswith("chapter.") else "section"
else:
noun = {
"fig": "figure",
"tbl": "table",
"eq": "equation",
"lst": "listing",
"alg": "algorithm",
}[kind]
if kind_token[0].isupper():
noun = noun.capitalize()
return f"{noun}\u00a0{record['number']}"
def _callout_number_prefix(document_kind: str, displayed_number: str) -> str:
"""Return the full-book prefix used by numbered callouts, if any.
Part-opener callouts are locally numbered in the authoritative full PDF;
only numbered chapters and appendices carry a document prefix.
"""
if document_kind in {"mainmatter_chapter", "appendix"}:
return displayed_number
return ""
def _replace_prose_segment(
segment: str,
internal: set[str],
labels: dict[str, dict[str, str]],
missing: set[str],
) -> tuple[str, int]:
"""Map xrefs in prose while preserving inline-code spans."""
parts = re.split(r"(`+[^`]*`+)", segment)
count = 0
def replace(match: re.Match[str]) -> str:
"""Replace an external xref with its AUX display text, recording unknown labels."""
nonlocal count
kind_token = match.group("kind")
label = f"{kind_token.lower()}-{match.group('rest')}"
if label in internal:
return match.group(0)
record = labels.get(label)
if record is None:
missing.add(label)
return match.group(0)
count += 1
return _display_text(kind_token, record)
for i in range(0, len(parts), 2):
parts[i] = XREF_RE.sub(replace, parts[i])
return "".join(parts), count
def mapped_source(
source: str,
labels: dict[str, dict[str, str]],
) -> tuple[str, int, list[str]]:
"""Map external xrefs without touching fenced code, inline code, or comments."""
internal = set(ID_RE.findall(source)) | set(CELL_LABEL_RE.findall(source))
missing: set[str] = set()
replaced = 0
output: list[str] = []
fence: str | None = None
in_comment = False
for line in source.splitlines(keepends=True):
fence_match = FENCE_RE.match(line)
if fence_match:
marker = fence_match.group(1)
if fence is None:
fence = marker[0]
elif marker[0] == fence:
fence = None
output.append(line)
continue
if fence is not None:
output.append(line)
continue
pieces: list[str] = []
cursor = 0
while cursor < len(line):
if in_comment:
end = line.find("-->", cursor)
if end < 0:
pieces.append(line[cursor:])
cursor = len(line)
else:
pieces.append(line[cursor : end + 3])
cursor = end + 3
in_comment = False
continue
start = line.find("<!--", cursor)
prose_end = len(line) if start < 0 else start
mapped, count = _replace_prose_segment(
line[cursor:prose_end], internal, labels, missing
)
pieces.append(mapped)
replaced += count
cursor = prose_end
if start >= 0:
in_comment = True
output.append("".join(pieces))
return "".join(output), replaced, sorted(missing)
def _mainmatter_counter_hook(
chapter_number: int,
start_page: int,
*,
lock_first_numbered: bool = True,
) -> dict[str, str]:
"""Build a LaTeX header include that restores chapter and page counters at ``\\mainmatter``.
The chapter counter is set to ``chapter_number - 1`` so the next chapter
heading receives ``chapter_number``. With ``lock_first_numbered`` the hook
also clears ``\\@firstnumbered`` so the class does not reset the folio.
"""
first_numbered = (
" \\@firstnumberedfalse% preserve the mapped folio\n"
if lock_first_numbered
else ""
)
return {
"text": (
"\\makeatletter\n"
"\\AtBeginDocument{%\n"
" \\let\\LayoutMapMainMatter\\mainmatter\n"
" \\renewcommand{\\mainmatter}{%\n"
" \\LayoutMapMainMatter\n"
f" \\setcounter{{chapter}}{{{chapter_number - 1}}}%\n"
f" \\setcounter{{page}}{{{start_page}}}%\n"
f"{first_numbered}"
" }%\n"
"}\n"
"\\makeatother"
)
}
def _appendix_counter_hook(appendix_number: int, start_page: int) -> dict[str, str]:
"""Restore an appendix letter and folio after Quarto enters appendix mode."""
return {
"text": (
"\\makeatletter\n"
"\\AtBeginDocument{%\n"
" \\let\\LayoutMapAppendix\\appendix\n"
" \\renewcommand{\\appendix}{%\n"
" \\LayoutMapAppendix\n"
f" \\setcounter{{chapter}}{{{appendix_number - 1}}}%\n"
f" \\setcounter{{page}}{{{start_page}}}%\n"
" \\@firstnumberedfalse% preserve the mapped folio\n"
" }%\n"
"}\n"
"\\makeatother"
)
}
@contextmanager
def _worktree_lock(lock_path: Path) -> Iterator[None]:
"""Serialize renders that temporarily redirect the active Quarto config."""
lock_path.parent.mkdir(parents=True, exist_ok=True)
with lock_path.open("w", encoding="utf-8") as handle:
try:
fcntl.flock(handle.fileno(), fcntl.LOCK_EX | fcntl.LOCK_NB)
except BlockingIOError as exc:
raise RuntimeError(
"Another mapped chapter build is active in this worktree. "
"Use a separate worktree for parallel renders."
) from exc
handle.write(f"pid={os.getpid()}\n")
handle.flush()
try:
yield
finally:
fcntl.flock(handle.fileno(), fcntl.LOCK_UN)
def _resolve_chapter(quarto_dir: Path, volume: str, spec: str) -> Path:
"""Resolve a chapter spec to an absolute QMD path inside the volume directory.
Accepts an absolute path, a path relative to ``quarto_dir``, or a stem that
matches exactly one ``<stem>.qmd`` under the volume. Raises ValueError when
the stem is ambiguous or missing, or when the file lies outside the volume.
"""
supplied = Path(spec)
direct = supplied if supplied.is_absolute() else quarto_dir / supplied
if direct.is_file():
path = direct.resolve()
else:
stem = supplied.stem
matches = sorted((quarto_dir / volume).rglob(f"{stem}.qmd"))
if len(matches) != 1:
detail = "none" if not matches else ", ".join(str(p) for p in matches)
raise ValueError(f"Chapter {spec!r} did not resolve uniquely: {detail}")
path = matches[0].resolve()
volume_root = (quarto_dir / volume).resolve()
if volume_root not in path.parents:
raise ValueError(f"Chapter must be under contents/{volume}: {path}")
return path
def _correct_custom_callout_tex(
tex: str, chapter_number: int | str
) -> tuple[str, int, int]:
"""Correct local callout headings and references in mapped TeX."""
corrected, heading_count = CUSTOM_HEADING_RE.subn(
lambda match: (
f"{match.group(1)}{chapter_number}."
f"{match.group('number').rsplit('.', 1)[-1]}{match.group('suffix')}"
),
tex,
)
target_numbers = {
match.group("id"): match.group("number")
for match in CUSTOM_TARGET_RE.finditer(corrected)
}
reference_count = 0
def replace_reference(match: re.Match[str]) -> str:
"""Rewrite a callout ``\\hyperref`` whose number differs from its corrected target."""
nonlocal reference_count
number = target_numbers.get(match.group("id"))
if number is None or number == match.group("number"):
return match.group(0)
reference_count += 1
return f"\\hyperref[{match.group('id')}]{{{number}}}"
corrected = CUSTOM_REF_RE.sub(replace_reference, corrected)
return corrected, heading_count, reference_count
def _correct_callout_numbers(
quarto_dir: Path,
output_dir: Path,
output_stem: str,
chapter_number: int | str,
) -> tuple[int, int]:
"""Correct filter-generated callout prefixes and rebuild when necessary."""
tex_path = quarto_dir / f"{output_stem}.tex"
if not tex_path.is_file():
raise RuntimeError(f"Quarto did not retain expected TeX: {tex_path}")
tex = tex_path.read_text(encoding="utf-8")
corrected, heading_count, reference_count = _correct_custom_callout_tex(
tex, chapter_number
)
if heading_count or reference_count:
tex_path.write_text(corrected, encoding="utf-8")
latex = [
"lualatex",
"-interaction=nonstopmode",
"-halt-on-error",
"-file-line-error",
tex_path.name,
]
subprocess.run(latex, cwd=quarto_dir, check=True, stdout=subprocess.DEVNULL)
idx_path = quarto_dir / f"{output_stem}.idx"
if idx_path.exists():
subprocess.run(
["makeindex", idx_path.name],
cwd=quarto_dir,
check=True,
stdout=subprocess.DEVNULL,
)
subprocess.run(latex, cwd=quarto_dir, check=True, stdout=subprocess.DEVNULL)
subprocess.run(latex, cwd=quarto_dir, check=True, stdout=subprocess.DEVNULL)
shutil.copy2(
quarto_dir / f"{output_stem}.pdf", output_dir / f"{output_stem}.pdf"
)
shutil.copy2(tex_path, output_dir.parent / f"{output_stem}.tex")
for suffix in (
".aux",
".idx",
".ilg",
".ind",
".log",
".out",
".pdf",
".toc",
".tex",
):
(quarto_dir / f"{output_stem}{suffix}").unlink(missing_ok=True)
return heading_count, reference_count
def render_mapped_chapter(
config_manager: Any,
*,
volume: str,
chapter: str,
aux_path: Path,
) -> dict[str, Any]:
"""Render one configured PDF component with full-volume layout context."""
quarto_dir = Path(config_manager.book_dir).resolve()
requested_path = _resolve_chapter(quarto_dir, volume, chapter)
volume_root = (quarto_dir / volume).resolve()
if requested_path == volume_root / "index.qmd":
raise ValueError(
"The volume index.qmd is an HTML landing page, not a PDF component. "
"Use a configured PDF QMD instead."
)
source_path = _fragment_owner(requested_path, volume_root) or requested_path
aux_path = aux_path.expanduser().resolve()
if not aux_path.is_file():
raise FileNotFoundError(f"Full-build aux file not found: {aux_path}")
pdf_config = Path(config_manager.get_config_file("pdf", volume)).resolve()
config = yaml.safe_load(pdf_config.read_text(encoding="utf-8"))
configured_chapters = _configured_sources(config, "chapters")
configured_appendices = _configured_sources(config, "appendices")
configured = configured_chapters + configured_appendices
source_rel = source_path.relative_to(quarto_dir).as_posix()
if source_rel not in configured:
raise ValueError(
f"{requested_path.relative_to(quarto_dir)} is not a configured PDF "
"component and has no supported owning wrapper"
)
source = source_path.read_text(encoding="utf-8")
labels = parse_aux(aux_path)
title, attrs = _h1_metadata(source)
label_match = ID_RE.search(attrs)
document_label = label_match.group(1) if label_match else None
# An unlabeled front-matter item must resolve through its unique TOC title.
# Some full-book AUX files contain an empty-string label for an unrelated
# float, so looking up ``""`` here can silently borrow that float's folio.
labeled_record = labels.get(document_label) if document_label else None
toc_records = parse_toc_chapters(aux_path)
is_part = "/parts/" in source_rel
is_frontmatter = "/frontmatter/" in source_rel
is_reference_matter = source_path.name == "references.qmd" or "backmatter/glossary/" in source_rel
is_appendix = bool(
labeled_record and labeled_record["anchor"].startswith("appendix.")
)
if is_part:
document_kind = "part_opener"
elif is_frontmatter:
document_kind = "frontmatter"
elif is_appendix:
document_kind = "appendix"
elif is_reference_matter:
document_kind = "appendix_backmatter"
elif labeled_record and labeled_record["anchor"].startswith("chapter."):
document_kind = "mainmatter_chapter"
else:
raise ValueError(f"Could not classify configured PDF component {source_rel}")
record = labeled_record or _toc_record_for_title(toc_records, title)
displayed_number = record.get("number", "")
start_folio = record["page"]
start_page = int(start_folio) if start_folio.isdigit() else roman_to_int(start_folio)
if document_kind == "appendix":
if len(displayed_number) != 1 or not displayed_number.isalpha():
raise ValueError(
f"Unsupported appendix number for {document_label}: {displayed_number}"
)
counter_number = ord(displayed_number.upper()) - ord("A") + 1
elif document_kind == "mainmatter_chapter":
counter_number = int(displayed_number)
elif document_kind == "part_opener":
parts = [item for item in configured_chapters if "/parts/" in item]
counter_number = parts.index(source_rel) + 1
displayed_number = ["I", "II", "III", "IV", "V", "VI"][counter_number - 1]
else:
counter_number = 0
displayed_number = ""
transformed, replacement_count, missing = mapped_source(source, labels)
if missing:
raise ValueError(
"Full-build aux is missing external labels: " + ", ".join(missing)
)
if document_kind == "frontmatter":
transformed = _inject_folio_after_h1(transformed, start_page, roman=True)
elif document_kind == "mainmatter_chapter":
# The title template's first unnumbered ``Welcome`` chapter switches
# the harness back to Roman numerals. Restore the authoritative Arabic
# folio immediately after Quarto emits this numbered chapter heading.
transformed = _inject_folio_after_h1(transformed, start_page, roman=False)
elif document_kind in {"part_opener", "appendix_backmatter"}:
transformed = _inject_folio_after_h1(transformed, start_page, roman=False)
mapped_path = source_path.with_name(f"{source_path.stem}_layoutmapped.qmd")
if mapped_path.exists():
raise FileExistsError(f"Refusing to overwrite generated file: {mapped_path}")
mapped_rel = mapped_path.relative_to(quarto_dir).as_posix()
harness_dir = quarto_dir / "tmp" / "layout-harness" / source_path.stem
output_dir = harness_dir / "output"
harness_config = harness_dir / "_quarto.yml"
manifest_path = harness_dir / "manifest.json"
harness_dir.mkdir(parents=True, exist_ok=True)
config["project"]["output-dir"] = output_dir.relative_to(quarto_dir).as_posix()
config["project"]["post-render"] = []
config["project"]["render"] = ["index.qmd", mapped_rel]
output_stem = f"Mapped-{source_path.stem}"
config["book"]["output-file"] = output_stem
config["book"]["chapters"] = ["index.qmd"]
in_appendices = document_kind in {"appendix", "appendix_backmatter"}
config["book"]["appendices"] = [mapped_rel] if in_appendices else []
if not in_appendices:
config["book"]["chapters"].append(mapped_rel)
headers = config["format"]["titlepage-pdf"].setdefault("include-in-header", [])
if document_kind == "appendix":
headers.append(_appendix_counter_hook(counter_number, start_page))
elif document_kind == "mainmatter_chapter":
headers.append(
_mainmatter_counter_hook(
counter_number, start_page, lock_first_numbered=False
)
)
elif document_kind == "part_opener":
headers.append(_mainmatter_counter_hook(1, start_page))
# Citeproc disambiguates names and year suffixes against the complete set of
# citations in a document. Rendering only one chapter otherwise changes
# visible strings (for example, ``A. Chen`` to ``Chen`` or ``2024b`` to
# ``2024a``), and those small width changes accumulate into different page
# and float breaks. Preserve the full volume's citation context for every
# mapped component; the resulting bibliography falls after the component
# and therefore does not affect its internal pagination.
citekeys = _volume_citekeys(quarto_dir, configured)
config["nocite"] = [f"@{key}" for key in citekeys]
harness_config.write_text(yaml.safe_dump(config, sort_keys=False), encoding="utf-8")
active_config = quarto_dir / "_quarto.yml"
active_index = quarto_dir / "index.qmd"
if not active_config.is_file() or not active_index.is_file():
raise RuntimeError("Expected _quarto.yml and index.qmd; run a volume build first")
original_config = active_config.read_bytes()
original_index = active_index.read_bytes()
target_index = f"index-{volume}.qmd"
xref_path = quarto_dir / "._pdfbook_xref.json"
xref_existed = xref_path.is_file()
xref_snapshot = xref_path.read_bytes() if xref_existed else None
render_env = _local_render_env(config_manager)
with _worktree_lock(quarto_dir / "tmp" / "layout-harness" / ".render.lock"):
mapped_path.write_text(transformed, encoding="utf-8")
try:
config_manager.activate_config_file(harness_config)
active_index.unlink()
shutil.copyfile(quarto_dir / target_index, active_index)
subprocess.run(
["quarto", "render", "--to=titlepage-pdf"],
cwd=quarto_dir,
env=render_env,
check=True,
)
finally:
active_config.write_bytes(original_config)
active_index.write_bytes(original_index)
mapped_path.unlink(missing_ok=True)
if xref_existed:
assert xref_snapshot is not None
xref_path.write_bytes(xref_snapshot)
else:
xref_path.unlink(missing_ok=True)
corrected_count = 0
corrected_reference_count = 0
callout_prefix = _callout_number_prefix(document_kind, displayed_number)
if callout_prefix:
corrected_count, corrected_reference_count = _correct_callout_numbers(
quarto_dir, output_dir, output_stem, callout_prefix
)
else:
tex_path = quarto_dir / f"{output_stem}.tex"
if tex_path.is_file():
shutil.copy2(tex_path, output_dir.parent / f"{output_stem}.tex")
for suffix in (".aux", ".idx", ".ilg", ".ind", ".log", ".out", ".pdf", ".toc", ".tex"):
(quarto_dir / f"{output_stem}{suffix}").unlink(missing_ok=True)
pdf_path = output_dir / f"{output_stem}.pdf"
manifest: dict[str, Any] = {
"schema_version": 1,
"volume": volume,
"requested_source": requested_path.relative_to(quarto_dir).as_posix(),
"rendered_source": source_rel,
"chapter_source": source_rel,
"chapter_label": document_label,
"chapter_number": displayed_number,
"document_kind": document_kind,
"display_number": displayed_number,
"start_folio": start_folio,
"start_page": start_page,
"external_references_mapped": replacement_count,
"custom_callout_prefixes_corrected": corrected_count,
"custom_callout_references_corrected": corrected_reference_count,
"numbering_source": str(aux_path),
"pdf": str(pdf_path),
"source_modified": False,
"active_config_restored": active_config.read_bytes() == original_config,
"active_index_restored": active_index.read_bytes() == original_index,
}
manifest_path.write_text(json.dumps(manifest, indent=2) + "\n", encoding="utf-8")
manifest["manifest"] = str(manifest_path)
return manifest