"""Sets text in a font whose letters are drawings rather than outlines read from a file.

The Python twin of the `write_drawn_letters` example in Rust. Two fonts are built here,
both out of content operators. The first paints its own colours, which no font read from
a file can do: a green tick, a red cross and a grey dash, set inline among ordinary
words. The second states shapes and no colour, so each is painted in whatever colour the
text around it is in — the same square comes out red on one line and blue on the next.

The letters still copy out of the file as the characters they stand for, because the
font carries the map back to them.

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

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

from __future__ import annotations

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 letters that paint themselves.
    painted: str
    # The line the coloured letters are set in, before each of them.
    checks: tuple[str, str, str]
    # The heading over the letters that take the colour around them.
    shaped: str
    # What the two coloured lines say, before the square.
    tinted: tuple[str, str]
    # The closing note about copying the text out.
    note: str


# The page in English.
ENGLISH = Words(
    title="Letters you draw yourself",
    lead=(
        "The three marks below are not in any font file. Each one is a drawing this "
        "program made, added to the document as a font, and set in a line of text like "
        "any other letter. Copy the line out of this file and the marks come back as "
        "the characters they stand for."
    ),
    painted="Marks that paint themselves",
    checks=("Invoice sent", "Payment received", "Delivery note"),
    shaped="Marks that take the colour around them",
    tinted=("Overdue", "Settled"),
    note=(
        "A mark of the first kind carries its own colour, which no font read from a "
        "file can do. A mark of the second kind states a shape and nothing else, so "
        "the colour of the words it sits among is the colour it comes out in."
    ),
)

# The page in French.
FRENCH = Words(
    title="Des lettres que vous dessinez vous-même",
    lead=(
        "Les trois marques ci-dessous ne viennent d'aucun fichier de police. Chacune "
        "est un dessin fait par ce programme, ajouté au document comme une police, et "
        "posé dans une ligne de texte comme n'importe quelle lettre. Copiez la ligne "
        "hors de ce fichier et les marques reviennent sous la forme des caractères "
        "qu'elles représentent."
    ),
    painted="Des marques qui posent leur propre couleur",
    checks=("Facture envoyée", "Paiement reçu", "Bon de livraison"),
    shaped="Des marques qui prennent la couleur autour d'elles",
    tinted=("En retard", "Réglé"),
    note=(
        "Une marque de la première sorte porte sa couleur, ce qu'aucune police lue "
        "dans un fichier ne sait faire. Une marque de la seconde n'énonce qu'une "
        "forme, donc elle sort dans la couleur des mots qu'elle accompagne."
    ),
)


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

# The matrix that puts a drawn font on the thousandth scale a font file uses, so a width
# of 600 is six tenths of the size the text is set at.
THOUSANDTH = (0.001, 0.0, 0.0, 0.001, 0.0, 0.0)

# How wide every drawn glyph is, in glyph space: one em.
GLYPH_WIDTH = 1000.0

# How thick a drawn stroke is, in glyph space.
STROKE = 150.0

# The left edge of everything on the page.
LEFT = 72.0

# How wide a block of text is.
WIDTH = 450.0

# The character the tick is set with.
TICK = "✓"
# The character the cross is set with.
CROSS = "✗"
# The character the dash is set with.
DASH = "–"
# The character the square is set with.
SQUARE = "■"


def glyph_box() -> hqf_pdf.Rect:
    """The box every drawn glyph stands in, in glyph space."""
    return hqf_pdf.Rect(0.0, -200.0, GLYPH_WIDTH, 1000.0)


def tick(ink: hqf_pdf.Rgb) -> hqf_pdf.Content:
    """A tick, stroked in `ink`."""
    pen = hqf_pdf.Content()
    pen.set_stroke(ink)
    pen.set_line_width(STROKE)
    pen.move_to(140.0, 480.0)
    pen.line_to(390.0, 230.0)
    pen.line_to(870.0, 740.0)
    pen.stroke()
    return pen


def cross(ink: hqf_pdf.Rgb) -> hqf_pdf.Content:
    """A cross, stroked in `ink`."""
    pen = hqf_pdf.Content()
    pen.set_stroke(ink)
    pen.set_line_width(STROKE)
    pen.move_to(200.0, 230.0)
    pen.line_to(810.0, 740.0)
    pen.move_to(810.0, 230.0)
    pen.line_to(200.0, 740.0)
    pen.stroke()
    return pen


def dash(ink: hqf_pdf.Rgb) -> hqf_pdf.Content:
    """A dash, filled in `ink`."""
    pen = hqf_pdf.Content()
    pen.set_fill(ink)
    pen.rect(160.0, 400.0, 680.0, STROKE)
    pen.fill()
    return pen


def square() -> hqf_pdf.Content:
    """A filled square, in no colour of its own."""
    pen = hqf_pdf.Content()
    pen.rect(200.0, 150.0, 600.0, 600.0)
    pen.fill()
    return pen


def painted_font() -> hqf_pdf.Type3Font:
    """The font whose three marks paint themselves."""
    glyphs = [
        hqf_pdf.Type3Glyph.painted(TICK, GLYPH_WIDTH, tick(hqf_pdf.Rgb(0.15, 0.55, 0.25))),
        hqf_pdf.Type3Glyph.painted(
            CROSS, GLYPH_WIDTH, cross(hqf_pdf.Rgb(0.75, 0.15, 0.15))
        ),
        hqf_pdf.Type3Glyph.painted(DASH, GLYPH_WIDTH, dash(hqf_pdf.Rgb.gray(0.55))),
    ]
    return hqf_pdf.Type3Font(THOUSANDTH, glyph_box(), glyphs)


def shaped_font() -> hqf_pdf.Type3Font:
    """The font whose one mark takes the colour of the text around it."""
    bounds = hqf_pdf.Rect(200.0, 150.0, 600.0, 600.0)
    return hqf_pdf.Type3Font(
        THOUSANDTH,
        glyph_box(),
        [hqf_pdf.Type3Glyph.shaped(SQUARE, GLYPH_WIDTH, bounds, square())],
    )


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

    doc = hqf_pdf.Document()
    doc.set_license(_licence.licensed())
    text = doc.add_font(hqf_pdf.Font.from_path(_out.font_path()))
    marks = doc.add_type3_font(painted_font())
    shapes = doc.add_type3_font(shaped_font())

    content = hqf_pdf.Content()

    top = 760.0
    top = block(content, text, 18.0, top, words.title) - 14.0
    top = block(content, text, 10.0, top, words.lead) - 26.0

    top = block(content, text, 12.0, top, words.painted) - 10.0
    label = hqf_pdf.Style(text, 11.0)
    mark = hqf_pdf.Style(marks, 11.0)
    line = hqf_pdf.RichText()
    for index, check in enumerate(words.checks):
        drawn = (TICK, CROSS, DASH)[index]
        # The gap after a mark is set in the words' own font: a font of drawings holds
        # the marks and nothing else, so a space in it would be a space it cannot draw.
        line = line.push(drawn, mark).push(f" {check}", label)
        line = line.push("" if index == len(words.checks) - 1 else "    ", label)
    lines = line.break_lines(WIDTH)
    line.draw(content, lines, LEFT, top, WIDTH)
    top -= hqf_pdf.RichText.height(lines) + 26.0

    top = block(content, text, 12.0, top, words.shaped) - 10.0
    for index, tinted in enumerate(words.tinted):
        ink = hqf_pdf.Rgb(0.75, 0.15, 0.15) if index == 0 else hqf_pdf.Rgb(0.15, 0.35, 0.75)
        shape = hqf_pdf.Style(shapes, 11.0, color=ink)
        word = hqf_pdf.Style(text, 11.0, color=ink)
        row = hqf_pdf.RichText().push(SQUARE, shape).push(f" {tinted}", word)
        drawn = row.break_lines(WIDTH)
        row.draw(content, drawn, LEFT, top, WIDTH)
        top -= hqf_pdf.RichText.height(drawn) + 4.0

    top -= 22.0
    block(content, text, 10.0, top, words.note)

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

    written = doc.write(out)
    print(f"wrote {out}: {written} bytes")


if __name__ == "__main__":
    main()
