Internal and external links

Pages that link out of themselves and to each other.

Python write_links.py 169 lines
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
"""Writes a document whose pages link out of themselves and to each other.

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

A link draws nothing, so nothing on the page proves it is there: only a reader
does, by following it. The first page carries a table of contents whose lines lead
to the pages after it, and an address that leaves the document; each of the pages
after it leads back.

The chapter titles and the lines around them are held in `Words`, once per language,
and `HQF_PDF_LANG` picks which set is drawn. A chapter title is written once and read
twice: it is what the contents shows, and what the navigation panel shows.

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

from __future__ import annotations

from dataclasses import dataclass
from pathlib import Path

import _language
import _licence
import _out

import hqf_pdf

# The address the contents leaves the document by. It is not language.
ADDRESS = "https://example.org/hqf-pdf"


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

    A chapter title is written once and read twice: the contents draws it, and the
    navigation panel carries it. Both read the same field, so the two can never fall
    out of step.
    """

    # The line at the head of the contents, and the name its bookmark takes.
    contents: str
    # The line that says which text on the page can be clicked.
    what_is_clickable: str
    # The chapters the contents lists, in the order they are laid out.
    chapters: tuple[str, str, str]
    # The line each chapter page leads back by.
    back: str
    # The title the document carries in its information dictionary.
    document_title: str


# The document in English.
ENGLISH = Words(
    contents="Contents",
    what_is_clickable="Blue text is clickable; black text is not.",
    chapters=("What a link is", "What a link draws", "Where a link goes"),
    back="Back to the contents",
    document_title="A document that links to itself",
)

# The document in French.
FRENCH = Words(
    contents="Sommaire",
    what_is_clickable="Le texte bleu est cliquable, le noir ne l'est pas.",
    chapters=("Ce qu'est un lien", "Ce qu'un lien dessine", "Où mène un lien"),
    back="Retour au sommaire",
    document_title="Un document qui renvoie à lui-même",
)


# 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 size the contents and the chapter titles are set at.
SIZE = 14.0


def contents_page(handle: hqf_pdf.FontHandle, words: Words) -> hqf_pdf.Page:
    """The contents: one clickable line per chapter, and an address that leaves."""
    content = hqf_pdf.Content()
    page = hqf_pdf.Page.a4()

    content.draw_text(handle, 20.0, 72.0, 760.0, words.contents)

    # A black line that is not a link, beside the blue lines that are.
    content.draw_text(handle, 10.0, 72.0, 735.0, words.what_is_clickable)

    top = 700.0
    for index, chapter in enumerate(words.chapters):
        line = f"{index + 1}. {chapter}"
        content.set_fill(hqf_pdf.Rgb(0.0, 0.2, 0.8))
        content.draw_text(handle, SIZE, 72.0, top, line)
        content.set_fill(hqf_pdf.Rgb(0.0, 0.0, 0.0))
        # Chapter one is the page after this one, which is index 1.
        page.add_link(
            hqf_pdf.Link.to_page(72.0, top - 3.0, handle.measure(line, SIZE), SIZE, index + 1)
        )
        top -= SIZE * 2.0

    content.set_fill(hqf_pdf.Rgb(0.0, 0.2, 0.8))
    content.draw_text(handle, SIZE, 72.0, top - 20.0, ADDRESS)
    content.set_fill(hqf_pdf.Rgb(0.0, 0.0, 0.0))
    page.add_link(
        hqf_pdf.Link.to_uri(72.0, top - 23.0, handle.measure(ADDRESS, SIZE), SIZE, ADDRESS)
    )

    page.set_content(content)
    return page


def chapter_page(handle: hqf_pdf.FontHandle, words: Words, index: int) -> hqf_pdf.Page:
    """A chapter page, with a line leading back to the contents."""
    content = hqf_pdf.Content()
    page = hqf_pdf.Page.a4()

    content.draw_text(handle, 20.0, 72.0, 760.0, f"{index + 1}. {words.chapters[index]}")

    content.set_fill(hqf_pdf.Rgb(0.0, 0.2, 0.8))
    content.draw_text(handle, SIZE, 72.0, 700.0, words.back)
    content.set_fill(hqf_pdf.Rgb(0.0, 0.0, 0.0))
    page.add_link(
        hqf_pdf.Link.to_page(72.0, 697.0, handle.measure(words.back, SIZE), SIZE, 0)
    )

    page.set_content(content)
    return page


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

    document = hqf_pdf.Document()
    document.set_license(_licence.licensed())
    document.set_info("Title", words.document_title)

    handle = document.add_font(hqf_pdf.Font.from_path(_out.font_path()))

    document.add_page(contents_page(handle, words))
    for index in range(len(words.chapters)):
        document.add_page(chapter_page(handle, words, index))

    # The same contents again, as the panel a reader navigates by.
    document.add_bookmark(
        hqf_pdf.Bookmark(
            words.contents,
            0,
            children=[
                hqf_pdf.Bookmark(f"{index + 1}. {chapter}", index + 1)
                for index, chapter in enumerate(words.chapters)
            ],
            is_open=True,
        )
    )

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


if __name__ == "__main__":
    main()