write_shrink_and_turn.py

Die Python-Datei des Beispiels „Verkleinerte Überschriften und eine gedrehte Beschriftung“. Überschriften unterschiedlicher Länge, verkleinert, um sich eine Spalte zu teilen, und eine Beschriftung, um eine Vierteldrehung den Rücken der Seite hinauf gedreht.

Python 207 Zeilen

Wofür dieses Beispiel da ist

Eine Überschrift, deren Länge Sie nicht steuern, ist eine kleine Krise: ein Produktname, die Firma eines Kunden, ein Titel, der auf dem Weg ins Deutsche um ein Drittel gewachsen ist. Wählen Sie eine Größe, die für den schlimmsten Fall klein genug ist, wirkt der gewöhnliche Fall zaghaft; wählen Sie eine für den gewöhnlichen Fall, läuft der schlimmste von der Seite. Keine davon ist eine Entscheidung, die jemand Dokument für Dokument von Hand treffen sollte.

Was dieses Beispiel zeigt

  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
"""Shrinks headings to a fixed column, and turns a label up the page's spine.

The Python twin of the `write_shrink_and_turn` example in Rust. Two things the
layout does so the caller does not have to measure anything. A heading of unknown
length is set with `FontHandle.fit`, which gives the largest size at which it still
fits its column: three headings of very different lengths come out at three sizes,
each filling the same width. And a label is turned a quarter turn with
`Content.rotate`, which sets a block of text on its side about a pivot the caller
names — here, up the left margin like the title on the spine of a book.

The headings and the spine label are held in `Words`, once per language, and
`HQF_PDF_LANG` picks which set is drawn. Each set keeps three headings of very
different lengths, which is what the page is about; the name of the firm on the spine
is the same in every language.

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

from __future__ import annotations

import math
from dataclasses import dataclass
from pathlib import Path

import _language
import _licence
import _out

import hqf_pdf

PAGE_WIDTH = 595.276
MARGIN = 64.0
# The column the headings are fitted into, well inside the spine label.
COLUMN_LEFT = 120.0
COLUMN_WIDTH = PAGE_WIDTH - COLUMN_LEFT - MARGIN

# The firm the statement is drawn up for, which keeps its name in every language.
FIRM = "ACME LTD"


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

    What is not language stays out of it: the firm keeps its name. Each set keeps three
    headings of very different lengths, because what the page shows is three lengths
    coming out at three sizes on one right edge.
    """

    # The headings, longest to shortest: each is set as large as it can be and still
    # fill the column, so all three end at the same right edge.
    headings: tuple[str, str, str]
    # What the spine says after the firm's name.
    spine: str

    def spine_label(self) -> str:
        """Return the label turned up the left margin, the firm's name in front."""
        return f"{FIRM} — {self.spine}"


# The page in English.
ENGLISH = Words(
    headings=(
        "Consolidated statement of professional services rendered",
        "Summary of accounts",
        "Total due",
    ),
    spine="ANNUAL STATEMENT",
)

# The page in French.
FRENCH = Words(
    headings=(
        "Relevé consolidé des prestations de services réalisées",
        "Récapitulatif des comptes",
        "Total à payer",
    ),
    spine="RELEVÉ ANNUEL",
)

# The page in Dutch.
DUTCH = Words(
    headings=(
        "Geconsolideerd overzicht van verleende professionele diensten",
        "Samenvatting van de rekeningen",
        "Te betalen",
    ),
    spine="JAAROVERZICHT",
)

# The page in Portuguese.
PORTUGUESE = Words(
    headings=(
        "Extrato consolidado dos serviços profissionais prestados",
        "Resumo de contas",
        "Total a pagar",
    ),
    spine="EXTRATO ANUAL",
)

# The page in Italian.
ITALIAN = Words(
    headings=(
        "Estratto consolidato dei servizi professionali resi",
        "Riepilogo dei conti",
        "Totale dovuto",
    ),
    spine="ESTRATTO ANNUALE",
)

# The page in Spanish.
SPANISH = Words(
    headings=(
        "Extracto consolidado de servicios profesionales prestados",
        "Resumen de cuentas",
        "Total a pagar",
    ),
    spine="EXTRACTO ANUAL",
)

# The page in German.
GERMAN = Words(
    headings=(
        "Zusammengefasste Aufstellung der erbrachten Dienstleistungen",
        "Kontenübersicht",
        "Zu zahlen",
    ),
    spine="JAHRESAUFSTELLUNG",
)


# 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, _language.GERMAN: GERMAN, _language.SPANISH: SPANISH, _language.ITALIAN: ITALIAN, _language.PORTUGUESE: PORTUGUESE, _language.DUTCH: DUTCH}


def heading(
    content: hqf_pdf.Content, font: hqf_pdf.FontHandle, text: str, baseline: float
) -> None:
    """Sets a heading as large as it fits the column, right edge on the column's."""
    size = font.fit(text, COLUMN_WIDTH, 30.0)
    right = COLUMN_LEFT + COLUMN_WIDTH
    content.draw_text(font, size, right - font.measure(text, size), baseline, text)


def spine_label(
    content: hqf_pdf.Content, font: hqf_pdf.FontHandle, text: str
) -> None:
    """Turns a label a quarter turn up the left margin, about its own start."""
    pivot_x = MARGIN
    pivot_y = MARGIN

    content.save_state()
    content.set_fill(hqf_pdf.Rgb.gray(0.55))
    # Turned a quarter turn about the pivot, the label is then drawn as if it ran along
    # the bottom from there: the turn carries it up the left margin.
    content.rotate(math.pi / 2, pivot_x, pivot_y)
    content.draw_text(font, 12.0, pivot_x, pivot_y, text)
    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("shrink_and_turn.pdf", language)).stem
    )
    font_path = _out.font_path()

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

    content = hqf_pdf.Content()

    # A hairline down the right edge of the column, so the shared right edge of the
    # fitted headings can be seen.
    right = COLUMN_LEFT + COLUMN_WIDTH
    content.save_state()
    content.set_stroke(hqf_pdf.Rgb.gray(0.85))
    content.set_line_width(0.5)
    content.move_to(right, 760.0)
    content.line_to(right, 560.0)
    content.stroke()
    content.restore_state()

    baseline = 740.0
    for text in words.headings:
        heading(content, font, text, baseline)
        baseline -= 56.0

    spine_label(content, font, words.spine_label())

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

    written = document.write(out)
    print(f"wrote {out}: {written} bytes, {len(words.headings)} headings fitted")


if __name__ == "__main__":
    main()