"""Colour the document defines, next to colour it leaves to the device.

The Python twin of the `write_measured_color` example in Rust. Three numbers
said as they are leave what they look like to whatever shows them. The rows below
say colour two other ways: through the ICC profile the file carries, so the
numbers mean what they meant when the document was made, and as CIE 1976 L*a*b*,
where lightness and colour are separate numbers by construction.

What the page reads is held in `Words`, once per language, and `HQF_PDF_LANG` picks
which set is drawn. The numbers the swatches are painted from are not language: they
are the same colours whoever reads the page.

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

from __future__ import annotations

from dataclasses import dataclass
from pathlib import Path
from typing import Callable

import _language
import _licence
import _out

import hqf_pdf

# The size of one swatch, in points.
SWATCH = (70.0, 60.0)

# The left edge of the first swatch.
LEFT = 70.0

# The left edge of each swatch in a row: the first one, then every seventy-eight points
# across.
COLUMN_X = [70.0, 148.0, 226.0, 304.0, 382.0, 460.0]

# The six colours the first two rows are painted in, as three numbers each.
COLORS = [
    [0.85, 0.20, 0.15],
    [0.92, 0.58, 0.10],
    [0.20, 0.55, 0.30],
    [0.12, 0.40, 0.70],
    [0.45, 0.25, 0.60],
    [0.20, 0.20, 0.22],
]

# Six lightnesses at one colour, as `L*`, `a*` and `b*`.
LIGHTNESSES = [
    [20.0, 40.0, 25.0],
    [35.0, 40.0, 25.0],
    [50.0, 40.0, 25.0],
    [65.0, 40.0, 25.0],
    [80.0, 40.0, 25.0],
    [95.0, 40.0, 25.0],
]

# Six colours at one lightness, as `L*`, `a*` and `b*`.
HUES = [
    [60.0, 60.0, 40.0],
    [60.0, 20.0, 60.0],
    [60.0, -50.0, 45.0],
    [60.0, -40.0, -20.0],
    [60.0, 10.0, -55.0],
    [60.0, 50.0, -20.0],
]


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

    The numbers the swatches are painted from are not here: three numbers are the same
    three numbers in every language.
    """

    # The line at the head of the page.
    title: str
    # The two lines under it, saying what the first two rows hold.
    intro: tuple[str, str]
    # What each row of swatches shows, in the order the rows are drawn.
    rows: tuple[str, str, str, str]
    # The line under the stroked rule.
    rule: str
    # The three lines at the foot of the page.
    closing: tuple[str, str, str]


# The page in English.
ENGLISH = Words(
    title="Colour the document defines, not the device",
    intro=(
        "The first two rows hold the same six sets of three numbers. The second "
        "row names a colour",
        "space the file carries the profile for, so what those numbers mean "
        "travels with the file.",
    ),
    rows=(
        "Three numbers said as they are, left to whatever shows them",
        "The same three numbers, read through the sRGB profile the file carries",
        "One colour at six lightnesses, as L*a*b* over a measured black: only the first "
        "number "
        "changes",
        "Six colours at one lightness: only the other two change",
    ),
    rule="A rule stroked in the same space, by the stroking operators",
    closing=(
        "The first two rows look alike on a screen that is already sRGB, and "
        "that is the point:",
        "the difference is not what they look like here. It is that the second "
        "row still names these",
        "colours on a press, on another screen, and in ten years, because the "
        "file says what it means.",
    ),
)

# The page in French.
FRENCH = Words(
    title="La couleur que le document définit, pas celle de l’appareil",
    intro=(
        (("Les deux premières rangées reprennent les six mêmes jeux de trois nombres. "
          "La deuxième")),
        (("nomme un espace dont le fichier contient le profil : ces nombres gardent "
          "leur sens partout.")),
    ),
    rows=(
        "Trois nombres donnés tels quels : chaque appareil les affiche à sa façon",
        ("Les trois mêmes nombres, interprétés selon le profil sRGB que contient le "
         "fichier"),
        ("Une même couleur à six clartés, en L*a*b* sur un noir mesuré : seul le "
         "premier nombre "
         "change"),
        "Six couleurs à une même clarté : seuls les deux autres changent",
    ),
    rule=("Un filet tracé dans le même espace colorimétrique, par les opérateurs de "
          "contour"),
    closing=(
        (("Les deux premières rangées se ressemblent sur un écran déjà sRGB, et c’est "
          "voulu :")),
        (("la différence ne se voit pas ici. Elle tient à ce que la deuxième rangée "
          "définit encore")),
        (("ces couleurs sur une presse, un autre écran ou dans dix ans : le fichier "
          "dit ce qu’elles sont.")),
    ),
)

# The page in Dutch.
DUTCH = Words(
    title="Kleur die het document bepaalt, niet het apparaat",
    intro=(
        "De eerste twee rijen bevatten dezelfde zes groepen van drie getallen. De "
        "tweede noemt een",
        "kleurruimte met haar profiel in het bestand: wat die getallen betekenen, "
        "reist mee.",
    ),
    rows=(
        "Drie getallen zoals ze zijn, overgelaten aan wat ze toont",
        "Dezelfde drie getallen, gelezen via het sRGB-profiel dat in het bestand zit",
        "Eén kleur in zes lichtheden, als L*a*b* boven een gemeten zwart: alleen het "
        "eerste getal verandert",
        "Zes kleuren in één lichtheid: alleen de andere twee veranderen",
    ),
    rule="Een lijn getrokken in dezelfde ruimte, met de lijnoperatoren",
    closing=(
        "Op een scherm dat al sRGB is, lijken de eerste twee rijen op elkaar, en daar "
        "gaat het om:",
        "het verschil is niet hoe ze er hier uitzien. De tweede rij blijft deze "
        "kleuren benoemen",
        "op een pers, op een ander scherm en over tien jaar: het bestand zegt wat ze "
        "betekenen.",
    ),
)

# The page in Portuguese.
PORTUGUESE = Words(
    title="Cor definida pelo documento, não pelo dispositivo",
    intro=(
        "As duas primeiras linhas têm os mesmos seis trios de números. A segunda "
        "nomeia",
        "um espaço de cor cujo perfil o ficheiro contém: o que os números significam "
        "viaja com ele.",
    ),
    rows=(
        "Três números tal como estão, entregues a quem os mostrar",
        "Os mesmos três números, lidos através do perfil sRGB que o ficheiro leva",
        "Uma cor em seis luminosidades, como L*a*b* sobre um preto medido: só muda o "
        "primeiro número",
        "Seis cores numa luminosidade: só mudam os outros dois",
    ),
    rule="Um filete traçado no mesmo espaço, com os operadores de traço",
    closing=(
        "As duas primeiras linhas parecem iguais num ecrã que já é sRGB, e é essa a "
        "questão:",
        "a diferença não é o seu aspeto aqui. É que a segunda linha continua a nomear "
        "estas",
        "cores numa impressora, noutro ecrã e daqui a dez anos: o ficheiro diz o que "
        "significam.",
    ),
)

# The page in Italian.
ITALIAN = Words(
    title="Colore definito dal documento, non dal dispositivo",
    intro=(
        "Le prime due righe portano gli stessi sei gruppi di tre numeri. La seconda "
        "nomina uno spazio",
        "colore di cui il file porta il profilo, così ciò che quei numeri significano "
        "viaggia con il file.",
    ),
    rows=(
        "Tre numeri così come sono, lasciati a chi li mostra",
        "Gli stessi tre numeri, letti con il profilo sRGB che il file porta",
        "Un colore in sei luminosità, come L*a*b* su un nero misurato: cambia solo il "
        "primo numero",
        "Sei colori in una luminosità: cambiano solo gli altri due",
    ),
    rule="Un filetto tracciato nello stesso spazio, con gli operatori di tratto",
    closing=(
        "Le prime due righe si somigliano su uno schermo già sRGB, ed è proprio "
        "questo il punto:",
        "la differenza non sta in come appaiono qui: la seconda riga nomina ancora "
        "questi colori",
        "in tipografia, su un altro schermo e fra dieci anni: il file dice cosa "
        "significano.",
    ),
)

# The page in Spanish.
SPANISH = Words(
    title="Color que define el documento, no el dispositivo",
    intro=(
        "Las dos primeras filas dan los mismos seis tríos de números. La segunda "
        "nombra un espacio",
        "de color cuyo perfil lleva el archivo, así que lo que significan esos "
        "números viaja con él.",
    ),
    rows=(
        "Tres números tal cual, a merced de quien los muestre",
        "Los mismos tres números, leídos con el perfil sRGB que lleva el archivo",
        "Un color en seis claridades, como L*a*b* sobre un negro medido: solo cambia "
        "el primer número",
        "Seis colores en una claridad: solo cambian los otros dos",
    ),
    rule="Una raya trazada en el mismo espacio, con los operadores de trazo",
    closing=(
        "Las dos primeras filas se parecen en una pantalla que ya es sRGB, y esa es "
        "la cuestión:",
        "la diferencia no es su aspecto aquí. Es que la segunda fila sigue nombrando "
        "estos colores",
        "en una prensa, en otra pantalla y dentro de diez años: el archivo dice lo "
        "que significan.",
    ),
)

# The page in German.
GERMAN = Words(
    title="Farbe, die das Dokument festlegt, nicht das Gerät",
    intro=(
        "Die ersten beiden Reihen enthalten dieselben sechs Dreiergruppen. Die zweite "
        "nennt",
        "einen Farbraum, dessen Profil die Datei trägt: Die Bedeutung der Zahlen "
        "reist mit.",
    ),
    rows=(
        "Drei Zahlen, unverändert dem Gerät überlassen, das sie anzeigt",
        "Dieselben drei Zahlen, gelesen über das sRGB-Profil, das die Datei trägt",
        "Eine Farbe in sechs Helligkeiten, L*a*b* über gemessenem Schwarz: Nur die "
        "erste Zahl ändert sich",
        "Sechs Farben in einer Helligkeit: Nur die anderen beiden ändern sich",
    ),
    rule="Eine Linie im selben Farbraum, mit den Operatoren für Striche gezogen",
    closing=(
        "Die ersten beiden Reihen sehen auf einem sRGB-Bildschirm gleich aus, und "
        "darum geht es:",
        "Der Unterschied liegt nicht im Aussehen hier, sondern darin, dass die zweite "
        "Reihe diese",
        "Farben auch im Druck, auf anderen Bildschirmen und in zehn Jahren benennt.",
    ),
)


# 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 row(
    content: hqf_pdf.Content,
    font: hqf_pdf.FontHandle,
    y: float,
    caption: str,
    count: int,
    paint: Callable[[hqf_pdf.Content, int], None],
) -> None:
    """Draws a row of swatches, each painted by `paint`, and captions it."""
    width, height = SWATCH

    for column, x in enumerate(COLUMN_X[:count]):
        content.save_state()
        paint(content, column)
        content.rect(x, y, width, height)
        content.fill()
        content.restore_state()

    content.save_state()
    content.set_fill(hqf_pdf.Rgb.gray(0.35))
    content.draw_text(font, 9.0, LEFT, y - 14.0, caption)
    content.restore_state()


def rows(
    content: hqf_pdf.Content,
    font: hqf_pdf.FontHandle,
    words: Words,
    profile: hqf_pdf.ColorSpaceHandle,
    lab: hqf_pdf.ColorSpaceHandle,
) -> None:
    """Draws the four rows of swatches."""

    def device(content: hqf_pdf.Content, column: int) -> None:
        r, g, b = COLORS[column]
        content.set_fill(hqf_pdf.Rgb(r, g, b))

    def through_profile(content: hqf_pdf.Content, column: int) -> None:
        content.set_fill_space(profile)
        content.set_fill_components(COLORS[column])

    def lightness(content: hqf_pdf.Content, column: int) -> None:
        content.set_fill_space(lab)
        content.set_fill_components(LIGHTNESSES[column])

    def hue(content: hqf_pdf.Content, column: int) -> None:
        content.set_fill_space(lab)
        content.set_fill_components(HUES[column])

    row(content, font, 660.0, words.rows[0], len(COLORS), device)
    row(content, font, 560.0, words.rows[1], len(COLORS), through_profile)
    row(content, font, 460.0, words.rows[2], len(LIGHTNESSES), lightness)
    row(content, font, 360.0, words.rows[3], len(HUES), hue)


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

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

    profile = document.add_color_space(
        hqf_pdf.IccBased(hqf_pdf.srgb_icc_profile(), hqf_pdf.DeviceSpace.Rgb)
    )
    # The white of D65 daylight, as CIE XYZ: the light L*a*b* is measured against here.
    lab = document.add_color_space(
        hqf_pdf.Lab((0.9505, 1.0, 1.089))
        .black_point((0.0080, 0.0083, 0.0069))
        .range((-100.0, 100.0, -100.0, 100.0))
    )

    content = hqf_pdf.Content()
    content.draw_text(font, 15.0, LEFT, 780.0, words.title)
    content.save_state()
    content.set_fill(hqf_pdf.Rgb.gray(0.35))
    content.draw_text(font, 10.0, LEFT, 762.0, words.intro[0])
    content.draw_text(font, 10.0, LEFT, 748.0, words.intro[1])
    content.restore_state()

    rows(content, font, words, profile, lab)

    # A stroke is said in its own space, by its own operators.
    content.save_state()
    content.set_stroke_space(lab)
    content.set_stroke_components([45.0, 55.0, 35.0])
    content.set_line_width(6.0)
    content.move_to(LEFT, 300.0)
    content.line_to(COLUMN_X[len(COLORS) - 1] + SWATCH[0], 300.0)
    content.stroke()
    content.restore_state()

    content.save_state()
    content.set_fill(hqf_pdf.Rgb.gray(0.35))
    content.draw_text(font, 9.0, LEFT, 280.0, words.rule)
    content.restore_state()

    content.save_state()
    content.set_fill(hqf_pdf.Rgb.gray(0.25))
    for y, line in zip([235.0, 221.0, 207.0], words.closing):
        content.draw_text(font, 10.0, LEFT, y, line)
    content.restore_state()

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

    written = document.write(out)
    print(
        f"wrote {out}: {written} bytes, {len(COLORS) * 3} swatches "
        "in two spaces the document defines"
    )


if __name__ == "__main__":
    main()
