write_typography.rs

Die Rust-Datei des Beispiels „Was eine Schrift über ihre eigenen Buchstaben weiß“. Fünf Proben, jede zweimal gesetzt, einmal schlicht und einmal mit einer einzigen aktivierten Einstellung: verbundene Buchstaben, Kapitälchen, unter die Linie hängende Ziffern, die Buchstaben, die eine Sprache auf eigene Weise zeichnet, und enger zusammengerückte Paare.

Rust 1025 Zeilen

Wofür dieses Beispiel da ist

Dafür haben Sie bereits bezahlt. Eine Schriftdatei trägt weit mehr als eine Glyphe pro Buchstabe, und das meiste davon wird nie verwendet: die Formen, die nur erscheinen, wenn zwei bestimmte Buchstaben aufeinandertreffen, die Ziffern, die zwischen Kleinbuchstaben stehen sollen, die Kapitälchen, die nicht einfach verkleinerte Großbuchstaben sind. Ein Dokument, das all das ignoriert, liest sich wie getippt statt gesetzt.

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
 974
 975
 976
 977
 978
 979
 980
 981
 982
 983
 984
 985
 986
 987
 988
 989
 990
 991
 992
 993
 994
 995
 996
 997
 998
 999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
//! Sets five short samples twice each — once as the font is read straight off
//! its `cmap`, once with one typographic feature turned on — so the difference
//! each feature makes can be seen side by side.
//!
//! The five are the ligatures a font offers, small capitals, old-style figures,
//! the forms the font draws for one language, and kerning. Nothing is on unless
//! it is asked for, so the two settings of a sample differ in exactly one
//! thing. The forms of one language are the font's `locl` feature read for
//! Serbian, the Cyrillic script and the `SRB ` language.
//!
//! The font here is Spectral, which offers all five.
//!
//! The closing lines are measured off the fonts themselves rather than written
//! down: how many glyphs the ligature sample takes each way, how much narrower
//! kerning draws its own, and how many of the face's glyphs no character leads
//! to. The first of those are drawn last, asked for by their number in the file
//! and centred on the width measured off them.
//!
//! A second page sets the ligature sample with ligatures and kerning on, then
//! with ligatures alone turned back off, draws the face's `fi` glyph by its
//! number, stated to stand for the two letters it joins, and sets three
//! fractions twice: the face's `frac` feature draws each figure before the
//! slash as a numerator and each after it as a denominator, by the glyphs
//! around it.
//!
//! The words are held in `Words`, once per language, and `HQF_PDF_LANG` picks
//! which set is drawn.
//!
//! Usage: `cargo run --example write_typography -- tmp/typography.pdf`
//!        `HQF_PDF_LANG=fr cargo run --example write_typography --
//! tmp/typographie.pdf`

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

use hqf_pdf::content::Content;
use hqf_pdf::font::GlyphId;
use hqf_pdf::{Document, Font, FontHandle, Page, 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 committed fonts sit.
fn font_dir() -> PathBuf {
    Path::new(env!("CARGO_MANIFEST_DIR"))
        .join("tests")
        .join("fonts")
}

/// The words the page is written in, one set per language.
#[derive(Debug)]
struct Words {
    /// The line across the top of the page.
    title: &'static str,
    /// What the page shows, in two sentences.
    why: &'static str,
    /// The heading over each of the five samples.
    headings: [&'static str; 5],
    /// What each feature does, one line each.
    explanations: [&'static str; 5],
    /// The five samples, each set twice.
    samples: [&'static str; 5],
    /// What is written beside the setting with nothing turned on.
    off: &'static str,
    /// What is written beside the setting with the feature turned on.
    on: &'static str,
    /// The line about the ligature sample, cut where the two counts go into it.
    glyphs_before: &'static str,
    glyphs_between: &'static str,
    glyphs_after: &'static str,
    /// The line about the kerning sample, cut where the width goes into it.
    kerned_before: &'static str,
    kerned_after: &'static str,
    /// The line over the glyphs drawn by number, cut where the count of the
    /// face's glyphs, the count no character leads to and the count drawn go
    /// into it.
    numbered_before: &'static str,
    numbered_between: &'static str,
    numbered_then: &'static str,
    numbered_after: &'static str,
    /// The line at the head of the second page.
    back_title: &'static str,
    /// What the second page shows.
    back_why: &'static str,
    /// What is written beside the sample set with ligatures and kerning.
    both: &'static str,
    /// What is written beside it with ligatures turned back off.
    without: &'static str,
    /// What stands over the glyph drawn by its number.
    by_number: &'static str,
    /// The heading over the fraction sample.
    fraction_heading: &'static str,
    /// What the `frac` feature does, in one line.
    fraction_explanation: &'static str,
}

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

    /// The line about the ligature sample, with the two counts in it.
    fn glyph_line(&self, apart: usize, joined: usize) -> String {
        format!(
            "{}{apart}{}{joined}{}",
            self.glyphs_before, self.glyphs_between, self.glyphs_after
        )
    }

    /// The line about the kerning sample, with the width it saves in it.
    fn kerned_line(&self, saved: f64) -> String {
        format!("{}{saved:.1}{}", self.kerned_before, self.kerned_after)
    }

    /// The line over the glyphs drawn by number, with its three counts in it.
    fn numbered_line(&self, held: usize, unreached: usize, drawn: usize) -> String {
        format!(
            "{}{held}{}{unreached}{}{drawn}{}",
            self.numbered_before, self.numbered_between, self.numbered_then, self.numbered_after
        )
    }
}

/// The page in English.
const ENGLISH: Words = Words {
    title: "Five things a font knows about its own letters",
    why: "A font file carries more than one glyph per character. It says which \
          letters are drawn joined, which figures hang below the line, which \
          letters have a small capital of their own, which letters take another \
          form in one language, and how far apart each pair of glyphs should \
          stand.\n\
          None of it is used unless it is asked for, so a document written \
          before these settings existed comes out byte for byte as it did. Each \
          sample below is set twice, and only the setting differs.",
    headings: [
        "Ligatures",
        "Small capitals",
        "Old-style figures",
        "Local forms",
        "Kerning",
    ],
    explanations: [
        "Letters that meet are drawn as one glyph the font designer cut for them.",
        "Lowercase letters drawn as capitals no taller than an x.",
        "Figures that rise and fall around the x-height instead of standing at cap height.",
        "The same Cyrillic letters, in the forms the font draws for Serbian.",
        "Each pair of glyphs moved as far apart as the font asks.",
    ],
    samples: [
        "office filing affidavit",
        "reprinted quarterly",
        "1 234 567 890",
        "пут, град, беседа",
        "Wavy TAXI Today",
    ],
    off: "off",
    on: "on",
    glyphs_before: "The first sample takes ",
    glyphs_between: " glyphs as it stands and ",
    glyphs_after: " with the ligatures on: the pairs the font joins are one glyph each.",
    kerned_before: "The last sample is drawn ",
    kerned_after: " points narrower with kerning on, and it is measured that way \
                   too, so the line is as wide as it looks.",
    numbered_before: "Spectral holds ",
    numbered_between: " glyphs, and no character leads to ",
    numbered_then: " of them: the forms a feature or a language puts in a \
                    letter's place. The first ",
    numbered_after: ", asked for by their number in the file and centred on \
                     their measured width:",
    back_title: "One feature turned back off, one glyph by its number",
    back_why: "Ligatures and kerning are turned on together, then ligatures alone are turned back off: the letters come apart again, and the pairs stay kerned.",
    both: "both",
    without: "no liga",
    by_number: "The glyph the face names fi, asked for by its number and stated to stand for the two letters it joins, so that the text copies out as those two letters:",
    fraction_heading: "Fractions",
    fraction_explanation: "Figures on either side of a slash, set as a numerator and a denominator around a fraction bar.",
};

/// The page in French.
const FRENCH: Words = Words {
    title: "Cinq choses qu’une police sait de ses propres lettres",
    why: "Un fichier de police contient plus d’un glyphe par caractère. Il indique \
quelles lettres se lient, quels chiffres descendent sous la ligne, quelles \
lettres ont leur propre petite capitale, lesquelles changent de forme dans une \
langue donnée, et quel écart laisser entre chaque paire de glyphes.\nRien de \
tout cela ne sert tant qu’on ne le demande pas : un document écrit avant que \
ces réglages existent sort à l’octet près comme avant. Chaque échantillon \
ci-dessous est composé deux fois, et seul le réglage change.",
    headings: [
        "Ligatures",
        "Petites capitales",
        "Chiffres elzéviriens",
        "Formes locales",
        "Crénage",
    ],
    explanations: [
        "Les lettres qui se touchent forment un seul glyphe, prévu pour elles par le \
créateur de la police.",
        "Des minuscules dessinées en capitales pas plus hautes qu’un x.",
        "Des chiffres qui montent et descendent autour de la hauteur d’x au lieu de \
s’aligner sur les capitales.",
        "Les mêmes lettres cyrilliques, dans les formes que la police dessine pour le serbe.",
        "Chaque paire de glyphes écartée autant que la police le demande.",
    ],
    samples: [
        "affiche officielle du greffe",
        "réédition trimestrielle",
        "1 234 567 890",
        "пут, град, беседа",
        "Wagon TAXI Toiture",
    ],
    off: "sans",
    on: "avec",
    glyphs_before: "Le premier échantillon compte ",
    glyphs_between: " glyphes tel quel et ",
    glyphs_after: " avec les ligatures : chaque paire liée ne fait plus qu’un glyphe.",
    kerned_before: "Le dernier échantillon est composé ",
    kerned_after: " points plus étroit avec le crénage, et il est aussi mesuré ainsi : la ligne \
fait bien la largeur qu’elle paraît faire.",
    numbered_before: "Spectral contient ",
    numbered_between: " glyphes, et aucun caractère ne mène à ",
    numbered_then: " d’entre eux : les formes qu’un réglage ou une langue met à la place d’une \
lettre. Les ",
    numbered_after: " premiers, demandés par leur numéro dans le fichier et centrés sur leur largeur \
mesurée :",
    back_title: "Une fonction désactivée, un glyphe par son numéro",
    back_why: "Ligatures et crénage sont activés ensemble, puis les ligatures seules \
sont désactivées : les lettres se séparent de nouveau, et les paires restent crénées.",
    both: "les deux",
    without: "sans liga",
    by_number: "Le glyphe que la police nomme fi, demandé par son numéro et déclaré tenir lieu des deux lettres qu’il joint, pour que le texte se copie en ces deux lettres :",
    fraction_heading: "Fractions",
    fraction_explanation: "Les chiffres de part et d’autre d’une barre oblique, composés en numérateur et en dénominateur.",
};

/// The page in Dutch.
const DUTCH: Words = Words {
    title: "Vijf dingen die een lettertype over zijn eigen letters weet",
    why: "Een lettertypebestand bevat meer dan één glyph per teken. Het zegt welke letters verbonden getekend worden, welke cijfers onder de regel hangen, welke letters een eigen kleinkapitaal hebben, welke letters in een bepaalde taal een andere vorm krijgen, en hoe ver elk paar glyphs uit elkaar moet staan.\nNiets ervan wordt gebruikt tenzij erom gevraagd wordt, dus een document dat geschreven is voordat deze instellingen bestonden, komt er byte voor byte uit zoals vroeger. Elk voorbeeld hieronder is twee keer gezet, en alleen de instelling verschilt.",
    headings: [
        "Ligaturen",
        "Kleinkapitalen",
        "Mediëvalcijfers",
        "Lokale vormen",
        "Kerning",
    ],
    explanations: [
        "Letters die elkaar raken, worden getekend als één glyph die de ontwerper ervoor sneed.",
        "Kleine letters getekend als hoofdletters niet hoger dan een x.",
        "Cijfers die rond de x-hoogte stijgen en dalen in plaats van op kapitaalhoogte te staan.",
        "Dezelfde Cyrillische letters, in de vormen die het lettertype voor het Servisch tekent.",
        "Elk paar glyphs zo ver uit elkaar gezet als het lettertype vraagt.",
    ],
    samples: [
        "officiële flessenafvulling",
        "elk kwartaal herdrukt",
        "1 234 567 890",
        "пут, град, беседа",
        "Wafel TAXI Toren",
    ],
    off: "uit",
    on: "aan",
    glyphs_before: "Het eerste voorbeeld neemt",
    glyphs_between: " glyphs zoals het is en",
    glyphs_after: " met de ligaturen aan: de paren die het lettertype verbindt, zijn elk één glyph.",
    kerned_before: "Het laatste voorbeeld wordt",
    kerned_after: " punten smaller getekend met kerning aan, en het wordt ook zo gemeten, dus is de regel zo breed als hij lijkt.",
    numbered_before: "Spectral bevat",
    numbered_between: " glyphs, en bij",
    numbered_then: " ervan komt geen teken uit: de vormen die een functie of een taal in de plaats van een letter zet. De eerste",
    numbered_after: ", opgevraagd bij hun nummer in het bestand en gecentreerd op hun gemeten breedte:",
    back_title: "Eén functie weer uitgezet, één glyph bij zijn nummer",
    back_why: "Ligaturen en kerning worden samen aangezet, dan worden alleen de ligaturen weer uitgezet: de letters gaan weer uit elkaar, en de paren blijven gekerned.",
    both: "beide",
    without: "zonder liga",
    by_number: "De glyph die het lettertype fi noemt, opgevraagd bij zijn nummer en opgegeven als de twee letters die hij verbindt, zodat de tekst als die twee letters gekopieerd wordt:",
    fraction_heading: "Breuken",
    fraction_explanation: "Cijfers aan weerszijden van een schuine streep, gezet als teller en noemer rond een breukstreep.",
};

/// The page in Portuguese.
const PORTUGUESE: Words = Words {
    title: "Cinco coisas que um tipo de letra sabe sobre as suas letras",
    why: "Um ficheiro de tipo de letra leva mais de um glifo por carácter. Diz que letras se desenham unidas, que algarismos descem abaixo da linha, que letras têm versaletes próprios, que letras tomam outra forma numa língua e a que distância deve ficar cada par de glifos.\nNada disto é usado sem ser pedido, por isso um documento escrito antes de estas definições existirem sai byte a byte como saía. Cada amostra abaixo é composta duas vezes, e só muda a definição.",
    headings: [
        "Ligaduras",
        "Versaletes",
        "Algarismos de estilo antigo",
        "Formas locais",
        "Crenagem",
    ],
    explanations: [
        "As letras que se tocam são desenhadas como um glifo que o desenhador do tipo cortou para elas.",
        "Minúsculas desenhadas como maiúsculas não mais altas do que um x.",
        "Algarismos que sobem e descem em torno da altura do x em vez de ficarem à altura das maiúsculas.",
        "As mesmas letras cirílicas, nas formas que o tipo desenha para o sérvio.",
        "Cada par de glifos afastado tanto quanto o tipo pede.",
    ],
    samples: [
        "ficha oficial do filiado",
        "reimpresso a cada trimestre",
        "1 234 567 890",
        "пут, град, беседа",
        "Vagão TAXI Torta",
    ],
    off: "não",
    on: "sim",
    glyphs_before: "A primeira amostra ocupa",
    glyphs_between: " glifos tal como está e",
    glyphs_after: " com as ligaduras: os pares que o tipo une são um glifo cada.",
    kerned_before: "A última amostra é desenhada",
    kerned_after: " pontos mais estreita com crenagem, e também é medida assim, por isso a linha é tão larga como parece.",
    numbered_before: "O Spectral tem",
    numbered_between: " glifos, e",
    numbered_then: " deles não levam nenhum carácter: as formas que uma funcionalidade ou uma língua põem no lugar de uma letra. Os primeiros",
    numbered_after: ", pedidos pelo seu número no ficheiro e centrados na largura medida:",
    back_title: "Uma funcionalidade desligada de novo, um glifo pelo seu número",
    back_why: "Ligaduras e crenagem são ligadas juntas, e depois só as ligaduras são desligadas: as letras voltam a separar-se, e os pares continuam com crenagem.",
    both: "ambas",
    without: "sem liga",
    by_number: "O glifo a que o tipo chama fi, pedido pelo seu número e indicado como representante das duas letras que une, para que o texto se copie como essas duas letras:",
    fraction_heading: "Frações",
    fraction_explanation: "Os algarismos de cada lado de uma barra, compostos como numerador e denominador em volta de um traço de fração.",
};

/// The page in Italian.
const ITALIAN: Words = Words {
    title: "Cinque cose che un font sa delle proprie lettere",
    why: "Un file di font porta più di un glifo per carattere. Dice quali lettere si disegnano unite, quali cifre scendono sotto la riga, quali lettere hanno un maiuscoletto proprio, quali lettere prendono un’altra forma in una lingua e quanto distante deve stare ogni coppia di glifi.\nNiente di tutto ciò è usato se non lo si chiede, così un documento scritto prima che queste impostazioni esistessero esce byte per byte come usciva. Ogni campione qui sotto è composto due volte, e cambia solo l’impostazione.",
    headings: [
        "Legature",
        "Maiuscoletto",
        "Cifre in stile antico",
        "Forme locali",
        "Crenatura",
    ],
    explanations: [
        "Le lettere che si toccano sono disegnate come un glifo che il disegnatore del carattere ha tagliato per loro.",
        "Minuscole disegnate come maiuscole non più alte di una x.",
        "Cifre che salgono e scendono intorno all’altezza della x anziché stare all’altezza delle maiuscole.",
        "Le stesse lettere cirilliche, nelle forme che il carattere disegna per il serbo.",
        "Ogni coppia di glifi distanziata quanto chiede il font.",
    ],
    samples: [
        "ufficio affiliato fluviale",
        "ristampato ogni trimestre",
        "1 234 567 890",
        "пут, град, беседа",
        "Vagone TAXI Torta",
    ],
    off: "no",
    on: "sì",
    glyphs_before: "Il primo campione occupa",
    glyphs_between: " glifi così com’è e",
    glyphs_after: " con le legature: le coppie che il font unisce sono un glifo ciascuna.",
    kerned_before: "L’ultimo campione è disegnato",
    kerned_after: " punti più stretto con la crenatura, ed è misurato così: la riga è larga quanto sembra.",
    numbered_before: "Spectral contiene",
    numbered_between: " glifi, e a",
    numbered_then: " di essi non corrisponde alcun carattere: le forme che una funzione o una lingua mettono al posto di una lettera. I primi",
    numbered_after: ", chiesti per numero nel file e centrati sulla larghezza misurata:",
    back_title: "Una funzione disattivata di nuovo, un glifo per numero",
    back_why: "Legature e crenatura si attivano insieme, poi si disattivano solo le legature: le lettere si separano di nuovo, e le coppie restano crenate.",
    both: "entrambe",
    without: "senza liga",
    by_number: "Il glifo che il carattere chiama fi, chiesto per numero e indicato come rappresentante delle due lettere che unisce, così il testo si copia come quelle due lettere:",
    fraction_heading: "Frazioni",
    fraction_explanation: "Le cifre ai due lati di una barra, composte come numeratore e denominatore attorno a una barra di frazione.",
};

/// The page in Spanish.
const SPANISH: Words = Words {
    title: "Cinco cosas que una fuente sabe de sus propias letras",
    why: "Un archivo de fuente lleva más de un glifo por carácter. Dice qué letras se dibujan unidas, qué cifras bajan de la línea, qué letras tienen versalita propia, qué letras toman otra forma en un idioma y a qué distancia debe estar cada par de glifos.\nNada de ello se usa si no se pide, así que un documento escrito antes de que existieran estos ajustes sale byte a byte como salía. Cada muestra de abajo se compone dos veces, y solo cambia el ajuste.",
    headings: [
        "Ligaduras",
        "Versalitas",
        "Cifras elzevirianas",
        "Formas locales",
        "Interletraje",
    ],
    explanations: [
        "Las letras que se tocan se dibujan como un glifo que el diseñador de la fuente cortó para ellas.",
        "Minúsculas dibujadas como mayúsculas no más altas que una x.",
        "Cifras que suben y bajan alrededor de la altura de la x en vez de alinearse con las mayúsculas.",
        "Las mismas letras cirílicas, en las formas que la fuente dibuja para el serbio.",
        "Cada par de glifos separado tanto como pide la fuente.",
    ],
    samples: [
        "ficha oficial del afiliado",
        "reimpreso cada trimestre",
        "1 234 567 890",
        "пут, град, беседа",
        "Vagón TAXI Tarta",
    ],
    off: "no",
    on: "sí",
    glyphs_before: "La primera muestra ocupa",
    glyphs_between: " glifos tal cual y",
    glyphs_after: " con las ligaduras: los pares que une la fuente son un glifo cada uno.",
    kerned_before: "La última muestra se dibuja",
    kerned_after: " puntos más estrecha con interletraje, y también se mide así, así que la línea es tan ancha como parece.",
    numbered_before: "Spectral tiene",
    numbered_between: " glifos, y",
    numbered_then: " de ellos no corresponden a ningún carácter: las formas que una función o un idioma ponen en lugar de una letra. Los primeros",
    numbered_after: ", pedidos por su número en el archivo y centrados en su ancho medido:",
    back_title: "Una función desactivada otra vez, un glifo por su número",
    back_why: "Ligaduras e interletraje se activan juntos, y luego se desactivan solo las ligaduras: las letras se separan de nuevo, y los pares siguen con interletraje.",
    both: "ambos",
    without: "sin liga",
    by_number: "El glifo que la fuente llama fi, pedido por su número e indicado como representante de las dos letras que une, para que el texto se copie como esas dos letras:",
    fraction_heading: "Fracciones",
    fraction_explanation: "Las cifras a cada lado de una barra, compuestas como numerador y denominador alrededor de una barra de fracción.",
};

/// The page in German.
const GERMAN: Words = Words {
    title: "Was eine Schrift über ihre Buchstaben weiß",
    why: "Eine Schriftdatei trägt mehr als einen Glyphen je Zeichen. Sie sagt, welche Buchstaben verbunden gezeichnet werden, welche Ziffern unter die Zeile hängen, welche Buchstaben ein eigenes Kapitälchen haben, welche Buchstaben in einer Sprache eine andere Form annehmen und wie weit jedes Glyphenpaar auseinanderstehen soll.\nNichts davon wird genutzt, wenn es nicht verlangt wird, also kommt ein Dokument, das vor diesen Einstellungen geschrieben wurde, Byte für Byte so heraus wie damals. Jedes Muster unten ist zweimal gesetzt, und nur die Einstellung unterscheidet sich.",
    headings: [
        "Ligaturen",
        "Kapitälchen",
        "Mediävalziffern",
        "Lokale Formen",
        "Unterschneidung",
    ],
    explanations: [
        "Sich berührende Buchstaben werden zu einem eigens geschnittenen Glyphen.",
        "Kleinbuchstaben als Versalien, nicht höher als ein x.",
        "Ziffern, die um die x-Höhe auf- und absteigen, statt auf Versalhöhe zu stehen.",
        "Dieselben kyrillischen Buchstaben in ihren serbischen Formen.",
        "Jedes Glyphenpaar so weit gerückt, wie die Schrift verlangt.",
    ],
    samples: [
        "offizielle Kaffeefilter",
        "vierteljährlich nachgedruckt",
        "1 234 567 890",
        "пут, град, беседа",
        "Wagen TAXI Torte",
    ],
    off: "aus",
    on: "an",
    glyphs_before: "Das erste Muster braucht ",
    glyphs_between: " Glyphen, wie es ist, und ",
    glyphs_after: " mit Ligaturen: Die Paare, die die Schrift verbindet, sind je ein Glyph.",
    kerned_before: "Das letzte Muster ist mit Unterschneidung ",
    kerned_after: " Punkt schmaler gezeichnet und wird auch so gemessen, also ist die Zeile so breit, wie sie aussieht.",
    numbered_before: "Spectral enthält ",
    numbered_between: " Glyphen, und kein Zeichen führt zu ",
    numbered_then: " davon: die Formen, die ein Merkmal oder eine Sprache an die Stelle eines Buchstabens setzt. Die ersten ",
    numbered_after: ", über ihre Nummer in der Datei verlangt und auf ihre gemessene Breite zentriert:",
    back_title: "Ein Merkmal wieder ausgeschaltet, ein Glyph über seine Nummer",
    back_why: "Ligaturen und Unterschneidung werden zusammen eingeschaltet, dann wird nur die Ligatur wieder ausgeschaltet: Die Buchstaben trennen sich wieder, und die Paare bleiben unterschnitten.",
    both: "beide",
    without: "ohne liga",
    by_number: "Der Glyph, den der Schnitt fi nennt, über seine Nummer verlangt und als Stellvertreter der zwei Buchstaben angegeben, die er verbindet, sodass der Text als diese zwei Buchstaben herauskopiert wird:",
    fraction_heading: "Brüche",
    fraction_explanation: "Ziffern links und rechts eines Schrägstrichs, gesetzt als Zähler und Nenner um einen Bruchstrich.",
};

/// 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),
];

/// The left edge of everything on the page.
const X: f64 = 60.0;

/// The width every block is broken to.
const ROOM: f64 = 475.0;

/// The baseline the title sits on.
const TITLE_TOP: f64 = 780.0;

/// The top of the block that says what the page shows.
const WHY_TOP: f64 = 748.0;

/// The top of each of the five samples, in the order they are drawn.
const TOPS: [f64; 5] = [650.0, 550.0, 450.0, 350.0, 250.0];

/// How far the line that explains a feature sits below its heading.
const EXPLAIN_DROP: f64 = 16.0;

/// How far the setting with nothing turned on sits below the heading.
const OFF_DROP: f64 = 42.0;

/// How far the setting with the feature turned on sits below the heading.
const ON_DROP: f64 = 68.0;

/// Where the word beside a setting is written, and where the sample begins.
const LABEL_X: f64 = X;
const SAMPLE_X: f64 = X + 46.0;

/// The size the samples are set in.
const SAMPLE_SIZE: f64 = 18.0;

/// The size the headings are set in.
const HEADING_SIZE: f64 = 12.0;

/// The size everything that is not a heading or a sample is set in.
const SMALL: f64 = 9.0;

/// The top of the two closing lines.
const CLOSING_TOP: f64 = 150.0;

/// How far the second closing line sits below the first.
const CLOSING_DROP: f64 = 30.0;

/// How far the closing line over the glyphs drawn by number sits below the
/// first.
const NUMBERED_DROP: f64 = 60.0;

/// The baseline of the glyphs drawn by number.
const NUMBERED_BASELINE: f64 = 28.0;

/// How many of the glyphs no character leads to are drawn.
const NUMBERED_SHOWN: usize = 12;

/// The glyphs of `font` no character of its map leads to, by number, leaving
/// out the empty box at zero.
fn unreached(font: &Font) -> Vec<u16> {
    let reached: BTreeSet<GlyphId> = font.coverage().map(|(_, glyph)| glyph).collect();
    let held = u16::try_from(font.glyph_count()).unwrap_or(u16::MAX);
    (1..held)
        .filter(|number| !reached.contains(&GlyphId::new(*number)))
        .collect()
}

/// Draws one line of text at `size`, left-aligned at `x`, with its baseline
/// under `top`.
fn line(
    c: &mut Content,
    handle: &FontHandle,
    text: &str,
    size: f64,
    x: f64,
    top: f64,
) -> hqf_pdf::Result<()> {
    let flow = TextFlow::new(handle, size);
    let lines = flow.break_lines(text, ROOM);
    c.begin_text();
    flow.draw(c, &lines, x, top, ROOM)?;
    c.end_text();
    Ok(())
}

/// The name the face gives the glyph joining `f` and `i`.
const JOINED: &str = "fi";

/// The tops of the second page's two settings, and of the line over the glyph
/// drawn by its number, and that glyph's baseline.
const BACK_TOPS: [f64; 2] = [680.0, 650.0];
const BY_NUMBER_TOP: f64 = 610.0;
const BY_NUMBER_BASELINE: f64 = 550.0;

/// The top of the fraction sample on the second page.
const FRACTION_TOP: f64 = 480.0;

/// The fraction sample, written the same way in every language.
const FRACTIONS: &str = "1/2 3/4 15/16";

/// The second page: the ligature sample with ligatures and kerning, then with
/// ligatures turned back off, the glyph numbered `joined` stated to stand for
/// the two letters it joins, and the fraction sample without and with the
/// `frac` feature.
fn back_page(
    plain: &FontHandle,
    words: &Words,
    joined: Option<u16>,
) -> Result<Page, hqf_pdf::Error> {
    let mut c = Content::new();
    line(&mut c, plain, words.back_title, 18.0, X, TITLE_TOP)?;
    let why = TextFlow::new(plain, 10.0).leading(14.0);
    let why_lines = why.break_lines(words.back_why, ROOM);
    c.begin_text();
    why.draw(&mut c, &why_lines, X, WHY_TOP, ROOM)?;
    c.end_text();

    let both = plain.clone().with_ligatures().kerning();
    let apart = both.clone().without_feature(*b"liga");
    for ((label, handle), top) in [(words.both, &both), (words.without, &apart)]
        .into_iter()
        .zip(BACK_TOPS)
    {
        line(&mut c, plain, label, SMALL, LABEL_X, top)?;
        line(&mut c, handle, words.samples[0], SAMPLE_SIZE, SAMPLE_X, top)?;
    }

    if let Some(number) = joined {
        let over = TextFlow::new(plain, 10.0).leading(14.0);
        let over_lines = over.break_lines(words.by_number, ROOM);
        c.begin_text();
        over.draw(&mut c, &over_lines, X, BY_NUMBER_TOP, ROOM)?;
        c.end_text();
        let glyph = plain.numbered_glyph_standing_for(number, JOINED)?;
        c.begin_text();
        c.set_font(plain, SAMPLE_SIZE)?
            .text_origin(X, BY_NUMBER_BASELINE)?;
        c.show_glyphs(&[glyph]);
        c.end_text();
    }

    line(
        &mut c,
        plain,
        words.fraction_heading,
        HEADING_SIZE,
        X,
        FRACTION_TOP,
    )?;
    line(
        &mut c,
        plain,
        words.fraction_explanation,
        SMALL,
        X,
        FRACTION_TOP - EXPLAIN_DROP,
    )?;
    let fractions = plain.clone().with_feature(*b"frac");
    for (drop, label, handle) in [
        (OFF_DROP, words.off, plain),
        (ON_DROP, words.on, &fractions),
    ] {
        line(&mut c, plain, label, SMALL, LABEL_X, FRACTION_TOP - drop)?;
        line(
            &mut c,
            handle,
            FRACTIONS,
            SAMPLE_SIZE,
            SAMPLE_X,
            FRACTION_TOP - drop,
        )?;
    }

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

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 out = env::args()
        .nth(1)
        .unwrap_or_else(|| language.file_name(&out::default_path("typography")));

    let bytes = fs::read(font_dir().join("Spectral-Regular.ttf"))?;

    let mut doc = Document::new();
    doc.set_license(licence::licensed());
    let font = Font::parse(bytes)?;
    let held = font.glyph_count();
    let unreached = unreached(&font);
    let ligature = font.glyph_named(JOINED).map(GlyphId::index);
    let plain = doc.add_font(font);

    // One handle per feature, each turned on alone: the two settings of a
    // sample then differ in exactly one thing.
    let with: [FontHandle; 5] = [
        plain.clone().with_ligatures(),
        plain.clone().with_small_capitals(),
        plain.clone().with_oldstyle_figures(),
        plain
            .clone()
            .with_feature(*b"locl")
            .with_language_system(*b"cyrl", Some(*b"SRB ")),
        plain.clone().kerning(),
    ];

    let mut c = Content::new();

    line(&mut c, &plain, words.title, 18.0, X, TITLE_TOP)?;

    let why = TextFlow::new(&plain, 10.0).leading(14.0);
    let why_lines = why.break_lines(words.why, ROOM);
    c.begin_text();
    why.draw(&mut c, &why_lines, X, WHY_TOP, ROOM)?;
    c.end_text();

    for (index, top) in TOPS.iter().enumerate() {
        line(&mut c, &plain, words.headings[index], HEADING_SIZE, X, *top)?;
        line(
            &mut c,
            &plain,
            words.explanations[index],
            SMALL,
            X,
            top - EXPLAIN_DROP,
        )?;

        for (drop, label, handle) in [
            (OFF_DROP, words.off, &plain),
            (ON_DROP, words.on, &with[index]),
        ] {
            line(&mut c, &plain, label, SMALL, LABEL_X, top - drop)?;
            line(
                &mut c,
                handle,
                words.samples[index],
                SAMPLE_SIZE,
                SAMPLE_X,
                top - drop,
            )?;
        }
    }

    // The closing lines are read off the fonts, not written down.
    let apart = plain
        .runs(words.samples[0])
        .iter()
        .map(|run| run.glyphs().len())
        .sum();
    let joined = with[0]
        .runs(words.samples[0])
        .iter()
        .map(|run| run.glyphs().len())
        .sum();
    let saved = plain.measure(words.samples[4], SAMPLE_SIZE)
        - with[4].measure(words.samples[4], SAMPLE_SIZE);

    let shown = &unreached[..NUMBERED_SHOWN.min(unreached.len())];

    let closing = TextFlow::new(&plain, 10.0).leading(14.0);
    for (drop, text) in [
        (0.0, words.glyph_line(apart, joined)),
        (CLOSING_DROP, words.kerned_line(saved)),
        (
            NUMBERED_DROP,
            words.numbered_line(held, unreached.len(), shown.len()),
        ),
    ] {
        let lines = closing.break_lines(&text, ROOM);
        c.begin_text();
        closing.draw(&mut c, &lines, X, CLOSING_TOP - drop, ROOM)?;
        c.end_text();
    }

    // Drawn by their number, since no character leads to them.
    let row = plain.numbered_glyphs(shown.iter().copied())?;
    let row_width = plain.measure_glyphs(&row, SAMPLE_SIZE);
    c.begin_text();
    c.set_font(&plain, SAMPLE_SIZE)?
        .text_origin(X + (ROOM - row_width) / 2.0, NUMBERED_BASELINE)?;
    c.show_glyphs(&row);
    c.end_text();

    let mut page = Page::a4();
    page.content = c.into_bytes();
    doc.add_page(page)?;
    doc.add_page(back_page(&plain, words, ligature)?)?;

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

#[cfg(test)]
mod tests {
    use super::{
        CLOSING_DROP, CLOSING_TOP, FRACTIONS, HEADING_SIZE, NUMBERED_BASELINE, NUMBERED_DROP,
        NUMBERED_SHOWN, ON_DROP, ROOM, SAMPLE_SIZE, SAMPLE_X, SMALL, TOPS, WORDS, X, language,
        unreached,
    };
    use hqf_pdf::{Document, Font, FontHandle, TextFlow};

    /// The lines two languages are allowed to write the same way: the figures
    /// are figures in both, the Serbian words are Serbian in both, and
    /// ligatures are called ligatures in both.
    const SPARED: [&str; 9] = [
        "\"1 234 567 890\"",
        "\"пут, град, беседа\"",
        "\"Ligatures\"",
        // Written the same way in Italian.
        r#"off: "no""#,
        // Written the same way in Portuguese.
        r#""Ligaduras""#,
        // Written the same way in Dutch.
        r#""Kerning""#,
        r#""Ligaturen""#,
        r#"both: "beide""#,
        // Written the same way in French.
        r#"fraction_heading: "Fractions""#,
    ];

    /// The room a sample has, from where it begins to the edge of the block.
    const SAMPLE_ROOM: f64 = ROOM - SAMPLE_X + X;

    /// The committed font, added to a document of its own.
    fn font() -> (Document, FontHandle) {
        let bytes = std::fs::read(super::font_dir().join("Spectral-Regular.ttf"))
            .expect("the font is committed");
        let mut doc = Document::new();
        let handle = doc.add_font(Font::parse(bytes).expect("the font parses"));
        (doc, handle)
    }

    #[test]
    fn every_language_draws_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:?}"
        );
    }

    /// The page rests on the font joining letters in the first sample, drawing
    /// Serbian forms in the fourth and kerning pairs in the last: a page whose
    /// two settings drew the same thing would show nothing at all.
    #[test]
    fn every_language_gives_the_two_settings_something_to_differ_about() {
        let (_doc, plain) = font();
        let joined = plain.clone().with_ligatures();
        let kerned = plain.clone().kerning();
        let local = plain
            .clone()
            .with_feature(*b"locl")
            .with_language_system(*b"cyrl", Some(*b"SRB "));

        for (code, words) in WORDS {
            let ligature_sample = words.samples[0];
            assert!(
                joined.runs(ligature_sample)[0].glyphs().len()
                    < plain.runs(ligature_sample)[0].glyphs().len(),
                "{code:?} writes a first sample the font joins nothing in"
            );

            let kerning_sample = words.samples[4];
            assert!(
                kerned.measure(kerning_sample, SAMPLE_SIZE)
                    < plain.measure(kerning_sample, SAMPLE_SIZE),
                "{code:?} writes a last sample the font kerns nothing in"
            );

            let figures = words.samples[2];
            assert_ne!(
                plain.clone().with_oldstyle_figures().runs(figures)[0].glyphs(),
                plain.runs(figures)[0].glyphs(),
                "{code:?} writes a sample of figures the font hangs none of"
            );

            let serbian = words.samples[3];
            assert_ne!(
                local.runs(serbian)[0].glyphs(),
                plain.runs(serbian)[0].glyphs(),
                "{code:?} writes a Serbian sample the font draws no local form in"
            );
            assert_eq!(
                plain.clone().with_feature(*b"locl").runs(serbian)[0].glyphs(),
                plain.runs(serbian)[0].glyphs(),
                "the forms are Serbian ones, not the default language's"
            );
        }
    }

    /// The fraction sample rests on the font drawing each figure after a slash
    /// as a denominator, which only the glyphs around it say.
    #[test]
    fn the_fraction_sample_draws_its_denominators_after_the_bar() {
        let (_doc, plain) = font();
        let fractions = plain.with_feature(*b"frac");
        let drawn = fractions.glyphs(FRACTIONS);
        let bytes = std::fs::read(super::font_dir().join("Spectral-Regular.ttf"))
            .expect("the font is committed");
        let face = Font::parse(bytes).expect("the font parses");
        let named = |name| face.glyph_named(name).expect("the face names it");

        assert_eq!(drawn[0], named("one.sups"));
        assert_eq!(drawn[2], named("two.sinf"), "after the bar");
        assert_eq!(drawn[4], named("three.sups"), "after the space");
    }

    /// The second sample rests on the font having a small capital for the
    /// letters it holds, and on the capitals being left where they stand.
    #[test]
    fn the_small_capitals_sample_is_drawn_with_small_capitals() {
        let (_doc, plain) = font();
        let asked = plain.clone().with_small_capitals();

        for (code, words) in WORDS {
            let sample = words.samples[1];
            assert_ne!(
                asked.runs(sample)[0].glyphs(),
                plain.runs(sample)[0].glyphs(),
                "{code:?} writes a second sample the font has no small capital for"
            );
            assert!(
                sample.chars().all(|ch| !ch.is_uppercase()),
                "{code:?} writes a capital into the sample, which the setting leaves alone"
            );
        }
    }

    /// Nothing the page draws runs off the paper.
    #[test]
    fn every_language_writes_blocks_no_wider_than_the_room_they_have() {
        let (_doc, plain) = font();

        for (code, words) in WORDS {
            for (text, size, room) in [
                (words.title, 18.0, ROOM),
                (words.off, SMALL, ROOM),
                (words.on, SMALL, ROOM),
                (words.fraction_heading, HEADING_SIZE, ROOM),
                (words.fraction_explanation, SMALL, ROOM),
                (FRACTIONS, SAMPLE_SIZE, SAMPLE_ROOM),
            ] {
                let width = plain.measure(text, size);
                assert!(
                    width <= room,
                    "{code:?} writes {width} points in {room}: {text}"
                );
            }

            for index in 0..TOPS.len() {
                let heading = plain.measure(words.headings[index], HEADING_SIZE);
                assert!(heading <= ROOM, "{code:?} writes a heading of {heading}");

                let explanation = plain.measure(words.explanations[index], SMALL);
                assert!(
                    explanation <= ROOM,
                    "{code:?} writes an explanation of {explanation} points"
                );

                let sample = plain.measure(words.samples[index], SAMPLE_SIZE);
                assert!(
                    sample <= SAMPLE_ROOM,
                    "{code:?} writes a sample of {sample} points in {SAMPLE_ROOM}"
                );
            }
        }
    }

    /// The samples stand clear of one another, and the last stands clear of the
    /// closing lines.
    #[test]
    fn nothing_the_page_draws_lands_on_what_comes_after_it() {
        let (_doc, plain) = font();
        let flow = TextFlow::new(&plain, SAMPLE_SIZE);

        for (code, words) in WORDS {
            for (index, top) in TOPS.iter().enumerate() {
                let lines = flow.break_lines(words.samples[index], ROOM);
                let bottom = top - ON_DROP - flow.height(&lines);
                let next = TOPS.get(index + 1).copied().unwrap_or(CLOSING_TOP);
                assert!(
                    bottom > next,
                    "{code:?} sets its sample {index} down to {bottom}, over {next}"
                );
            }

            let closing = TextFlow::new(&plain, 10.0).leading(14.0);
            let lines = closing.break_lines(&words.kerned_line(9.9), ROOM);
            let bottom = CLOSING_TOP - CLOSING_DROP - closing.height(&lines);
            assert!(
                bottom > CLOSING_TOP - NUMBERED_DROP,
                "{code:?} sets its kerning line down onto the next"
            );

            let numbered = words.numbered_line(1480, 601, NUMBERED_SHOWN);
            let lines = closing.break_lines(&numbered, ROOM);
            let bottom = CLOSING_TOP - NUMBERED_DROP - closing.height(&lines);
            let row_top = NUMBERED_BASELINE + plain.ascent(SAMPLE_SIZE);
            assert!(
                bottom > row_top,
                "{code:?} sets its last line down to {bottom}, over the glyphs at {row_top}"
            );
        }
    }

    /// The last row holds glyphs no character leads to, all of them the face
    /// holds, and it fits the block it is centred in.
    #[test]
    fn the_glyphs_drawn_by_number_are_ones_no_character_leads_to() {
        let bytes = std::fs::read(super::font_dir().join("Spectral-Regular.ttf"))
            .expect("the font is committed");
        let font = Font::parse(bytes).expect("the font parses");
        let numbers = unreached(&font);
        let mut doc = Document::new();
        let plain = doc.add_font(font.clone());

        assert!(
            numbers.len() >= NUMBERED_SHOWN,
            "the face has twelve to show"
        );
        for (_, glyph) in font.coverage() {
            assert!(!numbers.contains(&glyph.index()));
        }
        let row = plain
            .numbered_glyphs(numbers[..NUMBERED_SHOWN].iter().copied())
            .expect("the face holds them");
        let width = plain.measure_glyphs(&row, SAMPLE_SIZE);
        assert!(width > 0.0 && width <= ROOM, "a row of {width} points");
    }
}