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 | """Pours one long passage into three columns, flowing from the foot of each to
the head of the next.
The Python twin of the `write_columns` example in Rust. The passage is broken
once to the column width, then set column by column: each column fits as many of
the lines as its height allows, draws that slice with `Lines.sub`, and the next
column takes up where it stopped. A thin rule outlines each column so the text
can be seen reaching its foot and carrying on.
The passage is held in `Words`, once per language, and `HQF_PDF_LANG` picks which one
is poured. The measure, the gap and the height are not language: they are the same
three columns whoever reads the page.
Usage: python examples/write_columns.py [out.pdf] [font.ttf]
HQF_PDF_LANG=fr python examples/write_columns.py
"""
from __future__ import annotations
from dataclasses import dataclass
from pathlib import Path
import _language
import _licence
import _out
import hqf_pdf
@dataclass(frozen=True)
class Words:
"""Every word the page draws, in one language."""
# The passage poured through the columns, long enough to reach the third.
passage: str
# The page in English.
ENGLISH = Words(
passage=(
"A flow too long for one column is set column by column. Each column takes "
"as many of the broken lines as its height allows, draws them, and hands the "
"rest to the column beside it, which takes up exactly where the last one "
"stopped. The words are broken once, to the width one column has, so every "
"column is set to the same measure. A line that would fall below a column's "
"foot opens the next column instead, and the reading runs down one column "
"and on into the top of the next with no line lost and none set twice. This "
"is what sets a newspaper's text, a report's two columns, or the narrow "
"measure a wide page reads best in."
),
)
# The page in French.
FRENCH = Words(
passage=(
"Un texte trop long pour une seule colonne se compose colonne par colonne. "
"Chaque colonne prend autant de lignes coupées que sa hauteur le permet, les "
"dessine, et passe le reste à sa voisine, qui reprend là où la "
"précédente s'est arrêtée. Les mots ne sont coupés qu'une fois, à la largeur "
"d'une colonne, et toutes sont composées à la même mesure. Une ligne qui "
"tomberait sous le pied d'une colonne ouvre la suivante, et la lecture descend "
"une colonne puis remonte en haut de la prochaine sans qu'aucune ligne soit "
"perdue ni composée deux fois. C'est ainsi que se composent le "
"texte d'un journal, les deux colonnes d'un rapport, ou la colonne étroite "
"qui rend une page large lisible."
),
)
# 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}
# The left edge of the first column.
LEFT = 70.0
# The top edge every column shares.
TOP = 720.0
# The width of one column.
COLUMN = 145.0
# The gap between one column and the next.
GAP = 25.0
# How far down from TOP a column reaches.
HEIGHT = 130.0
# How many columns the passage is poured through.
COLUMNS = 3
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("columns.pdf", language)).stem)
document = hqf_pdf.Document()
document.set_license(_licence.licensed())
handle = document.add_font(hqf_pdf.Font.from_path(_out.font_path()))
flow = hqf_pdf.TextFlow(handle, 10.0, leading=14.0, align=hqf_pdf.Align.Justify)
lines = flow.break_lines(words.passage, COLUMN)
content = hqf_pdf.Content()
start = 0
x = LEFT
for _ in range(COLUMNS):
taken = flow.fit(lines, HEIGHT, start)
flow.draw(content, lines.sub(start, start + taken), x, TOP, COLUMN)
content.save_state()
content.set_stroke(hqf_pdf.Rgb.gray(0.8))
content.set_line_width(0.5)
content.rect(x, TOP - HEIGHT, COLUMN, HEIGHT)
content.stroke()
content.restore_state()
start += taken
x += COLUMN + GAP
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()
|