Stamps fitted to a box

What a document says about itself — DRAFT, COPY, PAID — laid across boxes of four different shapes and scaled to each.

Python write_stamp.py 184 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
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
"""Stamps a page with what it is: DRAFT, COPY, PAID.

The Python twin of the `write_stamp` example in Rust. A stamp is scaled to the box it
is given rather than set in a size, so the same line stamps a whole page, a table
cell and a form field without a single size being worked out by the caller. It is
turned first and scaled second, so that what stays inside the box is the line as the
reader sees it. The page below draws four of them across boxes of very different
shapes.

The stamp takes whatever colour it is given and nothing else: it is text, and a
caller that wants it under the page's own ink draws it first.

The stamped words, the title and the four captions are held in `Words`, once per
language, and `HQF_PDF_LANG` picks which set is drawn. Each set keeps its last word the
longest, because the last box is the one that shows a long word coming out smaller
rather than cut.

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

from __future__ import annotations

from dataclasses import dataclass
from pathlib import Path

import _language
import _licence
import _out

import hqf_pdf

MARGIN = 56.0


@dataclass(frozen=True)
class Stamped:
    """One box to stamp: where it is, how big, and which way the line runs."""

    x: float
    y: float
    width: float
    height: float
    slant: hqf_pdf.Slant
    color: hqf_pdf.Rgb


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

    The stamped words are language: a reader stamps a document in the language they read
    it in. Each set keeps its last word the longest, because the last box is the one
    that shows a long word coming out smaller rather than cut.
    """

    # The line at the head of the page.
    title: str
    # What each box is stamped with, in the order the boxes are laid out.
    stamps: tuple[str, str, str, str]
    # What is written under each box.
    captions: tuple[str, str, str, str]


# The page in English.
ENGLISH = Words(
    title="One stamp, four boxes",
    stamps=("DRAFT", "COPY", "PAID", "CANCELLED"),
    captions=(
        "A wide box: the line runs up its diagonal, which is nearly flat.",
        "A tall box, stamped the other way.",
        "Flat across the middle, and wide enough that the width is what binds.",
        "A longer word in a shorter box: the size follows, nothing is cut.",
    ),
)

# The page in French.
FRENCH = Words(
    title="Un tampon, quatre boîtes",
    stamps=("BROUILLON", "COPIE", "PAYÉ", "ANNULATION"),
    captions=(
        "Une boîte large : le mot monte le long de sa diagonale, presque à plat.",
        "Une boîte haute, tamponnée dans l'autre sens.",
        "À plat au milieu, et assez large pour que ce soit la largeur qui limite.",
        "Un mot plus long dans une boîte plus basse : la taille suit, rien n'est coupé.",
    ),
)


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


def boxes() -> list[Stamped]:
    """The boxes, from the top of the page down."""
    return [
        Stamped(
            MARGIN,
            606.0,
            483.0,
            150.0,
            hqf_pdf.Slant.Up,
            hqf_pdf.Rgb(0.75, 0.16, 0.16),
        ),
        Stamped(
            MARGIN,
            300.0,
            150.0,
            260.0,
            hqf_pdf.Slant.Down,
            hqf_pdf.Rgb(0.25, 0.35, 0.7),
        ),
        Stamped(
            268.0,
            300.0,
            271.0,
            260.0,
            hqf_pdf.Slant.Flat,
            hqf_pdf.Rgb(0.1, 0.45, 0.25),
        ),
        Stamped(
            MARGIN,
            130.0,
            483.0,
            110.0,
            hqf_pdf.Slant.Up,
            hqf_pdf.Rgb(0.45, 0.45, 0.45),
        ),
    ]


def outline(content: hqf_pdf.Content, stamped: Stamped) -> None:
    """Draws the outline of a box, so that what the stamp was fitted to can be seen."""
    content.save_state()
    content.set_stroke(hqf_pdf.Rgb.gray(0.8))
    content.set_line_width(0.5)
    content.rect(stamped.x, stamped.y, stamped.width, stamped.height)
    content.stroke()
    content.restore_state()


def caption(
    content: hqf_pdf.Content, font: hqf_pdf.FontHandle, stamped: Stamped, text: str
) -> None:
    """Writes the caption under a box."""
    content.save_state()
    content.set_fill(hqf_pdf.Rgb.gray(0.35))
    content.draw_text(font, 8.5, stamped.x, stamped.y - 14.0, text)
    content.restore_state()


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

    document = hqf_pdf.Document()
    document.set_license(_licence.licensed())
    font = document.add_font(hqf_pdf.Font.from_path(_out.DEFAULT_FONT))

    content = hqf_pdf.Content()
    content.draw_text(font, 14.0, MARGIN, 780.0, words.title)

    stamped_boxes = boxes()
    for stamped, text, caption_text in zip(stamped_boxes, words.stamps, words.captions):
        outline(content, stamped)
        stamp = hqf_pdf.Stamp(font, text, slant=stamped.slant, color=stamped.color)
        stamp.draw(content, stamped.x, stamped.y, stamped.width, stamped.height)
        caption(content, font, stamped, caption_text)

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

    written = document.write(out)
    print(f"wrote {out}: {written} bytes, {len(stamped_boxes)} stamps")


if __name__ == "__main__":
    main()