"""Writes a type specimen of the free faces the library carries in place of the fourteen
standard fonts: one page for each face, then a page of stamps and notes set in the
standard fonts.

The Python twin of the `write_type_specimen` example in Rust: the same document, through
the binding rather than through the library directly.

A first page lists the twenty-six faces: the twelve Liberation faces and the twelve TeX
Gyre faces, each drawn to the widths of one of the twelve standard text fonts, then HQF
Symbol and HQF Dingbats, drawn to the widths of Symbol and ZapfDingbats.

Each face then has a page of its own: its name, the standard font it stands in for and
its licence; a line set at eight sizes, from 8 to 48 points; the same line set twice at
14 points, first in the standard font, which the reading software draws from its own
copy and the file does not carry, then in the free face, which the file carries, each
with the width it comes out at; and every character the face reaches through the
standard font's codes.

The last page holds two stamps and two notes whose words are set in standard fonts: the
file carries, for each, the free face drawn to that font's widths, so the reading
software draws them with the face the file holds.

The words are held in `Words`, once per language, and `HQF_PDF_LANG` picks which set the
specimen is written in.

Usage: python examples/write_type_specimen.py [out.pdf]
       HQF_PDF_LANG=fr python examples/write_type_specimen.py
"""

from __future__ import annotations

import math
from dataclasses import dataclass
from pathlib import Path

import _language
import _licence
import _out

import hqf_pdf


@dataclass(frozen=True)
class Words:
    """The words the specimen is written in, in one language."""

    # What the specimen is called, at the head of the first page.
    title: str
    # The paragraph under the title.
    prose: str
    # The heading over the list of faces.
    contents: str
    # Which page a face is on; `{page}` stands for its number.
    page: str
    # The line under a face's name; `{standard}` stands for the standard font it is
    # drawn to the widths of, `{licence}` for its licence.
    drawn_to: str
    # The heading over the line set at eight sizes.
    sizes: str
    # The heading over the line set twice.
    compared: str
    # What the first of the two lines is set in; `{standard}` stands for the standard
    # font.
    in_standard: str
    # What the second is set in; `{face}` stands for the free face.
    in_free: str
    # How wide a line comes out; `{width}` stands for the width in points.
    width: str
    # The heading over every character; `{count}` stands for how many.
    characters: str
    # The last page's heading.
    stamps_title: str
    # The paragraph under it.
    stamps_prose: str
    # What the first stamp says.
    approved: str
    # What the second stamp says.
    confidential: str
    # What the two notes say.
    notes: tuple[str, str]
    # What a stamp or a note is set in, and what the file carries for it; `{standard}`
    # and `{face}` stand for them.
    carried: str
    # The line set at every size.
    pangram: str
    # The mark between the units and the decimals of a figure.
    decimal: str


ENGLISH = Words(
    title="The free faces, one by one",
    prose=(
        "A PDF may name fourteen standard fonts without carrying them: the reading "
        "software draws them from its own copies. Where a file must carry every font "
        "it uses, as an archival file must, the library carries instead a free face "
        "drawn to the same widths, so the text keeps its place to within a tenth of a "
        "point. Each page that follows shows one of these faces at eight sizes, set "
        "against the standard font it stands in for, and every character it reaches."
    ),
    contents="The twenty-six faces",
    page="page {page}",
    drawn_to="Drawn to the widths of {standard} · {licence}",
    sizes="From 8 to 48 points",
    compared="The same line at 14 points, in the standard font, then in the free face",
    in_standard="{standard}, drawn by the reading software from its own copy",
    in_free="{face}, carried in the file",
    width="{width} points wide",
    characters=(
        "The {count} characters the face reaches through the standard font's codes"
    ),
    stamps_title="Stamps and notes set in a standard font",
    stamps_prose=(
        "These two stamps and these two notes name a standard font. The file carries, "
        "for each, the free face drawn to that font's widths, so every reading "
        "software draws them with the same letters."
    ),
    approved="APPROVED",
    confidential="CONFIDENTIAL",
    notes=(
        "Checked against the order of 3 October.",
        "Total to confirm before payment.",
    ),
    carried="{standard}, carried as {face}",
    pangram="Sphinx of black quartz, judge my vow",
    decimal=".",
)


FRENCH = Words(
    title="Les polices libres, une à une",
    prose=(
        "Un PDF peut nommer quatorze polices standard sans les contenir : le logiciel "
        "de lecture fournit les siennes. Quand un fichier doit contenir toutes ses "
        "polices, comme un fichier d’archivage, la bibliothèque y incorpore à la place "
        "une police libre aux mêmes largeurs : le texte garde sa place à un dixième de "
        "point près. Chaque page qui suit présente l’une de ces polices en huit "
        "tailles, face à la police standard qu’elle remplace, puis tous les caractères "
        "qu’elle affiche à sa place."
    ),
    contents="Les vingt-six polices",
    page="page {page}",
    drawn_to="Mêmes largeurs que {standard} · {licence}",
    sizes="De 8 à 48 points",
    compared=(
        "La même ligne en 14 points, dans la police standard puis dans la police libre"
    ),
    in_standard="{standard}, fournie par le logiciel de lecture",
    in_free="{face}, incorporée au fichier",
    width="{width} points de large",
    characters="Les {count} caractères accessibles par les codes de la police standard",
    stamps_title="Tampons et notes composés dans une police standard",
    stamps_prose=(
        "Ces deux tampons et ces deux notes nomment chacun une police standard. Le "
        "fichier incorpore pour chacun la police libre aux mêmes largeurs : ils "
        "s’affichent donc à l’identique dans tous les logiciels de lecture."
    ),
    approved="APPROUVÉ",
    confidential="CONFIDENTIEL",
    notes=(
        "Vérifié sur la commande du 3 octobre.",
        "Total à confirmer avant paiement.",
    ),
    carried="{standard}, incorporée sous la forme de {face}",
    pangram="Portez ce vieux whisky au juge blond qui fume",
    decimal=",",
)


GERMAN = Words(
    title="Die freien Schriften, eine nach der anderen",
    prose=(
        "Ein PDF kann vierzehn Standardschriften nennen, ohne sie zu enthalten: Die "
        "Lesesoftware zeichnet sie mit ihren eigenen Exemplaren. Muss eine Datei alle "
        "ihre Schriften enthalten, wie eine Archivdatei, bettet die Bibliothek "
        "stattdessen eine freie Schrift mit denselben Breiten ein, und der Text bleibt "
        "bis auf einen Zehntelpunkt an seinem Platz. Jede folgende Seite zeigt eine "
        "dieser Schriften in acht Größen, neben der Standardschrift, die sie ersetzt, "
        "und mit allen Zeichen, die sie erreicht."
    ),
    contents="Die sechsundzwanzig Schriften",
    page="Seite {page}",
    drawn_to="Mit den Breiten von {standard} · {licence}",
    sizes="Von 8 bis 48 Punkt",
    compared=(
        "Dieselbe Zeile in 14 Punkt, in der Standardschrift, dann in der freien "
        "Schrift"
    ),
    in_standard=(
        "{standard}, von der Lesesoftware mit ihrem eigenen Exemplar gezeichnet"
    ),
    in_free="{face}, in der Datei eingebettet",
    width="{width} Punkt breit",
    characters=(
        "Die {count} Zeichen, die die Schrift über die Codes der Standardschrift "
        "erreicht"
    ),
    stamps_title="Stempel und Notizen in einer Standardschrift",
    stamps_prose=(
        "Diese zwei Stempel und diese zwei Notizen nennen eine Standardschrift. Für "
        "jede bettet die Datei die freie Schrift mit denselben Breiten ein, und jede "
        "Lesesoftware zeigt sie mit denselben Buchstaben."
    ),
    approved="GENEHMIGT",
    confidential="VERTRAULICH",
    notes=(
        "Mit der Bestellung vom 3. Oktober abgeglichen.",
        "Summe vor der Zahlung bestätigen.",
    ),
    carried="{standard}, eingebettet als {face}",
    pangram="Victor jagt zwölf Boxkämpfer quer über den großen Sylter Deich",
    decimal=",",
)


SPANISH = Words(
    title="Las fuentes libres, una a una",
    prose=(
        "Un PDF puede nombrar catorce fuentes estándar sin contenerlas: el programa de "
        "lectura las dibuja con sus propias copias. Cuando un archivo debe contener "
        "todas sus fuentes, como un archivo de conservación, la biblioteca incorpora "
        "en su lugar una fuente libre con las mismas anchuras, y el texto conserva su "
        "lugar a una décima de punto. Cada página siguiente muestra una de estas "
        "fuentes en ocho tamaños, frente a la fuente estándar a la que sustituye, con "
        "todos los caracteres que alcanza."
    ),
    contents="Las veintiséis fuentes",
    page="página {page}",
    drawn_to="Con las anchuras de {standard} · {licence}",
    sizes="De 8 a 48 puntos",
    compared=(
        "La misma línea a 14 puntos, en la fuente estándar y después en la fuente "
        "libre"
    ),
    in_standard="{standard}, dibujada por el programa de lectura con su propia copia",
    in_free="{face}, incorporada al archivo",
    width="{width} puntos de ancho",
    characters=(
        "Los {count} caracteres que la fuente alcanza con los códigos de la fuente "
        "estándar"
    ),
    stamps_title="Sellos y notas compuestos en una fuente estándar",
    stamps_prose=(
        "Estos dos sellos y estas dos notas nombran una fuente estándar. Para cada "
        "uno, el archivo incorpora la fuente libre con las mismas anchuras, y todos "
        "los programas de lectura los muestran con las mismas letras."
    ),
    approved="APROBADO",
    confidential="CONFIDENCIAL",
    notes=(
        "Comprobado con el pedido del 3 de octubre.",
        "Total por confirmar antes del pago.",
    ),
    carried="{standard}, incorporada como {face}",
    pangram="Jovencillo emponzoñado de whisky, qué figurota exhibe",
    decimal=",",
)


ITALIAN = Words(
    title="I caratteri liberi, uno per uno",
    prose=(
        "Un PDF può nominare quattordici caratteri standard senza contenerli: il "
        "programma di lettura li disegna con le proprie copie. Quando un file deve "
        "contenere tutti i suoi caratteri, come un file d’archivio, la libreria vi "
        "incorpora al loro posto un carattere libero con le stesse larghezze, e il "
        "testo resta al suo posto entro un decimo di punto. Ogni pagina che segue "
        "mostra uno di questi caratteri in otto corpi, accanto al carattere standard "
        "che sostituisce, con tutti i segni che raggiunge."
    ),
    contents="I ventisei caratteri",
    page="pagina {page}",
    drawn_to="Con le larghezze di {standard} · {licence}",
    sizes="Da 8 a 48 punti",
    compared=(
        "La stessa riga in corpo 14, nel carattere standard e poi nel carattere libero"
    ),
    in_standard="{standard}, disegnato dal programma di lettura con la propria copia",
    in_free="{face}, incorporato nel file",
    width="{width} punti di larghezza",
    characters=(
        "I {count} segni che il carattere raggiunge con i codici del carattere "
        "standard"
    ),
    stamps_title="Timbri e note composti in un carattere standard",
    stamps_prose=(
        "Questi due timbri e queste due note nominano un carattere standard. Per "
        "ciascuno, il file incorpora il carattere libero con le stesse larghezze, e "
        "ogni programma di lettura li mostra con le stesse lettere."
    ),
    approved="APPROVATO",
    confidential="RISERVATO",
    notes=(
        "Verificato sull’ordine del 3 ottobre.",
        "Totale da confermare prima del pagamento.",
    ),
    carried="{standard}, incorporato come {face}",
    pangram="Pranzo d’acqua fa volti sghembi",
    decimal=",",
)


PORTUGUESE = Words(
    title="Os tipos livres, um a um",
    prose=(
        "Um PDF pode nomear catorze tipos padrão sem os conter: o programa de leitura "
        "desenha-os com as suas próprias cópias. Quando um ficheiro tem de conter "
        "todos os seus tipos, como um ficheiro de arquivo, a biblioteca incorpora em "
        "vez deles um tipo livre com as mesmas larguras, e o texto mantém o seu lugar "
        "a uma décima de ponto. Cada página seguinte mostra um destes tipos em oito "
        "tamanhos, ao lado do tipo padrão que substitui, com todos os caracteres que "
        "alcança."
    ),
    contents="Os vinte e seis tipos",
    page="página {page}",
    drawn_to="Com as larguras de {standard} · {licence}",
    sizes="De 8 a 48 pontos",
    compared="A mesma linha a 14 pontos, no tipo padrão e depois no tipo livre",
    in_standard=(
        "{standard}, desenhado pelo programa de leitura com a sua própria cópia"
    ),
    in_free="{face}, incorporado no ficheiro",
    width="{width} pontos de largura",
    characters="Os {count} caracteres que o tipo alcança pelos códigos do tipo padrão",
    stamps_title="Carimbos e notas compostos num tipo padrão",
    stamps_prose=(
        "Estes dois carimbos e estas duas notas nomeiam um tipo padrão. Para cada um, "
        "o ficheiro incorpora o tipo livre com as mesmas larguras, e todos os "
        "programas de leitura os mostram com as mesmas letras."
    ),
    approved="APROVADO",
    confidential="CONFIDENCIAL",
    notes=(
        "Conferido com a encomenda de 3 de outubro.",
        "Total a confirmar antes do pagamento.",
    ),
    carried="{standard}, incorporado como {face}",
    pangram="Um pequeno jabuti xereta viu dez cegonhas felizes",
    decimal=",",
)


DUTCH = Words(
    title="De vrije lettertypen, een voor een",
    prose=(
        "Een pdf kan veertien standaardlettertypen noemen zonder ze te bevatten: de "
        "leessoftware tekent ze met haar eigen exemplaren. Moet een bestand al zijn "
        "lettertypen bevatten, zoals een archiefbestand, dan sluit de bibliotheek in "
        "hun plaats een vrij lettertype met dezelfde breedtes in, en de tekst blijft "
        "tot op een tiende punt op zijn plaats. Elke volgende pagina toont een van "
        "deze lettertypen in acht groottes, naast het standaardlettertype dat het "
        "vervangt, met alle tekens die het bereikt."
    ),
    contents="De zesentwintig lettertypen",
    page="pagina {page}",
    drawn_to="Met de breedtes van {standard} · {licence}",
    sizes="Van 8 tot 48 punt",
    compared=(
        "Dezelfde regel op 14 punt, in het standaardlettertype en daarna in het vrije "
        "lettertype"
    ),
    in_standard="{standard}, door de leessoftware getekend met haar eigen exemplaar",
    in_free="{face}, ingesloten in het bestand",
    width="{width} punt breed",
    characters=(
        "De {count} tekens die het lettertype via de codes van het standaardlettertype "
        "bereikt"
    ),
    stamps_title="Stempels en notities in een standaardlettertype",
    stamps_prose=(
        "Deze twee stempels en deze twee notities noemen een standaardlettertype. Voor "
        "elk ervan sluit het bestand het vrije lettertype met dezelfde breedtes in, en "
        "elke leessoftware toont ze met dezelfde letters."
    ),
    approved="GOEDGEKEURD",
    confidential="VERTROUWELIJK",
    notes=(
        "Gecontroleerd met de bestelling van 3 oktober.",
        "Totaal te bevestigen vóór betaling.",
    ),
    carried="{standard}, ingesloten als {face}",
    pangram="Pa’s wijze lynx bezag vroom het fikse aquaduct",
    decimal=",",
)


# Every language the specimen 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,
}

# The left edge of everything on a page.
LEFT = 56.0

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

# The first baseline of a page.
TOP = 790.0

# The sizes the line is set at, in points.
SIZES = (8.0, 10.0, 12.0, 14.0, 18.0, 24.0, 36.0, 48.0)

# The size from which the short line takes the place of the long one.
LARGE = 24.0

# The short line, set at the large sizes: letters and figures, which are the same in
# every language.
SHOWCASE = "Aa Gg Qq Éé 0123"

# The lines of the two faces that draw signs rather than letters: Symbol's Greek letters
# and signs, then ZapfDingbats's ornaments, each set at every size.
SIGNS = (
    "Σ √ ∫ αβγ ≥ ∞",
    "✓ ✈ ☎ ★ ✂ ❤",
)

# The characters of WinAnsiEncoding between codes 128 and 159, which are not the
# characters of the same numbers.
WINANSI_HIGH = "€‚ƒ„…†‡ˆ‰Š‹ŒŽ" "‘’“”•–—˜™š›œž" "Ÿ"

# How wide one cell of the grid of characters is.
CELL = 26.0

# The size the grid of characters is set at.
GRID_SIZE = 15.0

# The fourteen standard fonts, in the order the library lists them.
FOURTEEN = (
    hqf_pdf.StandardFont.courier,
    hqf_pdf.StandardFont.courier_bold,
    hqf_pdf.StandardFont.courier_bold_oblique,
    hqf_pdf.StandardFont.courier_oblique,
    hqf_pdf.StandardFont.helvetica,
    hqf_pdf.StandardFont.helvetica_bold,
    hqf_pdf.StandardFont.helvetica_bold_oblique,
    hqf_pdf.StandardFont.helvetica_oblique,
    hqf_pdf.StandardFont.times_roman,
    hqf_pdf.StandardFont.times_bold,
    hqf_pdf.StandardFont.times_bold_italic,
    hqf_pdf.StandardFont.times_italic,
    hqf_pdf.StandardFont.symbol,
    hqf_pdf.StandardFont.zapf_dingbats,
)


@dataclass(frozen=True)
class Face:
    """One face of the specimen: the standard font it stands in for, and the family it
    comes from."""

    standard: hqf_pdf.StandardFont
    family: hqf_pdf.FreeFamily

    @staticmethod
    def every() -> list[Face]:
        """The twelve Liberation faces, the twelve TeX Gyre faces, then HQF Symbol and
        HQF Dingbats, which both families share."""
        fonts = [make() for make in FOURTEEN]
        text = [font for font in fonts if not font.font_specific]
        signs = [font for font in fonts if font.font_specific]
        return (
            [Face(font, hqf_pdf.FreeFamily.Liberation) for font in text]
            + [Face(font, hqf_pdf.FreeFamily.TexGyre) for font in text]
            + [Face(font, hqf_pdf.FreeFamily.Liberation) for font in signs]
        )

    def licence(self) -> str:
        """The licence the face is given under."""
        name = self.standard.postscript_name
        if name == "Symbol":
            return "Apache License 2.0"
        if name == "ZapfDingbats":
            return "Bitstream Vera Fonts Copyright"
        if self.family == hqf_pdf.FreeFamily.TexGyre:
            return "GUST Font License"
        return "SIL Open Font License 1.1"

    def line(self, words: Words) -> str:
        """The line the face is set in: the pangram, or the signs it draws."""
        name = self.standard.postscript_name
        if name == "Symbol":
            return SIGNS[0]
        if name == "ZapfDingbats":
            return SIGNS[1]
        return words.pangram

    def characters(self, free: hqf_pdf.Font) -> list[str]:
        """The characters the face reaches through the standard font's codes: those of
        WinAnsiEncoding for a text font, every character the face maps for the other
        two; spaces left out."""
        if self.standard.font_specific:
            found = [character for character, __ in free.coverage]
        else:
            found = (
                [chr(code) for code in range(0x21, 0x7F)]
                + list(WINANSI_HIGH)
                + [chr(code) for code in range(0xA1, 0x100)]
            )
        kept = {
            character
            for character in found
            if not character.isspace() and character != "­"
        }
        return sorted(kept)


def figure(value: float, words: Words) -> str:
    """A figure with two decimals, written with the language's decimal mark."""
    return f"{value:.2f}".replace(".", words.decimal)


def block(content, handle, size, left, top, width, text) -> float:
    """Sets `text` in `handle` at `size` with its first baseline at `top`, wrapped to
    `width` from `left`, and hands back the ordinate the block ends at."""
    flow = hqf_pdf.TextFlow(handle, size)
    lines = flow.break_lines(text, width)
    flow.draw(content, lines, left, top, width)
    return top - flow.height(lines)


def contents(label, faces, words: Words) -> hqf_pdf.Page:
    """The first page: the title, the paragraph, and each face with its page."""
    content = hqf_pdf.Content()
    content.set_fill(hqf_pdf.Rgb.gray(0.0))
    top = block(content, label, 22.0, LEFT, TOP, WIDTH, words.title) - 10.0
    top = block(content, label, 10.0, LEFT, top, WIDTH, words.prose) - 18.0
    top = block(content, label, 13.0, LEFT, top, WIDTH, words.contents) - 16.0
    for at, (face, name) in enumerate(faces):
        page = words.page.replace("{page}", str(at + 2))
        content.set_fill(hqf_pdf.Rgb.gray(0.0))
        content.draw_text(label, 9.5, LEFT, top, name)
        content.set_fill(hqf_pdf.Rgb.gray(0.4))
        content.draw_text(label, 9.5, LEFT + 190.0, top, face.standard.postscript_name)
        content.draw_text(label, 9.5, LEFT + 400.0, top, page)
        top -= 16.0
    sheet = hqf_pdf.Page.a4()
    sheet.set_content(content)
    return sheet


def specimen(
    label, face: Face, name: str, free, handle, standard, words
) -> hqf_pdf.Page:
    """The page of one face: its name, what it stands in for and its licence, its line
    at every size, the line set twice, and every character it reaches."""
    content = hqf_pdf.Content()
    content.set_fill(hqf_pdf.Rgb.gray(0.0))
    # A symbol face has no letters to spell its own name with.
    named_in = label if face.standard.font_specific else handle
    top = block(content, named_in, 26.0, LEFT, TOP, WIDTH, name) - 4.0
    content.set_fill(hqf_pdf.Rgb.gray(0.4))
    drawn_to = words.drawn_to.replace(
        "{standard}", face.standard.postscript_name
    ).replace("{licence}", face.licence())
    top = block(content, label, 9.0, LEFT, top, WIDTH, drawn_to) - 16.0

    content.set_fill(hqf_pdf.Rgb.gray(0.0))
    top = block(content, label, 11.0, LEFT, top, WIDTH, words.sizes) - 6.0
    for size in SIZES:
        if size < LARGE or face.standard.font_specific:
            text = face.line(words)
        else:
            text = SHOWCASE
        top -= size * 0.9
        content.set_fill(hqf_pdf.Rgb.gray(0.5))
        content.draw_text(label, 7.0, LEFT, top, f"{size:.0f}")
        content.set_fill(hqf_pdf.Rgb.gray(0.0))
        content.draw_text(handle, size, LEFT + 26.0, top, text)
        top -= size * 0.35 + 6.0

    top = block(content, label, 11.0, LEFT, top - 12.0, WIDTH, words.compared) - 15.0
    text = face.line(words)
    for set_in, said in (
        (
            standard,
            words.in_standard.replace("{standard}", face.standard.postscript_name),
        ),
        (handle, words.in_free.replace("{face}", name)),
    ):
        width = words.width.replace(
            "{width}", figure(set_in.measure(text, 14.0), words)
        )
        content.set_fill(hqf_pdf.Rgb.gray(0.5))
        content.draw_text(label, 7.0, LEFT, top, f"{said} · {width}")
        content.set_fill(hqf_pdf.Rgb.gray(0.0))
        content.draw_text(set_in, 14.0, LEFT, top - 17.0, text)
        top -= 34.0

    characters = face.characters(free)
    heading = words.characters.replace("{count}", str(len(characters)))
    top = (
        block(content, label, 11.0, LEFT, top - 6.0, WIDTH, heading) - 10.0 - GRID_SIZE
    )
    columns = math.floor(WIDTH / CELL)
    for at, character in enumerate(characters):
        column = float(at % columns)
        row = float(at // columns)
        advance = handle.measure(character, GRID_SIZE)
        x = LEFT + column * CELL + (CELL - advance) * 0.5
        content.draw_text(handle, GRID_SIZE, x, top - row * 22.0, character)

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


def stamps(doc, label, words: Words) -> hqf_pdf.Page:
    """The last page: two stamps and two notes whose words are set in standard fonts,
    each with what the file carries for it."""
    content = hqf_pdf.Content()
    content.set_fill(hqf_pdf.Rgb.gray(0.0))
    top = block(content, label, 18.0, LEFT, TOP, WIDTH, words.stamps_title) - 8.0
    top = block(content, label, 10.0, LEFT, top, WIDTH, words.stamps_prose) - 30.0

    sheet = hqf_pdf.Page.a4()
    set_in = (
        hqf_pdf.StandardFont.helvetica_bold(),
        hqf_pdf.StandardFont.times_bold(),
        hqf_pdf.StandardFont.times_roman(),
        hqf_pdf.StandardFont.courier(),
    )
    for at, standard in enumerate(set_in):
        font = doc.add_standard_font(standard)
        face = standard.free_face(hqf_pdf.FreeFamily.Liberation)
        carried = words.carried.replace("{standard}", standard.postscript_name).replace(
            "{face}", face.postscript_name
        )
        y = top - 90.0
        if at < 2:
            if at == 0:
                icon, said = hqf_pdf.StampIcon.Approved, words.approved
                color = hqf_pdf.Rgb(0.0, 0.5, 0.0)
            else:
                icon, said = hqf_pdf.StampIcon.Confidential, words.confidential
                color = hqf_pdf.Rgb(0.75, 0.0, 0.0)
            annotation = (
                hqf_pdf.StampAnnotation(LEFT, y, 220.0, 70.0)
                .icon(icon)
                .words(hqf_pdf.Stamp(font, said, slant=hqf_pdf.Slant.Flat, color=color))
            )
        else:
            flow = hqf_pdf.TextFlow(font, 12.0)
            annotation = hqf_pdf.FreeTextAnnotation(
                LEFT, y + 30.0, 260.0, 40.0, flow, words.notes[at - 2]
            )
        sheet.add_annotation(annotation)
        content.set_fill(hqf_pdf.Rgb.gray(0.4))
        block(content, label, 9.0, LEFT + 290.0, y + 30.0, WIDTH - 290.0, carried)
        top -= 120.0
    sheet.set_content(content)
    return sheet


def build(words: Words) -> bytes:
    """The specimen, written in `words`."""
    doc = hqf_pdf.Document()
    doc.set_license(_licence.licensed())
    label = doc.add_font(
        hqf_pdf.StandardFont.helvetica().free_face(hqf_pdf.FreeFamily.Liberation)
    )
    faces = [
        (face, face.standard.free_face(face.family).postscript_name)
        for face in Face.every()
    ]
    doc.add_page(contents(label, faces, words))
    for face, name in faces:
        free = face.standard.free_face(face.family)
        handle = doc.add_font(free)
        standard = doc.add_standard_font(face.standard)
        doc.add_page(specimen(label, face, name, free, handle, standard, words))
    doc.add_page(stamps(doc, label, words))
    return doc.to_bytes()


def main() -> None:
    """Writes the specimen."""
    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
    # languages do not overwrite each other in `tmp/`.
    out = _out.output_path(
        Path(_language.file_name("type_specimen.pdf", language)).stem
    )
    data = build(words)

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


if __name__ == "__main__":
    main()
