write_own_notes.py

Le fichier Python de l'exemple « Les notes qu'un programme se laisse ». Une épreuve d'imprimerie qui porte le tirage sur le fichier, la plaque sur la page et l'encre sur la gamme de couleurs placée deux fois. Rien n'en est dessiné et aucun logiciel de lecture ne le montre : c'est là pour le programme qui l'a écrit.

Python 171 lignes

À quoi sert cet exemple

Une imprimerie envoie une épreuve et la récupère trois jours plus tard, une note au crayon dans la marge. Pour reprendre le travail, il lui faut savoir à quel tirage la feuille appartient, pour quelle plaque la page a été composée, et contre quelle encre la gamme de couleurs a été tracée — et rien de tout cela ne regarde celui qui tient la feuille. Cela voyage donc sous le document plutôt que dessus : le fichier porte le tirage, la page porte sa plaque, et la gamme de couleurs, placée deux fois sur cette page, porte l'encre contre laquelle elle a été réglée.

Ce que montre cet exemple

  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
"""Leaves a program's own notes in the file, for itself, where no reading software
shows them.

The Python twin of the `write_own_notes` example in Rust. A press room sends a proof
out and gets it back three days later with a note in the margin. To pick the job up
again it has to know which run the sheet belongs to, which plate the page was set
for, and which ink the colour bar was drawn against — and none of that is anything a
reading software should show.

So it travels under the document instead. The file carries the run, the page carries
its plate, and the drawing placed twice on it carries the ink it was set against.
Each packet is stamped with the moment its program last touched it, and each is filed
under the name of that program, so two programs leaving notes in the same file never
read each other's.

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

from __future__ import annotations

from dataclasses import dataclass
from pathlib import Path

import _language
import _licence
import _out

import hqf_pdf

# The margin the page is laid out inside.
MARGIN = 56.0

# How far the page runs across, between the margins.
WIDTH = 483.0

# The program the notes are filed under. Two programs leaving notes in the same file
# never read each other's, because each is filed under its own name.
PROGRAM = "PressDesk"

# When the program last touched what it left, in ISO 8601.
TOUCHED = "2026-09-07T08:15:00+02:00"

# What the file, the page and the drawing each carry.
FOR_THE_FILE = '{"run":47,"press":"K4","proof":3}'
FOR_THE_PAGE = '{"plate":"C","screen":175}'
FOR_THE_DRAWING = '{"ink":"process cyan","bar":"solid"}'

# How wide and how tall one patch of the bar is.
PATCH = (28.0, 18.0)

# How far along the bar each patch starts.
PATCH_AT = (0.0, 28.0, 56.0, 84.0, 112.0, 140.0)

# How strong the ink is in each patch.
STRENGTH = (0.15, 0.3, 0.45, 0.6, 0.8, 1.0)

# Where the bar is placed on the page, and at what scale.
PLACED = ((MARGIN, 672.0, 1.0), (MARGIN, 616.0, 0.5))


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

    # The line at the head of the page.
    title: str
    # The paragraph under it.
    body: str
    # The line under the second bar.
    caption: str


# The page in English.
ENGLISH = Words(
    title="Press proof",
    body="This sheet is a proof, and everything a reading software shows of it "
    "is on it: a title, this paragraph, and a colour bar placed twice. "
    "Under it the file states the run it belongs to, the page states the "
    "plate it was set for, and the bar states the ink it was drawn "
    "against.",
    caption="None of those three is drawn, and no reading software shows them. "
    "Each is filed under the name of the program that left it, stamped "
    "with the moment that program last touched it, and travels with "
    "the file until it opens the file again.",
)

# The page in French.
FRENCH = Words(
    title="Épreuve d'imprimerie",
    body="Cette feuille est une épreuve, et tout ce qu'un logiciel de lecture "
    "en montre est dessus : un titre, ce paragraphe, et une gamme de "
    "couleurs placée deux fois. En dessous, le fichier porte le tirage "
    "auquel il appartient, la page porte la plaque pour laquelle elle a "
    "été composée, et la gamme porte l'encre contre laquelle elle a été "
    "tracée.",
    caption="Aucune des trois n'est dessinée, et aucun logiciel de lecture ne "
    "les montre. Elles sont rangées sous le nom du programme qui les a "
    "laissées, datées du moment où il y a touché pour la dernière "
    "fois, et elles voyagent avec le fichier jusqu'à ce que ce "
    "programme le rouvre.",
)


# 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 colour_bar() -> hqf_pdf.Drawing:
    """The colour bar: six patches of increasing strength, side by side."""
    bar = hqf_pdf.Content()
    for at, strength in zip(PATCH_AT, STRENGTH):
        bar.set_fill(hqf_pdf.Rgb(1.0 - strength, 1.0 - strength * 0.15, 1.0))
        bar.rect(at, 0.0, PATCH[0], PATCH[1])
        bar.fill()
    across = PATCH_AT[len(PATCH_AT) - 1] + PATCH[0]
    return hqf_pdf.Drawing(bar, hqf_pdf.Rect(0.0, 0.0, across, PATCH[1]))


def paragraph(
    content: hqf_pdf.Content,
    font: hqf_pdf.FontHandle,
    size: float,
    top: float,
    color: hqf_pdf.Rgb,
    text: str,
) -> None:
    """Writes a paragraph in a column the width of the page."""
    flow = hqf_pdf.TextFlow(font, size, leading=size * 1.4, color=color)
    lines = flow.break_lines(text, WIDTH)
    flow.draw(content, lines, MARGIN, top, WIDTH)


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

    document = hqf_pdf.Document()
    document.set_license(_licence.licensed())
    document.set_private_data(hqf_pdf.PrivateData(PROGRAM, TOUCHED, FOR_THE_FILE))
    font = document.add_font(hqf_pdf.Font.from_path(_out.DEFAULT_FONT))

    bar = document.add_drawing(
        colour_bar().with_private_data(
            hqf_pdf.PrivateData(PROGRAM, TOUCHED, FOR_THE_DRAWING)
        )
    )

    content = hqf_pdf.Content()
    content.draw_text(font, 16.0, MARGIN, 780.0, words.title)
    paragraph(content, font, 10.0, 770.0, hqf_pdf.Rgb.gray(0.0), words.body)
    for x, y, scale in PLACED:
        content.draw_form(bar, x, y, scale)
    paragraph(content, font, 8.5, 560.0, hqf_pdf.Rgb.gray(0.35), words.caption)

    page = hqf_pdf.Page.a4()
    page.set_content(content)
    page.set_private_data(hqf_pdf.PrivateData(PROGRAM, TOUCHED, FOR_THE_PAGE))
    document.add_page(page)

    written = document.write(out)
    print(f"wrote {out}: {written} bytes, notes on the file, the page and the drawing")


if __name__ == "__main__":
    main()