PDF: Font-Fallback für Emoji und Sonderzeichen - 2070 Missing-character-Faelle behoben

This commit is contained in:
dschlueter 2026-09-09 19:10:50 +02:00
commit abc7a5693f
8 changed files with 198 additions and 1 deletions

View file

@ -29,6 +29,11 @@ bilder_04/ Quelle: Diagramme + erzeuge_*.py-
web_04/ Quelle: site.css, site.js, icons.svg, web_04/ Quelle: site.css, site.js, icons.svg,
plotly.min.js, katex/ - die statischen plotly.min.js, katex/ - die statischen
Bestandteile der Website Bestandteile der Website
pandoc/, pandoc-defaults-basis.yaml, pandoc-defaults-buch.yaml PDF-Konfiguration
(pandoc/emoji_fallback.lua schaltet Zeichen,
die Times New Roman fehlt, auf Symbola um -
siehe PROGRESS.md, "Sonderzeichen in der
PDF-Fassung")
Operations_Research_mit_Python_Version_04.md generiert: Gesamtdokument Operations_Research_mit_Python_Version_04.md generiert: Gesamtdokument
Operations_Research_mit_Python_Version_04.pdf generiert: PDF (xelatex) Operations_Research_mit_Python_Version_04.pdf generiert: PDF (xelatex)
OR_HTML_04/ generiert: Mehrseiten-Website OR_HTML_04/ generiert: Mehrseiten-Website

View file

@ -1194,6 +1194,21 @@ def baue_pdf() -> None:
print(f"PDF wurde nicht erzeugt - LaTeX-Protokoll in " print(f"PDF wurde nicht erzeugt - LaTeX-Protokoll in "
f"{BUILD_BASISNAME}.log pruefen.") f"{BUILD_BASISNAME}.log pruefen.")
# Bewachung des Sonderzeichen-Fallbacks: xelatex verschweigt fehlende
# Glyphen im Druckbild (nur "Missing character" im Protokoll). Der Filter
# pandoc/emoji_fallback.lua deckt die bekannten Zeichen ab; taucht hier
# eine Zahl auf, kennt Times New Roman ein Zeichen, das der Filter noch
# nicht umschaltet - Zeichenliste im Filter ergaenzen.
protokoll = os.path.join(BASIS, BUILD_BASISNAME + ".log")
if os.path.exists(protokoll):
with open(protokoll, encoding="utf-8", errors="replace") as datei:
fehlend = sum(1 for zeile in datei if "Missing character" in zeile)
if fehlend:
print(f" WARNUNG: {fehlend} fehlende Zeichen im LaTeX-Protokoll "
f"(pandoc/emoji_fallback.lua erweitern)")
else:
print(" Kein fehlendes Zeichen - Font-Fallback deckt alles ab.")
for endung in (".tex", ".aux", ".log", ".out", ".toc", ".idx", ".ind", ".ilg"): for endung in (".tex", ".aux", ".log", ".out", ".toc", ".idx", ".ind", ".ilg"):
pfad = os.path.join(BASIS, BUILD_BASISNAME + endung) pfad = os.path.join(BASIS, BUILD_BASISNAME + endung)
if os.path.exists(pfad) and endung != ".log": if os.path.exists(pfad) and endung != ".log":

View file

@ -2592,6 +2592,45 @@ eine **doppelte Nummer** — „Abbildung 21: Abb. 7.2: …". Die erste vergibt
`nummeriere_marken()`. Das betrifft alle 48 Abbildungen und besteht seit Langem; behoben wäre `nummeriere_marken()`. Das betrifft alle 48 Abbildungen und besteht seit Langem; behoben wäre
es dadurch, dass die LaTeX-Nummerierung der Gleitumgebung unterdrückt wird. es dadurch, dass die LaTeX-Nummerierung der Gleitumgebung unterdrückt wird.
### Sonderzeichen in der PDF-Fassung — ein stiller Font-Fehler behoben ✅
In der gesamten PDF-Fassung fehlten alle Zeichen, die Times New Roman nicht mitbringt: die
Lernelemente-Symbole (⭐ ✏ ❓ 🎯 🚀 …), aber auch die Sub-/Superskripte (₀ ₁ ₂ ₜ ₙ ⁶ ᵀ) und die
langen Pfeile (⟹ ⟶ ⟵ ⇒). Das alte LaTeX-Protokoll enthielt **rund 2 070**
`Missing character`-Meldungen; die Zeichen fielen stillschweigend aus dem Druckbild.
Zwei Ursachen, beide lange unentdeckt, weil die Website die Zeichen korrekt zeigt
(der Browser weicht selbst auf eine Emoji-Schrift aus):
* xelatex hat keine automatische Font-Fallback-Kette — fehlt einem Zeichen der Glyph, wird
es weggelassen, ohne dass der Bau fehlschlägt.
* Die pandoc-Variable `emoji-font: "Noto Color Emoji"` wertet der LaTeX-Writer
(pandoc 3.1.3) **nicht aus** — sie hat seit ihrer Einführung still nichts getan.
**Die Lösung ist zweistufig**, weil die Zeichen auf zwei Wegen in den PDF gelangen:
1. **`pandoc/emoji_fallback.lua`** (neu) — ein pandoc-Filter, der die betreffenden Zeichen
im Fließtext in `{\texorpdfstring{\symfont{…}}{…}}` einschüttet. `\symfont` ist
**Symbola** — die einzige Schrift auf dem Bausystem, die das komplette Set abdeckt
(Noto Color Emoji funktioniert unter XeTeX nicht: die Zeichen landen im `nullfont`);
monochrom, für den Drucksatz ohnehin passend. Der `\texorpdfstring`-Antil hält die
PDF-Lesezeichen ruhig und erhalten. Variationsselektoren (U+FE0F) werden entfernt.
Zwei pandoc-3.1.3-Eigenheiten unterwegs gefunden: Filterhandler funktionieren **nur als
globale Funktionen** — ein vom Filter zurückgegebenes Table wird stillschweigend
ignoriert; und der Einschub muss `RawInline` sein, als `Str` würde pandoc die
Backslashes escapen.
2. **`ucharclasses`** in `pandoc/header-includes.tex` — für den **Diagrammtext**: Das Paket
`svg` exportiert die SVG-Texte (x₁, σᵀ, ∈ in Achsen- und Knotenbeschriftungen) als
LaTeX-Text, dorthin reicht der AST-Filter nicht. Die Zurück-Umschaltung ist bewusst
`\fontfamily{\familydefault}\selectfont` statt `\normalfont``\normalfont` würde den
Fettdruck hinter dem Stern einer Aufgabentitel-Zeile zerstören (gemessen mit
pdfminer: „Titel“ bleibt Bold).
Die tote `emoji-font`-Variable ist aus `pandoc-defaults-basis.yaml` entfernt (mit
Begründung), und `baue_pdf()` zählt die `Missing character`-Meldungen im Protokoll:
Der neue Lauf meldet **null fehlende Zeichen**; taucht die Warnung wieder auf, kennt Times
New Roman ein Zeichen, das der Filter noch nicht umschaltet. Symbola ist damit eingebettet,
der Buchumfang bleibt bei 792 Seiten; alle HTML-Seiten sind byteidentisch geblieben.
--- ---
## 8. Commit-Historie des V04-Strangs ## 8. Commit-Historie des V04-Strangs

View file

@ -39,6 +39,15 @@ metadata:
include-in-header: include-in-header:
- pandoc/header-includes.tex - pandoc/header-includes.tex
# Sonderzeichen, die Times New Roman fehlen (Emoji, Lernelemente-Symbole,
# Sub-/Superskripte, lange Pfeile), auf \symfont (= Symbola, definiert in
# pandoc/header-includes.tex) umschalten. Ohne diesen Filter liess xelatex
# alle betreffenden Zeichen in der PDF-Fassung stillschweigend weg —
# die Website zeigte sie, das PDF nicht. Nur für den PDF-Bau; der HTML-Bau
# benutzt diese Defaults nicht.
filters:
- pandoc/emoji_fallback.lua
# -------------------------------- # --------------------------------
# Fonts & Layout Variables # Fonts & Layout Variables
# -------------------------------- # --------------------------------
@ -48,7 +57,9 @@ variables:
monofont: "DejaVu Sans Mono" monofont: "DejaVu Sans Mono"
# sansfont: "DejaVu Sans" # sansfont: "DejaVu Sans"
sansfont: "Arial" # Sans-Serif sansfont: "Arial" # Sans-Serif
emoji-font: "Noto Color Emoji" # "emoji-font" hier bewusst NICHT gesetzt: Die Variable wertet der
# LaTeX-Writer (pandoc 3.1.3) nicht aus, sie hat jahrelang still nichts
# getan. Die Emoji-Abdeckung läuft über pandoc/emoji_fallback.lua.
mathfont: "STIX Two Math" mathfont: "STIX Two Math"
monofontoptions: "Scale=MatchLowercase" monofontoptions: "Scale=MatchLowercase"
papersize: a4 papersize: a4

91
pandoc/emoji_fallback.lua Normal file
View file

@ -0,0 +1,91 @@
-- emoji_fallback.lua
-- Pandoc-Filter für den PDF-Bau (nur `-t latex`, nur PDF, nicht für die Website).
--
-- Problem: Der Haupttext steht in Times New Roman — diese Schrift kennt weder
-- Emoji noch die Darstellungssymbole der Lernelemente (⭐ ✏ ❓ 🧠 …) und auch
-- keine hoch-/tiefgestellten Ziffern. XeLaTeX besitzt keine automatische
-- Font-Fallback-Kette: Fehlt einem Zeichen der Glyph, wird es stillschweigend
-- weggelassen ("Missing character" nur im Protokoll). Die pandoc-Variable
-- "emoji-font" wird vom LaTeX-Writer (pandoc 3.1.3) nicht ausgewertet — die
-- Zeichen fehlten daher in der gesamten PDF-Fassung, während die Website sie
-- über den Browser-Fontstack korrekt zeigte.
--
-- Lösung: Dieser Filter wickelt alle betreffenden Zeichen in
-- {\texorpdfstring{\symfont{<Zeichen>}}{<Zeichen>}}
-- ein. \symfont ist in pandoc/header-includes.tex auf Symbola gesetzt — die
-- einzige auf dem Bausystem vorhandene Schrift, die das komplette Set abdeckt
-- (Noto Color Emoji funktioniert unter XeTeX nicht, die Zeichen landen im
-- nullfont). Symbola ist monochrom — für den Drucksatz ohnehin passend.
--
-- Der \texorpdfstring-Anteil hält hyperref ruhig: In PDF-Lesezeichen und im
-- Inhaltsverzeichnis steht das schlichte Zeichen statt des Fontwechsels.
--
-- Codeblöcke werden nicht angetastet (Code/CodeBlock sind eigene
-- AST-Elemente): Der Mono-Font DejaVu Sans Mono deckt die dort vorkommenden
-- Symbole (✓ ✗ ★, Sub-/Superskripte) selbst ab.
local function braucht_symfont(cp)
return (cp >= 0x1F000 and cp <= 0x1FAFF) -- Emoji-Blöcke (🎯 📌 🚀 …)
or (cp >= 0x2600 and cp <= 0x27BF) -- Misc Symbols + Dingbats (★ ⚠ ✅ ✏ ✓ ✗ ❌ ❓)
or (cp >= 0x2B00 and cp <= 0x2BFF) -- Misc Symbols and Arrows (⭐)
or (cp >= 0x27F5 and cp <= 0x27FF) -- Supplemental Arrows-A (⟵ ⟶ ⟹)
or (cp >= 0x2070 and cp <= 0x209F) -- hoch-/tiefgestellte Ziffern (⁶ ₀ ₁ ₂ ₜ ₙ)
or cp == 0x21D2 -- ⇒
or cp == 0x1D40 -- ᵀ (Modifier Letter Capital T)
end
local function ersetze(text)
local teile = {}
local normal = {}
local symbole = nil -- Puffer für aufeinanderfolgende Fallback-Zeichen
local function flush_normal()
if #normal > 0 then
teile[#teile + 1] = pandoc.Str(table.concat(normal))
normal = {}
end
end
local function flush_symbole()
if symbole and #symbole > 0 then
local zeichen = table.concat(symbole)
teile[#teile + 1] = pandoc.RawInline("latex",
"{\\texorpdfstring{\\symfont{" .. zeichen .. "}}{"
.. zeichen .. "}}")
symbole = nil
end
end
for _, cp in utf8.codes(text) do
if cp == 0xFE0F then
-- Variationsselektor (Emoji- vs. Textdarstellung): für das PDF ohne
-- Bedeutung, Symbola enthält keinen sichtbaren Glyphen — weglassen.
elseif braucht_symfont(cp) then
flush_normal()
symbole = symbole or {}
symbole[#symbole + 1] = utf8.char(cp)
else
flush_symbole()
normal[#normal + 1] = utf8.char(cp)
end
end
flush_normal()
flush_symbole()
return teile
end
-- pandoc 3.1.3 ruft Filterhandler ausschließlich als globale Funktionen auf;
-- ein zurückgegebenes Handler-Table würde stillschweigend ignoriert.
function Str(el)
-- Schneller Ausstieg: nur anfassen, wenn überhaupt Multibyte drin ist.
if not el.text:find("[\194-\244]") then
return nil
end
local ok, teile = pcall(ersetze, el.text)
if not ok then
-- Kaputtes UTF-8 o. ä.: Element unverändert durchreichen.
return nil
end
return teile
end

View file

@ -2,5 +2,41 @@
\setmonofont{DejaVu Sans Mono}[ \setmonofont{DejaVu Sans Mono}[
Scale=MatchLowercase Scale=MatchLowercase
] ]
% --- Symbol-/Emoji-Fallback ------------------------------------------------
% Times New Roman (Haupttext) kennt weder Emoji noch die Symbole der
% Lernelemente und keine hoch-/tiefgestellten Ziffern. XeLaTeX hat keine
% automatische Fallback-Kette — fehlende Zeichen fielen stillschweigend weg.
% Der Filter pandoc/emoji_fallback.lua schaltet die betreffenden Zeichen
% deshalb auf \symfont um. Symbola ist die einzige Schrift auf dem
% Bausystem, die das komplette Set abdeckt; Noto Color Emoji funktioniert
% unter XeTeX nicht (die Zeichen landen im nullfont). Monochrom — für den
% Drucksatz ohnehin passend.
\newfontfamily\symfont{Symbola}
% --- Automatische Umschaltung für den Diagrammtext --------------------------
% Die SVG-Diagramme werden vom Paket "svg" mit Inkscape nach LaTeX
% exportiert; deren Texte (Achsenbeschriftungen, Knotenlabels wie x₁, σᵀ)
% setzt LaTeX selbst — da erreicht der pandoc-Filter nicht mehr hin. Für
% diese Ebene übernimmt ucharclasses die Umschaltung auf \symfont je
% Unicode-Block. Die Zurück-Umschaltung ist bewusst
% \fontfamily{\familydefault}\selectfont und NICHT \normalfont:
% \normalfont würde auch Fettdruck und Kursiv zurücksetzen — "Aufgabe ⭐ —
% Titel" wäre nach dem Stern nur noch mager. Im Fließtext schaltet der
% pandoc-Filter bereits in geschlossenen Gruppen um; ucharclasses greift
% dort nur innerhalb dieser Gruppen und bleibt unsichtbar.
\usepackage[quiet]{ucharclasses}
\setTransitionsFor{SuperscriptsAndSubscripts}{\symfont}{\fontfamily{\familydefault}\selectfont}
\setTransitionsFor{PhoneticExtensions}{\symfont}{\fontfamily{\familydefault}\selectfont}
\setTransitionsFor{MathematicalOperators}{\symfont}{\fontfamily{\familydefault}\selectfont}
\setTransitionsFor{Arrows}{\symfont}{\fontfamily{\familydefault}\selectfont}
\setTransitionsFor{SupplementalArrowsA}{\symfont}{\fontfamily{\familydefault}\selectfont}
\setTransitionsFor{Dingbats}{\symfont}{\fontfamily{\familydefault}\selectfont}
\setTransitionsFor{MiscellaneousSymbols}{\symfont}{\fontfamily{\familydefault}\selectfont}
\setTransitionsFor{MiscellaneousSymbolsAndArrows}{\symfont}{\fontfamily{\familydefault}\selectfont}
\setTransitionsFor{MiscellaneousSymbolsAndPictographs}{\symfont}{\fontfamily{\familydefault}\selectfont}
\setTransitionsFor{Emoticons}{\symfont}{\fontfamily{\familydefault}\selectfont}
\setTransitionsFor{TransportAndMapSymbols}{\symfont}{\fontfamily{\familydefault}\selectfont}
\setTransitionsFor{SupplementalSymbolsAndPictographs}{\symfont}{\fontfamily{\familydefault}\selectfont}
\usepackage{microtype} \usepackage{microtype}
\usepackage{pifont} \usepackage{pifont}