"""Writes a shelf card that marks its lines the three ways a text can be marked.

The Python twin of the `write_text_ornaments` example in Rust. A rule under the
words, a rule through the letters, a rule over the top: the engine takes all
three from the font rather than from a coordinate, so they follow whatever size
the text is set in. The heading and the mark across the foot each carry a shadow
— the same words set once more, offset and paler, under the words themselves.
The heading's letters are pushed apart and the standfirst's narrowed, both counted
where the lines are broken.

The back of the card sets the rest of what spaces a text: the words of one line
pushed apart, one paragraph set close and then set wide, a figure lifted above the
line to call a note, and the mark's words drawn hollow. Word spacing moves the pen
only at the byte a one-byte encoding gives the space, so the face is added a second
time, reached through such an encoding, for the one line that shows it. Its last
part rules a plain paragraph: one line underlined, then hollow words set on a band
laid down before them.

A third page paints one word each of the four ways a text can be painted without
clipping what follows: filled, outlined, both, and not at all.

Every word is held in `Words`, once per language, and `HQF_PDF_LANG` picks which
set is set. The prices are not words: they stand in one table, shared by both
languages.

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

from __future__ import annotations

from dataclasses import dataclass
from pathlib import Path

import _language
import _licence
import _out

import hqf_pdf

# Where the card's text begins, in points from the left edge of the page.
LEFT = 56.0
# How wide it runs.
WIDTH = 483.0
# The size the heading is set in.
TITLE_SIZE = 32.0
# What every letter of the heading takes on top of the width the face gives it, in
# points.
TITLE_TRACKING = 1.5
# The size the standfirst and the closing note are set in.
NOTE_SIZE = 10.5
# How wide the letters of the standfirst are drawn, as a percentage of the width the
# face gives them.
STANDFIRST_SCALING = 90.0
# The size a line of the shelf is set in.
ROW_SIZE = 13.0
# The rule over the total: its top this far above the baseline, in points, and this
# thick.
TOTAL_RULE = (ROW_SIZE, 1.2)
# The top of the heading's box.
TITLE_TOP = 800.0
# The top of the standfirst.
STANDFIRST_TOP = 748.0
# Where the line under the standfirst is drawn.
HEAD_RULE = 730.0
# The top of the first line of the shelf.
FIRST_ROW = 720.0
# How far apart the lines of the shelf sit.
ROW_STEP = 26.0
# The top of the line that adds the three together.
TOTAL_TOP = 626.0
# The top of the first line of the legend.
LEGEND_TOP = 570.0
# How far apart the lines of the legend sit.
LEGEND_STEP = 24.0
# How far into the box the legend's marked words are set.
LEGEND_INDENT = 24.0
# The top of the closing note.
NOTE_TOP = 470.0
# The box the mark is laid across: its lower-left corner and its size.
MARK_BOX = (LEFT, 100.0, WIDTH, 230.0)

# The size the heading on the back of the card is set in.
BACK_TITLE_SIZE = 22.0
# The top of the heading on the back of the card.
BACK_TITLE_TOP = 800.0
# The top of the words the back of the card opens each of its five parts with.
LABEL_TOPS = (740.0, 648.0, 490.0, 398.0, 288.0)
# How far under its words each part sets what it shows.
LABEL_GAP = 22.0
# What every blank of the spaced line takes on top of a space, in points.
WORD_SPACING = 6.0
# How far apart the two lines of the first part sit.
SAMPLE_STEP = 22.0
# The two leadings the paragraph is set at, close and then wide, in points.
LEADINGS = (12.6, 21.0)
# The gutter between the two settings of the paragraph.
GUTTER = 24.0
# The size the figure calling the note is set in.
CALL_SIZE = 8.0
# How far above the baseline the figure calling the note stands, in points.
CALL_RISE = 5.0
# The figure that calls the note, the same in every language.
CALL = "1"
# How far under the line that calls it the note stands.
NOTE_STEP = 20.0
# The size the hollow words are set in.
HOLLOW_SIZE = 44.0
# How far under its words the baseline of the hollow words stands.
HOLLOW_DROP = 58.0
# The width of the pen that traces the hollow letters.
HOLLOW_PEN = 0.9
# The size the words set on a band are set in.
BAND_SIZE = 22.0
# The band under those words: its top this far above the baseline, in points, and this
# thick.
BAND = (17.0, 20.0)
# How far under the underlined line the words set on a band stand.
BAND_STEP = 24.0
# The top of the note at the foot of the back of the card.
BACK_NOTE_TOP = 196.0


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

    # What stands across the head of the card.
    title: str
    # The line under it, which says what the card is showing.
    standfirst: str
    # What each line of the shelf is called, in the order they are set.
    goods: tuple[str, str, str]
    # What stands against the sum of the three.
    total_label: str
    # What opens the legend.
    legend: str
    # The three marks, each named by words set the way the name says.
    under: str
    through: str
    over: str
    # The note at the foot, which says where each mark comes from.
    note: str
    # What the mark laid across the card reads.
    mark: str
    # What stands across the head of the back of the card.
    back_title: str
    # What opens each of the five parts of the back: words, lines, the lifted figure,
    # the hollow letters and the rules.
    labels: tuple[str, str, str, str, str]
    # The line set once as the face spaces it and once with its words pushed apart.
    spaced: str
    # The paragraph set close and then set wide.
    paragraph: str
    # The line the lifted figure calls a note from.
    called: str
    # The note the lifted figure calls.
    called_note: str
    # The line set underlined.
    underlined: str
    # The words set hollow on a band.
    banded: str
    # The note at the foot of the back, which says how each part is made.
    back_note: str
    # What stands across the head of the third page.
    modes_title: str
    # The word the third page paints each way.
    painted: str
    # What each way of painting is called, in the order `MODES` lists them.
    modes: tuple[str, ...]

    def marks(self) -> tuple[str, str, str]:
        """The three marks, in the order the legend sets them."""
        return (self.under, self.through, self.over)


# The card in English.
ENGLISH = Words(
    title="The half-price shelf",
    standfirst="What a shop marks by hand, set by the engine instead.",
    goods=(
        "Atlas of the northern coast",
        "Field guide to the estuary birds",
        "The lighthouse keepers, a history",
    ),
    total_label="The three together",
    legend="The three marks, each set the way it names:",
    under="a rule under the words",
    through="a rule through the letters",
    over="a rule over the top",
    note=(
        "One rule on this card was placed by hand: the heavier one over the "
        "total, standing clear of the figures. The one under the words and the "
        "one through the letters sit where the face itself says they go, and the "
        "one over the legend's last line rides the ascender the face states; "
        "those three follow the size the text is set in. The heading carries a "
        "copy of itself behind it, offset and paler, which is what keeps large "
        "lettering readable over a photograph. The mark laid across the foot "
        "carries one too, turned with it, so the copy falls the same way on the "
        "sheet as it does under the heading. The heading's letters are pushed a "
        "point and a half apart, and the standfirst is narrowed to nine tenths of "
        "the width the face gives it; each line is measured that way as well, so "
        "neither runs past the card."
    ),
    mark="HALF PRICE",
    back_title="On the back: spaced, raised, hollow",
    labels=(
        "Words pushed apart: the same line, then six points more at every space.",
        "Lines pushed apart: one paragraph, set close and then set wide.",
        "A figure raised above the line, the way a note is called:",
        "Letters drawn hollow: only their outline is traced.",
        "Rules along the text: a line underlined, then a band under hollow letters.",
    ),
    spaced="Three books for the price of one and a half.",
    paragraph=(
        "Every book on this shelf came back unread or was opened once in the "
        "shop. The pages are clean, the spines unbroken, and the price is half "
        "the one printed on the cover."
    ),
    called="Half price on every book of the shelf",
    called_note="While the shelf lasts.",
    underlined="Returns are accepted within thirty days.",
    banded="LAST COPIES",
    back_note=(
        "Nothing on this side is placed letter by letter. The room between words "
        "is a setting of the text state, and it moves the pen only at the byte a "
        "one-byte encoding gives the space, so the face is added a second time, "
        "reached through such an encoding. The leading is the distance from one "
        "baseline to the next. The lifted figure stands on a baseline raised five "
        "points, and is set smaller. The hollow letters are painted by their "
        "outline alone, in the colour and the width given to the pen. The underline "
        "and the band belong to the paragraph itself: the band goes down before the "
        "letters, which are painted over it."
    ),
    modes_title="Four ways of painting a letter",
    painted="Ink",
    modes=(
        "filled",
        "outlined",
        "filled and outlined",
        "invisible, yet there to be searched",
    ),
)

# The card in French.
FRENCH = Words(
    title="L’étagère à moitié prix",
    standfirst="Ce qu’un commerçant marque d’ordinaire à la main, le moteur le compose ici.",
    goods=(
        "Atlas de la côte nord",
        "Guide des oiseaux de l’estuaire",
        "Les gardiens de phare, une histoire",
    ),
    total_label="Les trois ensemble",
    legend="Les trois traits, chacun tracé sur les mots qui le nomment :",
    under="un trait sous les mots",
    through="un trait qui barre les lettres",
    over="un trait au-dessus de la ligne",
    note=(
        "Un seul trait de cette fiche a été placé à la main : celui, plus épais, qui "
        "coiffe le total sans toucher les chiffres. Celui qui souligne les mots et celui "
        "qui barre les lettres se placent là où la police elle-même l’indique ; celui qui"
        " surmonte la dernière ligne de la légende repose sur la hauteur d’ascendante que"
        " déclare la police. Ces trois-là suivent la taille du texte. Le titre est "
        "doublé, derrière lui, d’une copie décalée et plus pâle, ce qui garde un grand "
        "lettrage lisible sur une photographie. Le tampon posé en travers du bas de la "
        "fiche en a une aussi, qui tourne avec lui : la copie tombe ainsi sur la feuille "
        "du même côté que sous le titre. Les lettres du titre sont espacées d’un point et"
        " demi de plus, et le sous-titre est ramené aux neuf dixièmes de la largeur que "
        "lui donne la police. Chaque ligne est mesurée avec ces réglages, si bien "
        "qu’aucune ne dépasse de la fiche."
    ),
    mark="MOITIÉ PRIX",
    back_title="Au dos : espacé, en exposant, en creux",
    labels=(
        "Des mots espacés : la même ligne, puis six points de plus à chaque espace.",
        "Des lignes espacées : un même paragraphe, d’abord serré, puis aéré.",
        "Un chiffre en exposant, comme un appel de note :",
        "Des lettres creuses : seul leur contour est tracé.",
        "Des traits : une ligne soulignée, puis une bande sous des lettres creuses.",
    ),
    spaced="Trois livres pour le prix d’un et demi.",
    paragraph=(
        "Chaque livre de cette étagère a été rapporté sans avoir été lu, ou n’a été "
        "ouvert qu’une fois en librairie. Les pages sont propres, les dos intacts, et le "
        "prix est la moitié de celui qui est imprimé sur la couverture."
    ),
    called="Moitié prix sur tous les livres de l’étagère",
    called_note="Dans la limite des livres disponibles.",
    underlined="Les retours sont acceptés sous trente jours.",
    banded="DERNIERS EXEMPLAIRES",
    back_note=(
        "Rien sur cette face n’est placé lettre à lettre. L’espace entre les mots est "
        "un réglage de l’état du texte ; il n’agit que sur l’octet qu’un encodage à un "
        "octet attribue à l’espace. La police est donc ajoutée une seconde fois, sous "
        "un encodage de ce type. L’interligne est la distance d’une ligne de base à la "
        "suivante. Le chiffre en exposant repose sur une ligne de base remontée de cinq "
        "points, dans un corps plus petit. Les lettres creuses ne sont peintes que par "
        "leur contour, avec la couleur et l’épaisseur de trait qui leur sont données. "
        "Le soulignement et la bande appartiennent au paragraphe lui-même : la bande "
        "est posée avant les lettres, qui sont peintes par-dessus."
    ),
    modes_title="Quatre façons de peindre une lettre",
    painted="Encre",
    modes=(
        "rempli",
        "contouré",
        "rempli et contouré",
        "invisible, mais présent pour la recherche",
    ),
)

# The card in Dutch.
DUTCH = Words(
    title="De plank met halve prijzen",
    standfirst="Wat een winkel met de hand markeert, hier door de motor gezet.",
    goods=(
        "Atlas van de noordkust",
        "Veldgids voor de vogels van het estuarium",
        "De vuurtorenwachters, een geschiedenis",
    ),
    total_label="De drie samen",
    legend="De drie markeringen, elk gezet zoals haar naam zegt:",
    under="een lijn onder de woorden",
    through="een lijn door de letters",
    over="een lijn over de bovenkant",
    note=(
        "Eén lijn op deze kaart is met de hand geplaatst: de zwaardere boven het "
        "totaal, vrij van de cijfers. Die onder de woorden en die door de letters "
        "staan waar de letter zelf zegt dat ze horen, en die boven de laatste regel "
        "van het onderschrift rust op de stok die de letter opgeeft; die drie volgen "
        "de grootte waarin de tekst gezet is. De kop heeft een kopie van zichzelf "
        "erachter, verschoven en bleker, en die houdt grote letters leesbaar boven "
        "een foto. De markering schuin over de voet heeft er ook een, mee gedraaid, "
        "dus valt de kopie op het blad net zo als onder de kop. De letters van de kop "
        "staan anderhalve punt uit elkaar, en de intro is versmald tot negen tiende "
        "van de breedte die de letter haar geeft; elke regel is ook zo gemeten, dus "
        "loopt geen van beide over de kaart."
    ),
    mark="HALVE PRIJS",
    back_title="Achterkant: gespatieerd, verhoogd, hol",
    labels=(
        "Woorden uit elkaar: dezelfde regel, dan zes punten extra bij elke spatie.",
        "Regels uit elkaar: één alinea, krap gezet en dan ruim.",
        "Een cijfer boven de regel, zoals een voetnoot wordt aangeroepen:",
        "Letters hol getekend: alleen hun omtrek wordt getrokken.",
        "Lijnen langs de tekst: een onderstreepte regel, dan een band onder holle letters.",
    ),
    spaced="Drie boeken voor de prijs van anderhalf.",
    paragraph=(
        "Elk boek op deze plank kwam ongelezen terug of is één keer in de winkel "
        "geopend. De pagina’s zijn schoon, de ruggen ongebroken, en de prijs is de "
        "helft van die op het omslag."
    ),
    called="Halve prijs op elk boek van de plank",
    called_note="Zolang de voorraad strekt.",
    underlined="Retouren worden binnen dertig dagen aanvaard.",
    banded="LAATSTE EXEMPLAREN",
    back_note=(
        "Niets aan deze kant is letter voor letter geplaatst. De ruimte tussen "
        "woorden is een instelling van de tekststatus, en verplaatst de pen alleen "
        "bij de byte die een codering van één byte aan de spatie geeft, dus wordt de "
        "letter een tweede keer toegevoegd, via zo’n codering. De regelafstand is de "
        "afstand van de ene basislijn tot de volgende. Het verhoogde cijfer staat op "
        "een basislijn vijf punten hoger, en is kleiner gezet. De holle letters "
        "worden alleen door hun omtrek geschilderd, in de kleur en de dikte die de "
        "pen meekreeg. De onderstreping en de band horen bij de alinea zelf: de band "
        "komt vóór de letters, die eroverheen geschilderd worden."
    ),
    modes_title="Vier manieren om een letter te schilderen",
    painted="Inkt",
    modes=(
        "gevuld",
        "omlijnd",
        "gevuld en omlijnd",
        "onzichtbaar, maar vindbaar bij zoeken",
    ),
)

# The card in Portuguese.
PORTUGUESE = Words(
    title="Prateleira a meio preço",
    standfirst="O que uma loja marca à mão, composto pelo motor.",
    goods=(
        "Atlas da costa norte",
        "Guia de campo das aves do estuário",
        "Os faroleiros, uma história",
    ),
    total_label="Os três juntos",
    legend="As três marcas, cada uma composta como o seu nome diz:",
    under="um filete sob as palavras",
    through="um filete através das letras",
    over="um filete por cima",
    note=(
        "Um filete deste cartão foi colocado à mão: o mais grosso por cima do total, "
        "afastado dos números. O que fica sob as palavras e o que atravessa as letras "
        "estão onde o próprio tipo diz que vão, e o que fica sobre a última linha da "
        "legenda apoia-se no ascendente que o tipo indica; esses três seguem o corpo "
        "do texto. O título leva por trás uma cópia de si mesmo, deslocada e mais "
        "pálida, que é o que mantém legíveis as letras grandes sobre uma fotografia. "
        "A marca posta ao longo do pé também leva uma, rodada com ela, por isso a "
        "cópia cai na folha da mesma forma que sob o título. As letras do título são "
        "afastadas ponto e meio, e o subtítulo é estreitado para nove décimos da "
        "largura que o tipo lhe dá; cada linha é medida assim também, por isso "
        "nenhuma sai do cartão."
    ),
    mark="METADE DO PREÇO",
    back_title="No verso: espaçado, elevado, oco",
    labels=(
        "Palavras afastadas: a mesma linha, e depois seis pontos a mais em cada espaço.",
        "Linhas afastadas: um parágrafo, composto apertado e depois largo.",
        "Um número elevado acima da linha, como se chama uma nota:",
        "Letras desenhadas ocas: só o seu contorno é traçado.",
        "Filetes ao longo do texto: uma linha sublinhada, e depois uma faixa sob letras ocas.",
    ),
    spaced="Três livros pelo preço de um e meio.",
    paragraph=(
        "Cada livro desta prateleira voltou por ler ou foi aberto uma vez na loja. As "
        "páginas estão limpas, as lombadas intactas, e o preço é metade do impresso "
        "na capa."
    ),
    called="Metade do preço em todos os livros da prateleira",
    called_note="Até esgotar.",
    underlined="Aceitam-se devoluções no prazo de trinta dias.",
    banded="ÚLTIMOS EXEMPLARES",
    back_note=(
        "Nada deste lado é colocado letra a letra. O espaço entre palavras é uma "
        "definição do estado de texto, e só move a caneta no byte que uma codificação "
        "de um byte dá ao espaço, por isso o tipo é acrescentado uma segunda vez, "
        "através dessa codificação. O entrelinhamento é a distância de uma linha de "
        "base à seguinte. O número elevado assenta numa linha de base cinco pontos "
        "mais alta, e em corpo menor. As letras ocas são pintadas só pelo seu "
        "contorno, na cor e na espessura dadas à caneta. O sublinhado e a faixa "
        "pertencem ao próprio parágrafo: a faixa é posta antes das letras, que são "
        "pintadas por cima."
    ),
    modes_title="Quatro formas de pintar uma letra",
    painted="Tinta",
    modes=(
        "preenchida",
        "contornada",
        "preenchida e contornada",
        "invisível, mas presente para a pesquisa",
    ),
)

# The card in Italian.
ITALIAN = Words(
    title="Lo scaffale a metà prezzo",
    standfirst="Ciò che un negozio segna a mano, composto invece dal motore.",
    goods=(
        "Atlante della costa settentrionale",
        "Guida da campo agli uccelli dell’estuario",
        "I guardiani del faro, una storia",
    ),
    total_label="I tre insieme",
    legend="I tre segni, ciascuno composto come dice il suo nome:",
    under="un filetto sotto le parole",
    through="un filetto attraverso le lettere",
    over="un filetto sopra",
    note=(
        "Un filetto di questo cartoncino è stato collocato a mano: quello più spesso "
        "sopra il totale, staccato dalle cifre. Quello sotto le parole e quello "
        "attraverso le lettere stanno dove dice il carattere stesso, e quello sopra "
        "l’ultima riga della legenda poggia sull’ascendente indicato dal carattere; "
        "quei tre seguono il corpo del testo. Il titolo porta dietro una copia di sé, "
        "spostata e più chiara, ed è ciò che tiene leggibili le lettere grandi su una "
        "fotografia. Il segno che attraversa il piede ne porta una anche lui, ruotata "
        "con esso, così la copia cade sul foglio come sotto il titolo. Le lettere del "
        "titolo sono allargate di un punto e mezzo, e l’occhiello è ristretto a nove "
        "decimi della larghezza che gli dà il carattere; anche ogni riga è misurata "
        "così, perciò nessuna esce dal cartoncino."
    ),
    mark="METÀ PREZZO",
    back_title="Sul retro: spaziato, sollevato, vuoto",
    labels=(
        "Parole allargate: la stessa riga, poi sei punti in più a ogni spazio.",
        "Righe allargate: un paragrafo, composto stretto e poi largo.",
        "Una cifra sollevata sopra la riga, come si richiama una nota:",
        "Lettere disegnate vuote: se ne traccia solo il contorno.",
        "Filetti lungo il testo: una riga sottolineata, poi una fascia sotto lettere vuote.",
    ),
    spaced="Tre libri al prezzo di uno e mezzo.",
    paragraph=(
        "Ogni libro di questo scaffale è tornato non letto o è stato aperto una volta "
        "in negozio. Le pagine sono pulite, i dorsi integri, e il prezzo è la metà di "
        "quello stampato in copertina."
    ),
    called="Metà prezzo su ogni libro dello scaffale",
    called_note="Fino a esaurimento.",
    underlined="I resi si accettano entro trenta giorni.",
    banded="ULTIME COPIE",
    back_note=(
        "Niente su questo lato è collocato lettera per lettera. Lo spazio tra le "
        "parole è un’impostazione dello stato del testo, e sposta la penna solo al "
        "byte che una codifica a un byte dà allo spazio, così il carattere è aggiunto "
        "una seconda volta, attraverso una tale codifica. L’interlinea è la distanza "
        "da una linea di base alla successiva. La cifra sollevata sta su una linea di "
        "base alzata di cinque punti, ed è in corpo minore. Le lettere vuote sono "
        "dipinte solo dal loro contorno, nel colore e nello spessore dati alla penna. "
        "La sottolineatura e la fascia appartengono al paragrafo stesso: la fascia si "
        "stende prima delle lettere, che vi sono dipinte sopra."
    ),
    modes_title="Quattro modi di dipingere una lettera",
    painted="Inchiostro",
    modes=(
        "piena",
        "contornata",
        "piena e contornata",
        "invisibile, eppure lì da cercare",
    ),
)

# The card in Spanish.
SPANISH = Words(
    title="Estante a medio precio",
    standfirst="Lo que una tienda marca a mano, compuesto por el motor.",
    goods=(
        "Atlas de la costa norte",
        "Guía de campo de las aves del estuario",
        "Los fareros, una historia",
    ),
    total_label="Los tres juntos",
    legend="Las tres marcas, cada una compuesta como dice su nombre:",
    under="una raya bajo las palabras",
    through="una raya a través de las letras",
    over="una raya por encima",
    note=(
        "Una raya de esta tarjeta se colocó a mano: la más gruesa sobre el total, "
        "separada de las cifras. La de bajo las palabras y la que cruza las letras "
        "están donde la propia fuente dice que van, y la que va sobre la última línea "
        "de la leyenda se apoya en el ascendente que indica la fuente; esas tres "
        "siguen el cuerpo del texto. El título lleva detrás una copia de sí mismo, "
        "desplazada y más pálida, que es lo que mantiene legibles las letras grandes "
        "sobre una fotografía. La marca que cruza el pie lleva otra, girada con ella, "
        "así que la copia cae igual en la hoja que bajo el título. Las letras del "
        "título se separan punto y medio, y la entradilla se estrecha a nueve décimas "
        "del ancho que le da la fuente; cada línea también se mide así, así que "
        "ninguna se sale de la tarjeta."
    ),
    mark="MITAD DE PRECIO",
    back_title="Al dorso: espaciado, elevado, hueco",
    labels=(
        "Palabras separadas: la misma línea, y luego seis puntos más en cada espacio.",
        "Líneas separadas: un párrafo, compuesto apretado y luego holgado.",
        "Una cifra elevada sobre la línea, como se llama a una nota:",
        "Letras dibujadas huecas: solo se traza su contorno.",
        "Rayas a lo largo del texto: una línea subrayada, y luego una banda bajo letras huecas.",
    ),
    spaced="Tres libros por el precio de uno y medio.",
    paragraph=(
        "Cada libro de esta estantería volvió sin leer o se abrió una vez en la "
        "tienda. Las páginas están limpias, los lomos intactos, y el precio es la "
        "mitad del impreso en la cubierta."
    ),
    called="Mitad de precio en todos los libros de la estantería",
    called_note="Hasta agotar existencias.",
    underlined="Se aceptan devoluciones en treinta días.",
    banded="ÚLTIMOS EJEMPLARES",
    back_note=(
        "Nada en esta cara se coloca letra a letra. El espacio entre palabras es un "
        "ajuste del estado de texto, y solo mueve la pluma en el byte que una "
        "codificación de un byte da al espacio, así que la fuente se añade una "
        "segunda vez, a través de esa codificación. El interlineado es la distancia "
        "de una línea base a la siguiente. La cifra elevada está sobre una línea base "
        "cinco puntos más alta, y en cuerpo menor. Las letras huecas se pintan solo "
        "por su contorno, con el color y el ancho dados a la pluma. El subrayado y la "
        "banda pertenecen al propio párrafo: la banda se pone antes que las letras, "
        "que se pintan encima."
    ),
    modes_title="Cuatro formas de pintar una letra",
    painted="Tinta",
    modes=(
        "rellena",
        "contorneada",
        "rellena y contorneada",
        "invisible, pero presente para las búsquedas",
    ),
)

# The card in German.
GERMAN = Words(
    title="Das Halbpreisregal",
    standfirst="Was ein Laden von Hand auszeichnet, setzt hier die Engine.",
    goods=(
        "Atlas der Nordküste",
        "Bestimmungsbuch der Vögel des Ästuars",
        "Die Leuchtturmwärter, eine Geschichte",
    ),
    total_label="Alle drei zusammen",
    legend="Die drei Auszeichnungen, jede so gesetzt, wie sie heißt:",
    under="eine Linie unter den Wörtern",
    through="eine Linie durch die Buchstaben",
    over="eine Linie über den Buchstaben",
    note=(
        "Eine Linie auf dieser Karte wurde von Hand gesetzt: die kräftigere über der "
        "Summe, frei von den Ziffern. Die unter den Wörtern und die durch die "
        "Buchstaben stehen dort, wo der Schnitt selbst sie vorsieht, und die über der "
        "letzten Zeile der Legende liegt auf der Oberlänge, die der Schnitt angibt; "
        "diese drei folgen der Größe, in der der Text gesetzt ist. Die Überschrift "
        "trägt eine Kopie ihrer selbst dahinter, versetzt und blasser, was große "
        "Schrift über einem Foto lesbar hält. Die quer über den Fuß gelegte "
        "Auszeichnung trägt ebenfalls eine, mit ihr gedreht, sodass die Kopie auf dem "
        "Blatt genauso fällt wie unter der Überschrift. Die Buchstaben der "
        "Überschrift stehen anderthalb Punkt auseinander, und der Vorspann ist auf "
        "neun Zehntel der Breite verengt, die der Schnitt ihm gibt; jede Zeile wird "
        "ebenso gemessen, also läuft keine über die Karte hinaus."
    ),
    mark="HALBER PREIS",
    back_title="Rückseite: gesperrt, gehoben, hohl",
    labels=(
        "Wörter gesperrt: dieselbe Zeile, dann sechs Punkt mehr je Leerzeichen.",
        "Zeilen gesperrt: ein Absatz, eng und dann weit gesetzt.",
        "Eine hochgestellte Ziffer, wie ein Fußnotenzeichen:",
        "Hohle Buchstaben: Nur ihre Kontur wird gezogen.",
        "Linien am Text: unterstrichen, dann ein Band unter hohlen Buchstaben.",
    ),
    spaced="Drei Bücher zum Preis von anderthalb.",
    paragraph=(
        "Jedes Buch in diesem Regal kam ungelesen zurück oder wurde einmal im Laden "
        "aufgeschlagen. Die Seiten sind sauber, die Rücken ungebrochen, und der Preis "
        "ist die Hälfte des auf dem Umschlag gedruckten."
    ),
    called="Halber Preis für jedes Buch des Regals",
    called_note="Solange das Regal reicht.",
    underlined="Rückgabe binnen dreißig Tagen möglich.",
    banded="LETZTE EXEMPLARE",
    back_note=(
        "Nichts auf dieser Seite wird Buchstabe für Buchstabe platziert. Der Raum "
        "zwischen Wörtern ist eine Einstellung des Textzustands, und er bewegt den "
        "Stift nur bei dem Byte, das eine Ein-Byte-Kodierung dem Leerzeichen gibt, "
        "also wird der Schnitt ein zweites Mal hinzugefügt, über eine solche "
        "Kodierung erreicht. Der Zeilenabstand ist der Abstand von einer Grundlinie "
        "zur nächsten. Die gehobene Ziffer steht auf einer um fünf Punkt angehobenen "
        "Grundlinie und ist kleiner gesetzt. Die hohlen Buchstaben werden allein über "
        "ihre Kontur gemalt, in Farbe und Breite des Stifts. Unterstreichung und Band "
        "gehören zum Absatz selbst: Das Band kommt vor den Buchstaben, die "
        "darübergemalt werden."
    ),
    modes_title="Vier Arten, einen Buchstaben zu malen",
    painted="Tinte",
    modes=(
        "gefüllt",
        "umrandet",
        "gefüllt und umrandet",
        "unsichtbar, aber durchsuchbar",
    ),
)

# Every language the card is written in.
WORDS = {_language.ENGLISH: ENGLISH, _language.FRENCH: FRENCH, _language.GERMAN: GERMAN, _language.SPANISH: SPANISH, _language.ITALIAN: ITALIAN, _language.PORTUGUESE: PORTUGUESE, _language.DUTCH: DUTCH}

# What each line of the shelf was marked at and what it is marked at now.
#
# Figures are not words: both languages set the same ones, and the engine writes a
# decimal point in every language it is asked for.
PRICES = (
    ("24.00 EUR", "12.00 EUR"),
    ("18.00 EUR", "9.00 EUR"),
    ("30.00 EUR", "15.00 EUR"),
)

# What the three come to together.
TOTAL = "36.00 EUR"


def shadow_ink() -> hqf_pdf.Rgb:
    """The colour every shadow on the card is drawn in."""
    return hqf_pdf.Rgb.gray(0.78)


def mark_ink() -> hqf_pdf.Rgb:
    """The colour of the mark, which the hollow letters take up on the back."""
    return hqf_pdf.Rgb(0.72, 0.16, 0.16)


def heading(
    content: hqf_pdf.Content, font: hqf_pdf.FontHandle, words: Words
) -> None:
    """Draws the heading, which sets itself once more behind itself."""
    style = hqf_pdf.Style(font, TITLE_SIZE, character_spacing=TITLE_TRACKING)
    text = hqf_pdf.RichText(
        shadow=hqf_pdf.Shadow(1.5, -1.5, shadow_ink())
    ).span(words.title, style)
    lines = text.break_lines(WIDTH)
    text.draw(content, lines, LEFT, TITLE_TOP, WIDTH)


def standfirst(
    content: hqf_pdf.Content, font: hqf_pdf.FontHandle, words: Words
) -> None:
    """Draws the standfirst and the line under it."""
    flow = hqf_pdf.TextFlow(font, NOTE_SIZE, horizontal_scaling=STANDFIRST_SCALING)
    lines = flow.break_lines(words.standfirst, WIDTH)
    flow.draw(content, lines, LEFT, STANDFIRST_TOP, WIDTH)

    content.set_line_width(1.0)
    content.move_to(LEFT, HEAD_RULE)
    content.line_to(LEFT + WIDTH, HEAD_RULE)
    content.stroke()


def shelf_line(
    content: hqf_pdf.Content,
    font: hqf_pdf.FontHandle,
    name: str,
    prices: tuple[str, str],
    top: float,
) -> None:
    """Draws one line of the shelf: what it is called, then what it was marked at
    struck through and what it is marked at now."""
    plain = hqf_pdf.Style(font, ROW_SIZE)
    struck = hqf_pdf.Style(font, ROW_SIZE, strikethrough=True)

    left = hqf_pdf.RichText().span(name, plain)
    left.draw(content, left.break_lines(WIDTH), LEFT, top, WIDTH)

    right = (
        hqf_pdf.RichText(align=hqf_pdf.Align.Right)
        .span(prices[0], struck)
        .span("   ", plain)
        .span(prices[1], plain)
    )
    right.draw(content, right.break_lines(WIDTH), LEFT, top, WIDTH)


def total(content: hqf_pdf.Content, font: hqf_pdf.FontHandle, words: Words) -> None:
    """Draws the line that adds the three together, the sum ruled over the top the
    way a column of figures is ruled before it is totalled."""
    plain = hqf_pdf.Style(font, ROW_SIZE)
    ruled = hqf_pdf.Style(font, ROW_SIZE, overline_at=TOTAL_RULE)

    left = hqf_pdf.RichText().span(words.total_label, plain)
    left.draw(content, left.break_lines(WIDTH), LEFT, TOTAL_TOP, WIDTH)

    right = hqf_pdf.RichText(align=hqf_pdf.Align.Right).span(TOTAL, ruled)
    right.draw(content, right.break_lines(WIDTH), LEFT, TOTAL_TOP, WIDTH)


def legend(content: hqf_pdf.Content, font: hqf_pdf.FontHandle, words: Words) -> None:
    """Draws the legend: a line that says what follows, then the three marks, each
    named by words set the way the name says."""
    plain = hqf_pdf.Style(font, ROW_SIZE)
    opening = hqf_pdf.RichText().span(words.legend, plain)
    opening.draw(content, opening.break_lines(WIDTH), LEFT, LEGEND_TOP, WIDTH)

    marked = (
        hqf_pdf.Style(font, ROW_SIZE, underline=True),
        hqf_pdf.Style(font, ROW_SIZE, strikethrough=True),
        hqf_pdf.Style(font, ROW_SIZE, overline=True),
    )

    # The tops step down one line at a time rather than being multiplied out, so the
    # Rust twin adds the same numbers in the same order.
    top = LEGEND_TOP
    for name, style in zip(words.marks(), marked):
        top -= LEGEND_STEP
        line = hqf_pdf.RichText().span(name, style)
        room = WIDTH - LEGEND_INDENT
        line.draw(content, line.break_lines(room), LEFT + LEGEND_INDENT, top, room)


def note(content: hqf_pdf.Content, font: hqf_pdf.FontHandle, words: Words) -> None:
    """Draws the closing note, which says where each mark comes from."""
    flow = hqf_pdf.TextFlow(font, NOTE_SIZE, align=hqf_pdf.Align.Justify)
    lines = flow.break_lines(words.note, WIDTH)
    flow.draw(content, lines, LEFT, NOTE_TOP, WIDTH)


def mark(content: hqf_pdf.Content, font: hqf_pdf.FontHandle, words: Words) -> None:
    """Draws the mark laid across the foot of the card, which carries a shadow of
    its own."""
    x, y, width, height = MARK_BOX
    stamp = hqf_pdf.Stamp(
        font,
        words.mark,
        slant=hqf_pdf.Slant.Up,
        color=mark_ink(),
        shadow=hqf_pdf.Shadow(2.5, -2.5, shadow_ink()),
    )
    stamp.draw(content, x, y, width, height)


def block(
    content: hqf_pdf.Content,
    flow: hqf_pdf.TextFlow,
    text: str,
    at: tuple[float, float],
    width: float,
) -> float:
    """Sets `text` in `flow` from `top`, and gives back where it ends."""
    left, top = at
    lines = flow.break_lines(text, width)
    flow.draw(content, lines, left, top, width)
    return top - flow.height(lines)


def spaced_words(
    content: hqf_pdf.Content, coded: hqf_pdf.FontHandle, words: Words
) -> None:
    """Draws the first part of the back: one line as the face spaces it, then the
    same line with its words pushed apart."""
    top = LABEL_TOPS[0] - LABEL_GAP
    plain = hqf_pdf.TextFlow(coded, ROW_SIZE)
    block(content, plain, words.spaced, (LEFT, top), WIDTH)
    spaced = hqf_pdf.TextFlow(coded, ROW_SIZE, word_spacing=WORD_SPACING)
    block(content, spaced, words.spaced, (LEFT, top - SAMPLE_STEP), WIDTH)


def spaced_lines(
    content: hqf_pdf.Content, font: hqf_pdf.FontHandle, words: Words
) -> None:
    """Draws the second part of the back: the paragraph at each of the two leadings,
    side by side."""
    top = LABEL_TOPS[1] - LABEL_GAP
    column = (WIDTH - GUTTER) / 2.0
    left = LEFT
    for leading in LEADINGS:
        flow = hqf_pdf.TextFlow(font, NOTE_SIZE, leading=leading)
        block(content, flow, words.paragraph, (left, top), column)
        left += column + GUTTER


def lifted_figure(
    content: hqf_pdf.Content, font: hqf_pdf.FontHandle, words: Words
) -> None:
    """Draws the third part of the back: a line ending on a figure lifted above it,
    then the note the figure calls, opening on the same lifted figure."""
    baseline = LABEL_TOPS[2] - LABEL_GAP - ROW_SIZE

    content.begin_text()
    content.text_position(LEFT, baseline)
    content.set_font(font, ROW_SIZE)
    content.show_runs(font.runs(words.called), ROW_SIZE)
    lifted_call(content, font)
    content.end_text()

    content.begin_text()
    content.text_position(LEFT, baseline - NOTE_STEP)
    lifted_call(content, font)
    content.set_font(font, NOTE_SIZE)
    content.show_runs(font.runs(f" {words.called_note}"), NOTE_SIZE)
    content.end_text()


def lifted_call(content: hqf_pdf.Content, font: hqf_pdf.FontHandle) -> None:
    """Shows the figure that calls the note, smaller and lifted, where the pen stands,
    and sets the baseline back where it was."""
    content.set_font(font, CALL_SIZE)
    content.set_text_rise(CALL_RISE)
    content.show_runs(font.runs(CALL), CALL_SIZE)
    content.set_text_rise(0.0)


def hollow(content: hqf_pdf.Content, font: hqf_pdf.FontHandle, words: Words) -> None:
    """Draws the fourth part of the back: the mark's words traced by their outline
    alone."""
    content.save_state()
    content.set_stroke(mark_ink())
    content.set_line_width(HOLLOW_PEN)
    content.begin_text()
    content.text_position(LEFT, LABEL_TOPS[3] - HOLLOW_DROP)
    content.set_font(font, HOLLOW_SIZE)
    content.set_text_rendering(hqf_pdf.TextRendering.stroke())
    content.show_runs(font.runs(words.mark), HOLLOW_SIZE)
    content.end_text()
    content.restore_state()


def band_ink() -> hqf_pdf.Rgb:
    """The colour of the band the hollow words are set on."""
    return hqf_pdf.Rgb(1.0, 0.84, 0.3)


def ruled_lines(
    content: hqf_pdf.Content, font: hqf_pdf.FontHandle, words: Words
) -> None:
    """Draws the fifth part of the back: a plain line with its rule filled after its
    letters, then hollow words whose band is filled before them."""
    top = LABEL_TOPS[4] - LABEL_GAP
    underlined = hqf_pdf.TextFlow(font, ROW_SIZE, underline=True)
    block(content, underlined, words.underlined, (LEFT, top), WIDTH)

    banded = hqf_pdf.TextFlow(
        font,
        BAND_SIZE,
        color=band_ink(),
        outline=HOLLOW_PEN,
        outline_color=mark_ink(),
        strikethrough_at=BAND,
        rule_stacking=hqf_pdf.Stacking.Under,
    )
    block(content, banded, words.banded, (LEFT, top - BAND_STEP), WIDTH)


def back(
    content: hqf_pdf.Content,
    font: hqf_pdf.FontHandle,
    coded: hqf_pdf.FontHandle,
    words: Words,
) -> None:
    """Draws the back of the card: its heading, the five parts each under the words
    that open it, and the note at the foot."""
    heading = hqf_pdf.TextFlow(font, BACK_TITLE_SIZE)
    block(content, heading, words.back_title, (LEFT, BACK_TITLE_TOP), WIDTH)

    opening = hqf_pdf.TextFlow(font, NOTE_SIZE, color=hqf_pdf.Rgb.gray(0.35))
    for label, top in zip(words.labels, LABEL_TOPS):
        block(content, opening, label, (LEFT, top), WIDTH)

    spaced_words(content, coded, words)
    spaced_lines(content, font, words)
    lifted_figure(content, font, words)
    hollow(content, font, words)
    ruled_lines(content, font, words)

    note = hqf_pdf.TextFlow(font, NOTE_SIZE, align=hqf_pdf.Align.Justify)
    block(content, note, words.back_note, (LEFT, BACK_NOTE_TOP), WIDTH)


# The four ways a text is painted without clipping what follows, in the order the third
# page sets them.
MODES = (
    hqf_pdf.TextRendering.fill(),
    hqf_pdf.TextRendering.stroke(),
    hqf_pdf.TextRendering.fill_and_stroke(),
    hqf_pdf.TextRendering.invisible(),
)

# Where the label of the third page's first row sits, and the drop from one row to the
# next.
MODE_TOP = 740.0
MODE_STEP = 150.0

# The size the third page's word is painted at.
MODE_SIZE = 54.0

# How far under its label a painted word's baseline sits.
MODE_DROP = 70.0



def line_at(
    content: hqf_pdf.Content,
    font: hqf_pdf.FontHandle,
    text: str,
    size: float,
    at: tuple[float, float],
) -> None:
    """Sets `text` on one line from `x`, its baseline at `y`."""
    content.begin_text()
    content.text_position(at[0], at[1])
    content.set_font(font, size)
    content.show_runs(font.runs(text), size)
    content.end_text()


def painted(content: hqf_pdf.Content, font: hqf_pdf.FontHandle, words: Words) -> None:
    """Draws the third page: the word painted each of the four ways, two to a row, each
    under what the way is called."""
    line_at(content, font, words.modes_title, BACK_TITLE_SIZE, (LEFT, BACK_TITLE_TOP))
    half = WIDTH / 2.0
    top = MODE_TOP
    for index, (mode, label) in enumerate(zip(MODES, words.modes)):
        x = LEFT if index % 2 == 0 else LEFT + half
        content.save_state()
        content.set_fill(hqf_pdf.Rgb.gray(0.35))
        line_at(content, font, label, NOTE_SIZE, (x, top))
        content.restore_state()

        baseline = top - MODE_DROP
        content.save_state()
        content.set_fill(mark_ink())
        content.set_stroke(hqf_pdf.Rgb.gray(0.1))
        content.set_line_width(HOLLOW_PEN)
        content.begin_text()
        content.text_position(x, baseline)
        content.set_font(font, MODE_SIZE)
        content.set_text_rendering(mode)
        content.show_runs(font.runs(words.painted), MODE_SIZE)
        content.end_text()
        content.restore_state()
        if index % 2 == 1:
            top -= MODE_STEP


def main() -> None:
    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("text_ornaments.pdf", language)).stem
    )

    document = hqf_pdf.Document()
    document.set_license(_licence.licensed())
    document.set_info("Title", words.title)
    face = hqf_pdf.Font.from_path(_out.font_path())
    encoding = hqf_pdf.SimpleEncoding.fitting(face, words.spaced)
    coded_face = face.with_encoding(hqf_pdf.Encoding.simple(encoding))
    font = document.add_font(face)

    content = hqf_pdf.Content()
    heading(content, font, words)
    standfirst(content, font, words)

    # The tops step down one line at a time rather than being multiplied out, so the
    # Rust twin adds the same numbers in the same order.
    top = FIRST_ROW
    for name, prices in zip(words.goods, PRICES):
        shelf_line(content, font, name, prices, top)
        top -= ROW_STEP

    total(content, font, words)
    legend(content, font, words)
    note(content, font, words)
    mark(content, font, words)

    page = hqf_pdf.Page.a4()
    page.set_content(content)
    document.add_page(page)

    coded = document.add_font(coded_face)
    content = hqf_pdf.Content()
    back(content, font, coded, words)
    page = hqf_pdf.Page.a4()
    page.set_content(content)
    document.add_page(page)

    content = hqf_pdf.Content()
    painted(content, font, words)
    page = hqf_pdf.Page.a4()
    page.set_content(content)
    document.add_page(page)

    written = document.write(out)
    print(f"wrote {out}: {written} bytes")


if __name__ == "__main__":
    main()
