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 | //! Lays a whole French novel out and writes it as one PDF.
//!
//! The twin of `write_english_book`, on a text that draws the accented letters
//! as well as the plain ones. It shows what a wider alphabet costs: more
//! outlines to carry, and metrics still stated only for the glyphs the book
//! draws.
//!
//! Neither book is a translation of the other: each is a novel of its own, and
//! the two are laid out by the same code, so they come out looking alike.
//!
//! Its text is set the way French spaces its marks: a no-break space holds each
//! two-part mark and each quotation mark to the word beside it, the two hyphens
//! the file writes a dash as are drawn as one, and the dash a speech opens on
//! stands off its first word.
//!
//! Usage: `cargo run --example write_french_book -- tmp/french_book.pdf
//! [font.ttf]`
use std::env;
use std::fs;
use std::path::{Path, PathBuf};
use hqf_pdf::{Document, Font};
#[path = "shared/out.rs"]
mod out;
#[path = "shared/licence.rs"]
mod licence;
#[path = "shared/book.rs"]
mod book;
#[path = "shared/failure.rs"]
mod failure;
/// The font the example draws with when none is given on the command line.
fn default_font() -> PathBuf {
Path::new(env!("CARGO_MANIFEST_DIR"))
.join("tests")
.join("fonts")
.join("DejaVuSans.ttf")
}
/// The chapter titles as French sets them.
///
/// The file writes them in capitals throughout, which French does not do, and
/// the capitals have taken the accents with them: the file's second chapter
/// reads `TROUVE SON IDEAL`. Both are undone here — nothing else is, and
/// `check_the_titles_only_change_case` holds this list to that, letter for
/// letter.
const TITLES: [&str; 37] = [
"Dans lequel Phileas Fogg et Passepartout s'acceptent réciproquement l'un comme maître, \
l'autre comme domestique",
"Où Passepartout est convaincu qu'il a enfin trouvé son idéal",
"Où s'engage une conversation qui pourra coûter cher à Phileas Fogg",
"Dans lequel Phileas Fogg stupéfie Passepartout, son domestique",
"Dans lequel une nouvelle valeur apparaît sur la place de Londres",
"Dans lequel l'agent Fix montre une impatience bien légitime",
"Qui témoigne une fois de plus de l'inutilité des passeports en matière de police",
"Dans lequel Passepartout parle un peu plus peut-être qu'il ne conviendrait",
"Où la mer Rouge et la mer des Indes se montrent propices aux desseins de Phileas Fogg",
"Où Passepartout est trop heureux d'en être quitte en perdant sa chaussure",
"Où Phileas Fogg achète une monture à un prix fabuleux",
"Où Phileas Fogg et ses compagnons s'aventurent à travers les forêts de l'Inde et ce qui \
s'ensuit",
"Dans lequel Passepartout prouve une fois de plus que la fortune sourit aux audacieux",
"Dans lequel Phileas Fogg descend toute l'admirable vallée du Gange sans même songer à la voir",
"Où le sac aux bank-notes s'allège encore de quelques milliers de livres",
"Où Fix n'a pas l'air de connaître du tout les choses dont on lui parle",
"Où il est question de choses et d'autres pendant la traversée de Singapore à Hong-Kong",
"Dans lequel Phileas Fogg, Passepartout, Fix, chacun de son côté, va à ses affaires",
"Où Passepartout prend un trop vif intérêt à son maître, et ce qui s'ensuit",
"Dans lequel Fix entre directement en relation avec Phileas Fogg",
"Où le patron de la «\u{a0}Tankadère\u{a0}» risque fort de perdre une prime de deux cents \
livres",
"Où Passepartout voit bien que, même aux antipodes, il est prudent d'avoir quelque argent \
dans sa poche",
"Dans lequel le nez de Passepartout s'allonge démesurément",
"Pendant lequel s'accomplit la traversée de l'océan Pacifique",
"Où l'on donne un léger aperçu de San Francisco, un jour de meeting",
"Dans lequel on prend le train express du chemin de fer du Pacifique",
"Dans lequel Passepartout suit, avec une vitesse de vingt milles à l'heure, un cours \
d'histoire mormone",
"Dans lequel Passepartout ne put parvenir à faire entendre le langage de la raison",
"Où il sera fait le récit d'incidents divers qui ne se rencontrent que sur les rail-roads de \
l'Union",
"Dans lequel Phileas Fogg fait tout simplement son devoir",
"Dans lequel l'inspecteur Fix prend très sérieusement les intérêts de Phileas Fogg",
"Dans lequel Phileas Fogg engage une lutte directe contre la mauvaise chance",
"Où Phileas Fogg se montre à la hauteur des circonstances",
"Qui procure à Passepartout l'occasion de faire un jeu de mots atroce, mais peut-être inédit",
"Dans lequel Passepartout ne se fait pas répéter deux fois l'ordre que son maître lui donne",
"Dans lequel Phileas Fogg fait de nouveau prime sur le marché",
"Dans lequel il est prouvé que Phileas Fogg n'a rien gagné à faire ce tour du monde, si ce \
n'est le bonheur",
];
/// How French spaces its marks.
///
/// The two-part marks and the closing quotation mark stand off the word they
/// close, and the opening quotation mark stands off the word it opens; the
/// space is a no-break one, so a mark is never left alone at a line's end nor
/// opens a line of its own.
///
/// The file writes a dash as two hyphens, which is how a plain text file stands
/// in for one. A speech opens on that dash and an ordinary space stands between
/// it and the first word, which the line may be broken after like any other. A
/// dash between two words is an aside, and keeps to both of them.
const TYPOGRAPHY: book::Typography = book::Typography {
space_before: &['?', '!', ';', ':', '»', '%'],
space_after: &['«'],
dash: Some(book::Dash {
drawn: '—',
opening: " ",
}),
};
/// The novel this lays out, carried in full. Its file opens a chapter on a line
/// that carries the number alone, and it holds thirty-seven of them.
const BOOK: book::Book = book::Book {
title: "Le tour du monde en quatre-vingts jours",
author: "Jules Verne",
file: "tourdumonde.txt",
mark_before: "",
mark_after: "",
chapters: 37,
contents_title: "Table des matières",
titles: &TITLES,
typography: TYPOGRAPHY,
};
fn main() -> std::process::ExitCode {
failure::reported(run())
}
fn run() -> Result<(), Box<dyn std::error::Error>> {
let mut args = env::args().skip(1);
let out = args
.next()
.unwrap_or_else(|| out::default_path("french_book"));
let font_path = args.next().map_or_else(default_font, PathBuf::from);
let mut doc = Document::new();
doc.set_license(licence::licensed());
let handle = doc.add_font(Font::parse(fs::read(&font_path)?)?);
let text = fs::read_to_string(BOOK.path())?;
book::lay_out(&mut doc, &handle, &BOOK, &text)?;
let bytes = doc.to_bytes()?;
if let Some(parent) = Path::new(&out).parent() {
fs::create_dir_all(parent)?;
}
fs::write(&out, &bytes)?;
println!("wrote {out}: {} bytes", bytes.len());
Ok(())
}
#[cfg(test)]
mod tests {
use hqf_pdf::{Document, Font, FontHandle};
use super::{BOOK, book, default_font};
/// The font every measurement here is taken in, which is the one the
/// example draws with when the command line says nothing.
fn font(document: &mut Document) -> FontHandle {
document.add_font(
Font::parse(std::fs::read(default_font()).expect("the committed font is there"))
.expect("the committed font parses"),
)
}
#[test]
fn every_chapter_of_the_novel_is_headed_and_carries_prose() {
book::check_every_chapter_is_headed(&BOOK);
}
#[test]
fn the_titles_set_here_are_the_ones_the_file_carries() {
book::check_the_titles_only_change_case(&BOOK);
}
#[test]
fn no_chapter_is_headed_in_capitals_throughout() {
book::check_no_title_is_set_in_capitals(&BOOK);
}
/// This book holds every two-part mark and every quotation mark to the word
/// beside it, and opens a speech on a dash that stands off its first word.
#[test]
fn the_marks_are_spaced_the_way_this_book_sets_them() {
assert!(!BOOK.typography.space_before.is_empty());
assert!(!BOOK.typography.space_after.is_empty());
assert_eq!(BOOK.typography.dash.map(|dash| dash.opening), Some(" "));
book::check_the_marks_are_spaced_the_way_the_book_sets_them(&BOOK);
}
#[test]
fn the_contents_points_at_every_chapter() {
let mut document = Document::new();
let handle = font(&mut document);
book::check_the_contents_points_at_every_chapter(&handle, &BOOK);
}
#[test]
fn the_running_heads_stay_on_one_line() {
let mut document = Document::new();
let handle = font(&mut document);
book::check_the_running_heads_stay_on_one_line(&BOOK, &handle);
}
}
|