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
308
309
310
311
312 | """Writes a shelf card that marks its lines the three ways a text can be marked.
The Python twin of the `write_text_ornaments` example in Rust. A rule under the
words, a rule through the letters, a rule over the top: the engine takes all
three from the font rather than from a coordinate, so they follow whatever size
the text is set in. The heading and the mark across the foot each carry a shadow
— the same words set once more, offset and paler, under the words themselves.
Every word is held in `Words`, once per language, and `HQF_PDF_LANG` picks which
set is set. The prices are not words: they stand in one table, shared by both
languages.
Usage: python examples/write_text_ornaments.py [out.pdf] [font.ttf]
HQF_PDF_LANG=fr python examples/write_text_ornaments.py
"""
from __future__ import annotations
from dataclasses import dataclass
from pathlib import Path
import _language
import _licence
import _out
import hqf_pdf
# Where the card's text begins, in points from the left edge of the page.
LEFT = 56.0
# How wide it runs.
WIDTH = 483.0
# The size the heading is set in.
TITLE_SIZE = 32.0
# The size the standfirst and the closing note are set in.
NOTE_SIZE = 10.5
# The size a line of the shelf is set in.
ROW_SIZE = 13.0
# The top of the heading's box.
TITLE_TOP = 800.0
# The top of the standfirst.
STANDFIRST_TOP = 748.0
# Where the line under the standfirst is drawn.
HEAD_RULE = 730.0
# The top of the first line of the shelf.
FIRST_ROW = 720.0
# How far apart the lines of the shelf sit.
ROW_STEP = 26.0
# The top of the line that adds the three together.
TOTAL_TOP = 626.0
# The top of the first line of the legend.
LEGEND_TOP = 570.0
# How far apart the lines of the legend sit.
LEGEND_STEP = 24.0
# How far into the box the legend's marked words are set.
LEGEND_INDENT = 24.0
# The top of the closing note.
NOTE_TOP = 470.0
# The box the mark is laid across: its lower-left corner and its size.
MARK_BOX = (LEFT, 120.0, WIDTH, 250.0)
@dataclass(frozen=True)
class Words:
"""Every word the card draws, in one language."""
# What stands across the head of the card.
title: str
# The line under it, which says what the card is showing.
standfirst: str
# What each line of the shelf is called, in the order they are set.
goods: tuple[str, str, str]
# What stands against the sum of the three.
total_label: str
# What opens the legend.
legend: str
# The three marks, each named by words set the way the name says.
under: str
through: str
over: str
# The note at the foot, which says where each mark comes from.
note: str
# What the mark laid across the card reads.
mark: str
def marks(self) -> tuple[str, str, str]:
"""The three marks, in the order the legend sets them."""
return (self.under, self.through, self.over)
# The card in English.
ENGLISH = Words(
title="The half-price shelf",
standfirst="What a shop marks by hand, set by the engine instead.",
goods=(
"Atlas of the northern coast",
"Field guide to the estuary birds",
"The lighthouse keepers, a history",
),
total_label="The three together",
legend="The three marks, each set the way it names:",
under="a rule under the words",
through="a rule through the letters",
over="a rule over the top",
note=(
"No rule on this card was placed by hand. The one under the words and the "
"one through the letters sit where the face itself says they go, and the "
"one over the top rides the ascender the face states; all three follow the "
"size the text is set in. The heading carries a copy of itself behind it, "
"offset and paler, which is what keeps large lettering readable over a "
"photograph. The mark laid across the foot carries one too, turned with it, "
"so the copy falls the same way on the sheet as it does under the heading."
),
mark="HALF PRICE",
)
# The card in French.
FRENCH = Words(
title="L'étagère à moitié prix",
standfirst="Ce qu'un commerçant marque à la main, posé par le moteur.",
goods=(
"Atlas de la côte nord",
"Guide des oiseaux de l'estuaire",
"Les gardiens du phare, une histoire",
),
total_label="Les trois ensemble",
legend="Les trois marques, chacune posée comme elle se nomme :",
under="un trait sous les mots",
through="un trait au milieu des lettres",
over="un trait au-dessus",
note=(
"Aucun trait de cette fiche n'a été placé à la main. Celui du dessous et "
"celui du milieu sont là où le dessin de la lettre les met, et celui du "
"haut suit l'ascendante que la police annonce ; tous trois suivent la "
"taille du texte. Le titre porte une copie de lui-même derrière lui, "
"décalée et plus pâle, ce qui garde un grand lettrage lisible sur une "
"photographie. La mention posée en travers du pied en porte une aussi, "
"tournée avec elle, si bien que la copie tombe sur la feuille du même côté "
"que sous le titre."
),
mark="MOITIÉ PRIX",
)
# Every language the card is written in.
WORDS = {_language.ENGLISH: ENGLISH, _language.FRENCH: FRENCH}
# What each line of the shelf was marked at and what it is marked at now.
#
# Figures are not words: both languages set the same ones, and the engine writes a
# decimal point in every language it is asked for.
PRICES = (
("24.00 EUR", "12.00 EUR"),
("18.00 EUR", "9.00 EUR"),
("30.00 EUR", "15.00 EUR"),
)
# What the three come to together.
TOTAL = "36.00 EUR"
def shadow_ink() -> hqf_pdf.Rgb:
"""The colour every shadow on the card is drawn in."""
return hqf_pdf.Rgb.gray(0.78)
def heading(
content: hqf_pdf.Content, font: hqf_pdf.FontHandle, words: Words
) -> None:
"""Draws the heading, which sets itself once more behind itself."""
style = hqf_pdf.Style(font, TITLE_SIZE)
text = hqf_pdf.RichText(
shadow=hqf_pdf.Shadow(1.5, -1.5, shadow_ink())
).push(words.title, style)
lines = text.break_lines(WIDTH)
text.draw(content, lines, LEFT, TITLE_TOP, WIDTH)
def standfirst(
content: hqf_pdf.Content, font: hqf_pdf.FontHandle, words: Words
) -> None:
"""Draws the standfirst and the line under it."""
flow = hqf_pdf.TextFlow(font, NOTE_SIZE)
lines = flow.break_lines(words.standfirst, WIDTH)
flow.draw(content, lines, LEFT, STANDFIRST_TOP, WIDTH)
content.set_line_width(1.0)
content.move_to(LEFT, HEAD_RULE)
content.line_to(LEFT + WIDTH, HEAD_RULE)
content.stroke()
def shelf_line(
content: hqf_pdf.Content,
font: hqf_pdf.FontHandle,
name: str,
prices: tuple[str, str],
top: float,
) -> None:
"""Draws one line of the shelf: what it is called, then what it was marked at
struck through and what it is marked at now."""
plain = hqf_pdf.Style(font, ROW_SIZE)
struck = hqf_pdf.Style(font, ROW_SIZE, strikethrough=True)
left = hqf_pdf.RichText().push(name, plain)
left.draw(content, left.break_lines(WIDTH), LEFT, top, WIDTH)
right = (
hqf_pdf.RichText(align=hqf_pdf.Align.Right)
.push(prices[0], struck)
.push(" ", plain)
.push(prices[1], plain)
)
right.draw(content, right.break_lines(WIDTH), LEFT, top, WIDTH)
def total(content: hqf_pdf.Content, font: hqf_pdf.FontHandle, words: Words) -> None:
"""Draws the line that adds the three together, the sum ruled over the top the
way a column of figures is ruled before it is totalled."""
plain = hqf_pdf.Style(font, ROW_SIZE)
ruled = hqf_pdf.Style(font, ROW_SIZE, overline=True)
left = hqf_pdf.RichText().push(words.total_label, plain)
left.draw(content, left.break_lines(WIDTH), LEFT, TOTAL_TOP, WIDTH)
right = hqf_pdf.RichText(align=hqf_pdf.Align.Right).push(TOTAL, ruled)
right.draw(content, right.break_lines(WIDTH), LEFT, TOTAL_TOP, WIDTH)
def legend(content: hqf_pdf.Content, font: hqf_pdf.FontHandle, words: Words) -> None:
"""Draws the legend: a line that says what follows, then the three marks, each
named by words set the way the name says."""
plain = hqf_pdf.Style(font, ROW_SIZE)
opening = hqf_pdf.RichText().push(words.legend, plain)
opening.draw(content, opening.break_lines(WIDTH), LEFT, LEGEND_TOP, WIDTH)
marked = (
hqf_pdf.Style(font, ROW_SIZE, underline=True),
hqf_pdf.Style(font, ROW_SIZE, strikethrough=True),
hqf_pdf.Style(font, ROW_SIZE, overline=True),
)
# The tops step down one line at a time rather than being multiplied out, so the
# Rust twin adds the same numbers in the same order.
top = LEGEND_TOP
for name, style in zip(words.marks(), marked):
top -= LEGEND_STEP
line = hqf_pdf.RichText().push(name, style)
room = WIDTH - LEGEND_INDENT
line.draw(content, line.break_lines(room), LEFT + LEGEND_INDENT, top, room)
def note(content: hqf_pdf.Content, font: hqf_pdf.FontHandle, words: Words) -> None:
"""Draws the closing note, which says where each mark comes from."""
flow = hqf_pdf.TextFlow(font, NOTE_SIZE, align=hqf_pdf.Align.Justify)
lines = flow.break_lines(words.note, WIDTH)
flow.draw(content, lines, LEFT, NOTE_TOP, WIDTH)
def mark(content: hqf_pdf.Content, font: hqf_pdf.FontHandle, words: Words) -> None:
"""Draws the mark laid across the foot of the card, which carries a shadow of
its own."""
x, y, width, height = MARK_BOX
stamp = hqf_pdf.Stamp(
font,
words.mark,
slant=hqf_pdf.Slant.Up,
color=hqf_pdf.Rgb(0.72, 0.16, 0.16),
shadow=hqf_pdf.Shadow(2.5, -2.5, shadow_ink()),
)
stamp.draw(content, x, y, width, height)
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("text_ornaments.pdf", language)).stem
)
document = hqf_pdf.Document()
document.set_license(_licence.licensed())
document.set_info("Title", words.title)
font = document.add_font(hqf_pdf.Font.from_path(_out.font_path()))
content = hqf_pdf.Content()
heading(content, font, words)
standfirst(content, font, words)
# The tops step down one line at a time rather than being multiplied out, so the
# Rust twin adds the same numbers in the same order.
top = FIRST_ROW
for name, prices in zip(words.goods, PRICES):
shelf_line(content, font, name, prices, top)
top -= ROW_STEP
total(content, font, words)
legend(content, font, words)
note(content, font, words)
mark(content, font, words)
page = hqf_pdf.Page.a4()
page.set_content(content)
document.add_page(page)
written = document.write(out)
print(f"wrote {out}: {written} bytes")
if __name__ == "__main__":
main()
|