Traits d'union conditionnels et espaces insécables

Une colonne justifiée composée deux fois : les mots longs coupés à leurs traits d'union conditionnels, trait d'union affiché, ou laissés entiers, et un nombre qu'une espace insécable garde d'un seul tenant.

Python write_soft_hyphen.py 137 lignes
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
"""Sets one justified paragraph in a narrow column twice: above without its soft
hyphens, below with them, so the breaks and the hyphens they draw can be seen
against the loose lines above.

The Python twin of the `write_soft_hyphen` example in Rust. The text carries soft
hyphens (U+00AD) inside its long words and a no-break space (U+00A0) inside the
figure "1 200 EUR". The upper setting is the same text with the soft hyphens taken
out, so only the breaking differs between the two; the no-break space holds the
figure together in both.

The paragraph and the two headings are held in `Words`, once per language, and
`HQF_PDF_LANG` picks which set is drawn. Every language has to carry its own soft
hyphens, in words its own spelling allows to break: a paragraph translated without
them would show nothing.

Usage: python examples/write_soft_hyphen.py [out.pdf] [font.ttf]
       HQF_PDF_LANG=fr python examples/write_soft_hyphen.py
"""

from __future__ import annotations

from dataclasses import dataclass
from pathlib import Path

import _language
import _licence
import _out

import hqf_pdf


@dataclass(frozen=True)
class Words:
    """Every word the page draws, in one language."""

    # What stands over the setting whose soft hyphens have been taken out.
    without: str
    # What stands over the setting that keeps them.
    with_: str
    # The paragraph, with soft hyphens marking where its long words may break and a
    # no-break space inside the figure so it never falls across two lines. Each language
    # marks the breaks its own spelling allows.
    hyphenated: str


# The page in English.
ENGLISH = Words(
    without="Without soft hyphens: justify opens the lines to fill the column.",
    with_="With soft hyphens: the long words break, each break drawing a hyphen.",
    hyphenated=(
        "Our inter­national sub­scription, billed at "
        "1 200 EUR, covers unlimited docu­ments, prior­ity "
        "support and quarterly state­ments."
    ),
)

# The page in French.
FRENCH = Words(
    without=(
        "Sans césures conditionnelles : la justification écarte les mots pour "
        "remplir la colonne."
    ),
    with_=(
        "Avec césures conditionnelles : les mots longs se coupent, et chaque "
        "coupure dessine un trait d'union."
    ),
    hyphenated=(
        "Notre abonne­ment inter­national, factu­ré "
        "1 200 EUR, comprend un nombre illi­mité de "
        "docu­ments, une assis­tance prio­ritaire et des "
        "rele­vés trimes­triels."
    ),
)


# Every language the example is written in. A language is added by writing its own set
# of words and naming it here.
WORDS = {_language.ENGLISH: ENGLISH, _language.FRENCH: FRENCH}

# The column both settings are broken to, narrow enough that the long words reach its
# edge.
COLUMN = 150.0

# The left edge of both settings, stacked one above the other.
X = 80.0


def main() -> None:
    language = _language.from_environment()
    words = _language.words_of(WORDS, language)

    # A named file is written as named; the default one carries the language, so the two
    # languages do not overwrite each other in `tmp/`.
    out = _out.output_path(Path(_language.file_name("soft_hyphen.pdf", language)).stem)

    document = hqf_pdf.Document()
    document.set_license(_licence.licensed())
    handle = document.add_font(hqf_pdf.Font.from_path(_out.font_path()))

    content = hqf_pdf.Content()

    # The upper setting is the same text with its soft hyphens removed, so the two
    # panels differ only in whether the words may break.
    plain = words.hyphenated.replace("­", "")

    for top, label, paragraph in (
        (760.0, words.without, plain),
        (600.0, words.with_, words.hyphenated),
    ):
        heading = hqf_pdf.TextFlow(handle, 8.0)
        heading_lines = heading.break_lines(label, 400.0)
        heading.draw(content, heading_lines, X, top, 400.0)

        text_top = top - 20.0
        flow = hqf_pdf.TextFlow(handle, 11.0, leading=15.0, align=hqf_pdf.Align.Justify)
        lines = flow.break_lines(paragraph, COLUMN)
        flow.draw(content, lines, X, text_top, COLUMN)

        # The column's edge, so the lines can be read against it.
        height = flow.height(lines)
        content.save_state()
        content.set_stroke(hqf_pdf.Rgb.gray(0.8))
        content.set_line_width(0.5)
        content.rect(X, text_top - height, COLUMN, height)
        content.stroke()
        content.restore_state()

    page = hqf_pdf.Page.a4()
    page.set_content(content)
    document.add_page(page)

    written = document.write(out)
    print(f"wrote {out}: {written} bytes")


if __name__ == "__main__":
    main()