"""Typography and layout: invisible characters, CSS emphasis, scene breaks, boxes, styles, page markers.""" from conftest import Book, Doc ZW, SHY = "​", "­" def test_p6_invisible_characters_removed_outside_code(convert): body = (f"

Kapitel 1.{ZW}1

Lese{SHY}rinnen und 1.{ZW}1 sowie{ZW}Wort.

" f"
a{ZW}b

x{SHY}y

") run = convert(Book(docs=[Doc("c.xhtml", body)])) text = run.chapter("001") assert "Leserinnen und 1.1 sowieWort." in text assert "# Kapitel 1.1" in text and 'chapter_title: "Kapitel 1.1"' in text assert f"a{ZW}b" in text and f"x{SHY}y" in text # code stays verbatim assert run.report["text_cleanup"]["invisible_chars_removed"] == 4 kept = convert(Book(docs=[Doc("c.xhtml", body)]), "--keep-invisible-chars").chapter("001") assert f"Lese{SHY}rinnen" in kept CSS = b""".i { font-style: italic } .b { font-weight: bold } span.u { text-decoration: underline } .s { text-decoration: line-through } .hoch { vertical-align: super } .weg { display: none } li p.first > span.b { font-weight: normal; font-family: monospace } .schrift-c { font-family: "Courier New", monospace } p.zitat { font-style: italic } h2.t { font-weight: bold; font-style: italic } .url { text-decoration: underline } @media amzn-kf8 { .i { font-style: normal } }""" def styled(body: str, *args: str, convert=None): return convert(Book(docs=[Doc("c.xhtml", body)], media={"style.css": CSS}), *args) def test_p1_css_classes_become_markdown_emphasis(convert): body = ('

K

Ein kursives und fettes Wort, ' 'unterstrichen, gestrichen, x2, ' '[»] sichtbar, printf und ' 'zwei Wörter, inline, ' 'example.org.

' '' '

Ein ganzer kursiver Absatz mit Betonung.

' '

Überschrift bleibt

nur einmal

code
') run = styled(body, convert=convert) text = run.chapter("001") assert "Ein *kursives* und **fettes** Wort, unterstrichen, ~~gestrichen~~, x2, sichtbar," in text assert "`printf` und zwei Wörter, **inline**, [example.org](https://example.org)." in text assert "[»]" not in text and "\\[»\\]" not in text assert "- nicht fett im Listenkopf" in text assert "*Ein ganzer kursiver Absatz mit Betonung.*" in text assert "## Überschrift bleibt" in text and "*nur einmal*" in text and "**nur einmal**" not in text assert "```\ncode\n```" in text assert run.report["css"]["hidden_elements"] == 1 assert run.report["css"]["applied"]["italic"] >= 2 plain = styled(body, "--no-css-emphasis", convert=convert).chapter("001") assert "Ein kursives und fettes Wort" in plain def test_p1_css_emphasis_inside_html_tables(convert): body = ('

T

' '
kursiv und fett
ab
') text = styled(body, convert=convert).chapter("001") assert "kursiv und fett" in text def test_adjacent_emphasis_is_merged(convert): body = ('

K

Fläche (A=r2×π) und ' 'xy sowie ab und c d.

') text = styled(body, convert=convert).chapter("001") assert "2*×π*" in text assert "*xy*" in text and "**ab**" in text and "*c* *d*" in text def test_p4_scene_breaks(convert): body = ('

K

 

Erster Absatz.

 

' '

 

Neue Szene.

Weiter.

***

' '

Nach Ornament.

 

Abschnitt

Text

⁂

Ende.

') text = convert(Book(docs=[Doc("c.xhtml", body)])).chapter("001") body_md = text.split("# K", 1)[1] assert "Erster Absatz.\n\n* * *\n\nNeue Szene." in body_md assert body_md.count("* * *") == 3 # two empties collapse into one; ***, ⁂ are ornaments; none before the heading assert "Weiter.\n\n* * *\n\nNach Ornament." in body_md assert "\\*" not in body_md assert body_md.strip().startswith("Erster Absatz.") # no break at chapter start def test_p4_poems_keep_lines_and_stanzas(convert): body = ('

G

Davor.

»Erste Zeile

zweite Zeile

' '

 

Zweite Strophe«

Danach.

') text = convert(Book(docs=[Doc("c.xhtml", body)])).chapter("001") assert "> »Erste Zeile\\\n> zweite Zeile\n>\n> Zweite Strophe«" in text assert "* * *" not in text BOX_CSS = b""".rahmen { border: 1px solid #333; padding: 4px } .listing { border: 1px solid #ccc }""" def boxed(body: str, *args: str, convert=None): return convert(Book(docs=[Doc("c.xhtml", body)], media={"box.css": BOX_CSS}), *args) def test_p2_boxes_become_callouts(convert): body = ('

K

Vorher.

' '

[»] Erwartungen

' '

Kein Kompendium.

Zweiter Absatz.

' '
Image
' '

Wichtig: Funktionen sind Objekte.

' '

Tipp

Gelb mit Blau mischen.

Mehr Weiß.

' '' '

Achtung, heiß!

' '
code()

Nachher.

') from conftest import PNG run = convert(Book(docs=[Doc("c.xhtml", body)], media={"box.css": BOX_CSS, "tick.png": PNG})) text = run.chapter("001") assert "> [!NOTE]\n> **Erwartungen**\n>\n> Kein Kompendium.\n>\n> Zweiter Absatz." in text assert "> [!IMPORTANT]\n> **Wichtig:** Funktionen sind Objekte." in text and "tick" not in text assert "> [!TIP]\n> Gelb mit Blau mischen.\n>\n> Mehr Weiß." in text # label 'Tipp' = type word, no title assert "> [!NOTE]\n> **Exkurs**\n>\n> Am Rande." in text assert "> [!WARNING]\n> Achtung, heiß!" in text # box recognized via CSS border, type from text assert "```\ncode()\n```" in text and "> ```" not in text # bordered code listing is no box assert "### " not in text # box heading no longer pollutes the outline assert run.report["layout"]["boxes"] == 5 def test_p2_box_modes(convert): body = '

K

' quote = boxed(body, "--boxes", "quote", convert=convert).chapter("001") assert "> **Hinweis zum Pinsel**\n>\n> Sauber halten." in quote and "[!" not in quote htm = boxed(body, "--boxes", "html", convert=convert).chapter("001") assert '
\n\n**Hinweis zum Pinsel**\n\nSauber halten.\n\n
' in htm plain = boxed(body, "--boxes", "plain", convert=convert).chapter("001") assert "## Hinweis zum Pinsel" in plain and "Sauber halten." in plain and ">" not in plain.split("---")[-1] STYLE_CSS = b"""p { font-family: "Aldus", serif; font-size: 0.9em; color: #000 } .rot { color: #942f6a } .gelb { background-color: #ffff00 } .gross { font-size: 1.5em } .kap { font-variant: small-caps } p.mitte { text-align: center } .script { font-family: "Mulled Wine", cursive }""" def styles_book(body: str, *args: str, convert=None): return convert(Book(docs=[Doc("c.xhtml", body)], media={"s.css": STYLE_CSS}), *args) BODY_STYLES = ('

S

Normal rot, markiert, ' 'groß, Kapitälchen, Zierschrift, ' 'English words und deutsch.

' '

Zweiter Absatz.

Dritter Absatz.

Widmung

') def test_p3_semantic_styles_default(convert): text = styles_book(BODY_STYLES, convert=convert).chapter("001") assert "Normal rot, markiert, groß, Kapitälchen, Zierschrift, English words und deutsch." in text assert "style=" not in text and "lang=" not in text def test_p3_p7_html_styles(convert): run = styles_book(BODY_STYLES, "--styles", "html", convert=convert) text = run.chapter("001") assert 'rot' in text assert 'markiert' in text assert 'groß' in text assert 'Kapitälchen' in text assert "Zierschrift" in text assert 'English words und deutsch.' in text assert '
\n\nWidmung\n\n
' in text assert "Zweiter Absatz." in text and "Aldus" not in text # base paragraph style is not repeated assert run.report["css"]["applied"]["style"] >= 5 PAGES = ('

K

Ende Seite 89.

' '

Text weiter, Wort und fort.

' '

Absatz mit Absatz-ID.

Kapitelanfang

x

code
' '
1.2Tabellentitel
') def test_p5_page_markers(convert): none = convert(Book(docs=[Doc("c.xhtml", PAGES)])).chapter("001") assert "S. 90" not in none and "S. 12" not in none vis = convert(Book(docs=[Doc("c.xhtml", PAGES)]), "--page-markers", "visible") text = vis.chapter("001") assert "Ende Seite 89.\n\n\\[S. 90\\]" in text assert "Text\\[S. 12\\] weiter, \\[S. 13\\]Wort und\\[S. 14\\] fort." in text assert "S. 15" not in text # paragraph id, not a page assert "\\[S. 16\\]\n\n## Kapitelanfang" in text # marker before the heading, not inside it assert "```\ncode\n```" in text # nothing inside code assert "\\[S. 18\\]\n\n## 1.2 Tabellentitel" in text # layout-table heading keeps the page assert vis.report["layout"]["page_markers"] == 6 com = convert(Book(docs=[Doc("c.xhtml", PAGES)]), "--page-markers", "comment").chapter("001") assert "" in com and "Text weiter" in com def test_book_readme_suggests_callout_filter(convert): body = '

K

Merke

Wichtiger Satz.

' 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 plain = convert(Book(docs=[Doc("c.xhtml", body)]), "--boxes", "plain").out / "README.md" assert "--lua-filter" not in plain.read_text(encoding="utf-8") def test_hidden_span_gets_no_field_separator_and_class_type_wins(convert): css = b".u { text-decoration: underline; } .weg { display: none; }" body = ('

K

unterstrichenVERSTECKT.

' '

Wichtige Regel

Immer testen.

') text = convert(Book(docs=[Doc("c.xhtml", body)], media={"s.css": css})).chapter("001") assert "unterstrichen." in text and "VERSTECKT" not in text assert "> [!NOTE]\n> **Wichtige Regel**" in text # class 'hinweis' beats the word in the title