Skip to main content

pivot_pdf/
fonts.rs

1/// Index into the document's TrueType font list.
2#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
3pub struct TrueTypeFontId(pub usize);
4
5/// Unified font reference: either a builtin PDF font or a loaded
6/// TrueType font.
7#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
8pub enum FontRef {
9    /// One of the 14 standard PDF Type1 fonts, guaranteed available in all viewers.
10    Builtin(BuiltinFont),
11    /// A TrueType font loaded via [`crate::PdfDocument::load_font_file`] or
12    /// [`crate::PdfDocument::load_font_bytes`].
13    TrueType(TrueTypeFontId),
14}
15
16impl From<BuiltinFont> for FontRef {
17    fn from(font: BuiltinFont) -> Self {
18        FontRef::Builtin(font)
19    }
20}
21
22/// Font identifier for the 14 standard PDF fonts.
23/// These fonts are guaranteed available in all PDF viewers
24/// without embedding.
25#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
26pub enum BuiltinFont {
27    /// Helvetica (sans-serif, regular).
28    Helvetica,
29    /// Helvetica Bold.
30    HelveticaBold,
31    /// Helvetica Oblique (italic).
32    HelveticaOblique,
33    /// Helvetica Bold Oblique.
34    HelveticaBoldOblique,
35    /// Times Roman (serif, regular).
36    TimesRoman,
37    /// Times Bold.
38    TimesBold,
39    /// Times Italic.
40    TimesItalic,
41    /// Times Bold Italic.
42    TimesBoldItalic,
43    /// Courier (monospace, regular).
44    Courier,
45    /// Courier Bold.
46    CourierBold,
47    /// Courier Oblique (italic).
48    CourierOblique,
49    /// Courier Bold Oblique.
50    CourierBoldOblique,
51    /// Symbol (mathematical and special characters).
52    Symbol,
53    /// Zapf Dingbats (decorative symbols).
54    ZapfDingbats,
55}
56
57impl BuiltinFont {
58    /// Returns the PDF resource name used in content streams
59    /// (e.g. "F1"). Fixed mapping by variant order.
60    pub fn pdf_name(&self) -> &'static str {
61        match self {
62            BuiltinFont::Helvetica => "F1",
63            BuiltinFont::HelveticaBold => "F2",
64            BuiltinFont::HelveticaOblique => "F3",
65            BuiltinFont::HelveticaBoldOblique => "F4",
66            BuiltinFont::TimesRoman => "F5",
67            BuiltinFont::TimesBold => "F6",
68            BuiltinFont::TimesItalic => "F7",
69            BuiltinFont::TimesBoldItalic => "F8",
70            BuiltinFont::Courier => "F9",
71            BuiltinFont::CourierBold => "F10",
72            BuiltinFont::CourierOblique => "F11",
73            BuiltinFont::CourierBoldOblique => "F12",
74            BuiltinFont::Symbol => "F13",
75            BuiltinFont::ZapfDingbats => "F14",
76        }
77    }
78
79    /// Returns the PDF BaseFont name (e.g. "Helvetica",
80    /// "Times-Roman").
81    pub fn pdf_base_name(&self) -> &'static str {
82        match self {
83            BuiltinFont::Helvetica => "Helvetica",
84            BuiltinFont::HelveticaBold => "Helvetica-Bold",
85            BuiltinFont::HelveticaOblique => "Helvetica-Oblique",
86            BuiltinFont::HelveticaBoldOblique => "Helvetica-BoldOblique",
87            BuiltinFont::TimesRoman => "Times-Roman",
88            BuiltinFont::TimesBold => "Times-Bold",
89            BuiltinFont::TimesItalic => "Times-Italic",
90            BuiltinFont::TimesBoldItalic => "Times-BoldItalic",
91            BuiltinFont::Courier => "Courier",
92            BuiltinFont::CourierBold => "Courier-Bold",
93            BuiltinFont::CourierOblique => "Courier-Oblique",
94            BuiltinFont::CourierBoldOblique => "Courier-BoldOblique",
95            BuiltinFont::Symbol => "Symbol",
96            BuiltinFont::ZapfDingbats => "ZapfDingbats",
97        }
98    }
99
100    /// Look up a BuiltinFont by its PDF base name string.
101    /// Returns None if the name doesn't match any variant.
102    pub fn from_name(name: &str) -> Option<BuiltinFont> {
103        match name {
104            "Helvetica" => Some(BuiltinFont::Helvetica),
105            "Helvetica-Bold" => Some(BuiltinFont::HelveticaBold),
106            "Helvetica-Oblique" => Some(BuiltinFont::HelveticaOblique),
107            "Helvetica-BoldOblique" => Some(BuiltinFont::HelveticaBoldOblique),
108            "Times-Roman" => Some(BuiltinFont::TimesRoman),
109            "Times-Bold" => Some(BuiltinFont::TimesBold),
110            "Times-Italic" => Some(BuiltinFont::TimesItalic),
111            "Times-BoldItalic" => Some(BuiltinFont::TimesBoldItalic),
112            "Courier" => Some(BuiltinFont::Courier),
113            "Courier-Bold" => Some(BuiltinFont::CourierBold),
114            "Courier-Oblique" => Some(BuiltinFont::CourierOblique),
115            "Courier-BoldOblique" => Some(BuiltinFont::CourierBoldOblique),
116            "Symbol" => Some(BuiltinFont::Symbol),
117            "ZapfDingbats" => Some(BuiltinFont::ZapfDingbats),
118            _ => None,
119        }
120    }
121}
122
123/// Character widths for Helvetica (ASCII 32..=126) in units of 1/1000 em.
124/// Source: Adobe Helvetica AFM data.
125const HELVETICA_WIDTHS: [u16; 95] = [
126    278,  // 32 space
127    278,  // 33 !
128    355,  // 34 "
129    556,  // 35 #
130    556,  // 36 $
131    889,  // 37 %
132    667,  // 38 &
133    191,  // 39 '
134    333,  // 40 (
135    333,  // 41 )
136    389,  // 42 *
137    584,  // 43 +
138    278,  // 44 ,
139    333,  // 45 -
140    278,  // 46 .
141    278,  // 47 /
142    556,  // 48 0
143    556,  // 49 1
144    556,  // 50 2
145    556,  // 51 3
146    556,  // 52 4
147    556,  // 53 5
148    556,  // 54 6
149    556,  // 55 7
150    556,  // 56 8
151    556,  // 57 9
152    278,  // 58 :
153    278,  // 59 ;
154    584,  // 60 <
155    584,  // 61 =
156    584,  // 62 >
157    556,  // 63 ?
158    1015, // 64 @
159    667,  // 65 A
160    667,  // 66 B
161    722,  // 67 C
162    722,  // 68 D
163    667,  // 69 E
164    611,  // 70 F
165    778,  // 71 G
166    722,  // 72 H
167    278,  // 73 I
168    500,  // 74 J
169    667,  // 75 K
170    556,  // 76 L
171    833,  // 77 M
172    722,  // 78 N
173    778,  // 79 O
174    667,  // 80 P
175    778,  // 81 Q
176    722,  // 82 R
177    667,  // 83 S
178    611,  // 84 T
179    722,  // 85 U
180    667,  // 86 V
181    944,  // 87 W
182    667,  // 88 X
183    667,  // 89 Y
184    611,  // 90 Z
185    278,  // 91 [
186    278,  // 92 backslash
187    278,  // 93 ]
188    469,  // 94 ^
189    556,  // 95 _
190    333,  // 96 `
191    556,  // 97 a
192    556,  // 98 b
193    500,  // 99 c
194    556,  // 100 d
195    556,  // 101 e
196    278,  // 102 f
197    556,  // 103 g
198    556,  // 104 h
199    222,  // 105 i
200    222,  // 106 j
201    500,  // 107 k
202    222,  // 108 l
203    833,  // 109 m
204    556,  // 110 n
205    556,  // 111 o
206    556,  // 112 p
207    556,  // 113 q
208    333,  // 114 r
209    500,  // 115 s
210    278,  // 116 t
211    556,  // 117 u
212    500,  // 118 v
213    722,  // 119 w
214    500,  // 120 x
215    500,  // 121 y
216    500,  // 122 z
217    334,  // 123 {
218    260,  // 124 |
219    334,  // 125 }
220    584,  // 126 ~
221];
222
223/// Character widths for Helvetica-Bold (ASCII 32..=126) in 1/1000 em.
224/// Source: Adobe Helvetica-Bold AFM data.
225const HELVETICA_BOLD_WIDTHS: [u16; 95] = [
226    278, // 32 space
227    333, // 33 !
228    474, // 34 "
229    556, // 35 #
230    556, // 36 $
231    889, // 37 %
232    722, // 38 &
233    238, // 39 '
234    333, // 40 (
235    333, // 41 )
236    389, // 42 *
237    584, // 43 +
238    278, // 44 ,
239    333, // 45 -
240    278, // 46 .
241    278, // 47 /
242    556, // 48 0
243    556, // 49 1
244    556, // 50 2
245    556, // 51 3
246    556, // 52 4
247    556, // 53 5
248    556, // 54 6
249    556, // 55 7
250    556, // 56 8
251    556, // 57 9
252    333, // 58 :
253    333, // 59 ;
254    584, // 60 <
255    584, // 61 =
256    584, // 62 >
257    611, // 63 ?
258    975, // 64 @
259    722, // 65 A
260    722, // 66 B
261    722, // 67 C
262    722, // 68 D
263    667, // 69 E
264    611, // 70 F
265    778, // 71 G
266    722, // 72 H
267    278, // 73 I
268    556, // 74 J
269    722, // 75 K
270    611, // 76 L
271    833, // 77 M
272    722, // 78 N
273    778, // 79 O
274    667, // 80 P
275    778, // 81 Q
276    722, // 82 R
277    667, // 83 S
278    611, // 84 T
279    722, // 85 U
280    667, // 86 V
281    944, // 87 W
282    667, // 88 X
283    667, // 89 Y
284    611, // 90 Z
285    333, // 91 [
286    278, // 92 backslash
287    333, // 93 ]
288    584, // 94 ^
289    556, // 95 _
290    333, // 96 `
291    556, // 97 a
292    611, // 98 b
293    556, // 99 c
294    611, // 100 d
295    556, // 101 e
296    333, // 102 f
297    611, // 103 g
298    611, // 104 h
299    278, // 105 i
300    278, // 106 j
301    556, // 107 k
302    278, // 108 l
303    889, // 109 m
304    611, // 110 n
305    611, // 111 o
306    611, // 112 p
307    611, // 113 q
308    389, // 114 r
309    556, // 115 s
310    333, // 116 t
311    611, // 117 u
312    556, // 118 v
313    778, // 119 w
314    556, // 120 x
315    556, // 121 y
316    500, // 122 z
317    389, // 123 {
318    280, // 124 |
319    389, // 125 }
320    584, // 126 ~
321];
322
323/// Character widths for Times-Roman (ASCII 32..=126) in 1/1000 em.
324/// Source: Adobe Times-Roman AFM data.
325const TIMES_ROMAN_WIDTHS: [u16; 95] = [
326    250, // 32 space
327    333, // 33 !
328    408, // 34 "
329    500, // 35 #
330    500, // 36 $
331    833, // 37 %
332    778, // 38 &
333    180, // 39 '
334    333, // 40 (
335    333, // 41 )
336    500, // 42 *
337    564, // 43 +
338    250, // 44 ,
339    333, // 45 -
340    250, // 46 .
341    278, // 47 /
342    500, // 48 0
343    500, // 49 1
344    500, // 50 2
345    500, // 51 3
346    500, // 52 4
347    500, // 53 5
348    500, // 54 6
349    500, // 55 7
350    500, // 56 8
351    500, // 57 9
352    278, // 58 :
353    278, // 59 ;
354    564, // 60 <
355    564, // 61 =
356    564, // 62 >
357    444, // 63 ?
358    921, // 64 @
359    722, // 65 A
360    667, // 66 B
361    667, // 67 C
362    722, // 68 D
363    611, // 69 E
364    556, // 70 F
365    722, // 71 G
366    722, // 72 H
367    333, // 73 I
368    389, // 74 J
369    722, // 75 K
370    611, // 76 L
371    889, // 77 M
372    722, // 78 N
373    722, // 79 O
374    556, // 80 P
375    722, // 81 Q
376    667, // 82 R
377    556, // 83 S
378    611, // 84 T
379    722, // 85 U
380    722, // 86 V
381    944, // 87 W
382    722, // 88 X
383    722, // 89 Y
384    611, // 90 Z
385    333, // 91 [
386    278, // 92 backslash
387    333, // 93 ]
388    469, // 94 ^
389    500, // 95 _
390    333, // 96 `
391    444, // 97 a
392    500, // 98 b
393    444, // 99 c
394    500, // 100 d
395    444, // 101 e
396    333, // 102 f
397    500, // 103 g
398    500, // 104 h
399    278, // 105 i
400    278, // 106 j
401    500, // 107 k
402    278, // 108 l
403    778, // 109 m
404    500, // 110 n
405    500, // 111 o
406    500, // 112 p
407    500, // 113 q
408    333, // 114 r
409    389, // 115 s
410    278, // 116 t
411    500, // 117 u
412    500, // 118 v
413    722, // 119 w
414    500, // 120 x
415    500, // 121 y
416    444, // 122 z
417    480, // 123 {
418    200, // 124 |
419    480, // 125 }
420    541, // 126 ~
421];
422
423/// Character widths for Times-Bold (ASCII 32..=126) in 1/1000 em.
424/// Source: Adobe Times-Bold AFM data.
425const TIMES_BOLD_WIDTHS: [u16; 95] = [
426    250,  // 32 space
427    333,  // 33 !
428    555,  // 34 "
429    500,  // 35 #
430    500,  // 36 $
431    1000, // 37 %
432    833,  // 38 &
433    278,  // 39 '
434    333,  // 40 (
435    333,  // 41 )
436    500,  // 42 *
437    570,  // 43 +
438    250,  // 44 ,
439    333,  // 45 -
440    250,  // 46 .
441    278,  // 47 /
442    500,  // 48 0
443    500,  // 49 1
444    500,  // 50 2
445    500,  // 51 3
446    500,  // 52 4
447    500,  // 53 5
448    500,  // 54 6
449    500,  // 55 7
450    500,  // 56 8
451    500,  // 57 9
452    333,  // 58 :
453    333,  // 59 ;
454    570,  // 60 <
455    570,  // 61 =
456    570,  // 62 >
457    500,  // 63 ?
458    930,  // 64 @
459    722,  // 65 A
460    667,  // 66 B
461    722,  // 67 C
462    722,  // 68 D
463    667,  // 69 E
464    611,  // 70 F
465    778,  // 71 G
466    778,  // 72 H
467    389,  // 73 I
468    500,  // 74 J
469    778,  // 75 K
470    667,  // 76 L
471    944,  // 77 M
472    722,  // 78 N
473    778,  // 79 O
474    611,  // 80 P
475    778,  // 81 Q
476    722,  // 82 R
477    556,  // 83 S
478    667,  // 84 T
479    722,  // 85 U
480    722,  // 86 V
481    1000, // 87 W
482    722,  // 88 X
483    722,  // 89 Y
484    667,  // 90 Z
485    333,  // 91 [
486    278,  // 92 backslash
487    333,  // 93 ]
488    581,  // 94 ^
489    500,  // 95 _
490    333,  // 96 `
491    500,  // 97 a
492    556,  // 98 b
493    444,  // 99 c
494    556,  // 100 d
495    444,  // 101 e
496    333,  // 102 f
497    500,  // 103 g
498    556,  // 104 h
499    278,  // 105 i
500    333,  // 106 j
501    556,  // 107 k
502    278,  // 108 l
503    833,  // 109 m
504    556,  // 110 n
505    500,  // 111 o
506    556,  // 112 p
507    556,  // 113 q
508    444,  // 114 r
509    389,  // 115 s
510    333,  // 116 t
511    556,  // 117 u
512    500,  // 118 v
513    722,  // 119 w
514    500,  // 120 x
515    500,  // 121 y
516    444,  // 122 z
517    394,  // 123 {
518    220,  // 124 |
519    394,  // 125 }
520    520,  // 126 ~
521];
522
523/// Character widths for Times-Italic (ASCII 32..=126) in 1/1000 em.
524/// Source: Adobe Times-Italic AFM data.
525const TIMES_ITALIC_WIDTHS: [u16; 95] = [
526    250, // 32 space
527    333, // 33 !
528    420, // 34 "
529    500, // 35 #
530    500, // 36 $
531    833, // 37 %
532    778, // 38 &
533    214, // 39 '
534    333, // 40 (
535    333, // 41 )
536    500, // 42 *
537    675, // 43 +
538    250, // 44 ,
539    333, // 45 -
540    250, // 46 .
541    278, // 47 /
542    500, // 48 0
543    500, // 49 1
544    500, // 50 2
545    500, // 51 3
546    500, // 52 4
547    500, // 53 5
548    500, // 54 6
549    500, // 55 7
550    500, // 56 8
551    500, // 57 9
552    333, // 58 :
553    333, // 59 ;
554    675, // 60 <
555    675, // 61 =
556    675, // 62 >
557    500, // 63 ?
558    920, // 64 @
559    611, // 65 A
560    611, // 66 B
561    667, // 67 C
562    722, // 68 D
563    611, // 69 E
564    611, // 70 F
565    722, // 71 G
566    722, // 72 H
567    333, // 73 I
568    444, // 74 J
569    667, // 75 K
570    556, // 76 L
571    833, // 77 M
572    667, // 78 N
573    722, // 79 O
574    611, // 80 P
575    722, // 81 Q
576    611, // 82 R
577    500, // 83 S
578    556, // 84 T
579    722, // 85 U
580    611, // 86 V
581    833, // 87 W
582    611, // 88 X
583    556, // 89 Y
584    556, // 90 Z
585    389, // 91 [
586    278, // 92 backslash
587    389, // 93 ]
588    422, // 94 ^
589    500, // 95 _
590    333, // 96 `
591    500, // 97 a
592    500, // 98 b
593    444, // 99 c
594    500, // 100 d
595    444, // 101 e
596    278, // 102 f
597    500, // 103 g
598    500, // 104 h
599    278, // 105 i
600    278, // 106 j
601    444, // 107 k
602    278, // 108 l
603    722, // 109 m
604    500, // 110 n
605    500, // 111 o
606    500, // 112 p
607    500, // 113 q
608    389, // 114 r
609    389, // 115 s
610    278, // 116 t
611    500, // 117 u
612    444, // 118 v
613    667, // 119 w
614    444, // 120 x
615    444, // 121 y
616    389, // 122 z
617    400, // 123 {
618    275, // 124 |
619    400, // 125 }
620    541, // 126 ~
621];
622
623/// Character widths for Times-BoldItalic (ASCII 32..=126) in 1/1000 em.
624/// Source: Adobe Times-BoldItalic AFM data.
625const TIMES_BOLD_ITALIC_WIDTHS: [u16; 95] = [
626    250, // 32 space
627    389, // 33 !
628    555, // 34 "
629    500, // 35 #
630    500, // 36 $
631    833, // 37 %
632    778, // 38 &
633    278, // 39 '
634    333, // 40 (
635    333, // 41 )
636    500, // 42 *
637    570, // 43 +
638    250, // 44 ,
639    333, // 45 -
640    250, // 46 .
641    278, // 47 /
642    500, // 48 0
643    500, // 49 1
644    500, // 50 2
645    500, // 51 3
646    500, // 52 4
647    500, // 53 5
648    500, // 54 6
649    500, // 55 7
650    500, // 56 8
651    500, // 57 9
652    333, // 58 :
653    333, // 59 ;
654    570, // 60 <
655    570, // 61 =
656    570, // 62 >
657    500, // 63 ?
658    832, // 64 @
659    667, // 65 A
660    667, // 66 B
661    667, // 67 C
662    722, // 68 D
663    667, // 69 E
664    667, // 70 F
665    722, // 71 G
666    778, // 72 H
667    389, // 73 I
668    500, // 74 J
669    667, // 75 K
670    611, // 76 L
671    889, // 77 M
672    722, // 78 N
673    722, // 79 O
674    611, // 80 P
675    722, // 81 Q
676    667, // 82 R
677    556, // 83 S
678    611, // 84 T
679    722, // 85 U
680    667, // 86 V
681    889, // 87 W
682    667, // 88 X
683    611, // 89 Y
684    611, // 90 Z
685    333, // 91 [
686    278, // 92 backslash
687    333, // 93 ]
688    570, // 94 ^
689    500, // 95 _
690    333, // 96 `
691    500, // 97 a
692    500, // 98 b
693    444, // 99 c
694    500, // 100 d
695    444, // 101 e
696    333, // 102 f
697    500, // 103 g
698    556, // 104 h
699    278, // 105 i
700    278, // 106 j
701    500, // 107 k
702    278, // 108 l
703    778, // 109 m
704    556, // 110 n
705    500, // 111 o
706    556, // 112 p
707    556, // 113 q
708    389, // 114 r
709    389, // 115 s
710    278, // 116 t
711    556, // 117 u
712    444, // 118 v
713    667, // 119 w
714    500, // 120 x
715    444, // 121 y
716    389, // 122 z
717    348, // 123 {
718    220, // 124 |
719    348, // 125 }
720    570, // 126 ~
721];
722
723/// Courier uses a uniform width of 600 for all characters.
724const COURIER_WIDTH: u16 = 600;
725
726/// Default width for characters outside the mapped range (1/1000 em).
727const DEFAULT_WIDTH: u16 = 278;
728
729/// Font metrics for built-in PDF fonts.
730pub struct FontMetrics;
731
732impl FontMetrics {
733    /// Returns the width of a character in 1/1000 em units.
734    pub fn char_width(font: BuiltinFont, ch: char) -> u16 {
735        // Courier variants are monospaced
736        match font {
737            BuiltinFont::Courier
738            | BuiltinFont::CourierBold
739            | BuiltinFont::CourierOblique
740            | BuiltinFont::CourierBoldOblique => {
741                return COURIER_WIDTH;
742            }
743            // Symbol/ZapfDingbats use default width (Phase 1)
744            BuiltinFont::Symbol | BuiltinFont::ZapfDingbats => {
745                return DEFAULT_WIDTH;
746            }
747            _ => {}
748        }
749
750        let code = ch as u32;
751        if code < 32 || code > 126 {
752            return DEFAULT_WIDTH;
753        }
754        let index = (code - 32) as usize;
755        match font {
756            BuiltinFont::Helvetica | BuiltinFont::HelveticaOblique => HELVETICA_WIDTHS[index],
757            BuiltinFont::HelveticaBold | BuiltinFont::HelveticaBoldOblique => {
758                HELVETICA_BOLD_WIDTHS[index]
759            }
760            BuiltinFont::TimesRoman => TIMES_ROMAN_WIDTHS[index],
761            BuiltinFont::TimesBold => TIMES_BOLD_WIDTHS[index],
762            BuiltinFont::TimesItalic => TIMES_ITALIC_WIDTHS[index],
763            BuiltinFont::TimesBoldItalic => TIMES_BOLD_ITALIC_WIDTHS[index],
764            // Already handled above
765            _ => DEFAULT_WIDTH,
766        }
767    }
768
769    /// Measures the width of a text string in points.
770    pub fn measure_text(text: &str, font: BuiltinFont, font_size: f64) -> f64 {
771        let total: u32 = text
772            .chars()
773            .map(|ch| Self::char_width(font, ch) as u32)
774            .sum();
775        total as f64 * font_size / 1000.0
776    }
777
778    /// Returns the line height for a given font size
779    /// (1.2x multiplier).
780    pub fn line_height(_font: BuiltinFont, font_size: f64) -> f64 {
781        font_size * 1.2
782    }
783}