A plan that shows what can be read

A land plan whose layers decide by themselves when to appear. A wide name stands over the plot while the plan is looked at whole, the fine wall figures come in once it is enlarged twice over, and the sentence under the plan is there in two languages, of which a reader shows the one it is set to.

This page is the whole program, for whoever writes your software. There is nothing here to read otherwise. Go back to the document it writes.

Python write_self_showing_layers.py 307 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
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
"""Draws a land plan whose layers decide for themselves when to be shown.

The Python twin of the `write_self_showing_layers` example in Rust. The plot and
the house it holds are the page's own ink and are always there. Three kinds of layer
sit on top and nobody has to touch the reader's panel for any of them: one wide label
stands over the plot while the plan is looked at whole, the fine figures come in once
the plan is enlarged twice over, and one layer per language carries the sentence under
the plan, so a reader set to French reads French.

The page is held in `Words`, once per language, and `HQF_PDF_LANG` picks which set is
drawn. The table of words is also what the language layers are built from: writing one
more set of words gives the document one more layer, without a line that draws being
touched. The distances are not words — a metre is a metre.

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

from __future__ import annotations

import math
from dataclasses import dataclass
from pathlib import Path
from typing import Callable

import _language
import _licence
import _out

import hqf_pdf

# Where the page's text begins, in points from the left edge.
LEFT = 80.0

# The plot: where its lower left corner sits on the page, and how big it is.
PLOT = (LEFT, 380.0, 435.0, 360.0)

# The house inside the plot, as an inset from the plot's lower left corner and a size of
# its own.
HOUSE = (60.0, 90.0, 200.0, 170.0)

# The magnification the wide label gives way to the fine figures at. The label is shown
# below it and the figures from it, so exactly one of the two stands at every
# magnification.
CROSSOVER = 2.0

# How many layers watch the magnification.
SCALED_COUNT = 2

# The distances the plan is measured at, each written inside the line it stands for.
# None of them is language: a metre is a metre.
PLOT_ALONG = "24.30"
PLOT_ACROSS = "17.60"
HOUSE_ALONG = "11.20"
HOUSE_ACROSS = "8.40"
SIDE_SETBACK = "3.35"
FOOT_SETBACK = "5.10"

# The two sides of the plot, written the way the wide label states them.
OVERALL = "24.30 m × 17.60 m"


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

    The name of the language and the sentence under the plan are the language layer's
    own: the reader's panel shows the one and the page shows the other, and both stay
    in their language whatever language the rest of the page is in. Everything else
    follows the page.
    """

    # The line at the head of the page.
    title: str
    # What stands under it, saying what the page is about.
    intro: str
    # What this language is called, which is the name its layer carries in the reader's
    # panel.
    tongue: str
    # The sentence this language's layer carries under the plan.
    caption: str
    # What the two layers that watch the magnification are called.
    scaled: tuple[str, str]
    # What the legend says each of them holds.
    holdings: tuple[str, str]
    # The wide label the plot carries while the plan is looked at whole.
    whole: str
    # What the legend says about the layers that carry the wording.
    wording: str
    # The lines under the legend saying how to see all of it.
    seeing: tuple[str, str]


# The page in English.
ENGLISH = Words(
    title="A plan that shows what can be read",
    intro=(
        "Every layer here decides for itself, by the magnification and by the "
        "language."
    ),
    tongue="English",
    caption="Plot 12, Mill Lane — sold with the barn and the well.",
    scaled=("Whole plot", "Fine figures"),
    holdings=(
        "stands over the plan while it is looked at whole",
        "comes in once the plan is enlarged twice over",
    ),
    whole="PLOT 12",
    wording="one layer a language, and a reader shows the one it is set to",
    seeing=(
        "Enlarge the plan past two hundred per cent: the figures on the walls come "
        "in as the",
        "wide label steps back. Set the reader to another language and the sentence "
        "follows.",
    ),
)

# The page in French.
FRENCH = Words(
    title="Un plan qui montre ce qui se lit",
    intro="Chaque calque décide ici tout seul, par l'agrandissement et par la langue.",
    tongue="Français",
    caption="Parcelle 12, chemin du Moulin — vendue avec la grange et le puits.",
    scaled=("Parcelle entière", "Cotes fines"),
    holdings=(
        "se tient sur le plan tant qu'on le regarde en entier",
        "arrivent dès que le plan est agrandi deux fois",
    ),
    whole="PARCELLE 12",
    wording="un calque par langue, et le lecteur montre celle sur laquelle il est réglé",
    seeing=(
        "Agrandissez le plan au-delà de deux cents pour cent : les cotes des murs "
        "arrivent",
        "et le grand titre s'efface. Réglez le lecteur sur une autre langue, la "
        "phrase suit.",
    ),
)


# Every language the example is written in. A language is added by writing its own set
# of words and naming it here, and the document gains its layer.
WORDS = {_language.ENGLISH: ENGLISH, _language.FRENCH: FRENCH}


def house() -> tuple[float, float, float, float]:
    """The house, as a place and a size on the page."""
    x, y, _, _ = PLOT
    inset_x, inset_y, width, height = HOUSE
    return (x + inset_x, y + inset_y, width, height)


def ground(content: hqf_pdf.Content) -> None:
    """Draws the ink the plan always carries: the plot's boundary and the house
    standing in it."""
    x, y, width, height = PLOT
    hx, hy, hw, hh = house()
    content.save_state()
    content.set_stroke(hqf_pdf.Rgb.gray(0.2))
    content.set_line_width(1.6)
    content.rect(x, y, width, height)
    content.stroke()
    content.set_fill(hqf_pdf.Rgb(0.9, 0.91, 0.94))
    content.set_line_width(1.2)
    content.rect(hx, hy, hw, hh)
    content.fill()
    content.rect(hx, hy, hw, hh)
    content.stroke()
    content.restore_state()


def wide_label(
    content: hqf_pdf.Content, font: hqf_pdf.FontHandle, words: Words
) -> None:
    """Draws what the plot is called and how big it is, in type that reads from across
    a desk."""
    x, y, width, height = PLOT
    middle = x + width / 2.0
    content.save_state()
    content.set_fill(hqf_pdf.Rgb(0.35, 0.42, 0.55))
    name = font.measure(words.whole, 26.0)
    content.draw_text(
        font, 26.0, middle - name / 2.0, y + height - 54.0, words.whole
    )
    sizes = font.measure(OVERALL, 12.0)
    content.draw_text(font, 12.0, middle - sizes / 2.0, y + height - 76.0, OVERALL)
    content.restore_state()


def fine_figures(content: hqf_pdf.Content, font: hqf_pdf.FontHandle) -> None:
    """Draws the distance each wall and each setback stands for, in type too small to
    read until the plan is enlarged."""
    x, y, width, height = PLOT
    hx, hy, hw, hh = house()
    figures = [
        (x + width / 2.0, y + 5.0, PLOT_ALONG),
        (x + 14.0, y + height / 2.0, PLOT_ACROSS),
        (hx + hw / 2.0, hy + 5.0, HOUSE_ALONG),
        (hx + 14.0, hy + hh / 2.0, HOUSE_ACROSS),
        ((x + hx) / 2.0, hy + hh / 2.0, SIDE_SETBACK),
        (hx + hw / 2.0, (y + hy) / 2.0, FOOT_SETBACK),
    ]
    content.save_state()
    content.set_fill(hqf_pdf.Rgb(0.2, 0.45, 0.35))
    for at, up, figure in figures:
        content.draw_text(
            font, 4.5, at - font.measure(figure, 4.5) / 2.0, up, figure
        )
    content.restore_state()


def legend(content: hqf_pdf.Content, font: hqf_pdf.FontHandle, words: Words) -> None:
    """Draws the legend under the plan: what each layer holds and when it stands."""
    baseline = 300.0
    for name, what in zip(words.scaled, words.holdings):
        content.draw_text(font, 10.0, LEFT, baseline, name)
        content.save_state()
        content.set_fill(hqf_pdf.Rgb.gray(0.35))
        content.draw_text(font, 10.0, LEFT + 110.0, baseline, what)
        content.restore_state()
        baseline -= 18.0

    tongues = " · ".join(said.tongue for said in WORDS.values())
    content.draw_text(font, 10.0, LEFT, baseline, tongues)
    content.save_state()
    content.set_fill(hqf_pdf.Rgb.gray(0.35))
    content.draw_text(font, 10.0, LEFT + 110.0, baseline, words.wording)
    for rank, line in enumerate(words.seeing):
        content.draw_text(font, 10.0, LEFT, baseline - 32.0 - rank * 14.0, line)
    content.restore_state()


def on_layer(
    content: hqf_pdf.Content,
    layer: hqf_pdf.LayerHandle,
    body: Callable[[hqf_pdf.Content], None],
) -> None:
    """Draws `body` inside `layer`, so a reader shows and hides all of it at once."""
    content.begin_layer(layer)
    body(content)
    content.end_marked()


def build(language: str, words: Words) -> hqf_pdf.Document:
    """The document the example writes, in `words`."""
    document = hqf_pdf.Document()
    document.set_license(_licence.licensed())
    font = document.add_font(hqf_pdf.Font.from_path(_out.font_path()))

    wide = document.add_layer(hqf_pdf.Layer(words.scaled[0]).zoom(0.0, CROSSOVER))
    fine = document.add_layer(
        hqf_pdf.Layer(words.scaled[1]).zoom(CROSSOVER, math.inf)
    )
    # One layer a language, built from the same table the page is written from. The one
    # the page itself is in is the one a reader falls back on when it is set to a
    # language no layer states.
    spoken = []
    for named, said in WORDS.items():
        layer = hqf_pdf.Layer(said.tongue)
        if named == language:
            layer = layer.preferred_language(named)
        else:
            layer = layer.language(named)
        spoken.append((document.add_layer(layer), said))

    content = hqf_pdf.Content()
    content.draw_text(font, 15.0, LEFT, 790.0, words.title)
    content.save_state()
    content.set_fill(hqf_pdf.Rgb.gray(0.35))
    content.draw_text(font, 10.0, LEFT, 770.0, words.intro)
    content.restore_state()

    ground(content)
    on_layer(content, wide, lambda target: wide_label(target, font, words))
    on_layer(content, fine, lambda target: fine_figures(target, font))
    # Every language's sentence stands on a line of its own, so a reader that states no
    # language of its own reads them one under the other rather than one over the other.
    for rank, (layer, said) in enumerate(spoken):

        def sentence(
            target: hqf_pdf.Content, rank: int = rank, said: Words = said
        ) -> None:
            target.draw_text(font, 10.0, LEFT, 350.0 - rank * 15.0, said.caption)

        on_layer(content, layer, sentence)
    legend(content, font, words)

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


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

    document = build(language, words)
    written = document.write(out)
    print(f"wrote {out}: {written} bytes, {SCALED_COUNT + len(WORDS)} layers")


if __name__ == "__main__":
    main()