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 | """Hangs links and marks on text the engine has already placed, from the boxes
it reports rather than measured by hand.
The Python twin of the `write_linked_text` example in Rust. A flow and a table
are drawn, and then asked where each line and each cell landed. A pale band is
painted behind a paragraph's first line, one line is underlined and made
clickable, and one table cell is boxed and made clickable — every rectangle
taken from `line_boxes` or `cell_box`, none of it measured.
The words are held in `Words`, once per language, and `HQF_PDF_LANG` picks which set is
drawn. The addresses the two links go to, the product's name and its version number
stand the same in every language.
Usage: python examples/write_linked_text.py [out.pdf] [font.ttf]
HQF_PDF_LANG=fr python examples/write_linked_text.py
"""
from __future__ import annotations
from dataclasses import dataclass
from pathlib import Path
import _language
import _licence
import _out
import hqf_pdf
# The left edge every block shares.
LEFT = 70.0
# The width every block is set to.
WIDTH = 455.0
# Where the clickable line goes, and what it is drawn with after the words that
# introduce it.
CTA_URL = "https://example.org/hqf-pdf/terms"
# Where the boxed table cell goes.
LICENCE_URL = "https://example.org/hqf-pdf/licence"
# What the table names in its first row of values, and the version under it.
PRODUCT_NAME = "hqf-pdf"
VERSION_NUMBER = "1.0"
@dataclass(frozen=True)
class Words:
"""Every word the page draws, in one language.
What is not language stays out of it: the two addresses, the product's name and its
version number read the same wherever the page is read.
"""
# The title set at the head of the page.
title: str
# The paragraph whose first line is painted behind.
intro: str
# What stands before the address, on the line that is underlined and made clickable.
cta_before: str
# The two column headings of the table.
field: str
value: str
# What each row of the table is called.
product: str
licence: str
version: str
# The value of the licence row, the cell the box makes clickable.
licence_value: str
def cta(self) -> str:
"""Return the line that is drawn, underlined and made clickable."""
return f"{self.cta_before}{CTA_URL}"
# The page in English.
ENGLISH = Words(
title="Boxes over what the engine placed",
intro=(
"The engine knows where every line and every cell lands, "
"down to the point. This paragraph was flowed into a column, and the pale band "
"was painted behind its first line from the box the engine reported, not "
"measured by hand. The same boxes make a line clickable and draw a rule exactly "
"under it."
),
cta_before="Read the terms at ",
field="Field",
value="Value",
product="Product",
licence="Licence",
version="Version",
licence_value="See the licence online",
)
# The page in French.
FRENCH = Words(
title="Des cadres posés sur ce que le moteur a placé",
intro=(
("Le moteur sait où tombe chaque ligne et chaque cellule, au point près. Ce "
"paragraphe a été coulé dans une colonne, et la bande pâle a été peinte "
"derrière sa première ligne à partir du cadre que le moteur a indiqué, sans "
"rien mesurer à la main. Les mêmes cadres rendent une ligne cliquable et "
"tracent un filet exactement dessous.")
),
cta_before="Lisez les conditions sur ",
field="Champ",
value="Valeur",
product="Produit",
licence="Licence",
version="Version",
licence_value="Voir la licence en ligne",
)
# The page in Dutch.
DUTCH = Words(
title="Kaders over wat de motor plaatste",
intro=(
"De motor weet waar elke regel en elke cel terechtkomt, tot op de punt. Deze "
"alinea is in een kolom gegoten, en de lichte band is achter de eerste regel "
"geschilderd vanuit het kader dat de motor teruggaf, niet met de hand "
"gemeten. Dezelfde kaders maken een regel klikbaar en trekken er precies een "
"lijn onder."
),
cta_before="Lees de voorwaarden op",
field="Veld",
value="Waarde",
product="Product",
licence="Licentie",
version="Versie",
licence_value="Bekijk de licentie online",
)
# The page in Portuguese.
PORTUGUESE = Words(
title="Caixas sobre o que o motor colocou",
intro=(
"O motor sabe onde cai cada linha e cada célula, ao ponto. Este parágrafo foi "
"vertido numa coluna, e a faixa clara foi pintada por trás da primeira linha "
"a partir da caixa que o motor devolveu, não medida à mão. As mesmas caixas "
"tornam uma linha clicável e traçam um filete exatamente por baixo."
),
cta_before="Leia as condições em",
field="Campo",
value="Valor",
product="Produto",
licence="Licença",
version="Versão",
licence_value="Ver a licença online",
)
# The page in Italian.
ITALIAN = Words(
title="Riquadri su ciò che il motore ha collocato",
intro=(
"Il motore sa dove cade ogni riga e ogni cella, al punto tipografico. Questo "
"paragrafo è stato versato in una colonna, e la fascia chiara è stata dipinta "
"dietro la sua prima riga dal riquadro fornito dal motore, non misurato a "
"mano. Gli stessi riquadri rendono cliccabile una riga e tracciano un filetto "
"esattamente sotto."
),
cta_before="Leggete le condizioni su",
field="Campo",
value="Valore",
product="Prodotto",
licence="Licenza",
version="Versione",
licence_value="Vedere la licenza online",
)
# The page in Spanish.
SPANISH = Words(
title="Cajas sobre lo que colocó el motor",
intro=(
"El motor sabe dónde cae cada línea y cada celda, con precisión de un punto. "
"Este párrafo se vertió en una columna, y la franja clara se pintó detrás de "
"su primera línea con la caja que dio el motor, no medida a mano. Las mismas "
"cajas hacen una línea clicable y trazan una raya justo debajo."
),
cta_before="Lea las condiciones en ",
field="Campo",
value="Valor",
product="Producto",
licence="Licencia",
version="Versión",
licence_value="Ver la licencia en línea",
)
# The page in German.
GERMAN = Words(
title="Rahmen über dem, was die Engine platziert hat",
intro=(
"Die Engine weiß auf den Punkt genau, wo jede Zeile und jede Zelle landet. "
"Dieser Absatz wurde in eine Spalte gesetzt, und das blasse Band wurde hinter "
"seine erste Zeile gemalt, nach dem Rahmen, den die Engine meldete, nicht von "
"Hand gemessen. Dieselben Rahmen machen eine Zeile anklickbar und ziehen eine "
"Linie genau darunter."
),
cta_before="Die Bedingungen stehen unter ",
field="Feld",
value="Wert",
product="Produkt",
licence="Lizenz",
version="Version",
licence_value="Die Lizenz online ansehen",
)
# 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 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("linked_text.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 title.
title = hqf_pdf.TextFlow(handle, 18.0)
title_lines = title.break_lines(words.title, WIDTH)
title.draw(content, title_lines, LEFT, 790.0, WIDTH)
# A paragraph, with the box of its first line painted behind it.
intro = hqf_pdf.TextFlow(handle, 11.0, leading=15.0)
intro_lines = intro.break_lines(words.intro, WIDTH)
band = intro.line_boxes(intro_lines, LEFT, 750.0, WIDTH)[0]
content.save_state()
content.set_fill(hqf_pdf.Rgb(1.0, 0.93, 0.6))
content.rect(band.x, band.y, band.width, band.height)
content.fill()
content.restore_state()
intro.draw(content, intro_lines, LEFT, 750.0, WIDTH)
# One line, drawn, then underlined and linked from its own box.
cta = hqf_pdf.TextFlow(handle, 12.0, color=hqf_pdf.Rgb(0.0, 0.2, 0.8))
cta_lines = cta.break_lines(words.cta(), WIDTH)
cta.draw(content, cta_lines, LEFT, 655.0, WIDTH)
link_box = cta.line_boxes(cta_lines, LEFT, 655.0, WIDTH)[0]
content.save_state()
content.set_fill(hqf_pdf.Rgb(0.0, 0.2, 0.8))
content.rect(link_box.x, link_box.y, link_box.width, 0.6)
content.fill()
content.restore_state()
# A table, then the box of one value cell drawn and linked.
columns = hqf_pdf.Columns(
[hqf_pdf.ColumnWidth.points(140.0), hqf_pdf.ColumnWidth.points(315.0)],
WIDTH,
)
pad = hqf_pdf.Padding.symmetric(6.0, 4.0)
table = hqf_pdf.Table(columns)
table.header(1)
table.rule(hqf_pdf.Rule.frame(), hqf_pdf.Stroke(0.8))
table.rule(
hqf_pdf.Rule.horizontal_other(),
hqf_pdf.Stroke(0.25, color=hqf_pdf.Rgb(0.75, 0.75, 0.75)),
)
table.push(
hqf_pdf.Row(
cells=[
hqf_pdf.Cell(handle, 10.0, words.field, padding=pad),
hqf_pdf.Cell(handle, 10.0, words.value, padding=pad),
]
)
)
table.push(
hqf_pdf.Row(
cells=[
hqf_pdf.Cell(handle, 10.0, words.product, padding=pad),
hqf_pdf.Cell(handle, 10.0, PRODUCT_NAME, padding=pad),
]
)
)
table.push(
hqf_pdf.Row(
cells=[
hqf_pdf.Cell(handle, 10.0, words.licence, padding=pad),
hqf_pdf.Cell(handle, 10.0, words.licence_value, padding=pad),
]
)
)
table.push(
hqf_pdf.Row(
cells=[
hqf_pdf.Cell(handle, 10.0, words.version, padding=pad),
hqf_pdf.Cell(handle, 10.0, VERSION_NUMBER, padding=pad),
]
)
)
placed = table.fit(LEFT, 610.0, 200.0)
placed.draw(content)
cell = placed.cell_box(2, 1)
content.save_state()
content.set_stroke(hqf_pdf.Rgb(0.8, 0.0, 0.0))
content.set_line_width(1.0)
content.rect(cell.x, cell.y, cell.width, cell.height)
content.stroke()
content.restore_state()
page = hqf_pdf.Page.a4()
page.set_content(content)
page.add_link(link_box.link_to_uri(CTA_URL))
page.add_link(cell.link_to_uri(LICENCE_URL))
document.add_page(page)
written = document.write(out)
print(f"wrote {out}: {written} bytes")
if __name__ == "__main__":
main()
|