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
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780 | """Sets five short samples twice each — once as the font is read straight off its
`cmap`, once with one typographic feature turned on — so the difference each
feature makes can be seen side by side.
The Python twin of the `write_typography` example in Rust. The five are the
ligatures a font offers, small capitals, old-style figures, the forms the font
draws for one language, and kerning. Nothing is on unless it is asked for, so the
two settings of a sample differ in exactly one thing. The forms of one language
are the font's `locl` feature read for Serbian, the Cyrillic script and the
`SRB ` language.
The font here is Spectral, which offers all five.
The closing lines are measured off the fonts themselves rather than written down:
how many glyphs the ligature sample takes each way, how much narrower kerning
draws its own, and how many of the face's glyphs no character leads to. The first
of those are drawn last, asked for by their number in the file and centred on the
width measured off them.
A second page sets the ligature sample with ligatures and kerning on, then with
ligatures alone turned back off, draws the face's `fi` glyph by its number, stated to
stand for the two letters it joins, and sets three fractions twice: the face's `frac`
feature draws each figure before the slash as a numerator and each after it as a
denominator, by the glyphs around it.
The words are held in `Words`, once per language, and `HQF_PDF_LANG` picks which
set is drawn.
Usage: python examples/write_typography.py [out.pdf]
HQF_PDF_LANG=fr python examples/write_typography.py
"""
from __future__ import annotations
from dataclasses import dataclass
from pathlib import Path
import _language
import _licence
import _out
import hqf_pdf
# Where the committed fonts sit.
FONT_DIR = _out.ROOT / "crates" / "hqf-pdf" / "tests" / "fonts"
@dataclass(frozen=True)
class Words:
"""Every word the page draws, in one language."""
# The line across the top of the page.
title: str
# What the page shows, in two sentences.
why: str
# The heading over each of the five samples.
headings: tuple[str, str, str, str, str]
# What each feature does, one line each.
explanations: tuple[str, str, str, str, str]
# The five samples, each set twice.
samples: tuple[str, str, str, str, str]
# What is written beside the setting with nothing turned on.
off: str
# What is written beside the setting with the feature turned on.
on: str
# The line about the ligature sample, cut where the two counts go into it.
glyphs_before: str
glyphs_between: str
glyphs_after: str
# The line about the kerning sample, cut where the width goes into it.
kerned_before: str
kerned_after: str
# The line over the glyphs drawn by number, cut where the count of the face's
# glyphs, the count no character leads to and the count drawn go into it.
numbered_before: str
numbered_between: str
numbered_then: str
numbered_after: str
# The line at the head of the second page.
back_title: str
# What the second page shows.
back_why: str
# What is written beside the sample set with ligatures and kerning.
both: str
# What is written beside it with ligatures turned back off.
without: str
# What stands over the glyph drawn by its number.
by_number: str
# The heading over the fraction sample.
fraction_heading: str
# What the `frac` feature does, in one line.
fraction_explanation: str
def glyph_line(self, apart: int, joined: int) -> str:
"""Return the line about the ligature sample, with the two counts in it."""
return (
f"{self.glyphs_before}{apart}{self.glyphs_between}"
f"{joined}{self.glyphs_after}"
)
def kerned_line(self, saved: float) -> str:
"""Return the line about the kerning sample, with the width it saves."""
return f"{self.kerned_before}{saved:.1f}{self.kerned_after}"
def numbered_line(self, held: int, unreached: int, drawn: int) -> str:
"""Return the line over the glyphs drawn by number, with its three counts."""
return (
f"{self.numbered_before}{held}{self.numbered_between}{unreached}"
f"{self.numbered_then}{drawn}{self.numbered_after}"
)
# The page in English.
ENGLISH = Words(
title="Five things a font knows about its own letters",
why=(
"A font file carries more than one glyph per character. It says which "
"letters are drawn joined, which figures hang below the line, which "
"letters have a small capital of their own, which letters take another "
"form in one language, and how far apart each pair of glyphs should "
"stand.\n"
"None of it is used unless it is asked for, so a document written "
"before these settings existed comes out byte for byte as it did. Each "
"sample below is set twice, and only the setting differs."
),
headings=(
"Ligatures",
"Small capitals",
"Old-style figures",
"Local forms",
"Kerning",
),
explanations=(
"Letters that meet are drawn as one glyph the font designer cut for them.",
"Lowercase letters drawn as capitals no taller than an x.",
"Figures that rise and fall around the x-height instead of standing at cap height.",
"The same Cyrillic letters, in the forms the font draws for Serbian.",
"Each pair of glyphs moved as far apart as the font asks.",
),
samples=(
"office filing affidavit",
"reprinted quarterly",
"1 234 567 890",
"пут, град, беседа",
"Wavy TAXI Today",
),
off="off",
on="on",
glyphs_before="The first sample takes ",
glyphs_between=" glyphs as it stands and ",
glyphs_after=(
" with the ligatures on: the pairs the font joins are one glyph each."
),
kerned_before="The last sample is drawn ",
kerned_after=(
" points narrower with kerning on, and it is measured that way too, so "
"the line is as wide as it looks."
),
numbered_before="Spectral holds ",
numbered_between=" glyphs, and no character leads to ",
numbered_then=(
" of them: the forms a feature or a language puts in a letter's place. "
"The first "
),
numbered_after=(
", asked for by their number in the file and centred on their measured "
"width:"
),
back_title="One feature turned back off, one glyph by its number",
back_why="Ligatures and kerning are turned on together, then ligatures alone are turned back off: the letters come apart again, and the pairs stay kerned.",
both="both",
without="no liga",
by_number="The glyph the face names fi, asked for by its number and stated to stand for the two letters it joins, so that the text copies out as those two letters:",
fraction_heading="Fractions",
fraction_explanation="Figures on either side of a slash, set as a numerator and a denominator around a fraction bar.",
)
# The page in French.
FRENCH = Words(
title="Cinq choses qu’une police sait de ses propres lettres",
why=(
("Un fichier de police contient plus d’un glyphe par caractère. Il indique "
"quelles lettres se lient, quels chiffres descendent sous la ligne, quelles "
"lettres ont leur propre petite capitale, lesquelles changent de forme dans "
"une langue donnée, et quel écart laisser entre chaque paire de "
"glyphes.\nRien de tout cela ne sert tant qu’on ne le demande pas : un "
"document écrit avant que ces réglages existent sort à l’octet près comme "
"avant. Chaque échantillon ci-dessous est composé deux fois, et seul le "
"réglage change.")
),
headings=(
"Ligatures",
"Petites capitales",
"Chiffres elzéviriens",
"Formes locales",
"Crénage",
),
explanations=(
("Les lettres qui se touchent forment un seul glyphe, prévu pour elles par le "
"créateur de la police."),
"Des minuscules dessinées en capitales pas plus hautes qu’un x.",
("Des chiffres qui montent et descendent autour de la hauteur d’x au lieu de "
"s’aligner sur les capitales."),
"Les mêmes lettres cyrilliques, dans les formes que la police dessine pour le serbe.",
"Chaque paire de glyphes écartée autant que la police le demande.",
),
samples=(
"affiche officielle du greffe",
"réédition trimestrielle",
"1 234 567 890",
"пут, град, беседа",
"Wagon TAXI Toiture",
),
off="sans",
on="avec",
glyphs_before="Le premier échantillon compte ",
glyphs_between=" glyphes tel quel et ",
glyphs_after=(
" avec les ligatures : chaque paire liée ne fait plus qu’un glyphe."
),
kerned_before="Le dernier échantillon est composé ",
kerned_after=(
(" points plus étroit avec le crénage, et il est aussi mesuré ainsi : la ligne"
" fait bien la largeur qu’elle paraît faire.")
),
numbered_before="Spectral contient ",
numbered_between=" glyphes, et aucun caractère ne mène à ",
numbered_then=(
(" d’entre eux : les formes qu’un réglage ou une langue met à la place d’une "
"lettre. Les ")
),
numbered_after=(
(" premiers, demandés par leur numéro dans le fichier et centrés sur leur "
"largeur mesurée :")
),
back_title="Une fonction désactivée, un glyphe par son numéro",
back_why=(
"Ligatures et crénage sont activés ensemble, puis les ligatures seules sont "
"désactivées : les lettres se séparent de nouveau, et les paires restent "
"crénées."
),
both="les deux",
without="sans liga",
by_number="Le glyphe que la police nomme fi, demandé par son numéro et déclaré tenir lieu des deux lettres qu’il joint, pour que le texte se copie en ces deux lettres :",
fraction_heading="Fractions",
fraction_explanation="Les chiffres de part et d’autre d’une barre oblique, composés en numérateur et en dénominateur.",
)
# The page in Dutch.
DUTCH = Words(
title="Vijf dingen die een lettertype over zijn eigen letters weet",
why=(
"Een lettertypebestand bevat meer dan één glyph per teken. Het zegt welke "
"letters verbonden getekend worden, welke cijfers onder de regel hangen, "
"welke letters een eigen kleinkapitaal hebben, welke letters in een bepaalde "
"taal een andere vorm krijgen, en hoe ver elk paar glyphs uit elkaar moet "
"staan.\nNiets ervan wordt gebruikt tenzij erom gevraagd wordt, dus een "
"document dat geschreven is voordat deze instellingen bestonden, komt er byte "
"voor byte uit zoals vroeger. Elk voorbeeld hieronder is twee keer gezet, en "
"alleen de instelling verschilt."
),
headings=(
"Ligaturen",
"Kleinkapitalen",
"Mediëvalcijfers",
"Lokale vormen",
"Kerning",
),
explanations=(
"Letters die elkaar raken, worden getekend als één glyph die de ontwerper ervoor sneed.",
"Kleine letters getekend als hoofdletters niet hoger dan een x.",
"Cijfers die rond de x-hoogte stijgen en dalen in plaats van op kapitaalhoogte te staan.",
"Dezelfde Cyrillische letters, in de vormen die het lettertype voor het Servisch tekent.",
"Elk paar glyphs zo ver uit elkaar gezet als het lettertype vraagt.",
),
samples=(
"officiële flessenafvulling",
"elk kwartaal herdrukt",
"1 234 567 890",
"пут, град, беседа",
"Wafel TAXI Toren",
),
off="uit",
on="aan",
glyphs_before="Het eerste voorbeeld neemt",
glyphs_between=" glyphs zoals het is en",
glyphs_after=(
" met de ligaturen aan: de paren die het lettertype verbindt, zijn elk één glyph."
),
kerned_before="Het laatste voorbeeld wordt",
kerned_after=(
" punten smaller getekend met kerning aan, en het wordt ook zo gemeten, dus "
"is de regel zo breed als hij lijkt."
),
numbered_before="Spectral bevat",
numbered_between=" glyphs, en bij",
numbered_then=(
" ervan komt geen teken uit: de vormen die een functie of een taal in de "
"plaats van een letter zet. De eerste"
),
numbered_after=(
", opgevraagd bij hun nummer in het bestand en gecentreerd op hun gemeten "
"breedte:"
),
back_title="Eén functie weer uitgezet, één glyph bij zijn nummer",
back_why="Ligaturen en kerning worden samen aangezet, dan worden alleen de ligaturen weer uitgezet: de letters gaan weer uit elkaar, en de paren blijven gekerned.",
both="beide",
without="zonder liga",
by_number="De glyph die het lettertype fi noemt, opgevraagd bij zijn nummer en opgegeven als de twee letters die hij verbindt, zodat de tekst als die twee letters gekopieerd wordt:",
fraction_heading="Breuken",
fraction_explanation="Cijfers aan weerszijden van een schuine streep, gezet als teller en noemer rond een breukstreep.",
)
# The page in Portuguese.
PORTUGUESE = Words(
title="Cinco coisas que um tipo de letra sabe sobre as suas letras",
why=(
"Um ficheiro de tipo de letra leva mais de um glifo por carácter. Diz que "
"letras se desenham unidas, que algarismos descem abaixo da linha, que letras "
"têm versaletes próprios, que letras tomam outra forma numa língua e a que "
"distância deve ficar cada par de glifos.\nNada disto é usado sem ser pedido, "
"por isso um documento escrito antes de estas definições existirem sai byte a "
"byte como saía. Cada amostra abaixo é composta duas vezes, e só muda a "
"definição."
),
headings=(
"Ligaduras",
"Versaletes",
"Algarismos de estilo antigo",
"Formas locais",
"Crenagem",
),
explanations=(
"As letras que se tocam são desenhadas como um glifo que o desenhador do tipo cortou para elas.",
"Minúsculas desenhadas como maiúsculas não mais altas do que um x.",
"Algarismos que sobem e descem em torno da altura do x em vez de ficarem à altura das maiúsculas.",
"As mesmas letras cirílicas, nas formas que o tipo desenha para o sérvio.",
"Cada par de glifos afastado tanto quanto o tipo pede.",
),
samples=(
"ficha oficial do filiado",
"reimpresso a cada trimestre",
"1 234 567 890",
"пут, град, беседа",
"Vagão TAXI Torta",
),
off="não",
on="sim",
glyphs_before="A primeira amostra ocupa",
glyphs_between=" glifos tal como está e",
glyphs_after=(
" com as ligaduras: os pares que o tipo une são um glifo cada."
),
kerned_before="A última amostra é desenhada",
kerned_after=(
" pontos mais estreita com crenagem, e também é medida assim, por isso a "
"linha é tão larga como parece."
),
numbered_before="O Spectral tem",
numbered_between=" glifos, e",
numbered_then=(
" deles não levam nenhum carácter: as formas que uma funcionalidade ou uma "
"língua põem no lugar de uma letra. Os primeiros"
),
numbered_after=(
", pedidos pelo seu número no ficheiro e centrados na largura medida:"
),
back_title="Uma funcionalidade desligada de novo, um glifo pelo seu número",
back_why="Ligaduras e crenagem são ligadas juntas, e depois só as ligaduras são desligadas: as letras voltam a separar-se, e os pares continuam com crenagem.",
both="ambas",
without="sem liga",
by_number="O glifo a que o tipo chama fi, pedido pelo seu número e indicado como representante das duas letras que une, para que o texto se copie como essas duas letras:",
fraction_heading="Frações",
fraction_explanation="Os algarismos de cada lado de uma barra, compostos como numerador e denominador em volta de um traço de fração.",
)
# The page in Italian.
ITALIAN = Words(
title="Cinque cose che un font sa delle proprie lettere",
why=(
"Un file di font porta più di un glifo per carattere. Dice quali lettere si "
"disegnano unite, quali cifre scendono sotto la riga, quali lettere hanno un "
"maiuscoletto proprio, quali lettere prendono un’altra forma in una lingua e "
"quanto distante deve stare ogni coppia di glifi.\nNiente di tutto ciò è "
"usato se non lo si chiede, così un documento scritto prima che queste "
"impostazioni esistessero esce byte per byte come usciva. Ogni campione qui "
"sotto è composto due volte, e cambia solo l’impostazione."
),
headings=(
"Legature",
"Maiuscoletto",
"Cifre in stile antico",
"Forme locali",
"Crenatura",
),
explanations=(
"Le lettere che si toccano sono disegnate come un glifo che il disegnatore del carattere ha tagliato per loro.",
"Minuscole disegnate come maiuscole non più alte di una x.",
"Cifre che salgono e scendono intorno all’altezza della x anziché stare all’altezza delle maiuscole.",
"Le stesse lettere cirilliche, nelle forme che il carattere disegna per il serbo.",
"Ogni coppia di glifi distanziata quanto chiede il font.",
),
samples=(
"ufficio affiliato fluviale",
"ristampato ogni trimestre",
"1 234 567 890",
"пут, град, беседа",
"Vagone TAXI Torta",
),
off="no",
on="sì",
glyphs_before="Il primo campione occupa",
glyphs_between=" glifi così com’è e",
glyphs_after=(
" con le legature: le coppie che il font unisce sono un glifo ciascuna."
),
kerned_before="L’ultimo campione è disegnato",
kerned_after=(
" punti più stretto con la crenatura, ed è misurato così: la riga è larga "
"quanto sembra."
),
numbered_before="Spectral contiene",
numbered_between=" glifi, e a",
numbered_then=(
" di essi non corrisponde alcun carattere: le forme che una funzione o una "
"lingua mettono al posto di una lettera. I primi"
),
numbered_after=(
", chiesti per numero nel file e centrati sulla larghezza misurata:"
),
back_title="Una funzione disattivata di nuovo, un glifo per numero",
back_why="Legature e crenatura si attivano insieme, poi si disattivano solo le legature: le lettere si separano di nuovo, e le coppie restano crenate.",
both="entrambe",
without="senza liga",
by_number="Il glifo che il carattere chiama fi, chiesto per numero e indicato come rappresentante delle due lettere che unisce, così il testo si copia come quelle due lettere:",
fraction_heading="Frazioni",
fraction_explanation="Le cifre ai due lati di una barra, composte come numeratore e denominatore attorno a una barra di frazione.",
)
# The page in Spanish.
SPANISH = Words(
title="Cinco cosas que una fuente sabe de sus propias letras",
why=(
"Un archivo de fuente lleva más de un glifo por carácter. Dice qué letras se "
"dibujan unidas, qué cifras bajan de la línea, qué letras tienen versalita "
"propia, qué letras toman otra forma en un idioma y a qué distancia debe "
"estar cada par de glifos.\nNada de ello se usa si no se pide, así que un "
"documento escrito antes de que existieran estos ajustes sale byte a byte "
"como salía. Cada muestra de abajo se compone dos veces, y solo cambia el "
"ajuste."
),
headings=(
"Ligaduras",
"Versalitas",
"Cifras elzevirianas",
"Formas locales",
"Interletraje",
),
explanations=(
"Las letras que se tocan se dibujan como un glifo que el diseñador de la fuente cortó para ellas.",
"Minúsculas dibujadas como mayúsculas no más altas que una x.",
"Cifras que suben y bajan alrededor de la altura de la x en vez de alinearse con las mayúsculas.",
"Las mismas letras cirílicas, en las formas que la fuente dibuja para el serbio.",
"Cada par de glifos separado tanto como pide la fuente.",
),
samples=(
"ficha oficial del afiliado",
"reimpreso cada trimestre",
"1 234 567 890",
"пут, град, беседа",
"Vagón TAXI Tarta",
),
off="no",
on="sí",
glyphs_before="La primera muestra ocupa",
glyphs_between=" glifos tal cual y",
glyphs_after=(
" con las ligaduras: los pares que une la fuente son un glifo cada uno."
),
kerned_before="La última muestra se dibuja",
kerned_after=(
" puntos más estrecha con interletraje, y también se mide así, así que la "
"línea es tan ancha como parece."
),
numbered_before="Spectral tiene",
numbered_between=" glifos, y",
numbered_then=(
" de ellos no corresponden a ningún carácter: las formas que una función o un "
"idioma ponen en lugar de una letra. Los primeros"
),
numbered_after=(
", pedidos por su número en el archivo y centrados en su ancho medido:"
),
back_title="Una función desactivada otra vez, un glifo por su número",
back_why="Ligaduras e interletraje se activan juntos, y luego se desactivan solo las ligaduras: las letras se separan de nuevo, y los pares siguen con interletraje.",
both="ambos",
without="sin liga",
by_number="El glifo que la fuente llama fi, pedido por su número e indicado como representante de las dos letras que une, para que el texto se copie como esas dos letras:",
fraction_heading="Fracciones",
fraction_explanation="Las cifras a cada lado de una barra, compuestas como numerador y denominador alrededor de una barra de fracción.",
)
# The page in German.
GERMAN = Words(
title="Was eine Schrift über ihre Buchstaben weiß",
why=(
"Eine Schriftdatei trägt mehr als einen Glyphen je Zeichen. Sie sagt, welche "
"Buchstaben verbunden gezeichnet werden, welche Ziffern unter die Zeile "
"hängen, welche Buchstaben ein eigenes Kapitälchen haben, welche Buchstaben "
"in einer Sprache eine andere Form annehmen und wie weit jedes Glyphenpaar "
"auseinanderstehen soll.\nNichts davon wird genutzt, wenn es nicht verlangt "
"wird, also kommt ein Dokument, das vor diesen Einstellungen geschrieben "
"wurde, Byte für Byte so heraus wie damals. Jedes Muster unten ist zweimal "
"gesetzt, und nur die Einstellung unterscheidet sich."
),
headings=(
"Ligaturen",
"Kapitälchen",
"Mediävalziffern",
"Lokale Formen",
"Unterschneidung",
),
explanations=(
"Sich berührende Buchstaben werden zu einem eigens geschnittenen Glyphen.",
"Kleinbuchstaben als Versalien, nicht höher als ein x.",
"Ziffern, die um die x-Höhe auf- und absteigen, statt auf Versalhöhe zu stehen.",
"Dieselben kyrillischen Buchstaben in ihren serbischen Formen.",
"Jedes Glyphenpaar so weit gerückt, wie die Schrift verlangt.",
),
samples=(
"offizielle Kaffeefilter",
"vierteljährlich nachgedruckt",
"1 234 567 890",
"пут, град, беседа",
"Wagen TAXI Torte",
),
off="aus",
on="an",
glyphs_before="Das erste Muster braucht ",
glyphs_between=" Glyphen, wie es ist, und ",
glyphs_after=(
" mit Ligaturen: Die Paare, die die Schrift verbindet, sind je ein Glyph."
),
kerned_before="Das letzte Muster ist mit Unterschneidung ",
kerned_after=(
" Punkt schmaler gezeichnet und wird auch so gemessen, also ist die Zeile so "
"breit, wie sie aussieht."
),
numbered_before="Spectral enthält ",
numbered_between=" Glyphen, und kein Zeichen führt zu ",
numbered_then=(
" davon: die Formen, die ein Merkmal oder eine Sprache an die Stelle eines "
"Buchstabens setzt. Die ersten "
),
numbered_after=(
", über ihre Nummer in der Datei verlangt und auf ihre gemessene Breite "
"zentriert:"
),
back_title="Ein Merkmal wieder ausgeschaltet, ein Glyph über seine Nummer",
back_why="Ligaturen und Unterschneidung werden zusammen eingeschaltet, dann wird nur die Ligatur wieder ausgeschaltet: Die Buchstaben trennen sich wieder, und die Paare bleiben unterschnitten.",
both="beide",
without="ohne liga",
by_number="Der Glyph, den der Schnitt fi nennt, über seine Nummer verlangt und als Stellvertreter der zwei Buchstaben angegeben, die er verbindet, sodass der Text als diese zwei Buchstaben herauskopiert wird:",
fraction_heading="Brüche",
fraction_explanation="Ziffern links und rechts eines Schrägstrichs, gesetzt als Zähler und Nenner um einen Bruchstrich.",
)
# 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, _language.GERMAN: GERMAN, _language.SPANISH: SPANISH, _language.ITALIAN: ITALIAN, _language.PORTUGUESE: PORTUGUESE, _language.DUTCH: DUTCH}
# The left edge of everything on the page.
X = 60.0
# The width every block is broken to.
ROOM = 475.0
# The baseline the title sits on.
TITLE_TOP = 780.0
# The top of the block that says what the page shows.
WHY_TOP = 748.0
# The top of each of the five samples, in the order they are drawn.
TOPS = (650.0, 550.0, 450.0, 350.0, 250.0)
# How far the line that explains a feature sits below its heading.
EXPLAIN_DROP = 16.0
# How far the setting with nothing turned on sits below the heading.
OFF_DROP = 42.0
# How far the setting with the feature turned on sits below the heading.
ON_DROP = 68.0
# Where the word beside a setting is written, and where the sample begins.
LABEL_X = X
SAMPLE_X = X + 46.0
# The size the samples are set in.
SAMPLE_SIZE = 18.0
# The size the headings are set in.
HEADING_SIZE = 12.0
# The size everything that is not a heading or a sample is set in.
SMALL = 9.0
# The top of the two closing lines.
CLOSING_TOP = 150.0
# How far the second closing line sits below the first.
CLOSING_DROP = 30.0
# How far the closing line over the glyphs drawn by number sits below the first.
NUMBERED_DROP = 60.0
# The baseline of the glyphs drawn by number.
NUMBERED_BASELINE = 28.0
# How many of the glyphs no character leads to are drawn.
NUMBERED_SHOWN = 12
def unreached(font: hqf_pdf.Font) -> list[int]:
"""Return the glyphs of `font` no character of its map leads to, by number,
leaving out the empty box at zero."""
reached = {glyph for __, glyph in font.coverage}
held = min(font.glyph_count, 65535)
return [number for number in range(1, held) if number not in reached]
def line(
content: hqf_pdf.Content,
handle: hqf_pdf.FontHandle,
text: str,
size: float,
x: float,
top: float,
) -> None:
"""Draw one line of text at `size`, left-aligned at `x`, below `top`."""
flow = hqf_pdf.TextFlow(handle, size)
flow.draw(content, flow.break_lines(text, ROOM), x, top, ROOM)
# The name the face gives the glyph joining `f` and `i`.
JOINED = "fi"
# The tops of the second page's two settings, and of the line over the glyph drawn by
# its number, and that glyph's baseline.
BACK_TOPS = (680.0, 650.0)
BY_NUMBER_TOP = 610.0
BY_NUMBER_BASELINE = 550.0
# The top of the fraction sample on the second page.
FRACTION_TOP = 480.0
# The fraction sample, written the same way in every language.
FRACTIONS = "1/2 3/4 15/16"
def back_page(plain: hqf_pdf.FontHandle, words: Words, joined: int | None) -> hqf_pdf.Page:
"""The second page: the ligature sample with ligatures and kerning, then with
ligatures turned back off, the glyph numbered `joined` stated to stand for the two
letters it joins, and the fraction sample without and with the `frac` feature."""
content = hqf_pdf.Content()
line(content, plain, words.back_title, 18.0, X, TITLE_TOP)
why = hqf_pdf.TextFlow(plain, 10.0, leading=14.0)
why.draw(content, why.break_lines(words.back_why, ROOM), X, WHY_TOP, ROOM)
both = plain.with_ligatures().kerning()
apart = both.without_feature("liga")
for (label, handle), top in zip(((words.both, both), (words.without, apart)), BACK_TOPS):
line(content, plain, label, SMALL, LABEL_X, top)
line(content, handle, words.samples[0], SAMPLE_SIZE, SAMPLE_X, top)
if joined is not None:
over = hqf_pdf.TextFlow(plain, 10.0, leading=14.0)
over.draw(content, over.break_lines(words.by_number, ROOM), X, BY_NUMBER_TOP, ROOM)
glyph = plain.numbered_glyph_standing_for(joined, JOINED)
content.begin_text()
content.set_font(plain, SAMPLE_SIZE)
content.text_origin(X, BY_NUMBER_BASELINE)
content.show_glyphs(glyph)
content.end_text()
line(content, plain, words.fraction_heading, HEADING_SIZE, X, FRACTION_TOP)
line(
content, plain, words.fraction_explanation, SMALL, X, FRACTION_TOP - EXPLAIN_DROP
)
fractions = plain.with_feature("frac")
for drop, label, handle in ((OFF_DROP, words.off, plain), (ON_DROP, words.on, fractions)):
line(content, plain, label, SMALL, LABEL_X, FRACTION_TOP - drop)
line(content, handle, FRACTIONS, SAMPLE_SIZE, SAMPLE_X, FRACTION_TOP - drop)
page = hqf_pdf.Page.a4()
page.set_content(content)
return page
def main() -> None:
language = _language.from_environment()
words = _language.words_of(WORDS, language)
out = _out.output_path(Path(_language.file_name("typography.pdf", language)).stem)
document = hqf_pdf.Document()
document.set_license(_licence.licensed())
font = hqf_pdf.Font.from_path(FONT_DIR / "Spectral-Regular.ttf")
held = font.glyph_count
numbers = unreached(font)
ligature = font.glyph_named(JOINED)
plain = document.add_font(font)
# One handle per feature, each turned on alone: the two settings of a sample then
# differ in exactly one thing.
with_feature = (
plain.with_ligatures(),
plain.with_small_capitals(),
plain.with_oldstyle_figures(),
plain.with_feature("locl").with_language_system("cyrl", "SRB "),
plain.kerning(),
)
content = hqf_pdf.Content()
line(content, plain, words.title, 18.0, X, TITLE_TOP)
why = hqf_pdf.TextFlow(plain, 10.0, leading=14.0)
why.draw(content, why.break_lines(words.why, ROOM), X, WHY_TOP, ROOM)
for index, top in enumerate(TOPS):
line(content, plain, words.headings[index], HEADING_SIZE, X, top)
line(content, plain, words.explanations[index], SMALL, X, top - EXPLAIN_DROP)
for drop, label, handle in (
(OFF_DROP, words.off, plain),
(ON_DROP, words.on, with_feature[index]),
):
line(content, plain, label, SMALL, LABEL_X, top - drop)
line(content, handle, words.samples[index], SAMPLE_SIZE, SAMPLE_X, top - drop)
# The closing lines are read off the fonts, not written down.
apart = sum(len(run.glyphs) for run in plain.runs(words.samples[0]))
joined = sum(len(run.glyphs) for run in with_feature[0].runs(words.samples[0]))
saved = plain.measure(words.samples[4], SAMPLE_SIZE) - with_feature[4].measure(
words.samples[4], SAMPLE_SIZE
)
shown = numbers[:NUMBERED_SHOWN]
closing = hqf_pdf.TextFlow(plain, 10.0, leading=14.0)
for drop, text in (
(0.0, words.glyph_line(apart, joined)),
(CLOSING_DROP, words.kerned_line(saved)),
(NUMBERED_DROP, words.numbered_line(held, len(numbers), len(shown))),
):
closing.draw(content, closing.break_lines(text, ROOM), X, CLOSING_TOP - drop, ROOM)
# Drawn by their number, since no character leads to them.
row = plain.numbered_glyphs(shown)
row_width = plain.measure_glyphs(row, SAMPLE_SIZE)
content.begin_text()
content.set_font(plain, SAMPLE_SIZE)
content.text_origin(X + (ROOM - row_width) / 2.0, NUMBERED_BASELINE)
content.show_glyphs(row)
content.end_text()
page = hqf_pdf.Page.a4()
page.set_content(content)
document.add_page(page)
document.add_page(back_page(plain, words, ligature))
written = document.write(out)
print(f"wrote {out}: {written} bytes")
if __name__ == "__main__":
main()
|