Pandocs --toc setzt Inhaltsverzeichnis und Titelkopf immer vor den Buchtext, also vor Cover, Titelseite und Impressum. - epub2md_merge.py: Kapiteltypen aus manifest.json; Platzhalter <div id="epub2md-toc"> hinter dem Vorspann und, ohne eigene Titelseite, <div id="epub2md-titlepage"> hinter dem Cover; --toc-Liste an derselben Stelle - pandoc/book.lua (HTML/weasyprint, statt --toc): baut dort das Inhaltsverzeichnis, lässt Pandocs Titelkopf bei eigener Titelseite weg, Cover ohne Bildunterschrift, Kapitelanker → div.chapter-start; andere Formate: nur Platzhalter entfernen - print.css: jedes Kapitel (auch Cover/Titelseite/Impressum) und das Verzeichnis auf neuer Seite, Verzeichnis mit Punktlinie und Seitenzahl - Buch-README, Bedienungsanleitung, README: Befehle mit book.lua Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
140 lines
5.4 KiB
Lua
140 lines
5.4 KiB
Lua
--[[
|
||
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:
|
||
|
||
<div id="epub2md-titlepage"></div> after the cover, only if the book has no title page of its own
|
||
<div id="epub2md-toc"></div> 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 <a id="001-…"></a> into <div class="chapter-start"> so that print.css can
|
||
start every chapter – including cover, title page and imprint – on a new page.
|
||
|
||
For other formats it only removes the placeholders (EPUB, DOCX, ODT: use --toc there).
|
||
|
||
Usage:
|
||
pandoc buch.md -s --lua-filter /pfad/zu/epub2md/pandoc/book.lua \
|
||
--lua-filter /pfad/zu/epub2md/pandoc/callouts.lua --css /pfad/zu/epub2md/pandoc/print.css \
|
||
--pdf-engine=weasyprint -o buch.pdf
|
||
]]
|
||
|
||
local TOC_ID, TITLE_ID = "epub2md-toc", "epub2md-titlepage"
|
||
local TOC_TITLE = { de = "Inhaltsverzeichnis", en = "Contents" }
|
||
|
||
local function is_placeholder(block, id)
|
||
return block.t == "Div" and block.identifier == id and #block.content == 0
|
||
end
|
||
|
||
local function esc(s)
|
||
return (s:gsub("&", "&"):gsub("<", "<"):gsub(">", ">"))
|
||
end
|
||
|
||
-- <header id="title-block-header"> as Pandoc's HTML template writes it.
|
||
local function title_block(meta)
|
||
local html = { '<header id="title-block-header">', '<h1 class="title">' .. esc(pandoc.utils.stringify(meta.title)) .. "</h1>" }
|
||
if meta.subtitle then
|
||
html[#html + 1] = '<p class="subtitle">' .. esc(pandoc.utils.stringify(meta.subtitle)) .. "</p>"
|
||
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] = '<p class="author">' .. esc(pandoc.utils.stringify(a)) .. "</p>"
|
||
end
|
||
if meta.date then
|
||
html[#html + 1] = '<p class="date">' .. esc(pandoc.utils.stringify(meta.date)) .. "</p>"
|
||
end
|
||
html[#html + 1] = "</header>"
|
||
return pandoc.RawBlock("html", table.concat(html, "\n"))
|
||
end
|
||
|
||
-- Paragraph that only holds chapter start anchors (<a id="001-cover"></a>) → id of the first one.
|
||
local function chapter_anchor(block)
|
||
if block.t ~= "Para" and block.t ~= "Plain" then
|
||
return nil
|
||
end
|
||
local id = nil
|
||
for _, inl in ipairs(block.content) do
|
||
if inl.t ~= "RawInline" then
|
||
return nil
|
||
end
|
||
local found = inl.text:match('^<a id="(%d%d%d%-[^"]*)">$') or inl.text:match('^<a id="(%d%d%d%-[^"]*)"></a>$')
|
||
if found then
|
||
id = id or found
|
||
elseif inl.text ~= "</a>" then
|
||
return nil
|
||
end
|
||
end
|
||
return id
|
||
end
|
||
|
||
function Pandoc(doc)
|
||
local html = FORMAT:match("html") and not FORMAT:match("epub")
|
||
local toc_at, title_at = nil, nil
|
||
for i, block in ipairs(doc.blocks) do
|
||
if is_placeholder(block, TOC_ID) then
|
||
toc_at = i
|
||
elseif is_placeholder(block, TITLE_ID) then
|
||
title_at = i
|
||
end
|
||
end
|
||
if not toc_at and not title_at then
|
||
return nil -- not a file from epub2md_merge.py
|
||
end
|
||
if not html then -- placeholders are top-level blocks: just drop them
|
||
local kept = pandoc.Blocks({})
|
||
for _, block in ipairs(doc.blocks) do
|
||
if not is_placeholder(block, TOC_ID) and not is_placeholder(block, TITLE_ID) then
|
||
kept:insert(block)
|
||
end
|
||
end
|
||
doc.blocks = kept
|
||
return doc
|
||
end
|
||
|
||
local blocks = pandoc.Blocks({})
|
||
for i, block in ipairs(doc.blocks) do
|
||
local anchor = chapter_anchor(block)
|
||
if i == toc_at then
|
||
if PANDOC_WRITER_OPTIONS.table_of_contents then
|
||
-- --toc given as well: Pandoc's own table of contents stays at the top, no second one
|
||
else
|
||
local lang = doc.meta.lang and pandoc.utils.stringify(doc.meta.lang):sub(1, 2):lower() or "de"
|
||
local toc = pandoc.structure.table_of_contents(doc.blocks, PANDOC_WRITER_OPTIONS)
|
||
blocks:insert(pandoc.Div({
|
||
pandoc.Header(1, TOC_TITLE[lang] or TOC_TITLE.de, pandoc.Attr("toc-title", { "unnumbered", "unlisted" })),
|
||
toc,
|
||
}, pandoc.Attr("TOC", { "toc" })))
|
||
end
|
||
elseif i == title_at then
|
||
if doc.meta.title then
|
||
blocks:insert(title_block(doc.meta))
|
||
end
|
||
elseif anchor then
|
||
blocks:insert(pandoc.Div({}, pandoc.Attr(anchor, { "chapter-start" })))
|
||
local nxt = doc.blocks[i + 1]
|
||
if anchor:match("^%d%d%d%-cover") and nxt and nxt.t == "Figure" then
|
||
-- cover chapter: the picture without the caption "Cover", centred on its own page
|
||
local imgs = {}
|
||
nxt:walk({ Image = function(img) imgs[#imgs + 1] = img end })
|
||
if #imgs == 1 then
|
||
doc.blocks[i + 1] = pandoc.Div({ pandoc.Plain({ imgs[1] }) }, pandoc.Attr("", { "cover" }))
|
||
end
|
||
end
|
||
else
|
||
blocks:insert(block)
|
||
end
|
||
end
|
||
doc.blocks = blocks
|
||
-- The book's own title page (or the moved block) replaces Pandoc's title block at the very top.
|
||
if doc.meta.title then
|
||
doc.meta.pagetitle = doc.meta.pagetitle or pandoc.utils.stringify(doc.meta.title)
|
||
doc.meta.title = nil
|
||
end
|
||
return doc
|
||
end
|