"""A handbook that says how it would like to be opened, shown and printed.

The Python twin of the `write_reading_preferences` example in Rust. None of what
this document states changes a single mark on its pages. It changes what happens
the moment somebody double-clicks the file: which page comes up, how much of it
fills the window, what stands beside it, and what the print dialogue is already
set to when it opens.

Usage: python examples/write_reading_preferences.py [out.pdf] [font.ttf]
"""

from __future__ import annotations

import _licence
import _out

import hqf_pdf

# A4, in points.
SHEET = (595.276, 841.890)

# How far in from the edge of the sheet everything is set.
MARGIN = 64.0

# The colour the band across the head of a page is laid in.
BAND = (0.13, 0.29, 0.36)

# The address this document is published under, and the address its relative links are
# read against.
BASE = "https://example.org/handbook/"

# Where the underlined words of the link sit, and how wide they run.
LINK = (290.0, 104.0, 16.0)


def band():
    """The colour the band across the head of a page is laid in."""
    return hqf_pdf.Rgb(BAND[0], BAND[1], BAND[2])


def head(content, font, heading):
    """Draw the band across the head of a page and the heading standing in it."""
    content.save_state()
    content.set_fill(band())
    content.rect(0.0, SHEET[1] - 96.0, SHEET[0], 96.0)
    content.fill()
    content.set_fill(hqf_pdf.Rgb(1.0, 1.0, 1.0))
    content.draw_text(font, 20.0, MARGIN, SHEET[1] - 58.0, heading)
    content.restore_state()


def foot(content, font, number):
    """Draw the hairline and the line of small print that close a page."""
    content.save_state()
    content.set_stroke(hqf_pdf.Color.gray(0.82))
    content.set_line_width(0.4)
    content.move_to(MARGIN, 78.0)
    content.line_to(SHEET[0] - MARGIN, 78.0)
    content.stroke()
    content.set_fill(hqf_pdf.Color.gray(0.5))
    content.draw_text(font, 8.0, MARGIN, 62.0, "Field handbook")
    content.draw_text(font, 8.0, SHEET[0] - MARGIN - 6.0, 62.0, number)
    content.restore_state()


def lines(content, font, top, size, step, body):
    """Draw a run of lines down the page from `top`.

    Hands back the baseline the next thing would go on.
    """
    y = top
    for line in body:
        if line:
            content.draw_text(font, size, MARGIN, y, line)
        y -= step
    return y


def setting(content, font, y, asked, does):
    """Draw a row of the settings table: what was asked for, and what it does."""
    content.save_state()
    content.set_fill(band())
    content.draw_text(font, 9.5, MARGIN, y, asked)
    content.set_fill(hqf_pdf.Color.gray(0.3))
    content.draw_text(font, 9.5, MARGIN + 190.0, y, does)
    content.set_stroke(hqf_pdf.Color.gray(0.85))
    content.set_line_width(0.4)
    content.move_to(MARGIN, y - 7.0)
    content.line_to(SHEET[0] - MARGIN, y - 7.0)
    content.stroke()
    content.restore_state()


def cover(font):
    """The cover: the one page nobody is meant to land on."""
    content = hqf_pdf.Content()
    content.save_state()
    content.set_fill(band())
    content.rect(0.0, 0.0, SHEET[0], SHEET[1])
    content.fill()
    content.restore_state()

    content.save_state()
    content.set_fill(hqf_pdf.Rgb(1.0, 1.0, 1.0))
    content.draw_text(font, 34.0, MARGIN, 540.0, "Field handbook")
    content.set_fill(hqf_pdf.Rgb(0.72, 0.83, 0.86))
    content.draw_text(
        font,
        13.0,
        MARGIN,
        504.0,
        "How a document asks to be opened, shown and printed",
    )
    content.draw_text(font, 10.0, MARGIN, 120.0, "HQF Development")
    content.restore_state()

    content.save_state()
    content.set_fill(hqf_pdf.Rgb(0.72, 0.83, 0.86))
    lines(
        content,
        font,
        420.0,
        10.5,
        16.0,
        [
            "This cover is page one, and it is not the page this document "
            "opens on.",
            "A reader that honours what the file states turns straight to the "
            "contents,",
            "shows the bookmarks beside it, and stands the cover on its own — "
            "the way",
            "a book falls open with its first page facing nothing.",
        ],
    )
    content.restore_state()
    return content


def section_list(content, font):
    """Draw the list of sections, each under a hairline.

    Hands back the baseline the next thing would go on.
    """
    content.save_state()
    content.set_fill(hqf_pdf.Color.gray(0.25))
    y = SHEET[1] - 160.0
    for item, page in [
        ("The cover", "1"),
        ("Contents", "2"),
        ("What this file asks of a reader", "3"),
        ("What it asks of a print dialogue", "4"),
    ]:
        content.draw_text(font, 11.0, MARGIN, y, item)
        content.draw_text(font, 11.0, SHEET[0] - MARGIN - 10.0, y, page)
        content.save_state()
        content.set_stroke(hqf_pdf.Color.gray(0.85))
        content.set_line_width(0.4)
        content.move_to(MARGIN, y - 7.0)
        content.line_to(SHEET[0] - MARGIN, y - 7.0)
        content.stroke()
        content.restore_state()
        y -= 34.0
    content.restore_state()
    return y


def terms(content, font):
    """Draw the base address, and the underlined words the link is laid over."""
    y, width, _height = LINK
    content.save_state()
    content.set_fill(hqf_pdf.Color.gray(0.35))
    content.draw_text(
        font,
        10.5,
        MARGIN,
        y + 72.0,
        "The link below goes to terms.html, and to nothing else. It works "
        "because this",
    )
    content.draw_text(
        font,
        10.5,
        MARGIN,
        y + 56.0,
        "file states the address it was published under:",
    )
    content.set_fill(band())
    content.draw_text(font, 10.5, MARGIN, y + 34.0, BASE)
    content.set_fill(hqf_pdf.Rgb(0.10, 0.35, 0.65))
    content.draw_text(font, 11.0, MARGIN, y + 4.0, "Read the full terms")
    content.set_stroke(hqf_pdf.Rgb(0.10, 0.35, 0.65))
    content.set_line_width(0.6)
    content.move_to(MARGIN, y)
    content.line_to(MARGIN + width, y)
    content.stroke()
    content.restore_state()


def contents(font):
    """The contents page: what the document opens on, and the one relative link."""
    content = hqf_pdf.Content()
    head(content, font, "Contents")
    y = section_list(content, font)

    content.save_state()
    content.set_fill(hqf_pdf.Color.gray(0.35))
    lines(
        content,
        font,
        y - 66.0,
        10.5,
        16.0,
        [
            "This is the page the file opens on, and it opens showing what is "
            "drawn on it",
            "rather than the whole sheet: the words fill the window, not the "
            "paper around",
            "them. Turn to page three for the whole list of what this file asks "
            "for, and to",
            "page four for what it asks of a printer.",
        ],
    )
    content.restore_state()

    terms(content, font)

    content.save_state()
    content.set_fill(hqf_pdf.Color.gray(0.45))
    lines(
        content,
        font,
        170.0,
        9.5,
        14.0,
        [
            "Move this file to another address and that link breaks — unless "
            "the address",
            "moves with it. That is all a base address is for: one line to "
            "restate, instead",
            "of every relative link in the document to rewrite.",
        ],
    )
    content.restore_state()
    foot(content, font, "2")
    return content


def on_screen(font):
    """What the file asks of the reader on the screen."""
    content = hqf_pdf.Content()
    head(content, font, "What this file asks of a reader")

    content.save_state()
    content.set_fill(hqf_pdf.Color.gray(0.35))
    y = lines(
        content,
        font,
        SHEET[1] - 148.0,
        10.5,
        16.0,
        [
            "Every line below is a wish, not an instruction. A reader whose "
            "owner has said",
            "otherwise does what its owner said, and a reader that has never "
            "heard of one of",
            "these entries ignores it and shows the file all the same.",
        ],
    )
    content.restore_state()

    y -= 24.0
    for asked, does in [
        ("Opens on", "the contents page, not the cover"),
        ("Shows", "what is drawn, filling the window"),
        ("Beside the page", "the list of bookmarks"),
        ("Arranges the pages", "two at a time, page one on its own"),
        ("Window", "resized to the first page, centred on the screen"),
        ("Title bar", "the title this file states, not its file name"),
        ("Leaving full screen", "back to the bookmarks"),
        ("Relative links", BASE),
    ]:
        setting(content, font, y, asked, does)
        y -= 44.0

    content.save_state()
    content.set_fill(hqf_pdf.Color.gray(0.45))
    lines(
        content,
        font,
        160.0,
        9.5,
        14.0,
        [
            "Nothing here is drawn on a page. Print this file and not one of "
            "these lines",
            "leaves a mark: they live in the catalogue, which is the first "
            "thing a reader",
            "reads and the last thing a printer cares about.",
        ],
    )
    content.restore_state()
    foot(content, font, "3")
    return content


def sheet_sketch(content, font, corner, caption):
    """Draw one sheet of the little diagram.

    An outline, a band marking its head, and a caption under it.
    """
    x, y = corner
    content.save_state()
    content.set_stroke(hqf_pdf.Color.gray(0.55))
    content.set_line_width(0.8)
    content.rect(x, y, 84.0, 118.0)
    content.stroke()
    content.set_fill(band())
    content.rect(x + 8.0, y + 96.0, 68.0, 14.0)
    content.fill()
    content.set_fill(hqf_pdf.Color.gray(0.45))
    content.draw_text(font, 8.0, x, y - 14.0, caption)
    content.restore_state()


def long_edge(content, font):
    """Draw what turning a sheet on its long edge does.

    The back of the sheet stands the same way up as its front.
    """
    sheet_sketch(content, font, (MARGIN + 40.0, 216.0), "front: page two")
    sheet_sketch(content, font, (MARGIN + 260.0, 216.0), "back: page three")

    content.save_state()
    content.set_stroke(hqf_pdf.Color.gray(0.55))
    content.set_line_width(1.2)
    content.move_to(MARGIN + 146.0, 275.0)
    content.line_to(MARGIN + 246.0, 275.0)
    content.line_to(MARGIN + 234.0, 283.0)
    content.move_to(MARGIN + 246.0, 275.0)
    content.line_to(MARGIN + 234.0, 267.0)
    content.stroke()
    content.set_fill(hqf_pdf.Color.gray(0.45))
    content.draw_text(
        font, 8.0, MARGIN + 148.0, 291.0, "turned on the long edge"
    )
    content.restore_state()


def at_the_printer(font):
    """What the file asks of the print dialogue."""
    content = hqf_pdf.Content()
    head(content, font, "What it asks of a print dialogue")

    content.save_state()
    content.set_fill(hqf_pdf.Color.gray(0.35))
    y = lines(
        content,
        font,
        SHEET[1] - 148.0,
        10.5,
        16.0,
        [
            "A print dialogue opens with something already filled in. A file "
            "may say what,",
            "which saves whoever prints it from setting the same four boxes "
            "every time.",
        ],
    )
    content.restore_state()

    y -= 24.0
    for asked, does in [
        ("Scaling", "none: every page prints at the size it was drawn"),
        ("Sides", "both, the sheet turned on its long edge"),
        ("Paper tray", "chosen by the size of the page"),
        ("Copies", "two"),
        ("Pages", "two to four: the cover is not worth the toner"),
    ]:
        setting(content, font, y, asked, does)
        y -= 44.0

    long_edge(content, font)

    content.save_state()
    content.set_fill(hqf_pdf.Color.gray(0.45))
    lines(
        content,
        font,
        160.0,
        9.5,
        14.0,
        [
            "Printing at actual size is the one that matters on a document "
            "meant to be",
            "measured: a form, a label sheet, a plan. A dialogue left to shrink "
            "the page to",
            "fit the paper is a dialogue that quietly makes every distance on "
            "it wrong.",
        ],
    )
    content.restore_state()
    foot(content, font, "4")
    return content


def main() -> None:
    out = _out.output_path("handbook")

    document = hqf_pdf.Document()
    document.set_license(_licence.licensed())
    document.set_metadata(
        hqf_pdf.Metadata(
            title="Field handbook",
            author="HQF Development",
            subject="How a document asks to be opened, shown and printed",
        )
    )
    font = document.add_font(hqf_pdf.Font.from_path(_out.font_path()))

    first = hqf_pdf.Page(SHEET[0], SHEET[1])
    first.set_content(cover(font))
    document.add_page(first)

    second = hqf_pdf.Page(SHEET[0], SHEET[1])
    second.set_content(contents(font))
    second.add_link(
        hqf_pdf.Link.to_uri(MARGIN, LINK[0], LINK[1], LINK[2], "terms.html")
    )
    document.add_page(second)

    third = hqf_pdf.Page(SHEET[0], SHEET[1])
    third.set_content(on_screen(font))
    document.add_page(third)

    fourth = hqf_pdf.Page(SHEET[0], SHEET[1])
    fourth.set_content(at_the_printer(font))
    document.add_page(fourth)

    for title, page in [
        ("The cover", 0),
        ("Contents", 1),
        ("What this file asks of a reader", 2),
        ("What it asks of a print dialogue", 3),
    ]:
        document.add_bookmark(hqf_pdf.Bookmark(title, page))

    # The document opens on its contents, filled with what is drawn there, with the
    # bookmarks beside it and the cover standing on its own.
    document.set_open_action(
        hqf_pdf.OpenAction(1).view(hqf_pdf.PageFit.whole_drawing())
    )
    document.set_open_mode(hqf_pdf.OpenMode.Bookmarks)
    document.set_page_layout(hqf_pdf.PageLayout.TwoPagesOddRight)
    document.set_base_uri(BASE)
    document.set_viewer_preferences(
        hqf_pdf.ViewerPreferences()
        .fit_window(True)
        .center_window(True)
        .show_title(True)
        .after_full_screen(hqf_pdf.SidePanel.Bookmarks)
        .print_scaling(hqf_pdf.PrintScaling.ActualSize)
        .duplex(hqf_pdf.Duplex.LongEdge)
        .tray_by_page_size(True)
        .copies(2)
        .print_run(1, 3)
    )

    written = document.write(out)
    print(
        f"wrote {out}: {written} bytes, {document.page_count} pages, and a "
        f"catalogue that says how to open, show and print them"
    )


if __name__ == "__main__":
    main()
