"""Sets a page whose every letter is one byte in the stream rather than two.

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

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

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

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

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

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

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

from __future__ import annotations

from dataclasses import dataclass
from pathlib import Path

import _language
import _licence
import _out

import hqf_pdf


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

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

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

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


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

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

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

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

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

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

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

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

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

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

# The size the three samples are drawn at.
SAMPLE = 14.0

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

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

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

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

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


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


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


def stream_bytes(handle: hqf_pdf.FontHandle, words: Words) -> int:
    """What the three samples cost in a page stream, drawn through one handle.

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


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


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


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


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

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

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

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


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


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

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

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

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

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


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

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

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

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

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

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

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

    top = block(content, text, 12.0, top, words.page) - 12.0
    top = block(content, cyrillic, SAMPLE, top, PAGED) - 10.0
    said = words.named.replace(
        "%bytes%", first_bytes(page_encoding, PAGED, PRINTED_BYTES)
    )
    top = block(content, text, 10.0, top, said) - 20.0

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

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


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

    # A named file is written as named; the default one carries the language, so the two
    # languages do not overwrite each other in `tmp/`.
    out = _out.output_path(
        Path(_language.file_name("simple_encoding.pdf", language)).stem
    )
    font = hqf_pdf.Font.from_path(_out.font_path())
    encoding = hqf_pdf.SimpleEncoding.fitting(font, words.drawn())
    carried, described = file_weights(font, encoding, words)
    drawn = build(words, font, encoding)

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


if __name__ == "__main__":
    main()
