"""Writes a month's activity report out of the shapes a page has no operator for.

The Python twin of the `write_shapes` example in Rust. PDF states a path with straight
segments and cubic curves and nothing else, so a circle is four curves and a slice of a
pie is an arc with two straight sides. Everything on this page is asked for by name — a
centre and a radius, two angles, three points — and the curves are worked out for it.

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

Usage: python examples/write_shapes.py [out.pdf] [font.ttf]
       HQF_PDF_LANG=fr python examples/write_shapes.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:
    """Every word the page draws, in one language."""

    # The page's title.
    title: str
    # What the page is about.
    lead: str
    # The heading over the pie.
    pie: str
    # What the four slices of the pie are called.
    slices: tuple[str, str, str, str]
    # The heading over the ring gauge.
    gauge: str
    # What the ring gauge counts.
    gauge_label: str
    # The heading over the card.
    card: str
    # What the line inside the card shows.
    card_label: str
    # The heading over the arcs.
    arcs: str
    # What the arc through three points is called.
    through: str
    # What the arc whose radii are enlarged until they reach is called.
    enlarged: str
    # What the four arcs of one ellipse are called.
    four: str
    # What a curve costs, and what it does not.
    caveat: str
    # The second page's title.
    pen_title: str
    # What the second page is about.
    pen_lead: str
    # The heading over the steps and the stars.
    steps: str
    # What each of the four stars is painted with.
    stars: tuple[str, str, str, str]
    # The heading over the ellipse, the slice and the turned arcs.
    ellipses: str
    # What the heart given as SVG path data is called.
    heart: str
    # The heading over the joins and the dash.
    corners: str
    # What the two zigzags' miter limits do.
    miters: tuple[str, str]
    # The heading over the three squares whose axes move.
    axes: str
    # What each of the three squares shows.
    squares: tuple[str, str, str]
    # The heading over the colours given by number.
    numbers: str


# The page in English.
ENGLISH = Words(
    title="The month, drawn in curves",
    lead=(
        "A page states a path with straight segments and cubic curves, and with "
        "nothing else: there is no circle operator and no arc operator. Every shape "
        "below was asked for by name — a centre and a radius, two angles, three points "
        "to pass through — and the curves were worked out for it. No piece bends "
        "further than a quarter turn, so a circle is four curves and the outline is "
        "out by a fortieth of a point on a circle an inch across."
    ),
    pie="Where the hours went",
    slices=(
        "Drawing office",
        "Plates and proofs",
        "Delivery",
        "Storage",
    ),
    gauge="How far into the year the work is",
    gauge_label="of the year's work is behind us",
    card="A card whose corners are eased off",
    card_label="Orders taken, week by week",
    arcs="Arcs told where to go, not how far to turn",
    through="One arc, through three points",
    enlarged="Radii enlarged",
    four="Four arcs, between the same two points",
    caveat=(
        "A curve costs six numbers and a straight segment costs two, so a circle is "
        "five lines of a page rather than one. What it does not cost is a picture: the "
        "shapes here are drawn by reading software at whatever size the page is shown, "
        "and they stay sharp at any of them."
    ),
    pen_title="Where the pen goes next",
    pen_lead=(
        "A point can be given from where the pen stands rather than from the corner of "
        "the page: a step across and up, or a turn and a length. A path is closed, "
        "filled and stroked in one operator, by either of the two rules that decide "
        "what is inside. The axes themselves can be moved, leaned, and put back as the "
        "page had them."
    ),
    steps="Steps from where the pen stands, and a star told by turns",
    stars=(
        "Even-odd, closed",
        "Non-zero, closed",
        "Even-odd, left open",
        "Outline, closed",
    ),
    ellipses="An ellipse, a slice closed on its centre, and an arc turned",
    heart="A heart given as SVG path data",
    corners="Pointed joins cut off past a limit, and a dash begun partway",
    miters=(
        "Limit 1.5: cut",
        "Limit 10: pointed",
    ),
    axes="Axes moved, leaned, and put back",
    squares=(
        "Moved",
        "Leaned",
        "Put back inside a turn",
    ),
    numbers="Colours given by their numbers: grey, then the four inks",
)

# The page in French.
FRENCH = Words(
    title="Le mois, en courbes",
    lead=(
        "Une page décrit un tracé avec des segments droits et des courbes cubiques, et "
        "rien d’autre : il n’y a ni opérateur de cercle ni opérateur d’arc. Chaque "
        "forme ci-dessous a été demandée par son nom — un centre et un rayon, deux "
        "angles, trois points par où passer — et les courbes ont été calculées pour "
        "elle. Aucun morceau ne tourne de plus d’un quart de tour : un cercle fait "
        "donc quatre courbes, et le contour s’écarte d’un quarantième de point sur un "
        "cercle de deux centimètres et demi."
    ),
    pie="Où sont passées les heures",
    slices=(
        "Bureau d’études",
        "Plaques et épreuves",
        "Livraison",
        "Entreposage",
    ),
    gauge="Où en est le travail de l’année",
    gauge_label="du travail de l’année est déjà fait",
    card="Une carte aux coins arrondis",
    card_label="Commandes reçues, semaine par semaine",
    arcs="Des arcs à qui l’on dit où aller, pas de combien tourner",
    through="Un arc, par trois points",
    enlarged="Rayons agrandis",
    four="Quatre arcs, entre les deux mêmes points",
    caveat=(
        "Une courbe coûte six nombres et un segment droit en coûte deux : un cercle "
        "fait donc cinq lignes de page au lieu d’une. Ce qu’il ne coûte pas, c’est une "
        "image : les formes ci-dessus sont tracées par le logiciel de lecture à la "
        "taille où la page est affichée, et restent nettes à toutes les tailles."
    ),
    pen_title="Où va la plume ensuite",
    pen_lead=(
        "Un point peut se donner depuis la position de la plume plutôt que depuis le "
        "coin de la page : un pas en largeur et en hauteur, ou un angle et une "
        "longueur. Un seul opérateur ferme un tracé, le remplit et en trace le contour, "
        "selon l’une ou l’autre des deux règles qui décident de ce qui est dedans. Les "
        "axes eux-mêmes se déplacent, s’inclinent, et reviennent tels que la page les "
        "avait."
    ),
    steps=(
        "Des pas depuis la position de la plume, et une étoile décrite par ses angles"
    ),
    stars=(
        "Pair-impair, fermée",
        "Non nul, fermée",
        "Pair-impair, ouverte",
        "Contour, fermée",
    ),
    ellipses="Une ellipse, une part refermée sur son centre, et un arc tourné",
    heart="Un cœur décrit par un chemin SVG",
    corners=(
        "Des angles vifs coupés au-delà d’une limite, et un tireté entamé en cours de "
        "motif"
    ),
    miters=(
        "Limite 1,5 : coupé",
        "Limite 10 : pointu",
    ),
    axes="Des axes déplacés, inclinés, puis remis en place",
    squares=(
        "Déplacé",
        "Incliné",
        "Remis droit dans une rotation",
    ),
    numbers="Des couleurs données par leurs valeurs : un gris, puis les quatre encres",
)

# The page in Dutch.
DUTCH = Words(
    title="De maand, getekend in curven",
    lead=(
        "Een pagina beschrijft een pad met rechte stukken en kubische curven, en met "
        "niets anders: er is geen cirkeloperator en geen boogoperator. Elke vorm "
        "hieronder is bij naam gevraagd (een middelpunt en een straal, twee hoeken, "
        "drie punten om door te gaan) en de curven zijn ervoor berekend. Geen stuk "
        "buigt verder dan een kwartslag, dus een cirkel is vier curven en de omtrek "
        "wijkt een veertigste punt af op een cirkel van een inch."
    ),
    pie="Waar de uren heen gingen",
    slices=(
        "Tekenkamer",
        "Platen en proeven",
        "Levering",
        "Opslag",
    ),
    gauge="Hoe ver in het jaar het werk is",
    gauge_label="van het werk van het jaar ligt achter ons",
    card="Een kaart met afgeronde hoeken",
    card_label="Orders binnen, week na week",
    arcs="Bogen die te horen krijgen waar ze heen gaan, niet hoever ze draaien",
    through="Eén boog, door drie punten",
    enlarged="Stralen vergroot",
    four="Vier bogen, tussen dezelfde twee punten",
    caveat=(
        "Een curve kost zes getallen en een recht stuk twee, dus een cirkel is vijf "
        "regels van een pagina in plaats van één. Wat ze niet kost is een afbeelding: "
        "de vormen hier worden door leessoftware getekend op het formaat waarop de "
        "pagina getoond wordt, en blijven op elk formaat scherp."
    ),
    pen_title="Waar de pen daarna heen gaat",
    pen_lead=(
        "Een punt kan gegeven worden vanaf waar de pen staat in plaats van vanaf de "
        "hoek van de pagina: een stap opzij en omhoog, of een draai en een lengte. "
        "Een pad wordt gesloten, gevuld en getrokken met één operator, volgens een "
        "van de twee regels die bepalen wat binnen ligt. De assen zelf kunnen "
        "verschoven, schuin gezet en teruggezet worden zoals de pagina ze had."
    ),
    steps="Stappen vanaf waar de pen staat, en een ster gegeven in draaien",
    stars=(
        "Even-oneven, dicht",
        "Niet-nul, gesloten",
        "Even-oneven, open",
        "Omtrek, gesloten",
    ),
    ellipses="Een ellips, een punt gesloten op zijn middelpunt, en een gedraaide boog",
    heart="Een hart beschreven met SVG-padgegevens",
    corners="Puntige hoeken afgeknot voorbij een grens, en een streep halverwege begonnen",
    miters=(
        "Grens 1,5: afgeknot",
        "Grens 10: puntig",
    ),
    axes="Assen verschoven, schuin gezet en teruggezet",
    squares=(
        "Verschoven",
        "Schuin gezet",
        "Teruggezet binnen een draai",
    ),
    numbers="Kleuren gegeven met hun getallen: grijs, dan de vier inkten",
)

# The page in Portuguese.
PORTUGUESE = Words(
    title="O mês, desenhado em curvas",
    lead=(
        "Uma página só conhece traçados feitos de segmentos retos e curvas cúbicas, e "
        "nada mais: não há operador de círculo nem de arco. Cada forma abaixo foi "
        "pedida pelo nome (um centro e um raio, dois ângulos, três pontos por onde "
        "passar) e as curvas foram calculadas para ela. Nenhuma peça dobra mais de um "
        "quarto de volta, por isso um círculo são quatro curvas e o contorno "
        "desvia-se um quadragésimo de ponto num círculo de uma polegada."
    ),
    pie="Para onde foram as horas",
    slices=(
        "Gabinete técnico",
        "Chapas e provas",
        "Entrega",
        "Armazenagem",
    ),
    gauge="Em que ponto do ano está o trabalho",
    gauge_label="do trabalho do ano já ficou para trás",
    card="Um cartão de cantos suavizados",
    card_label="Encomendas recebidas, semana a semana",
    arcs="Arcos a que se diz para onde ir, não quanto rodar",
    through="Um arco, por três pontos",
    enlarged="Raios maiores",
    four="Quatro arcos, entre os mesmos dois pontos",
    caveat=(
        "Uma curva custa seis números e um segmento reto dois, por isso um círculo "
        "são cinco linhas de página e não uma. O que não custa é uma imagem: as "
        "formas aqui são desenhadas pelo programa de leitura ao tamanho a que a "
        "página for mostrada, e ficam nítidas em qualquer um."
    ),
    pen_title="Para onde vai a caneta a seguir",
    pen_lead=(
        "Um ponto pode ser dado a partir de onde está a caneta em vez do canto da "
        "página: um passo na largura e para cima, ou uma rotação e um comprimento. Um "
        "traçado fecha-se, preenche-se e traça-se com um só operador, por qualquer "
        "das duas regras que decidem o que está dentro. Os próprios eixos podem ser "
        "deslocados, inclinados e repostos como a página os tinha."
    ),
    steps="Passos a partir de onde está a caneta, e uma estrela dada por rotações",
    stars=(
        "Par-ímpar, fechado",
        "Não nulo, fechado",
        "Par-ímpar, aberto",
        "Contorno, fechado",
    ),
    ellipses="Uma elipse, uma fatia fechada no centro e um arco rodado",
    heart="Um coração definido por um caminho SVG",
    corners="Junções em bico cortadas para lá de um limite, e um tracejado começado a meio",
    miters=(
        "Limite 1,5: cortada",
        "Limite 10: em bico",
    ),
    axes="Eixos deslocados, inclinados e repostos",
    squares=(
        "Deslocados",
        "Inclinados",
        "Repostos dentro de uma rotação",
    ),
    numbers="Cores dadas pelos seus números: cinzento, depois as quatro tintas",
)

# The page in Italian.
ITALIAN = Words(
    title="Il mese, disegnato in curve",
    lead=(
        "Una pagina dà un tracciato con segmenti retti e curve cubiche, e "
        "nient’altro: non esiste un operatore di cerchio né di arco. Ogni forma qui "
        "sotto è stata chiesta per nome (un centro e un raggio, due angoli, tre punti "
        "per cui passare) e le curve sono state calcolate apposta. Nessun pezzo gira "
        "più di un quarto di giro, così un cerchio è fatto di quattro curve e il "
        "contorno sbaglia di un quarantesimo di punto su un cerchio di un pollice."
    ),
    pie="Dove sono andate le ore",
    slices=(
        "Ufficio tecnico",
        "Lastre e prove",
        "Consegna",
        "Deposito",
    ),
    gauge="A che punto dell’anno è il lavoro",
    gauge_label="del lavoro dell’anno è alle spalle",
    card="Un biglietto dagli angoli smussati",
    card_label="Ordini ricevuti, settimana per settimana",
    arcs="Archi a cui si dice dove andare, non quanto girare",
    through="Un arco, per tre punti",
    enlarged="Raggi ingranditi",
    four="Quattro archi, tra gli stessi due punti",
    caveat=(
        "Una curva costa sei numeri e un segmento retto due, così un cerchio occupa "
        "cinque righe di pagina anziché una. Ciò che non costa è un’immagine: le "
        "forme qui sono disegnate dal programma di lettura alla dimensione a cui è "
        "mostrata la pagina, e restano nitide a ognuna."
    ),
    pen_title="Dove va poi la penna",
    pen_lead=(
        "Un punto può essere dato da dove sta la penna anziché dall’angolo della "
        "pagina: un passo in larghezza e in alto, o una rotazione e una lunghezza. Un "
        "tracciato si chiude, si riempie e si traccia con un solo operatore, secondo "
        "l’una o l’altra delle due regole che decidono cosa sta dentro. Gli assi "
        "stessi si possono spostare, inclinare e rimettere come li aveva la pagina."
    ),
    steps="Passi da dove sta la penna, e una stella data per rotazioni",
    stars=(
        "Pari-dispari, chiuso",
        "Non zero, chiuso",
        "Pari-dispari, aperto",
        "Contorno, chiuso",
    ),
    ellipses="Un’ellisse, una fetta chiusa sul centro e un arco ruotato",
    heart="Un cuore definito da un tracciato SVG",
    corners="Giunzioni a punta tagliate oltre un limite, e un tratteggio iniziato a metà",
    miters=(
        "Limite 1,5: tagliata",
        "Limite 10: a punta",
    ),
    axes="Assi spostati, inclinati e rimessi",
    squares=(
        "Spostati",
        "Inclinati",
        "Rimessi dentro una rotazione",
    ),
    numbers="Colori dati per numeri: grigio, poi i quattro inchiostri",
)

# The page in Spanish.
SPANISH = Words(
    title="El mes, dibujado en curvas",
    lead=(
        "Una página da un trazado con segmentos rectos y curvas cúbicas, y con nada "
        "más: no hay operador de círculo ni de arco. Cada forma de abajo se pidió por "
        "su nombre —un centro y un radio, dos ángulos, tres puntos por los que pasar— "
        "y las curvas se calcularon para ella. Ninguna pieza gira más de un cuarto de "
        "vuelta, así que un círculo son cuatro curvas y el contorno se desvía la "
        "cuadragésima parte de un punto en un círculo de una pulgada."
    ),
    pie="Adónde se fueron las horas",
    slices=(
        "Oficina técnica",
        "Planchas y pruebas",
        "Entrega",
        "Almacenaje",
    ),
    gauge="Cuánto del año lleva hecho el trabajo",
    gauge_label="del trabajo del año ya está hecho",
    card="Una tarjeta de esquinas suavizadas",
    card_label="Pedidos recibidos, semana a semana",
    arcs="Arcos a los que se dice adónde ir, no cuánto girar",
    through="Un arco, por tres puntos",
    enlarged="Radios ampliados",
    four="Cuatro arcos, entre los mismos dos puntos",
    caveat=(
        "Una curva cuesta seis números y un segmento recto dos, así que un círculo "
        "son cinco líneas de página y no una. Lo que no cuesta es una imagen: las "
        "formas de aquí las dibuja el programa de lectura al tamaño en que se muestre "
        "la página, y se ven nítidas a cualquier tamaño."
    ),
    pen_title="Adónde va la pluma después",
    pen_lead=(
        "Un punto puede darse desde donde está la pluma en vez de desde la esquina de "
        "la página: un paso a lo ancho y hacia arriba, o un giro y una longitud. Un "
        "trazado se cierra, se rellena y se traza con un solo operador, por "
        "cualquiera de las dos reglas que deciden qué está dentro. Los propios ejes "
        "pueden moverse, inclinarse y volver a como los tenía la página."
    ),
    steps="Pasos desde donde está la pluma, y una estrella dada por giros",
    stars=(
        "Par-impar, cerrado",
        "No nulo, cerrado",
        "Par-impar, abierto",
        "Contorno, cerrado",
    ),
    ellipses="Una elipse, una porción cerrada en su centro y un arco girado",
    heart="Un corazón definido con datos de trazado SVG",
    corners="Uniones en punta cortadas pasado un límite, y un guion empezado a medias",
    miters=(
        "Límite 1,5: cortada",
        "Límite 10: en punta",
    ),
    axes="Ejes movidos, inclinados y restablecidos",
    squares=(
        "Movidos",
        "Inclinados",
        "Restablecidos dentro de un giro",
    ),
    numbers="Colores dados por sus números: gris, luego las cuatro tintas",
)

# The page in German.
GERMAN = Words(
    title="Der Monat, in Kurven gezeichnet",
    lead=(
        "Eine Seite gibt einen Pfad mit geraden Abschnitten und kubischen Kurven an, "
        "und mit nichts anderem: Es gibt keinen Operator für Kreise und keinen für "
        "Bögen. Jede Form unten wurde beim Namen verlangt — ein Mittelpunkt und ein "
        "Radius, zwei Winkel, drei Punkte, durch die sie läuft —, und die Kurven "
        "wurden für sie berechnet. Kein Stück biegt sich weiter als eine "
        "Vierteldrehung, also ist ein Kreis vier Kurven, und die Kontur weicht bei "
        "einem Kreis von einem Zoll Durchmesser um ein Vierzigstel Punkt ab."
    ),
    pie="Wohin die Stunden gingen",
    slices=(
        "Zeichenbüro",
        "Platten und Andrucke",
        "Lieferung",
        "Lagerung",
    ),
    gauge="Wie weit die Jahresarbeit fortgeschritten ist",
    gauge_label="der Jahresarbeit liegen hinter uns",
    card="Eine Karte mit abgerundeten Ecken",
    card_label="Aufträge, Woche für Woche",
    arcs="Bögen, denen gesagt wird, wohin, nicht wie weit sie drehen",
    through="Ein Bogen, durch drei Punkte",
    enlarged="Größere Radien",
    four="Vier Bögen zwischen denselben zwei Punkten",
    caveat=(
        "Eine Kurve kostet sechs Zahlen und ein gerader Abschnitt zwei, also ist ein "
        "Kreis fünf Zeilen einer Seite statt einer. Was er nicht kostet, ist ein "
        "Bild: Die Formen hier zeichnet die Lesesoftware in jeder Größe, in der die "
        "Seite gezeigt wird, und sie bleiben in jeder scharf."
    ),
    pen_title="Wohin der Stift als Nächstes geht",
    pen_lead=(
        "Ein Punkt kann von dort aus angegeben werden, wo der Stift steht, statt von "
        "der Ecke der Seite: ein Schritt nach rechts und nach oben oder eine Drehung "
        "und eine Länge. Ein Pfad wird mit einem Operator geschlossen, gefüllt und "
        "gezogen, nach einer der beiden Regeln, die bestimmen, was innen liegt. Die "
        "Achsen selbst lassen sich verschieben, neigen und so zurücksetzen, wie die "
        "Seite sie hatte."
    ),
    steps="Schritte von der Stelle des Stifts, und ein Stern aus Drehungen",
    stars=(
        "Gerade/ungerade, zu",
        "Nicht null, zu",
        "Gerade/ungerade",
        "Kontur, geschlossen",
    ),
    ellipses="Eine Ellipse, ein zur Mitte geschlossenes Stück und ein gedrehter Bogen",
    heart="Ein Herz aus SVG-Pfaddaten",
    corners="Spitze Verbindungen jenseits einer Grenze abgeschnitten, und ein mittendrin begonnener Strich",
    miters=(
        "Grenze 1,5: abgeschnitten",
        "Grenze 10: spitz",
    ),
    axes="Achsen verschoben, geneigt und zurückgesetzt",
    squares=(
        "Verschoben",
        "Geneigt",
        "In einer Drehung zurückgesetzt",
    ),
    numbers="Farben nach ihren Zahlen: Grau, dann die vier Farben",
)


# Every language the example 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 the page.
LEFT = 72.0

# How wide a block of text, and the card, are.
WIDTH = 451.0

# The hours the four slices of the pie stand for.
HOURS = (64, 41, 27, 18)

# The orders taken in each of seven weeks, which the line in the card draws.
ORDERS = (26, 38, 31, 52, 44, 61, 57)

# The most orders the line in the card leaves room for.
CEILING = 70.0

# How far round the year the gauge has come.
DONE = 0.62

# How far in from the left edge the arc whose radii are enlarged begins.
ENLARGED_AT = 212.0

# How much room the name of the arc whose radii are enlarged has.
ENLARGED_ROOM = 76.0

# The radii the arc whose radii are enlarged is asked for, too short to reach across the
# sixty points between its two ends.
SHORT_RADII = (6.0, 3.0)


# How far in from the left edge the name of the heart begins, under the ellipse's right
# end, below everything else in that row.
HEART_NAME_AT = 110.0

# How much room the name of the heart has: up to where the turned arcs begin.
HEART_ROOM = 185.0

# The heart beside the ellipse, as SVG path data on a square a hundred units across: two
# lobes of cubic curves, each carried on by a smooth one, and a round window of two
# relative arcs.
HEART = (
    "M50 30C50 10 10 10 10 35S40 70 50 90C60 70 90 60 90 35S50 10 50 30Z"
    "M22 34a6 6 0 1 1 12 0a6 6 0 1 1-12 0z"
)


def palette() -> list[hqf_pdf.Rgb]:
    """The four colours the slices and the line are drawn in."""
    return [
        hqf_pdf.Rgb(0.16, 0.33, 0.62),
        hqf_pdf.Rgb(0.30, 0.57, 0.75),
        hqf_pdf.Rgb(0.55, 0.73, 0.82),
        hqf_pdf.Rgb(0.79, 0.86, 0.90),
    ]


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 label(
    content: hqf_pdf.Content,
    handle: hqf_pdf.FontHandle,
    size: float,
    x: float,
    y: float,
    text: str,
    room: float,
) -> None:
    """Sets one line of words at `(x, y)`."""
    flow = hqf_pdf.TextFlow(handle, size)
    flow.draw(content, flow.break_lines(text, room), x, y, room)


def shares() -> list[float]:
    """What share of the hours each slice of the pie stands for."""
    total = 0
    for hours in HOURS:
        total += hours
    return [hours / total for hours in HOURS]


def pie(
    content: hqf_pdf.Content,
    handle: hqf_pdf.FontHandle,
    words: Words,
    cx: float,
    cy: float,
) -> None:
    """Draws the pie about `(cx, cy)`, and names each slice beside it.

    A slice is one arc with two straight sides: the arc writes where it begins, a
    segment runs back to the centre, and closing the subpath draws the other side. Each
    swatch beside a name is a closed run of four corners grooved by a radius below
    nothing, which cuts every corner in rather than off.
    """
    radius = 68.0
    start = math.pi / 2.0
    for share, colour in zip(shares(), palette()):
        sweep = math.tau * share
        content.save_state()
        content.set_fill(colour)
        content.arc(cx, cy, radius, start, -sweep)
        content.line_to(cx, cy)
        content.close_path()
        content.fill()
        content.restore_state()
        start -= sweep

    # The names sit to the right of the pie, one under the other, each behind a swatch
    # of its own colour.
    y = cy + 46.0
    for colour, name in zip(palette(), words.slices):
        content.save_state()
        content.set_fill(colour)
        left, bottom = cx + 96.0, y
        corners = [
            (left, bottom),
            (left + 12.0, bottom),
            (left + 12.0, bottom + 12.0),
            (left, bottom + 12.0),
        ]
        content.rounded_polyline(corners, -3.0, close=True)
        content.fill()
        content.restore_state()
        label(content, handle, 9.5, cx + 116.0, y + 3.0, name, 180.0)
        y -= 26.0


def gauge(content: hqf_pdf.Content, cx: float, cy: float) -> None:
    """Draws the ring gauge about `(cx, cy)`, the part that is done in the darkest
    colour of the palette.

    The pale track is one circle laid inside another and filled by the even-odd rule,
    which leaves the ring between them. The part that is done is an arc drawn with a
    thick pen over that ring.
    """
    content.save_state()
    content.set_fill(hqf_pdf.Rgb(0.90, 0.92, 0.95))
    content.circle(cx, cy, 46.0)
    content.circle(cx, cy, 30.0)
    content.fill_even_odd()
    content.restore_state()

    content.save_state()
    content.set_line_width(16.0)
    content.set_stroke(palette()[0])
    content.arc(cx, cy, 38.0, math.pi / 2.0, -(math.tau * DONE))
    content.stroke()
    content.restore_state()


def card(
    content: hqf_pdf.Content,
    handle: hqf_pdf.FontHandle,
    words: Words,
    top: float,
) -> float:
    """Draws the card and the line of orders inside it, and hands back the ordinate the
    card ends at."""
    height = 96.0
    bottom = top - height

    content.save_state()
    content.set_fill(hqf_pdf.Rgb(0.96, 0.97, 0.98))
    content.set_stroke(hqf_pdf.Rgb.gray(0.72))
    content.set_line_width(0.8)
    content.rounded_rect(LEFT, bottom, WIDTH, height, 14.0)
    content.fill_and_stroke()
    content.restore_state()

    label(content, handle, 9.0, LEFT + 18.0, top - 20.0, words.card_label, 300.0)

    # The seven weeks are spaced evenly across the card, and each order count is
    # measured up from the floor of the plot.
    floor = bottom + 18.0
    plot = 52.0
    step = (WIDTH - 36.0) / 6.0
    points = []
    x = LEFT + 18.0
    for orders in ORDERS:
        share = orders / CEILING
        points.append((x, floor + plot * share))
        x += step

    content.save_state()
    content.set_line_width(2.2)
    content.set_stroke(palette()[1])
    content.rounded_polyline(points, 14.0)
    content.stroke()
    content.restore_state()
    return bottom


def curves(
    content: hqf_pdf.Content,
    handle: hqf_pdf.FontHandle,
    words: Words,
    top: float,
) -> None:
    """Draws the arc through three points, the arc whose radii are enlarged, and the
    four arcs of one ellipse, and names all three."""
    # The two figures bulge above and below the line they are drawn on, so both are
    # named over their own drawing rather than under it.
    base = top - 74.0

    label(content, handle, 8.5, LEFT, top - 12.0, words.through, 200.0)
    content.save_state()
    content.set_line_width(1.6)
    content.set_stroke(palette()[0])
    content.move_to(LEFT, base)
    content.arc_through(LEFT, base, LEFT + 100.0, base + 26.0, LEFT + 200.0, base)
    content.stroke()
    content.restore_state()

    # Radii too short to reach from one end to the other are refused unless the shape
    # asks for them to be enlarged: both are scaled by the same amount, and the arc is
    # half of the ellipse that comes of it.
    start = LEFT + ENLARGED_AT
    label(content, handle, 8.5, start, top - 12.0, words.enlarged, ENLARGED_ROOM)
    content.save_state()
    content.set_line_width(1.6)
    content.set_stroke(palette()[0])
    content.move_to(start, base)
    content.elliptical_arc(
        start,
        base,
        start + 60.0,
        base,
        SHORT_RADII[0],
        SHORT_RADII[1],
        hqf_pdf.EllipticalArc(clockwise=True, enlarge_radii=True),
    )
    content.stroke()
    content.restore_state()

    # The same two ends, the same two radii, and the four arcs that answer them: the
    # long way round and the short, each walked either way.
    from_x = LEFT + 300.0
    to_x = LEFT + 400.0
    shapes = [
        hqf_pdf.EllipticalArc(),
        hqf_pdf.EllipticalArc(clockwise=True),
        hqf_pdf.EllipticalArc(large=True),
        hqf_pdf.EllipticalArc(large=True, clockwise=True),
    ]
    label(content, handle, 8.5, from_x, top - 12.0, words.four, 220.0)
    for shape, colour in zip(shapes, palette()):
        content.save_state()
        content.set_line_width(1.6)
        content.set_stroke(colour)
        content.move_to(from_x, base)
        content.elliptical_arc(from_x, base, to_x, base, 62.0, 18.0, shape)
        content.stroke()
        content.restore_state()


def steps_and_stars(
    content: hqf_pdf.Content, handle: hqf_pdf.FontHandle, words: Words, top: float
) -> None:
    """Draws the steps and the four stars from `top` down.

    The steps are given from where the pen stands, and the second flight begins a step
    away from where the first one ends. Each star is five equal sides, each turned two
    fifths of a turn from the one before.
    """
    content.save_state()
    content.set_stroke(hqf_pdf.Color.gray(0.2))
    content.set_line_width(1.4)
    content.move_to(LEFT, top - 70.0)
    for __ in range(4):
        content.line_by(14.0, 0.0)
        content.line_by(0.0, 14.0)
    content.move_by(10.0, -56.0)
    for __ in range(4):
        content.line_by(10.0, 0.0)
        content.line_by(0.0, 10.0)
    content.stroke()
    content.restore_state()

    side = 50.0
    turn = 0.4 * math.tau
    left = LEFT + 130.0
    for index, name in enumerate(words.stars):
        content.save_state()
        content.set_fill(palette()[1])
        content.set_stroke(palette()[0])
        content.set_line_width(1.2)
        content.move_to(left, top - 22.0)
        content.move_polar(0.0, 0.0)
        angle = 0.0
        for __ in range(4):
            content.line_polar(angle, side)
            angle -= turn
        if index == 0:
            content.close_fill_and_stroke_even_odd()
        elif index == 1:
            content.close_fill_and_stroke()
        elif index == 2:
            content.fill_and_stroke_even_odd()
        else:
            content.close_and_stroke()
        content.restore_state()
        label(content, handle, 7.5, left, top - 88.0, name, 80.0)
        left += 84.0


def ellipses(
    content: hqf_pdf.Content,
    handle: hqf_pdf.FontHandle,
    words: Words,
    top: float,
) -> None:
    """Draws an ellipse, a heart given as SVG path data, a slice closed on its centre
    and an arc on a turned ellipse, from `top` down."""
    middle = top - 40.0
    content.save_state()
    content.set_fill(palette()[2])
    content.ellipse(LEFT + 60.0, middle, 60.0, 30.0)
    content.fill()

    # The heart's square of a hundred units fitted into a box of fifty points, and its
    # window left open by the even-odd rule.
    heart = hqf_pdf.SvgPath.parse(HEART)
    content.set_fill(palette()[1])
    content.svg_path_in(
        heart,
        (LEFT + 130.0, middle - 25.0, 50.0, 50.0),
        hqf_pdf.FitBox(hqf_pdf.FitMode.Contain),
        view=(0.0, 0.0, 100.0, 100.0),
    )
    content.fill_even_odd()

    # The slice: out from the centre to the rim, round, and closed back.
    cx, cy, radius = LEFT + 200.0, middle - 20.0, 50.0
    content.set_fill(palette()[0])
    content.move_to(cx, cy)
    content.line_to(cx + radius, cy)
    content.arc_to(cx, cy, radius, 0.0, math.pi / 2 * 0.8)
    content.close_path()
    content.fill()

    # The same two ends and radii, the ellipse turned a twelfth of a turn and then a
    # third.
    for turned, colour in ((math.tau / 12.0, palette()[1]), (math.tau / 3.0, palette()[0])):
        content.set_stroke(colour)
        content.set_line_width(1.6)
        content.move_to(LEFT + 300.0, middle)
        content.elliptical_arc(
            LEFT + 300.0,
            middle,
            LEFT + 400.0,
            middle,
            60.0,
            50.0,
            hqf_pdf.EllipticalArc(x_rotation=turned),
        )
        content.stroke()
    content.restore_state()
    label(
        content,
        handle,
        7.5,
        LEFT + HEART_NAME_AT,
        top - 82.0,
        words.heart,
        HEART_ROOM,
    )


def corners(
    content: hqf_pdf.Content, handle: hqf_pdf.FontHandle, words: Words, top: float
) -> None:
    """Draws two zigzags whose miter limits differ, and a dashed line whose pattern
    begins partway, from `top` down."""
    left = LEFT
    for limit, name in zip((1.5, 10.0), words.miters):
        content.save_state()
        content.set_stroke(palette()[0])
        content.set_line_width(7.0)
        content.set_line_style(hqf_pdf.LineStyle(join=hqf_pdf.LineJoin.Miter))
        content.set_miter_limit(limit)
        content.move_to(left, top - 60.0)
        content.line_to(left + 20.0, top - 20.0)
        content.line_to(left + 40.0, top - 60.0)
        content.line_to(left + 60.0, top - 20.0)
        content.stroke()
        content.restore_state()
        label(content, handle, 7.5, left, top - 78.0, name, 120.0)
        left += 130.0

    dash = hqf_pdf.Dash([9.0, 3.0, 2.0, 3.0], phase=5.0)
    lengths = " ".join(f"{length:.0f}" for length in dash.lengths)
    pattern = f"[{lengths}] {dash.phase:.0f}"
    content.save_state()
    content.set_stroke(palette()[1])
    content.set_line_width(3.0)
    content.set_line_style(
        hqf_pdf.LineStyle(
            dash=dash,
            cap=hqf_pdf.LineCap.Round,
            join=hqf_pdf.LineJoin.Miter,
            miter_limit=4.0,
        )
    )
    content.move_to(LEFT + 270.0, top - 30.0)
    content.line_to(LEFT + 440.0, top - 30.0)
    content.line_to(LEFT + 400.0, top - 60.0)
    content.stroke()
    content.restore_state()
    label(content, handle, 7.5, LEFT + 270.0, top - 78.0, pattern, 160.0)


def axes(
    content: hqf_pdf.Content, handle: hqf_pdf.FontHandle, words: Words, top: float
) -> None:
    """Draws the same square three times, from `top` down: its axes moved, leaned, and
    put back as the page had them inside a turn."""
    bottom = top - 70.0
    content.save_state()
    content.set_fill(palette()[2])
    content.translate(LEFT, bottom)
    content.rect(0.0, 0.0, 50.0, 50.0)
    content.fill()
    content.restore_state()

    content.save_state()
    content.set_fill(palette()[1])
    content.translate(LEFT + 150.0, bottom)
    content.skew(math.tau / 12.0, 0.0)
    content.rect(0.0, 0.0, 50.0, 50.0)
    content.fill()
    content.restore_state()

    # Inside a turn of an eighth, the axes are put back as the page had them, so the
    # square stands upright where the turn would have tipped it.
    content.save_state()
    content.rotate(math.tau / 8.0, LEFT + 325.0, bottom + 25.0)
    content.replace_transform((1.0, 0.0, 0.0, 1.0, LEFT + 300.0, bottom))
    content.set_fill(palette()[0])
    content.rect(0.0, 0.0, 50.0, 50.0)
    content.fill()
    content.restore_state()

    left = LEFT
    for name in words.squares:
        label(content, handle, 7.5, left, bottom - 14.0, name, 140.0)
        left += 150.0


def numbers(content: hqf_pdf.Content, handle: hqf_pdf.FontHandle, top: float) -> None:
    """Paints a row of swatches whose colours are given by their numbers, from `top`
    down: three greys, then a colour in the four inks, filled and outlined, and the
    same colour named as a device colour."""
    bottom = top - 40.0
    left = LEFT
    for grey in (0.25, 0.5, 0.75):
        content.set_fill(hqf_pdf.Color.gray(grey))
        content.rect(left, bottom, 40.0, 30.0)
        content.fill()
        left += 50.0

    content.save_state()
    content.set_fill(hqf_pdf.Cmyk(0.0, 0.6, 0.9, 0.0))
    content.set_stroke(hqf_pdf.Cmyk(0.9, 0.5, 0.0, 0.2))
    content.set_line_width(4.0)
    content.rect(left + 10.0, bottom, 40.0, 30.0)
    content.fill_and_stroke()
    content.restore_state()
    left += 70.0

    inks = hqf_pdf.Color.cmyk(0.0, 0.6, 0.9, 0.0)
    light = hqf_pdf.Color.rgb(0.30, 0.57, 0.75)
    for colour in (inks, light):
        content.save_state()
        content.set_fill(colour)
        content.rect(left, bottom, 40.0, 30.0)
        content.fill()
        content.restore_state()
        values = " ".join(f"{value:.2f}" for value in colour.to_components())
        label(content, handle, 7.5, left, bottom - 14.0, values, 110.0)
        left += 120.0


def pen_page(content: hqf_pdf.Content, text: hqf_pdf.FontHandle, words: Words) -> None:
    """Draws the second page."""
    top = 790.0
    top = block(content, text, 17.0, top, words.pen_title) - 12.0
    top = block(content, text, 9.5, top, words.pen_lead) - 22.0

    top = block(content, text, 11.0, top, words.steps) - 12.0
    steps_and_stars(content, text, words, top)
    top -= 110.0

    top = block(content, text, 11.0, top, words.ellipses) - 12.0
    ellipses(content, text, words, top)
    top -= 100.0

    top = block(content, text, 11.0, top, words.corners) - 12.0
    corners(content, text, words, top)
    top -= 100.0

    top = block(content, text, 11.0, top, words.axes) - 12.0
    axes(content, text, words, top)
    top -= 110.0

    top = block(content, text, 11.0, top, words.numbers) - 12.0
    numbers(content, text, top)


def build(words: Words, font: Path) -> bytes:
    """Draws the whole page."""
    doc = hqf_pdf.Document()
    doc.set_license(_licence.licensed())
    text = doc.add_font(hqf_pdf.Font.from_path(font))

    content = hqf_pdf.Content()
    top = 790.0
    top = block(content, text, 17.0, top, words.title) - 12.0
    top = block(content, text, 9.5, top, words.lead) - 22.0

    top = block(content, text, 11.0, top, words.pie) - 12.0
    pie(content, text, words, LEFT + 74.0, top - 74.0)
    top -= 152.0

    top = block(content, text, 11.0, top, words.gauge) - 12.0
    gauge(content, LEFT + 52.0, top - 50.0)
    label(content, text, 9.5, LEFT + 118.0, top - 46.0, words.gauge_label, 280.0)
    top -= 100.0

    top = block(content, text, 11.0, top, words.card) - 12.0
    top = card(content, text, words, top) - 24.0

    top = block(content, text, 11.0, top, words.arcs) - 12.0
    curves(content, text, words, top)
    top -= 118.0

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

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

    content = hqf_pdf.Content()
    pen_page(content, text, words)
    page = hqf_pdf.Page.a4()
    page.set_content(content)
    doc.add_page(page)
    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("shapes.pdf", language)).stem)

    data = build(words, _out.font_path())

    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()
