"""Sets a page in a font read from a Type 1 program, and states what it says of itself.

The Python twin of the `write_type_one_font` example in Rust. Adobe's first font format
is not a table anybody can look a glyph up in: it is a PostScript program whose glyphs
are ciphered, and every measurement below is read out of it rather than written in by
hand. The program is carried into the file cut down to the glyphs the page draws.

The face is `HqfStroke`, HQF Development's own: a stroke face built by
`scripts/build_type1_sample.py`, committed beside the fonts the tests run against.

A second page reads the program glyph by glyph: its matrix, its slant, whether its
glyphs share one width, where its codes come from, and for each character of one line
the code that reaches it, the glyph's name and its advance. The same line is then set a
second time with a metrics file written over the program that widens every glyph by a
quarter, and the page says how long each line runs.

The face is measured on the text metrics file committed beside it, `HqfStroke.afm`, and
a third page sets one line twice: without kerning, then kerned on the pairs that file
states.

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

Usage: python examples/write_type_one_font.py [out.pdf] [face.pfb [face.afm]]
       HQF_PDF_LANG=fr python examples/write_type_one_font.py

A face named without a metrics file is measured on its program alone.
"""

from __future__ import annotations

from dataclasses import dataclass
from pathlib import Path

import _language
import _licence
import _out

import hqf_pdf


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

    # The page's title.
    title: str
    # What the page is about.
    lead: str
    # The label over the three lines set in the face itself.
    shown: str
    # The three lines, in the capitals the face draws.
    samples: tuple[str, str, str]
    # The label over what the program states about itself.
    stated: str
    # The sentence those figures are put into.
    facts: str
    # The label over the weight.
    weight: str
    # The sentence the two weights are put into.
    weighed: str
    # The label over what of the program travels with the page.
    travels: str
    # How the program is cut down to what the page draws.
    carried: str
    # What such a font cannot do.
    caveat: str
    # The second page's heading.
    glyph_by_glyph: str
    # What the program states of its geometry; `{matrix}`, `{slant}`, `{pitch}` and
    # `{codes}` stand for them.
    geometry: str
    # Whether the glyphs share one width: yes, then no.
    pitch: tuple[str, str]
    # Where the codes come from: the published table, then the program's own, `{count}`
    # standing for how many codes it fills.
    codes: tuple[str, str]
    # One character of the line; `{character}`, `{code}`, `{name}` and `{advance}` stand
    # for them.
    reached_by: str
    # What the line set twice shows; `{program}` and `{metrics}` stand for how long it
    # runs each way.
    widened: str
    # The character that separates the whole part of a figure from its tenths.
    decimal: str
    # The label over the line set twice, on the third page.
    kerned: str
    # The line set twice: every pair the metrics file kerns.
    pairs: str
    # What the two settings of that line show.
    kerning: str


# Every pair the metrics file of the default face kerns, one after the other.
PAIRS = "AT AV AW AY FA LT LV LW LY PA TA VA WA YA"


# The page in English.
ENGLISH = Words(
    title="A page set in a Type 1 font",
    lead=(
        "The three lines below are drawn from a font program in Adobe's first format. "
        "It is not a font file with tables to look a glyph up in: it is a PostScript "
        "program, its glyphs ciphered one by one, and the library reads it to find out "
        "which code reaches each of them. Each glyph is set as wide as the text "
        "metrics file that comes with the program says, or, without one, as the "
        "program itself says. The program is then carried into this file cut down to "
        "the glyphs this page draws, and reading software draws them from those very "
        "bytes."
    ),
    shown="Drawn from the program",
    samples=(
        "HQF DEVELOPMENT",
        "ABCDEFGHIJKLM NOPQRSTUVWXYZ 0123456789",
        "A STROKE FACE, READ AND CUT TO SIZE.",
    ),
    stated="What the program states about itself",
    facts=(
        "It calls itself %name% and holds %glyphs% glyphs, of which %reached% are "
        "reached by a code. Every glyph stands in one box, whose corners are %low% and "
        "%high%; a vertical stem is %stem% thousandths of an em thick, and an "
        "underline sits %underline% below the line. Not one of those figures is "
        "written into this page: each is read out of the program."
    ),
    weight="What it weighs",
    weighed=(
        "The program itself is %program% bytes on disk, and a page setting the three "
        "lines above in it weighs %document% bytes, the program cut down to the glyphs "
        "those lines draw. Both figures are measured by the program that created this "
        "page, not written in by hand."
    ),
    travels="What travels with it",
    carried=(
        "Like every other kind of font this library carries, a Type 1 program travels "
        "light: only the glyphs a page actually draws go into the file, so a page that "
        "says one word carries the letters of that word and not a whole alphabet. The "
        "format makes that a piece of work of its own: each glyph is ciphered on its "
        "own inside the program, so the library deciphers the program, rewrites its "
        "list of glyphs to hold only .notdef, the glyphs drawn and the glyphs an "
        "accented letter is built from, and ciphers it again. Each glyph kept is "
        "copied byte for byte, still under its own cipher, and the pieces of outline "
        "several glyphs share are kept whole. The font's description then names every "
        "glyph the file holds. On a face of a few dozen glyphs that saves little; on a "
        "large one it is most of the program: a face of sixty thousand bytes set in "
        "three words carries the glyphs of those three words and not the rest."
    ),
    caveat=(
        "A Type 1 font is a simple font: its codes are one byte, so at most 256 of its "
        "glyphs are reachable at a time and it never sets text down the page. Which "
        "code reaches which glyph is the program's own business — this file repeats "
        "the program's own names for them."
    ),
    glyph_by_glyph="The program, glyph by glyph",
    geometry="Its matrix is [{matrix}]; it slants {slant}°; {pitch}; {codes}.",
    pitch=(
        "every glyph has one width",
        "each glyph has a width of its own",
    ),
    codes=(
        "its codes follow the table the format publishes",
        "it spells out its own codes, {count} of them",
    ),
    reached_by=(
        "{character} is reached by code {code}, the glyph {name}, which advances "
        "{advance}"
    ),
    widened=(
        "The line above is set once from the widths the program states, and once with "
        "a metrics file written over it that widens every glyph by a quarter: at 15 "
        "points it runs {program} points, then {metrics}."
    ),
    decimal=".",
    kerned="Kerned from its metrics file",
    pairs=PAIRS,
    kerning=(
        "The same pairs twice: the first line is set without kerning, the second "
        "kerned on the pairs of the text metrics file the library reads beside the "
        "program. Each pair whose facing sides slant or stand open is brought "
        "together, by as much as a quarter of an em, and the move is written into the "
        "line itself, so every reader draws the letters where they were set."
    ),
)

# The page in French.
FRENCH = Words(
    title="Une page composée dans une police Type 1",
    lead=(
        "Les trois lignes ci-dessous sont tracées par un programme de police écrit "
        "dans le premier format d’Adobe. Ce n’est pas un fichier de police avec des "
        "tables où chercher un glyphe : c’est un programme PostScript, dont chaque "
        "glyphe est chiffré à part, et la bibliothèque le lit pour savoir quel code "
        "mène à chacun. Chaque glyphe prend la largeur que donne le fichier de "
        "métriques texte fourni avec le programme, ou, à défaut, celle que donne le "
        "programme lui-même. Le programme est ensuite incorporé à ce fichier, "
        "réduit aux seuls glyphes de cette page, et le logiciel de lecture les trace "
        "à partir de ces octets mêmes."
    ),
    shown="Tracé par le programme",
    samples=(
        "HQF DEVELOPMENT",
        "ABCDEFGHIJKLM NOPQRSTUVWXYZ 0123456789",
        "UNE POLICE DE TRAITS, LUE PUIS ÉLAGUÉE.",
    ),
    stated="Ce que le programme dit de lui-même",
    facts=(
        "Il s’appelle %name% et contient %glyphs% glyphes, dont %reached% sont "
        "accessibles par un code. Tous les glyphes tiennent dans une même boîte, dont "
        "les coins sont %low% et %high% ; un fût vertical a une épaisseur de %stem% "
        "millièmes de cadratin, et le soulignement se place à %underline% sous la "
        "ligne. Aucun de ces chiffres n’est écrit à la main dans cette page : chacun "
        "est lu dans le programme."
    ),
    weight="Ce que cela pèse",
    weighed=(
        "Le programme lui-même pèse %program% octets sur le disque, et une page où les "
        "trois lignes ci-dessus sont composées dans cette police pèse %document% "
        "octets, y compris le programme réduit à leurs seuls glyphes. Les deux "
        "chiffres sont mesurés par le logiciel qui a créé cette page, pas écrits à la "
        "main."
    ),
    travels="Ce qui l’accompagne",
    carried=(
        "Comme toutes les autres sortes de polices que cette bibliothèque incorpore, "
        "un programme Type 1 voyage léger : seuls les glyphes réellement tracés sur "
        "une page entrent dans le fichier, si bien qu’une page qui contient un mot "
        "emporte les lettres de ce mot, et non un alphabet entier. Le format en fait "
        "un travail à part entière : chaque glyphe y est chiffré séparément, donc la "
        "bibliothèque déchiffre le programme, réécrit sa liste de glyphes pour n’y "
        "garder que .notdef, les glyphes tracés et ceux qui servent à construire une "
        "lettre accentuée, puis le rechiffre. Chaque glyphe gardé est recopié octet "
        "pour octet, toujours sous son propre chiffrement, et les morceaux de contour "
        "que plusieurs glyphes partagent sont conservés entiers. La description de la "
        "police nomme ensuite chaque glyphe que contient le fichier. Sur une police de "
        "quelques dizaines de glyphes, le gain est faible ; sur une grande, c’est "
        "l’essentiel du programme : une police de soixante mille octets employée pour "
        "trois mots n’emporte que les glyphes de ces trois mots."
    ),
    caveat=(
        "Une police Type 1 est une police simple : ses codes tiennent sur un octet, "
        "donc 256 de ses glyphes au plus sont accessibles à la fois, et elle ne "
        "compose jamais à la verticale. Quel code mène à quel glyphe relève du seul "
        "programme — ce fichier reprend les noms que le programme leur donne."
    ),
    glyph_by_glyph="Le programme, glyphe par glyphe",
    geometry="Sa matrice est [{matrix}] ; il penche de {slant}° ; {pitch} ; {codes}.",
    pitch=(
        "tous ses glyphes ont une même chasse",
        "chaque glyphe a sa propre chasse",
    ),
    codes=(
        "ses codes suivent la table que publie le format",
        "il détaille ses propres codes, au nombre de {count}",
    ),
    reached_by=(
        "{character} est atteint par le code {code}, le glyphe {name}, qui avance de "
        "{advance}"
    ),
    widened=(
        "La ligne ci-dessus est composée une fois avec les chasses que donne le "
        "programme, et une fois avec un fichier de métriques posé par-dessus qui "
        "élargit chaque glyphe d’un quart : à 15 points elle mesure {program} points, "
        "puis {metrics}."
    ),
    decimal=",",
    kerned="Crénage lu dans son fichier de métriques",
    pairs=PAIRS,
    kerning=(
        "Les mêmes paires, deux fois : la première ligne est composée sans crénage, la "
        "seconde avec le crénage des paires du fichier de métriques texte que la "
        "bibliothèque lit à côté du programme. Chaque paire dont les côtés en regard "
        "sont obliques ou ouverts est rapprochée, jusqu’à un quart de cadratin, et ce "
        "rapprochement est écrit dans la ligne elle-même : tout logiciel de lecture "
        "trace les lettres là où elles ont été placées."
    ),
)

# The page in Dutch.
DUTCH = Words(
    title="Een pagina gezet in een Type 1-lettertype",
    lead=(
        "De drie regels hieronder zijn getekend vanuit een lettertypeprogramma in het "
        "eerste formaat van Adobe. Het is geen lettertypebestand met tabellen om een "
        "glyph in op te zoeken: het is een PostScript-programma, met zijn glyphs een "
        "voor een versleuteld, en de bibliotheek leest het om te weten welke code "
        "elke glyph bereikt. Elke glyph wordt zo breed gezet als het metriekbestand "
        "bij het programma zegt, of, zonder zo’n bestand, als het programma zelf "
        "zegt. Het programma wordt dan in dit bestand ingesloten, ingekort tot de "
        "glyphs die deze pagina tekent, en leessoftware tekent ze vanuit precies die "
        "bytes."
    ),
    shown="Getekend vanuit het programma",
    samples=(
        "HQF DEVELOPMENT",
        "ABCDEFGHIJKLM NOPQRSTUVWXYZ 0123456789",
        "EEN LIJNLETTER, GELEZEN EN OP MAAT GESNEDEN.",
    ),
    stated="Wat het programma over zichzelf zegt",
    facts=(
        "Het heet %name% en bevat %glyphs% glyphs, waarvan er %reached% door een code "
        "bereikt worden. Elke glyph staat in één kader, met als hoeken %low% en "
        "%high%; een verticale stam is %stem% duizendste em dik, en een onderstreping "
        "ligt %underline% onder de regel. Geen van die getallen staat op deze pagina "
        "geschreven: elk is uit het programma gelezen."
    ),
    weight="Wat het weegt",
    weighed=(
        "Het programma zelf is %program% bytes op schijf, en een pagina die er de "
        "drie regels hierboven in zet, weegt %document% bytes, met het programma "
        "ingekort tot de glyphs die die regels tekenen. Beide getallen zijn gemeten "
        "door het programma dat deze pagina maakte, niet met de hand geschreven."
    ),
    travels="Wat ermee meereist",
    carried=(
        "Zoals elke andere soort lettertype die deze bibliotheek insluit, reist een "
        "Type 1-programma licht: alleen de glyphs die een pagina echt tekent, gaan "
        "het bestand in, dus een pagina die één woord zegt, bevat de letters van dat "
        "woord en geen heel alfabet. Het formaat maakt daar een eigen karwei van: "
        "elke glyph is binnen het programma apart versleuteld, dus ontsleutelt de "
        "bibliotheek het programma, herschrijft de lijst van glyphs zodat die alleen "
        ".notdef bevat, de getekende glyphs en de glyphs waaruit een letter met "
        "accent wordt opgebouwd, en versleutelt het opnieuw. Elke behouden glyph "
        "wordt byte voor byte gekopieerd, nog onder zijn eigen versleuteling, en de "
        "stukken omtrek die meerdere glyphs delen blijven heel. De beschrijving van "
        "het lettertype noemt dan elke glyph die het bestand bevat. Bij een letter "
        "van een paar dozijn glyphs scheelt dat weinig; bij een grote is het het "
        "grootste deel van het programma: een letter van zestigduizend bytes, gezet "
        "in drie woorden, bevat de glyphs van die drie woorden en niet de rest."
    ),
    caveat=(
        "Een Type 1-lettertype is een eenvoudig lettertype: de codes zijn één byte, "
        "dus hoogstens 256 van de glyphs zijn tegelijk bereikbaar en het zet nooit "
        "tekst langs de pagina omlaag. Welke code welke glyph bereikt, is de zaak van "
        "het programma: dit bestand herhaalt de namen die het programma ze zelf geeft."
    ),
    glyph_by_glyph="Het programma, glyph voor glyph",
    geometry="De matrix is [{matrix}]; het helt {slant}°; {pitch}; {codes}.",
    pitch=(
        "elke glyph heeft één breedte",
        "elke glyph heeft een eigen breedte",
    ),
    codes=(
        "de codes volgen de tabel die het formaat publiceert",
        "het schrijft zijn eigen codes uit, {count} stuks",
    ),
    reached_by=(
        "{character} wordt bereikt met code {code}, de glyph {name}, die {advance} "
        "opschuift"
    ),
    widened=(
        "De regel hierboven is één keer gezet met de breedtes die het programma "
        "noemt, en één keer met een metriekbestand erover dat elke glyph een kwart "
        "breder maakt: op 15 punten loopt hij {program} punten, dan {metrics}."
    ),
    decimal=".",
    kerned="Gekerned uit het metriekbestand",
    pairs=PAIRS,
    kerning=(
        "Dezelfde paren twee keer: de eerste regel is gezet zonder kerning, de tweede "
        "met kerning op de paren van het metriekbestand dat de bibliotheek naast het "
        "programma leest. Elk paar waarvan de naar elkaar gekeerde zijden schuin "
        "lopen of open staan, wordt naar elkaar toe geschoven, tot een kwart em, en "
        "de verplaatsing wordt in de regel zelf geschreven, dus tekent elke lezer de "
        "letters waar ze gezet zijn."
    ),
)

# The page in Portuguese.
PORTUGUESE = Words(
    title="Uma página composta num tipo de letra Type 1",
    lead=(
        "As três linhas abaixo são desenhadas a partir de um programa de tipo de "
        "letra no primeiro formato da Adobe. Não é um ficheiro de tipo de letra com "
        "tabelas onde procurar um glifo: é um programa PostScript, com os glifos "
        "cifrados um a um, e a biblioteca lê-o para saber que código chega a cada um. "
        "Cada glifo é composto com a largura que diz o ficheiro de métricas que "
        "acompanha o programa ou, sem ele, a que diz o próprio programa. O programa é "
        "depois levado para este ficheiro reduzido aos glifos que esta página "
        "desenha, e o programa de leitura desenha-os a partir desses mesmos bytes."
    ),
    shown="Desenhado a partir do programa",
    samples=(
        "HQF DEVELOPMENT",
        "ABCDEFGHIJKLM NOPQRSTUVWXYZ 0123456789",
        "UM TIPO DE TRAÇOS, LIDO E CORTADO À MEDIDA.",
    ),
    stated="O que o programa diz de si mesmo",
    facts=(
        "Chama-se %name% e tem %glyphs% glifos, dos quais %reached% são alcançados "
        "por um código. Cada glifo cabe numa caixa, cujos cantos são %low% e %high%; "
        "uma haste vertical tem %stem% milésimos de eme de espessura, e um sublinhado "
        "fica %underline% abaixo da linha. Nenhum destes números está escrito nesta "
        "página: cada um é lido do programa."
    ),
    weight="O que pesa",
    weighed=(
        "O próprio programa ocupa %program% bytes em disco, e uma página que compõe "
        "com ele as três linhas acima pesa %document% bytes, com o programa reduzido "
        "aos glifos que essas linhas desenham. Os dois números são medidos pelo "
        "programa que criou esta página, não escritos à mão."
    ),
    travels="O que viaja com ele",
    carried=(
        "Como qualquer outro tipo de letra que esta biblioteca leva, um programa Type "
        "1 viaja leve: só entram no ficheiro os glifos que uma página desenha de "
        "facto, por isso uma página que diz uma palavra leva as letras dessa palavra "
        "e não um alfabeto inteiro. O formato faz disso um trabalho próprio: cada "
        "glifo é cifrado à parte dentro do programa, por isso a biblioteca decifra o "
        "programa, reescreve a sua lista de glifos para conter só .notdef, os glifos "
        "desenhados e os glifos com que se constrói uma letra acentuada, e volta a "
        "cifrá-lo. Cada glifo mantido é copiado byte a byte, ainda sob a sua própria "
        "cifra, e os pedaços de contorno que vários glifos partilham são mantidos "
        "inteiros. A descrição do tipo de letra nomeia então cada glifo que o "
        "ficheiro contém. Num tipo de umas dezenas de glifos isso poupa pouco; num "
        "grande é quase todo o programa: um tipo de sessenta mil bytes composto em "
        "três palavras leva os glifos dessas três palavras e não o resto."
    ),
    caveat=(
        "Um tipo de letra Type 1 é um tipo simples: os seus códigos são de um byte, "
        "por isso no máximo 256 dos seus glifos são alcançáveis de cada vez e nunca "
        "compõe texto para baixo. Que código chega a que glifo é assunto do programa: "
        "este ficheiro repete os nomes que o próprio programa lhes dá."
    ),
    glyph_by_glyph="O programa, glifo a glifo",
    geometry="A sua matriz é [{matrix}]; inclina {slant}°; {pitch}; {codes}.",
    pitch=(
        "todos os glifos têm a mesma largura",
        "cada glifo tem uma largura própria",
    ),
    codes=(
        "os seus códigos seguem a tabela que o formato publica",
        "detalha os seus próprios códigos, {count} ao todo",
    ),
    reached_by=(
        "{character} é alcançado pelo código {code}, o glifo {name}, que avança "
        "{advance}"
    ),
    widened=(
        "A linha acima é composta uma vez com as larguras que o programa indica, e "
        "outra com um ficheiro de métricas escrito por cima que alarga cada glifo em "
        "um quarto: a 15 pontos mede {program} pontos, depois {metrics}."
    ),
    decimal=".",
    kerned="Com crenagem do seu ficheiro de métricas",
    pairs=PAIRS,
    kerning=(
        "Os mesmos pares duas vezes: a primeira linha é composta sem crenagem, a "
        "segunda com os pares do ficheiro de métricas que a biblioteca lê ao lado do "
        "programa. Cada par cujos lados virados um para o outro se inclinam ou ficam "
        "abertos é aproximado, até um quarto de eme, e o movimento é escrito na "
        "própria linha, por isso cada leitor desenha as letras onde foram compostas."
    ),
)

# The page in Italian.
ITALIAN = Words(
    title="Una pagina composta in un font Type 1",
    lead=(
        "Le tre righe qui sotto sono disegnate da un programma di font nel primo "
        "formato di Adobe. Non è un file di font con tabelle in cui cercare un glifo: "
        "è un programma PostScript, con i glifi cifrati uno per uno, e la libreria lo "
        "legge per sapere quale codice raggiunge ciascuno. Ogni glifo è composto "
        "largo quanto dice il file di metriche che accompagna il programma o, senza, "
        "quanto dice il programma stesso. Il programma è poi portato in questo file "
        "ridotto ai glifi che questa pagina disegna, e il programma di lettura li "
        "disegna da quegli stessi byte."
    ),
    shown="Disegnato dal programma",
    samples=(
        "HQF DEVELOPMENT",
        "ABCDEFGHIJKLM NOPQRSTUVWXYZ 0123456789",
        "UN CARATTERE A TRATTI, LETTO E RIDOTTO.",
    ),
    stated="Cosa dice di sé il programma",
    facts=(
        "Si chiama %name% e contiene %glyphs% glifi, di cui %reached% raggiunti da un "
        "codice. Ogni glifo sta in un riquadro, i cui angoli sono %low% e %high%; "
        "un’asta verticale è spessa %stem% millesimi di em, e una sottolineatura sta "
        "%underline% sotto la riga. Nessuna di queste cifre è scritta in questa "
        "pagina: ciascuna è letta dal programma."
    ),
    weight="Quanto pesa",
    weighed=(
        "Il programma stesso occupa %program% byte su disco, e una pagina che vi "
        "compone le tre righe qui sopra pesa %document% byte, con il programma "
        "ridotto ai glifi che quelle righe disegnano. Entrambe le cifre sono misurate "
        "dal programma che ha creato questa pagina, non scritte a mano."
    ),
    travels="Cosa viaggia con esso",
    carried=(
        "Come ogni altro tipo di font che questa libreria porta, un programma Type 1 "
        "viaggia leggero: nel file entrano solo i glifi che una pagina disegna "
        "davvero, così una pagina che dice una parola porta le lettere di quella "
        "parola e non un alfabeto intero. Il formato ne fa un lavoro a sé: ogni glifo "
        "è cifrato separatamente dentro il programma, così la libreria decifra il "
        "programma, riscrive il suo elenco di glifi perché contenga solo .notdef, i "
        "glifi disegnati e quelli da cui si costruisce una lettera accentata, e lo "
        "cifra di nuovo. Ogni glifo conservato è copiato byte per byte, ancora sotto "
        "il proprio cifrario, e i pezzi di contorno che più glifi condividono sono "
        "conservati interi. La descrizione del font nomina allora ogni glifo che il "
        "file contiene. Su un carattere di qualche decina di glifi si risparmia poco; "
        "su uno grande è quasi tutto il programma: un carattere di sessantamila byte "
        "composto in tre parole porta i glifi di quelle tre parole e non il resto."
    ),
    caveat=(
        "Un font Type 1 è un font semplice: i suoi codici sono di un byte, così al "
        "massimo 256 dei suoi glifi sono raggiungibili insieme e non compone mai "
        "testo verso il basso. Quale codice raggiunge quale glifo è affare del "
        "programma: questo file ripete i nomi che il programma stesso dà loro."
    ),
    glyph_by_glyph="Il programma, glifo per glifo",
    geometry="La sua matrice è [{matrix}]; inclina di {slant}°; {pitch}; {codes}.",
    pitch=(
        "ogni glifo ha la stessa larghezza",
        "ogni glifo ha una larghezza propria",
    ),
    codes=(
        "i suoi codici seguono la tabella che il formato pubblica",
        "elenca i propri codici, {count} in tutto",
    ),
    reached_by=(
        "{character} è raggiunto dal codice {code}, il glifo {name}, che avanza di "
        "{advance}"
    ),
    widened=(
        "La riga qui sopra è composta una volta con le larghezze che indica il "
        "programma, e una volta con un file di metriche scritto sopra che allarga "
        "ogni glifo di un quarto: a 15 punti misura {program} punti, poi {metrics}."
    ),
    decimal=".",
    kerned="Con la crenatura del suo file di metriche",
    pairs=PAIRS,
    kerning=(
        "Le stesse coppie due volte: la prima riga è composta senza crenatura, la "
        "seconda con le coppie del file di metriche che la libreria legge accanto al "
        "programma. Ogni coppia i cui lati affacciati si inclinano o restano aperti è "
        "avvicinata, fino a un quarto di em, e lo spostamento è scritto nella riga "
        "stessa, così ogni programma di lettura disegna le lettere dove sono state "
        "composte."
    ),
)

# The page in Spanish.
SPANISH = Words(
    title="Una página compuesta en una fuente Type 1",
    lead=(
        "Las tres líneas de abajo se dibujan con un programa de fuente en el primer "
        "formato de Adobe. No es un archivo de fuente con tablas en que buscar un "
        "glifo: es un programa PostScript, con sus glifos cifrados uno a uno, y la "
        "biblioteca lo lee para saber qué código lleva a cada uno. Cada glifo se "
        "compone con el ancho que dice el archivo de métricas que acompaña al "
        "programa o, sin él, el que dice el propio programa. Luego el programa se "
        "lleva a este archivo recortado a los glifos que dibuja esta página, y el "
        "programa de lectura los dibuja con esos mismos bytes."
    ),
    shown="Dibujado con el programa",
    samples=(
        "HQF DEVELOPMENT",
        "ABCDEFGHIJKLM NOPQRSTUVWXYZ 0123456789",
        "UNA FUENTE DE TRAZOS, LEÍDA Y RECORTADA.",
    ),
    stated="Lo que el programa dice de sí mismo",
    facts=(
        "Se llama %name% y tiene %glyphs% glifos, de los que %reached% son accesibles "
        "por un código. Cada glifo cabe en una caja, cuyas esquinas son %low% y "
        "%high%; un fuste vertical tiene %stem% milésimas de eme de grosor, y un "
        "subrayado va %underline% bajo la línea. Ninguna de esas cifras se escribe en "
        "esta página: cada una se lee del programa."
    ),
    weight="Lo que pesa",
    weighed=(
        "El programa mismo ocupa %program% bytes en disco, y una página que compone "
        "con él las tres líneas de arriba pesa %document% bytes, con el programa "
        "recortado a los glifos que dibujan esas líneas. Las dos cifras las mide el "
        "programa que creó esta página, no se escriben a mano."
    ),
    travels="Lo que viaja con ella",
    carried=(
        "Como cualquier otro tipo de fuente que lleva esta biblioteca, un programa "
        "Type 1 viaja ligero: solo los glifos que una página dibuja de verdad entran "
        "en el archivo, así que una página que dice una palabra lleva las letras de "
        "esa palabra y no un alfabeto entero. El formato hace de eso un trabajo "
        "propio: cada glifo va cifrado por separado dentro del programa, así que la "
        "biblioteca descifra el programa, reescribe su lista de glifos para que solo "
        "tenga .notdef, los glifos dibujados y los glifos con que se construye una "
        "letra acentuada, y lo vuelve a cifrar. Cada glifo conservado se copia byte a "
        "byte, aún bajo su propio cifrado, y los trozos de contorno que comparten "
        "varios glifos se conservan enteros. La descripción de la fuente nombra "
        "entonces cada glifo que tiene el archivo. En una fuente de unas decenas de "
        "glifos eso ahorra poco; en una grande es casi todo el programa: una fuente "
        "de sesenta mil bytes compuesta en tres palabras lleva los glifos de esas "
        "tres palabras y no el resto."
    ),
    caveat=(
        "Una fuente Type 1 es una fuente simple: sus códigos son de un byte, así que "
        "como mucho 256 de sus glifos son accesibles a la vez y nunca compone texto "
        "hacia abajo. Qué código lleva a qué glifo es cosa del programa: este archivo "
        "repite los nombres que el propio programa les da."
    ),
    glyph_by_glyph="El programa, glifo a glifo",
    geometry="Su matriz es [{matrix}]; se inclina {slant}°; {pitch}; {codes}.",
    pitch=(
        "todos los glifos tienen un mismo ancho",
        "cada glifo tiene su propio ancho",
    ),
    codes=(
        "sus códigos siguen la tabla que publica el formato",
        "detalla sus propios códigos, {count} en total",
    ),
    reached_by=(
        "{character} se alcanza con el código {code}, el glifo {name}, que avanza "
        "{advance}"
    ),
    widened=(
        "La línea de arriba se compone una vez con los anchos que indica el programa, "
        "y otra con un archivo de métricas escrito encima que ensancha cada glifo un "
        "cuarto: a 15 puntos mide {program} puntos, luego {metrics}."
    ),
    decimal=".",
    kerned="Con interletraje de su archivo de métricas",
    pairs=PAIRS,
    kerning=(
        "Los mismos pares dos veces: la primera línea se compone sin interletraje, la "
        "segunda con los pares del archivo de métricas que la biblioteca lee junto al "
        "programa. Cada par cuyos lados enfrentados se inclinan o quedan abiertos se "
        "acerca, hasta un cuarto de eme, y el movimiento se escribe en la propia "
        "línea, así que todo programa de lectura dibuja las letras donde se "
        "compusieron."
    ),
)

# The page in German.
GERMAN = Words(
    title="Eine in einer Type-1-Schrift gesetzte Seite",
    lead=(
        "Die drei Zeilen unten sind aus einem Schriftprogramm in Adobes erstem Format "
        "gezeichnet. Es ist keine Schriftdatei mit Tabellen, in denen man einen "
        "Glyphen nachschlägt: Es ist ein PostScript-Programm, seine Glyphen einzeln "
        "verschlüsselt, und die Bibliothek liest es, um herauszufinden, welcher Code "
        "jeden von ihnen erreicht. Jeder Glyph wird so breit gesetzt, wie die zum "
        "Programm gehörende Metrikdatei sagt, oder, ohne sie, wie das Programm selbst "
        "sagt. Das Programm wird dann, auf die Glyphen dieser Seite gekürzt, in diese "
        "Datei mitgenommen, und die Lesesoftware zeichnet sie aus genau diesen Bytes."
    ),
    shown="Aus dem Programm gezeichnet",
    samples=(
        "HQF DEVELOPMENT",
        "ABCDEFGHIJKLM NOPQRSTUVWXYZ 0123456789",
        "EIN STRICHSCHNITT, GELESEN UND ZUGESCHNITTEN.",
    ),
    stated="Was das Programm über sich angibt",
    facts=(
        "Es nennt sich %name% und enthält %glyphs% Glyphen, von denen %reached% über "
        "einen Code erreichbar sind. Jeder Glyph steht in einem Rahmen mit den Ecken "
        "%low% und %high%; ein senkrechter Stamm ist %stem% Tausendstel Geviert dick, "
        "und eine Unterstreichung liegt %underline% unter der Zeile. Keine dieser "
        "Zahlen ist in diese Seite geschrieben: Jede wird aus dem Programm gelesen."
    ),
    weight="Was es wiegt",
    weighed=(
        "Das Programm selbst hat %program% Bytes auf der Platte, und eine Seite, die "
        "die drei Zeilen oben darin setzt, wiegt %document% Bytes, das Programm auf "
        "die Glyphen dieser Zeilen gekürzt. Beide Zahlen misst das Programm, das "
        "diese Seite erstellt hat, keine ist von Hand eingetragen."
    ),
    travels="Was mitreist",
    carried=(
        "Wie jede andere Schriftart, die diese Bibliothek mitführt, reist ein "
        "Type-1-Programm mit leichtem Gepäck: Nur die Glyphen, die eine Seite "
        "tatsächlich zeichnet, kommen in die Datei, also trägt eine Seite, die ein "
        "Wort sagt, die Buchstaben dieses Worts und nicht ein ganzes Alphabet. Das "
        "Format macht daraus eine eigene Arbeit: Jeder Glyph ist im Programm einzeln "
        "verschlüsselt, also entschlüsselt die Bibliothek das Programm, schreibt "
        "seine Glyphenliste so um, dass sie nur .notdef, die gezeichneten Glyphen und "
        "die Glyphen enthält, aus denen ein Buchstabe mit Akzent gebaut ist, und "
        "verschlüsselt es wieder. Jeder behaltene Glyph wird Byte für Byte kopiert, "
        "noch unter seiner eigenen Verschlüsselung, und die Konturteile, die mehrere "
        "Glyphen teilen, bleiben ganz. Die Beschreibung der Schrift nennt dann jeden "
        "Glyphen, den die Datei enthält. Bei einem Schnitt von ein paar Dutzend "
        "Glyphen spart das wenig; bei einem großen ist es der größte Teil des "
        "Programms: Ein Schnitt von sechzigtausend Bytes, in drei Wörtern gesetzt, "
        "trägt die Glyphen dieser drei Wörter und nicht den Rest."
    ),
    caveat=(
        "Eine Type-1-Schrift ist eine einfache Schrift: Ihre Codes sind ein Byte, "
        "also sind höchstens 256 ihrer Glyphen zugleich erreichbar, und sie setzt nie "
        "senkrecht. Welcher Code welchen Glyphen erreicht, ist Sache des Programms — "
        "diese Datei wiederholt die eigenen Namen, die das Programm ihnen gibt."
    ),
    glyph_by_glyph="Das Programm, Glyph für Glyph",
    geometry="Seine Matrix ist [{matrix}]; es neigt sich {slant}°; {pitch}; {codes}.",
    pitch=(
        "alle Glyphen haben dieselbe Breite",
        "jeder Glyph hat seine eigene Breite",
    ),
    codes=(
        "seine Codes folgen der Tabelle, die das Format veröffentlicht",
        "es schreibt seine eigenen Codes aus, {count} davon",
    ),
    reached_by=(
        "{character} erreicht über Code {code} den Glyphen {name}, der um {advance} "
        "vorrückt"
    ),
    widened=(
        "Die Zeile oben ist einmal nach den Breiten gesetzt, die das Programm angibt, "
        "und einmal mit einer darübergelegten Metrikdatei, die jeden Glyphen um ein "
        "Viertel verbreitert: bei 15 Punkt läuft sie {program} Punkt, dann {metrics}."
    ),
    decimal=".",
    kerned="Nach seiner Metrikdatei unterschnitten",
    pairs=PAIRS,
    kerning=(
        "Dieselben Paare zweimal: Die erste Zeile ist ohne Unterschneidung gesetzt, "
        "die zweite nach den Paaren der Metrikdatei unterschnitten, die die "
        "Bibliothek neben dem Programm liest. Jedes Paar, dessen einander zugewandte "
        "Seiten schräg stehen oder offen sind, rückt zusammen, um bis zu ein Viertel "
        "Geviert, und die Verschiebung wird in die Zeile selbst geschrieben, also "
        "zeichnet jeder Leser die Buchstaben dort, wo sie gesetzt wurden."
    ),
)

# Every language the example is written in. A language is added by writing its own set
# of words and naming it here.
WORDS = {_language.ENGLISH: ENGLISH, _language.FRENCH: FRENCH, _language.GERMAN: GERMAN, _language.SPANISH: SPANISH, _language.ITALIAN: ITALIAN, _language.PORTUGUESE: PORTUGUESE, _language.DUTCH: DUTCH}

# The left edge of everything on the page.
LEFT = 72.0

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

# The Type 1 program the page is set in when the caller names none.
DEFAULT_FACE = _out.ROOT / "crates" / "hqf-pdf" / "tests" / "fonts" / "HqfStroke.pfb"

# The text metrics file of the default face, committed beside it.
DEFAULT_METRICS = DEFAULT_FACE.with_suffix(".afm")


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


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


def reached(face: hqf_pdf.Type1Font) -> int:
    """How many of the program's glyphs a code reaches."""
    return sum(1 for code in range(256) if face.character_at(code) is not None)


def corners(face: hqf_pdf.Type1Font) -> tuple[str, str]:
    """The two corners of the box a face states, as the page writes them."""
    llx, lly, urx, ury = face.bbox
    return f"{llx:g} · {lly:g}", f"{urx:g} · {ury:g}"


def weighed(words: Words, face: hqf_pdf.Type1Font) -> int:
    """What a page holding nothing but the three samples weighs.

    It is measured on a page of its own: a page that states its own weight never
    settles, because writing the figure changes the figure.
    """
    doc = hqf_pdf.Document()
    doc.set_license(_licence.licensed())
    handle = doc.add_type1_font(face)

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

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


def build(
    words: Words,
    face: hqf_pdf.Type1Font,
    bare: hqf_pdf.Type1Font,
    prose: Path,
    program: int,
    document: int,
) -> bytes:
    """Draws the whole page."""
    doc = hqf_pdf.Document()
    doc.set_license(_licence.licensed())
    text = doc.add_font(hqf_pdf.Font.from_path(prose))
    drawn = doc.add_type1_font(face)

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

    top = block(content, text, 12.0, top, words.shown) - 14.0
    for sample in words.samples:
        top = block(content, drawn, 15.0, top, sample) - 10.0

    top -= 14.0
    top = block(content, text, 12.0, top, words.stated) - 10.0
    low, high = corners(face)
    stem = "—" if face.stem_v is None else f"{face.stem_v:g}"
    underline = "—" if face.underline is None else f"{-face.underline[0]:g}"
    stated = (
        words.facts.replace("%name%", face.postscript_name)
        .replace("%glyphs%", str(len(face.glyph_names)))
        .replace("%reached%", str(reached(face)))
        .replace("%low%", low)
        .replace("%high%", high)
        .replace("%stem%", stem)
        .replace("%underline%", underline)
    )
    top = block(content, text, 10.0, top, stated) - 20.0

    top = block(content, text, 12.0, top, words.weight) - 10.0
    said = words.weighed.replace("%program%", spaced(program)).replace(
        "%document%", spaced(document)
    )
    top = block(content, text, 10.0, top, said) - 20.0

    top = block(content, text, 12.0, top, words.travels) - 10.0
    top = block(content, text, 10.0, top, words.carried) - 20.0

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

    page = hqf_pdf.Page.a4()
    page.set_content(content)
    doc.add_page(page)
    doc.add_page(second_page(doc, text, bare, words))

    kerned = drawn.kerning()
    content = hqf_pdf.Content()
    top = 760.0
    top = block(content, text, 12.0, top, words.kerned) - 14.0
    top = block(content, drawn, 15.0, top, words.pairs) - 10.0
    top = block(content, kerned, 15.0, top, words.pairs) - 20.0
    block(content, text, 10.0, top, words.kerning)

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


# The line the second page reads glyph by glyph and sets twice, drawn in the capitals
# and figures the face holds.
LINE = "HQF 2038"


def number(value: float) -> str:
    """`value` written the shortest way that reads back as it, a whole number without
    its point."""
    return str(int(value)) if value == int(value) else repr(value)


def widening(face: hqf_pdf.Type1Font) -> str:
    """A metrics file for `face` that states every glyph a quarter wider than the
    program does."""
    lines = [
        f"C -1 ; WX {(face.advance_of(name) or 0.0) * 1.25:.0f} ; N {name} ;"
        for name in face.glyph_names
    ]
    return (
        f"StartFontMetrics 4.1\nFontName {face.postscript_name}\n"
        f"StartCharMetrics {len(lines)}\n" + "\n".join(lines)
        + "\nEndCharMetrics\nEndFontMetrics\n"
    )


def second_page(doc, text, face, words: Words) -> hqf_pdf.Page:
    """The second page: the program's geometry, the line read glyph by glyph, and the
    line set twice, the second time under a metrics file that widens it."""
    content = hqf_pdf.Content()
    top = 760.0
    top = block(content, text, 18.0, top, words.glyph_by_glyph) - 14.0

    matrix = " ".join(number(entry) for entry in face.matrix)
    if face.names_the_published_encoding:
        codes = words.codes[0]
    else:
        codes = words.codes[1].replace("{count}", str(len(face.encoding or {})))
    geometry = (
        words.geometry.replace("{matrix}", matrix)
        .replace("{slant}", number(face.italic_angle))
        .replace("{pitch}", words.pitch[0 if face.fixed_pitch else 1])
        .replace("{codes}", codes)
    )
    top = block(content, text, 10.0, top, geometry) - 12.0

    for character in LINE.replace(" ", ""):
        code = face.code_of(character)
        if code is None:
            continue
        name = face.name_at(code) or ""
        advance = face.advance_of(name)
        if advance is None:
            advance = face.advance_at(code)
        said = (
            words.reached_by.replace("{character}", character)
            .replace("{code}", str(code))
            .replace("{name}", name)
            .replace("{advance}", number(advance))
        )
        top = block(content, text, 9.5, top, said) - 2.0

    widened = face.with_metrics(widening(face).encode())
    handles = [doc.add_type1_font(face), doc.add_type1_font(widened)]
    top -= 16.0
    runs = []
    for handle in handles:
        top = block(content, handle, 15.0, top, LINE) - 8.0
        runs.append(f"{handle.measure(LINE, 15.0):.1f}".replace(".", words.decimal))
    said = words.widened.replace("{program}", runs[0]).replace("{metrics}", runs[1])
    block(content, text, 10.0, top - 8.0, said)

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


def main() -> None:
    """Writes the page."""
    language = _language.from_environment()
    words = _language.words_of(WORDS, language)

    # A named file is written as named; the default one carries the language, so the two
    # languages do not overwrite each other in `tmp/`.
    out = _out.output_path(
        Path(_language.file_name("type_one_font.pdf", language)).stem
    )
    argv = __import__("sys").argv
    if len(argv) > 2:
        face_path = _out.font_path()
        metrics_path = Path(argv[3]) if len(argv) > 3 else None
    else:
        face_path, metrics_path = DEFAULT_FACE, DEFAULT_METRICS

    data = Path(face_path).read_bytes()
    bare = hqf_pdf.Type1Font.from_bytes(data)
    face = bare
    if metrics_path is not None:
        face = bare.with_metrics(metrics_path.read_bytes())

    document = weighed(words, face)
    drawn = build(words, face, bare, _out.DEFAULT_FONT, len(data), document)

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


if __name__ == "__main__":
    main()
