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
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612 | """Writes a document that opens on its own and asks not to be copied.
The Python twin of the `write_restricted` example in Rust: the same document,
through the binding rather than through the library directly.
Every string and every stream in the file is ciphertext, locked with AES under a
two-hundred-and-fifty-six-bit key. The user password is empty, so reading software opens the file without asking anybody for anything, which is what most protected files in the
world do.
What the file grants is printing, at the resolution the page was drawn at, and reading
aloud. Copying, changing and taking pages out are withheld — as requests reading software honours, not as locks: reading software that ignores them copies the page all the same, and the page says so rather than letting the file be
taken for a safe.
The document reading software is shown nothing of until a password is typed is `write_locked`,
and this page names it in words rather than by that name.
The page is written in the language `HQF_PDF_LANG` names. The password is not: it is a string typed into reading software, and a translated password opens nothing.
A second page says what the same first page weighs locked other ways: granting
everything, granting changes, copying, notes, forms and assembly, under the older cipher
earlier reading software knows, with its metadata left readable, carrying one file, with
only that file locked, and locked for the holder of one certificate rather than under a
password.
Usage: python examples/write_restricted.py [out.pdf] [font.ttf]
HQF_PDF_LANG=fr python examples/write_restricted.py
"""
from __future__ import annotations
from dataclasses import dataclass
from pathlib import Path
import _language
import _licence
import _out
import hqf_pdf
# The certificate the first page is locked for in the last of the other ways: one
# committed for the tests, holding an RSA key, so that the example runs on any machine.
CERTIFICATE = _out.ROOT / "crates" / "hqf-pdf" / "tests" / "certificates" / "recipient.der"
# How far in from the left edge of the sheet every line is set, in points.
LEFT = 72.0
# The size the heading is set at, in points.
HEADING_SIZE = 18.0
# Where the baseline of the first line of the heading sits, in points up from the foot
# of the sheet.
HEADING_BASELINE = 760.0
# How far one line of the heading sits below the one before it, in points.
HEADING_LEADING = 22.0
# The size the body is set at, in points.
BODY_SIZE = 11.0
# Where the baseline of the first line of the body sits, in points up from the foot of
# the sheet.
FIRST_BASELINE = 700.0
# How far one line of the body sits below the one before it, in points.
LEADING = 18.0
# The size the heading over each closing section is set at, in points.
SECTION_SIZE = 13.0
# Where the baseline of the heading over the other document sits, in points up from the
# foot of the sheet.
COMPANION_HEADING_BASELINE = 520.0
# Where the baseline of the first line about the other document sits, in points up from
# the foot of the sheet.
COMPANION_BASELINE = 492.0
# Where the baseline of the heading over the closing note sits, in points up from the
# foot of the sheet.
NOTE_HEADING_BASELINE = 420.0
# Where the baseline of the first line of the note sits, in points up from the foot of
# the sheet.
NOTE_BASELINE = 392.0
# The author's password, which the page states so that what the file withholds can be
# lifted. It stands outside the words: a password is typed into reading software, and a
# translated one opens nothing.
OWNER_PASSWORD = "the owner"
# The thirty-two bytes the file key is built from.
#
# They are fixed here, so that the example writes the same file on every run and one
# build can be compared with the last. A program takes them from its operating system —
# `os.urandom(32)`. A file locked under a seed anybody can read is a file anybody opens.
SEED = bytes.fromhex(
"00112233445566778899aabbccddeeff0f1e2d3c4b5a69788796a5b4c3d2e1f0"
)
@dataclass(frozen=True)
class Words:
"""Every word the page is written in, in one language.
The password is not among them: it is typed into reading software, not translated.
"""
# What the document is called, set over two lines at the head of the page and joined
# by a space in what the file says of itself.
heading: tuple[str, ...]
# The body of the page, one line to a line.
body: tuple[str, ...]
# What stands before the author's password.
password_label: str
# What stands over the lines about the other document.
companion_heading: str
# What the other document is, and how it differs from this one.
companion: tuple[str, ...]
# What stands over the closing note.
note_heading: str
# The closing note, which says where the key comes from.
note: tuple[str, ...]
# The second page's heading.
other_heading: str
# What the first page weighs locked each other way; `{bytes}` stands for the weight.
other: tuple[str, ...]
# What the file carried by the copy that carries one is.
carried: str
def title(self) -> str:
"""What the file says of itself: the two lines of the heading, in a row."""
return " ".join(self.heading)
# The page in English.
ENGLISH = Words(
heading=("A document that reads freely", "and asks not to be copied"),
body=(
"Everything written in this file is scrambled: every word,",
"every picture, under a key of two hundred and fifty-six bits.",
"It opens all the same. Nobody is asked for anything, which is",
"what most protected files in the world do.",
"What it asks is asked of the reading software: let this be",
"printed and read out loud, but do not let it be copied or",
"changed. It is a request and not a lock, and software that",
"pays no heed to it copies the page all the same.",
),
password_label="The author's password lifts the request:",
companion_heading="The other document of this pair",
companion=(
"A second document was written beside this one. That one shows",
"nothing at all until its password is typed in, while the page",
"you are reading opened on its own.",
),
note_heading="Where the key comes from",
note=(
"The thirty-two bytes the key is built from are fixed in this",
"example, so that it writes the same file on every run and one",
"build can be compared with the last. A program takes them from",
"its operating system: a seed anybody can read locks nothing.",
),
other_heading="The first page, locked other ways",
other=(
"Granting everything, it weighs {bytes} bytes.",
"Granting changes, copying, notes, forms and assembly: {bytes} bytes.",
"Under the older cipher earlier software knows: {bytes} bytes.",
"With what it says of itself left readable: {bytes} bytes.",
"Carrying one file, with only that file locked: {bytes} bytes.",
"Locked for one person's certificate instead: {bytes} bytes.",
),
carried="The file carried inside",
)
# The page in French.
FRENCH = Words(
heading=(
"Un document qui se lit librement",
"et demande à ne pas être copié",
),
body=(
"Tout ce que ce fichier contient est chiffré : chaque mot,",
"chaque image, avec une clé de deux cent cinquante-six bits.",
"Il s’ouvre quand même. On ne demande rien à personne, comme",
"le font la plupart des fichiers protégés.",
"Sa demande s’adresse au logiciel de lecture : autoriser",
"l’impression et la lecture à voix haute, mais ni la copie",
"ni la modification. C’est une demande, pas un verrou, et un logiciel",
"qui n’en tient pas compte copie la page quand même.",
),
password_label="Le mot de passe de l’auteur lève la demande :",
companion_heading="L’autre document de la paire",
companion=(
"Un second document a été créé avec celui-ci. Il n’affiche",
"rien du tout tant qu’on n’a pas saisi son mot de passe,",
"alors que la page que vous lisez s’est ouverte toute seule.",
),
note_heading="D’où vient la clé",
note=(
"Les trente-deux octets dont la clé est tirée sont figés dans cet",
"exemple, pour qu’il produise le même fichier à chaque exécution et",
"qu’on puisse le comparer au précédent. Un programme les demande à son",
"système d’exploitation : une graine lisible par tous ne protège rien.",
),
other_heading="La première page, verrouillée autrement",
other=(
"En accordant tout, elle pèse {bytes} octets.",
"En accordant modification, copie, notes, formulaires et assemblage : {bytes} octets.",
(
"Avec l’ancien chiffrement que connaissent les logiciels d’avant : "
"{bytes} octets."
),
"En laissant lisible ce qu’elle dit d’elle-même : {bytes} octets.",
"Avec un fichier joint, seul ce fichier verrouillé : {bytes} octets.",
"Pour le certificat d’une seule personne : {bytes} octets.",
),
carried="Le fichier joint",
)
# The page in Dutch.
DUTCH = Words(
heading=("Een document dat vrij te lezen is", "en niet gekopieerd wil worden"),
body=(
"Alles wat in dit bestand staat, is versleuteld: elk woord,",
"elke afbeelding, met een sleutel van tweehonderdzesenvijftig bits.",
"Het opent toch. Niemand wordt iets gevraagd, en zo",
"werken de meeste beveiligde bestanden ter wereld.",
"Wat het vraagt, vraagt het aan de leessoftware: laat dit",
"afdrukken en voorlezen, maar niet kopiëren of",
"wijzigen. Het is een verzoek en geen slot, en software die",
"er geen acht op slaat, kopieert de pagina toch.",
),
password_label="Het wachtwoord van de auteur heft het verzoek op:",
companion_heading="Het andere document van dit paar",
companion=(
"Naast dit document is een tweede geschreven. Dat toont",
"helemaal niets tot het wachtwoord is ingetypt, terwijl de pagina",
"die u leest, vanzelf openging.",
),
note_heading="Waar de sleutel vandaan komt",
note=(
"De tweeëndertig bytes waaruit de sleutel wordt opgebouwd, liggen in dit",
"voorbeeld vast, zodat het bij elke run hetzelfde bestand schrijft en een",
"build met de vorige vergeleken kan worden. Een programma haalt ze uit",
"zijn besturingssysteem: een startwaarde die iedereen kan lezen, sluit niets af.",
),
other_heading="De eerste pagina, op andere manieren vergrendeld",
other=(
"Met alles toegestaan weegt ze {bytes} bytes.",
"Met wijzigen, kopiëren, notities, formulieren en samenstellen toegestaan: {bytes} bytes.",
"Met de oudere versleuteling die eerdere software kent: {bytes} bytes.",
"Met leesbaar gelaten wat ze over zichzelf zegt: {bytes} bytes.",
"Met één bestand erin, en alleen dat bestand vergrendeld: {bytes} bytes.",
"Vergrendeld voor het certificaat van één persoon: {bytes} bytes.",
),
carried="Het ingesloten bestand",
)
# The page in Portuguese.
PORTUGUESE = Words(
heading=("Um documento que se lê livremente", "e pede para não ser copiado"),
body=(
"Tudo o que está escrito neste ficheiro está cifrado: cada palavra,",
"cada imagem, com uma chave de duzentos e cinquenta e seis bits.",
"Abre na mesma. Ninguém tem de escrever nada, que é",
"o que faz a maioria dos ficheiros protegidos do mundo.",
"O que pede, pede-o ao programa de leitura: que o deixe",
"imprimir e ler em voz alta, mas não copiar nem",
"alterar. É um pedido e não um cadeado, e um programa que",
"não lhe liga copia a página na mesma.",
),
password_label="A palavra-passe do autor levanta o pedido:",
companion_heading="O outro documento deste par",
companion=(
"Ao lado deste foi escrito um segundo documento. Esse não mostra",
"nada até se escrever a sua palavra-passe, enquanto a página",
"que está a ler abriu sozinha.",
),
note_heading="De onde vem a chave",
note=(
"Os trinta e dois bytes a partir dos quais a chave é construída são fixos neste",
"exemplo, para que escreva o mesmo ficheiro a cada execução e uma",
"compilação possa ser comparada com a anterior. Um programa tira-os do",
"seu sistema operativo: uma semente que qualquer um pode ler não fecha nada.",
),
other_heading="A primeira página, fechada de outras formas",
other=(
"Permitindo tudo, pesa {bytes} bytes.",
"Permitindo alterações, cópia, notas, formulários e montagem: {bytes} bytes.",
"Com a cifra antiga que programas anteriores conhecem: {bytes} bytes.",
"Com o que diz de si mesmo deixado legível: {bytes} bytes.",
"Com um ficheiro dentro, e só esse ficheiro fechado: {bytes} bytes.",
"Fechada para o certificado de uma só pessoa: {bytes} bytes.",
),
carried="O ficheiro levado dentro",
)
# The page in Italian.
ITALIAN = Words(
heading=("Un documento che si legge liberamente", "e chiede di non essere copiato"),
body=(
"Tutto ciò che è scritto in questo file è cifrato: ogni parola,",
"ogni immagine, con una chiave di duecentocinquantasei bit.",
"Si apre lo stesso. A nessuno si chiede nulla, come fa",
"la maggior parte dei file protetti al mondo.",
"Ciò che chiede lo chiede al programma di lettura: lasciarlo",
"stampare e leggere ad alta voce, ma non copiarlo né",
"modificarlo. È una richiesta e non un lucchetto, e un programma che",
"non ne tiene conto copia la pagina lo stesso.",
),
password_label="La password dell’autore toglie la richiesta:",
companion_heading="L’altro documento di questa coppia",
companion=(
"Accanto a questo è stato scritto un secondo documento. Quello non mostra",
"nulla finché non si digita la sua password, mentre la pagina",
"che state leggendo si è aperta da sola.",
),
note_heading="Da dove viene la chiave",
note=(
"I trentadue byte da cui si costruisce la chiave sono fissi in questo",
"esempio, così scrive lo stesso file a ogni esecuzione e una",
"compilazione si può confrontare con la precedente. Un programma li prende dal",
"suo sistema operativo: un seme che chiunque può leggere non chiude nulla.",
),
other_heading="La prima pagina, chiusa in altri modi",
other=(
"Concedendo tutto, pesa {bytes} byte.",
"Concedendo modifiche, copia, note, moduli e assemblaggio: {bytes} byte.",
"Con il vecchio cifrario che conoscono i programmi precedenti: {bytes} byte.",
"Con ciò che dice di sé lasciato leggibile: {bytes} byte.",
"Con un file dentro, e solo quel file chiuso: {bytes} byte.",
"Chiusa per il certificato di una sola persona: {bytes} byte.",
),
carried="Il file portato dentro",
)
# The page in Spanish.
SPANISH = Words(
heading=("Un documento que se lee libremente", "y pide que no se copie"),
body=(
"Todo lo escrito en este archivo está cifrado: cada palabra,",
"cada imagen, con una clave de doscientos cincuenta y seis bits.",
"Se abre igualmente. A nadie se le pide nada, que es",
"lo que hacen la mayoría de los archivos protegidos del mundo.",
"Lo que pide se lo pide al programa de lectura: que deje",
"imprimirlo y leerlo en voz alta, pero no copiarlo ni",
"cambiarlo. Es una petición y no un cerrojo, y un programa que",
"no le hace caso copia la página igualmente.",
),
password_label="La contraseña del autor anula la petición:",
companion_heading="El otro documento de esta pareja",
companion=(
"Junto a este se escribió un segundo documento. Ese no muestra",
"nada hasta que se teclea su contraseña, mientras que la página",
"que está leyendo se abrió sola.",
),
note_heading="De dónde sale la clave",
note=(
"Los treinta y dos bytes con que se construye la clave son fijos en este",
"ejemplo, para que escriba el mismo archivo en cada ejecución y una",
"compilación pueda compararse con la anterior. Un programa real los toma de",
"su sistema operativo: una semilla que cualquiera puede leer no cierra nada.",
),
other_heading="La primera página, cerrada de otras formas",
other=(
"Permitiéndolo todo, pesa {bytes} bytes.",
"Permitiendo cambios, copia, notas, formularios y montaje: {bytes} bytes.",
"Con el cifrado antiguo que conocen programas anteriores: {bytes} bytes.",
"Con lo que dice de sí mismo sin cifrar: {bytes} bytes.",
"Con un archivo dentro, y solo ese archivo cerrado: {bytes} bytes.",
"Cerrada para el certificado de una sola persona: {bytes} bytes.",
),
carried="El archivo que lleva dentro",
)
# The page in German.
GERMAN = Words(
heading=("Ein Dokument, das sich frei lesen lässt", "und bittet, nicht kopiert zu werden"),
body=(
"Alles in dieser Datei ist verschlüsselt: jedes Wort,",
"jedes Bild, unter einem Schlüssel von 256 Bit.",
"Sie öffnet sich trotzdem. Niemand wird nach etwas gefragt,",
"wie bei den meisten geschützten Dateien der Welt.",
"Die Bitte richtet sich an die Lesesoftware: dies drucken",
"und vorlesen lassen, aber nicht kopieren oder ändern",
"lassen. Es ist eine Bitte und kein Schloss, und Software,",
"die sie missachtet, kopiert die Seite trotzdem.",
),
password_label="Das Passwort des Autors hebt die Bitte auf:",
companion_heading="Das andere Dokument dieses Paares",
companion=(
"Neben diesem wurde ein zweites Dokument geschrieben. Es zeigt",
"gar nichts, bis sein Passwort eingegeben ist, während die Seite,",
"die Sie lesen, sich von selbst geöffnet hat.",
),
note_heading="Woher der Schlüssel kommt",
note=(
"Die zweiunddreißig Bytes, aus denen der Schlüssel gebaut wird,",
"sind in diesem Beispiel fest, damit es bei jedem Lauf dieselbe",
"Datei schreibt. Ein Programm holt sie von seinem Betriebssystem:",
"Ein Startwert, den jeder lesen kann, schließt nichts ab.",
),
other_heading="Die erste Seite, anders verschlossen",
other=(
"Wenn alles erlaubt ist, wiegt sie {bytes} Bytes.",
"Mit erlaubtem Ändern, Kopieren, Notizen, Formularen und Zusammenstellen: {bytes} Bytes.",
"Mit der älteren Verschlüsselung, die frühere Software kennt: {bytes} Bytes.",
"Mit lesbar gelassenen Angaben über sich selbst: {bytes} Bytes.",
"Mit einer mitgeführten Datei, und nur diese verschlossen: {bytes} Bytes.",
"Für das Zertifikat einer einzigen Person verschlossen: {bytes} Bytes.",
),
carried="Die darin mitgeführte Datei",
)
# 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 lines(words: Words) -> list[str]:
"""What the body says, the last line of which states the author's password."""
return [*words.body, f"{words.password_label} {OWNER_PASSWORD}"]
def encryption() -> hqf_pdf.Encryption:
"""What the document is locked with: the author's password, and what is allowed.
What reading software is asked to allow is printing at the resolution the page was drawn
at, and reading aloud.
"""
return (
hqf_pdf.Encryption(SEED)
.owner_password(OWNER_PASSWORD)
.permissions(
hqf_pdf.Permissions()
.printing()
.printing_at_full_resolution()
)
)
def weighed(
words: Words, face: Path, locked: hqf_pdf.Encryption, carrying: bool
) -> int:
"""What the first page weighs under `locked`, `carrying` a file where it is true."""
doc = document(words, face)
if carrying:
doc.attach(
hqf_pdf.Attachment(
"note.txt",
words.carried,
"text/plain",
hqf_pdf.Relationship.Supplement,
"2026-09-17T09:00:00+02:00",
words.carried.encode(),
)
)
doc.set_encryption(locked)
return len(doc.to_bytes())
def other_ways(words: Words, face: Path) -> list[str]:
"""The second page's lines: what the first page weighs locked each other way."""
weights = [
weighed(
words,
face,
hqf_pdf.Encryption(SEED)
.owner_password(OWNER_PASSWORD)
.permissions(hqf_pdf.Permissions.everything()),
False,
),
weighed(
words,
face,
hqf_pdf.Encryption(SEED)
.owner_password(OWNER_PASSWORD)
.permissions(
hqf_pdf.Permissions()
.modifying()
.copying()
.annotating()
.filling_forms()
.assembling()
),
False,
),
weighed(words, face, encryption().for_older_readers(), False),
weighed(words, face, encryption().metadata_in_the_clear(), False),
weighed(
words,
face,
hqf_pdf.Encryption(SEED)
.user_password(OWNER_PASSWORD)
.only_the_attachments(),
True,
),
weighed(
words,
face,
hqf_pdf.Encryption(SEED).recipient(
hqf_pdf.Recipient.from_der(CERTIFICATE.read_bytes()),
hqf_pdf.Permissions().printing().printing_at_full_resolution(),
),
False,
),
]
return [
line.replace("{bytes}", str(weight))
for line, weight in zip(words.other, weights)
]
def drawing(words: Words) -> list[tuple[str, float, float]]:
"""Every line the page draws, in the order they are drawn.
Each is what it says, the size it is set at, and where its baseline sits in points
up from the foot of the sheet.
"""
drawn = []
baseline = HEADING_BASELINE
for line in words.heading:
drawn.append((line, HEADING_SIZE, baseline))
baseline -= HEADING_LEADING
baseline = FIRST_BASELINE
for line in lines(words):
drawn.append((line, BODY_SIZE, baseline))
baseline -= LEADING
drawn.append(
(words.companion_heading, SECTION_SIZE, COMPANION_HEADING_BASELINE)
)
baseline = COMPANION_BASELINE
for line in words.companion:
drawn.append((line, BODY_SIZE, baseline))
baseline -= LEADING
drawn.append((words.note_heading, SECTION_SIZE, NOTE_HEADING_BASELINE))
baseline = NOTE_BASELINE
for line in words.note:
drawn.append((line, BODY_SIZE, baseline))
baseline -= LEADING
return drawn
def page(
handle: hqf_pdf.FontHandle, drawn: list[tuple[str, float, float]]
) -> hqf_pdf.Page:
"""The page: every line set by its own origin, each in an object of its own."""
content = hqf_pdf.Content()
for line, size, baseline in drawn:
content.draw_text(handle, size, LEFT, baseline, line)
result = hqf_pdf.Page.a4()
result.set_content(content)
return result
def composed(
words: Words, face: Path
) -> tuple[hqf_pdf.Document, hqf_pdf.FontHandle]:
"""The document and its first page, before it is locked, with the handle the page is
set through."""
result = hqf_pdf.Document()
result.set_license(_licence.licensed())
result.set_info("Title", words.title())
handle = result.add_font(hqf_pdf.Font.from_path(face))
result.add_page(page(handle, drawing(words)))
return result, handle
def document(words: Words, face: Path) -> hqf_pdf.Document:
"""The document and its first page, before it is locked."""
return composed(words, face)[0]
def document_with(words: Words, face: Path, second: list[str]) -> hqf_pdf.Document:
"""The document and its first page, before it is locked, and a second page setting
`second` under its heading."""
result, handle = composed(words, face)
drawn = [(words.other_heading, SECTION_SIZE, HEADING_BASELINE)]
baseline = FIRST_BASELINE
for line in second:
drawn.append((line, BODY_SIZE, baseline))
baseline -= LEADING
result.add_page(page(handle, drawn))
return result
def written(path: Path, doc: hqf_pdf.Document) -> None:
"""Writes `doc` to `path`, making the directory it goes in if it is not there."""
path.parent.mkdir(parents=True, exist_ok=True)
print(f"wrote {path} ({doc.write(path)} bytes)")
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("restricted.pdf", language)).stem)
face = _out.font_path()
doc = document_with(words, face, other_ways(words, face))
doc.set_encryption(encryption())
written(Path(out), doc)
if __name__ == "__main__":
main()
|