write_mesh.rs

Die Rust-Datei des Beispiels „Vier Arten von Gittern, jede Ecke in eigener Farbe“. Sechs Dreiecke um eine gemeinsame Mitte, ihre Kanten über die Farbe gezogen; ein Punktgitter in den vier Druckfarben; ein Patch, dessen vier Seiten Kurven sind; und derselbe Patch, von vier Punkten in seinem Inneren verzogen.

Rust 618 Zeilen

Wofür dieses Beispiel da ist

Ein Verlauf läuft geradlinig oder von einem Punkt aus, und das deckt das meiste ab, was eine Seite verlangt. Es deckt keinen Hintergrund ab, dessen Farbe um eine Ecke biegt, keine Karte, die aus Messwerten an zwanzig Orten schattiert ist, und kein Wärmebild, in dem jede Stelle ihren eigenen Wert hat. Der übliche Ausweg ist, das Bild in einem Zeichenprogramm zu bauen und einzufügen — und dann ist es ein Foto von Farbe: schwer in der Datei, auf die Größe festgelegt, in der es gemacht wurde, und an den Rändern unscharf, sobald jemand die Seite größer druckt.

Was dieses Beispiel zeigt

  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
//! Paints the four kinds of mesh, every point carrying its own colour.
//!
//! A mesh states its own shape. It is handed points, each a place and a colour,
//! and the colour anywhere between them is worked out from the points around
//! it. The four kinds differ in how they say which points belong together, and
//! in whether the sides between them run straight or bend.
//!
//! Four panels, one per kind: six free-form triangles round a centre they
//! share, their edges traced over them; a lattice of rows of points in the four
//! printing inks, its rows and columns traced; a Coons patch whose four sides
//! are curves; and a tensor-product patch with the same sides and the same
//! corners, four points inside it pulling its colours about.
//!
//! A second page hands the same four meshes over as fill patterns, and fills a
//! circle at the centre of each panel with one: the circle shows the part of
//! the mesh that lies where it is.
//!
//! The title, the four labels and the legend are held in `Words`, once per
//! language, and `HQF_PDF_LANG` picks which set is written.
//!
//! Usage: `cargo run --example write_mesh -- tmp/mesh.pdf [font.ttf]`
//!        `HQF_PDF_LANG=fr cargo run --example write_mesh -- tmp/maillage.pdf`

use std::env;
use std::fs;
use std::path::{Path, PathBuf};

use hqf_pdf::content::Content;
use hqf_pdf::layout::TextFlow;
use hqf_pdf::shading::{CoonsPatchMesh, LatticeMesh, TensorPatchMesh, TriangleMesh};
use hqf_pdf::{Cmyk, Document, Font, FontHandle, Page, Rgb};

#[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 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 width and height of every panel, in points.
const BOX: (f64, f64) = (210.0, 200.0);

/// How far the corners of the hexagon of triangles stand from its centre.
const RADIUS: f64 = 95.0;

/// Half the square root of three: how far to the side of the centre of a
/// hexagon each of its four slanting corners stands, as a share of the radius.
const HALF_ROOT_THREE: f64 = 0.866_025_403_784_438_6;

/// How many cells the lattice runs across; a row holds one point more.
const COLUMNS: u32 = 6;

/// How many cells the lattice runs up; the lattice holds one row more.
const ROWS: u32 = 5;

/// How far the legend runs across, between the margins.
const WIDTH: f64 = 455.0;

/// The colour at each corner of both patches, in the order the corners come in:
/// the bottom left, the top left, the top right, the bottom right.
const PATCH_CORNERS: [Rgb; 4] = [
    Rgb::new(0.9, 0.15, 0.1),
    Rgb::new(0.95, 0.8, 0.1),
    Rgb::new(0.1, 0.3, 0.9),
    Rgb::new(0.1, 0.7, 0.35),
];

/// The words the page is written in, one set per language.
#[derive(Debug)]
struct Words {
    /// The title across the top of the page.
    title: &'static str,
    /// The label under the free-form triangles.
    triangles: &'static str,
    /// The label under the lattice.
    lattice: &'static str,
    /// The label under the Coons patch.
    coons: &'static str,
    /// The label under the tensor-product patch.
    tensor: &'static str,
    /// The paragraph at the foot of the page, saying what is above it.
    legend: &'static str,
    /// The line at the head of the second page.
    patterns_title: &'static str,
    /// The paragraph at the foot of the second page.
    patterns: &'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: "Four kinds of mesh, a colour at every point",
    triangles: "Free-form: six triangles, one by one",
    lattice: "A lattice: rows of points, four inks",
    coons: "A Coons patch: four curved sides",
    tensor: "A tensor patch: same sides, pulled inside",
    legend: "Every point above carries a place and a colour, and the colour \
             between points is worked out from the points around it. The six \
             triangles at the top left each name their own three corners, so \
             the centre they share is stated six times over: that is what lets \
             a free-form mesh take any shape at all, and their edges are \
             stroked over the paint so they can be counted. The lattice beside \
             them states each point once, row after row, and the grid says \
             which points make a triangle: forty-two points make sixty \
             triangles, here in cyan, magenta, yellow and black, which is what \
             a press is given. The two patches at the bottom bend their four \
             sides into curves and carry a colour at each of their four \
             corners. The Coons patch works its inside out from those sides \
             alone; the tensor-product patch beside it has the same sides and \
             the same corners, and four more points inside it pull its colours \
             towards the top right. All four ask the reader to smooth their \
             colour against aliasing.",
    patterns_title: "The same four meshes, as patterns",
    patterns: "Each mesh above is handed over a second time, as a pattern. A pattern is laid from the page's own origin, so a circle filled with one shows the part of the mesh that lies where the circle is, and nothing of the rest.",
};

/// The page in French.
const FRENCH: Words = Words {
    title: "Quatre sortes de maillages, une couleur par point",
    triangles: "Triangles libres : six, un par un",
    lattice: "Un treillis : rangées de points, quatre encres",
    coons: "Un carreau de Coons : quatre côtés courbes",
    tensor: "Un carreau tensoriel : mêmes côtés, tiré de l’intérieur",
    legend: "Chaque point ci-dessus a une position et une couleur, et la couleur entre \
les points se déduit des points qui l’entourent. Les six triangles en haut à gauche \
nomment chacun leurs trois sommets : le centre qu’ils partagent est donc écrit six fois, \
et c’est ce qui permet à un maillage libre de prendre n’importe quelle forme. Leurs \
arêtes sont tracées par-dessus la couleur pour qu’on puisse les compter. Le treillis à \
côté écrit chaque point une seule fois, rangée après rangée, et c’est la grille qui dit \
quels points forment un triangle : quarante-deux points font soixante triangles, ici en \
cyan, magenta, jaune et noir, ce qu’on remet à une presse. Les deux carreaux du bas \
courbent leurs quatre côtés et reçoivent une couleur à chacun de leurs quatre coins. Le \
carreau de Coons déduit son intérieur de ses seuls côtés ; le carreau tensoriel à côté a \
les mêmes côtés et les mêmes coins, et quatre points de plus, à l’intérieur, tirent ses \
couleurs vers le haut à droite. Les quatre demandent l’anticrénelage au logiciel de \
lecture.",
    patterns_title: "Les quatre maillages, en motifs",
    patterns: "Chaque maillage ci-dessus est fourni une seconde fois, sous forme de \
motif. Un motif se pose à partir de l’origine de la page : un cercle rempli d’un motif \
montre donc la partie du maillage qui se trouve sous le cercle, et rien du reste.",
};

/// The page in Dutch.
const DUTCH: Words = Words {
    title: "Vier soorten mesh, een kleur op elk punt",
    triangles: "Vrije vorm: zes driehoeken, een voor een",
    lattice: "Een rooster: rijen punten, vier inkten",
    coons: "Een Coons-vlak: vier gebogen zijden",
    tensor: "Een tensorvlak: dezelfde zijden, van binnen getrokken",
    legend: "Elk punt hierboven heeft een plaats en een kleur, en de kleur tussen de punten wordt berekend uit de punten eromheen. De zes driehoeken linksboven noemen elk hun eigen drie hoekpunten, dus het midden dat ze delen wordt zes keer gegeven: daardoor kan een vrije mesh elke vorm aannemen, en hun randen worden over de verf getrokken zodat ze te tellen zijn. Het rooster ernaast geeft elk punt één keer, rij na rij, en het raster zegt welke punten een driehoek vormen: tweeënveertig punten maken zestig driehoeken, hier in cyaan, magenta, geel en zwart, wat een pers krijgt. De twee vlakken onderaan buigen hun vier zijden tot curven en hebben een kleur op elk van hun vier hoeken. Het Coons-vlak berekent zijn binnenkant alleen uit die zijden; het tensorvlak ernaast heeft dezelfde zijden en dezelfde hoeken, en vier extra punten binnenin trekken zijn kleuren naar rechtsboven. Alle vier vragen de leessoftware om anti-aliasing op hun kleuren.",
    patterns_title: "Dezelfde vier meshes, als patronen",
    patterns: "Elke mesh hierboven wordt een tweede keer aangereikt, als patroon. Een patroon wordt gelegd vanaf de eigen oorsprong van de pagina, dus een cirkel gevuld met een patroon toont het deel van de mesh dat ligt waar de cirkel ligt, en niets van de rest.",
};

/// The page in Portuguese.
const PORTUGUESE: Words = Words {
    title: "Quatro tipos de malha, uma cor em cada ponto",
    triangles: "Livre: seis triângulos, um a um",
    lattice: "Uma rede: filas de pontos, quatro tintas",
    coons: "Uma superfície de Coons: quatro lados curvos",
    tensor: "Uma superfície tensorial: os mesmos lados, puxada por dentro",
    legend: "Cada ponto acima leva um lugar e uma cor, e a cor entre pontos calcula-se a partir dos pontos à volta. Os seis triângulos em cima à esquerda nomeiam cada um os seus três vértices, por isso o centro que partilham é dado seis vezes: é isso que permite a uma malha livre tomar qualquer forma, e as suas arestas são traçadas por cima da pintura para se poderem contar. A rede ao lado dá cada ponto uma só vez, fila após fila, e a grelha diz que pontos formam um triângulo: quarenta e dois pontos fazem sessenta triângulos, aqui em ciano, magenta, amarelo e preto, que é o que recebe uma máquina de impressão. As duas superfícies em baixo curvam os seus quatro lados e levam uma cor em cada um dos quatro cantos. A superfície de Coons calcula o interior só a partir desses lados; a tensorial ao lado tem os mesmos lados e os mesmos cantos, e mais quatro pontos no interior puxam as suas cores para cima à direita. As quatro pedem ao leitor que suavize a cor contra o serrilhado.",
    patterns_title: "As mesmas quatro malhas, como padrões",
    patterns: "Cada malha acima é entregue uma segunda vez, como padrão. Um padrão estende-se a partir da origem própria da página, por isso um círculo preenchido com um mostra a parte da malha que está onde está o círculo, e nada do resto.",
};

/// The page in Italian.
const ITALIAN: Words = Words {
    title: "Quattro tipi di maglia, un colore in ogni punto",
    triangles: "Libera: sei triangoli, uno per uno",
    lattice: "Un reticolo: file di punti, quattro inchiostri",
    coons: "Una superficie di Coons: quattro lati curvi",
    tensor: "Una superficie tensoriale: stessi lati, tirata dentro",
    legend: "Ogni punto qui sopra porta una posizione e un colore, e il colore tra i punti si calcola dai punti che lo circondano. I sei triangoli in alto a sinistra nominano ciascuno i propri tre vertici, così il centro che condividono è dato sei volte: è questo che permette a una maglia libera di prendere qualsiasi forma, e i loro bordi sono tracciati sopra la pittura per poterli contare. Il reticolo accanto dà ogni punto una volta sola, fila dopo fila, e la griglia dice quali punti formano un triangolo: quarantadue punti fanno sessanta triangoli, qui in ciano, magenta, giallo e nero, cioè ciò che riceve una macchina da stampa. Le due superfici in basso curvano i loro quattro lati e portano un colore a ciascuno dei quattro angoli. La superficie di Coons calcola il suo interno solo da quei lati; quella tensoriale accanto ha gli stessi lati e gli stessi angoli, e altri quattro punti al suo interno ne tirano i colori verso l’alto a destra. Tutte e quattro chiedono al programma di lettura di sfumare il colore contro la scalettatura.",
    patterns_title: "Le stesse quattro maglie, come motivi",
    patterns: "Ogni maglia qui sopra è consegnata una seconda volta, come motivo. Un motivo si stende dall’origine propria della pagina, così un cerchio riempito con uno mostra la parte di maglia che sta dove sta il cerchio, e nulla del resto.",
};

/// The page in Spanish.
const SPANISH: Words = Words {
    title: "Cuatro tipos de malla, un color en cada punto",
    triangles: "Libre: seis triángulos, uno a uno",
    lattice: "Una retícula: filas de puntos, cuatro tintas",
    coons: "Un parche de Coons: cuatro lados curvos",
    tensor: "Un parche tensorial: los mismos lados, estirado por dentro",
    legend: "Cada punto de arriba lleva un lugar y un color, y el color entre puntos se calcula a partir de los puntos que lo rodean. Los seis triángulos de arriba a la izquierda nombran cada uno sus tres esquinas, así que el centro que comparten se da seis veces: eso es lo que permite a una malla libre tomar cualquier forma, y sus bordes se trazan sobre la pintura para poder contarlos. La retícula de al lado da cada punto una sola vez, fila tras fila, y la cuadrícula dice qué puntos forman un triángulo: cuarenta y dos puntos hacen sesenta triángulos, aquí en cian, magenta, amarillo y negro, que es lo que recibe una imprenta. Los dos parches de abajo curvan sus cuatro lados y llevan un color en cada una de sus cuatro esquinas. El parche de Coons calcula su interior solo a partir de esos lados; el parche tensorial de al lado tiene los mismos lados y las mismas esquinas, y cuatro puntos más en su interior tiran de sus colores hacia arriba a la derecha. Los cuatro piden al programa de lectura que suavice su color contra el dentado.",
    patterns_title: "Las mismas cuatro mallas, como patrones",
    patterns: "Cada malla de arriba se entrega una segunda vez, como patrón. Un patrón se coloca desde el origen propio de la página, así que un círculo relleno con uno muestra la parte de la malla que está donde está el círculo, y nada del resto.",
};

/// The page in German.
const GERMAN: Words = Words {
    title: "Vier Arten von Netz, eine Farbe an jedem Punkt",
    triangles: "Frei geformt: sechs Dreiecke, eines nach dem anderen",
    lattice: "Ein Gitter: Reihen von Punkten, vier Farben",
    coons: "Ein Coons-Patch: vier gekrümmte Seiten",
    tensor: "Ein Tensor-Patch: dieselben Seiten, innen gezogen",
    legend: "Jeder Punkt oben trägt einen Ort und eine Farbe, und die Farbe zwischen den Punkten wird aus den Punkten ringsum berechnet. Die sechs Dreiecke oben links nennen jeweils ihre eigenen drei Ecken, also wird die Mitte, die sie teilen, sechsmal angegeben: Das erlaubt einem frei geformten Netz jede beliebige Form, und ihre Kanten sind über die Farbe gezogen, damit man sie zählen kann. Das Gitter daneben gibt jeden Punkt einmal an, Reihe für Reihe, und das Raster sagt, welche Punkte ein Dreieck bilden: Zweiundvierzig Punkte ergeben sechzig Dreiecke, hier in Cyan, Magenta, Gelb und Schwarz, also dem, was eine Druckmaschine erhält. Die beiden Patches unten biegen ihre vier Seiten zu Kurven und tragen an jeder ihrer vier Ecken eine Farbe. Der Coons-Patch berechnet sein Inneres allein aus diesen Seiten; der Tensorprodukt-Patch daneben hat dieselben Seiten und Ecken, und vier weitere Punkte in seinem Inneren ziehen seine Farben nach rechts oben. Alle vier bitten die Lesesoftware, ihre Farbe gegen Treppeneffekte zu glätten.",
    patterns_title: "Dieselben vier Netze, als Muster",
    patterns: "Jedes Netz oben wird ein zweites Mal übergeben, als Muster. Ein Muster wird vom Ursprung der Seite aus gelegt, also zeigt ein damit gefüllter Kreis den Teil des Netzes, der dort liegt, wo der Kreis ist, und nichts vom Rest.",
};

/// 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),
];

/// A point of a mesh: where it stands, and the colour the mesh holds there.
type Corner<T> = ((f64, f64), T);

/// A triangle of a mesh, corner by corner.
type Triangle<T> = [Corner<T>; 3];

/// The centre of the panel at `(x, y)`.
fn centre(x: f64, y: f64) -> (f64, f64) {
    (x + BOX.0 / 2.0, y + BOX.1 / 2.0)
}

/// The six corners of the hexagon in the panel at `(x, y)`, from the top one
/// clockwise, each carrying the next colour round the colour wheel: red,
/// yellow, green, cyan, blue and magenta.
fn rim(x: f64, y: f64) -> [Corner<Rgb>; 6] {
    let (cx, cy) = centre(x, y);
    let across = HALF_ROOT_THREE * RADIUS;
    let up = RADIUS / 2.0;
    [
        ((cx, cy + RADIUS), Rgb::new(0.9, 0.1, 0.1)),
        ((cx + across, cy + up), Rgb::new(0.95, 0.8, 0.1)),
        ((cx + across, cy - up), Rgb::new(0.1, 0.7, 0.2)),
        ((cx, cy - RADIUS), Rgb::new(0.1, 0.7, 0.8)),
        ((cx - across, cy - up), Rgb::new(0.1, 0.2, 0.9)),
        ((cx - across, cy + up), Rgb::new(0.8, 0.1, 0.7)),
    ]
}

/// The six triangles of the panel at `(x, y)`: each joins the centre, held
/// almost white, to two neighbouring corners of the hexagon.
fn fan(x: f64, y: f64) -> Vec<Triangle<Rgb>> {
    let middle = (centre(x, y), Rgb::gray(0.95));
    let corners = rim(x, y);
    corners
        .iter()
        .zip(corners.iter().cycle().skip(1))
        .map(|(&from, &to)| [middle, from, to])
        .collect()
}

/// Strokes the edges of the six triangles over the panel at `(x, y)`: a spoke
/// from the centre to every corner, then the rim.
fn fan_edges(content: &mut Content, x: f64, y: f64) -> Result<(), hqf_pdf::Error> {
    let (cx, cy) = centre(x, y);
    let corners = rim(x, y);
    for ((px, py), _) in corners {
        content.move_to(cx, cy)?;
        content.line_to(px, py)?;
    }
    let [((fx, fy), _), rest @ ..] = corners;
    content.move_to(fx, fy)?;
    for ((px, py), _) in rest {
        content.line_to(px, py)?;
    }
    content.close_path();
    content.stroke();
    Ok(())
}

/// The point of the lattice over the panel at `(x, y)` that is `u` of the way
/// across it and `v` of the way up, and the four inks it carries. The bottom
/// row runs straight; each row above it arches higher in the middle.
#[expect(
    clippy::suboptimal_flops,
    reason = "a fused multiply-add rounds once, and the Python twin rounds twice"
)]
fn lattice_point(x: f64, y: f64, u: f64, v: f64) -> Corner<Cmyk> {
    (
        (
            x + u * BOX.0,
            y + BOX.1 * (0.05 + 0.75 * v + 0.6 * v * u * (1.0 - u)),
        ),
        Cmyk::new(
            0.8 * u,
            0.75 * v,
            0.1 + 0.6 * (1.0 - u),
            0.05 + 0.15 * u * v,
        ),
    )
}

/// The rows of the lattice over the panel at `(x, y)`, from the bottom one up:
/// [`ROWS`] + 1 of them, each of [`COLUMNS`] + 1 points.
fn lattice(x: f64, y: f64) -> Vec<Vec<Corner<Cmyk>>> {
    (0..=ROWS)
        .map(|row| {
            let v = f64::from(row) / f64::from(ROWS);
            (0..=COLUMNS)
                .map(|column| lattice_point(x, y, f64::from(column) / f64::from(COLUMNS), v))
                .collect()
        })
        .collect()
}

/// Strokes the rows and the columns of the lattice over the panel at `(x, y)`,
/// each as a line through its points.
fn lattice_lines(content: &mut Content, x: f64, y: f64) -> Result<(), hqf_pdf::Error> {
    for row in 0..=ROWS {
        let v = f64::from(row) / f64::from(ROWS);
        for column in 0..=COLUMNS {
            let u = f64::from(column) / f64::from(COLUMNS);
            let ((px, py), _) = lattice_point(x, y, u, v);
            if column == 0 {
                content.move_to(px, py)?;
            } else {
                content.line_to(px, py)?;
            }
        }
    }
    for column in 0..=COLUMNS {
        let u = f64::from(column) / f64::from(COLUMNS);
        for row in 0..=ROWS {
            let v = f64::from(row) / f64::from(ROWS);
            let ((px, py), _) = lattice_point(x, y, u, v);
            if row == 0 {
                content.move_to(px, py)?;
            } else {
                content.line_to(px, py)?;
            }
        }
    }
    content.stroke();
    Ok(())
}

/// The twelve control points of the boundary of the patch in the panel at `(x,
/// y)`, in the order a patch takes them: up the left side from the bottom left
/// corner, along the top, down the right side and back along the bottom, each
/// side a corner and the two points that bend the side leaving it. Every side
/// bends one way and then the other.
fn boundary(x: f64, y: f64) -> [(f64, f64); 12] {
    [
        (x + 20.0, y + 20.0),
        (x, y + 75.0),
        (x + 40.0, y + 125.0),
        (x + 20.0, y + 180.0),
        (x + 75.0, y + 200.0),
        (x + 135.0, y + 160.0),
        (x + 190.0, y + 180.0),
        (x + 210.0, y + 125.0),
        (x + 170.0, y + 75.0),
        (x + 190.0, y + 20.0),
        (x + 135.0, y),
        (x + 75.0, y + 40.0),
    ]
}

/// The sixteen control points of the tensor-product patch in the panel at `(x,
/// y)`: the twelve of [`boundary`], then the four inside it — the ones nearest
/// the bottom left, the top left, the top right and the bottom right corner —
/// all four pulled up and to the right.
fn tensor(x: f64, y: f64) -> [(f64, f64); 16] {
    let [p0, p1, p2, p3, p4, p5, p6, p7, p8, p9, p10, p11] = boundary(x, y);
    [
        p0,
        p1,
        p2,
        p3,
        p4,
        p5,
        p6,
        p7,
        p8,
        p9,
        p10,
        p11,
        (x + 150.0, y + 135.0),
        (x + 130.0, y + 195.0),
        (x + 195.0, y + 195.0),
        (x + 195.0, y + 130.0),
    ]
}

/// Strokes the four curved sides of the patch in the panel at `(x, y)`.
fn sides(content: &mut Content, x: f64, y: f64) -> Result<(), hqf_pdf::Error> {
    let [p0, p1, p2, p3, p4, p5, p6, p7, p8, p9, p10, p11] = boundary(x, y);
    content.move_to(p0.0, p0.1)?;
    content.curve_to(p1.0, p1.1, p2.0, p2.1, p3.0, p3.1)?;
    content.curve_to(p4.0, p4.1, p5.0, p5.1, p6.0, p6.1)?;
    content.curve_to(p7.0, p7.1, p8.0, p8.1, p9.0, p9.1)?;
    content.curve_to(p10.0, p10.1, p11.0, p11.1, p0.0, p0.1)?;
    content.close_path();
    content.stroke();
    Ok(())
}

/// Writes a line of text at `(x, y)`.
fn write(
    content: &mut Content,
    font: &FontHandle,
    x: f64,
    y: f64,
    size: f64,
    text: &str,
) -> Result<(), hqf_pdf::Error> {
    content.begin_text();
    content.set_font(font, size)?;
    content.text_origin(x, y)?;
    content.show_glyphs(&font.glyphs(text));
    content.end_text();
    Ok(())
}

/// Writes the legend in a column the width of the page.
fn legend(
    content: &mut Content,
    font: &FontHandle,
    top: f64,
    color: Rgb,
    text: &str,
) -> Result<(), hqf_pdf::Error> {
    let flow = TextFlow::new(font, 8.5).leading(12.0).color(color);
    let lines = flow.break_lines(text, WIDTH);
    content.begin_text();
    flow.draw(content, &lines, 70.0, top, WIDTH)?;
    content.end_text();
    Ok(())
}

/// How far from the centre of its panel the second page's circles reach.
const CIRCLE: f64 = 60.0;

/// The second page: each mesh added again as a fill pattern, filling a circle
/// at the centre of its panel, under the label the first page gives it.
fn patterns_page(
    doc: &mut Document,
    font: &FontHandle,
    words: &Words,
    (left, right, top, bottom): (f64, f64, f64, f64),
) -> Result<Page, Box<dyn std::error::Error>> {
    let filled = [
        (
            doc.add_triangle_mesh_pattern(&TriangleMesh::rgb(fan(left, top)).anti_alias(true))?,
            (left, top, words.triangles),
        ),
        (
            doc.add_lattice_mesh_pattern(&LatticeMesh::cmyk(lattice(right, top)).anti_alias(true))?,
            (right, top, words.lattice),
        ),
        (
            doc.add_coons_patch_mesh_pattern(
                &CoonsPatchMesh::rgb([(boundary(left, bottom), PATCH_CORNERS)]).anti_alias(true),
            )?,
            (left, bottom, words.coons),
        ),
        (
            doc.add_tensor_patch_mesh_pattern(
                &TensorPatchMesh::rgb([(tensor(right, bottom), PATCH_CORNERS)]).anti_alias(true),
            )?,
            (right, bottom, words.tensor),
        ),
    ];

    let mut content = Content::new();
    write(&mut content, font, left, 790.0, 18.0, words.patterns_title)?;
    for (pattern, (x, y, label)) in &filled {
        let (cx, cy) = centre(*x, *y);
        content.save_state();
        content.set_fill_pattern(pattern);
        content.circle(cx, cy, CIRCLE)?;
        content.fill();
        content.restore_state();
        write(&mut content, font, *x, *y - 15.0, 10.0, label)?;
    }
    legend(&mut content, font, 265.0, Rgb::gray(0.35), words.patterns)?;

    let mut page = Page::a4();
    page.content = content.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("mesh")));
    let font_path = args.next().map_or_else(default_font, PathBuf::from);

    let mut doc = Document::new();
    doc.set_license(licence::licensed());
    let font = doc.add_font(Font::parse(fs::read(&font_path)?)?);

    // Left column x, right column x, top row foot, bottom row foot.
    let (left, right, top, bottom) = (70.0, 315.0, 560.0, 300.0);

    let triangles = doc.add_triangle_mesh(&TriangleMesh::rgb(fan(left, top)).anti_alias(true))?;
    let points = doc.add_lattice_mesh(&LatticeMesh::cmyk(lattice(right, top)).anti_alias(true))?;
    let coons = doc.add_coons_patch_mesh(
        &CoonsPatchMesh::rgb([(boundary(left, bottom), PATCH_CORNERS)]).anti_alias(true),
    )?;
    let pulled = doc.add_tensor_patch_mesh(
        &TensorPatchMesh::rgb([(tensor(right, bottom), PATCH_CORNERS)]).anti_alias(true),
    )?;

    let mut content = Content::new();
    write(&mut content, &font, left, 790.0, 18.0, words.title)?;

    content.draw_shading(&triangles);
    content.save_state();
    content.set_stroke(Rgb::gray(1.0))?;
    content.set_line_width(0.4)?;
    fan_edges(&mut content, left, top)?;
    content.restore_state();
    write(&mut content, &font, left, top - 15.0, 10.0, words.triangles)?;

    content.draw_shading(&points);
    content.save_state();
    content.set_stroke(Rgb::gray(1.0))?;
    content.set_line_width(0.4)?;
    lattice_lines(&mut content, right, top)?;
    content.restore_state();
    write(&mut content, &font, right, top - 15.0, 10.0, words.lattice)?;

    content.draw_shading(&coons);
    content.save_state();
    content.set_stroke(Rgb::gray(0.3))?;
    content.set_line_width(0.6)?;
    sides(&mut content, left, bottom)?;
    content.restore_state();
    write(&mut content, &font, left, bottom - 15.0, 10.0, words.coons)?;

    content.draw_shading(&pulled);
    content.save_state();
    content.set_stroke(Rgb::gray(0.3))?;
    content.set_line_width(0.6)?;
    sides(&mut content, right, bottom)?;
    content.restore_state();
    write(
        &mut content,
        &font,
        right,
        bottom - 15.0,
        10.0,
        words.tensor,
    )?;

    legend(&mut content, &font, 265.0, Rgb::gray(0.35), words.legend)?;

    let mut page = Page::a4();
    page.content = content.into_bytes();
    doc.add_page(page)?;
    let patterns = patterns_page(&mut doc, &font, words, (left, right, top, bottom))?;
    doc.add_page(patterns)?;

    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, one mesh of each of the four kinds, twice",
        bytes.len()
    );
    Ok(())
}

#[cfg(test)]
mod tests {
    use super::{WORDS, language};

    /// The lines two languages are allowed to write the same way. There are
    /// none: every word is a word of the language it is written in.
    const SPARED: [&str; 0] = [];

    #[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:?}"
        );
    }
}