#!/usr/bin/env python3 """ Generate a test EPUB for
section-break rendering. Tests that a bare
element between paragraphs produces a visible blank-line gap (section separator), while a
inside a paragraph only produces a line break with no extra spacing. Cases covered: 1. Standalone
between paragraphs (section break — must show gap). 2.
with a CSS class (calibre-style section break). 3. Multiple consecutive
elements (each adds one line of spacing). 4. Inline
inside a

(line break only — no extra gap). 5.
at start of chapter (no gap before first paragraph). 6.
following a heading. Visual verification instructions are embedded as the first paragraph of each chapter so a human tester can confirm the expected result on device. """ import os import zipfile from pathlib import Path OUTPUT_DIR = Path(__file__).parent.parent / "test" / "epubs" OUTPUT_PATH = OUTPUT_DIR / "test_br_section_break.epub" FILLER = ( "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod " "tempor incididunt ut labore et dolore magna aliqua." ) CSS = """\ body { margin: 0; padding: 0; } p { margin-top: 1pt; margin-bottom: 0; text-indent: 1em; text-align: justify; } h1 { text-align: center; margin-top: 0.5em; margin-bottom: 0.5em; } h2 { text-align: center; margin-top: 0.5em; margin-bottom: 0.5em; } .section-br { display: block; } """ def xhtml(title, body): return f"""\ {title} {body} """ # --------------------------------------------------------------------------- # Chapter 1 — standalone
between paragraphs # --------------------------------------------------------------------------- ch1 = xhtml("Ch1: Standalone br", f"""

Ch 1: Standalone <br> Section Break

PASS: A visible blank-line gap should appear between the two sections below.

{FILLER}


{FILLER}

PASS: The gap above should be roughly one line tall (same as a blank line).

""") # --------------------------------------------------------------------------- # Chapter 2 —
CSS-classed section break (calibre style) # --------------------------------------------------------------------------- ch2 = xhtml("Ch2: Classed br", f"""

Ch 2: <br class="section-br"/>

PASS: A blank-line gap should appear between the two sections below, identical to Ch 1, even though the <br> carries a CSS class.

{FILLER}


{FILLER}

""") # --------------------------------------------------------------------------- # Chapter 3 — multiple consecutive
elements # --------------------------------------------------------------------------- ch3 = xhtml("Ch3: Multiple br", f"""

Ch 3: Multiple Consecutive <br> Elements

PASS: Two blank lines should appear between the sections (one per <br>).

{FILLER}



{FILLER}

PASS: Three blank lines should appear below.

{FILLER}




{FILLER}

""") # --------------------------------------------------------------------------- # Chapter 4 — inline
inside a paragraph (line break, NOT a gap) # --------------------------------------------------------------------------- ch4 = xhtml("Ch4: Inline br", """

Ch 4: Inline <br> Inside a Paragraph

PASS: The two lines below should be adjacent with NO extra gap between them. The <br> is inside the paragraph and must only break the line.

First line of the paragraph.
Second line of the paragraph — directly below, no gap.

PASS: Above should look like two closely-spaced lines, not like two paragraphs separated by a blank line.

""") # --------------------------------------------------------------------------- # Chapter 5 —
following a heading # --------------------------------------------------------------------------- ch5 = xhtml("Ch5: br after heading", f"""

Ch 5: <br> After a Heading


PASS: There should be a blank-line gap between the heading above and this paragraph.

{FILLER}

Section heading


PASS: There should be a blank-line gap between the section heading and this paragraph.

""") # --------------------------------------------------------------------------- # Chapter 6 —
at very start of chapter (no spurious leading gap) # --------------------------------------------------------------------------- ch6 = xhtml("Ch6: br at chapter start", f"""

Ch 6: <br> at Chapter Start

PASS: This heading should appear near the top of the page with no large blank area above it despite the <br> being the very first element.

{FILLER}

""") CHAPTERS = [ ("ch1", "chapter1.xhtml", "Chapter 1: Standalone br", ch1), ("ch2", "chapter2.xhtml", "Chapter 2: Classed br", ch2), ("ch3", "chapter3.xhtml", "Chapter 3: Multiple br", ch3), ("ch4", "chapter4.xhtml", "Chapter 4: Inline br", ch4), ("ch5", "chapter5.xhtml", "Chapter 5: br after heading", ch5), ("ch6", "chapter6.xhtml", "Chapter 6: br at start", ch6), ] def build_epub(path): os.makedirs(os.path.dirname(path), exist_ok=True) with zipfile.ZipFile(path, "w", zipfile.ZIP_DEFLATED) as epub: # mimetype must be first and uncompressed epub.writestr("mimetype", "application/epub+zip", compress_type=zipfile.ZIP_STORED) epub.writestr("META-INF/container.xml", """\ """) epub.writestr("OEBPS/styles/test.css", CSS) manifest_items = [] spine_items = [] nav_items = [] for (chid, chfile, chtitle, chcontent) in CHAPTERS: epub.writestr(f"OEBPS/{chfile}", chcontent) manifest_items.append( f' ') spine_items.append(f' ') nav_items.append(f'
  • {chtitle}
  • ') manifest_items.append( ' ') content_opf = f"""\ test-epub-br-section-break Test: br Section Break en {chr(10).join(manifest_items)} {chr(10).join(spine_items)} """ epub.writestr("OEBPS/content.opf", content_opf) nav_xhtml = f"""\ Table of Contents """ epub.writestr("OEBPS/nav.xhtml", nav_xhtml) print(f"Generated: {path}") if __name__ == "__main__": build_epub(OUTPUT_PATH)