"""Writes a page of one word painted the four ways a text can clip what follows it.

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

A text set in a clipping rendering mode adds the outlines of its letters to the
clipping path, and whatever is drawn after it, until the graphics state is restored,
shows only inside those letters. The word is filled, outlined, both, or not painted at
all, and each time stripes are laid across it after it is set: they show only where its
letters are.

Every word is held in `Words`, once per language, and `HQF_PDF_LANG` picks which set is
set.

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

from __future__ import annotations

from dataclasses import dataclass
from pathlib import Path

import _language
import _licence
import _out

import hqf_pdf

# Where the page's text begins, in points from the left edge of the page.
LEFT = 56.0
# How wide it runs.
WIDTH = 483.0
# The size the heading is set in.
TITLE_SIZE = 22.0
# The baseline of the heading.
TITLE_TOP = 800.0
# The size the line under the heading and the name of each way are set in.
NOTE_SIZE = 10.5
# The baseline of the line under the heading.
STANDFIRST_TOP = 776.0
# The gutter between the two words of a row.
GUTTER = 24.0
# The width of the pen that outlines the letters.
HOLLOW_PEN = 0.9

# The four ways a text clips what follows it, in the order the page sets them.
MODES = (
    hqf_pdf.TextRendering.fill_and_clip(),
    hqf_pdf.TextRendering.stroke_and_clip(),
    hqf_pdf.TextRendering.fill_stroke_and_clip(),
    hqf_pdf.TextRendering.clip(),
)

# Where the name of the first row's ways sits, and the drop from one row to the next.
MODE_TOP = 740.0
MODE_STEP = 150.0

# The size the word is painted at.
MODE_SIZE = 54.0

# How far under its name a painted word's baseline sits.
MODE_DROP = 70.0

# How far under the baseline the first stripe starts, how far apart the stripes are, how
# thick each is, and how many there are.
STRIPE_DROP = 14.0
STRIPE_STEP = 4.0
STRIPE = 2.0
STRIPES = 16


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

    # What stands across the head of the page, and what the file says of itself.
    title: str
    # The line under the heading, which says what the page shows.
    standfirst: str
    # The word the page paints each way.
    painted: str
    # What each way is called, in the order `MODES` lists them.
    modes: tuple[str, str, str, str]


# The page in English.
ENGLISH = Words(
    title="Letters that clip what follows them",
    standfirst=(
        "Each word is painted, then the stripes laid after it show only inside its "
        "letters."
    ),
    painted="Ink",
    modes=(
        "filled, then clipping the stripes",
        "outlined, then clipping the stripes",
        "filled, outlined, then clipping",
        "clipping alone: only the stripes show",
    ),
)

# The page in French.
FRENCH = Words(
    title="Lettres qui détourent ce qui les suit",
    standfirst=(
        "Chaque mot est peint, puis les rayures posées après lui ne se voient que dans "
        "ses lettres."
    ),
    painted="Encre",
    modes=(
        "rempli, puis détoure les rayures",
        "contouré, puis détoure les rayures",
        "rempli, contouré, puis détoure",
        "invisible : seules les rayures se voient",
    ),
)

# The page in Dutch.
DUTCH = Words(
    title="Letters die uitsnijden wat erna komt",
    standfirst=(
        "Elk woord wordt ingekleurd; de strepen daarna zijn alleen binnen de letters "
        "te zien."
    ),
    painted="Inkt",
    modes=(
        "gevuld, dan de strepen uitsnijden",
        "omlijnd, dan de strepen uitsnijden",
        "gevuld, omlijnd, dan uitsnijden",
        "alleen uitknippen: je ziet enkel de strepen",
    ),
)

# The page in Portuguese.
PORTUGUESE = Words(
    title="Letras que recortam o que as segue",
    standfirst=(
        "Cada palavra é pintada, e as riscas postas depois só se veem dentro das suas "
        "letras."
    ),
    painted="Tinta",
    modes=(
        "preenchida, depois recorta as riscas",
        "contornada, depois recorta as riscas",
        "preenchida, contornada, depois recorta",
        "só recorte: só se veem as riscas",
    ),
)

# The page in Italian.
ITALIAN = Words(
    title="Lettere che ritagliano ciò che segue",
    standfirst=(
        "Ogni parola è dipinta, poi le strisce posate dopo si vedono solo dentro le "
        "sue lettere."
    ),
    painted="Colore",
    modes=(
        "pieno, poi ritaglia le strisce",
        "contornato, poi ritaglia le strisce",
        "pieno, contornato, poi ritaglia",
        "solo ritaglio: si vedono solo le strisce",
    ),
)

# The page in Spanish.
SPANISH = Words(
    title="Letras que recortan lo que les sigue",
    standfirst=(
        "Cada palabra se pinta y las rayas puestas después solo se ven dentro de sus "
        "letras."
    ),
    painted="Tinta",
    modes=(
        "rellena, luego recorta las rayas",
        "contorneada, luego recorta las rayas",
        "rellena, contorneada, luego recorta",
        "solo recorte: solo se ven las rayas",
    ),
)

# The page in German.
GERMAN = Words(
    title="Buchstaben als Schnittmaske",
    standfirst=(
        "Jedes Wort wird gemalt, dann zeigen sich die folgenden Streifen nur in "
        "seinen Buchstaben."
    ),
    painted="Tinte",
    modes=(
        "gefüllt, dann die Streifen beschneidend",
        "umrandet, dann die Streifen beschneidend",
        "gefüllt, umrandet, dann beschneidend",
        "nur beschneidend: nur Streifen",
    ),
)

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


def word_ink() -> hqf_pdf.Rgb:
    """The colour the word is filled in."""
    return hqf_pdf.Rgb(0.72, 0.16, 0.16)


def stripe_ink() -> hqf_pdf.Rgb:
    """The colour of the stripes a clipping word lets through."""
    return hqf_pdf.Rgb(0.95, 0.55, 0.1)


def line_at(
    content: hqf_pdf.Content,
    font: hqf_pdf.FontHandle,
    text: str,
    size: float,
    at: tuple[float, float],
) -> None:
    """Sets `text` on one line from `x`, its baseline at `y`."""
    content.begin_text()
    content.text_position(at[0], at[1])
    content.set_font(font, size)
    content.show_runs(font.runs(text), size)
    content.end_text()


def clipped(content: hqf_pdf.Content, font: hqf_pdf.FontHandle, words: Words) -> None:
    """Draws the page: the heading, the line under it, and the word painted each of the
    four ways, two to a row, each under what the way is called and followed by stripes
    across it, held to its letters."""
    line_at(content, font, words.title, TITLE_SIZE, (LEFT, TITLE_TOP))
    content.save_state()
    content.set_fill(hqf_pdf.Rgb.gray(0.35))
    line_at(content, font, words.standfirst, NOTE_SIZE, (LEFT, STANDFIRST_TOP))
    content.restore_state()

    half = WIDTH / 2.0
    top = MODE_TOP
    for index, (mode, label) in enumerate(zip(MODES, words.modes)):
        x = LEFT if index % 2 == 0 else LEFT + half
        content.save_state()
        content.set_fill(hqf_pdf.Rgb.gray(0.35))
        line_at(content, font, label, NOTE_SIZE, (x, top))
        content.restore_state()

        # The clipping path the letters add holds until the state is restored, so the
        # stripes are drawn inside the same saved state.
        baseline = top - MODE_DROP
        content.save_state()
        content.set_fill(word_ink())
        content.set_stroke(hqf_pdf.Rgb.gray(0.1))
        content.set_line_width(HOLLOW_PEN)
        content.begin_text()
        content.text_position(x, baseline)
        content.set_font(font, MODE_SIZE)
        content.set_text_rendering(mode)
        content.show_runs(font.runs(words.painted), MODE_SIZE)
        content.end_text()
        content.set_fill(stripe_ink())
        y = baseline - STRIPE_DROP
        for __ in range(STRIPES):
            content.rect(x, y, half - GUTTER, STRIPE)
            y += STRIPE_STEP
        content.fill()
        content.restore_state()
        if index % 2 == 1:
            top -= MODE_STEP


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

    document = hqf_pdf.Document()
    document.set_license(_licence.licensed())
    document.set_info("Title", words.title)
    font = document.add_font(hqf_pdf.Font.from_path(_out.font_path()))

    content = hqf_pdf.Content()
    clipped(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")


if __name__ == "__main__":
    main()
