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 | """Paints meshes of triangles, each corner carrying its own colour.
The Python twin of the `write_mesh` example in Rust. A mesh states its own shape.
Every triangle names three corners, and every corner names a point and a colour; what
lies between them is worked out from the three. So one triangle already runs from red to
green to blue, and a grid of them carries a gradient a straight axis and a circle cannot.
Four panels: a single triangle, a grid of sixty in red, green and blue with its edges
traced over it, the same grid in the four printing inks, and the grid once more named as
a fill and painted inside a disc.
The title, the four labels and the legend are held in `Words`, once per language, and
`HQF_PDF_LANG` picks which set is written.
Usage: python examples/write_mesh.py [out.pdf] [font.ttf]
HQF_PDF_LANG=fr python examples/write_mesh.py
"""
from __future__ import annotations
from dataclasses import dataclass
from pathlib import Path
import _language
import _licence
import _out
import hqf_pdf
# The width and height of every panel, in points.
BOX = (210.0, 200.0)
# How many cells the grid runs across.
COLUMNS = 6
# How many cells the grid runs up.
ROWS = 5
# How many triangles the page states: the single one, and two per cell in each of the
# three grids.
TRIANGLES = 1 + 3 * COLUMNS * ROWS * 2
# How far the legend runs across, between the margins.
WIDTH = 455.0
# How far a circle's control points stand from its quarter marks, as a share of the
# radius: the number that turns four cubics into a circle.
KAPPA = 0.5522847498307934
@dataclass(frozen=True)
class Words:
"""Every word the page writes, in one language."""
# The title across the top of the page.
title: str
# The label under the single triangle.
one: str
# The label under the grid in red, green and blue.
grid: str
# The label under the grid in the four printing inks.
inks: str
# The label under the disc the mesh fills.
fill: str
# The paragraph at the foot of the page, saying what is above it.
legend: str
# The page in English.
ENGLISH = Words(
title="Meshes of triangles, corner by corner",
one="One triangle, three corners",
grid="Sixty triangles, edges traced",
inks="The same sixty, in the four inks",
fill="The mesh as a fill, inside a disc",
legend=(
"Every corner above carries a point and a colour, and the colour "
"between three corners is worked out from those three. The single "
"triangle at the top left holds red, green and blue at its corners "
"and nothing in between; the grid beside it cuts each of its thirty "
"cells in two, and its edges are stroked over the paint so the "
"triangles can be counted. The grid at the bottom left states the "
"same corners in cyan, magenta, yellow and black, which is what a "
"press is given. The last one is the same mesh again, named as a "
"fill rather than painted straight onto the page, so the disc it "
"sits under is what decides where it shows."
),
)
# The page in French.
FRENCH = Words(
title="Maillages de triangles, sommet par sommet",
one="Un triangle, trois sommets",
grid="Soixante triangles, arêtes tracées",
inks="Les mêmes soixante, dans les quatre encres",
fill="Le maillage en remplissage, dans un disque",
legend=(
"Chaque sommet ci-dessus porte un point et une couleur, et la "
"couleur entre trois sommets se déduit de ces trois-là. Le triangle "
"seul, en haut à gauche, tient le rouge, le vert et le bleu à ses "
"sommets et rien entre eux ; la grille à côté coupe en deux "
"chacune de ses trente cases, et ses arêtes sont tracées par-dessus "
"la couleur pour qu'on puisse compter les triangles. La grille en "
"bas à gauche énonce les mêmes sommets en cyan, magenta, jaune et "
"noir, ce qu'on remet à une presse. La dernière est encore le même "
"maillage, nommé comme remplissage au lieu d'être peint à même la "
"page : c'est alors le disque qui décide où il se montre."
),
)
# 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 one_triangle(x: float, y: float) -> tuple[
tuple[tuple[float, float], hqf_pdf.Rgb],
tuple[tuple[float, float], hqf_pdf.Rgb],
tuple[tuple[float, float], hqf_pdf.Rgb],
]:
"""The one triangle of the panel at (x, y): green at its foot on the left, blue at
its foot on the right, red at its apex."""
return (
((x + 10.0, y + 15.0), hqf_pdf.Rgb(0.1, 0.7, 0.2)),
((x + BOX[0] - 10.0, y + 15.0), hqf_pdf.Rgb(0.1, 0.2, 0.9)),
((x + BOX[0] / 2.0, y + BOX[1] - 15.0), hqf_pdf.Rgb(0.9, 0.1, 0.1)),
)
def rgb_corner(
x: float, y: float, u: float, v: float
) -> tuple[tuple[float, float], hqf_pdf.Rgb]:
"""The point and the colour a grid corner carries in red, green and blue, `u` of the
way across the panel at (x, y) and `v` of the way up it."""
return (
(x + u * BOX[0], y + v * BOX[1]),
hqf_pdf.Rgb(0.15 + 0.8 * u, 0.15 + 0.8 * v, 0.95 - 0.7 * u - 0.2 * v),
)
def cmyk_corner(
x: float, y: float, u: float, v: float
) -> tuple[tuple[float, float], hqf_pdf.Cmyk]:
"""The point and the colour a grid corner carries in the four printing inks, `u` of
the way across the panel at (x, y) and `v` of the way up it."""
return (
(x + u * BOX[0], y + v * BOX[1]),
hqf_pdf.Cmyk(0.8 * u, 0.75 * v, 0.1 + 0.6 * (1.0 - u), 0.05 + 0.15 * u * v),
)
def grid(x: float, y: float, corner) -> list[tuple[object, object, object]]:
"""The triangles of a grid of COLUMNS by ROWS cells over the panel at (x, y), each
cell cut in two along the diagonal that rises to the right, every corner carrying
what `corner` gives it."""
triangles = []
for row in range(ROWS):
foot = row / ROWS
head = (row + 1) / ROWS
for column in range(COLUMNS):
left = column / COLUMNS
right = (column + 1) / COLUMNS
triangles.append(
(
corner(x, y, left, foot),
corner(x, y, right, foot),
corner(x, y, right, head),
)
)
triangles.append(
(
corner(x, y, left, foot),
corner(x, y, right, head),
corner(x, y, left, head),
)
)
return triangles
def edges(content: hqf_pdf.Content, x: float, y: float) -> None:
"""Strokes the edges of the grid over the panel at (x, y): the lines between the
rows, those between the columns, and the diagonal of each cell."""
for row in range(ROWS + 1):
v = row / ROWS
content.move_to(x, y + v * BOX[1])
content.line_to(x + BOX[0], y + v * BOX[1])
for column in range(COLUMNS + 1):
u = column / COLUMNS
content.move_to(x + u * BOX[0], y)
content.line_to(x + u * BOX[0], y + BOX[1])
for row in range(ROWS):
foot = row / ROWS
head = (row + 1) / ROWS
for column in range(COLUMNS):
left = column / COLUMNS
right = (column + 1) / COLUMNS
content.move_to(x + left * BOX[0], y + foot * BOX[1])
content.line_to(x + right * BOX[0], y + head * BOX[1])
content.stroke()
def disc(content: hqf_pdf.Content, cx: float, cy: float, radius: float) -> None:
"""Fills a disc of radius `radius` about (cx, cy) with whatever the fill is set to,
out of the four cubics that make a circle."""
pull = KAPPA * radius
content.move_to(cx + radius, cy)
content.curve_to(cx + radius, cy + pull, cx + pull, cy + radius, cx, cy + radius)
content.curve_to(cx - pull, cy + radius, cx - radius, cy + pull, cx - radius, cy)
content.curve_to(cx - radius, cy - pull, cx - pull, cy - radius, cx, cy - radius)
content.curve_to(cx + pull, cy - radius, cx + radius, cy - pull, cx + radius, cy)
content.close_path()
content.fill()
def legend(
content: hqf_pdf.Content,
font: hqf_pdf.FontHandle,
top: float,
color: hqf_pdf.Rgb,
text: str,
) -> None:
"""Writes the legend in a column the width of the page."""
flow = hqf_pdf.TextFlow(font, 8.5, leading=12.0, color=color)
lines = flow.break_lines(text, WIDTH)
flow.draw(content, lines, 70.0, 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("mesh.pdf", language)).stem)
document = hqf_pdf.Document()
document.set_license(_licence.licensed())
font = document.add_font(hqf_pdf.Font.from_path(_out.font_path()))
# Left column x, right column x, top row foot, bottom row foot.
left, right, top, bottom = 70.0, 315.0, 560.0, 300.0
single = document.add_triangle_mesh([one_triangle(left, top)])
colours = document.add_triangle_mesh(grid(right, top, rgb_corner))
inks = document.add_triangle_mesh(grid(left, bottom, cmyk_corner))
fill = document.add_triangle_mesh_pattern(grid(right, bottom, rgb_corner))
content = hqf_pdf.Content()
content.draw_text(font, 18.0, left, 790.0, words.title)
content.draw_shading(single)
content.draw_text(font, 10.0, left, top - 15.0, words.one)
content.draw_shading(colours)
content.save_state()
content.set_stroke(hqf_pdf.Rgb.gray(1.0))
content.set_line_width(0.4)
edges(content, right, top)
content.restore_state()
content.draw_text(font, 10.0, right, top - 15.0, words.grid)
content.draw_shading(inks)
content.draw_text(font, 10.0, left, bottom - 15.0, words.inks)
content.save_state()
content.set_fill_pattern(fill)
disc(content, right + BOX[0] / 2.0, bottom + BOX[1] / 2.0, BOX[1] / 2.0)
content.restore_state()
content.draw_text(font, 10.0, right, bottom - 15.0, words.fill)
legend(content, font, 265.0, hqf_pdf.Rgb.gray(0.35), words.legend)
page = hqf_pdf.Page.a4()
page.set_content(content)
document.add_page(page)
written = document.write(out)
print(f"wrote {out}: {written} bytes, {TRIANGLES} triangles in all")
if __name__ == "__main__":
main()
|