diff --git a/BEDIENUNGSANLEITUNG.md b/BEDIENUNGSANLEITUNG.md index 8fa4a26..72e83bf 100644 --- a/BEDIENUNGSANLEITUNG.md +++ b/BEDIENUNGSANLEITUNG.md @@ -222,7 +222,7 @@ Blöcke übernommen: ``` GitHub und Obsidian zeigen das als farbigen Kasten mit Symbol; andere Programme als eingerückten Zitatblock. -Für Pandoc (HTML, EPUB, PDF) gibt es einen Filter, der echte Kästen zeichnet, siehe Kapitel 7. +Für Pandoc (HTML, EPUB, PDF) gibt es einen Filter, der echte Kästen zeichnet, siehe Kapitel 7 („Weiterverarbeiten“). Andere Darstellungen wählst du mit `--boxes`: - `quote`: Zitatblock ohne `[!TIP]` @@ -309,50 +309,52 @@ cd output/meinbuch ``` `meinbuch.md` steht im Folgenden für den Namen der Gesamtdatei. Er entspricht dem Namen des -Original-E-Books. +Original-E-Books. `$P` ist der Ordner mit den Pandoc-Hilfsdateien von epub2md; lege ihn einmal fest: + +```bash +P=~/Python_Programs/epub2md/pandoc # Pfad zu deinem epub2md-Ordner anpassen +``` | Ziel | Befehl | | --- | --- | -| Webseite (HTML) | `pandoc meinbuch.md -s --toc -o meinbuch.html` | -| Webseite mit Formeln | `pandoc meinbuch.md -s --toc --mathml -o meinbuch.html` | -| E-Book (EPUB) | `pandoc meinbuch.md --toc -o meinbuch.epub` | -| Word | `pandoc meinbuch.md -o meinbuch.docx` | -| LibreOffice | `pandoc meinbuch.md -o meinbuch.odt` | -| PDF mit LaTeX | `pandoc meinbuch.md -s -o meinbuch.html && pandoc meinbuch.html --toc --pdf-engine=xelatex -o meinbuch.pdf` | -| PDF ohne LaTeX | `pandoc meinbuch.md -s --toc --css ~/Python_Programs/epub2md/pandoc/print.css --pdf-engine=weasyprint -o meinbuch.pdf` | -| Kindle (AZW3) | `pandoc meinbuch.md --toc -o meinbuch.epub && ebook-convert meinbuch.epub meinbuch.azw3` | +| PDF (empfohlen) | `pandoc meinbuch.md -s --lua-filter $P/book.lua --lua-filter $P/callouts.lua --css $P/print.css --pdf-engine=weasyprint -o meinbuch.pdf` | +| Webseite (HTML) | `pandoc meinbuch.md -s --lua-filter $P/book.lua --lua-filter $P/callouts.lua -o meinbuch.html` | +| Webseite mit Formeln | wie HTML, zusätzlich `--mathml` | +| E-Book (EPUB) | `pandoc meinbuch.md --toc --lua-filter $P/callouts.lua -o meinbuch.epub` | +| Word | `pandoc meinbuch.md --toc --lua-filter $P/callouts.lua -o meinbuch.docx` | +| LibreOffice | `pandoc meinbuch.md --toc --lua-filter $P/callouts.lua -o meinbuch.odt` | +| PDF mit LaTeX | `pandoc meinbuch.md -s --lua-filter $P/callouts.lua -o meinbuch.html && pandoc meinbuch.html --toc --lua-filter $P/callouts.lua --pdf-engine=xelatex -o meinbuch.pdf` | +| Kindle (AZW3) | erst EPUB wie oben, dann `ebook-convert meinbuch.epub meinbuch.azw3` | + +Die drei Hilfsdateien: + +- **`book.lua`** ordnet den Buchanfang wie im gedruckten Buch: Cover, Titelseite, Impressum, dann das + Inhaltsverzeichnis, jeweils auf einer neuen Seite. Ohne den Filter setzt Pandoc mit `--toc` das + Inhaltsverzeichnis (und seinen eigenen Titelkopf) vor das Cover. Mit `book.lua` deshalb **kein** `--toc` + angeben. Hat das Buch keine eigene Titelseite, setzt der Filter Pandocs Titelkopf hinter das Cover. Er + wirkt nur bei HTML und beim PDF über weasyprint; EPUB, Word und LibreOffice bauen ihr Verzeichnis mit + `--toc` selbst. +- **`callouts.lua`** zeichnet die Infokästen (`> [!TIP]`). Pandoc in der Ubuntu-Version kennt diese + Schreibweise nicht und druckt „[!TIP]“ sonst einfach mit. In HTML, EPUB und im PDF über weasyprint werden + daraus gerahmte, farbige Kästen mit Titel („Tipp: …“), im PDF über LaTeX und in Word/LibreOffice ein + eingerückter Block mit fettem Titel. +- **`print.css`** liefert das Druck-Layout für weasyprint: A4, Seitenzahlen, jedes Kapitel auf einer neuen + Seite, Inhaltsverzeichnis mit Seitenzahlen, Silbentrennung und umbrechende lange Codezeilen. Ohne sie + übernimmt weasyprint das Bildschirmlayout, und lange Codezeilen werden am Seitenrand abgeschnitten. + +Du musst die Befehle nicht abtippen: Die `README.md` im Ausgabeordner enthält sie fertig, mit allen Pfaden. **Zum PDF:** +- **Mit weasyprint** (empfohlen) genügt ein Schritt. Tabellen, Farben, Infokästen und die Gestaltung aus + `--styles html` bleiben erhalten, der Buchanfang ist richtig geordnet. Für Bücher mit vielen Formeln ist + der LaTeX-Weg sicherer. - **Mit LaTeX** immer den Zwischenschritt über HTML nehmen, wie in der Tabelle. Beim direkten Weg von - Markdown nach LaTeX lässt Pandoc komplexe Tabellen weg, und Querverweise im PDF funktionieren nicht. Bei - Büchern mit Formeln ist der LaTeX-Weg die beste Wahl. -- **Mit weasyprint** genügt ein Schritt; Tabellen, Farben, Infokästen und die Gestaltung aus `--styles html` - bleiben erhalten. Für Bücher mit vielen Formeln ist der LaTeX-Weg sicherer. -- **`print.css`** (im Ordner `pandoc/` von epub2md) sorgt beim weasyprint-Weg für ein Buchlayout: A4, - Seitenzahlen, jedes Kapitel auf einer neuen Seite, Silbentrennung und umbrechende lange Codezeilen. Ohne - sie übernimmt weasyprint das Bildschirmlayout, und lange Codezeilen werden am Seitenrand abgeschnitten. + Markdown nach LaTeX lässt Pandoc komplexe Tabellen weg, und Querverweise im PDF funktionieren nicht. Das + Inhaltsverzeichnis steht hier am Anfang, vor dem Cover. Bei Büchern mit Formeln für HTML zusätzlich `--mathml` angeben, sonst bleiben Matrizen als TeX-Text stehen. -#### Infokästen richtig darstellen - -Pandoc in der Ubuntu-Version kennt die Kasten-Schreibweise `> [!TIP]` nicht und druckt „[!TIP]“ einfach mit. -Dafür liegt im epub2md-Ordner der Filter `pandoc/callouts.lua`. Hänge ihn an jeden Pandoc-Befehl an: - -```bash -F=~/Python_Programs/epub2md/pandoc/callouts.lua # Pfad zu deinem epub2md-Ordner -pandoc meinbuch.md -s --toc --lua-filter $F -o meinbuch.html -pandoc meinbuch.md --toc --lua-filter $F -o meinbuch.epub -pandoc meinbuch.md -s --lua-filter $F -o meinbuch.html && pandoc meinbuch.html --toc --lua-filter $F --pdf-engine=xelatex -o meinbuch.pdf -pandoc meinbuch.md -s --toc --lua-filter $F --css ~/Python_Programs/epub2md/pandoc/print.css --pdf-engine=weasyprint -o meinbuch.pdf -``` - -In HTML, EPUB und im PDF über weasyprint werden daraus gerahmte, farbige Kästen mit Titel („Tipp: …“). Im -PDF über LaTeX und in Word/LibreOffice wird ein eingerückter Block mit fettem Titel daraus. Du musst nicht -selbst nachsehen, ob dein Buch Kästen hat: Die `README.md` im Ausgabeordner enthält in diesem Fall die -Befehle bereits mit Filter. - #### Direkt aus den Kapiteldateien Ohne Gesamtdatei geht es auch. Dann im Ordner `chapters/` arbeiten und die Buchdaten aus diff --git a/CHANGELOG.md b/CHANGELOG.md index 31115d1..41e40f1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,11 @@ Versionsnummern nach [Semantic Versioning](https://semver.org/lang/de/). ### Neu +- `pandoc/book.lua`: Inhaltsverzeichnis wie im gedruckten Buch nach Cover, Titelseite und Impressum statt + davor (Pandocs `--toc` setzt es immer an den Anfang). `epub2md_merge.py` markiert dafür das Ende des + Vorspanns; ohne eigene Titelseite kommt Pandocs Titelkopf hinter das Cover. Jedes Kapitel, auch Cover, + Titelseite und Impressum, beginnt mit `print.css` auf einer neuen Seite; das Inhaltsverzeichnis hat + Seitenzahlen. Mit `epub2md_merge.py --toc` steht dessen Verzeichnis an derselben Stelle. - `pandoc/print.css`: Druck-Layout für PDFs mit weasyprint (A4, Seitenzahlen, Kapitel auf neuer Seite, Silbentrennung außer in Code, umbrechende Codezeilen). Die Buch-README enthält den passenden Befehl. diff --git a/README.md b/README.md index d6b1a89..c91bfab 100644 --- a/README.md +++ b/README.md @@ -331,9 +331,11 @@ konsistente Anker: ```bash cd output/programmierbuch # Medienpfade sind relativ zur Gesamtdatei -F=~/Python_Programs/epub2md/pandoc/callouts.lua # Pfad zum epub2md-Ordner anpassen -pandoc programmierbuch.md --toc -s --lua-filter $F -o programmierbuch.html -pandoc programmierbuch.md --toc --lua-filter $F -o programmierbuch.epub +P=~/Python_Programs/epub2md/pandoc # Pfad zum epub2md-Ordner anpassen +pandoc programmierbuch.md -s --lua-filter $P/book.lua --lua-filter $P/callouts.lua \ + --css $P/print.css --pdf-engine=weasyprint -o programmierbuch.pdf # PDF (empfohlen) +pandoc programmierbuch.md -s --lua-filter $P/book.lua --lua-filter $P/callouts.lua -o programmierbuch.html +pandoc programmierbuch.md --toc --lua-filter $P/callouts.lua -o programmierbuch.epub cd output/mathematikbuch pandoc mathematikbuch.md --mathml -s -o mathematikbuch.html @@ -341,16 +343,21 @@ pandoc mathematikbuch.md --mathml -s -o mathematikbuch.html pandoc mathematikbuch.html --toc --pdf-engine=xelatex -o mathematikbuch.pdf ``` -**PDF mit weasyprint** (ohne LaTeX; Farben, Kästen und `--styles html` bleiben erhalten): +Hilfsdateien in `pandoc/`: -```bash -pandoc buch.md -s --toc --lua-filter $F --css ~/Python_Programs/epub2md/pandoc/print.css \ - --pdf-engine=weasyprint -o buch.pdf -``` - -`pandoc/print.css` liefert das Buchlayout: A4, Seitenzahlen, Kapitel auf neuer Seite, Silbentrennung (nicht in -Code) und umbrechende Codezeilen. Mit `--css` lässt Pandoc sein eigenes Stylesheet weg, deshalb bringt -`print.css` auch Grundschrift, Zeilenabstand und Titelkopf mit. Getestet mit dem Python-Buch (342 Seiten). +- **`book.lua`** (HTML, PDF über weasyprint; statt `--toc`): Pandocs `--toc` setzt Inhaltsverzeichnis und + Titelkopf immer an den Anfang, vor Cover, Titelseite und Impressum. `epub2md_merge.py` markiert deshalb das + Ende des Vorspanns (`
`, Kapiteltypen aus `manifest.json`) und, falls das Buch + keine eigene Titelseite hat, die Stelle hinter dem Cover (``). Der + Filter baut dort das Inhaltsverzeichnis (`--toc-depth` gilt), lässt Pandocs Titelkopf bei vorhandener + Titelseite weg, zeigt das Cover ohne Bildunterschrift und macht aus den Kapitelankern + `div.chapter-start` für Seitenumbrüche. Bei anderen Formaten entfernt er nur die Platzhalter. Wer selbst + `--toc` angibt, bekommt Pandocs Verzeichnis wie gewohnt. +- **`print.css`** (weasyprint): A4, Seitenzahlen, jedes Kapitel (auch Cover, Titelseite, Impressum) und das + Inhaltsverzeichnis auf neuer Seite, Inhaltsverzeichnis mit Punktlinie und Seitenzahl, Silbentrennung + (nicht in Code), umbrechende Codezeilen. Mit `--css` lässt Pandoc sein eigenes Stylesheet weg, deshalb + bringt `print.css` auch Grundschrift, Zeilenabstand und Titelkopf mit. Getestet mit dem Python-Buch + (362 Seiten). **Infokästen:** Pandoc kennt die Schreibweise `> [!TIP]` erst ab neueren Versionen (Erweiterung `alerts`); 3.1.3 (Ubuntu 24.04) zeigt „[!TIP]“ als Text. Der Filter `pandoc/callouts.lua` behebt das: In HTML und @@ -450,6 +457,7 @@ epub2md/ ├── models.py Dataclasses (Kapitel, Medien, Noten, Metadaten …) └── utils.py Slugs, YAML-Emitter, Pfadhelfer pandoc/ +├── book.lua Pandoc-Filter: Inhaltsverzeichnis nach Cover/Titelseite/Impressum, Kapitel-Seitenumbrüche ├── callouts.lua Pandoc-Filter: '> [!TIP]'-Kästen → gerahmte Kästen (HTML/EPUB) bzw. Zitatblock mit Titel ├── callouts.css Gestaltung der Kästen (vom Filter eingebunden; muss neben callouts.lua liegen) └── print.css Druck-Layout für PDFs mit weasyprint (A4, Seitenzahlen, Kapitel auf neuer Seite, Codeumbruch) diff --git a/epub2md/merge.py b/epub2md/merge.py index 80ef7b9..0eb2532 100644 --- a/epub2md/merge.py +++ b/epub2md/merge.py @@ -46,6 +46,7 @@ class ChapterFile: body: str = "" start_anchor: str = "" anchor_map: dict[str, str] = field(default_factory=dict) + types: list[str] = field(default_factory=list) @dataclass @@ -74,12 +75,12 @@ def load_book(out_dir: Path) -> tuple[dict, list[ChapterFile]]: report = json.loads(report_path.read_text(encoding="utf-8")) if report_path.is_file() else {} if manifest_path.is_file(): manifest = json.loads(manifest_path.read_text(encoding="utf-8")) - entries = [(c["number"], c["title"], c["file"]) for c in manifest.get("chapters", [])] + entries = [(c["number"], c["title"], c["file"], c.get("types") or []) for c in manifest.get("chapters", [])] else: files = sorted((out_dir / "chapters").glob("*.md")) - entries = [(i + 1, p.stem, f"chapters/{p.name}") for i, p in enumerate(files)] - chapters = [ChapterFile(n, t, rel) for n, t, rel in entries if (out_dir / rel).is_file()] - missing = [rel for _, _, rel in entries if not (out_dir / rel).is_file()] + entries = [(i + 1, p.stem, f"chapters/{p.name}", []) for i, p in enumerate(files)] + chapters = [ChapterFile(n, t, rel, types=list(ty)) for n, t, rel, ty in entries if (out_dir / rel).is_file()] + missing = [rel for _, _, rel, _ in entries if not (out_dir / rel).is_file()] if missing: raise MergeError(f"Kapiteldateien fehlen: {', '.join(missing[:5])}") if not chapters: @@ -89,6 +90,21 @@ def load_book(out_dir: Path) -> tuple[dict, list[ChapterFile]]: return report, chapters +# Front matter that precedes the table of contents in a printed book (cover, title page, imprint …). +FRONT_TYPES = {"cover", "titlepage", "halftitlepage", "colophon", "copyright-page", "imprint", "dedication", + "epigraph", "frontispiece", "seriespage", "imprimatur"} +TOC_PLACEHOLDER = '' # pandoc/book.lua puts the table of contents here +TITLE_PLACEHOLDER = '' # … and Pandoc's title block here (no own title page) + + +def front_matter_count(chapters: list[ChapterFile]) -> int: + """Number of leading chapters that are front matter (cover, title page, imprint …).""" + n = 0 + while n < len(chapters) and FRONT_TYPES & set(chapters[n].types): + n += 1 + return n + + ISO_DATE = re.compile(r"^(\d{4})-(\d{2})-(\d{2})(?:[T ][\d:.]+(?:Z|[+-]\d{2}:?\d{2})?)?$") @@ -240,15 +256,28 @@ def merge(out_dir: Path, target: Path | None = None, *, frontmatter: bool = True fm[key] = display_date(md[src], md.get("language", "")) if key == "date" else md[src] if fm: parts.append("---\n" + to_yaml(fm) + "---") + front = front_matter_count(chapters) + has_title_page = any("titlepage" in ch.types for ch in chapters[:front]) + cover_first = bool(chapters) and "cover" in chapters[0].types if toc: lines = ["## Inhalt" if (report.get("metadata") or {}).get("language", "de").startswith("de") else "## Contents", ""] lines += [f"{ch.number}. [{_escape_link_text(ch.title)}](#{ch.start_anchor})" for ch in chapters] - parts.append("\n".join(lines)) - for ch in chapters: + toc_block = "\n".join(lines) + else: + toc_block = TOC_PLACEHOLDER + if not has_title_page and not cover_first: + parts.append(TITLE_PLACEHOLDER) + if front == 0: + parts.append(toc_block) + for i, ch in enumerate(chapters): segments = [(is_code, seg if is_code else rewrite_segment(seg, ch)) for is_code, seg in _non_code_segments(ch.body)] body, n = _drop_duplicate_definitions(_join(segments), seen_defs) dropped += n parts.append(f'\n\n{body.strip()}') + if i == 0 and cover_first and not has_title_page: + parts.append(TITLE_PLACEHOLDER) + if i + 1 == front: + parts.append(toc_block) text = "\n\n".join(parts).rstrip() + "\n" existing = {p.resolve().as_posix() for p in out_dir.rglob("*") if p.is_file()} diff --git a/epub2md/writer.py b/epub2md/writer.py index 17cae9e..f00c8de 100644 --- a/epub2md/writer.py +++ b/epub2md/writer.py @@ -18,6 +18,7 @@ from .utils import rel_link, to_yaml, url_path CALLOUT_FILTER = Path(__file__).resolve().parent.parent / "pandoc" / "callouts.lua" PRINT_CSS = Path(__file__).resolve().parent.parent / "pandoc" / "print.css" +BOOK_FILTER = Path(__file__).resolve().parent.parent / "pandoc" / "book.lua" SPECIAL_LABELS = { "glossary": "Glossar", @@ -137,11 +138,13 @@ def readme(md: BookMetadata, chapters: list[Chapter], endnotes: Chapter | None, lua = "" if callouts and CALLOUT_FILTER.is_file(): # '> [!TIP]' boxes: Pandoc needs the filter to draw real boxes lua = f" --lua-filter {shlex.quote(str(CALLOUT_FILTER))}" + # book.lua: table of contents after cover/title page/imprint instead of Pandoc's --toc at the very top + toc = f" --lua-filter {shlex.quote(str(BOOK_FILTER))}" if BOOK_FILTER.is_file() else " --toc" commands = [ "# Gesamtdatei erzeugen (im selben Arbeitsverzeichnis wie die Konvertierung)", f"python3 epub2md_merge.py {shlex.quote(output_dir)}", "# danach in diesem Ordner:", - f"pandoc {shlex.quote(stem + '.md')} --toc -s{math}{lua} -o {shlex.quote(stem + '.html')}", + f"pandoc {shlex.quote(stem + '.md')} -s{math}{toc}{lua} -o {shlex.quote(stem + '.html')}", f"pandoc {shlex.quote(stem + '.md')} --toc{lua} -o {shlex.quote(stem + '.epub')}", "# PDF über HTML: Pandoc übernimmt dann auch HTML-Tabellen und interne Sprungziele", f"pandoc {shlex.quote(stem + '.md')} -s{math}{lua} -o {shlex.quote(stem + '.html')} && " @@ -150,7 +153,7 @@ def readme(md: BookMetadata, chapters: list[Chapter], endnotes: Chapter | None, if PRINT_CSS.is_file(): commands += [ "# PDF ohne LaTeX mit weasyprint: Farben, Kästen und HTML-Stile bleiben erhalten", - f"pandoc {shlex.quote(stem + '.md')} -s --toc{math}{lua} --css {shlex.quote(str(PRINT_CSS))} " + f"pandoc {shlex.quote(stem + '.md')} -s{math}{toc}{lua} --css {shlex.quote(str(PRINT_CSS))} " f"--pdf-engine=weasyprint -o {shlex.quote(stem + '.pdf')}", ] out.append("## Weiterverarbeitung") diff --git a/pandoc/book.lua b/pandoc/book.lua new file mode 100644 index 0000000..ef78a17 --- /dev/null +++ b/pandoc/book.lua @@ -0,0 +1,140 @@ +--[[ +book.lua – Pandoc filter for book files from epub2md_merge.py (HTML output, e.g. PDF via weasyprint). + +Pandoc's --toc always puts the table of contents (and its own title block) before the whole text, +i.e. before the book's cover, title page and imprint. epub2md_merge.py marks the right places: + + after the cover, only if the book has no title page of its own + after the front matter (cover, title page, imprint …) + +This filter, for HTML output: + * builds the table of contents at the placeholder (use it INSTEAD of --toc; --toc-depth is honoured), + * drops Pandoc's title block when the book has its own title page, or moves it to the placeholder, + * shows the cover picture (chapter "NNN-cover") without the caption "Cover", + * turns the chapter anchors into' .. esc(pandoc.utils.stringify(meta.subtitle)) .. "
" + end + local authors = meta.author + if authors and pandoc.utils.type(authors) ~= "List" then + authors = { authors } + end + for _, a in ipairs(authors or {}) do + html[#html + 1] = '" + end + if meta.date then + html[#html + 1] = '' .. esc(pandoc.utils.stringify(meta.date)) .. "
" + end + html[#html + 1] = "
Erika Musterfrau
Vorspann
Roman
"), + Doc("impressum.xhtml", "© 2024 Testverlag
Das Werk ist urheberrechtlich geschützt.
"), + Doc("a.xhtml", "a
")] + return Book(title="Vorspann", docs=docs, nav=[("Kapitel A", "a.xhtml")], media={"img/a.png": PNG}) + + +def test_merge_places_toc_placeholder_after_front_matter(convert): + run = convert(front_book()) + assert merge_main([str(run.out)]) == 0 + text = (run.out / f"{run.report['input']['name'][:-5]}.md").read_text(encoding="utf-8") + cover, title, imprint = text.index("![Cover]"), text.index("\nRoman"), text.index("© 2024") + toc, first = text.index(''), text.index("# Kapitel A") + assert cover < title < imprint < toc < first + assert "epub2md-titlepage" not in text # the book has its own title page + + +def test_merge_title_placeholder_after_cover_without_own_title_page(convert): + docs = [Doc("cover.xhtml", '
a
")] + run = convert(Book(title="Ohne Titelei", docs=docs, nav=[("Kapitel A", "a.xhtml")], media={"img/a.png": PNG})) + assert merge_main([str(run.out)]) == 0 + text = (run.out / f"{run.report['input']['name'][:-5]}.md").read_text(encoding="utf-8") + assert text.index("![Cover]") < text.index('') \ + < text.index('') < text.index("# Kapitel A") + + +def test_merge_own_toc_replaces_placeholder(convert): + run = convert(front_book()) + assert merge_main([str(run.out), "--toc"]) == 0 + text = (run.out / f"{run.report['input']['name'][:-5]}.md").read_text(encoding="utf-8") + assert "epub2md-toc" not in text and text.index("© 2024") < text.index("## Inhalt") < text.index("# Kapitel A") diff --git a/tests/test_styles.py b/tests/test_styles.py index 2964d5b..b3faa65 100644 --- a/tests/test_styles.py +++ b/tests/test_styles.py @@ -191,8 +191,9 @@ def test_book_readme_suggests_callout_filter(convert): readme = (convert(Book(docs=[Doc("c.xhtml", body)])).out / "README.md").read_text(encoding="utf-8") assert "--lua-filter" in readme and "pandoc/callouts.lua" in readme and "1 Infokasten " in readme assert "pandoc/print.css --pdf-engine=weasyprint" in readme + assert "pandoc/book.lua --lua-filter" in readme # table of contents after the front matter plain = convert(Book(docs=[Doc("c.xhtml", body)]), "--boxes", "plain").out / "README.md" - assert "--lua-filter" not in plain.read_text(encoding="utf-8") + assert "callouts.lua" not in plain.read_text(encoding="utf-8") def test_hidden_span_gets_no_field_separator_and_class_type_wins(convert):