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
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406 | //! Writes one page of each paper size the library names, and a few pages told
//! how a reader is to show them.
//!
//! Each page states its own name and its size, measured off the page in
//! millimetres and in inches, and is framed a centimetre in from its edges, so
//! a reader that fits it to the screen shows the whole sheet. The sizes run
//! from A0 to A6, B4 and B5, then the four the North American trade uses.
//!
//! After them come an A5 page laid on its side; pages the reader is told to
//! turn upside down and a quarter to the left; pages that ask for the order a
//! reader tabs through what they carry; and a banner ten metres long, larger
//! than a page may be, drawn on a page whose unit is ten points.
//!
//! The words are held in `Words`, once per language, and `HQF_PDF_LANG` picks
//! which set the pages are written in.
//!
//! Usage: `cargo run --example write_paper_sizes -- tmp/paper_sizes.pdf`
//! `HQF_PDF_LANG=fr cargo run --example write_paper_sizes --
//! tmp/paper_sizes_fr.pdf`
use std::env;
use std::fs;
use std::path::{Path, PathBuf};
use hqf_pdf::content::Content;
use hqf_pdf::cos::Name;
use hqf_pdf::{Document, Font, FontHandle, Page, PageTurn, TabOrder, TextFlow, Version};
#[path = "shared/out.rs"]
mod out;
#[path = "shared/licence.rs"]
mod licence;
#[path = "shared/language.rs"]
mod language;
#[path = "shared/failure.rs"]
mod failure;
use language::Language;
/// The font the pages are written in.
fn default_font() -> PathBuf {
Path::new(env!("CARGO_MANIFEST_DIR"))
.join("tests")
.join("fonts")
.join("DejaVuSans.ttf")
}
/// How many points make a millimetre.
const POINTS_PER_MM: f64 = 72.0 / 25.4;
/// How far in from each edge the frame is drawn, in points: one centimetre.
const FRAME: f64 = 10.0 * POINTS_PER_MM;
/// How long the banner is, in millimetres: ten metres.
const BANNER_MM: f64 = 10_000.0;
/// How many points one unit of the banner's page stands for.
const BANNER_UNIT: f64 = 10.0;
/// Every word the pages say, in one language.
#[derive(Debug)]
struct Words {
/// What the file says of itself.
title: &'static str,
/// What a page says of its size; `{name}`, `{mm}` and `{inches}` stand for
/// them.
size: &'static str,
/// What the A5 page laid on its side is called.
side: &'static str,
/// What the three turned pages say: upside down, a quarter to the left,
/// upright.
turns: [&'static str; 3],
/// What the pages asking for a tab order say, one per order: structure,
/// annotations, widgets.
tabs: [&'static str; 3],
/// What the banner says; `{metres}` and `{unit}` stand for its length and
/// the points one unit stands for.
banner: &'static str,
/// The character separating the whole part of a figure from its tenths.
decimal: char,
}
impl Words {
/// The words the pages are written in, in `language`.
fn of(language: Language) -> &'static Self {
language::pick(&WORDS, language)
}
}
/// The pages in English.
const ENGLISH: Words = Words {
title: "Every paper size, and pages told how to be shown",
size: "{name}: {mm} mm, {inches} in",
side: "A5 on its side",
turns: [
"This page is to be shown upside down.",
"This page is to be shown turned a quarter to the left.",
"This page is to be shown as it was drawn.",
],
tabs: [
"A reader tabs through this page in the order its structure reads it.",
"A reader tabs through this page in the order it lists its annotations.",
"A reader tabs through this page's fields first, then the rest.",
],
banner: "A banner {metres} metres long: each unit of this page stands for {unit} points.",
decimal: '.',
};
/// The pages in French.
const FRENCH: Words = Words {
title: "Tous les formats de papier, et des pages qui disent comment les afficher",
size: "{name} : {mm} mm, {inches} po",
side: "A5 à l’italienne",
turns: [
"Cette page doit être affichée à l’envers.",
"Cette page doit être affichée tournée d’un quart vers la gauche.",
"Cette page doit être affichée telle qu’elle a été composée.",
],
tabs: [
"Avec la touche Tab, on parcourt cette page dans l’ordre de sa structure.",
"Avec la touche Tab, on parcourt cette page dans l’ordre de ses annotations.",
"Avec la touche Tab, on parcourt d’abord les champs de cette page, puis le reste.",
],
banner: "Une banderole de {metres} mètres : chaque unité de cette page vaut {unit} points.",
decimal: ',',
};
/// The pages in Dutch.
const DUTCH: Words = Words {
title: "Elk papierformaat, en pagina’s die te horen krijgen hoe ze getoond worden",
size: "{name}: {mm} mm, {inches} in",
side: "A5 liggend",
turns: [
"Deze pagina moet ondersteboven getoond worden.",
"Deze pagina moet een kwartslag naar links gedraaid getoond worden.",
"Deze pagina moet getoond worden zoals ze getekend is.",
],
tabs: [
"Een lezer tabt door deze pagina in de volgorde waarin haar structuur haar leest.",
"Een lezer tabt door deze pagina in de volgorde waarin ze haar annotaties opsomt.",
"Een lezer tabt eerst door de velden van deze pagina, dan door de rest.",
],
banner: "Een spandoek van {metres} meter: elke eenheid van deze pagina staat voor {unit} punten.",
decimal: '.',
};
/// The pages in Portuguese.
const PORTUGUESE: Words = Words {
title: "Todos os formatos de papel, e páginas às quais se diz como devem ser mostradas",
size: "{name}: {mm} mm, {inches} in",
side: "A5 ao baixo",
turns: [
"Esta página deve ser mostrada de pernas para o ar.",
"Esta página deve ser mostrada rodada um quarto de volta para a esquerda.",
"Esta página deve ser mostrada tal como foi desenhada.",
],
tabs: [
"Um leitor percorre esta página com o tabulador pela ordem da sua estrutura.",
"Um leitor percorre esta página com o tabulador pela ordem em que lista as suas anotações.",
"Um leitor percorre com o tabulador primeiro os campos desta página, depois o resto.",
],
banner: "Uma faixa de {metres} metros: cada unidade desta página vale {unit} pontos.",
decimal: '.',
};
/// The pages in Italian.
const ITALIAN: Words = Words {
title: "Ogni formato di carta, e pagine a cui si dice come mostrarsi",
size: "{name}: {mm} mm, {inches} in",
side: "A5 orizzontale",
turns: [
"Questa pagina va mostrata capovolta.",
"Questa pagina va mostrata ruotata di un quarto a sinistra.",
"Questa pagina va mostrata come è stata disegnata.",
],
tabs: [
"Un lettore percorre questa pagina col tasto Tab nell’ordine della sua struttura.",
"Un lettore percorre questa pagina col tasto Tab nell’ordine in cui elenca le sue annotazioni.",
"Un lettore percorre col tasto Tab prima i campi di questa pagina, poi il resto.",
],
banner: "Uno striscione di {metres} metri: ogni unità di questa pagina vale {unit} punti.",
decimal: '.',
};
/// The pages in Spanish.
const SPANISH: Words = Words {
title: "Todos los tamaños de papel, y páginas a las que se dice cómo mostrarse",
size: "{name}: {mm} mm, {inches} in",
side: "A5 apaisado",
turns: [
"Esta página debe mostrarse cabeza abajo.",
"Esta página debe mostrarse girada un cuarto a la izquierda.",
"Esta página debe mostrarse tal como se dibujó.",
],
tabs: [
"Un lector recorre esta página con el tabulador en el orden en que la lee su estructura.",
"Un lector recorre esta página con el tabulador en el orden en que enumera sus anotaciones.",
"Un lector recorre primero los campos de esta página con el tabulador, y luego el resto.",
],
banner: "Una pancarta de {metres} metros: cada unidad de esta página vale {unit} puntos.",
decimal: '.',
};
/// The pages in German.
const GERMAN: Words = Words {
title: "Jedes Papierformat und Seiten, denen gesagt wird, wie sie anzuzeigen sind",
size: "{name}: {mm} mm, {inches} Zoll",
side: "A5 quer",
turns: [
"Diese Seite ist auf dem Kopf anzuzeigen.",
"Diese Seite ist um eine Vierteldrehung nach links gedreht anzuzeigen.",
"Diese Seite ist so anzuzeigen, wie sie gezeichnet wurde.",
],
tabs: [
"Ein Leser springt mit der Tabulatortaste in der Reihenfolge ihrer Struktur durch diese Seite.",
"Ein Leser springt mit der Tabulatortaste in der Reihenfolge ihrer Anmerkungen durch diese Seite.",
"Ein Leser springt mit der Tabulatortaste zuerst durch die Felder dieser Seite, dann durch den Rest.",
],
banner: "Ein Banner von {metres} Metern Länge: Jede Einheit dieser Seite steht für {unit} Punkt.",
decimal: '.',
};
/// Every language the example is written in. A language is added by writing its
/// own set of words and naming it here.
static WORDS: [(Language, &Words); 7] = [
(Language::English, &ENGLISH),
(Language::French, &FRENCH),
(Language::German, &GERMAN),
(Language::Spanish, &SPANISH),
(Language::Italian, &ITALIAN),
(Language::Portuguese, &PORTUGUESE),
(Language::Dutch, &DUTCH),
];
/// The sizes the library names, each with what it is called.
const fn sizes() -> [(&'static str, Page); 13] {
[
("A0", Page::a0()),
("A1", Page::a1()),
("A2", Page::a2()),
("A3", Page::a3()),
("A4", Page::a4()),
("A5", Page::a5()),
("A6", Page::a6()),
("B4", Page::b4()),
("B5", Page::b5()),
("Letter", Page::letter()),
("Legal", Page::legal()),
("Tabloid", Page::tabloid()),
("Ledger", Page::ledger()),
]
}
/// `value` to `places` decimals, its decimal separator the language's own.
fn figure(value: f64, places: usize, words: &Words) -> String {
format!("{value:.places$}").replace('.', &words.decimal.to_string())
}
/// What `page`, called `called`, says of its size.
fn size_line(called: &str, page: &Page, words: &Words) -> String {
let (width, height) = (page.width, page.height);
words
.size
.replace("{name}", called)
.replace(
"{mm}",
&format!(
"{} × {}",
figure(width / POINTS_PER_MM, 0, words),
figure(height / POINTS_PER_MM, 0, words)
),
)
.replace(
"{inches}",
&format!(
"{} × {}",
figure(width / 72.0, 1, words),
figure(height / 72.0, 1, words)
),
)
}
/// `page` framed a centimetre in from its edges, with `lines` set in its top
/// left corner at a size that grows with the sheet, each broken to the frame.
fn framed(mut page: Page, font: &FontHandle, lines: &[String]) -> Result<Page, hqf_pdf::Error> {
let (width, height) = (page.width, page.height);
let size = (width.min(height) / 24.0).max(10.0);
let mut c = Content::new();
c.set_line_width(1.0)?;
c.rect(FRAME, FRAME, width - FRAME - FRAME, height - FRAME - FRAME)?
.stroke();
let flow = TextFlow::new(font, size).leading(size * 1.4);
let room = width - FRAME - FRAME - size - size;
let mut top = height - FRAME - size;
for line in lines {
let broken = flow.break_lines(line, room);
c.begin_text();
flow.draw(&mut c, &broken, FRAME + size, top, room)?;
c.end_text();
top -= flow.height(&broken);
}
page.content = c.into_bytes();
Ok(page)
}
/// Every page of the document, in order.
fn pages(font: &FontHandle, words: &Words) -> Result<Vec<Page>, hqf_pdf::Error> {
let mut pages = Vec::new();
for (name, page) in sizes() {
let line = size_line(name, &page, words);
pages.push(framed(page, font, &[line])?);
}
let side = Page::a5().on_its_side();
let line = size_line(words.side, &side, words);
pages.push(framed(side, font, &[line])?);
for (turn, said) in [PageTurn::UpsideDown, PageTurn::Left, PageTurn::Upright]
.into_iter()
.zip(words.turns)
{
let mut page = framed(Page::a5(), font, &[said.to_owned()])?;
page.turn = turn;
pages.push(page);
}
for (order, said) in [
TabOrder::Structure,
TabOrder::Annotations,
TabOrder::Widgets,
]
.into_iter()
.zip(words.tabs)
{
let mut page = framed(Page::a5(), font, &[said.to_owned()])?;
page.tab_order = Some(order);
pages.push(page);
}
let long = BANNER_MM * POINTS_PER_MM / BANNER_UNIT;
let tall = 500.0 * POINTS_PER_MM / BANNER_UNIT;
let said = words
.banner
.replace("{metres}", &figure(BANNER_MM / 1000.0, 0, words))
.replace("{unit}", &figure(BANNER_UNIT, 0, words));
let mut banner = framed(Page::new(long, tall), font, &[said])?;
banner.user_unit = Some(BANNER_UNIT);
pages.push(banner);
Ok(pages)
}
fn main() -> std::process::ExitCode {
failure::reported(run())
}
fn run() -> Result<(), Box<dyn std::error::Error>> {
let language = Language::from_environment()?;
let words = Words::of(language);
let path = env::args()
.nth(1)
.unwrap_or_else(|| language.file_name(&out::default_path("paper_sizes")));
let mut doc = Document::new();
doc.set_license(licence::licensed());
// The two last tab orders are read from PDF 2.0 on.
doc.set_version(Version::V2_0);
doc.set_info(Name::new("Title"), words.title);
let font = doc.add_font(Font::parse(fs::read(default_font())?)?);
for page in pages(&font, words)? {
doc.add_page(page)?;
}
let bytes = doc.to_bytes()?;
if let Some(parent) = Path::new(&path).parent() {
fs::create_dir_all(parent)?;
}
fs::write(&path, &bytes)?;
println!("wrote {} ({} bytes)", path, bytes.len());
Ok(())
}
#[cfg(test)]
mod tests {
use super::{WORDS, language};
/// The lines two languages are allowed to write the same way, where two
/// languages spell a word alike. Every line is words a person reads.
const SPARED: [&str; 1] = [
// Written the same way in Spanish.
r#"size: "{name}: {mm} mm, {inches} in""#,
];
#[test]
fn every_language_writes_the_pages_in_its_own_words() {
let untranslated = language::untranslated_lines(&WORDS, &SPARED);
assert!(
untranslated.is_empty(),
"the pages say these in more than one language: {untranslated:?}"
);
}
}
|