"""Sets four short samples twice each — once as the font is read straight off its
`cmap`, once with one typographic feature turned on — so the difference each
feature makes can be seen side by side.

The Python twin of the `write_typography` example in Rust. The four are the
ligatures a font offers, small capitals, old-style figures, and kerning. Nothing
is on unless it is asked for, so the two settings of a sample differ in exactly
one thing.

The font here is Spectral, which offers all four.

The closing lines are measured off the fonts themselves rather than written down:
how many glyphs the ligature sample takes each way, and how much narrower kerning
draws its own.

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

Usage: python examples/write_typography.py [out.pdf]
       HQF_PDF_LANG=fr python examples/write_typography.py
"""

from __future__ import annotations

from dataclasses import dataclass
from pathlib import Path

import _language
import _licence
import _out

import hqf_pdf

# Where the committed fonts sit.
FONT_DIR = _out.ROOT / "crates" / "hqf-pdf" / "tests" / "fonts"


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

    # The line across the top of the page.
    title: str
    # What the page shows, in two sentences.
    why: str
    # The heading over each of the four samples.
    headings: tuple[str, str, str, str]
    # What each feature does, one line each.
    explanations: tuple[str, str, str, str]
    # The four samples, each set twice.
    samples: tuple[str, str, str, str]
    # What is written beside the setting with nothing turned on.
    off: str
    # What is written beside the setting with the feature turned on.
    on: str
    # The line about the ligature sample, cut where the two counts go into it.
    glyphs_before: str
    glyphs_between: str
    glyphs_after: str
    # The line about the kerning sample, cut where the width goes into it.
    kerned_before: str
    kerned_after: str

    def glyph_line(self, apart: int, joined: int) -> str:
        """Return the line about the ligature sample, with the two counts in it."""
        return (
            f"{self.glyphs_before}{apart}{self.glyphs_between}"
            f"{joined}{self.glyphs_after}"
        )

    def kerned_line(self, saved: float) -> str:
        """Return the line about the kerning sample, with the width it saves."""
        return f"{self.kerned_before}{saved:.1f}{self.kerned_after}"


# The page in English.
ENGLISH = Words(
    title="Four things a font knows about its own letters",
    why=(
        "A font file carries more than one glyph per character. It says which "
        "letters are drawn joined, which figures hang below the line, which "
        "letters have a small capital of their own, and how far apart each "
        "pair of glyphs should stand.\n"
        "None of it is used unless it is asked for, so a document written "
        "before these settings existed comes out byte for byte as it did. Each "
        "sample below is set twice, and only the setting differs."
    ),
    headings=("Ligatures", "Small capitals", "Old-style figures", "Kerning"),
    explanations=(
        "Letters that meet are drawn as one glyph the font designer cut for them.",
        "Lowercase letters drawn as capitals no taller than an x.",
        "Figures that rise and fall around the x-height instead of standing at cap height.",
        "Each pair of glyphs moved as far apart as the font asks.",
    ),
    samples=(
        "office filing affidavit",
        "reprinted quarterly",
        "1 234 567 890",
        "Wavy TAXI Today",
    ),
    off="off",
    on="on",
    glyphs_before="The first sample takes ",
    glyphs_between=" glyphs as it stands and ",
    glyphs_after=(
        " with the ligatures on: the pairs the font joins are one glyph each."
    ),
    kerned_before="The last sample is drawn ",
    kerned_after=(
        " points narrower with kerning on, and it is measured that way too, so "
        "the line is as wide as it looks."
    ),
)

# The page in French.
FRENCH = Words(
    title="Quatre choses qu'une police sait de ses propres lettres",
    why=(
        "Un fichier de police porte plus d'un dessin par caractère. Il dit "
        "quelles lettres se dessinent liées, quels chiffres descendent sous la "
        "ligne, quelles lettres ont une petite capitale à elles, et à quelle "
        "distance chaque paire de dessins doit se tenir.\n"
        "Rien de tout cela ne sert tant qu'on ne le demande pas : un document "
        "écrit avant que ces réglages existent sort octet pour octet comme "
        "avant. Chaque échantillon ci-dessous est composé deux fois, et seul le "
        "réglage change."
    ),
    headings=(
        "Lettres liées",
        "Petites capitales",
        "Chiffres elzéviriens",
        "Approche de paire",
    ),
    explanations=(
        "Deux lettres qui se touchent sont dessinées d'un seul trait, prévu par le dessinateur.",
        "Des minuscules dessinées en capitales pas plus hautes qu'un x.",
        "Des chiffres qui montent et descendent autour du x au lieu de se tenir à hauteur de capitale.",
        "Chaque paire de dessins écartée d'autant que la police le demande.",
    ),
    samples=(
        "affiche officielle du greffe",
        "réédition trimestrielle",
        "1 234 567 890",
        "Wagon TAXI Toiture",
    ),
    off="sans",
    on="avec",
    glyphs_before="Le premier échantillon prend ",
    glyphs_between=" dessins tel quel et ",
    glyphs_after=(
        " une fois les lettres liées : chaque paire liée ne fait plus qu'un dessin."
    ),
    kerned_before="Le dernier échantillon est dessiné ",
    kerned_after=(
        " points plus étroit avec l'approche, et il est mesuré ainsi aussi, donc "
        "la ligne fait la largeur qu'elle paraît faire."
    ),
)


# 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 left edge of everything on the page.
X = 60.0

# The width every block is broken to.
ROOM = 475.0

# The baseline the title sits on.
TITLE_TOP = 780.0

# The top of the block that says what the page shows.
WHY_TOP = 748.0

# The top of each of the four samples, in the order they are drawn.
TOPS = (640.0, 515.0, 390.0, 265.0)

# How far the line that explains a feature sits below its heading.
EXPLAIN_DROP = 16.0

# How far the setting with nothing turned on sits below the heading.
OFF_DROP = 42.0

# How far the setting with the feature turned on sits below the heading.
ON_DROP = 68.0

# Where the word beside a setting is written, and where the sample begins.
LABEL_X = X
SAMPLE_X = X + 46.0

# The size the samples are set in.
SAMPLE_SIZE = 18.0

# The size the headings are set in.
HEADING_SIZE = 12.0

# The size everything that is not a heading or a sample is set in.
SMALL = 9.0

# The top of the two closing lines.
CLOSING_TOP = 150.0

# How far the second closing line sits below the first.
CLOSING_DROP = 30.0


def line(
    content: hqf_pdf.Content,
    handle: hqf_pdf.FontHandle,
    text: str,
    size: float,
    x: float,
    top: float,
) -> None:
    """Draw one line of text at `size`, left-aligned at `x`, below `top`."""
    flow = hqf_pdf.TextFlow(handle, size)
    flow.draw(content, flow.break_lines(text, ROOM), x, top, ROOM)


def main() -> None:
    language = _language.from_environment()
    words = _language.words_of(WORDS, language)

    out = _out.output_path(Path(_language.file_name("typography.pdf", language)).stem)

    document = hqf_pdf.Document()
    document.set_license(_licence.licensed())
    plain = document.add_font(hqf_pdf.Font.from_path(FONT_DIR / "Spectral-Regular.ttf"))

    # One handle per feature, each turned on alone: the two settings of a sample then
    # differ in exactly one thing.
    with_feature = (
        plain.with_ligatures(),
        plain.with_small_capitals(),
        plain.with_oldstyle_figures(),
        plain.with_kerning(),
    )

    content = hqf_pdf.Content()

    line(content, plain, words.title, 18.0, X, TITLE_TOP)

    why = hqf_pdf.TextFlow(plain, 10.0, leading=14.0)
    why.draw(content, why.break_lines(words.why, ROOM), X, WHY_TOP, ROOM)

    for index, top in enumerate(TOPS):
        line(content, plain, words.headings[index], HEADING_SIZE, X, top)
        line(content, plain, words.explanations[index], SMALL, X, top - EXPLAIN_DROP)

        for drop, label, handle in (
            (OFF_DROP, words.off, plain),
            (ON_DROP, words.on, with_feature[index]),
        ):
            line(content, plain, label, SMALL, LABEL_X, top - drop)
            line(content, handle, words.samples[index], SAMPLE_SIZE, SAMPLE_X, top - drop)

    # Both closing lines are read off the fonts, not written down.
    apart = sum(len(run.glyphs) for run in plain.runs(words.samples[0]))
    joined = sum(len(run.glyphs) for run in with_feature[0].runs(words.samples[0]))
    saved = plain.measure(words.samples[3], SAMPLE_SIZE) - with_feature[3].measure(
        words.samples[3], SAMPLE_SIZE
    )

    closing = hqf_pdf.TextFlow(plain, 10.0, leading=14.0)
    for drop, text in (
        (0.0, words.glyph_line(apart, joined)),
        (CLOSING_DROP, words.kerned_line(saved)),
    ):
        closing.draw(content, closing.break_lines(text, ROOM), X, CLOSING_TOP - drop, ROOM)

    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()
