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 | """Writes a document with a small form: labelled boxes a reader fills in, a
check box a reader ticks, a drop-down a reader picks from, a set of radio
buttons a reader chooses one of, and a field a reader signs.
The Python twin of the `write_form` example in Rust: the same document, through
the binding rather than through the library directly.
Each text field is a labelled rectangle the reader types into. The label is
drawn on the page, as any other text; the box beside it is the field, empty but
for any value it starts with. The check box under them is ticked on or off, the
drop-down under that offers a set of options, the radio buttons under that let
one of several be chosen, and the signature field under those is a place the
reader signs. Reading software draws what is typed, the tick, the chosen
option, and the dot itself. The notes box at the foot of the page is shown on the
screen and left off the paper.
The email field reports its answer under `customer_email` rather than under its
own name, which is the name the program receiving the form reads.
Every field carries a border, so the boxes a reader clicks are plain to see. The
standard draws none by default.
The labels, the options and the document's title are held in `Words`, once per
language, and `HQF_PDF_LANG` picks which set is drawn. A field's name is not among
them: it is what a filled form's data comes back under, so it stays the same
whichever language the page is drawn in.
Usage: python examples/write_form.py [out.pdf] [font.ttf]
HQF_PDF_LANG=fr python examples/write_form.py
"""
from __future__ import annotations
from dataclasses import dataclass
from pathlib import Path
import _language
import _licence
import _out
import hqf_pdf
# The text fields the form asks for: the name a filled form's data comes back under, and
# the value the field starts with.
FIELDS = [("Name", ""), ("Email", ""), ("Country", "France")]
# The names the two radio buttons send back, in the order they are drawn.
PAYMENT_VALUES = ("card", "transfer")
# The text field whose answer a submitted form reports under another name, and that
# name.
SENT_AS = ("Email", "customer_email")
@dataclass(frozen=True)
class Words:
"""Every word the form draws, in one language.
A field's name is not here: it is the name a filled form's data comes back under,
and reading software that changed language would send back something else.
"""
# The line at the head of the page.
title: str
# The label drawn beside each text field, in the order `FIELDS` names them.
labels: tuple[str, str, str]
# The line beside the check box.
subscribe: str
# The label beside the drop-down.
plan: str
# The options the drop-down offers; the second is the one it starts on.
plans: tuple[str, str]
# The label beside the radio buttons.
payment: str
# The line beside each radio button, in the order they are drawn.
payments: tuple[str, str]
# The label beside the signature field.
signature: str
# The label above the notes box, which is shown on the screen and left off the
# paper.
notes: str
# The title the document carries in its information dictionary.
document_title: str
# The page in English.
ENGLISH = Words(
title="Please fill in",
labels=("Name", "Email", "Country"),
subscribe="Subscribe to the newsletter",
plan="Plan",
plans=("Monthly", "Yearly"),
payment="Payment",
payments=("Card", "Transfer"),
signature="Signature",
notes="Notes, shown on the screen and left off the paper",
document_title="A document with a form",
)
# The page in French.
FRENCH = Words(
title="Merci de remplir ce formulaire",
labels=("Nom", "Courriel", "Pays"),
subscribe="S'abonner à la lettre d'information",
plan="Formule",
plans=("Mensuelle", "Annuelle"),
payment="Paiement",
payments=("Carte", "Virement"),
signature="Signature",
notes="Notes, affichées à l'écran et absentes du papier",
document_title="Un document avec un formulaire",
)
# 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}
# The size the labels and the field text are set at.
SIZE = 12.0
# The colour every field's border is stroked in.
BORDER = hqf_pdf.Rgb.gray(0.6)
# The width every field's border is stroked at, in points.
BORDER_WIDTH = 0.75
def form_page(handle: hqf_pdf.FontHandle, words: Words) -> hqf_pdf.Page:
"""The form page: a title, then a label and a fillable box for each field."""
content = hqf_pdf.Content()
page = hqf_pdf.Page.a4()
content.draw_text(handle, 20.0, 72.0, 760.0, words.title)
top = 700.0
for (name, value), label in zip(FIELDS, words.labels):
content.draw_text(handle, SIZE, 72.0, top, label)
page.add_field(
hqf_pdf.TextField(
name,
180.0,
top - 3.0,
240.0,
SIZE + 4.0,
handle,
SIZE,
value=value or None,
border_color=BORDER,
border_width=BORDER_WIDTH,
mapping_name=SENT_AS[1] if name == SENT_AS[0] else None,
)
)
top -= SIZE * 3.0
content.draw_text(handle, SIZE, 90.0, top, words.subscribe)
page.add_checkbox(hqf_pdf.CheckBox(
"Subscribe",
72.0,
top - 2.0,
SIZE,
SIZE,
checked=True,
border_color=BORDER,
border_width=BORDER_WIDTH,
))
top -= SIZE * 3.0
content.draw_text(handle, SIZE, 72.0, top, words.plan)
page.add_choice(
hqf_pdf.ChoiceField(
"Plan",
180.0,
top - 3.0,
240.0,
SIZE + 4.0,
handle,
SIZE,
options=list(words.plans),
selected=words.plans[1],
combo=True,
border_color=BORDER,
border_width=BORDER_WIDTH,
)
)
top -= SIZE * 3.0
content.draw_text(handle, SIZE, 72.0, top, words.payment)
page.add_radio(
hqf_pdf.RadioGroup(
"Payment",
[
(PAYMENT_VALUES[0], 180.0, top - 3.0, SIZE, SIZE),
(PAYMENT_VALUES[1], 320.0, top - 3.0, SIZE, SIZE),
],
selected=PAYMENT_VALUES[0],
border_color=BORDER,
border_width=BORDER_WIDTH,
)
)
content.draw_text(handle, SIZE, 198.0, top, words.payments[0])
content.draw_text(handle, SIZE, 338.0, top, words.payments[1])
top -= SIZE * 4.0
content.draw_text(handle, SIZE, 72.0, top, words.signature)
page.add_signature(
hqf_pdf.SignatureField(
"Signature",
180.0,
top - 12.0,
240.0,
40.0,
border_color=BORDER,
border_width=BORDER_WIDTH,
)
)
top -= SIZE * 5.0
# Neither printed nor hidden: on the screen, and off the paper.
content.draw_text(handle, SIZE, 72.0, top, words.notes)
page.add_field(
hqf_pdf.TextField(
"Notes",
72.0,
top - 42.0,
348.0,
SIZE + 20.0,
handle,
SIZE,
multiline=True,
border_color=BORDER,
border_width=BORDER_WIDTH,
flags=hqf_pdf.AnnotationFlags.none(),
)
)
page.set_content(content)
return page
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("form.pdf", language)).stem)
document = hqf_pdf.Document()
document.set_license(_licence.licensed())
document.set_info("Title", words.document_title)
handle = document.add_font(hqf_pdf.Font.from_path(_out.font_path()))
document.add_page(form_page(handle, words))
written = document.write(out)
print(f"wrote {out} ({written} bytes)")
if __name__ == "__main__":
main()
|