Retraits et espacement des paragraphes

Les deux mêmes paragraphes de six façons : les retraits, la place autour du paragraphe, sa dernière ligne, sa première ligne.

Python write_paragraphs.py 213 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
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
"""Sets the same two paragraphs six ways, one shape to a block: indents, the room
around a paragraph, the alignment of its last line, and where its first baseline
sits.

The Python twin of the `write_paragraphs` example in Rust.

The paragraphs and the heading over each block are written in the language
`HQF_PDF_LANG` names: what a shape does to a paragraph is only visible on a paragraph
somebody can read.

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

from __future__ import annotations

from dataclasses import dataclass
from pathlib import Path

import _language
import _licence
import _out

import hqf_pdf

# The width of a block, and the gap between the two columns of them.
COLUMN = 225.0
# The left edge of the two columns.
LEFT = (60.0, 310.0)
# The top of the first block of each column.
TOP = 780.0
# How far apart the blocks of a column are stacked.
STEP = 190.0

# The body size every block is set at, and the leading it is set on.
SIZE = 10.0
LEADING = 13.0

# The size a block's heading is set at, and the leading it is set on.
HEADING = 7.5
HEADING_LEADING = 9.5

# How far under its heading the text of a block starts.
HEADING_GAP = 8.0


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

    # Two paragraphs, so that every block shows both what happens inside one and what
    # happens between two.
    text: str
    # What each block is headed by, in the order they are set.
    headings: tuple[str, str, str, str, str, str]


# The page in English.
ENGLISH = Words(
    text=(
        "The deposit is held on the account named overleaf and is returned within "
        "thirty days of the keys being handed back.\nDeductions are itemised in "
        "writing, and anything not itemised is not deducted."
    ),
    headings=(
        "Nothing set: the shape a flow has by default.",
        "First line indented; the paragraphs are told apart by that alone.",
        "First line hung out to the left of the block.",
        "Room left above and below each paragraph.",
        "Justified, with the last line of each paragraph centred.",
        "First baseline on the top of the capitals, last line clear of its "
        "descenders.",
    ),
)

# The page in French.
FRENCH = Words(
    text=(
        "Le dépôt de garantie est conservé sur le compte indiqué "
        "au verso et restitué dans les trente jours qui suivent la remise des "
        "clés.\nToute retenue est détaillée par écrit, et ce qui "
        "n'est pas détaillé n'est pas retenu."
    ),
    headings=(
        "Rien de réglé : la forme qu'un bloc prend par défaut.",
        "Première ligne en retrait ; c'est le seul signe qui sépare les "
        "paragraphes.",
        "Première ligne sortie à gauche du bloc.",
        "De l'air laissé au-dessus et au-dessous de chaque paragraphe.",
        "Justifié, la dernière ligne de chaque paragraphe centrée.",
        "Première ligne de base sur le haut des capitales, dernière ligne "
        "dégagée de ses jambages.",
    ),
)

# 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}


def block(
    content: hqf_pdf.Content,
    handle: hqf_pdf.FontHandle,
    flow: hqf_pdf.TextFlow,
    x: float,
    top: float,
    label: str,
    text: str,
) -> None:
    """Sets one block: its heading, the text under it, and the box the flow fills."""
    heading = hqf_pdf.TextFlow(
        handle, HEADING, leading=HEADING_LEADING, color=hqf_pdf.Rgb.gray(0.35)
    )
    heading_lines = heading.break_lines(label, COLUMN)
    heading.draw(content, heading_lines, x, top, COLUMN)

    text_top = top - heading.height(heading_lines) - HEADING_GAP
    lines = flow.break_lines(text, COLUMN)
    flow.draw(content, lines, x, text_top, COLUMN)

    # The box the flow reports filling, so that the room it keeps around a paragraph is
    # visible and not merely stated.
    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()


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("paragraphs.pdf", language)).stem)

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

    blocks = (
        (
            words.headings[0],
            hqf_pdf.TextFlow(handle, SIZE, leading=LEADING),
        ),
        (
            words.headings[1],
            hqf_pdf.TextFlow(handle, SIZE, leading=LEADING, first_line_indent=18.0),
        ),
        (
            words.headings[2],
            hqf_pdf.TextFlow(
                handle,
                SIZE,
                leading=LEADING,
                left_indent=24.0,
                first_line_indent=-24.0,
            ),
        ),
        (
            words.headings[3],
            hqf_pdf.TextFlow(
                handle, SIZE, leading=LEADING, space_before=4.0, space_after=8.0
            ),
        ),
        (
            words.headings[4],
            hqf_pdf.TextFlow(
                handle,
                SIZE,
                leading=LEADING,
                align=hqf_pdf.Align.Justify,
                last_align=hqf_pdf.Align.Center,
                right_indent=12.0,
            ),
        ),
        (
            words.headings[5],
            hqf_pdf.TextFlow(
                handle,
                SIZE,
                leading=LEADING,
                first_baseline=hqf_pdf.FirstBaseline.cap_height(),
                last_baseline=hqf_pdf.LastBaseline.descender(),
            ),
        ),
    )

    content = hqf_pdf.Content()
    for index, (label, flow) in enumerate(blocks):
        block(
            content,
            handle,
            flow,
            LEFT[index // 3],
            TOP - STEP * (index % 3),
            label,
            words.text,
        )

    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()