write_font_facts.rs

Le fichier Rust de l’exemple « Ce qu’un fichier de police dit de lui-même ». Trois fichiers de police lus et présentés sur une page : chaque police écrite dans ses propres lettres, avec son nom, le nombre de lettres qu’elle contient, ses hauteurs, son soulignement et ce que son auteur permet d’en faire dans un document.

Rust 942 lignes

À quoi sert cet exemple

Avant d’écrire du texte dans une police, un programme a souvent besoin de savoir ce que seul le fichier de la police peut lui dire : la hauteur de ses capitales, la place de son soulignement, le nombre de lettres qu’il contient, et si son auteur autorise à l’incorporer dans un document. Ici, trois fichiers de police sont lus, et chaque police est écrite une fois dans ses propres lettres, avec en dessous tout ce que son fichier déclare sur elle. Aucun de ces chiffres n’a été tapé à la main : chacun est lu dans le fichier.

Ce que montre cet exemple

  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
//! Writes what three font files state about themselves: `DejaVu` Sans and
//! Spectral, whose outlines are quadratic, and IBM Plex Sans, whose outlines
//! are cubic.
//!
//! Each face is read from its file by the family the file names, and set once
//! in its own letters; under it, lines say what the file states: the names it
//! goes by and how its outlines are stored, how many units make its em and how
//! many glyphs it holds, its traits, where it rises and falls, where its
//! underline goes, what its own flag allows of embedding it, which code pages
//! it claims, and how one glyph and one line measure across and down the page.
//! Not one of those figures is written by hand.
//!
//! The last lines add one file twice, once to set across and once to set down
//! the page, and say how this document treats a font whose own flag forbids
//! embedding it.
//!
//! The words are held in `Words`, once per language, and `HQF_PDF_LANG` picks
//! which set the page is written in.
//!
//! Usage: `cargo run --example write_font_facts -- tmp/font_facts.pdf`
//!        `HQF_PDF_LANG=fr cargo run --example write_font_facts --
//! tmp/font_facts_fr.pdf`

use std::env;
use std::fs;
use std::path::{Path, PathBuf};

use hqf_pdf::content::Content;
use hqf_pdf::cos::Name;
use hqf_pdf::font::{CodePage, OutlineFormat, WritingMode};
use hqf_pdf::{Document, Embedding, Font, FontHandle, FontPermissionPolicy, Page, Rgb, TextFlow};

#[path = "shared/out.rs"]
mod out;

#[path = "shared/licence.rs"]
mod licence;

#[path = "shared/language.rs"]
mod language;

#[path = "shared/failure.rs"]
mod failure;

use language::Language;

/// Where the fonts the page reads are committed.
fn fonts() -> PathBuf {
    Path::new(env!("CARGO_MANIFEST_DIR"))
        .join("tests")
        .join("fonts")
}

/// The three files the page reads, in the order it reads them.
const FILES: [&str; 3] = [
    "DejaVuSans.ttf",
    "IBMPlexSans-Regular.otf",
    "Spectral-Regular.ttf",
];

/// The line each face is measured on, the same in every language.
const SAMPLE: &str = "Façade 2026";

/// How far in from the left edge of the sheet everything starts, in points.
const LEFT: f64 = 56.0;

/// How wide the lines run, in points.
const WIDTH: f64 = 483.0;

/// The size the facts are set at, in points.
const FACTS: f64 = 8.5;

/// Every word the page says, in one language.
#[derive(Debug)]
struct Words {
    /// The heading, and what the file says of itself.
    title: &'static str,
    /// What the page says under its heading.
    lead: &'static str,
    /// What a face is called and how it is stored; `{postscript}`, `{family}`,
    /// `{outlines}`, `{units}` and `{glyphs}` stand for them.
    identity: &'static str,
    /// How the outlines are stored: quadratic, then cubic.
    outlines: [&'static str; 2],
    /// The traits a face has; `{traits}` stands for them.
    traits: &'static str,
    /// The five traits: serifs, one width, bold, italic, a symbol encoding.
    trait_names: [&'static str; 5],
    /// What a face with none of the five is said to have, and what a symbol
    /// code reaching nothing is said to reach.
    none: &'static str,
    /// What a figure the file does not state, or a glyph it does not name, is
    /// said to be.
    unknown: &'static str,
    /// Where a face rises and falls at 10 points; `{ascent}`, `{descent}`,
    /// `{cap}`, `{x}`, `{gap}`, `{slant}` and `{bbox}` stand for them.
    heights: &'static str,
    /// Where the underline goes; `{position}`, `{thickness}` and `{stated}`
    /// stand for them.
    underline: &'static str,
    /// What the file states of its underline; `{position}` and `{thickness}`
    /// stand for the two figures in units.
    stated: &'static str,
    /// What a file stating nothing of its underline is said to state.
    unstated: &'static str,
    /// What the font's flag allows; `{embedding}`, `{subsetting}` and
    /// `{bitmap}` stand for them.
    permissions: &'static str,
    /// The four things the flag may say of embedding.
    embeddings: [&'static str; 4],
    /// Whether the font may be cut down: yes, then no.
    subsetting: [&'static str; 2],
    /// Whether only its bitmaps may be carried: no, then yes.
    bitmap: [&'static str; 2],
    /// The code pages a face claims; `{pages}` stands for them.
    code_pages: &'static str,
    /// How one glyph and the sample measure; `{across}`, `{down}`, `{drawn}`,
    /// `{sample}`, `{count}`, `{width}` and `{height}` stand for them.
    measures: &'static str,
    /// Whether the face states how its glyphs advance down the page: yes, then
    /// no.
    vertical: [&'static str; 2],
    /// What reading code 0x41 as a symbol font reaches; `{symbol}` stands for
    /// it.
    symbol: &'static str,
    /// The variation selectors a face names sequences for, and the glyph a zero
    /// followed by the first of them draws; `{count}` and `{variant}` stand for
    /// them.
    selectors: &'static str,
    /// The last lines: one file added twice; `{first}` and `{second}` stand for
    /// the two ways.
    twice: &'static str,
    /// The two ways a font sets its text: across, then down the page.
    writings: [&'static str; 2],
    /// What this document does with a font whose flag forbids embedding it;
    /// `{policy}` stands for it.
    policy: &'static str,
    /// The three things it may do: carry it and name it, leave it out, refuse
    /// the document.
    policies: [&'static str; 3],
    /// The character separating the whole part of a figure from its tenths.
    decimal: char,
}

impl Words {
    /// The words the page is written in, in `language`.
    fn of(language: Language) -> &'static Self {
        language::pick(&WORDS, language)
    }
}

/// The page in English.
const ENGLISH: Words = Words {
    title: "What a font file states about itself",
    lead: "Three faces read from their files. Each is set once in its own letters, and \
           under it every figure is read out of the file, none written by hand.",
    identity: "{postscript}, of the family {family}: {outlines}; {units} units to the em; \
               {glyphs} glyphs.",
    outlines: [
        "quadratic outlines, a TrueType font",
        "cubic outlines, an OpenType font with CFF outlines",
    ],
    traits: "Traits: {traits}.",
    trait_names: [
        "serifs",
        "one width for every glyph",
        "bold",
        "italic",
        "a symbol encoding",
    ],
    none: "none",
    unknown: "not stated",
    heights: "At 10 pt it rises {ascent} and falls {descent}; capitals: {cap}; lower \
              case: {x}; it asks {gap} between lines; it slants {slant}°; every glyph \
              stands within [{bbox}] units.",
    underline: "An underline at 10 pt sits {position} pt from the baseline, {thickness} pt \
                thick; the file states {stated}.",
    stated: "{position} and {thickness} units",
    unstated: "nothing of it",
    permissions: "Its own flag: {embedding}; {subsetting}; {bitmap}.",
    embeddings: [
        "it may be embedded and installed",
        "it must not be embedded without its owner's leave",
        "it may be embedded to view and print the document",
        "it may be embedded and the document edited",
    ],
    subsetting: ["it may be cut down", "it must be carried whole"],
    bitmap: [
        "its outlines may be carried",
        "only its bitmaps may be carried",
    ],
    code_pages: "Code pages it claims: {pages}.",
    measures: "W advances {across} units across and {down} down the page; the glyph \
               named Eacute: {drawn}; \u{201C}{sample}\u{201D} takes {count} glyphs, runs \
               {width} pt at 10 pt and {height} pt down the page.",
    vertical: [
        "it states how far each glyph advances down the page",
        "it states nothing of setting down the page",
    ],
    symbol: "Read as a symbol font, code 0x41: {symbol}.",
    selectors: "Variation selectors it names sequences for: {count}; a zero followed by \
                U+FE00: {variant}.",
    twice: "The first file is added twice, {first} and {second}.",
    writings: ["to set across the page", "to set down the page"],
    policy: "This document is told to {policy} a font whose own flag forbids embedding it.",
    policies: [
        "carry, and name in a warning,",
        "leave out",
        "refuse the document over",
    ],
    decimal: '.',
};

/// The page in French.
const FRENCH: Words = Words {
    title: "Ce qu’un fichier de police dit de lui-même",
    lead: "Trois polices lues dans leurs fichiers. Chacune s’affiche une fois avec ses \
propres caractères ; en dessous, chaque valeur est lue dans le fichier, aucune n’est \
saisie à la main.",
    identity: "{postscript}, de la famille {family} : {outlines} ; {units} unités par \
cadratin ; {glyphs} glyphes.",
    outlines: [
        "contours quadratiques, une police TrueType",
        "contours cubiques, une police OpenType à contours CFF",
    ],
    traits: "Traits : {traits}.",
    trait_names: [
        "empattements",
        "une même chasse pour tous les glyphes",
        "grasse",
        "italique",
        "un encodage de symboles",
    ],
    none: "aucun",
    unknown: "non indiqué",
    heights: "À 10 pt elle monte de {ascent} et descend de {descent} ; capitales : \
{cap} ; bas de casse : {x} ; elle demande {gap} entre les lignes ; elle \
penche de {slant}° ; tout glyphe tient dans [{bbox}] unités.",
    underline: "À 10 pt, le soulignement se place à {position} pt de la ligne de base, \
épais de {thickness} pt ; ce qu’en dit le fichier : {stated}.",
    stated: "{position} et {thickness} unités",
    unstated: "rien",
    permissions: "Son indicateur de droits : {embedding} ; {subsetting} ; {bitmap}.",
    embeddings: [
        "elle peut être incorporée et installée",
        "elle ne doit pas être incorporée sans l’accord de son propriétaire",
        "elle peut être incorporée pour afficher et imprimer le document",
        "elle peut être incorporée et le document modifié",
    ],
    subsetting: [
        "elle peut être incorporée en sous-ensemble",
        "elle doit être incorporée entière",
    ],
    bitmap: [
        "ses contours peuvent être incorporés",
        "seules ses images matricielles peuvent être incorporées",
    ],
    code_pages: "Pages de code qu’elle déclare : {pages}.",
    measures: "W avance de {across} unités en largeur et de {down} en hauteur ; le glyphe \
nommé Eacute : {drawn} ; « {sample} » prend {count} glyphes, mesure \
{width} pt à 10 pt et {height} pt de haut en bas.",
    vertical: [
        "elle indique de combien chaque glyphe avance de haut en bas",
        "elle ne dit rien de la composition de haut en bas",
    ],
    symbol: "Lue comme une police de symboles, code 0x41 : {symbol}.",
    selectors: "Sélecteurs de variante pour lesquels elle nomme des séquences : {count} ; \
un zéro suivi de U+FE00 : {variant}.",
    twice: "Le premier fichier est ajouté deux fois, {first} et {second}.",
    writings: ["pour composer en largeur", "pour composer de haut en bas"],
    policy: "Ce document a pour consigne de {policy} une police dont l’indicateur interdit \
l’incorporation.",
    policies: [
        "quand même incorporer, en la nommant dans un avertissement,",
        "laisser de côté",
        "refuser le document pour",
    ],
    decimal: ',',
};

/// The page in Dutch.
const DUTCH: Words = Words {
    title: "Wat een lettertypebestand over zichzelf zegt",
    lead: "Drie letters, gelezen uit hun bestanden. Elk wordt één keer in de eigen letters gezet, en eronder wordt elk getal uit het bestand gelezen, geen enkel met de hand geschreven.",
    identity: "{postscript}, uit de familie {family}: {outlines}; {units} eenheden per em; {glyphs} glyphs.",
    outlines: [
        "kwadratische contouren, een TrueType-lettertype",
        "kubische contouren, een OpenType-lettertype met CFF-contouren",
    ],
    traits: "Kenmerken: {traits}.",
    trait_names: [
        "schreven",
        "één breedte voor elke glyph",
        "vet",
        "cursief",
        "een symboolcodering",
    ],
    none: "geen",
    unknown: "niet vermeld",
    heights: "Op 10 pt stijgt het {ascent} en daalt het {descent}; hoofdletters: {cap}; kleine letters: {x}; het vraagt {gap} tussen regels; het helt {slant}°; elke glyph valt binnen [{bbox}] eenheden.",
    underline: "Een onderstreping op 10 pt ligt {position} pt van de basislijn, {thickness} pt dik; het bestand vermeldt {stated}.",
    stated: "{position} en {thickness} eenheden",
    unstated: "niets daarvan",
    permissions: "Eigen vlag: {embedding}; {subsetting}; {bitmap}.",
    embeddings: [
        "het mag ingesloten en geïnstalleerd worden",
        "het mag niet ingesloten worden zonder toestemming van de eigenaar",
        "het mag ingesloten worden om het document te bekijken en af te drukken",
        "het mag ingesloten worden en het document bewerkt",
    ],
    subsetting: [
        "het mag worden ingekort",
        "het moet in zijn geheel worden ingesloten",
    ],
    bitmap: [
        "de contouren mogen worden ingesloten",
        "alleen de bitmaps mogen worden ingesloten",
    ],
    code_pages: "Codetabellen die het opgeeft: {pages}.",
    measures: "W schuift {across} eenheden naar rechts en {down} omlaag; de glyph Eacute: {drawn}; „{sample}” neemt {count} glyphs, loopt {width} pt op 10 pt en {height} pt omlaag.",
    vertical: [
        "het zegt hoe ver elke glyph omlaag opschuift",
        "het zegt niets over zetten langs de pagina omlaag",
    ],
    symbol: "Gelezen als symboollettertype, code 0x41: {symbol}.",
    selectors: "Variatiekiezers waarvoor het reeksen noemt: {count}; een nul gevolgd door U+FE00: {variant}.",
    twice: "Het eerste bestand wordt twee keer toegevoegd, {first} en {second}.",
    writings: ["om horizontaal te zetten", "om verticaal te zetten"],
    policy: "Voor een lettertype waarvan de eigen vlag insluiten verbiedt, krijgt dit document de opdracht: {policy}.",
    policies: [
        "insluiten, en in een waarschuwing noemen",
        "weglaten",
        "het document weigeren",
    ],
    decimal: '.',
};

/// The page in Portuguese.
const PORTUGUESE: Words = Words {
    title: "O que um ficheiro de tipo de letra diz de si mesmo",
    lead: "Três tipos lidos dos seus ficheiros. Cada um é composto uma vez nas suas próprias letras, e por baixo cada número é lido do ficheiro, nenhum escrito à mão.",
    identity: "{postscript}, da família {family}: {outlines}; {units} unidades por eme; {glyphs} glifos.",
    outlines: [
        "contornos quadráticos, um tipo de letra TrueType",
        "contornos cúbicos, um tipo de letra OpenType com contornos CFF",
    ],
    traits: "Características: {traits}.",
    trait_names: [
        "serifas",
        "a mesma largura para todos os glifos",
        "negrito",
        "itálico",
        "uma codificação de símbolos",
    ],
    none: "nenhuma",
    unknown: "não indicado",
    heights: "A 10 pt sobe {ascent} e desce {descent}; maiúsculas: {cap}; minúsculas: {x}; pede {gap} entre linhas; inclina {slant}°; cada glifo cabe em [{bbox}] unidades.",
    underline: "Um sublinhado a 10 pt fica a {position} pt da linha de base, com {thickness} pt de espessura; o ficheiro indica {stated}.",
    stated: "{position} e {thickness} unidades",
    unstated: "nada disso",
    permissions: "O seu indicador: {embedding}; {subsetting}; {bitmap}.",
    embeddings: [
        "pode ser incorporado e instalado",
        "não deve ser incorporado sem autorização do titular dos direitos",
        "pode ser incorporado para ver e imprimir o documento",
        "pode ser incorporado e o documento editado",
    ],
    subsetting: [
        "pode ser reduzido a um subconjunto",
        "tem de ser levado inteiro",
    ],
    bitmap: [
        "os seus contornos podem ser levados",
        "só os seus bitmaps podem ser levados",
    ],
    code_pages: "Páginas de código que declara: {pages}.",
    measures: "W avança {across} unidades na largura e {down} para baixo; o glifo chamado Eacute: {drawn}; «{sample}» ocupa {count} glifos, mede {width} pt a 10 pt e {height} pt para baixo.",
    vertical: [
        "indica quanto avança cada glifo para baixo",
        "não indica nada sobre compor para baixo",
    ],
    symbol: "Lido como tipo de letra de símbolos, código 0x41: {symbol}.",
    selectors: "Seletores de variante para os quais nomeia sequências: {count}; um zero seguido de U+FE00: {variant}.",
    twice: "O primeiro ficheiro é acrescentado duas vezes, {first} e {second}.",
    writings: ["para compor na largura", "para compor para baixo"],
    policy: "Pede-se a este documento que {policy} um tipo de letra cujo indicador proíbe incorporá-lo.",
    policies: [
        "leve, e nomeie num aviso,",
        "omita",
        "recuse o documento por",
    ],
    decimal: '.',
};

/// The page in Italian.
const ITALIAN: Words = Words {
    title: "Ciò che un file di font dice di sé",
    lead: "Tre font letti dai loro file. Ciascuno è composto una volta nelle proprie lettere, e sotto ogni cifra è letta dal file, nessuna scritta a mano.",
    identity: "{postscript}, della famiglia {family}: {outlines}; {units} unità per em; {glyphs} glifi.",
    outlines: [
        "contorni quadratici, un font TrueType",
        "contorni cubici, un font OpenType con contorni CFF",
    ],
    traits: "Tratti: {traits}.",
    trait_names: [
        "grazie",
        "una larghezza per ogni glifo",
        "grassetto",
        "corsivo",
        "una codifica di simboli",
    ],
    none: "nessuno",
    unknown: "non indicato",
    heights: "A 10 pt sale {ascent} e scende {descent}; maiuscole: {cap}; minuscole: {x}; chiede {gap} tra le righe; inclina di {slant}°; ogni glifo sta in [{bbox}] unità.",
    underline: "Una sottolineatura a 10 pt sta a {position} pt dalla linea di base, spessa {thickness} pt; il file indica {stated}.",
    stated: "{position} e {thickness} unità",
    unstated: "nulla di ciò",
    permissions: "Il suo indicatore: {embedding}; {subsetting}; {bitmap}.",
    embeddings: [
        "può essere incorporato e installato",
        "non deve essere incorporato senza il permesso del proprietario",
        "può essere incorporato per vedere e stampare il documento",
        "può essere incorporato e il documento modificato",
    ],
    subsetting: ["può essere ridotto", "va portato intero"],
    bitmap: [
        "i suoi contorni possono essere portati",
        "solo le sue bitmap possono essere portate",
    ],
    code_pages: "Pagine di codici che dichiara: {pages}.",
    measures: "W avanza di {across} unità in larghezza e {down} in basso; il glifo chiamato Eacute: {drawn}; «{sample}» occupa {count} glifi, misura {width} pt a 10 pt e {height} pt in basso.",
    vertical: [
        "dice quanto avanza ogni glifo verso il basso",
        "non dice nulla della composizione verso il basso",
    ],
    symbol: "Letto come font di simboli, codice 0x41: {symbol}.",
    selectors: "Selettori di variante per cui nomina sequenze: {count}; uno zero seguito da U+FE00: {variant}.",
    twice: "Il primo file è aggiunto due volte, {first} e {second}.",
    writings: ["per comporre in larghezza", "per comporre verso il basso"],
    policy: "A questo documento si chiede di {policy} un font il cui indicatore ne vieta l’incorporazione.",
    policies: [
        "portare, e nominare in un avviso,",
        "omettere",
        "rifiutare il documento per",
    ],
    decimal: '.',
};

/// The page in Spanish.
const SPANISH: Words = Words {
    title: "Lo que un archivo de fuente dice de sí mismo",
    lead: "Tres caras leídas de sus archivos. Cada una se compone una vez en sus propias letras, y debajo cada cifra se lee del archivo, ninguna escrita a mano.",
    identity: "{postscript}, de la familia {family}: {outlines}; {units} unidades por eme; {glyphs} glifos.",
    outlines: [
        "contornos cuadráticos, una fuente TrueType",
        "contornos cúbicos, una fuente OpenType con contornos CFF",
    ],
    traits: "Rasgos: {traits}.",
    trait_names: [
        "serifas",
        "un mismo ancho para cada glifo",
        "negrita",
        "cursiva",
        "una codificación de símbolos",
    ],
    none: "ninguno",
    unknown: "no indicado",
    heights: "A 10 pt sube {ascent} y baja {descent}; mayúsculas: {cap}; minúsculas: {x}; pide {gap} entre líneas; se inclina {slant}°; cada glifo cabe en [{bbox}] unidades.",
    underline: "Un subrayado a 10 pt está a {position} pt de la línea base, con {thickness} pt de grosor; el archivo indica {stated}.",
    stated: "{position} y {thickness} unidades",
    unstated: "nada de ello",
    permissions: "Su propio indicador: {embedding}; {subsetting}; {bitmap}.",
    embeddings: [
        "puede incrustarse e instalarse",
        "no debe incrustarse sin permiso de su propietario",
        "puede incrustarse para ver e imprimir el documento",
        "puede incrustarse y el documento editarse",
    ],
    subsetting: ["puede recortarse", "debe llevarse entera"],
    bitmap: [
        "sus contornos pueden llevarse",
        "solo sus mapas de bits pueden llevarse",
    ],
    code_pages: "Páginas de códigos que declara: {pages}.",
    measures: "W avanza {across} unidades a lo ancho y {down} hacia abajo; el glifo llamado Eacute: {drawn}; «{sample}» ocupa {count} glifos, mide {width} pt a 10 pt y {height} pt hacia abajo.",
    vertical: [
        "indica cuánto avanza cada glifo hacia abajo",
        "no indica nada para componer hacia abajo",
    ],
    symbol: "Leída como fuente de símbolos, código 0x41: {symbol}.",
    selectors: "Selectores de variante para los que nombra secuencias: {count}; un cero seguido de U+FE00: {variant}.",
    twice: "El primer archivo se añade dos veces, {first} y {second}.",
    writings: ["para componer a lo ancho", "para componer hacia abajo"],
    policy: "A este documento se le pide {policy} una fuente cuyo propio indicador prohíbe incrustarla.",
    policies: [
        "llevar, y nombrar en un aviso,",
        "omitir",
        "rechazar el documento por",
    ],
    decimal: '.',
};

/// The page in German.
const GERMAN: Words = Words {
    title: "Was eine Schriftdatei über sich selbst sagt",
    lead: "Drei Schnitte, aus ihren Dateien gelesen. Jeder wird einmal in seinen eigenen Buchstaben gesetzt, und darunter wird jede Zahl aus der Datei gelesen, keine von Hand geschrieben.",
    identity: "{postscript}, aus der Familie {family}: {outlines}; {units} Einheiten pro Geviert; {glyphs} Glyphen.",
    outlines: [
        "quadratische Konturen, eine TrueType-Schrift",
        "kubische Konturen, eine OpenType-Schrift mit CFF-Konturen",
    ],
    traits: "Merkmale: {traits}.",
    trait_names: [
        "Serifen",
        "dieselbe Breite für alle Glyphen",
        "fett",
        "kursiv",
        "eine Symbolkodierung",
    ],
    none: "keine",
    unknown: "nicht angegeben",
    heights: "Bei 10 pt beträgt ihre Oberlänge {ascent} und ihre Unterlänge {descent}; Versalhöhe: {cap}; x-Höhe: {x}; sie verlangt {gap} Zwischenraum zwischen den Zeilen; sie neigt sich um {slant}°; jeder Glyph liegt innerhalb von [{bbox}] Einheiten.",
    underline: "Eine Unterstreichung bei 10 pt liegt {position} pt von der Grundlinie entfernt, {thickness} pt dick; die Datei gibt an: {stated}.",
    stated: "{position} und {thickness} Einheiten",
    unstated: "nichts davon",
    permissions: "Ihr eigenes Kennzeichen: {embedding}; {subsetting}; {bitmap}.",
    embeddings: [
        "sie darf eingebettet und installiert werden",
        "sie darf ohne Erlaubnis ihres Rechteinhabers nicht eingebettet werden",
        "sie darf eingebettet werden, um das Dokument anzuzeigen und zu drucken",
        "sie darf eingebettet und das Dokument bearbeitet werden",
    ],
    subsetting: [
        "sie darf als Teilmenge eingebettet werden",
        "sie muss vollständig mitgeführt werden",
    ],
    bitmap: [
        "ihre Konturen dürfen mitgeführt werden",
        "nur ihre Bitmaps dürfen mitgeführt werden",
    ],
    code_pages: "Codepages, die sie beansprucht: {pages}.",
    measures: "W rückt {across} Einheiten nach rechts und {down} die Seite hinunter vor; der Glyph namens Eacute: {drawn}; „{sample}“ braucht {count} Glyphen, läuft bei 10 pt {width} pt weit und {height} pt die Seite hinunter.",
    vertical: [
        "sie gibt an, wie weit jeder Glyph im senkrechten Satz vorrückt",
        "sie sagt nichts über senkrechten Satz",
    ],
    symbol: "Als Symbolschrift gelesen, Code 0x41: {symbol}.",
    selectors: "Variantenselektoren, für die sie Folgen nennt: {count}; eine Null gefolgt von U+FE00: {variant}.",
    twice: "Die erste Datei wird zweimal hinzugefügt, {first} und {second}.",
    writings: ["für waagerechten Satz", "für senkrechten Satz"],
    policy: "Dieses Dokument soll eine Schrift, deren eigenes Kennzeichen das Einbetten verbietet, {policy}.",
    policies: [
        "mitführen und in einer Warnung nennen",
        "weglassen",
        "zum Anlass nehmen, das Dokument abzulehnen",
    ],
    decimal: '.',
};

/// Every language the example is written in. A language is added by writing its
/// own set of words and naming it here.
static WORDS: [(Language, &Words); 7] = [
    (Language::English, &ENGLISH),
    (Language::French, &FRENCH),
    (Language::German, &GERMAN),
    (Language::Spanish, &SPANISH),
    (Language::Italian, &ITALIAN),
    (Language::Portuguese, &PORTUGUESE),
    (Language::Dutch, &DUTCH),
];

/// What `page` is called on the page: its Windows code page number, or the name
/// of the character set it stands for.
const fn code_page_name(page: CodePage) -> &'static str {
    match page {
        CodePage::Latin1 => "1252",
        CodePage::Latin2 => "1250",
        CodePage::Cyrillic => "1251",
        CodePage::Greek => "1253",
        CodePage::Turkish => "1254",
        CodePage::Hebrew => "1255",
        CodePage::Arabic => "1256",
        CodePage::Baltic => "1257",
        CodePage::Vietnamese => "1258",
        CodePage::Thai => "874",
        CodePage::Japanese => "932",
        CodePage::ChineseSimplified => "936",
        CodePage::KoreanWansung => "949",
        CodePage::ChineseTraditional => "950",
        CodePage::KoreanJohab => "1361",
        CodePage::Macintosh => "Mac",
        CodePage::Oem => "OEM",
        CodePage::Symbol => "Symbol",
        CodePage::IbmGreek => "869",
        CodePage::DosRussian => "866",
        CodePage::DosNordic => "865",
        CodePage::DosArabic => "864",
        CodePage::DosCanadianFrench => "863",
        CodePage::DosHebrew => "862",
        CodePage::DosIcelandic => "861",
        CodePage::DosPortuguese => "860",
        CodePage::IbmTurkish => "857",
        CodePage::IbmCyrillic => "855",
        CodePage::DosLatin2 => "852",
        CodePage::DosBaltic => "775",
        CodePage::DosGreek => "737",
        CodePage::Asmo708 => "708",
        CodePage::DosLatin1 => "850",
        CodePage::UnitedStates => "437",
        _ => "?",
    }
}

/// Where `embedding` stands in [`Words::embeddings`].
#[allow(
    clippy::match_same_arms,
    reason = "a permission the library adds later reads as the strictest"
)]
const fn embedding_place(embedding: Embedding) -> usize {
    match embedding {
        Embedding::Installable => 0,
        Embedding::Restricted => 1,
        Embedding::PreviewAndPrint => 2,
        Embedding::Editable => 3,
        _ => 1,
    }
}

/// `value` to one decimal, its decimal separator the language's own.
fn figure(value: f64, words: &Words) -> String {
    format!("{value:.1}").replace('.', &words.decimal.to_string())
}

/// Where `format` stands in [`Words::outlines`].
#[allow(
    clippy::match_same_arms,
    reason = "a format the library adds later reads as cubic"
)]
const fn outline_place(format: OutlineFormat) -> usize {
    match format {
        OutlineFormat::TrueType => 0,
        OutlineFormat::Cff => 1,
        _ => 1,
    }
}

/// What `font`, of the family `family_name`, states about its names, its
/// outlines, its traits and its heights, one line each.
fn facts(font: &Font, family_name: &str, words: &Words) -> Vec<String> {
    let stored = words.outlines[outline_place(font.outlines())];
    let identity = words
        .identity
        .replace("{postscript}", font.postscript_name())
        .replace("{family}", family_name)
        .replace("{outlines}", stored)
        .replace("{units}", &font.units_per_em().to_string())
        .replace("{glyphs}", &font.glyph_count().to_string());

    let flags = font.traits();
    let named: Vec<&str> = [
        flags.serif,
        flags.fixed_pitch,
        flags.bold,
        flags.italic,
        flags.symbolic,
    ]
    .iter()
    .zip(words.trait_names)
    .filter(|(held, _)| **held)
    .map(|(_, trait_name)| trait_name)
    .collect();
    let held = if named.is_empty() {
        words.none.to_owned()
    } else {
        named.join(", ")
    };

    let metrics = font.metrics();
    let edges = metrics.bbox.map(|edge| edge.to_string()).join(" ");
    let heights = words
        .heights
        .replace("{ascent}", &figure(font.ascent(10.0), words))
        .replace("{descent}", &figure(font.descent(10.0), words))
        .replace("{cap}", &figure(font.cap_height(10.0), words))
        .replace(
            "{x}",
            &font
                .x_height(10.0)
                .map_or_else(|| words.unknown.to_owned(), |height| figure(height, words)),
        )
        .replace("{gap}", &figure(font.line_gap(10.0), words))
        .replace("{slant}", &figure(metrics.italic_angle, words))
        .replace("{bbox}", &edges);

    let stroke = font.underline(10.0);
    let said_of = metrics.underline.map_or_else(
        || words.unstated.to_owned(),
        |line| {
            words
                .stated
                .replace("{position}", &line.position.to_string())
                .replace("{thickness}", &line.thickness.to_string())
        },
    );
    let underline = words
        .underline
        .replace("{position}", &figure(stroke.position, words))
        .replace("{thickness}", &figure(stroke.thickness, words))
        .replace("{stated}", &said_of);

    vec![
        identity,
        words.traits.replace("{traits}", &held),
        heights,
        underline,
    ]
}

/// What `font` states of what may be done with it, which code pages it claims,
/// and how one glyph and one line measure, one line each.
fn uses(font: &Font, words: &Words) -> Result<Vec<String>, hqf_pdf::Error> {
    let flag = font.permissions();
    let permissions = words
        .permissions
        .replace(
            "{embedding}",
            words.embeddings[embedding_place(flag.embedding)],
        )
        .replace(
            "{subsetting}",
            words.subsetting[usize::from(!flag.subsetting)],
        )
        .replace("{bitmap}", words.bitmap[usize::from(flag.bitmap_only)]);

    let claimed_list = font.claimed_code_pages().map_or_else(
        || words.none.to_owned(),
        |claimed| {
            claimed
                .into_iter()
                .map(code_page_name)
                .collect::<Vec<_>>()
                .join(", ")
        },
    );
    let code_pages = words.code_pages.replace("{pages}", &claimed_list);

    let w = font.glyph('W').unwrap_or_default();
    let eacute: String = font.glyph_named("Eacute").map_or_else(
        || words.unknown.to_owned(),
        |glyph| font.characters_of(glyph).into_iter().collect(),
    );
    let measures = words
        .measures
        .replace("{across}", &font.advance(w).to_string())
        .replace("{down}", &font.vertical_advance(w).to_string())
        .replace("{drawn}", &eacute)
        .replace("{sample}", SAMPLE)
        .replace("{count}", &font.try_glyphs(SAMPLE)?.len().to_string())
        .replace("{width}", &figure(font.try_measure(SAMPLE, 10.0)?, words))
        .replace(
            "{height}",
            &figure(font.measure_vertically(SAMPLE, 10.0), words),
        );
    let vertical = words.vertical[usize::from(!font.sets_vertically())];
    let symbol_line = words.symbol.replace(
        "{symbol}",
        &font
            .symbol_character(0x41)
            .map_or_else(|| words.none.to_owned(), |reached| reached.to_string()),
    );

    let sequence = font.variant_glyph('0', '\u{FE00}').map_or_else(
        || words.unknown.to_owned(),
        |glyph| glyph.index().to_string(),
    );
    let selectors = words
        .selectors
        .replace("{count}", &font.variation_selectors().len().to_string())
        .replace("{variant}", &sequence);

    Ok(vec![
        permissions,
        code_pages,
        format!("{measures} {}.", capitalised(vertical)),
        symbol_line,
        selectors,
    ])
}

/// `text` with its first letter a capital.
fn capitalised(text: &str) -> String {
    let mut letters = text.chars();
    letters.next().map_or_else(String::new, |first| {
        first.to_uppercase().chain(letters).collect()
    })
}

/// `lines` set from `top` down in `font`, each broken to the page's width;
/// hands back where the last ends.
fn set(
    c: &mut Content,
    font: &FontHandle,
    size: f64,
    top: f64,
    lines: &[String],
) -> Result<f64, hqf_pdf::Error> {
    let flow = TextFlow::new(font, size).leading(size * 1.3);
    let mut y = top;
    for line in lines {
        let broken = flow.break_lines(line, WIDTH);
        c.begin_text();
        flow.draw(c, &broken, LEFT, y, WIDTH)?;
        c.end_text();
        y -= flow.height(&broken) + 1.5;
    }
    Ok(y)
}

/// Where `policy` stands in [`Words::policies`].
#[allow(
    clippy::match_same_arms,
    reason = "a policy the library adds later reads as the default"
)]
const fn policy_place(policy: FontPermissionPolicy) -> usize {
    match policy {
        FontPermissionPolicy::Warn => 0,
        FontPermissionPolicy::LeaveOut => 1,
        FontPermissionPolicy::Refuse => 2,
        _ => 0,
    }
}

/// Where `writing` stands in [`Words::writings`].
#[allow(
    clippy::match_same_arms,
    reason = "a way the library adds later reads as across"
)]
const fn writing_place(writing: WritingMode) -> usize {
    match writing {
        WritingMode::Horizontal => 0,
        WritingMode::Vertical => 1,
        _ => 0,
    }
}

/// The document: the three faces, each set in its own letters over what its
/// file states, and the first file added twice.
fn build(words: &Words) -> Result<Document, Box<dyn std::error::Error>> {
    let mut doc = Document::new();
    doc.set_license(licence::licensed());
    doc.set_info(Name::new("Title"), words.title);
    let told_policy = FontPermissionPolicy::Warn;
    doc.set_font_permission_policy(told_policy);

    let mut faces = Vec::new();
    for (place, file) in FILES.iter().enumerate() {
        let bytes = fs::read(fonts().join(file))?;
        let family = Font::families(&bytes)?
            .into_iter()
            .next()
            .unwrap_or_default();
        let font = if place == 2 {
            Font::parse_face(bytes, 0)?
        } else {
            Font::parse_family(bytes, &family)?
        };
        faces.push((family, font));
    }
    let text = doc.add_font(faces[0].1.clone());
    let upright = doc.add_vertical_font(faces[0].1.clone());

    let mut c = Content::new();
    c.set_fill(Rgb::gray(0.0))?;
    let mut top = set(&mut c, &text, 16.0, 790.0, &[words.title.to_owned()])? - 4.0;
    top = set(&mut c, &text, 9.5, top, &[words.lead.to_owned()])? - 14.0;
    for (family, font) in &faces {
        let mut said = facts(font, family, words);
        said.extend(uses(font, words)?);
        let own = doc.add_font(font.clone());
        top = set(&mut c, &own, 14.0, top, std::slice::from_ref(family))? - 2.0;
        c.set_fill(Rgb::gray(0.3))?;
        top = set(&mut c, &text, FACTS, top, &said)? - 12.0;
        c.set_fill(Rgb::gray(0.0))?;
    }

    let twice = words
        .twice
        .replace("{first}", words.writings[writing_place(text.writing())])
        .replace("{second}", words.writings[writing_place(upright.writing())]);
    let told = words
        .policy
        .replace("{policy}", words.policies[policy_place(told_policy)]);
    set(&mut c, &text, 9.5, top, &[twice, told])?;

    let mut page = Page::a4();
    page.content = c.into_bytes();
    doc.add_page(page)?;
    Ok(doc)
}

fn main() -> std::process::ExitCode {
    failure::reported(run())
}

fn run() -> Result<(), Box<dyn std::error::Error>> {
    let language = Language::from_environment()?;
    let words = Words::of(language);

    let path = env::args()
        .nth(1)
        .unwrap_or_else(|| language.file_name(&out::default_path("font_facts")));

    let bytes = build(words)?.to_bytes()?;
    if let Some(parent) = Path::new(&path).parent() {
        fs::create_dir_all(parent)?;
    }
    fs::write(&path, &bytes)?;
    println!("wrote {} ({} bytes)", path, bytes.len());
    Ok(())
}

#[cfg(test)]
mod tests {
    use super::{WORDS, language};

    /// The lines two languages are allowed to write the same way, where two
    /// languages spell a word alike. Every line is words a person reads.
    const SPARED: [&str; 1] = [
        // Written the same way in Portuguese.
        r#""serifas""#,
    ];

    #[test]
    fn every_language_writes_the_page_in_its_own_words() {
        let untranslated = language::untranslated_lines(&WORDS, &SPARED);

        assert!(
            untranslated.is_empty(),
            "the page says these in more than one language: {untranslated:?}"
        );
    }
}