"""Paints the four kinds of mesh, every point carrying its own colour.

The Python twin of the `write_mesh` example in Rust. A mesh states its own shape. It is
handed points, each a place and a colour, and the colour anywhere between them is worked
out from the points around it. The four kinds differ in how they say which points belong
together, and in whether the sides between them run straight or bend.

Four panels, one per kind: six free-form triangles round a centre they share, their
edges traced over them; a lattice of rows of points in the four printing inks, its rows
and columns traced; a Coons patch whose four sides are curves; and a tensor-product
patch with the same sides and the same corners, four points inside it pulling its
colours about.

A second page hands the same four meshes over as fill patterns, and fills a circle at
the centre of each panel with one: the circle shows the part of the mesh that lies where
it is.

The title, the four labels and the legend are held in `Words`, once per language, and
`HQF_PDF_LANG` picks which set is written.

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

from __future__ import annotations

from dataclasses import dataclass
from pathlib import Path

import _language
import _licence
import _out

import hqf_pdf

# The width and height of every panel, in points.
BOX = (210.0, 200.0)

# How far the corners of the hexagon of triangles stand from its centre.
RADIUS = 95.0

# Half the square root of three: how far to the side of the centre of a hexagon each of
# its four slanting corners stands, as a share of the radius.
HALF_ROOT_THREE = 0.8660254037844386

# How many cells the lattice runs across; a row holds one point more.
COLUMNS = 6

# How many cells the lattice runs up; the lattice holds one row more.
ROWS = 5

# How far the legend runs across, between the margins.
WIDTH = 455.0

# The colour at each corner of both patches, in the order the corners come in: the
# bottom left, the top left, the top right, the bottom right.
PATCH_CORNERS = (
    hqf_pdf.Rgb(0.9, 0.15, 0.1),
    hqf_pdf.Rgb(0.95, 0.8, 0.1),
    hqf_pdf.Rgb(0.1, 0.3, 0.9),
    hqf_pdf.Rgb(0.1, 0.7, 0.35),
)


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

    # The title across the top of the page.
    title: str
    # The label under the free-form triangles.
    triangles: str
    # The label under the lattice.
    lattice: str
    # The label under the Coons patch.
    coons: str
    # The label under the tensor-product patch.
    tensor: str
    # The paragraph at the foot of the page, saying what is above it.
    legend: str
    # The line at the head of the second page.
    patterns_title: str
    # The paragraph at the foot of the second page.
    patterns: str


# The page in English.
ENGLISH = Words(
    title="Four kinds of mesh, a colour at every point",
    triangles="Free-form: six triangles, one by one",
    lattice="A lattice: rows of points, four inks",
    coons="A Coons patch: four curved sides",
    tensor="A tensor patch: same sides, pulled inside",
    legend=(
        "Every point above carries a place and a colour, and the colour between points "
        "is worked out from the points around it. The six triangles at the top left "
        "each name their own three corners, so the centre they share is stated six "
        "times over: that is what lets a free-form mesh take any shape at all, and "
        "their edges are stroked over the paint so they can be counted. The lattice "
        "beside them states each point once, row after row, and the grid says which "
        "points make a triangle: forty-two points make sixty triangles, here in cyan, "
        "magenta, yellow and black, which is what a press is given. The two patches at "
        "the bottom bend their four sides into curves and carry a colour at each of "
        "their four corners. The Coons patch works its inside out from those sides "
        "alone; the tensor-product patch beside it has the same sides and the same "
        "corners, and four more points inside it pull its colours towards the top "
        "right. All four ask the reader to smooth their colour against aliasing."
    ),
    patterns_title="The same four meshes, as patterns",
    patterns="Each mesh above is handed over a second time, as a pattern. A pattern is laid from the page's own origin, so a circle filled with one shows the part of the mesh that lies where the circle is, and nothing of the rest.",
)

# The page in French.
FRENCH = Words(
    title="Quatre sortes de maillages, une couleur par point",
    triangles="Triangles libres : six, un par un",
    lattice="Un treillis : rangées de points, quatre encres",
    coons="Un carreau de Coons : quatre côtés courbes",
    tensor="Un carreau tensoriel : mêmes côtés, tiré de l’intérieur",
    legend=(
        "Chaque point ci-dessus a une position et une couleur, et la couleur entre les "
        "points se déduit des points qui l’entourent. Les six triangles en haut à "
        "gauche nomment chacun leurs trois sommets : le centre qu’ils partagent est "
        "donc écrit six fois, et c’est ce qui permet à un maillage libre de prendre "
        "n’importe quelle forme. Leurs arêtes sont tracées par-dessus la couleur pour "
        "qu’on puisse les compter. Le treillis à côté écrit chaque point une seule "
        "fois, rangée après rangée, et c’est la grille qui dit quels points forment un "
        "triangle : quarante-deux points font soixante triangles, ici en cyan, magenta, "
        "jaune et noir, ce qu’on remet à une presse. Les deux carreaux du bas courbent "
        "leurs quatre côtés et reçoivent une couleur à chacun de leurs quatre coins. Le "
        "carreau de Coons déduit son intérieur de ses seuls côtés ; le carreau "
        "tensoriel à côté a les mêmes côtés et les mêmes coins, et quatre points de "
        "plus, à l’intérieur, tirent ses couleurs vers le haut à droite. Les quatre "
        "demandent l’anticrénelage au logiciel de lecture."
    ),
    patterns_title="Les quatre maillages, en motifs",
    patterns=(
        "Chaque maillage ci-dessus est fourni une seconde fois, sous forme de motif. Un "
        "motif se pose à partir de l’origine de la page : un cercle rempli d’un motif "
        "montre donc la partie du maillage qui se trouve sous le cercle, et rien du "
        "reste."
    ),
)

# The page in Dutch.
DUTCH = Words(
    title="Vier soorten mesh, een kleur op elk punt",
    triangles="Vrije vorm: zes driehoeken, een voor een",
    lattice="Een rooster: rijen punten, vier inkten",
    coons="Een Coons-vlak: vier gebogen zijden",
    tensor="Een tensorvlak: dezelfde zijden, van binnen getrokken",
    legend=(
        "Elk punt hierboven heeft een plaats en een kleur, en de kleur tussen de "
        "punten wordt berekend uit de punten eromheen. De zes driehoeken linksboven "
        "noemen elk hun eigen drie hoekpunten, dus het midden dat ze delen wordt zes "
        "keer gegeven: daardoor kan een vrije mesh elke vorm aannemen, en hun randen "
        "worden over de verf getrokken zodat ze te tellen zijn. Het rooster ernaast "
        "geeft elk punt één keer, rij na rij, en het raster zegt welke punten een "
        "driehoek vormen: tweeënveertig punten maken zestig driehoeken, hier in "
        "cyaan, magenta, geel en zwart, wat een pers krijgt. De twee vlakken onderaan "
        "buigen hun vier zijden tot curven en hebben een kleur op elk van hun vier "
        "hoeken. Het Coons-vlak berekent zijn binnenkant alleen uit die zijden; het "
        "tensorvlak ernaast heeft dezelfde zijden en dezelfde hoeken, en vier extra "
        "punten binnenin trekken zijn kleuren naar rechtsboven. Alle vier vragen de "
        "leessoftware om anti-aliasing op hun kleuren."
    ),
    patterns_title="Dezelfde vier meshes, als patronen",
    patterns="Elke mesh hierboven wordt een tweede keer aangereikt, als patroon. Een patroon wordt gelegd vanaf de eigen oorsprong van de pagina, dus een cirkel gevuld met een patroon toont het deel van de mesh dat ligt waar de cirkel ligt, en niets van de rest.",
)

# The page in Portuguese.
PORTUGUESE = Words(
    title="Quatro tipos de malha, uma cor em cada ponto",
    triangles="Livre: seis triângulos, um a um",
    lattice="Uma rede: filas de pontos, quatro tintas",
    coons="Uma superfície de Coons: quatro lados curvos",
    tensor="Uma superfície tensorial: os mesmos lados, puxada por dentro",
    legend=(
        "Cada ponto acima leva um lugar e uma cor, e a cor entre pontos calcula-se a "
        "partir dos pontos à volta. Os seis triângulos em cima à esquerda nomeiam "
        "cada um os seus três vértices, por isso o centro que partilham é dado seis "
        "vezes: é isso que permite a uma malha livre tomar qualquer forma, e as suas "
        "arestas são traçadas por cima da pintura para se poderem contar. A rede ao "
        "lado dá cada ponto uma só vez, fila após fila, e a grelha diz que pontos "
        "formam um triângulo: quarenta e dois pontos fazem sessenta triângulos, aqui "
        "em ciano, magenta, amarelo e preto, que é o que recebe uma máquina de "
        "impressão. As duas superfícies em baixo curvam os seus quatro lados e levam "
        "uma cor em cada um dos quatro cantos. A superfície de Coons calcula o "
        "interior só a partir desses lados; a tensorial ao lado tem os mesmos lados e "
        "os mesmos cantos, e mais quatro pontos no interior puxam as suas cores para "
        "cima à direita. As quatro pedem ao leitor que suavize a cor contra o "
        "serrilhado."
    ),
    patterns_title="As mesmas quatro malhas, como padrões",
    patterns="Cada malha acima é entregue uma segunda vez, como padrão. Um padrão estende-se a partir da origem própria da página, por isso um círculo preenchido com um mostra a parte da malha que está onde está o círculo, e nada do resto.",
)

# The page in Italian.
ITALIAN = Words(
    title="Quattro tipi di maglia, un colore in ogni punto",
    triangles="Libera: sei triangoli, uno per uno",
    lattice="Un reticolo: file di punti, quattro inchiostri",
    coons="Una superficie di Coons: quattro lati curvi",
    tensor="Una superficie tensoriale: stessi lati, tirata dentro",
    legend=(
        "Ogni punto qui sopra porta una posizione e un colore, e il colore tra i "
        "punti si calcola dai punti che lo circondano. I sei triangoli in alto a "
        "sinistra nominano ciascuno i propri tre vertici, così il centro che "
        "condividono è dato sei volte: è questo che permette a una maglia libera di "
        "prendere qualsiasi forma, e i loro bordi sono tracciati sopra la pittura per "
        "poterli contare. Il reticolo accanto dà ogni punto una volta sola, fila dopo "
        "fila, e la griglia dice quali punti formano un triangolo: quarantadue punti "
        "fanno sessanta triangoli, qui in ciano, magenta, giallo e nero, cioè ciò che "
        "riceve una macchina da stampa. Le due superfici in basso curvano i loro "
        "quattro lati e portano un colore a ciascuno dei quattro angoli. La "
        "superficie di Coons calcola il suo interno solo da quei lati; quella "
        "tensoriale accanto ha gli stessi lati e gli stessi angoli, e altri quattro "
        "punti al suo interno ne tirano i colori verso l’alto a destra. Tutte e "
        "quattro chiedono al programma di lettura di sfumare il colore contro la "
        "scalettatura."
    ),
    patterns_title="Le stesse quattro maglie, come motivi",
    patterns="Ogni maglia qui sopra è consegnata una seconda volta, come motivo. Un motivo si stende dall’origine propria della pagina, così un cerchio riempito con uno mostra la parte di maglia che sta dove sta il cerchio, e nulla del resto.",
)

# The page in Spanish.
SPANISH = Words(
    title="Cuatro tipos de malla, un color en cada punto",
    triangles="Libre: seis triángulos, uno a uno",
    lattice="Una retícula: filas de puntos, cuatro tintas",
    coons="Un parche de Coons: cuatro lados curvos",
    tensor="Un parche tensorial: los mismos lados, estirado por dentro",
    legend=(
        "Cada punto de arriba lleva un lugar y un color, y el color entre puntos se "
        "calcula a partir de los puntos que lo rodean. Los seis triángulos de arriba "
        "a la izquierda nombran cada uno sus tres esquinas, así que el centro que "
        "comparten se da seis veces: eso es lo que permite a una malla libre tomar "
        "cualquier forma, y sus bordes se trazan sobre la pintura para poder "
        "contarlos. La retícula de al lado da cada punto una sola vez, fila tras "
        "fila, y la cuadrícula dice qué puntos forman un triángulo: cuarenta y dos "
        "puntos hacen sesenta triángulos, aquí en cian, magenta, amarillo y negro, "
        "que es lo que recibe una imprenta. Los dos parches de abajo curvan sus "
        "cuatro lados y llevan un color en cada una de sus cuatro esquinas. El parche "
        "de Coons calcula su interior solo a partir de esos lados; el parche "
        "tensorial de al lado tiene los mismos lados y las mismas esquinas, y cuatro "
        "puntos más en su interior tiran de sus colores hacia arriba a la derecha. "
        "Los cuatro piden al programa de lectura que suavice su color contra el "
        "dentado."
    ),
    patterns_title="Las mismas cuatro mallas, como patrones",
    patterns="Cada malla de arriba se entrega una segunda vez, como patrón. Un patrón se coloca desde el origen propio de la página, así que un círculo relleno con uno muestra la parte de la malla que está donde está el círculo, y nada del resto.",
)

# The page in German.
GERMAN = Words(
    title="Vier Arten von Netz, eine Farbe an jedem Punkt",
    triangles="Frei geformt: sechs Dreiecke, eines nach dem anderen",
    lattice="Ein Gitter: Reihen von Punkten, vier Farben",
    coons="Ein Coons-Patch: vier gekrümmte Seiten",
    tensor="Ein Tensor-Patch: dieselben Seiten, innen gezogen",
    legend=(
        "Jeder Punkt oben trägt einen Ort und eine Farbe, und die Farbe zwischen den "
        "Punkten wird aus den Punkten ringsum berechnet. Die sechs Dreiecke oben "
        "links nennen jeweils ihre eigenen drei Ecken, also wird die Mitte, die sie "
        "teilen, sechsmal angegeben: Das erlaubt einem frei geformten Netz jede "
        "beliebige Form, und ihre Kanten sind über die Farbe gezogen, damit man sie "
        "zählen kann. Das Gitter daneben gibt jeden Punkt einmal an, Reihe für Reihe, "
        "und das Raster sagt, welche Punkte ein Dreieck bilden: Zweiundvierzig Punkte "
        "ergeben sechzig Dreiecke, hier in Cyan, Magenta, Gelb und Schwarz, also dem, "
        "was eine Druckmaschine erhält. Die beiden Patches unten biegen ihre vier "
        "Seiten zu Kurven und tragen an jeder ihrer vier Ecken eine Farbe. Der "
        "Coons-Patch berechnet sein Inneres allein aus diesen Seiten; der "
        "Tensorprodukt-Patch daneben hat dieselben Seiten und Ecken, und vier weitere "
        "Punkte in seinem Inneren ziehen seine Farben nach rechts oben. Alle vier "
        "bitten die Lesesoftware, ihre Farbe gegen Treppeneffekte zu glätten."
    ),
    patterns_title="Dieselben vier Netze, als Muster",
    patterns="Jedes Netz oben wird ein zweites Mal übergeben, als Muster. Ein Muster wird vom Ursprung der Seite aus gelegt, also zeigt ein damit gefüllter Kreis den Teil des Netzes, der dort liegt, wo der Kreis ist, und nichts vom Rest.",
)

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


def centre(x: float, y: float) -> tuple[float, float]:
    """The centre of the panel at (x, y)."""
    return (x + BOX[0] / 2.0, y + BOX[1] / 2.0)


def rim(x: float, y: float) -> list[tuple[tuple[float, float], hqf_pdf.Rgb]]:
    """The six corners of the hexagon in the panel at (x, y), from the top one
    clockwise, each carrying the next colour round the colour wheel: red, yellow,
    green, cyan, blue and magenta."""
    cx, cy = centre(x, y)
    across = HALF_ROOT_THREE * RADIUS
    up = RADIUS / 2.0
    return [
        ((cx, cy + RADIUS), hqf_pdf.Rgb(0.9, 0.1, 0.1)),
        ((cx + across, cy + up), hqf_pdf.Rgb(0.95, 0.8, 0.1)),
        ((cx + across, cy - up), hqf_pdf.Rgb(0.1, 0.7, 0.2)),
        ((cx, cy - RADIUS), hqf_pdf.Rgb(0.1, 0.7, 0.8)),
        ((cx - across, cy - up), hqf_pdf.Rgb(0.1, 0.2, 0.9)),
        ((cx - across, cy + up), hqf_pdf.Rgb(0.8, 0.1, 0.7)),
    ]


def fan(x: float, y: float) -> list[tuple[object, object, object]]:
    """The six triangles of the panel at (x, y): each joins the centre, held almost
    white, to two neighbouring corners of the hexagon."""
    middle = (centre(x, y), hqf_pdf.Rgb.gray(0.95))
    corners = rim(x, y)
    return [
        (middle, corner, following)
        for corner, following in zip(corners, corners[1:] + corners[:1])
    ]


def fan_edges(content: hqf_pdf.Content, x: float, y: float) -> None:
    """Strokes the edges of the six triangles over the panel at (x, y): a spoke from
    the centre to every corner, then the rim."""
    cx, cy = centre(x, y)
    corners = rim(x, y)
    for (px, py), __ in corners:
        content.move_to(cx, cy)
        content.line_to(px, py)
    (fx, fy), __ = corners[0]
    content.move_to(fx, fy)
    for (px, py), __ in corners[1:]:
        content.line_to(px, py)
    content.close_path()
    content.stroke()


def lattice_point(
    x: float, y: float, u: float, v: float
) -> tuple[tuple[float, float], hqf_pdf.Cmyk]:
    """The point of the lattice over the panel at (x, y) that is `u` of the way across
    it and `v` of the way up, and the four inks it carries. The bottom row runs
    straight; each row above it arches higher in the middle."""
    return (
        (x + u * BOX[0], y + BOX[1] * (0.05 + 0.75 * v + 0.6 * v * u * (1.0 - u))),
        hqf_pdf.Cmyk(0.8 * u, 0.75 * v, 0.1 + 0.6 * (1.0 - u), 0.05 + 0.15 * u * v),
    )


def lattice(
    x: float, y: float
) -> list[list[tuple[tuple[float, float], hqf_pdf.Cmyk]]]:
    """The rows of the lattice over the panel at (x, y), from the bottom one up:
    ROWS + 1 of them, each of COLUMNS + 1 points."""
    return [
        [
            lattice_point(x, y, column / COLUMNS, row / ROWS)
            for column in range(COLUMNS + 1)
        ]
        for row in range(ROWS + 1)
    ]


def lattice_lines(content: hqf_pdf.Content, x: float, y: float) -> None:
    """Strokes the rows and the columns of the lattice over the panel at (x, y), each
    as a line through its points."""
    for row in range(ROWS + 1):
        v = row / ROWS
        for column in range(COLUMNS + 1):
            u = column / COLUMNS
            (px, py), __ = lattice_point(x, y, u, v)
            if column == 0:
                content.move_to(px, py)
            else:
                content.line_to(px, py)
    for column in range(COLUMNS + 1):
        u = column / COLUMNS
        for row in range(ROWS + 1):
            v = row / ROWS
            (px, py), __ = lattice_point(x, y, u, v)
            if row == 0:
                content.move_to(px, py)
            else:
                content.line_to(px, py)
    content.stroke()


def boundary(x: float, y: float) -> list[tuple[float, float]]:
    """The twelve control points of the boundary of the patch in the panel at (x, y),
    in the order a patch takes them: up the left side from the bottom left corner,
    along the top, down the right side and back along the bottom, each side a corner
    and the two points that bend the side leaving it. Every side bends one way and then
    the other."""
    return [
        (x + 20.0, y + 20.0),
        (x, y + 75.0),
        (x + 40.0, y + 125.0),
        (x + 20.0, y + 180.0),
        (x + 75.0, y + 200.0),
        (x + 135.0, y + 160.0),
        (x + 190.0, y + 180.0),
        (x + 210.0, y + 125.0),
        (x + 170.0, y + 75.0),
        (x + 190.0, y + 20.0),
        (x + 135.0, y),
        (x + 75.0, y + 40.0),
    ]


def tensor(x: float, y: float) -> list[tuple[float, float]]:
    """The sixteen control points of the tensor-product patch in the panel at (x, y):
    the twelve of `boundary`, then the four inside it — the ones nearest the bottom
    left, the top left, the top right and the bottom right corner — all four pulled up
    and to the right."""
    return boundary(x, y) + [
        (x + 150.0, y + 135.0),
        (x + 130.0, y + 195.0),
        (x + 195.0, y + 195.0),
        (x + 195.0, y + 130.0),
    ]


def sides(content: hqf_pdf.Content, x: float, y: float) -> None:
    """Strokes the four curved sides of the patch in the panel at (x, y)."""
    p0, p1, p2, p3, p4, p5, p6, p7, p8, p9, p10, p11 = boundary(x, y)
    content.move_to(p0[0], p0[1])
    content.curve_to(p1[0], p1[1], p2[0], p2[1], p3[0], p3[1])
    content.curve_to(p4[0], p4[1], p5[0], p5[1], p6[0], p6[1])
    content.curve_to(p7[0], p7[1], p8[0], p8[1], p9[0], p9[1])
    content.curve_to(p10[0], p10[1], p11[0], p11[1], p0[0], p0[1])
    content.close_path()
    content.stroke()


def legend(
    content: hqf_pdf.Content,
    font: hqf_pdf.FontHandle,
    top: float,
    color: hqf_pdf.Rgb,
    text: str,
) -> None:
    """Writes the legend in a column the width of the page."""
    flow = hqf_pdf.TextFlow(font, 8.5, leading=12.0, color=color)
    lines = flow.break_lines(text, WIDTH)
    flow.draw(content, lines, 70.0, top, WIDTH)


# How far from the centre of its panel the second page's circles reach.
CIRCLE = 60.0


def patterns_page(
    document: hqf_pdf.Document,
    font: hqf_pdf.FontHandle,
    words: Words,
    corners: tuple[float, float, float, float],
) -> hqf_pdf.Page:
    """The second page: each mesh added again as a fill pattern, filling a circle at the
    centre of its panel, under the label the first page gives it."""
    left, right, top, bottom = corners
    filled = [
        (
            document.add_triangle_mesh_pattern(fan(left, top), anti_alias=True),
            (left, top, words.triangles),
        ),
        (
            document.add_lattice_mesh_pattern(lattice(right, top), anti_alias=True),
            (right, top, words.lattice),
        ),
        (
            document.add_coons_patch_mesh_pattern(
                [(boundary(left, bottom), PATCH_CORNERS)], anti_alias=True
            ),
            (left, bottom, words.coons),
        ),
        (
            document.add_tensor_patch_mesh_pattern(
                [(tensor(right, bottom), PATCH_CORNERS)], anti_alias=True
            ),
            (right, bottom, words.tensor),
        ),
    ]

    content = hqf_pdf.Content()
    content.draw_text(font, 18.0, left, 790.0, words.patterns_title)
    for pattern, (x, y, label) in filled:
        cx, cy = centre(x, y)
        content.save_state()
        content.set_fill_pattern(pattern)
        content.circle(cx, cy, CIRCLE)
        content.fill()
        content.restore_state()
        content.draw_text(font, 10.0, x, y - 15.0, label)
    legend(content, font, 265.0, hqf_pdf.Rgb.gray(0.35), words.patterns)

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


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("mesh.pdf", language)).stem)

    document = hqf_pdf.Document()
    document.set_license(_licence.licensed())
    font = document.add_font(hqf_pdf.Font.from_path(_out.font_path()))

    # Left column x, right column x, top row foot, bottom row foot.
    left, right, top, bottom = 70.0, 315.0, 560.0, 300.0

    triangles = document.add_triangle_mesh(fan(left, top), anti_alias=True)
    points = document.add_lattice_mesh(lattice(right, top), anti_alias=True)
    coons = document.add_coons_patch_mesh(
        [(boundary(left, bottom), PATCH_CORNERS)], anti_alias=True
    )
    pulled = document.add_tensor_patch_mesh(
        [(tensor(right, bottom), PATCH_CORNERS)], anti_alias=True
    )

    content = hqf_pdf.Content()
    content.draw_text(font, 18.0, left, 790.0, words.title)

    content.draw_shading(triangles)
    content.save_state()
    content.set_stroke(hqf_pdf.Rgb.gray(1.0))
    content.set_line_width(0.4)
    fan_edges(content, left, top)
    content.restore_state()
    content.draw_text(font, 10.0, left, top - 15.0, words.triangles)

    content.draw_shading(points)
    content.save_state()
    content.set_stroke(hqf_pdf.Rgb.gray(1.0))
    content.set_line_width(0.4)
    lattice_lines(content, right, top)
    content.restore_state()
    content.draw_text(font, 10.0, right, top - 15.0, words.lattice)

    content.draw_shading(coons)
    content.save_state()
    content.set_stroke(hqf_pdf.Rgb.gray(0.3))
    content.set_line_width(0.6)
    sides(content, left, bottom)
    content.restore_state()
    content.draw_text(font, 10.0, left, bottom - 15.0, words.coons)

    content.draw_shading(pulled)
    content.save_state()
    content.set_stroke(hqf_pdf.Rgb.gray(0.3))
    content.set_line_width(0.6)
    sides(content, right, bottom)
    content.restore_state()
    content.draw_text(font, 10.0, right, bottom - 15.0, words.tensor)

    legend(content, font, 265.0, hqf_pdf.Rgb.gray(0.35), words.legend)

    page = hqf_pdf.Page.a4()
    page.set_content(content)
    document.add_page(page)
    document.add_page(patterns_page(document, font, words, (left, right, top, bottom)))

    written = document.write(out)
    print(f"wrote {out}: {written} bytes, one mesh of each of the four kinds, twice")


if __name__ == "__main__":
    main()
