2026-10-06 02:15:35 +02:00
|
|
|
|
"""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"<h1>Kapitel 1.{ZW}1</h1><p>Lese{SHY}rinnen und 1.{ZW}1 sowie{ZW}Wort.</p>"
|
|
|
|
|
|
f"<pre>a{ZW}b</pre><p><code>x{SHY}y</code></p>")
|
|
|
|
|
|
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
|
2026-10-06 04:52:36 +02:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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 = ('<h1>K</h1><p>Ein <span class="i">kursives</span> und <span class="b">fettes</span> Wort, '
|
|
|
|
|
|
'<span class="u">unterstrichen</span>, <span class="s">gestrichen</span>, x<span class="hoch">2</span>, '
|
|
|
|
|
|
'<span class="weg">[»] </span>sichtbar, <span class="schrift-c">printf</span> und '
|
|
|
|
|
|
'<span class="schrift-c">zwei Wörter</span>, <span style="font-weight:700">inline</span>, '
|
|
|
|
|
|
'<span class="url"><a href="https://example.org">example.org</a></span>.</p>'
|
|
|
|
|
|
'<ul><li><p class="first"><span class="b">nicht fett im Listenkopf</span></p></li></ul>'
|
|
|
|
|
|
'<p class="zitat">Ein ganzer kursiver Absatz mit <em>Betonung</em>.</p>'
|
|
|
|
|
|
'<h2 class="t">Überschrift bleibt</h2><p><em class="i">nur einmal</em></p><pre><span class="b">code</span></pre>')
|
|
|
|
|
|
run = styled(body, convert=convert)
|
|
|
|
|
|
text = run.chapter("001")
|
|
|
|
|
|
assert "Ein *kursives* und **fettes** Wort, <u>unterstrichen</u>, ~~gestrichen~~, x<sup>2</sup>, 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 = ('<h1>T</h1><table><tr><td colspan="2"><span class="i">kursiv</span> und <span class="b">fett</span></td></tr>'
|
|
|
|
|
|
'<tr><td>a</td><td>b</td></tr></table>')
|
|
|
|
|
|
text = styled(body, convert=convert).chapter("001")
|
|
|
|
|
|
assert "<em>kursiv</em> und <strong>fett</strong>" in text
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_adjacent_emphasis_is_merged(convert):
|
|
|
|
|
|
body = ('<h1>K</h1><p>Fläche (A=r<sup>2</sup><span class="i">×</span><span class="i">π</span>) und '
|
|
|
|
|
|
'<em>x</em><em>y</em> sowie <strong>a</strong><strong>b</strong> und <em>c</em> <em>d</em>.</p>')
|
|
|
|
|
|
text = styled(body, convert=convert).chapter("001")
|
|
|
|
|
|
assert "<sup>2</sup>*×π*" in text
|
|
|
|
|
|
assert "*xy*" in text and "**ab**" in text and "*c* *d*" in text
|
2026-10-06 04:57:03 +02:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_p4_scene_breaks(convert):
|
|
|
|
|
|
body = ('<h1>K</h1><p class="leerzeile"> </p><p>Erster Absatz.</p><p class="leerzeile"> </p>'
|
|
|
|
|
|
'<p class="leerzeile"> </p><p>Neue Szene.</p><p>Weiter.</p><p class="centertext">***</p>'
|
|
|
|
|
|
'<p>Nach Ornament.</p><p> </p><h2>Abschnitt</h2><p>Text</p><p>⁂</p><p>Ende.</p>')
|
|
|
|
|
|
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 = ('<h1>G</h1><p>Davor.</p><div class="poem"><p>»Erste Zeile</p><p>zweite Zeile</p>'
|
|
|
|
|
|
'<p class="leerzeile"> </p><p>Zweite Strophe«</p></div><p>Danach.</p>')
|
|
|
|
|
|
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
|