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 | """Writes a deed of assignment claiming the second part of the archival
standard, at level U.
Level U adds one promise to level B: every mark of ink drawn on the page
says which letter it stands for, so a registry searching thirty thousand
deeds finds this one, and a clerk copying a clause out of it gets the
clause. Nothing about the layout changes for it.
Whether the result really is PDF/A-2U is not for us to say. `veraPDF` says,
and `scripts/check_archival.sh` asks it.
The words of the page and of its metadata are held in `Words`, once per language,
and `HQF_PDF_LANG` picks which set the record is written in.
Usage: `python examples/write_pdfa2u.py [out.pdf] [font.ttf]`
`HQF_PDF_LANG=fr python examples/write_pdfa2u.py`
"""
from __future__ import annotations
from dataclasses import dataclass
from pathlib import Path
import _language
import _licence
import _out
import hqf_pdf
# When the record was drawn up. The library never reads the clock — a document that
# stamped itself with the time would be a different file on every build — so the caller
# says when.
ISSUED = "2026-04-17T10:15:00+02:00"
# Who the file says wrote it, and what produced it, in every language alike.
AUTHOR = "HQF Development"
PRODUCER = "hqf-pdf-server"
@dataclass(frozen=True)
class Words:
"""Every word the deed is written in, in one language."""
# What the file says it is, in its metadata.
title: str
# What the file says it is about, in its metadata.
subject: str
# The heading at the top of the page.
heading: str
# The line under the heading saying what the record is filed under.
reference: str
# What the page says of the part it claims.
body: str
# What each line of the record asks, one a line.
labels: str
# What each line of the record answers, beside its label.
answers: str
# The deed in English.
ENGLISH = Words(
title="Deed of assignment, ACME/2026/0417",
subject="A record whose every word can be searched for and copied out",
heading="Deed of assignment",
reference="Reference ACME/2026/0417, filed 17 April 2026",
body=(
"This deed claims the second part of the archival standard at the level above "
"the plain one: on top of the promise that the page keeps its looks, every "
"mark of ink drawn on it says which letter it stands for. A registry searching "
"thirty thousand deeds for a reference finds this one; a clerk copying a "
"clause out of it gets the clause, not a row of question marks. That is the "
"whole of the difference between the two levels. Nothing about the layout "
"changes, and nothing is added to the page."
),
labels="Assignor\nAssignee\nEffective",
answers="Ateliers du Nord, Lille\nACME Industries, Rouen\n1 May 2026",
)
# The deed in French.
FRENCH = Words(
title="Acte de cession, ACME/2026/0417",
subject="Un document dont chaque mot peut être recherché et recopié",
heading="Acte de cession",
reference="Référence ACME/2026/0417, enregistrée le 17 avril 2026",
body=(
("Cet acte déclare suivre la deuxième partie de la norme d’archivage, au "
"niveau situé juste au-dessus du plus simple : en plus de la promesse que la "
"page garde son apparence, chaque trace d’encre dit quelle lettre elle "
"représente. Un greffe qui cherche une référence parmi trente mille actes "
"trouve celui-ci ; un clerc qui en recopie une clause obtient la clause, et "
"non une rangée de points d’interrogation. C’est toute la différence entre "
"les deux niveaux : la mise en page ne change en rien, et rien n’est ajouté à"
" la page.")
),
labels="Cédant\nCessionnaire\nPrise d’effet",
answers="Ateliers du Nord, Lille\nACME Industries, Rouen\n1er mai 2026",
)
# The deed in German.
GERMAN = Words(
title="Abtretungsurkunde, ACME/2026/0417",
subject="Ein Dokument, in dem sich jedes Wort suchen und herauskopieren lässt",
heading="Abtretungsurkunde",
reference="Aktenzeichen ACME/2026/0417, eingereicht am 17. April 2026",
body=(
("Diese Urkunde erklärt Konformität mit dem zweiten Teil der Archivnorm, auf "
"der Stufe über der einfachen: Zusätzlich zur Zusage, dass die Seite ihr "
"Aussehen behält, gibt jede darauf gesetzte Druckspur an, für welchen "
"Buchstaben sie steht. Ein Register, das dreißigtausend Urkunden nach einem "
"Aktenzeichen durchsucht, findet diese; wer eine Klausel daraus "
"herauskopiert, erhält die Klausel und keine Reihe von Fragezeichen. Das ist "
"der ganze Unterschied zwischen den beiden Stufen. Am Layout ändert sich "
"nichts, und der Seite wird nichts hinzugefügt.")
),
labels="Zedent\nZessionar\nWirksam ab",
answers="Ateliers du Nord, Lille\nACME Industries, Rouen\n1. Mai 2026",
)
# The deed in Spanish.
SPANISH = Words(
title="Escritura de cesión, ACME/2026/0417",
subject="Un documento en el que cada palabra se puede buscar y copiar",
heading="Escritura de cesión",
reference="Referencia ACME/2026/0417, registrada el 17 de abril de 2026",
body=(
"Esta escritura declara cumplir la segunda parte de la norma de archivo, en el "
"nivel por encima del básico: además de prometer que la página conserva su "
"aspecto, cada trazo de tinta dice qué letra representa. Un registro que busca "
"una referencia entre treinta mil escrituras encuentra esta; un oficial que "
"copia una cláusula obtiene la cláusula, no una hilera de signos de "
"interrogación. Esa es toda la diferencia entre los dos niveles. La "
"maquetación no cambia en nada y no se añade nada a la página."
),
labels="Cedente\nCesionario\nFecha de efecto",
answers="Ateliers du Nord, Lille\nACME Industries, Rouen\n1 de mayo de 2026",
)
# The deed in Italian.
ITALIAN = Words(
title="Atto di cessione, ACME/2026/0417",
subject="Un documento di cui ogni parola si può cercare e copiare",
heading="Atto di cessione",
reference="Riferimento ACME/2026/0417, depositato il 17 aprile 2026",
body=(
("Quest’atto dichiara di rispettare la seconda parte della norma di "
"archiviazione, al livello sopra quello di base: oltre alla promessa che la "
"pagina conservi il suo aspetto, ogni segno d’inchiostro dice quale lettera "
"rappresenta. Un ufficio che cerca un riferimento fra trentamila atti trova "
"quest’atto; un impiegato che ne copia una clausola ottiene la clausola, non "
"una fila di punti interrogativi. Tutta la differenza fra i due livelli è "
"qui. L’impaginazione non cambia in nulla e alla pagina non si aggiunge "
"niente.")
),
labels="Cedente\nCessionario\nDecorrenza",
answers="Ateliers du Nord, Lille\nACME Industries, Rouen\n1° maggio 2026",
)
# The deed in Portuguese.
PORTUGUESE = Words(
title="Escritura de cessão, ACME/2026/0417",
subject="Um documento em que cada palavra pode ser pesquisada e copiada",
heading="Escritura de cessão",
reference="Referência ACME/2026/0417, registada a 17 de abril de 2026",
body=(
("Esta escritura declara cumprir a segunda parte da norma de arquivo, no nível"
" acima do básico: além da promessa de que a página mantém o seu aspeto, cada"
" traço de tinta diz que letra representa. Uma conservatória que procure uma "
"referência entre trinta mil escrituras encontra esta; um funcionário que "
"copia uma cláusula obtém a cláusula, e não uma fila de pontos de "
"interrogação. É essa toda a diferença entre os dois níveis. A paginação não "
"muda em nada e nada é acrescentado à página.")
),
labels="Cedente\nCessionário\nEm vigor a partir de",
answers="Ateliers du Nord, Lille\nACME Industries, Rouen\n1 de maio de 2026",
)
# The deed in Dutch.
DUTCH = Words(
title="Akte van overdracht, ACME/2026/0417",
subject="Een document waarvan elk woord doorzocht en overgenomen kan worden",
heading="Akte van overdracht",
reference="Kenmerk ACME/2026/0417, ingediend op 17 april 2026",
body=(
("Deze akte verklaart te voldoen aan het tweede deel van de archiefnorm, op "
"het niveau boven het eenvoudige: bovenop de belofte dat de pagina haar "
"uiterlijk behoudt, zegt elk inktspoor voor welke letter het staat. Een "
"register dat dertigduizend akten naar een kenmerk doorzoekt, vindt deze; een"
" griffier die er een clausule uit overneemt, krijgt de clausule en geen rij "
"vraagtekens. Dat is het hele verschil tussen de twee niveaus. Aan de opmaak "
"verandert niets, en er komt niets bij op de pagina.")
),
labels="Overdrager\nVerkrijger\nIngangsdatum",
answers="Ateliers du Nord, Lille\nACME Industries, Rouen\n1 mei 2026",
)
# 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 blocks of words the page carries, in the order it draws them: the box each goes
# in, the size it is set at, and the leading and alignment where it states them. A block
# the request states in several runs is one run of words here.
BLOCKS = (
{
"text": lambda words: words.heading,
"size": 20.0,
"left": 72.0,
"top": 782.0,
"width": 451.0,
"set": None,
},
{
"text": lambda words: words.reference,
"size": 10.5,
"left": 72.0,
"top": 750.0,
"width": 451.0,
"set": None,
},
{
"text": lambda words: words.body,
"size": 11.0,
"left": 72.0,
"top": 716.0,
"width": 451.0,
"set": (16.0, hqf_pdf.Align.Justify),
},
{
"text": lambda words: words.labels,
"size": 11.0,
"left": 72.0,
"top": 576.0,
"width": 451.0,
"set": (17.0, hqf_pdf.Align.Left),
},
{
"text": lambda words: words.answers,
"size": 11.0,
"left": 200.0,
"top": 576.0,
"width": 323.0,
"set": (17.0, hqf_pdf.Align.Left),
},
)
def sheet(handle: hqf_pdf.FontHandle, words: Words) -> hqf_pdf.Page:
"""The one page, block by block."""
content = hqf_pdf.Content()
for block in BLOCKS:
settings = {}
if block["set"] is not None:
leading, align = block["set"]
settings = {"leading": leading, "align": align}
flow = hqf_pdf.TextFlow(handle, block["size"], **settings)
lines = flow.break_lines(block["text"](words), block["width"])
# The Python side opens and closes the text object itself, where the Rust side
# leaves that to the caller.
flow.draw(content, lines, block["left"], block["top"], block["width"])
page = hqf_pdf.Page.a4()
page.set_content(content)
return page
def main() -> None:
"""Writes the file, and says where it went."""
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
# languages do not overwrite each other in `tmp/`.
out = _out.output_path(Path(_language.file_name("pdfa2u.pdf", language)).stem)
document = hqf_pdf.Document()
document.set_license(_licence.licensed())
document.set_conformance(hqf_pdf.PdfA.A2U)
document.set_metadata(
hqf_pdf.Metadata(
title=words.title,
author=AUTHOR,
subject=words.subject,
producer=PRODUCER,
created=ISSUED,
)
)
handle = document.add_font(hqf_pdf.Font.from_path(_out.font_path()))
document.add_page(sheet(handle, words))
data = document.to_bytes()
out.parent.mkdir(parents=True, exist_ok=True)
out.write_bytes(data)
print(f"wrote {out}: {len(data)} bytes")
if __name__ == "__main__":
main()
|