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 | """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 de la boîte que le moteur a "
"rendue, sans aucune mesure à la main. Les mêmes boîtes 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",
)
# 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 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, hqf_pdf.Rgb(0.75, 0.75, 0.75)),
)
table.push(
hqf_pdf.Row(
[
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(
[
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(
[
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(
[
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()
|