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
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406 | """A contents page: every entry led to its page number by a row of dots.
The Python twin of the `write_contents` example in Rust.
The dots are not written into the text. Each entry is a cell that asks for a leader,
and the layout fills whatever room the entry leaves — which is why the dots still meet
the numbers when an entry is edited, translated or set in another font.
What the entries say is written in the language `HQF_PDF_LANG` names. What they are
filed under is not: `1`, `2.3` and the page numbers are the shape of the report, and a
translation that renumbered them would send the reader elsewhere.
Usage: python examples/write_contents.py [out.pdf] [font.ttf]
HQF_PDF_LANG=fr python examples/write_contents.py
"""
from __future__ import annotations
from dataclasses import dataclass
from pathlib import Path
import _language
import _licence
import _out
import hqf_pdf
# A4's width, and the margins the contents are laid out between.
PAGE_WIDTH = 595.276
MARGIN = 64.0
TABLE_WIDTH = PAGE_WIDTH - 2.0 * MARGIN
# The company the report is about, which no language renames.
COMPANY = "ACME Ltd"
# How many spaces stand between what an entry is filed under and what it says.
GAP = " "
@dataclass(frozen=True)
class Entry:
"""One line of the contents: how deep it sits, what it is filed under, and the
page it sends the reader to.
What the entry says is not here: it is a word, and it lives with the other words of
its language.
"""
depth: int
# The number the entry is filed under, or None for the appendix, which is headed by
# a word rather than a number.
number: str | None
page: str
# The shape of the report this page opens: what each entry is filed under, and where it
# sends the reader.
ENTRIES = (
Entry(0, "1", "3"),
Entry(1, "1.1", "3"),
Entry(1, "1.2", "5"),
Entry(0, "2", "8"),
Entry(1, "2.1", "8"),
Entry(1, "2.2", "11"),
Entry(1, "2.3", "14"),
Entry(0, "3", "17"),
Entry(1, "3.1", "17"),
Entry(1, "3.2", "20"),
Entry(1, "3.3", "23"),
Entry(0, "4", "27"),
Entry(1, "4.1", "27"),
Entry(1, "4.2", "31"),
Entry(0, None, "34"),
)
# How far a sub-entry is set in from the margin, in points.
INDENT = 18.0
@dataclass(frozen=True)
class Words:
"""Every word the contents are written in, in one language.
What each entry is filed under is not among them: the numbers and the page they
send the reader to are the shape of the report, and they are held with the entries
themselves.
"""
# What the page is called, both drawn at its head and in what the file says of
# itself.
title: str
# What stands before the name of the company in the line under the title.
subtitle_before: str
# What stands after it.
subtitle_after: str
# What heads the entry that carries no number.
appendix: str
# What each entry says, in the order ENTRIES files them.
entries: tuple[str, ...]
# The contents in English.
ENGLISH = Words(
title="Contents",
subtitle_before="Audit of document production — ",
subtitle_after=", second quarter",
appendix="Appendix",
entries=(
"Scope of the audit",
"What was examined",
"What was left out, and why",
"How the documents are produced today",
"The invoicing run",
"The nightly statement of accounts",
"Documents produced on demand from the counter",
"What the archival standard asks for",
"Fonts that travel with the file",
"Colour that means the same on every screen",
"Text a reading machine can follow",
"What it would cost to change",
"The work, month by month",
"What is saved once it is done",
"Files read for this report",
),
)
# The contents in French.
FRENCH = Words(
title="Sommaire",
subtitle_before="Audit de la production documentaire — ",
subtitle_after=", deuxième trimestre",
appendix="Annexe",
entries=(
"Périmètre de l’audit",
"Ce qui a été examiné",
"Ce qui a été laissé de côté, et pourquoi",
"Comment les documents sont produits aujourd’hui",
"Le traitement de facturation",
"Le relevé de comptes nocturne",
"Les documents produits à la demande au guichet",
"Ce que la norme d’archivage exige",
"Les polices qui voyagent avec le fichier",
"Une couleur identique sur tous les écrans",
"Un texte que les lecteurs d’écran peuvent suivre",
"Ce que le changement coûterait",
"Le travail, mois par mois",
"Ce qu’on économise une fois que c’est fait",
"Fichiers lus pour ce rapport",
),
)
# The contents in Dutch.
DUTCH = Words(
title="Inhoud",
subtitle_before="Audit van de documentproductie —",
subtitle_after=", tweede kwartaal",
appendix="Bijlage",
entries=(
"Reikwijdte van de audit",
"Wat is onderzocht",
"Wat is weggelaten, en waarom",
"Hoe de documenten nu worden gemaakt",
"De facturatierun",
"Het nachtelijke rekeningoverzicht",
"Documenten op aanvraag aan de balie",
"Wat de archiefnorm vraagt",
"Lettertypen die met het bestand meereizen",
"Kleur die op elk scherm hetzelfde betekent",
"Tekst die een schermlezer kan volgen",
"Wat een verandering zou kosten",
"Het werk, maand na maand",
"Wat het oplevert als het klaar is",
"Bestanden gelezen voor dit rapport",
),
)
# The contents in Portuguese.
PORTUGUESE = Words(
title="Índice",
subtitle_before="Auditoria da produção documental —",
subtitle_after=", segundo trimestre",
appendix="Apêndice",
entries=(
"Âmbito da auditoria",
"O que foi examinado",
"O que ficou de fora, e porquê",
"Como se produzem hoje os documentos",
"O ciclo de faturação",
"O extrato de conta noturno",
"Documentos produzidos a pedido ao balcão",
"O que pede a norma de arquivo",
"Tipos de letra que viajam com o ficheiro",
"Cor que significa o mesmo em cada ecrã",
"Texto que um leitor de ecrã consegue seguir",
"O que custaria mudar",
"O trabalho, mês a mês",
"O que se poupa depois de feito",
"Ficheiros lidos para este relatório",
),
)
# The contents in Italian.
ITALIAN = Words(
title="Indice",
subtitle_before="Verifica della produzione documentale —",
subtitle_after=", secondo trimestre",
appendix="Appendice",
entries=(
"Ambito della verifica",
"Che cosa è stato esaminato",
"Che cosa è stato escluso, e perché",
"Come si producono oggi i documenti",
"Il ciclo di fatturazione",
"L’estratto conto notturno",
"Documenti prodotti su richiesta allo sportello",
"Che cosa chiede la norma d’archivio",
"Font che viaggiano con il file",
"Colore che significa lo stesso su ogni schermo",
"Testo che una macchina di lettura può seguire",
"Quanto costerebbe cambiare",
"Il lavoro, mese per mese",
"Che cosa si risparmia una volta fatto",
"File letti per questo rapporto",
),
)
# The contents in Spanish.
SPANISH = Words(
title="Índice",
subtitle_before="Auditoría de la producción documental — ",
subtitle_after=", segundo trimestre",
appendix="Apéndice",
entries=(
"Alcance de la auditoría",
"Qué se examinó",
"Qué quedó fuera, y por qué",
"Cómo se producen hoy los documentos",
"La tanda de facturación",
"El extracto de cuentas nocturno",
"Documentos producidos a demanda en el mostrador",
"Lo que pide la norma de archivo",
"Fuentes que viajan con el archivo",
"Color que significa lo mismo en cada pantalla",
"Texto que una máquina de lectura puede seguir",
"Lo que costaría cambiar",
"El trabajo, mes a mes",
"Lo que se ahorra una vez hecho",
"Archivos leídos para este informe",
),
)
# The contents in German.
GERMAN = Words(
title="Inhalt",
subtitle_before="Prüfung der Dokumentenerstellung — ",
subtitle_after=", zweites Quartal",
appendix="Anhang",
entries=(
"Umfang der Prüfung",
"Was untersucht wurde",
"Was ausgelassen wurde, und warum",
"Wie die Dokumente heute erstellt werden",
"Der Rechnungslauf",
"Der nächtliche Kontoauszug",
"Am Schalter auf Anfrage erstellte Dokumente",
"Was die Archivnorm verlangt",
"Schriften, die mit der Datei reisen",
"Farbe, die auf jedem Bildschirm dasselbe bedeutet",
"Text, dem ein Screenreader folgen kann",
"Was eine Umstellung kosten würde",
"Die Arbeit, Monat für Monat",
"Was danach eingespart wird",
"Für diesen Bericht gelesene Dateien",
),
)
# 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}
# The width of the column the page numbers stand in, in points.
NUMBER_COLUMN = 28.0
# How much of the row an entry leaves free on its right, in points.
RIGHT_PADDING = 4.0
# The size an entry is set at, top level first.
SIZES = (10.5, 9.5)
# Where the head of the table sits, and how much room it is given below.
TABLE_TOP = 730.0
TABLE_ROOM = 660.0
# The sizes the two lines above the table are set at, in points.
TITLE_SIZE = 20.0
SUBTITLE_SIZE = 8.5
def label(words: Words, entry: Entry, index: int) -> str:
"""What one entry reads on the page: what it is filed under, then what it says."""
filed = words.appendix if entry.number is None else entry.number
return f"{filed}{GAP}{words.entries[index]}"
def subtitle(words: Words) -> str:
"""The line under the title, which names the company the report is about."""
return f"{words.subtitle_before}{COMPANY}{words.subtitle_after}"
def contents(font: hqf_pdf.FontHandle, words: Words) -> hqf_pdf.Table:
"""The contents, as one table: the entry, led by dots to its page number."""
columns = hqf_pdf.Columns(
[
hqf_pdf.ColumnWidth.fraction(1.0),
hqf_pdf.ColumnWidth.points(NUMBER_COLUMN),
],
TABLE_WIDTH,
)
table = hqf_pdf.Table(columns)
table.rule(
hqf_pdf.Rule.horizontal(0), hqf_pdf.Stroke(0.5, color=hqf_pdf.Rgb.gray(0.7))
)
for index, entry in enumerate(ENTRIES):
top = entry.depth == 0
size = SIZES[0] if top else SIZES[1]
color = hqf_pdf.Rgb(0.0, 0.0, 0.0) if top else hqf_pdf.Rgb.gray(0.25)
padding = hqf_pdf.Padding(
left=INDENT * entry.depth,
right=RIGHT_PADDING,
top=7.0 if top else 2.0,
bottom=2.0,
)
table.push(
hqf_pdf.Row(
cells=[
# The dots are drawn by the layout, in the room the entry leaves.
hqf_pdf.Cell(
font,
size,
label(words, entry, index),
color=color,
padding=padding,
leader=".",
),
hqf_pdf.Cell(
font,
size,
entry.page,
color=color,
align=hqf_pdf.Align.Right,
padding=padding,
),
]
)
)
return table
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("contents.pdf", language)).stem)
document = hqf_pdf.Document()
document.set_license(_licence.licensed())
font = document.add_font(hqf_pdf.Font.from_path(_out.font_path()))
content = hqf_pdf.Content()
title = hqf_pdf.TextFlow(font, TITLE_SIZE)
title.draw(
content,
title.break_lines(words.title, TABLE_WIDTH),
MARGIN,
790.0,
TABLE_WIDTH,
)
under_title = hqf_pdf.TextFlow(font, SUBTITLE_SIZE, color=hqf_pdf.Rgb.gray(0.4))
under_title.draw(
content,
under_title.break_lines(subtitle(words), TABLE_WIDTH),
MARGIN,
762.0,
TABLE_WIDTH,
)
contents(font, words).fit(MARGIN, TABLE_TOP, TABLE_ROOM).draw(content)
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()
|