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
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698 | //! Sets a whole page in fonts the file does not carry, and weighs what that
//! saves.
//!
//! Reading software is required to draw fourteen fonts whether the file carries
//! them or not, so a document that names one of them writes no font program: a
//! few hundred bytes of dictionary instead of the tens of kilobytes a subset
//! costs. The page below is set in four of them, and the last line says what
//! the same page weighs when the font is carried instead — the figure is
//! measured by the program, not written in by hand.
//!
//! A second page sets a line in each of the fourteen, and under it what the
//! face states about itself: how far it rises and falls, how tall its capitals
//! are and how far it slants, whether every glyph has one width, whether it
//! reaches its glyphs through an encoding of its own, how wide its line comes
//! out, and which characters of a probe it has no glyph for.
//!
//! A third page sets the same lines in the free faces drawn to the widths of
//! the fourteen, which a file carries where it must carry its fonts, and says
//! how wide each line comes out.
//!
//! Both sets of words are held in `Words`, once per language, and
//! `HQF_PDF_LANG` picks which set is drawn.
//!
//! Usage: `cargo run --example write_no_font_at_all -- tmp/light.pdf
//! [font.ttf]`
//! `HQF_PDF_LANG=fr cargo run --example write_no_font_at_all`
use std::env;
use std::fs;
use std::path::{Path, PathBuf};
use hqf_pdf::content::Content;
use hqf_pdf::font::standard::FreeFamily;
use hqf_pdf::{Document, Font, FontHandle, Page, Rgb, StandardFont, TextFlow};
#[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 weighed against the fourteen when none is given on the command
/// line: the one committed for the tests, so the example runs on any machine.
fn default_font() -> PathBuf {
Path::new(env!("CARGO_MANIFEST_DIR"))
.join("tests")
.join("fonts")
.join("DejaVuSans.ttf")
}
/// The words the page is written in, one set per language.
#[derive(Debug)]
struct Words {
/// The page's title.
title: &'static str,
/// What the page is about.
lead: &'static str,
/// The label over the four samples.
faces: &'static str,
/// What each of the four samples says.
samples: [&'static str; 4],
/// The heading over the weight.
weight: &'static str,
/// What the two weights are called, and the sentence they are put into.
saved: &'static str,
/// What such a font cannot do.
caveat: &'static str,
/// The second page's heading.
fourteen: &'static str,
/// The line each of the twelve text faces is set in.
pangram: &'static str,
/// What a face states about itself; `{ascent}`, `{descent}`, `{cap}`,
/// `{slant}`, `{pitch}` and `{encoding}` stand for the figures and the two
/// words below.
facts: &'static str,
/// Whether every glyph has one width: yes, then no.
pitch: [&'static str; 2],
/// Whether the face has an encoding of its own: yes, then no.
encoding: [&'static str; 2],
/// How wide the line comes out and what of the probe the face lacks;
/// `{width}`, `{count}` and `{lacks}` stand for the width, how many
/// characters were probed and the ones lacking.
measured: &'static str,
/// What a face that lacks nothing of the probe is said to lack.
nothing: &'static str,
/// The character that separates the whole part of a figure from its tenths.
decimal: char,
/// The third page's heading.
carried: &'static str,
/// What the third page shows.
carried_lead: &'static str,
/// How wide the line comes out in the free face; `{width}` stands for it.
carried_width: &'static str,
}
impl Words {
/// The words the page is written in, in `language`.
fn of(language: Language) -> &'static Self {
language::pick(&WORDS, language)
}
}
/// The page in English.
const ENGLISH: Words = Words {
title: "A page that carries no font",
lead: "Every one of the lines below is set in a font this file does not hold. \
Reading software draws them from its own store, which is what the \
standard asks of it, so nothing of the typefaces travels with the document.",
faces: "Four of the fourteen",
samples: [
"Helvetica, the one without serifs.",
"Times Bold Italic, and it is already accented: déjà, ç, ù, ô, ï.",
"Courier, where every letter is the same width.",
"\u{03A3} \u{221A} \u{222B} \u{03B1}\u{03B2}\u{03B3} \u{2265} \u{221E}",
],
weight: "What it saves",
saved: "Those four lines, on a page of their own, weigh %light% bytes. The \
same four lines, with one font carried inside the file, weigh %heavy%. \
Both figures are measured by the program that drew this page, not \
written in by hand.",
caveat: "It is not free. A file claiming an archival or accessibility standard \
may not do this — those standards want every font carried — and these \
faces draw only the letters their own encoding reaches, so a writing \
system outside it needs a font the file does carry.",
fourteen: "The fourteen, and what each states about itself",
pangram: "Sphinx of black quartz, judge my vow",
facts: "rises {ascent}, falls {descent}, capitals {cap} pt at 10 pt; slants {slant}°; \
{pitch}; {encoding}",
pitch: ["one width for every glyph", "a width for each glyph"],
encoding: [
"an encoding of its own",
"the encoding the text faces share",
],
measured: "the line runs {width} pt; of the {count} characters probed it lacks {lacks}",
nothing: "nothing",
decimal: '.',
carried: "The same fourteen, embedded in the file",
carried_lead: "A file that must embed its fonts sets each of the fourteen in a \
free typeface designed to the same character widths: Liberation \
here, though TeX Gyre would do as well. Each line below is set in \
that typeface, labelled with its own name, and comes out exactly as \
wide as on the previous page.",
carried_width: "line width: {width} pt",
};
/// The page in French.
const FRENCH: Words = Words {
title: "Une page qui n’incorpore aucune police",
lead: "Chacune des lignes ci-dessous est composée dans une police que ce fichier ne \
contient pas. Le logiciel de lecture les puise dans sa propre réserve, comme \
la norme le lui demande : rien de ces polices ne voyage avec le document.",
faces: "Quatre des quatorze",
samples: [
"Helvetica, celle qui n’a pas d’empattements.",
"Times gras italique, et déjà accentuée : ç, ù, ô, ï, œ.",
"Courier, où chaque lettre occupe la même largeur.",
"\u{03A3} \u{221A} \u{222B} \u{03B1}\u{03B2}\u{03B3} \u{2265} \u{221E}",
],
weight: "Ce que cela économise",
saved: "Ces quatre lignes, seules sur une page, pèsent %light% octets. Les mêmes \
quatre lignes, avec une police incorporée dans le fichier, en pèsent %heavy%. \
Les deux chiffres sont mesurés par le programme qui a créé cette page, pas \
saisis à la main.",
caveat: "Cela a un prix. Un fichier qui se réclame d’une norme d’archivage ou \
d’accessibilité n’a pas le droit de le faire — ces normes exigent que toute \
police soit incorporée — et ces polices n’affichent que les lettres que couvre \
leur propre codage : une écriture qui en sort exige une police que le fichier \
incorpore vraiment.",
fourteen: "Les quatorze, et ce que chacune dit d’elle-même",
pangram: "Portez ce vieux whisky au juge blond qui fume",
facts: "monte de {ascent}, descend de {descent}, capitales de {cap} pt à 10 pt ; \
penche de {slant}° ; {pitch} ; {encoding}",
pitch: [
"une même chasse pour tous les glyphes",
"une chasse par glyphe",
],
encoding: [
"son propre encodage",
"l’encodage commun aux polices de texte",
],
measured: "la ligne fait {width} pt ; caractères absents parmi les {count} testés : {lacks}",
nothing: "aucun",
decimal: ',',
carried: "Les mêmes quatorze, incorporées au fichier",
carried_lead: "Un fichier qui doit incorporer ses polices compose chacune des \
quatorze dans une police libre dessinée avec les mêmes chasses : \
ici Liberation, mais TeX Gyre conviendrait aussi. Chaque ligne \
ci-dessous est composée dans cette police, sous son propre nom, et \
a exactement la même largeur qu’à la page précédente.",
carried_width: "la ligne mesure {width} pt",
};
/// The page in Dutch.
const DUTCH: Words = Words {
title: "Een pagina die geen lettertype bevat",
lead: "Elk van de regels hieronder is gezet in een lettertype dat dit bestand niet bevat. Leessoftware tekent ze uit haar eigen voorraad, wat de norm van haar vraagt, dus reist er niets van de letters met het document mee.",
faces: "Vier van de veertien",
samples: [
"Helvetica, die zonder schreven.",
"Times Bold Italic, en al van accenten voorzien: déjà, ç, ù, ô, ï.",
"Courier, waarin elke letter even breed is.",
"Σ √ ∫ αβγ ≥ ∞",
],
weight: "Wat het bespaart",
saved: "Die vier regels, op een eigen pagina, wegen %light% bytes. Dezelfde vier regels, met één lettertype in het bestand, wegen %heavy%. Beide getallen zijn gemeten door het programma dat deze pagina tekende, niet met de hand geschreven.",
caveat: "Het is niet gratis. Een bestand dat een archief- of toegankelijkheidsnorm claimt, mag dit niet, want die normen willen elk lettertype aan boord, en deze letters tekenen alleen wat hun eigen codering bereikt, dus een schrift daarbuiten heeft een lettertype nodig dat het bestand wel bevat.",
fourteen: "De veertien, en wat elk over zichzelf zegt",
pangram: "Pa’s wijze lynx bezag vroom het fikse aquaduct",
facts: "stijgt {ascent}, daalt {descent}, hoofdletters {cap} pt op 10 pt; helt {slant}°; {pitch}; {encoding}",
pitch: ["één breedte voor elke glyph", "een eigen breedte per glyph"],
encoding: [
"een eigen codering",
"de codering die de tekstletters delen",
],
measured: "de regel loopt {width} pt; van de {count} geteste tekens mist het {lacks}",
nothing: "niets",
decimal: '.',
carried: "Dezelfde veertien, ingebed in het bestand",
carried_lead: "Een bestand dat zijn lettertypen moet inbedden, zet elk van de \
veertien in een vrij lettertype met dezelfde tekenbreedtes: hier \
Liberation, maar TeX Gyre volstaat evengoed. Elke regel hieronder \
staat in dat lettertype, onder zijn eigen naam, en is precies even \
breed als op de vorige pagina.",
carried_width: "de regel is {width} pt breed",
};
/// The page in Portuguese.
const PORTUGUESE: Words = Words {
title: "Uma página que não leva nenhum tipo de letra",
lead: "Cada uma das linhas abaixo está composta num tipo de letra que este ficheiro não contém. O programa de leitura desenha-as a partir do seu próprio acervo, que é o que a norma lhe pede, por isso nada dos tipos viaja com o documento.",
faces: "Quatro dos catorze",
samples: [
"Helvetica, a que não tem serifas.",
"Times Bold Italic, e já tem acentos: déjà, ç, ù, ô, ï.",
"Courier, onde cada letra tem a mesma largura.",
"Σ √ ∫ αβγ ≥ ∞",
],
weight: "O que se poupa",
saved: "Essas quatro linhas, numa página só delas, pesam %light% bytes. As mesmas quatro linhas, com um tipo de letra dentro do ficheiro, pesam %heavy%. Os dois números são medidos pelo programa que criou esta página, não escritos à mão.",
caveat: "Não é de graça. Um ficheiro que declara uma norma de arquivo ou de acessibilidade não o pode fazer, porque essas normas querem todos os tipos de letra dentro, e estes tipos só desenham as letras que a sua codificação alcança, por isso uma escrita fora dela precisa de um tipo de letra que o ficheiro leve.",
fourteen: "Os catorze, e o que cada um diz de si mesmo",
pangram: "Um pequeno jabuti xereta viu dez cegonhas felizes",
facts: "sobe {ascent}, desce {descent}, maiúsculas {cap} pt a 10 pt; inclina {slant}°; {pitch}; {encoding}",
pitch: [
"a mesma largura para todos os glifos",
"uma largura própria para cada glifo",
],
encoding: [
"uma codificação própria",
"a codificação que os tipos de texto partilham",
],
measured: "a linha mede {width} pt; dos {count} caracteres testados faltam-lhe {lacks}",
nothing: "nada",
decimal: '.',
carried: "Os mesmos catorze, incorporados no ficheiro",
carried_lead: "Um ficheiro que tem de incorporar os seus tipos de letra compõe \
cada um dos catorze num tipo de letra livre desenhado com as mesmas \
larguras de carácter: aqui o Liberation, mas o TeX Gyre serviria \
igualmente. Cada linha abaixo está composta nesse tipo de letra, \
com o seu próprio nome, e mede exatamente o mesmo que na página \
anterior.",
carried_width: "a linha mede {width} pt",
};
/// The page in Italian.
const ITALIAN: Words = Words {
title: "Una pagina che non porta nessun font",
lead: "Ognuna delle righe qui sotto è composta in un font che questo file non contiene. Il programma di lettura le disegna con i propri caratteri, che è ciò che gli chiede la norma, così niente dei caratteri viaggia con il documento.",
faces: "Quattro dei quattordici",
samples: [
"Helvetica, quella senza grazie.",
"Times Bold Italic, e ha già gli accenti: déjà, ç, ù, ô, ï.",
"Courier, dove ogni lettera ha la stessa larghezza.",
"Σ √ ∫ αβγ ≥ ∞",
],
weight: "Cosa fa risparmiare",
saved: "Quelle quattro righe, su una pagina propria, pesano %light% byte. Le stesse quattro righe, con un font dentro il file, pesano %heavy%. Entrambe le cifre sono misurate dal programma che ha disegnato questa pagina, non scritte a mano.",
caveat: "Non è gratis. Un file che dichiara una norma d’archivio o di accessibilità non può farlo, perché quelle norme vogliono ogni font a bordo, e questi caratteri disegnano solo le lettere che la loro codifica raggiunge, così una scrittura fuori da essa richiede un font che il file porti davvero.",
fourteen: "I quattordici, e ciò che ciascuno dice di sé",
pangram: "Pranzo d’acqua fa volti sghembi",
facts: "sale {ascent}, scende {descent}, maiuscole {cap} pt a 10 pt; inclina di {slant}°; {pitch}; {encoding}",
pitch: ["una larghezza per ogni glifo", "una larghezza per glifo"],
encoding: [
"una codifica propria",
"la codifica che condividono i caratteri da testo",
],
measured: "la riga misura {width} pt; dei {count} caratteri provati gli mancano {lacks}",
nothing: "nulla",
decimal: '.',
carried: "Gli stessi quattordici, incorporati nel file",
carried_lead: "Un file che deve incorporare i propri font compone ciascuno dei \
quattordici in un font libero disegnato con le stesse larghezze dei \
caratteri: qui Liberation, ma va bene anche TeX Gyre. Ogni riga qui \
sotto è composta in quel font, con il suo nome, ed è larga \
esattamente quanto nella pagina precedente.",
carried_width: "la riga misura {width} pt",
};
/// The page in Spanish.
const SPANISH: Words = Words {
title: "Una página que no lleva ninguna fuente",
lead: "Cada una de las líneas de abajo está compuesta en una fuente que este archivo no contiene. El programa de lectura las dibuja con su propio repertorio, que es lo que le pide la norma, así que nada de los tipos viaja con el documento.",
faces: "Cuatro de las catorce",
samples: [
"Helvetica, la que no tiene serifas.",
"Times Bold Italic, y ya lleva acentos: déjà, ç, ù, ô, ï.",
"Courier, donde cada letra tiene el mismo ancho.",
"Σ √ ∫ αβγ ≥ ∞",
],
weight: "Lo que ahorra",
saved: "Esas cuatro líneas, en una página propia, pesan %light% bytes. Las mismas cuatro líneas, con una fuente dentro del archivo, pesan %heavy%. Las dos cifras las mide el programa que dibujó esta página, no se escriben a mano.",
caveat: "No es gratis. Un archivo que declara una norma de archivo o de accesibilidad no puede hacerlo —esas normas exigen todas las fuentes dentro— y estos tipos solo dibujan las letras que alcanza su propia codificación, así que una escritura fuera de ella necesita una fuente que el archivo sí lleve.",
fourteen: "Las catorce, y lo que cada una dice de sí misma",
pangram: "Jovencillo emponzoñado de whisky, qué figurota exhibe",
facts: "sube {ascent}, baja {descent}, mayúsculas {cap} pt a 10 pt; se inclina {slant}°; {pitch}; {encoding}",
pitch: ["un mismo ancho para cada glifo", "un ancho por glifo"],
encoding: [
"una codificación propia",
"la codificación que comparten los tipos de texto",
],
measured: "la línea mide {width} pt; de los {count} caracteres probados le faltan {lacks}",
nothing: "nada",
decimal: '.',
carried: "Las mismas catorce, incrustadas en el archivo",
carried_lead: "Un archivo que debe incrustar sus fuentes compone cada una de las \
catorce en una fuente libre diseñada con las mismas anchuras de \
carácter: aquí Liberation, aunque TeX Gyre serviría igual. Cada \
línea que sigue está compuesta en esa fuente, con su propio nombre, \
y mide exactamente lo mismo que en la página anterior.",
carried_width: "la línea mide {width} pt",
};
/// The page in German.
const GERMAN: Words = Words {
title: "Eine Seite, die keine Schrift trägt",
lead: "Jede der Zeilen unten ist in einer Schrift gesetzt, die diese Datei nicht enthält. Die Lesesoftware zeichnet sie aus ihrem eigenen Vorrat, wie es die Norm von ihr verlangt, also reist nichts von den Schriften mit dem Dokument.",
faces: "Vier der vierzehn",
samples: [
"Helvetica, die ohne Serifen.",
"Times Bold Italic, schon mit Akzenten: déjà, ç, ù, ô, ï.",
"Courier, bei der jeder Buchstabe gleich breit ist.",
"Σ √ ∫ αβγ ≥ ∞",
],
weight: "Was es spart",
saved: "Diese vier Zeilen wiegen auf einer eigenen Seite %light% Bytes. Dieselben vier Zeilen mit einer in der Datei getragenen Schrift wiegen %heavy%. Beide Zahlen misst das Programm, das diese Seite erstellt hat, keine ist von Hand eingetragen.",
caveat: "Umsonst ist es nicht. Eine Datei, die eine Archiv- oder Barrierefreiheitsnorm beansprucht, darf das nicht — diese Normen wollen jede Schrift mitgeführt sehen —, und diese Schriften zeichnen nur die Buchstaben, die ihre eigene Kodierung erreicht, also braucht ein Schriftsystem außerhalb davon eine Schrift, die die Datei trägt.",
fourteen: "Die vierzehn und was jede über sich angibt",
pangram: "Victor jagt zwölf Boxkämpfer quer über den großen Sylter Deich",
facts: "Oberlänge {ascent}, Unterlänge {descent}, Versalhöhe {cap} pt bei 10 pt; Neigung {slant}°; {pitch}; {encoding}",
pitch: [
"dieselbe Breite für alle Glyphen",
"eine eigene Breite je Glyph",
],
encoding: [
"eine eigene Kodierung",
"die Kodierung, die die Textschriften teilen",
],
measured: "die Zeile ist {width} pt lang; von den {count} geprüften Zeichen fehlen ihr {lacks}",
nothing: "keine",
decimal: '.',
carried: "Dieselben vierzehn, in die Datei eingebettet",
carried_lead: "Eine Datei, die ihre Schriften einbetten muss, setzt jede der \
vierzehn in einer freien Schrift mit denselben Zeichenbreiten: hier \
Liberation, TeX Gyre ginge ebenso. Jede der folgenden Zeilen ist in \
dieser Schrift gesetzt, mit deren eigenem Namen beschriftet und \
genau so breit wie auf der vorigen Seite.",
carried_width: "die Zeile ist {width} pt breit",
};
/// 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 left edge of everything on the page.
const LEFT: f64 = 72.0;
/// How wide a block of text is.
const WIDTH: f64 = 450.0;
/// Sets a block of words with its first baseline at `top`, and hands back the
/// ordinate the block ends at.
fn block(
c: &mut Content,
handle: &FontHandle,
size: f64,
top: f64,
text: &str,
) -> Result<f64, Box<dyn std::error::Error>> {
let flow = TextFlow::new(handle, size);
let lines = flow.break_lines(text, WIDTH);
c.begin_text();
flow.draw(c, &lines, LEFT, top, WIDTH)?;
c.end_text();
Ok(top - flow.height(&lines))
}
/// What a page holding nothing but the four samples weighs, set either in fonts
/// the file does not carry or in one it does.
///
/// This is the measurement the page reports. It is made on a page of its own so
/// that the prose around it — and the figures themselves — weigh nothing in it.
fn weighed(words: &Words, carried: Option<&Path>) -> Result<usize, Box<dyn std::error::Error>> {
let mut doc = Document::new();
doc.set_license(licence::licensed());
let handles: Vec<FontHandle> = match carried {
Some(path) => {
let one = doc.add_font(Font::parse(fs::read(path)?)?);
vec![one; words.samples.len()]
}
None => [
StandardFont::Helvetica,
StandardFont::TimesBoldItalic,
StandardFont::Courier,
StandardFont::Symbol,
]
.into_iter()
.map(|face| doc.add_standard_font(face))
.collect(),
};
let mut c = Content::new();
let mut top = 760.0;
for (handle, sample) in handles.iter().zip(words.samples) {
top = block(&mut c, handle, 13.0, top, sample)? - 10.0;
}
let mut page = Page::a4();
page.content = c.into_bytes();
doc.add_page(page)?;
Ok(doc.to_bytes()?.len())
}
/// Draws the whole page, stating `light` as its own weight and `heavy` as the
/// weight of the page that carries a font.
fn build(words: &Words, light: usize, heavy: usize) -> Result<Vec<u8>, Box<dyn std::error::Error>> {
let mut doc = Document::new();
doc.set_license(licence::licensed());
let faces = [
doc.add_standard_font(StandardFont::Helvetica),
doc.add_standard_font(StandardFont::TimesBoldItalic),
doc.add_standard_font(StandardFont::Courier),
doc.add_standard_font(StandardFont::Symbol),
];
let text = &faces[0];
let mut c = Content::new();
let mut top = 760.0;
top = block(&mut c, text, 18.0, top, words.title)? - 14.0;
top = block(&mut c, text, 10.0, top, words.lead)? - 24.0;
top = block(&mut c, text, 12.0, top, words.faces)? - 12.0;
for (face, sample) in faces.iter().zip(words.samples) {
top = block(&mut c, face, 13.0, top, sample)? - 10.0;
}
top -= 14.0;
top = block(&mut c, text, 12.0, top, words.weight)? - 10.0;
let said = words
.saved
.replace("%light%", &spaced(light))
.replace("%heavy%", &spaced(heavy));
top = block(&mut c, text, 10.0, top, &said)? - 20.0;
c.set_fill(Rgb::gray(0.35))?;
block(&mut c, text, 9.0, top, words.caveat)?;
let mut page = Page::a4();
page.content = c.into_bytes();
doc.add_page(page)?;
let page = fourteen(&mut doc, words)?;
doc.add_page(page)?;
let page = carried(&mut doc, words)?;
doc.add_page(page)?;
Ok(doc.to_bytes()?)
}
/// The probe each face is asked which characters it lacks: an accented Latin
/// letter, a currency sign, a Greek capital and a check mark.
const PROBE: &str = "\u{00E9}\u{20AC}\u{03A9}\u{2713}";
/// The line the two faces whose glyphs are not letters are set in.
const SIGNS: [&str; 2] = [
"\u{03A3} \u{221A} \u{222B} \u{03B1}\u{03B2}\u{03B3} \u{2265} \u{221E}",
"\u{2713} \u{2708} \u{260E} \u{2605} \u{2702} \u{2764}",
];
/// `value` to one decimal, its decimal separator the language's own.
fn figure(value: f64, words: &Words) -> String {
format!("{value:.1}").replace('.', &words.decimal.to_string())
}
/// What `face` states about itself and how its `line` measures, said in
/// `words`.
fn stated(face: StandardFont, line: &str, words: &Words) -> [String; 2] {
let lacking = face.uncovered(PROBE);
let missing = if lacking.is_empty() {
words.nothing.to_owned()
} else {
lacking
.iter()
.map(|character| format!("U+{:04X}", u32::from(*character)))
.collect::<Vec<_>>()
.join(", ")
};
[
words
.facts
.replace("{ascent}", &figure(face.ascent(10.0), words))
.replace("{descent}", &figure(face.descent(10.0), words))
.replace("{cap}", &figure(face.cap_height(10.0), words))
.replace("{slant}", &figure(face.italic_angle(), words))
.replace("{pitch}", words.pitch[usize::from(!face.fixed_pitch())])
.replace(
"{encoding}",
words.encoding[usize::from(!face.font_specific())],
),
words
.measured
.replace("{width}", &figure(face.measure(line, 10.0), words))
.replace("{count}", &PROBE.chars().count().to_string())
.replace("{lacks}", &missing),
]
}
/// The second page: a line in each of the fourteen faces, its name beside it
/// and what it states under it.
fn fourteen(doc: &mut Document, words: &Words) -> Result<Page, Box<dyn std::error::Error>> {
let label = doc.add_standard_font(StandardFont::Helvetica);
let mut c = Content::new();
let mut top = block(&mut c, &label, 16.0, 780.0, words.fourteen)? - 12.0;
for face in StandardFont::ALL {
let handle = doc.add_standard_font(face);
let line = match face {
StandardFont::Symbol => SIGNS[0],
StandardFont::ZapfDingbats => SIGNS[1],
_ => words.pangram,
};
c.set_fill(Rgb::gray(0.0))?;
c.begin_text();
c.set_font(&label, 8.0)?;
c.text_origin(LEFT, top)?;
c.show_glyphs(&label.glyphs(face.postscript_name()));
c.end_text();
c.begin_text();
c.set_font(&handle, 10.0)?;
c.text_origin(LEFT + 150.0, top)?;
c.show_glyphs(&handle.glyphs(line));
c.end_text();
let note = TextFlow::new(&label, 7.0)
.leading(8.5)
.color(Rgb::gray(0.4));
let mut y = top - 4.0;
for said in stated(face, line, words) {
let lines = note.break_lines(&said, WIDTH - 150.0);
c.begin_text();
note.draw(&mut c, &lines, LEFT + 150.0, y, WIDTH - 150.0)?;
c.end_text();
y -= note.height(&lines);
}
top = y - 10.0;
}
let mut page = Page::a4();
page.content = c.into_bytes();
Ok(page)
}
/// The third page: the line of each of the fourteen set in the free face drawn
/// to its widths, the face's own name beside it and the width under it.
fn carried(doc: &mut Document, words: &Words) -> Result<Page, Box<dyn std::error::Error>> {
let label = doc.add_standard_font(StandardFont::Helvetica);
let mut c = Content::new();
let mut top = block(&mut c, &label, 16.0, 780.0, words.carried)? - 6.0;
top = block(&mut c, &label, 9.0, top, words.carried_lead)? - 14.0;
for face in StandardFont::ALL {
let free = face.free_face(FreeFamily::Liberation)?;
let name = free.postscript_name().to_owned();
let handle = doc.add_font(free);
let line = match face {
StandardFont::Symbol => SIGNS[0],
StandardFont::ZapfDingbats => SIGNS[1],
_ => words.pangram,
};
c.set_fill(Rgb::gray(0.0))?;
c.begin_text();
c.set_font(&label, 8.0)?;
c.text_origin(LEFT, top)?;
c.show_glyphs(&label.glyphs(&name));
c.end_text();
c.begin_text();
c.set_font(&handle, 10.0)?;
c.text_origin(LEFT + 150.0, top)?;
c.show_glyphs(&handle.glyphs(line));
c.end_text();
let note = words
.carried_width
.replace("{width}", &figure(handle.measure(line, 10.0), words));
c.set_fill(Rgb::gray(0.4))?;
c.begin_text();
c.set_font(&label, 7.0)?;
c.text_origin(LEFT + 150.0, top - 10.0)?;
c.show_glyphs(&label.glyphs(¬e));
c.end_text();
top -= 30.0;
}
let mut page = Page::a4();
page.content = c.into_bytes();
Ok(page)
}
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 mut args = env::args().skip(1);
// A named file is written as named; the default one carries the language,
// so the two languages do not overwrite each other in `tmp/`.
let out = args
.next()
.unwrap_or_else(|| language.file_name(&out::default_path("no_font_at_all")));
let font_path = args.next().map_or_else(default_font, PathBuf::from);
let light = weighed(words, None)?;
let heavy = weighed(words, Some(&font_path))?;
let bytes = build(words, light, heavy)?;
if let Some(parent) = Path::new(&out).parent() {
fs::create_dir_all(parent)?;
}
fs::write(&out, &bytes)?;
println!("wrote {out}: {} bytes", bytes.len());
Ok(())
}
/// A count with a space between its thousands, which is how every language this
/// example is written in writes one.
fn spaced(count: usize) -> String {
let digits = count.to_string();
let mut out = String::with_capacity(digits.len() + digits.len() / 3);
for (index, digit) in digits.chars().enumerate() {
if index > 0 && (digits.len() - index).is_multiple_of(3) {
out.push('\u{00A0}');
}
out.push(digit);
}
out
}
#[cfg(test)]
mod tests {
use super::{WORDS, language, spaced};
/// The lines two languages are allowed to write the same way: the row of
/// signs is not words, it is the same signs whoever reads it.
const SPARED: [&str; 2] = [
"\"\u{03A3} \u{221A} \u{222B} \u{03B1}\u{03B2}\u{03B3} \u{2265} \u{221E}\"",
// Written the same way in Portuguese.
r#"nothing: "nada""#,
];
#[test]
fn every_language_draws_the_page_in_its_own_words() {
let untranslated = language::untranslated_lines(&WORDS, &SPARED);
assert!(
untranslated.is_empty(),
"the page says these in more than one language: {untranslated:?}"
);
}
#[test]
fn a_count_is_written_with_a_space_between_its_thousands() {
assert_eq!(spaced(7), "7");
assert_eq!(spaced(999), "999");
assert_eq!(spaced(1000), "1\u{00A0}000");
assert_eq!(spaced(45_826), "45\u{00A0}826");
assert_eq!(spaced(1_234_567), "1\u{00A0}234\u{00A0}567");
}
}
|