write_simple_encoding.py

Die Python-Datei des Beispiels „Eine Seite, deren Buchstaben je ein Byte kosten“. Dieselben drei Zeilen zweimal geschrieben — auf gewöhnliche Weise, dann mit jedem Buchstaben als einzelnem Byte —, und die Seite gibt an, was jedes der beiden ausmachte. Der kurze Weg lässt außerdem den Abstand zwischen den Wörtern einer Zeile einstellen, was der gewöhnliche nicht tut.

Python 973 Zeilen

Wofür dieses Beispiel da ist

Eine Bank, die zwei Millionen Kontoauszüge pro Nacht druckt, bezahlt jedes Byte doppelt: einmal auf der Platte, auf der der Lauf aufbewahrt wird, einmal im Netz, durch das er reist. Ein Buchstabe wird normalerweise als Bytepaar in eine Datei geschrieben, denn so viel braucht es, um jeden Buchstaben jedes Alphabets der Welt zu erreichen. Ein französischer Kontoauszug braucht etwa hundert davon, und das zweite Byte ist bei jedem einzelnen Buchstaben des Laufs eine Null.

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
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
"""Sets a page whose every letter is one byte in the stream rather than two.

The Python twin of the `write_simple_encoding` example in Rust. A font is reached by
glyph index unless it is told otherwise: two bytes a glyph, and every glyph the face
holds within reach. Reached through a simple encoding it is addressed by one-byte codes
instead, at most 255 of them, and the file states which character each code stands for.
The page weighs the same three lines both ways and prints the two figures it measured.

The single byte also gives word spacing something to reach: the operator moves the pen
at the byte 32 and at no other code, so the last two lines of the page are the same
words drawn without it and with it.

A font may also be reached through a code page named by its standard rather than
through codes fitted to the text: one line of the page is set through Windows code page
1251, and the page prints the first bytes it cost.

A second page reads the byte 0xC9 against each of the thirty encodings a code can start
from, and sets an encoding by hand: four codes settled over an empty one, each with the
name of the glyph it reaches, and a line drawn through them. It says what the file names
of that encoding, what a face is handed where it lacks a character, and which glyph
stands for the ones it cannot draw.

The terminal also says what a file holding only the samples weighs with the face's
program carried and with its description alone, which a face reached by one-byte codes
allows.

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

Usage: python examples/write_simple_encoding.py [out.pdf] [face.ttf]
       HQF_PDF_LANG=fr python examples/write_simple_encoding.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 three lines set through one-byte codes.
    shown: str
    # The three lines, drawn through the encoding.
    samples: tuple[str, str, str]
    # The label over the codes the encoding settled.
    codes: str
    # The sentence the first of those codes are put into.
    settled: str
    # The label over what the two streams weigh.
    weight: str
    # The sentence the two weights are put into.
    weighed: str
    # The label over the two lines that show word spacing.
    spacing: str
    # The line drawn twice, once with word spacing and once without.
    pushed: str
    # What those two lines show.
    spread: str
    # The label over the line set through a named code page.
    page: str
    # The sentence that line's first bytes are put into.
    named: str
    # What an encoding of one byte cannot do.
    caveat: str
    # The heading over the thirty encodings.
    bases: str
    # What the thirty encodings are shown with.
    bases_lead: str
    # What a code that stands for nothing is shown as.
    nothing: str
    # The heading over the encoding settled by hand.
    own: str
    # One code of it; `{code}`, `{point}` and `{name}` stand for the code, the
    # character's code point and its glyph's name.
    slot: str
    # What the file names of it; `{base}` and `{count}` stand for the encoding it starts
    # from and how many codes differ from it.
    named_of: str
    # What an encoding that starts from none is said to start from.
    from_nothing: str
    # What a face lacking a character is handed; `{fi}` and `{ohm}` stand for what
    # stands in for the ligature and for the Ohm sign.
    stand_ins: str
    # Which glyph a face draws for what it cannot draw; `{index}` stands for it.
    notdef: str

    def drawn(self) -> str:
        """Every character the page draws through the encoding, the samples first.

        The encoding hands out its codes in the order it meets its characters, so the
        lines the page lists the codes of are the ones that take the first codes.
        """
        return "".join(self.samples) + self.pushed


# The page in English.
ENGLISH = Words(
    title="A page addressed by one-byte codes",
    lead=(
        "Every letter on the three lines below costs one byte of this file's page "
        "stream instead of two. A font is reached by glyph index unless it is told "
        "otherwise, two bytes a glyph, which puts every glyph the face holds within "
        "reach. Reached through an encoding it is addressed by one-byte codes instead: "
        "at most 255 of them, each standing for one character, and the file states "
        "which character each of them stands for."
    ),
    shown="Set through one-byte codes",
    samples=(
        "HQF Development",
        "An invoice, a payslip, a delivery note.",
        "One byte a letter, and the file says which.",
    ),
    codes="The codes it settled",
    settled=(
        "The encoding is fitted to the words this page draws: the space keeps code 32, "
        "and every other character takes the lowest code free from 33 up, so nothing "
        "in the stream is a byte reading software has to escape. The first of them are "
        "%codes%."
    ),
    weight="What the stream weighs",
    weighed=(
        "Drawn by glyph index, the three lines above cost %identity% bytes of page "
        "stream; drawn through one-byte codes, %simple% bytes, which is %saved% less. "
        "Both figures are measured by the program that drew this page, not written in "
        "by hand."
    ),
    spacing="What the single byte 32 buys",
    pushed="Words reading software can push apart.",
    spread=(
        "The two lines above hold the same characters. The second is drawn with word "
        "spacing set, which moves the pen at the single byte 32 and at no other code. "
        "Under glyph indices no code is a single byte 32, so the operator has nothing "
        "to reach and the words never move; the space of this encoding sits at 32, so "
        "they do."
    ),
    page="A code page named by its standard",
    named=(
        "The line above is set through Windows code page 1251, named once rather than "
        "spelled out code by code: the file states the Windows encoding and names over "
        "it every code the page fills differently, so a reader finds each Cyrillic "
        "letter where the page puts it. Its first bytes are %bytes%."
    ),
    caveat=(
        "One byte addresses 255 characters at a time, so a page of several alphabets "
        "is still reached by glyph index. No ligature and no small capital is drawn "
        "through an encoding either: those are glyphs no character stands for, and a "
        "code stands for a character. A file that claims archival conformance states "
        "the Windows encoding as the one its codes are read against, which this one "
        "does."
    ),
    bases="Thirty encodings a code can be read against",
    bases_lead=(
        "What the byte 0xC9 stands for in each: the character's code point, and the "
        "character itself where this face can draw it."
    ),
    nothing="nothing",
    own="An encoding settled by hand",
    slot="code {code} stands for {point}, whose glyph is named {name}",
    named_of=(
        "The line above is drawn through those four codes alone. The encoding starts "
        "from {base}, and the file names {count} codes that differ from it."
    ),
    from_nothing="no encoding at all",
    stand_ins=(
        "A face that lacks the ligature U+FB01 is handed “{fi}” in its place, and one "
        "that lacks the Ohm sign U+2126, “{ohm}”."
    ),
    notdef=(
        "What a face draws for a character it cannot draw is its glyph {index}, which "
        "every subset keeps."
    ),
)

# The page in French.
FRENCH = Words(
    title="Une page adressée par des codes d’un octet",
    lead=(
        "Chaque lettre des trois lignes ci-dessous coûte un octet du flux de cette "
        "page, et non deux. Une police est atteinte par indice de glyphe tant qu’on ne "
        "précise rien d’autre, à deux octets par glyphe, ce qui rend accessibles tous "
        "les glyphes qu’elle contient. Atteinte par un encodage, elle est adressée par "
        "des codes d’un octet : 255 au plus, chacun représentant un caractère, et le "
        "fichier indique quel caractère chacun d’eux représente."
    ),
    shown="Composé avec des codes d’un octet",
    samples=(
        "HQF Development",
        "Une facture, une fiche de paie, un bon de livraison.",
        "Un octet par lettre, et le fichier dit laquelle.",
    ),
    codes="Les codes retenus",
    settled=(
        "L’encodage est ajusté aux mots de cette page : l’espace garde le code 32, et "
        "chaque autre caractère prend le plus petit code libre à partir de 33, si bien "
        "qu’aucun octet du flux ne demande d’échappement au logiciel de lecture. Les "
        "premiers sont %codes%."
    ),
    weight="Ce que pèse le flux",
    weighed=(
        "Écrites par indice de glyphe, les trois lignes ci-dessus coûtent %identity% "
        "octets de flux de page ; écrites par des codes d’un octet, %simple% octets, "
        "soit %saved% de moins. Les deux chiffres sont mesurés par le programme qui a "
        "créé cette page, et non saisis à la main."
    ),
    spacing="Ce que permet l’octet 32",
    pushed="Des mots qu’un logiciel de lecture peut écarter.",
    spread=(
        "Les deux lignes ci-dessus contiennent les mêmes caractères. La seconde est "
        "écrite avec un espacement des mots, qui avance la position d’écriture sur "
        "l’octet 32 et sur aucun autre code. Par indice de glyphe, aucun code ne vaut "
        "l’octet 32 seul : l’opérateur n’a rien à atteindre et les mots ne bougent "
        "jamais ; l’espace de cet encodage est en 32, donc ils bougent."
    ),
    page="Une page de code nommée par sa norme",
    named=(
        "La ligne ci-dessus est composée avec la page de code Windows 1251, nommée une "
        "fois au lieu d’être détaillée code par code : le fichier annonce l’encodage "
        "Windows et redéfinit par-dessus chaque code que la page de code remplit "
        "autrement, si bien qu’un logiciel de lecture trouve chaque lettre cyrillique "
        "là où elle la place. Ses premiers octets sont %bytes%."
    ),
    caveat=(
        "Un octet adresse 255 caractères à la fois : une page qui mêle plusieurs "
        "alphabets reste atteinte par indice de glyphe. Aucune ligature ni petite "
        "capitale ne passe non plus par un encodage : ce sont des glyphes qu’aucun "
        "caractère ne représente, et un code représente un caractère. Un fichier qui "
        "se déclare conforme à l’archivage annonce l’encodage Windows comme référence "
        "de lecture de ses codes, ce que fait celui-ci."
    ),
    bases="Trente encodages selon lesquels lire un code",
    bases_lead=(
        "Ce que l’octet 0xC9 représente dans chacun : le point de code du caractère, "
        "et le caractère lui-même là où cette police sait le dessiner."
    ),
    nothing="rien",
    own="Un encodage réglé à la main",
    slot="le code {code} représente {point}, dont le glyphe s’appelle {name}",
    named_of=(
        "La ligne ci-dessus n’est tracée qu’avec ces quatre codes. Base de l’encodage : "
        "{base} ; le fichier nomme {count} codes qui s’en écartent."
    ),
    from_nothing="aucun encodage",
    stand_ins=(
        "Une police sans la ligature U+FB01 reçoit « {fi} » à sa place, et une police "
        "sans le signe ohm U+2126, « {ohm} »."
    ),
    notdef=(
        "Pour un caractère qu’elle ne sait pas dessiner, une police dessine son glyphe "
        "{index}, que tout sous-ensemble conserve."
    ),
)

# The page in Dutch.
DUTCH = Words(
    title="Een pagina aangesproken met codes van één byte",
    lead=(
        "Elke letter op de drie regels hieronder kost één byte van de paginastroom "
        "van dit bestand in plaats van twee. Een lettertype wordt bereikt via "
        "glyphindex, tenzij anders aangegeven, twee bytes per glyph, waardoor elke "
        "glyph van de letter bereikbaar is. Via een codering wordt het in plaats "
        "daarvan aangesproken met codes van één byte: hoogstens 255, elk voor één "
        "teken, en het bestand zegt voor welk teken elke code staat."
    ),
    shown="Gezet met codes van één byte",
    samples=(
        "HQF Development",
        "Een factuur, een loonstrook, een pakbon.",
        "Eén byte per letter, en het bestand zegt welke.",
    ),
    codes="De codes die het vastlegde",
    settled=(
        "De codering is afgestemd op de woorden die deze pagina tekent: de spatie "
        "houdt code 32, en elk ander teken krijgt de laagste vrije code vanaf 33, dus "
        "niets in de stroom is een byte die leessoftware moet escapen. De eerste zijn "
        "%codes%."
    ),
    weight="Wat de stroom weegt",
    weighed=(
        "Getekend via glyphindex kosten de drie regels hierboven %identity% bytes "
        "paginastroom; via codes van één byte %simple% bytes, dat is %saved% minder. "
        "Beide getallen zijn gemeten door het programma dat deze pagina tekende, niet "
        "met de hand geschreven."
    ),
    spacing="Wat de losse byte 32 oplevert",
    pushed="Woorden die leessoftware uit elkaar kan duwen.",
    spread=(
        "De twee regels hierboven bevatten dezelfde tekens. De tweede is getekend met "
        "woordafstand ingesteld, die de pen verplaatst bij de losse byte 32 en bij "
        "geen andere code. Onder glyphindices is geen enkele code een losse byte 32, "
        "dus heeft de operator niets te bereiken en bewegen de woorden nooit; de "
        "spatie van deze codering staat op 32, dus bewegen ze wel."
    ),
    page="Een codetabel genoemd bij haar norm",
    named=(
        "De regel hierboven is gezet via Windows-codetabel 1251, één keer genoemd in "
        "plaats van code voor code uitgeschreven: het bestand noemt de "
        "Windows-codering en noemt daarbovenop elke code die de tabel anders invult, "
        "dus een lezer vindt elke Cyrillische letter waar de tabel haar zet. De "
        "eerste bytes zijn %bytes%."
    ),
    caveat=(
        "Eén byte bereikt hoogstens 255 tekens, dus een pagina met meerdere schriften "
        "wordt nog steeds via glyphindex bereikt. Ook een ligatuur of kleinkapitaal "
        "wordt niet via een codering getekend: dat zijn glyphs waar geen teken voor "
        "staat, en een code staat voor een teken. Een bestand dat archiefconformiteit "
        "claimt, noemt de Windows-codering als die waartegen zijn codes gelezen "
        "worden, en dit bestand doet dat."
    ),
    bases="Dertig coderingen waartegen een code gelezen kan worden",
    bases_lead=(
        "Waar de byte 0xC9 in elk voor staat: het codepunt van het teken, en het "
        "teken zelf waar deze letter het kan tekenen."
    ),
    nothing="niets",
    own="Een codering met de hand vastgelegd",
    slot="code {code} staat voor {point}, waarvan de glyph {name} heet",
    named_of=(
        "De regel hierboven is getekend met alleen die vier codes. De codering "
        "vertrekt van {base}, en het bestand noemt {count} codes die ervan afwijken."
    ),
    from_nothing="helemaal geen codering",
    stand_ins=(
        "Een letter zonder de ligatuur U+FB01 krijgt „{fi}” in de plaats, en een "
        "zonder het ohmteken U+2126, „{ohm}”."
    ),
    notdef=(
        "Wat een letter tekent voor een teken dat hij niet kan tekenen, is zijn glyph "
        "{index}, die elke subset behoudt."
    ),
)

# The page in Portuguese.
PORTUGUESE = Words(
    title="Uma página endereçada por códigos de um byte",
    lead=(
        "Cada letra das três linhas abaixo custa um byte do fluxo de página deste "
        "ficheiro em vez de dois. Chega-se a um tipo de letra por índice de glifo "
        "salvo indicação em contrário, dois bytes por glifo, o que põe ao alcance "
        "cada glifo do tipo. Através de uma codificação é endereçado antes por "
        "códigos de um byte: no máximo 255, cada um por um carácter, e o ficheiro "
        "indica que carácter cada um representa."
    ),
    shown="Composto por códigos de um byte",
    samples=(
        "HQF Development",
        "Uma fatura, um recibo de vencimento, uma guia de remessa.",
        "Um byte por letra, e o ficheiro diz qual.",
    ),
    codes="Os códigos que fixou",
    settled=(
        "A codificação ajusta-se às palavras que esta página desenha: o espaço mantém "
        "o código 32, e cada um dos outros caracteres toma o código livre mais baixo "
        "a partir de 33, por isso nada no fluxo é um byte que o programa de leitura "
        "tenha de escapar. Os primeiros são %codes%."
    ),
    weight="O que pesa o fluxo",
    weighed=(
        "Desenhadas por índice de glifo, as três linhas acima custam %identity% bytes "
        "de fluxo de página; por códigos de um byte, %simple% bytes, ou seja %saved% "
        "a menos. Os dois números são medidos pelo programa que criou esta página, "
        "não escritos à mão."
    ),
    spacing="O que o byte 32 sozinho permite",
    pushed="Palavras que um programa de leitura pode afastar.",
    spread=(
        "As duas linhas acima têm os mesmos caracteres. A segunda é desenhada com "
        "espaçamento entre palavras, que move a caneta no byte 32 sozinho e em nenhum "
        "outro código. Com índices de glifo nenhum código é um byte 32 sozinho, por "
        "isso o operador não tem nada a alcançar e as palavras nunca se mexem; o "
        "espaço desta codificação está no 32, por isso mexem-se."
    ),
    page="Uma página de código nomeada pela sua norma",
    named=(
        "A linha acima é composta através da página de código 1251 do Windows, "
        "nomeada uma vez em vez de detalhada código a código: o ficheiro indica a "
        "codificação Windows e nomeia por cima cada código que a página preenche de "
        "outra forma, por isso um leitor encontra cada letra cirílica onde a página a "
        "põe. Os seus primeiros bytes são %bytes%."
    ),
    caveat=(
        "Um byte endereça 255 caracteres de cada vez, por isso a uma página de vários "
        "alfabetos chega-se ainda por índice de glifo. Também nenhuma ligadura nem "
        "versalete é desenhada através de uma codificação: são glifos que nenhum "
        "carácter representa, e um código representa um carácter. Um ficheiro que "
        "declara conformidade de arquivo indica a codificação Windows como aquela "
        "contra a qual os seus códigos são lidos, e este fá-lo."
    ),
    bases="Trinta codificações contra as quais um código pode ser lido",
    bases_lead=(
        "O que o byte 0xC9 representa em cada uma: o ponto de código do carácter, e o "
        "próprio carácter onde este tipo o sabe desenhar."
    ),
    nothing="nada",
    own="Uma codificação fixada à mão",
    slot="o código {code} representa {point}, cujo glifo se chama {name}",
    named_of=(
        "A linha acima é desenhada só com esses quatro códigos. A codificação parte "
        "de {base}, e o ficheiro nomeia {count} códigos que diferem dela."
    ),
    from_nothing="nenhuma codificação",
    stand_ins=(
        "A um tipo sem a ligadura U+FB01 dá-se «{fi}» no seu lugar, e a um sem o "
        "símbolo de ohm U+2126, «{ohm}»."
    ),
    notdef=(
        "O que um tipo desenha para um carácter que não sabe desenhar é o seu glifo "
        "{index}, que todo o subconjunto conserva."
    ),
)

# The page in Italian.
ITALIAN = Words(
    title="Una pagina indirizzata con codici di un byte",
    lead=(
        "Ogni lettera delle tre righe qui sotto costa un byte del flusso di pagina di "
        "questo file anziché due. A un font si arriva per indice di glifo salvo "
        "diversa indicazione, due byte per glifo, il che mette a portata ogni glifo "
        "del carattere. Attraverso una codifica lo si indirizza invece con codici di "
        "un byte: al massimo 255, ciascuno per un carattere, e il file indica quale "
        "carattere rappresenta ciascuno."
    ),
    shown="Composto con codici di un byte",
    samples=(
        "HQF Development",
        "Una fattura, una busta paga, un documento di trasporto.",
        "Un byte per lettera, e il file dice quale.",
    ),
    codes="I codici che ha fissato",
    settled=(
        "La codifica è adattata alle parole che disegna questa pagina: lo spazio "
        "tiene il codice 32, e ogni altro carattere prende il codice libero più basso "
        "dal 33 in su, così niente nel flusso è un byte che il programma di lettura "
        "debba proteggere. I primi sono %codes%."
    ),
    weight="Quanto pesa il flusso",
    weighed=(
        "Disegnate per indice di glifo, le tre righe qui sopra costano %identity% "
        "byte di flusso di pagina; con codici di un byte, %simple% byte, cioè %saved% "
        "in meno. Entrambe le cifre sono misurate dal programma che ha disegnato "
        "questa pagina, non scritte a mano."
    ),
    spacing="Cosa compra il singolo byte 32",
    pushed="Parole che un programma di lettura può allargare.",
    spread=(
        "Le due righe qui sopra hanno gli stessi caratteri. La seconda è disegnata "
        "con la spaziatura tra parole impostata, che sposta la penna al singolo byte "
        "32 e a nessun altro codice. Con gli indici di glifo nessun codice è un "
        "singolo byte 32, così l’operatore non ha nulla da raggiungere e le parole "
        "non si muovono; lo spazio di questa codifica sta al 32, così si muovono."
    ),
    page="Una pagina di codici nominata dalla sua norma",
    named=(
        "La riga qui sopra è composta con la pagina di codici 1251 di Windows, "
        "nominata una volta anziché scritta codice per codice: il file indica la "
        "codifica Windows e nomina sopra ogni codice che la pagina riempie in modo "
        "diverso, così un programma di lettura trova ogni lettera cirillica dove la "
        "mette la pagina. I suoi primi byte sono %bytes%."
    ),
    caveat=(
        "Un byte indirizza 255 caratteri alla volta, così a una pagina di più "
        "alfabeti si arriva ancora per indice di glifo. Nemmeno una legatura o un "
        "maiuscoletto si disegna attraverso una codifica: sono glifi che nessun "
        "carattere rappresenta, e un codice rappresenta un carattere. Un file che "
        "dichiara conformità d’archivio indica la codifica Windows come quella su cui "
        "si leggono i suoi codici, e questo lo fa."
    ),
    bases="Trenta codifiche su cui si può leggere un codice",
    bases_lead=(
        "Ciò che il byte 0xC9 rappresenta in ciascuna: il punto di codice del "
        "carattere, e il carattere stesso dove questo carattere tipografico sa "
        "disegnarlo."
    ),
    nothing="nulla",
    own="Una codifica fissata a mano",
    slot="il codice {code} rappresenta {point}, il cui glifo si chiama {name}",
    named_of=(
        "La riga qui sopra è disegnata solo con quei quattro codici. La codifica "
        "parte da {base}, e il file nomina {count} codici che ne differiscono."
    ),
    from_nothing="nessuna codifica",
    stand_ins=(
        "A un carattere tipografico privo della legatura U+FB01 si dà «{fi}» al suo "
        "posto, e a uno privo del simbolo di ohm U+2126, «{ohm}»."
    ),
    notdef=(
        "Ciò che un carattere tipografico disegna per un carattere che non sa "
        "disegnare è il suo glifo {index}, che ogni sottoinsieme conserva."
    ),
)

# The page in Spanish.
SPANISH = Words(
    title="Una página direccionada con códigos de un byte",
    lead=(
        "Cada letra de las tres líneas de abajo cuesta un byte del flujo de página de "
        "este archivo en vez de dos. A una fuente se llega por índice de glifo salvo "
        "que se diga otra cosa, dos bytes por glifo, lo que da acceso a cada glifo de "
        "la fuente. A través de una codificación se la direcciona en cambio con "
        "códigos de un byte: 255 como mucho, cada uno por un carácter, y el archivo "
        "indica qué carácter representa cada uno."
    ),
    shown="Compuesto con códigos de un byte",
    samples=(
        "HQF Development",
        "Una factura, una nómina, un albarán.",
        "Un byte por letra, y el archivo dice cuál.",
    ),
    codes="Los códigos que fijó",
    settled=(
        "La codificación se ajusta a las palabras que dibuja esta página: el espacio "
        "conserva el código 32, y cada uno de los demás caracteres toma el código "
        "libre más bajo desde el 33, así que nada en el flujo es un byte que el "
        "programa de lectura tenga que escapar. Los primeros son %codes%."
    ),
    weight="Lo que pesa el flujo",
    weighed=(
        "Dibujadas por índice de glifo, las tres líneas de arriba cuestan %identity% "
        "bytes de flujo de página; con códigos de un byte, %simple% bytes, que es "
        "%saved% menos. Las dos cifras las mide el programa que dibujó esta página, "
        "no se escriben a mano."
    ),
    spacing="Lo que da el byte 32 solo",
    pushed="Palabras que un programa de lectura puede separar.",
    spread=(
        "Las dos líneas de arriba tienen los mismos caracteres. La segunda se dibuja "
        "con espaciado de palabras, que mueve la pluma en el byte 32 solo y en ningún "
        "otro código. Con índices de glifo ningún código es un byte 32 solo, así que "
        "el operador no tiene nada que alcanzar y las palabras no se mueven; el "
        "espacio de esta codificación está en el 32, así que sí se mueven."
    ),
    page="Una página de códigos nombrada por su norma",
    named=(
        "La línea de arriba se compone con la página de códigos 1251 de Windows, "
        "nombrada una vez en vez de detallada código a código: el archivo indica la "
        "codificación Windows y nombra además cada código que la página de códigos "
        "asigna de otra forma, así que un programa de lectura encuentra cada letra "
        "cirílica donde la pone la página. Sus primeros bytes son %bytes%."
    ),
    caveat=(
        "Un byte direcciona 255 caracteres a la vez, así que a una página de varios "
        "alfabetos se sigue llegando por índice de glifo. Tampoco se dibuja ninguna "
        "ligadura ni versalita a través de una codificación: son glifos que ningún "
        "carácter representa, y un código representa un carácter. Un archivo que "
        "declara conformidad de archivo indica la codificación Windows como aquella "
        "con que se leen sus códigos, y este lo hace."
    ),
    bases="Treinta codificaciones con que puede leerse un código",
    bases_lead=(
        "Lo que representa el byte 0xC9 en cada una: el punto de código del carácter, "
        "y el carácter mismo donde esta fuente puede dibujarlo."
    ),
    nothing="nada",
    own="Una codificación fijada a mano",
    slot="el código {code} representa {point}, cuyo glifo se llama {name}",
    named_of=(
        "La línea de arriba se dibuja solo con esos cuatro códigos. La codificación "
        "parte de {base}, y el archivo nombra {count} códigos que difieren de ella."
    ),
    from_nothing="ninguna codificación",
    stand_ins=(
        "A una fuente sin la ligadura U+FB01 se le da «{fi}» en su lugar, y a una sin "
        "el signo ohmio U+2126, «{ohm}»."
    ),
    notdef=(
        "Lo que dibuja una fuente para un carácter que no puede dibujar es su glifo "
        "{index}, que todo subconjunto conserva."
    ),
)

# The page in German.
GERMAN = Words(
    title="Eine Seite, über Ein-Byte-Codes angesprochen",
    lead=(
        "Jeder Buchstabe in den drei Zeilen unten kostet ein Byte im Seitenstrom "
        "dieser Datei statt zwei. Eine Schrift wird über den Glyphenindex "
        "angesprochen, sofern ihr nichts anderes gesagt wird, zwei Bytes je Glyph, "
        "womit jeder Glyph des Schnitts erreichbar ist. Über eine Kodierung erreicht, "
        "wird sie stattdessen mit Ein-Byte-Codes angesprochen: höchstens 255, jeder "
        "steht für ein Zeichen, und die Datei gibt an, für welches."
    ),
    shown="Über Ein-Byte-Codes gesetzt",
    samples=(
        "HQF Development",
        "Eine Rechnung, eine Gehaltsabrechnung, ein Lieferschein.",
        "Ein Byte je Buchstabe, und die Datei sagt, welcher.",
    ),
    codes="Die festgelegten Codes",
    settled=(
        "Die Kodierung ist auf die Wörter zugeschnitten, die diese Seite zeichnet: "
        "Das Leerzeichen behält Code 32, und jedes andere Zeichen nimmt den "
        "niedrigsten freien Code ab 33, also steht im Strom kein Byte, das eine "
        "Lesesoftware maskieren müsste. Die ersten davon sind %codes%."
    ),
    weight="Was der Strom wiegt",
    weighed=(
        "Über den Glyphenindex gezeichnet, kosten die drei Zeilen oben %identity% "
        "Bytes Seitenstrom; über Ein-Byte-Codes %simple% Bytes, also %saved% weniger. "
        "Beide Zahlen misst das Programm, das diese Seite erstellt hat, keine ist von "
        "Hand eingetragen."
    ),
    spacing="Was das einzelne Byte 32 bringt",
    pushed="Wörter, die eine Lesesoftware auseinanderschieben kann.",
    spread=(
        "Die beiden Zeilen oben enthalten dieselben Zeichen. Die zweite ist mit "
        "Wortabstand gezeichnet, der den Stift beim einzelnen Byte 32 weiterschiebt "
        "und bei keinem anderen Code. Unter Glyphenindizes ist kein Code ein "
        "einzelnes Byte 32, also findet der Operator nichts und die Wörter bewegen "
        "sich nie; das Leerzeichen dieser Kodierung liegt auf 32, also bewegen sie "
        "sich."
    ),
    page="Eine Codepage, nach ihrer Norm benannt",
    named=(
        "Die Zeile oben ist über die Windows-Codepage 1251 gesetzt, einmal benannt, "
        "statt Code für Code ausgeschrieben: Die Datei nennt die Windows-Kodierung "
        "und darüber jeden Code, den die Codepage anders belegt, also findet ein "
        "Leser jeden kyrillischen Buchstaben dort, wo die Codepage ihn hinsetzt. Ihre "
        "ersten Bytes sind %bytes%."
    ),
    caveat=(
        "Ein Byte reicht für höchstens 255 Zeichen zugleich, also wird eine Seite mit "
        "mehreren Alphabeten weiter über den Glyphenindex erreicht. Auch Ligaturen "
        "und Kapitälchen lassen sich nicht über eine Kodierung zeichnen: Das sind "
        "Glyphen, für die kein Zeichen steht, und ein Code steht für ein Zeichen. "
        "Eine Datei, die Archivkonformität beansprucht, nennt die Windows-Kodierung "
        "als die, nach der ihre Codes gelesen werden, wie diese es tut."
    ),
    bases="Dreißig Kodierungen, nach denen ein Code gelesen werden kann",
    bases_lead=(
        "Wofür das Byte 0xC9 in jeder steht: der Codepunkt des Zeichens und das "
        "Zeichen selbst, wo dieser Schnitt es zeichnen kann."
    ),
    nothing="nichts",
    own="Eine von Hand festgelegte Kodierung",
    slot="Code {code} steht für {point}, dessen Glyph {name} heißt",
    named_of=(
        "Die Zeile oben ist allein über diese vier Codes gezeichnet. Die Kodierung "
        "geht von {base} aus, und die Datei nennt {count} Codes, die davon abweichen."
    ),
    from_nothing="überhaupt keine Kodierung",
    stand_ins=(
        "Ein Schnitt ohne die Ligatur U+FB01 bekommt an ihrer Stelle „{fi}“, und "
        "einer ohne das Ohm-Zeichen U+2126 „{ohm}“."
    ),
    notdef=(
        "Was ein Schnitt für ein Zeichen zeichnet, das er nicht zeichnen kann, ist "
        "sein Glyph {index}, den jede Teilmenge behält."
    ),
)

# 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 size the three samples are drawn at.
SAMPLE = 14.0

# The size the two lines showing word spacing are drawn at.
SPACED = 12.0

# How far apart the second of those two lines pushes its words.
WORD_SPACING = 6.0

# The code page the Cyrillic line is set through.
CODE_PAGE = hqf_pdf.BaseEncoding.Windows1251

# The line set through that code page, the same in every language.
PAGED = "Счёт-фактура № 117 — оплата до 30 сентября"

# How many of that line's bytes the page prints.
PRINTED_BYTES = 8


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 stream_bytes(handle: hqf_pdf.FontHandle, words: Words) -> int:
    """What the three samples cost in a page stream, drawn through one handle.

    The two handles write the same operators around the same three lines, so what one
    weighs against the other is what the codes weigh against the indices.
    """
    content = hqf_pdf.Content()
    top = 700.0
    for sample in words.samples:
        top = block(content, handle, SAMPLE, top, sample) - 10.0
    return len(content.to_bytes())


def file_weights(
    font: hqf_pdf.Font, encoding: hqf_pdf.SimpleEncoding, words: Words
) -> tuple[int, int]:
    """What a file holding only the three samples weighs, drawn through `font` reached
    by `encoding`: with the face's program carried, then with its description alone,
    which leaves the glyphs to a face the reader finds."""
    weights = []
    for described_only in (False, True):
        doc = hqf_pdf.Document()
        doc.set_license(_licence.licensed())
        handle = doc.add_font(font.with_encoding(hqf_pdf.Encoding.simple(encoding)))
        if described_only:
            doc.carry_font_description_only(handle)
        content = hqf_pdf.Content()
        top = 700.0
        for sample in words.samples:
            top = block(content, handle, SAMPLE, top, sample) - 10.0
        page = hqf_pdf.Page.a4()
        page.set_content(content)
        doc.add_page(page)
        weights.append(len(doc.to_bytes()))
    return weights[0], weights[1]


def settled(encoding: hqf_pdf.SimpleEncoding, words: Words, count: int) -> str:
    """The first `count` characters of the samples, each beside the code that reaches
    it."""
    seen: list[str] = []
    for character in "".join(words.samples):
        if character != " " and character not in seen:
            seen.append(character)
    listed = []
    for character in seen:
        code = encoding.code(character)
        if code is not None:
            listed.append(f"{character} {code}")
        if len(listed) == count:
            break
    return " · ".join(listed)


def first_bytes(encoding: hqf_pdf.SimpleEncoding, line: str, count: int) -> str:
    """The first `count` codes `line` is written in through `encoding`, in
    hexadecimal."""
    codes = [code for code in map(encoding.code, line) if code is not None]
    return " ".join(f"{code:02X}" for code in codes[:count])


# What each of the thirty encodings is called, in the order `BaseEncoding.all()` holds
# them: names of standards, the same in every language.
BASE_NAMES = (
    "WinAnsi",
    "Standard",
    "PDFDoc",
    "ISO 8859-1",
    "ISO 8859-2",
    "ISO 8859-3",
    "ISO 8859-4",
    "ISO 8859-5",
    "ISO 8859-6",
    "ISO 8859-7",
    "ISO 8859-8",
    "ISO 8859-9",
    "ISO 8859-10",
    "ISO 8859-11",
    "ISO 8859-13",
    "ISO 8859-14",
    "ISO 8859-15",
    "ISO 8859-16",
    "Windows-874",
    "Windows-1250",
    "Windows-1251",
    "Windows-1253",
    "Windows-1254",
    "Windows-1255",
    "Windows-1256",
    "Windows-1257",
    "Windows-1258",
    "Mac Roman",
    "IBM 037",
    "IBM 500",
)

# The byte the second page reads against every encoding.
PROBED = 0xC9

# The four codes the encoding settled by hand holds, and what each stands for.
SLOTS = ((0x41, "\u0416"), (0x42, "\u03a9"), (0x43, "\u20ac"), (0x44, "\u00c9"))

# The glyph a face draws for what it cannot draw: the binding names no constant for it.
NOTDEF_INDEX = 0


def base_name(base) -> str:
    """The name `BASE_NAMES` gives `base`."""
    return BASE_NAMES[hqf_pdf.BaseEncoding.all().index(base)]


def second_page(doc, font, text, words: Words) -> hqf_pdf.Page:
    """The second page: the byte 0xC9 read against every encoding, and an encoding
    settled by hand."""
    content = hqf_pdf.Content()
    top = 780.0
    top = block(content, text, 16.0, top, words.bases) - 10.0
    top = block(content, text, 10.0, top, words.bases_lead) - 16.0

    for place, base in enumerate(hqf_pdf.BaseEncoding.all()):
        x = LEFT + 150.0 * (place // 10)
        y = top - 14.0 * (place % 10)
        character = base.character(PROBED)
        if character is None:
            read = words.nothing
        else:
            point = f"U+{ord(character):04X}"
            read = f"{point} {character}" if not font.uncovered(character) else point
        content.draw_text(text, 9.0, x, y, base_name(base))
        content.draw_text(text, 9.0, x + 72.0, y, read)
    top -= 14.0 * 10.0 + 18.0

    top = block(content, text, 12.0, top, words.own) - 10.0
    own = hqf_pdf.SimpleEncoding.empty()
    for code, character in SLOTS:
        own = own.slot(code, character)
    for code, character in SLOTS:
        name = hqf_pdf.glyph_name(character)
        read = hqf_pdf.character_named(name)
        back = words.nothing if read is None else f"U+{ord(read):04X}"
        said = (
            words.slot.replace("{code}", f"0x{code:02X}")
            .replace("{point}", back)
            .replace("{name}", name)
        )
        top = block(content, text, 9.5, top, said) - 2.0
    encoding = hqf_pdf.Encoding.simple(own)
    handed = doc.add_font(font.with_encoding(encoding))
    line = "".join(own.character(code) or "" for code, __ in SLOTS)
    top = block(content, handed, 16.0, top - 6.0, line) - 10.0
    settled_base = encoding.simple_encoding.base
    base = words.from_nothing if settled_base is None else base_name(settled_base)
    said = words.named_of.replace("{base}", base).replace(
        "{count}", str(len(own.differences))
    )
    top = block(content, text, 10.0, top, said) - 14.0

    said = words.stand_ins.replace("{fi}", hqf_pdf.stand_in("\ufb01") or "").replace(
        "{ohm}", hqf_pdf.stand_in("\u2126") or ""
    )
    top = block(content, text, 10.0, top, said) - 14.0
    said = words.notdef.replace("{index}", str(NOTDEF_INDEX))
    block(content, text, 10.0, top, said)

    page = hqf_pdf.Page.a4()
    page.set_content(content)
    return page


def build(words: Words, font: hqf_pdf.Font, encoding: hqf_pdf.SimpleEncoding) -> bytes:
    """Draws the whole page."""
    coded = font.with_encoding(hqf_pdf.Encoding.simple(encoding))
    page_encoding = hqf_pdf.SimpleEncoding(base=CODE_PAGE)
    paged = font.with_encoding(hqf_pdf.Encoding.simple(page_encoding))

    doc = hqf_pdf.Document()
    doc.set_license(_licence.licensed())
    text = doc.add_font(font)
    drawn = doc.add_font(coded)
    cyrillic = doc.add_font(paged)

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

    top = block(content, text, 12.0, top, words.shown) - 12.0
    for sample in words.samples:
        top = block(content, drawn, SAMPLE, top, sample) - 8.0

    top -= 14.0
    top = block(content, text, 12.0, top, words.codes) - 10.0
    listed = words.settled.replace("%codes%", settled(encoding, words, 8))
    top = block(content, text, 10.0, top, listed) - 20.0

    indices = stream_bytes(text, words)
    codes = stream_bytes(drawn, words)
    top = block(content, text, 12.0, top, words.weight) - 10.0
    said = (
        words.weighed.replace("%identity%", spaced(indices))
        .replace("%simple%", spaced(codes))
        .replace("%saved%", spaced(indices - codes))
    )
    top = block(content, text, 10.0, top, said) - 20.0

    top = block(content, text, 12.0, top, words.spacing) - 12.0
    top = block(content, drawn, SPACED, top, words.pushed) - 8.0
    content.set_word_spacing(WORD_SPACING)
    top = block(content, drawn, SPACED, top, words.pushed) - 18.0
    content.set_word_spacing(0.0)
    top = block(content, text, 10.0, top, words.spread) - 20.0

    top = block(content, text, 12.0, top, words.page) - 12.0
    top = block(content, cyrillic, SAMPLE, top, PAGED) - 10.0
    said = words.named.replace(
        "%bytes%", first_bytes(page_encoding, PAGED, PRINTED_BYTES)
    )
    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(second_page(doc, font, text, 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("simple_encoding.pdf", language)).stem
    )
    font = hqf_pdf.Font.from_path(_out.font_path())
    encoding = hqf_pdf.SimpleEncoding.fitting(font, words.drawn())
    carried, described = file_weights(font, encoding, words)
    drawn = build(words, font, encoding)

    Path(out).parent.mkdir(parents=True, exist_ok=True)
    Path(out).write_bytes(drawn)
    print(f"wrote {out}: {len(drawn)} bytes")
    print(
        f"the samples alone weigh {carried} bytes with the face carried, {described} "
        "with its description only"
    )


if __name__ == "__main__":
    main()