- CSS in pandoc/callouts.css (eine Quelle), vom Filter neben sich gelesen - HTML: <style> am Dokumentanfang statt header-includes, das ein -H auf der Kommandozeile ersetzt hat (PDF über weasyprint mit Druck-CSS: Kästen ohne Rahmen) - EPUB: Kasten-CSS als Stylesheet aller Kapitel (zusammen mit Pandocs Standard-epub.css oder zu eigenen css:-Angaben); vorher nur auf der Titelseite Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
174 lines
5.2 KiB
Lua
174 lines
5.2 KiB
Lua
--[[
|
||
callouts.lua – Pandoc filter for the info boxes written by epub2md (--boxes callout).
|
||
|
||
epub2md writes boxes as GitHub alerts / Obsidian callouts:
|
||
|
||
> [!TIP]
|
||
> **Title**
|
||
>
|
||
> Text …
|
||
|
||
Pandoc < 3.1.10 does not know this syntax and prints "[!TIP]" literally. This filter turns such
|
||
block quotes into real boxes:
|
||
|
||
* HTML, EPUB: <div class="callout callout-tip"> with a bold label, styled by callouts.css
|
||
(next to this filter; HTML: <style> at the start of the body, so -H cannot drop it;
|
||
EPUB: added to the book's stylesheets together with Pandoc's default epub.css)
|
||
* other formats (LaTeX/PDF, DOCX, ODT …): block quote with a bold label "Tipp: Title"
|
||
|
||
It also handles <div class="callout …"> coming back from HTML (md -> html -> pdf route) and styles
|
||
the <div class="box …"> blocks of --boxes html.
|
||
|
||
Usage:
|
||
pandoc buch.md -s --lua-filter /pfad/zu/epub2md/pandoc/callouts.lua -o buch.html
|
||
]]
|
||
|
||
local LABELS = {
|
||
de = { NOTE = "Hinweis", TIP = "Tipp", IMPORTANT = "Wichtig", WARNING = "Warnung", CAUTION = "Vorsicht" },
|
||
en = { NOTE = "Note", TIP = "Tip", IMPORTANT = "Important", WARNING = "Warning", CAUTION = "Caution" },
|
||
}
|
||
|
||
-- callouts.css next to this filter is the single source of the box styles.
|
||
local function read_file(path)
|
||
local fh = io.open(path, "r")
|
||
if not fh then
|
||
return nil
|
||
end
|
||
local text = fh:read("a")
|
||
fh:close()
|
||
return text
|
||
end
|
||
|
||
local CSS_PATH = (PANDOC_SCRIPT_FILE and PANDOC_SCRIPT_FILE:match("^(.*[/\\])") or "") .. "callouts.css"
|
||
local CSS = read_file(CSS_PATH)
|
||
if not CSS then
|
||
io.stderr:write("callouts.lua: " .. CSS_PATH .. " nicht gefunden – Kästen ohne Rahmen\n")
|
||
end
|
||
|
||
-- EPUB: a stylesheet in metadata `css` replaces Pandoc's default epub.css, so combine both in a temp file.
|
||
local function epub_stylesheet()
|
||
local default = ""
|
||
local pipe = io.popen("pandoc --print-default-data-file epub.css 2>/dev/null")
|
||
if pipe then
|
||
default = pipe:read("a") or ""
|
||
pipe:close()
|
||
end
|
||
local path = os.tmpname() -- small temp file; Pandoc reads it after the filter has finished
|
||
local fh = io.open(path, "w")
|
||
if not fh then
|
||
return nil
|
||
end
|
||
fh:write(default, "\n", CSS)
|
||
fh:close()
|
||
return path
|
||
end
|
||
|
||
local lang = "de"
|
||
|
||
local function is_html()
|
||
return FORMAT:match("html") ~= nil or FORMAT:match("epub") ~= nil
|
||
end
|
||
|
||
local function label_for(kind)
|
||
local labels = LABELS[lang] or LABELS.de
|
||
return labels[kind] or labels.NOTE
|
||
end
|
||
|
||
local function drop_leading_breaks(inlines)
|
||
while #inlines > 0 and (inlines[1].t == "SoftBreak" or inlines[1].t == "LineBreak" or inlines[1].t == "Space") do
|
||
inlines:remove(1)
|
||
end
|
||
return inlines
|
||
end
|
||
|
||
-- Build the box from its kind ("TIP" …), optional title inlines and body blocks.
|
||
local function make_box(kind, title, body)
|
||
local head = pandoc.Inlines({ pandoc.Str(label_for(kind)) })
|
||
if title and #title > 0 then
|
||
head:insert(pandoc.Str(":"))
|
||
head:insert(pandoc.Space())
|
||
head:extend(title)
|
||
end
|
||
local blocks = pandoc.Blocks({ pandoc.Para({ pandoc.Strong(head) }) })
|
||
blocks:extend(body)
|
||
if is_html() then
|
||
return pandoc.Div(blocks, pandoc.Attr("", { "callout", "callout-" .. kind:lower() }))
|
||
end
|
||
return pandoc.BlockQuote(blocks)
|
||
end
|
||
|
||
local function Meta(meta)
|
||
if meta.lang then
|
||
local l = pandoc.utils.stringify(meta.lang):lower():sub(1, 2)
|
||
if LABELS[l] then
|
||
lang = l
|
||
end
|
||
end
|
||
if CSS and FORMAT:match("epub") then
|
||
local css = meta.css
|
||
local path = nil
|
||
if css == nil then -- no own stylesheet: default epub.css + ours; otherwise just add ours
|
||
path = epub_stylesheet()
|
||
end
|
||
if css == nil then
|
||
css = pandoc.List({})
|
||
elseif pandoc.utils.type(css) ~= "List" then
|
||
css = pandoc.List({ css })
|
||
end
|
||
if path then
|
||
css:insert(pandoc.MetaString(path))
|
||
else
|
||
css:insert(pandoc.MetaString(CSS_PATH))
|
||
end
|
||
meta.css = css
|
||
end
|
||
return meta
|
||
end
|
||
|
||
local function BlockQuote(quote)
|
||
local first = quote.content[1]
|
||
if not first or first.t ~= "Para" or #first.content == 0 or first.content[1].t ~= "Str" then
|
||
return nil
|
||
end
|
||
local kind = first.content[1].text:match("^%[!(%a+)%]$")
|
||
if not kind then
|
||
return nil
|
||
end
|
||
kind = kind:upper()
|
||
local rest = drop_leading_breaks(first.content:clone())
|
||
rest:remove(1)
|
||
rest = drop_leading_breaks(rest)
|
||
local body = pandoc.Blocks({})
|
||
for i = 2, #quote.content do
|
||
body:insert(quote.content[i])
|
||
end
|
||
local title = nil
|
||
if #rest == 1 and rest[1].t == "Strong" then
|
||
title = rest[1].content -- "> **Title**" line
|
||
elseif #rest > 0 then
|
||
body:insert(1, pandoc.Para(rest)) -- text directly after the marker
|
||
end
|
||
return make_box(kind, title, body)
|
||
end
|
||
|
||
-- <div class="callout callout-tip"> read back from HTML: keep it as a box in non-HTML formats.
|
||
local function Div(div)
|
||
if not div.classes:includes("callout") or is_html() then
|
||
return nil
|
||
end
|
||
return pandoc.BlockQuote(div.content)
|
||
end
|
||
|
||
-- HTML: the stylesheet goes into the body, because a command-line -H replaces header-includes from a filter.
|
||
local function Pandoc(doc)
|
||
if CSS and FORMAT:match("html") and not FORMAT:match("epub") then
|
||
doc.blocks:insert(1, pandoc.RawBlock("html", "<style>\n" .. CSS .. "</style>"))
|
||
end
|
||
return doc
|
||
end
|
||
|
||
return {
|
||
{ Meta = Meta },
|
||
{ BlockQuote = BlockQuote, Div = Div },
|
||
{ Pandoc = Pandoc },
|
||
}
|