write_no_font_at_all.py

Die Python-Datei des Beispiels „Eine Seite ohne Schrift“. Vier Schriften, die jede Anzeigesoftware selbst zeichnet, auf einer Seite gesetzt, die gar keine Schrift enthält — und das Beispiel wiegt dieselben Zeilen auf beide Arten, um zu zeigen, was das spart. Dann noch einmal die vierzehn, in freien Schriften derselben Breiten, in der Datei enthalten.

Python 723 Zeilen

Wofür dieses Beispiel da ist

Eine Schrift ist jemandes Eigentum, bevor sie eine Datei ist, und eine davon in ein Dokument zu stecken, das Sie einem Kunden aushändigen, ist eine Frage, die Ihre Rechtsabteilung beantworten will. Sie kostet auch Gewicht, bei jedem einzelnen Dokument, das Sie erzeugen. Seit den Anfängen des Formats gibt es einen Weg um beides herum: vierzehn Schriften, die jede Anzeigesoftware selbst zeichnen muss, ob die Datei sie mitbringt oder nicht, und ein Dokument, das eine davon verlangt, schreibt ein paar Zeilen, die sie nennen, und sonst nichts.

Was dieses Beispiel zeigt

  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
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
"""Sets a whole page in fonts the file does not carry, and weighs what that saves.

The Python twin of the `write_no_font_at_all` example in Rust. Reading software is
required to draw fourteen fonts whether the file carries them or not, so a document that
names one of them writes no font program: a few hundred bytes of dictionary instead of
the tens of kilobytes a subset costs. The page is set in four of them, and the figures
it states are measured by the program, not written in by hand.

A second page sets a line in each of the fourteen, and under it what the face states
about itself: how far it rises and falls, how tall its capitals are and how far it
slants, whether every glyph has one width, whether it reaches its glyphs through an
encoding of its own, how wide its line comes out, and which characters of a probe it
has no glyph for.

A third page sets the same lines in the free faces drawn to the widths of the
fourteen, which a file carries where it must carry its fonts, and says how wide each
line comes out.

Both sets of words are held in `Words`, once per language, and `HQF_PDF_LANG` picks
which set is drawn.

Usage: python examples/write_no_font_at_all.py [out.pdf] [font.ttf]
       HQF_PDF_LANG=fr python examples/write_no_font_at_all.py
"""

from __future__ import annotations

from dataclasses import dataclass
from pathlib import Path

import _language
import _licence
import _out

import hqf_pdf


@dataclass(frozen=True)
class Words:
    """Every word the page draws, in one language."""

    # The page's title.
    title: str
    # What the page is about.
    lead: str
    # The label over the four samples.
    faces: str
    # What each of the four samples says.
    samples: tuple[str, str, str, str]
    # The heading over the weight.
    weight: str
    # The sentence the two measured weights are put into.
    saved: str
    # What such a font cannot do.
    caveat: str
    # The second page's heading.
    fourteen: str
    # The line each of the twelve text faces is set in.
    pangram: str
    # What a face states about itself; `{ascent}`, `{descent}`, `{cap}`, `{slant}`,
    # `{pitch}` and `{encoding}` stand for the figures and the two words below.
    facts: str
    # Whether every glyph has one width: yes, then no.
    pitch: tuple[str, str]
    # Whether the face has an encoding of its own: yes, then no.
    encoding: tuple[str, str]
    # How wide the line comes out and what of the probe the face lacks; `{width}`,
    # `{count}` and `{lacks}` stand for the width, how many characters were probed and
    # the ones lacking.
    measured: str
    # What a face that lacks nothing of the probe is said to lack.
    nothing: str
    # The character that separates the whole part of a figure from its tenths.
    decimal: str
    # The third page's heading.
    carried: str
    # What the third page shows.
    carried_lead: str
    # How wide the line comes out in the free face; `{width}` stands for it.
    carried_width: str


# The page in English.
ENGLISH = Words(
    title="A page that carries no font",
    lead=(
        "Every one of the lines below is set in a font this file does not hold. "
        "Reading software draws them from its own store, which is what the standard "
        "asks of it, so nothing of the typefaces travels with the document."
    ),
    faces="Four of the fourteen",
    samples=(
        "Helvetica, the one without serifs.",
        "Times Bold Italic, and it is already accented: déjà, ç, ù, ô, ï.",
        "Courier, where every letter is the same width.",
        "Σ √ ∫ αβγ ≥ ∞",
    ),
    weight="What it saves",
    saved=(
        "Those four lines, on a page of their own, weigh %light% bytes. The same four "
        "lines, with one font carried inside the file, weigh %heavy%. Both figures are "
        "measured by the program that drew this page, not written in by hand."
    ),
    caveat=(
        "It is not free. A file claiming an archival or accessibility standard may not "
        "do this — those standards want every font carried — and these faces draw only "
        "the letters their own encoding reaches, so a writing system outside it needs "
        "a font the file does carry."
    ),
    fourteen="The fourteen, and what each states about itself",
    pangram="Sphinx of black quartz, judge my vow",
    facts=(
        "rises {ascent}, falls {descent}, capitals {cap} pt at 10 pt; slants {slant}°; "
        "{pitch}; {encoding}"
    ),
    pitch=(
        "one width for every glyph",
        "a width for each glyph",
    ),
    encoding=(
        "an encoding of its own",
        "the encoding the text faces share",
    ),
    measured=(
        "the line runs {width} pt; of the {count} characters probed it lacks {lacks}"
    ),
    nothing="nothing",
    decimal=".",
    carried="The same fourteen, embedded in the file",
    carried_lead=(
        "A file that must embed its fonts sets each of the fourteen in a free typeface "
        "designed to the same character widths: Liberation here, though TeX Gyre would "
        "do as well. Each line below is set in that typeface, labelled with its own "
        "name, and comes out exactly as wide as on the previous page."
    ),
    carried_width="line width: {width} pt",
)

# The page in French.
FRENCH = Words(
    title="Une page qui n’incorpore aucune police",
    lead=(
        "Chacune des lignes ci-dessous est composée dans une police que ce fichier ne "
        "contient pas. Le logiciel de lecture les puise dans sa propre réserve, comme "
        "la norme le lui demande : rien de ces polices ne voyage avec le document."
    ),
    faces="Quatre des quatorze",
    samples=(
        "Helvetica, celle qui n’a pas d’empattements.",
        "Times gras italique, et déjà accentuée : ç, ù, ô, ï, œ.",
        "Courier, où chaque lettre occupe la même largeur.",
        "Σ √ ∫ αβγ ≥ ∞",
    ),
    weight="Ce que cela économise",
    saved=(
        "Ces quatre lignes, seules sur une page, pèsent %light% octets. Les mêmes "
        "quatre lignes, avec une police incorporée dans le fichier, en pèsent %heavy%. "
        "Les deux chiffres sont mesurés par le programme qui a créé cette page, pas "
        "saisis à la main."
    ),
    caveat=(
        "Cela a un prix. Un fichier qui se réclame d’une norme d’archivage ou "
        "d’accessibilité n’a pas le droit de le faire — ces normes exigent que toute "
        "police soit incorporée — et ces polices n’affichent que les lettres que "
        "couvre leur propre codage : une écriture qui en sort exige une police que le "
        "fichier incorpore vraiment."
    ),
    fourteen="Les quatorze, et ce que chacune dit d’elle-même",
    pangram="Portez ce vieux whisky au juge blond qui fume",
    facts=(
        "monte de {ascent}, descend de {descent}, capitales de {cap} pt à 10 pt ; "
        "penche de {slant}° ; {pitch} ; {encoding}"
    ),
    pitch=(
        "une même chasse pour tous les glyphes",
        "une chasse par glyphe",
    ),
    encoding=(
        "son propre encodage",
        "l’encodage commun aux polices de texte",
    ),
    measured=(
        "la ligne fait {width} pt ; caractères absents parmi les {count} testés : "
        "{lacks}"
    ),
    nothing="aucun",
    decimal=",",
    carried="Les mêmes quatorze, incorporées au fichier",
    carried_lead=(
        "Un fichier qui doit incorporer ses polices compose chacune des quatorze dans "
        "une police libre dessinée avec les mêmes chasses : ici Liberation, mais TeX "
        "Gyre conviendrait aussi. Chaque ligne ci-dessous est composée dans cette "
        "police, sous son propre nom, et a exactement la même largeur qu’à la page "
        "précédente."
    ),
    carried_width="la ligne mesure {width} pt",
)

# The page in Dutch.
DUTCH = Words(
    title="Een pagina die geen lettertype bevat",
    lead=(
        "Elk van de regels hieronder is gezet in een lettertype dat dit bestand niet "
        "bevat. Leessoftware tekent ze uit haar eigen voorraad, wat de norm van haar "
        "vraagt, dus reist er niets van de letters met het document mee."
    ),
    faces="Vier van de veertien",
    samples=(
        "Helvetica, die zonder schreven.",
        "Times Bold Italic, en al van accenten voorzien: déjà, ç, ù, ô, ï.",
        "Courier, waarin elke letter even breed is.",
        "Σ √ ∫ αβγ ≥ ∞",
    ),
    weight="Wat het bespaart",
    saved=(
        "Die vier regels, op een eigen pagina, wegen %light% bytes. Dezelfde vier "
        "regels, met één lettertype in het bestand, wegen %heavy%. Beide getallen "
        "zijn gemeten door het programma dat deze pagina tekende, niet met de hand "
        "geschreven."
    ),
    caveat=(
        "Het is niet gratis. Een bestand dat een archief- of toegankelijkheidsnorm "
        "claimt, mag dit niet, want die normen willen elk lettertype aan boord, en "
        "deze letters tekenen alleen wat hun eigen codering bereikt, dus een schrift "
        "daarbuiten heeft een lettertype nodig dat het bestand wel bevat."
    ),
    fourteen="De veertien, en wat elk over zichzelf zegt",
    pangram="Pa’s wijze lynx bezag vroom het fikse aquaduct",
    facts=(
        "stijgt {ascent}, daalt {descent}, hoofdletters {cap} pt op 10 pt; helt "
        "{slant}°; {pitch}; {encoding}"
    ),
    pitch=(
        "één breedte voor elke glyph",
        "een eigen breedte per glyph",
    ),
    encoding=(
        "een eigen codering",
        "de codering die de tekstletters delen",
    ),
    measured=(
        "de regel loopt {width} pt; van de {count} geteste tekens mist het {lacks}"
    ),
    nothing="niets",
    decimal=".",
    carried="Dezelfde veertien, ingebed in het bestand",
    carried_lead=(
        "Een bestand dat zijn lettertypen moet inbedden, zet elk van de veertien in "
        "een vrij lettertype met dezelfde tekenbreedtes: hier Liberation, maar TeX "
        "Gyre volstaat evengoed. Elke regel hieronder staat in dat lettertype, onder "
        "zijn eigen naam, en is precies even breed als op de vorige pagina."
    ),
    carried_width="de regel is {width} pt breed",
)

# The page in Portuguese.
PORTUGUESE = Words(
    title="Uma página que não leva nenhum tipo de letra",
    lead=(
        "Cada uma das linhas abaixo está composta num tipo de letra que este ficheiro "
        "não contém. O programa de leitura desenha-as a partir do seu próprio acervo, "
        "que é o que a norma lhe pede, por isso nada dos tipos viaja com o documento."
    ),
    faces="Quatro dos catorze",
    samples=(
        "Helvetica, a que não tem serifas.",
        "Times Bold Italic, e já tem acentos: déjà, ç, ù, ô, ï.",
        "Courier, onde cada letra tem a mesma largura.",
        "Σ √ ∫ αβγ ≥ ∞",
    ),
    weight="O que se poupa",
    saved=(
        "Essas quatro linhas, numa página só delas, pesam %light% bytes. As mesmas "
        "quatro linhas, com um tipo de letra dentro do ficheiro, pesam %heavy%. Os "
        "dois números são medidos pelo programa que criou esta página, não escritos à "
        "mão."
    ),
    caveat=(
        "Não é de graça. Um ficheiro que declara uma norma de arquivo ou de "
        "acessibilidade não o pode fazer, porque essas normas querem todos os tipos "
        "de letra dentro, e estes tipos só desenham as letras que a sua codificação "
        "alcança, por isso uma escrita fora dela precisa de um tipo de letra que o "
        "ficheiro leve."
    ),
    fourteen="Os catorze, e o que cada um diz de si mesmo",
    pangram="Um pequeno jabuti xereta viu dez cegonhas felizes",
    facts=(
        "sobe {ascent}, desce {descent}, maiúsculas {cap} pt a 10 pt; inclina "
        "{slant}°; {pitch}; {encoding}"
    ),
    pitch=(
        "a mesma largura para todos os glifos",
        "uma largura própria para cada glifo",
    ),
    encoding=(
        "uma codificação própria",
        "a codificação que os tipos de texto partilham",
    ),
    measured=(
        "a linha mede {width} pt; dos {count} caracteres testados faltam-lhe {lacks}"
    ),
    nothing="nada",
    decimal=".",
    carried="Os mesmos catorze, incorporados no ficheiro",
    carried_lead=(
        "Um ficheiro que tem de incorporar os seus tipos de letra compõe cada um dos "
        "catorze num tipo de letra livre desenhado com as mesmas larguras de carácter: "
        "aqui o Liberation, mas o TeX Gyre serviria igualmente. Cada linha abaixo está "
        "composta nesse tipo de letra, com o seu próprio nome, e mede exatamente o "
        "mesmo que na página anterior."
    ),
    carried_width="a linha mede {width} pt",
)

# The page in Italian.
ITALIAN = Words(
    title="Una pagina che non porta nessun font",
    lead=(
        "Ognuna delle righe qui sotto è composta in un font che questo file non "
        "contiene. Il programma di lettura le disegna con i propri caratteri, che è "
        "ciò che gli chiede la norma, così niente dei caratteri viaggia con il "
        "documento."
    ),
    faces="Quattro dei quattordici",
    samples=(
        "Helvetica, quella senza grazie.",
        "Times Bold Italic, e ha già gli accenti: déjà, ç, ù, ô, ï.",
        "Courier, dove ogni lettera ha la stessa larghezza.",
        "Σ √ ∫ αβγ ≥ ∞",
    ),
    weight="Cosa fa risparmiare",
    saved=(
        "Quelle quattro righe, su una pagina propria, pesano %light% byte. Le stesse "
        "quattro righe, con un font dentro il file, pesano %heavy%. Entrambe le cifre "
        "sono misurate dal programma che ha disegnato questa pagina, non scritte a "
        "mano."
    ),
    caveat=(
        "Non è gratis. Un file che dichiara una norma d’archivio o di accessibilità "
        "non può farlo, perché quelle norme vogliono ogni font a bordo, e questi "
        "caratteri disegnano solo le lettere che la loro codifica raggiunge, così una "
        "scrittura fuori da essa richiede un font che il file porti davvero."
    ),
    fourteen="I quattordici, e ciò che ciascuno dice di sé",
    pangram="Pranzo d’acqua fa volti sghembi",
    facts=(
        "sale {ascent}, scende {descent}, maiuscole {cap} pt a 10 pt; inclina di "
        "{slant}°; {pitch}; {encoding}"
    ),
    pitch=(
        "una larghezza per ogni glifo",
        "una larghezza per glifo",
    ),
    encoding=(
        "una codifica propria",
        "la codifica che condividono i caratteri da testo",
    ),
    measured=(
        "la riga misura {width} pt; dei {count} caratteri provati gli mancano {lacks}"
    ),
    nothing="nulla",
    decimal=".",
    carried="Gli stessi quattordici, incorporati nel file",
    carried_lead=(
        "Un file che deve incorporare i propri font compone ciascuno dei quattordici "
        "in un font libero disegnato con le stesse larghezze dei caratteri: qui "
        "Liberation, ma va bene anche TeX Gyre. Ogni riga qui sotto è composta in quel "
        "font, con il suo nome, ed è larga esattamente quanto nella pagina precedente."
    ),
    carried_width="la riga misura {width} pt",
)

# The page in Spanish.
SPANISH = Words(
    title="Una página que no lleva ninguna fuente",
    lead=(
        "Cada una de las líneas de abajo está compuesta en una fuente que este "
        "archivo no contiene. El programa de lectura las dibuja con su propio "
        "repertorio, que es lo que le pide la norma, así que nada de los tipos viaja "
        "con el documento."
    ),
    faces="Cuatro de las catorce",
    samples=(
        "Helvetica, la que no tiene serifas.",
        "Times Bold Italic, y ya lleva acentos: déjà, ç, ù, ô, ï.",
        "Courier, donde cada letra tiene el mismo ancho.",
        "Σ √ ∫ αβγ ≥ ∞",
    ),
    weight="Lo que ahorra",
    saved=(
        "Esas cuatro líneas, en una página propia, pesan %light% bytes. Las mismas "
        "cuatro líneas, con una fuente dentro del archivo, pesan %heavy%. Las dos "
        "cifras las mide el programa que dibujó esta página, no se escriben a mano."
    ),
    caveat=(
        "No es gratis. Un archivo que declara una norma de archivo o de accesibilidad "
        "no puede hacerlo —esas normas exigen todas las fuentes dentro— y estos tipos "
        "solo dibujan las letras que alcanza su propia codificación, así que una "
        "escritura fuera de ella necesita una fuente que el archivo sí lleve."
    ),
    fourteen="Las catorce, y lo que cada una dice de sí misma",
    pangram="Jovencillo emponzoñado de whisky, qué figurota exhibe",
    facts=(
        "sube {ascent}, baja {descent}, mayúsculas {cap} pt a 10 pt; se inclina "
        "{slant}°; {pitch}; {encoding}"
    ),
    pitch=(
        "un mismo ancho para cada glifo",
        "un ancho por glifo",
    ),
    encoding=(
        "una codificación propia",
        "la codificación que comparten los tipos de texto",
    ),
    measured=(
        "la línea mide {width} pt; de los {count} caracteres probados le faltan {lacks}"
    ),
    nothing="nada",
    decimal=".",
    carried="Las mismas catorce, incrustadas en el archivo",
    carried_lead=(
        "Un archivo que debe incrustar sus fuentes compone cada una de las catorce en "
        "una fuente libre diseñada con las mismas anchuras de carácter: aquí "
        "Liberation, aunque TeX Gyre serviría igual. Cada línea que sigue está "
        "compuesta en esa fuente, con su propio nombre, y mide exactamente lo mismo "
        "que en la página anterior."
    ),
    carried_width="la línea mide {width} pt",
)

# The page in German.
GERMAN = Words(
    title="Eine Seite, die keine Schrift trägt",
    lead=(
        "Jede der Zeilen unten ist in einer Schrift gesetzt, die diese Datei nicht "
        "enthält. Die Lesesoftware zeichnet sie aus ihrem eigenen Vorrat, wie es die "
        "Norm von ihr verlangt, also reist nichts von den Schriften mit dem Dokument."
    ),
    faces="Vier der vierzehn",
    samples=(
        "Helvetica, die ohne Serifen.",
        "Times Bold Italic, schon mit Akzenten: déjà, ç, ù, ô, ï.",
        "Courier, bei der jeder Buchstabe gleich breit ist.",
        "Σ √ ∫ αβγ ≥ ∞",
    ),
    weight="Was es spart",
    saved=(
        "Diese vier Zeilen wiegen auf einer eigenen Seite %light% Bytes. Dieselben "
        "vier Zeilen mit einer in der Datei getragenen Schrift wiegen %heavy%. Beide "
        "Zahlen misst das Programm, das diese Seite erstellt hat, keine ist von Hand "
        "eingetragen."
    ),
    caveat=(
        "Umsonst ist es nicht. Eine Datei, die eine Archiv- oder "
        "Barrierefreiheitsnorm beansprucht, darf das nicht — diese Normen wollen jede "
        "Schrift mitgeführt sehen —, und diese Schriften zeichnen nur die Buchstaben, "
        "die ihre eigene Kodierung erreicht, also braucht ein Schriftsystem außerhalb "
        "davon eine Schrift, die die Datei trägt."
    ),
    fourteen="Die vierzehn und was jede über sich angibt",
    pangram="Victor jagt zwölf Boxkämpfer quer über den großen Sylter Deich",
    facts=(
        "Oberlänge {ascent}, Unterlänge {descent}, Versalhöhe {cap} pt bei 10 pt; "
        "Neigung {slant}°; {pitch}; {encoding}"
    ),
    pitch=(
        "dieselbe Breite für alle Glyphen",
        "eine eigene Breite je Glyph",
    ),
    encoding=(
        "eine eigene Kodierung",
        "die Kodierung, die die Textschriften teilen",
    ),
    measured=(
        "die Zeile ist {width} pt lang; von den {count} geprüften Zeichen fehlen ihr {lacks}"
    ),
    nothing="keine",
    decimal=".",
    carried="Dieselben vierzehn, in die Datei eingebettet",
    carried_lead=(
        "Eine Datei, die ihre Schriften einbetten muss, setzt jede der vierzehn in "
        "einer freien Schrift mit denselben Zeichenbreiten: hier Liberation, TeX Gyre "
        "ginge ebenso. Jede der folgenden Zeilen ist in dieser Schrift gesetzt, mit "
        "deren eigenem Namen beschriftet und genau so breit wie auf der vorigen Seite."
    ),
    carried_width="die Zeile ist {width} pt breit",
)

# 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 left edge of everything on the page.
LEFT = 72.0

# How wide a block of text is.
WIDTH = 450.0

# The four the page is set in, in the order it sets them.
FACES = (
    hqf_pdf.StandardFont.helvetica,
    hqf_pdf.StandardFont.times_bold_italic,
    hqf_pdf.StandardFont.courier,
    hqf_pdf.StandardFont.symbol,
)


def block(
    content: hqf_pdf.Content,
    handle: hqf_pdf.FontHandle,
    size: float,
    top: float,
    text: str,
) -> float:
    """Sets a block of words at `top`, and hands back the ordinate it ends at."""
    flow = hqf_pdf.TextFlow(handle, size)
    lines = flow.break_lines(text, WIDTH)
    # The binding opens the text object itself, so opening another here would write a
    # pair of operators its twin in Rust does not.
    flow.draw(content, lines, LEFT, top, WIDTH)
    return top - flow.height(lines)


def spaced(count: int) -> str:
    """A count with a no-break space between its thousands."""
    digits = str(count)
    out = []
    for index, digit in enumerate(digits):
        if index > 0 and (len(digits) - index) % 3 == 0:
            out.append(" ")
        out.append(digit)
    return "".join(out)


def weighed(words: Words, carried: Path | None) -> int:
    """What a page holding nothing but the four samples weighs.

    This is the measurement the page reports. It is made on a page of its own so that
    the prose around it — and the figures themselves — weigh nothing in it.
    """
    doc = hqf_pdf.Document()
    doc.set_license(_licence.licensed())
    if carried is None:
        handles = [doc.add_standard_font(face()) for face in FACES]
    else:
        one = doc.add_font(hqf_pdf.Font.from_path(carried))
        handles = [one] * len(words.samples)

    content = hqf_pdf.Content()
    top = 760.0
    for handle, sample in zip(handles, words.samples):
        top = block(content, handle, 13.0, top, sample) - 10.0

    page = hqf_pdf.Page.a4()
    page.set_content(content)
    doc.add_page(page)
    return len(doc.to_bytes())


# The fourteen, in the order the library holds their metrics.
FOURTEEN = (
    hqf_pdf.StandardFont.courier,
    hqf_pdf.StandardFont.courier_bold,
    hqf_pdf.StandardFont.courier_bold_oblique,
    hqf_pdf.StandardFont.courier_oblique,
    hqf_pdf.StandardFont.helvetica,
    hqf_pdf.StandardFont.helvetica_bold,
    hqf_pdf.StandardFont.helvetica_bold_oblique,
    hqf_pdf.StandardFont.helvetica_oblique,
    hqf_pdf.StandardFont.times_roman,
    hqf_pdf.StandardFont.times_bold,
    hqf_pdf.StandardFont.times_bold_italic,
    hqf_pdf.StandardFont.times_italic,
    hqf_pdf.StandardFont.symbol,
    hqf_pdf.StandardFont.zapf_dingbats,
)

# The probe each face is asked which characters it lacks: an accented Latin letter, a
# currency sign, a Greek capital and a check mark.
PROBE = "\u00e9\u20ac\u03a9\u2713"

# The line the two faces whose glyphs are not letters are set in, by the name the font
# dictionary calls them.
SIGNS = {
    "Symbol": "\u03a3 \u221a \u222b \u03b1\u03b2\u03b3 \u2265 \u221e",
    "ZapfDingbats": "\u2713 \u2708 \u260e \u2605 \u2702 \u2764",
}


def figure(value: float, words: Words) -> str:
    """`value` to one decimal, its decimal separator the language's own."""
    return f"{value:.1f}".replace(".", words.decimal)


def stated(face, line: str, words: Words) -> list[str]:
    """What `face` states about itself and how its `line` measures, said in `words`."""
    lacking = face.uncovered(PROBE)
    if lacking:
        lacks = ", ".join(f"U+{ord(character):04X}" for character in lacking)
    else:
        lacks = words.nothing
    return [
        words.facts.replace("{ascent}", figure(face.ascent(10.0), words))
        .replace("{descent}", figure(face.descent(10.0), words))
        .replace("{cap}", figure(face.cap_height(10.0), words))
        .replace("{slant}", figure(face.italic_angle, words))
        .replace("{pitch}", words.pitch[0 if face.fixed_pitch else 1])
        .replace("{encoding}", words.encoding[0 if face.font_specific else 1]),
        words.measured.replace("{width}", figure(face.measure(line, 10.0), words))
        .replace("{count}", str(len(PROBE)))
        .replace("{lacks}", lacks),
    ]


def fourteen(doc, words: Words) -> hqf_pdf.Page:
    """The second page: a line in each of the fourteen faces, its name beside it and
    what it states under it."""
    label = doc.add_standard_font(hqf_pdf.StandardFont.helvetica())
    content = hqf_pdf.Content()
    top = block(content, label, 16.0, 780.0, words.fourteen) - 12.0
    note = hqf_pdf.TextFlow(label, 7.0, leading=8.5, color=hqf_pdf.Rgb.gray(0.4))
    for make in FOURTEEN:
        face = make()
        handle = doc.add_standard_font(face)
        name = face.postscript_name
        line = SIGNS.get(name, words.pangram)
        content.set_fill(hqf_pdf.Rgb.gray(0.0))
        content.draw_text(label, 8.0, LEFT, top, name)
        content.draw_text(handle, 10.0, LEFT + 150.0, top, line)
        y = top - 4.0
        for said in stated(face, line, words):
            lines = note.break_lines(said, WIDTH - 150.0)
            note.draw(content, lines, LEFT + 150.0, y, WIDTH - 150.0)
            y -= note.height(lines)
        top = y - 10.0
    page = hqf_pdf.Page.a4()
    page.set_content(content)
    return page


def carried(doc, words: Words) -> hqf_pdf.Page:
    """The third page: the line of each of the fourteen set in the free face drawn to
    its widths, the face's own name beside it and the width under it."""
    label = doc.add_standard_font(hqf_pdf.StandardFont.helvetica())
    content = hqf_pdf.Content()
    top = block(content, label, 16.0, 780.0, words.carried) - 6.0
    top = block(content, label, 9.0, top, words.carried_lead) - 14.0
    for make in FOURTEEN:
        face = make()
        free = face.free_face(hqf_pdf.FreeFamily.Liberation)
        name = free.postscript_name
        handle = doc.add_font(free)
        line = SIGNS.get(face.postscript_name, words.pangram)
        content.set_fill(hqf_pdf.Rgb.gray(0.0))
        content.draw_text(label, 8.0, LEFT, top, name)
        content.draw_text(handle, 10.0, LEFT + 150.0, top, line)
        width = words.carried_width.replace(
            "{width}", figure(handle.measure(line, 10.0), words)
        )
        content.set_fill(hqf_pdf.Rgb.gray(0.4))
        content.draw_text(label, 7.0, LEFT + 150.0, top - 10.0, width)
        top -= 30.0
    page = hqf_pdf.Page.a4()
    page.set_content(content)
    return page


def build(words: Words, light: int, heavy: int) -> bytes:
    """Draws the whole page, stating the two weights it measured."""
    doc = hqf_pdf.Document()
    doc.set_license(_licence.licensed())
    handles = [doc.add_standard_font(face()) for face in FACES]
    text = handles[0]

    content = hqf_pdf.Content()
    top = 760.0
    top = block(content, text, 18.0, top, words.title) - 14.0
    top = block(content, text, 10.0, top, words.lead) - 24.0

    top = block(content, text, 12.0, top, words.faces) - 12.0
    for handle, sample in zip(handles, words.samples):
        top = block(content, handle, 13.0, top, sample) - 10.0

    top -= 14.0
    top = block(content, text, 12.0, top, words.weight) - 10.0
    said = words.saved.replace("%light%", spaced(light)).replace(
        "%heavy%", spaced(heavy)
    )
    top = block(content, text, 10.0, top, said) - 20.0

    content.set_fill(hqf_pdf.Rgb.gray(0.35))
    block(content, text, 9.0, top, words.caveat)

    page = hqf_pdf.Page.a4()
    page.set_content(content)
    doc.add_page(page)
    doc.add_page(fourteen(doc, words))
    doc.add_page(carried(doc, words))
    return doc.to_bytes()


def main() -> None:
    """Writes the page."""
    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("no_font_at_all.pdf", language)).stem
    )

    light = weighed(words, None)
    heavy = weighed(words, _out.font_path())
    data = build(words, light, heavy)

    Path(out).parent.mkdir(parents=True, exist_ok=True)
    Path(out).write_bytes(data)
    print(f"wrote {out}: {len(data)} bytes")


if __name__ == "__main__":
    main()