"""The same numbers, painted through spaces that say what they mean.

The Python twin of the `write_calibrated_color` example in Rust. A calibrated
space says what white the numbers are measured against, what exponent they are
raised to and — for colour — what the three primaries actually reach, so the same
numbers are the same colour wherever the file is opened.

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

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

from __future__ import annotations

from dataclasses import dataclass
from pathlib import Path

import _language
import _licence
import _out

import hqf_pdf

# The left edge of everything, and how wide a row runs.
LEFT = 70.0
RUN = 455.0

# The white every calibrated space on the page is measured against, as CIE XYZ.
WHITE = (0.9505, 1.0, 1.089)

# The six colours the first three rows paint, each as three numbers.
COLORS = [
    [0.80, 0.10, 0.10],
    [0.90, 0.50, 0.10],
    [0.90, 0.85, 0.20],
    [0.15, 0.55, 0.30],
    [0.15, 0.35, 0.70],
    [0.45, 0.20, 0.55],
]

# The size of one colour swatch, and the gap to the next.
SWATCH = (70.0, 55.0)
SWATCH_GAP = 7.0

# How many steps the grey ramp is cut into, and how tall it stands.
STEPS = 10
RAMP_HEIGHT = 45.0


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

    The numbers the bands 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 that nothing below was painted twice.
    intro: tuple[str, str]
    # The heading of each panel, and the line under it.
    panels: tuple[tuple[str, str], tuple[str, str]]
    # The caption each band stands under, in the order the bands are painted.
    captions: tuple[str, str, str, str, str, str]
    # The three lines at the foot of the page.
    closing: tuple[str, str, str]


# The page in English.
ENGLISH = Words(
    title="The same numbers, through spaces that say what they mean",
    intro=(
        "Nothing below was painted twice with different numbers. Each band "
        "holds the very same numbers as",
        "the one above it, and what changes is the space they are said in.",
    ),
    panels=(
        (
            "Six colours, three ways of meaning them",
            "The three numbers are the same in every band. The primaries under them "
            "are not.",
        ),
        (
            "One grey ramp, three exponents",
            "Ten steps from 0.05 to 0.95, painted with the same ten numbers each time.",
        ),
    ),
    captions=(
        "Left to the device",
        "Through one set of primaries",
        "Through a wider set of primaries",
        "Left to the device",
        "Measured, raised to 1",
        "Measured, raised to 2.2",
    ),
    closing=(
        "A calibrated space carries its measurement in a handful of numbers "
        "rather than in a profile,",
        "so a file states what its colours are without embedding anything "
        "and without asking the",
        "reader to have the right profile installed.",
    ),
)

# The page in French.
FRENCH = Words(
    title="Les mêmes nombres, dans des espaces qui disent ce qu'ils valent",
    intro=(
        "Rien, plus bas, n'a été peint deux fois avec des nombres différents. "
        "Chaque bande porte les mêmes",
        "nombres que celle du dessus, et ce qui change est l'espace dans lequel "
        "on les dit.",
    ),
    panels=(
        (
            "Six couleurs, trois façons de les définir",
            "Les trois nombres sont les mêmes dans chaque bande. Les primaires sous "
            "eux, non.",
        ),
        (
            "Une rampe de gris, trois exposants",
            "Dix marches de 0.05 à 0.95, peintes à chaque fois avec les mêmes dix "
            "nombres.",
        ),
    ),
    captions=(
        "Laissé à l'appareil",
        "À travers un jeu de primaires",
        "À travers un jeu de primaires plus large",
        "Laissé à l'appareil",
        "Mesuré, élevé à 1",
        "Mesuré, élevé à 2.2",
    ),
    closing=(
        "Un espace calibré porte sa mesure dans une poignée de nombres plutôt "
        "que dans un profil,",
        "si bien qu'un fichier dit ce que sont ses couleurs sans rien embarquer "
        "et sans exiger",
        "que le lecteur ait le bon profil installé.",
    ),
)


# 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}


def caption(content, font, y, line):
    """Write the small caption a band stands under."""
    content.save_state()
    content.set_fill(hqf_pdf.Color.gray(0.45))
    content.draw_text(font, 8.0, LEFT, y, line)
    content.restore_state()


def panel_heading(content, font, y, title, under):
    """Draw one panel's heading, with the line under it."""
    content.save_state()
    content.set_fill(hqf_pdf.Color.gray(0.2))
    content.draw_text(font, 11.0, LEFT, y, title)
    content.set_fill(hqf_pdf.Color.gray(0.45))
    content.draw_text(font, 8.0, LEFT, y - 15.0, under)
    content.restore_state()


def band(content, space, bottom):
    """Paint the six colours across, in `space`; without one, left to the device."""
    x = LEFT
    for color in COLORS:
        content.save_state()
        if space is None:
            content.set_fill(hqf_pdf.Rgb(color[0], color[1], color[2]))
        else:
            content.set_fill_space(space)
            content.set_fill_components(color)
        content.rect(x, bottom, SWATCH[0], SWATCH[1])
        content.fill()
        content.restore_state()
        x += SWATCH[0] + SWATCH_GAP


def ramp(content, space, bottom):
    """Paint the grey ramp across, in `space`; without one, left to the device."""
    width = RUN / STEPS
    x = LEFT
    grey = 0.05
    for _ in range(STEPS):
        content.save_state()
        if space is None:
            content.set_fill(hqf_pdf.Color.gray(grey))
        else:
            content.set_fill_space(space)
            content.set_fill_components([grey])
        content.rect(x, bottom, width, RAMP_HEIGHT)
        content.fill()
        content.restore_state()
        x += width
        grey += 0.1


def heading(content, font, words):
    """Draw the title and the two lines saying what the page is showing."""
    content.draw_text(font, 15.0, LEFT, 780.0, words.title)
    content.save_state()
    content.set_fill(hqf_pdf.Color.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()


def closing(content, font, words):
    """Draw the closing note."""
    content.save_state()
    content.set_fill(hqf_pdf.Color.gray(0.35))
    for y, line in zip([140.0, 126.0, 112.0], words.closing):
        content.draw_text(font, 9.0, LEFT, y, line)
    content.restore_state()


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

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

    ordinary = document.add_color_space(
        hqf_pdf.CalRgb(WHITE)
        .gamma((2.2, 2.2, 2.2))
        .primaries(
            (0.4124, 0.2126, 0.0193),
            (0.3576, 0.7152, 0.1192),
            (0.1805, 0.0722, 0.9505),
        )
    )
    wider = document.add_color_space(
        hqf_pdf.CalRgb(WHITE)
        .gamma((2.2, 2.2, 2.2))
        .primaries(
            (0.5767, 0.2974, 0.0270),
            (0.1856, 0.6273, 0.0707),
            (0.1882, 0.0753, 0.9911),
        )
    )
    straight = document.add_color_space(hqf_pdf.CalGray(WHITE).gamma(1.0))
    bent = document.add_color_space(hqf_pdf.CalGray(WHITE).gamma(2.2))

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

    panel_heading(content, font, 706.0, words.panels[0][0], words.panels[0][1])
    caption(content, font, 668.0, words.captions[0])
    band(content, None, 610.0)
    caption(content, font, 594.0, words.captions[1])
    band(content, ordinary, 536.0)
    caption(content, font, 520.0, words.captions[2])
    band(content, wider, 462.0)

    panel_heading(content, font, 420.0, words.panels[1][0], words.panels[1][1])
    caption(content, font, 382.0, words.captions[3])
    ramp(content, None, 334.0)
    caption(content, font, 318.0, words.captions[4])
    ramp(content, straight, 270.0)
    caption(content, font, 254.0, words.captions[5])
    ramp(content, bent, 206.0)
    closing(content, font, words)

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

    written = document.write(out)
    print(
        f"wrote {out}: {written} bytes, {len(COLORS)} colours and {STEPS} greys "
        f"painted three times each"
    )


if __name__ == "__main__":
    main()
