Skip to main content

forme/font/
metrics.rs

1//! # Standard Font Metrics
2//!
3//! Glyph advance widths for the 14 standard PDF fonts.
4//! Data sourced from Adobe AFM files via Hopding/standard-fonts.
5//! Widths are in units of 1/1000 em (the standard AFM unit).
6//!
7//! Each array covers WinAnsiEncoding code points 32..=255 (224 entries).
8//! Index 0 = code 32 (space), index 223 = code 255 (ydieresis).
9
10use super::StandardFont;
11
12/// Map a Unicode codepoint to a WinAnsiEncoding byte value.
13///
14/// WinAnsiEncoding is based on Windows-1252. Most codepoints in
15/// 0x20..=0x7E and 0xA0..=0xFF map directly. The 0x80..=0x9F range
16/// contains special mappings for smart quotes, bullets, dashes, etc.
17pub fn unicode_to_winansi(ch: char) -> Option<u8> {
18    let cp = ch as u32;
19    // ASCII printable range maps directly
20    if (0x20..=0x7E).contains(&cp) || (0xA0..=0xFF).contains(&cp) {
21        return Some(cp as u8);
22    }
23    // Windows-1252 special mappings (0x80-0x9F)
24    match cp {
25        0x20AC => Some(0x80), // Euro sign
26        0x201A => Some(0x82), // Single low-9 quotation mark
27        0x0192 => Some(0x83), // Latin small letter f with hook
28        0x201E => Some(0x84), // Double low-9 quotation mark
29        0x2026 => Some(0x85), // Horizontal ellipsis
30        0x2020 => Some(0x86), // Dagger
31        0x2021 => Some(0x87), // Double dagger
32        0x02C6 => Some(0x88), // Modifier letter circumflex accent
33        0x2030 => Some(0x89), // Per mille sign
34        0x0160 => Some(0x8A), // Latin capital letter S with caron
35        0x2039 => Some(0x8B), // Single left-pointing angle quotation
36        0x0152 => Some(0x8C), // Latin capital ligature OE
37        0x017D => Some(0x8E), // Latin capital letter Z with caron
38        0x2018 => Some(0x91), // Left single quotation mark
39        0x2019 => Some(0x92), // Right single quotation mark
40        0x201C => Some(0x93), // Left double quotation mark
41        0x201D => Some(0x94), // Right double quotation mark
42        0x2022 => Some(0x95), // Bullet
43        0x2013 => Some(0x96), // En dash
44        0x2014 => Some(0x97), // Em dash
45        0x02DC => Some(0x98), // Small tilde
46        0x2122 => Some(0x99), // Trade mark sign
47        0x0161 => Some(0x9A), // Latin small letter s with caron
48        0x203A => Some(0x9B), // Single right-pointing angle quotation
49        0x0153 => Some(0x9C), // Latin small ligature oe
50        0x017E => Some(0x9E), // Latin small letter z with caron
51        0x0178 => Some(0x9F), // Latin capital letter Y with diaeresis
52        _ => None,
53    }
54}
55
56/// Glyph widths for a standard PDF font.
57/// Indexed by WinAnsiEncoding code point: index 0 = code 32, index 223 = code 255.
58/// Values are advance widths in units of 1/1000 em.
59pub struct StandardFontMetrics {
60    pub widths: &'static [u16; 224],
61    pub default_width: u16,
62}
63
64impl StandardFontMetrics {
65    /// Get the advance width of a character in points.
66    pub fn char_width(&self, ch: char, font_size: f64) -> f64 {
67        let code = ch as u32;
68        let w = if (32..=255).contains(&code) {
69            let idx = (code - 32) as usize;
70            let w = self.widths[idx];
71            if w > 0 {
72                w
73            } else {
74                self.default_width
75            }
76        } else if let Some(winansi) = unicode_to_winansi(ch) {
77            if winansi >= 32 {
78                let idx = (winansi as u32 - 32) as usize;
79                let w = self.widths[idx];
80                if w > 0 {
81                    w
82                } else {
83                    self.default_width
84                }
85            } else {
86                self.default_width
87            }
88        } else {
89            self.default_width
90        };
91        (w as f64 / 1000.0) * font_size
92    }
93
94    /// Measure the width of a string in points.
95    pub fn measure_string(&self, text: &str, font_size: f64, letter_spacing: f64) -> f64 {
96        let mut width = 0.0;
97        for ch in text.chars() {
98            width += self.char_width(ch, font_size) + letter_spacing;
99        }
100        width
101    }
102}
103
104impl StandardFont {
105    /// Get the metrics for this standard font.
106    pub fn metrics(&self) -> StandardFontMetrics {
107        match self {
108            Self::Helvetica => StandardFontMetrics {
109                widths: &HELVETICA_WIDTHS,
110                default_width: 278,
111            },
112            Self::HelveticaBold => StandardFontMetrics {
113                widths: &HELVETICA_BOLD_WIDTHS,
114                default_width: 278,
115            },
116            Self::HelveticaOblique => StandardFontMetrics {
117                widths: &HELVETICA_OBLIQUE_WIDTHS,
118                default_width: 278,
119            },
120            Self::HelveticaBoldOblique => StandardFontMetrics {
121                widths: &HELVETICA_BOLD_OBLIQUE_WIDTHS,
122                default_width: 278,
123            },
124            Self::TimesRoman => StandardFontMetrics {
125                widths: &TIMES_ROMAN_WIDTHS,
126                default_width: 250,
127            },
128            Self::TimesBold => StandardFontMetrics {
129                widths: &TIMES_BOLD_WIDTHS,
130                default_width: 250,
131            },
132            Self::TimesItalic => StandardFontMetrics {
133                widths: &TIMES_ITALIC_WIDTHS,
134                default_width: 250,
135            },
136            Self::TimesBoldItalic => StandardFontMetrics {
137                widths: &TIMES_BOLD_ITALIC_WIDTHS,
138                default_width: 250,
139            },
140            Self::Courier => StandardFontMetrics {
141                widths: &COURIER_WIDTHS,
142                default_width: 600,
143            },
144            Self::CourierBold => StandardFontMetrics {
145                widths: &COURIER_BOLD_WIDTHS,
146                default_width: 600,
147            },
148            Self::CourierOblique => StandardFontMetrics {
149                widths: &COURIER_OBLIQUE_WIDTHS,
150                default_width: 600,
151            },
152            Self::CourierBoldOblique => StandardFontMetrics {
153                widths: &COURIER_BOLD_OBLIQUE_WIDTHS,
154                default_width: 600,
155            },
156            Self::Symbol => StandardFontMetrics {
157                widths: &SYMBOL_WIDTHS,
158                default_width: 250,
159            },
160            Self::ZapfDingbats => StandardFontMetrics {
161                widths: &ZAPF_DINGBATS_WIDTHS,
162                default_width: 278,
163            },
164        }
165    }
166}
167
168static HELVETICA_WIDTHS: [u16; 224] = [
169    278, 278, 355, 556, 556, 889, 667, 222, 333, 333, 389, 584, 278, 333, 278, 278, 556, 556, 556,
170    556, 556, 556, 556, 556, 556, 556, 278, 278, 584, 584, 584, 556, 1015, 667, 667, 722, 722, 667,
171    611, 778, 722, 278, 500, 667, 556, 833, 722, 778, 667, 778, 722, 667, 611, 722, 667, 944, 667,
172    667, 611, 278, 278, 278, 469, 556, 222, 556, 556, 500, 556, 556, 278, 556, 556, 222, 222, 500,
173    222, 833, 556, 556, 556, 556, 333, 500, 278, 556, 500, 722, 500, 500, 500, 334, 260, 334, 584,
174    0, 556, 0, 222, 556, 333, 1000, 556, 556, 333, 1000, 667, 333, 1000, 0, 611, 0, 0, 222, 222,
175    333, 333, 350, 556, 1000, 333, 1000, 500, 333, 944, 0, 500, 667, 278, 333, 556, 556, 167, 556,
176    556, 556, 556, 191, 333, 556, 333, 333, 500, 500, 400, 556, 556, 556, 278, 556, 537, 350, 222,
177    333, 333, 556, 1000, 1000, 834, 611, 667, 333, 333, 333, 333, 333, 333, 333, 333, 667, 333,
178    333, 278, 333, 333, 333, 1000, 722, 778, 778, 778, 778, 778, 584, 778, 722, 722, 722, 722, 667,
179    667, 611, 556, 1000, 556, 370, 556, 556, 889, 500, 556, 778, 1000, 365, 278, 278, 278, 278,
180    556, 889, 556, 556, 556, 278, 556, 584, 222, 611, 944, 611, 556, 500, 556, 500,
181];
182
183static HELVETICA_BOLD_WIDTHS: [u16; 224] = [
184    278, 333, 474, 556, 556, 889, 722, 278, 333, 333, 389, 584, 278, 333, 278, 278, 556, 556, 556,
185    556, 556, 556, 556, 556, 556, 556, 333, 333, 584, 584, 584, 611, 975, 722, 722, 722, 722, 667,
186    611, 778, 722, 278, 556, 722, 611, 833, 722, 778, 667, 778, 722, 667, 611, 722, 667, 944, 667,
187    667, 611, 333, 278, 333, 584, 556, 278, 556, 611, 556, 611, 556, 333, 611, 611, 278, 278, 556,
188    278, 889, 611, 611, 611, 611, 389, 556, 333, 611, 556, 778, 556, 556, 500, 389, 280, 389, 584,
189    0, 556, 0, 278, 556, 500, 1000, 556, 556, 333, 1000, 667, 333, 1000, 0, 611, 0, 0, 278, 278,
190    500, 500, 350, 556, 1000, 333, 1000, 556, 333, 944, 0, 500, 667, 278, 333, 556, 556, 167, 556,
191    556, 556, 556, 238, 500, 556, 333, 333, 611, 611, 400, 556, 556, 556, 278, 611, 556, 350, 278,
192    500, 500, 556, 1000, 1000, 834, 611, 722, 333, 333, 333, 333, 333, 333, 333, 333, 667, 333,
193    333, 278, 333, 333, 333, 1000, 722, 778, 778, 778, 778, 778, 584, 778, 722, 722, 722, 722, 667,
194    667, 611, 556, 1000, 556, 370, 556, 556, 889, 556, 611, 778, 1000, 365, 278, 278, 278, 278,
195    611, 889, 611, 611, 611, 278, 611, 584, 278, 611, 944, 611, 611, 556, 611, 556,
196];
197
198static HELVETICA_OBLIQUE_WIDTHS: [u16; 224] = [
199    278, 278, 355, 556, 556, 889, 667, 222, 333, 333, 389, 584, 278, 333, 278, 278, 556, 556, 556,
200    556, 556, 556, 556, 556, 556, 556, 278, 278, 584, 584, 584, 556, 1015, 667, 667, 722, 722, 667,
201    611, 778, 722, 278, 500, 667, 556, 833, 722, 778, 667, 778, 722, 667, 611, 722, 667, 944, 667,
202    667, 611, 278, 278, 278, 469, 556, 222, 556, 556, 500, 556, 556, 278, 556, 556, 222, 222, 500,
203    222, 833, 556, 556, 556, 556, 333, 500, 278, 556, 500, 722, 500, 500, 500, 334, 260, 334, 584,
204    0, 556, 0, 222, 556, 333, 1000, 556, 556, 333, 1000, 667, 333, 1000, 0, 611, 0, 0, 222, 222,
205    333, 333, 350, 556, 1000, 333, 1000, 500, 333, 944, 0, 500, 667, 278, 333, 556, 556, 167, 556,
206    556, 556, 556, 191, 333, 556, 333, 333, 500, 500, 400, 556, 556, 556, 278, 556, 537, 350, 222,
207    333, 333, 556, 1000, 1000, 834, 611, 667, 333, 333, 333, 333, 333, 333, 333, 333, 667, 333,
208    333, 278, 333, 333, 333, 1000, 722, 778, 778, 778, 778, 778, 584, 778, 722, 722, 722, 722, 667,
209    667, 611, 556, 1000, 556, 370, 556, 556, 889, 500, 556, 778, 1000, 365, 278, 278, 278, 278,
210    556, 889, 556, 556, 556, 278, 556, 584, 222, 611, 944, 611, 556, 500, 556, 500,
211];
212
213static HELVETICA_BOLD_OBLIQUE_WIDTHS: [u16; 224] = [
214    278, 333, 474, 556, 556, 889, 722, 278, 333, 333, 389, 584, 278, 333, 278, 278, 556, 556, 556,
215    556, 556, 556, 556, 556, 556, 556, 333, 333, 584, 584, 584, 611, 975, 722, 722, 722, 722, 667,
216    611, 778, 722, 278, 556, 722, 611, 833, 722, 778, 667, 778, 722, 667, 611, 722, 667, 944, 667,
217    667, 611, 333, 278, 333, 584, 556, 278, 556, 611, 556, 611, 556, 333, 611, 611, 278, 278, 556,
218    278, 889, 611, 611, 611, 611, 389, 556, 333, 611, 556, 778, 556, 556, 500, 389, 280, 389, 584,
219    0, 556, 0, 278, 556, 500, 1000, 556, 556, 333, 1000, 667, 333, 1000, 0, 611, 0, 0, 278, 278,
220    500, 500, 350, 556, 1000, 333, 1000, 556, 333, 944, 0, 500, 667, 278, 333, 556, 556, 167, 556,
221    556, 556, 556, 238, 500, 556, 333, 333, 611, 611, 400, 556, 556, 556, 278, 611, 556, 350, 278,
222    500, 500, 556, 1000, 1000, 834, 611, 722, 333, 333, 333, 333, 333, 333, 333, 333, 667, 333,
223    333, 278, 333, 333, 333, 1000, 722, 778, 778, 778, 778, 778, 584, 778, 722, 722, 722, 722, 667,
224    667, 611, 556, 1000, 556, 370, 556, 556, 889, 556, 611, 778, 1000, 365, 278, 278, 278, 278,
225    611, 889, 611, 611, 611, 278, 611, 584, 278, 611, 944, 611, 611, 556, 611, 556,
226];
227
228static TIMES_ROMAN_WIDTHS: [u16; 224] = [
229    250, 333, 408, 500, 500, 833, 778, 333, 333, 333, 500, 564, 250, 333, 250, 278, 500, 500, 500,
230    500, 500, 500, 500, 500, 500, 500, 278, 278, 564, 564, 564, 444, 921, 722, 667, 667, 722, 611,
231    556, 722, 722, 333, 389, 722, 611, 889, 722, 722, 556, 722, 667, 556, 611, 722, 722, 944, 722,
232    722, 611, 333, 278, 333, 469, 500, 333, 444, 500, 444, 500, 444, 333, 500, 500, 278, 278, 500,
233    278, 778, 500, 500, 500, 500, 333, 389, 278, 500, 500, 722, 500, 500, 444, 480, 200, 480, 541,
234    0, 500, 0, 333, 500, 444, 1000, 500, 500, 333, 1000, 556, 333, 889, 0, 611, 0, 0, 333, 333,
235    444, 444, 350, 500, 1000, 333, 980, 389, 333, 722, 0, 444, 722, 250, 333, 500, 500, 167, 500,
236    500, 500, 500, 180, 444, 500, 333, 333, 556, 556, 400, 500, 500, 500, 250, 500, 453, 350, 333,
237    444, 444, 500, 1000, 1000, 750, 444, 722, 333, 333, 333, 333, 333, 333, 333, 333, 611, 333,
238    333, 333, 333, 333, 333, 1000, 722, 722, 722, 722, 722, 722, 564, 722, 722, 722, 722, 722, 722,
239    556, 500, 444, 889, 444, 276, 444, 444, 667, 444, 611, 722, 889, 310, 278, 278, 278, 278, 500,
240    667, 500, 500, 500, 278, 500, 564, 278, 500, 722, 500, 500, 500, 500, 500,
241];
242
243static TIMES_BOLD_WIDTHS: [u16; 224] = [
244    250, 333, 555, 500, 500, 1000, 833, 333, 333, 333, 500, 570, 250, 333, 250, 278, 500, 500, 500,
245    500, 500, 500, 500, 500, 500, 500, 333, 333, 570, 570, 570, 500, 930, 722, 667, 722, 722, 667,
246    611, 778, 778, 389, 500, 778, 667, 944, 722, 778, 611, 778, 722, 556, 667, 722, 722, 1000, 722,
247    722, 667, 333, 278, 333, 581, 500, 333, 500, 556, 444, 556, 444, 333, 500, 556, 278, 333, 556,
248    278, 833, 556, 500, 556, 556, 444, 389, 333, 556, 500, 722, 500, 500, 444, 394, 220, 394, 520,
249    0, 500, 0, 333, 500, 500, 1000, 500, 500, 333, 1000, 556, 333, 1000, 0, 667, 0, 0, 333, 333,
250    500, 500, 350, 500, 1000, 333, 1000, 389, 333, 722, 0, 444, 722, 250, 333, 500, 500, 167, 500,
251    500, 500, 500, 278, 500, 500, 333, 333, 556, 556, 400, 500, 500, 500, 250, 556, 540, 350, 333,
252    500, 500, 500, 1000, 1000, 750, 500, 722, 333, 333, 333, 333, 333, 333, 333, 333, 667, 333,
253    333, 389, 333, 333, 333, 1000, 722, 778, 778, 778, 778, 778, 570, 778, 722, 722, 722, 722, 722,
254    611, 556, 500, 1000, 500, 300, 500, 500, 722, 444, 667, 778, 1000, 330, 278, 278, 278, 278,
255    500, 722, 500, 500, 500, 278, 500, 570, 278, 500, 722, 556, 556, 500, 556, 500,
256];
257
258static TIMES_ITALIC_WIDTHS: [u16; 224] = [
259    250, 333, 420, 500, 500, 833, 778, 333, 333, 333, 500, 675, 250, 333, 250, 278, 500, 500, 500,
260    500, 500, 500, 500, 500, 500, 500, 333, 333, 675, 675, 675, 500, 920, 611, 611, 667, 722, 611,
261    611, 722, 722, 333, 444, 667, 556, 833, 667, 722, 611, 722, 611, 500, 556, 722, 611, 833, 611,
262    556, 556, 389, 278, 389, 422, 500, 333, 500, 500, 444, 500, 444, 278, 500, 500, 278, 278, 444,
263    278, 722, 500, 500, 500, 500, 389, 389, 278, 500, 444, 667, 444, 444, 389, 400, 275, 400, 541,
264    0, 500, 0, 333, 500, 556, 889, 500, 500, 333, 1000, 500, 333, 944, 0, 556, 0, 0, 333, 333, 556,
265    556, 350, 500, 889, 333, 980, 389, 333, 667, 0, 389, 556, 250, 389, 500, 500, 167, 500, 500,
266    500, 500, 214, 556, 500, 333, 333, 500, 500, 400, 500, 500, 500, 250, 500, 523, 350, 333, 556,
267    556, 500, 889, 1000, 750, 500, 611, 333, 333, 333, 333, 333, 333, 333, 333, 611, 333, 333, 333,
268    333, 333, 333, 889, 667, 722, 722, 722, 722, 722, 675, 722, 722, 722, 722, 722, 556, 611, 500,
269    500, 889, 500, 276, 500, 500, 667, 444, 556, 722, 944, 310, 278, 278, 278, 278, 500, 667, 500,
270    500, 500, 278, 500, 675, 278, 500, 667, 500, 500, 444, 500, 444,
271];
272
273static TIMES_BOLD_ITALIC_WIDTHS: [u16; 224] = [
274    250, 389, 555, 500, 500, 833, 778, 333, 333, 333, 500, 570, 250, 333, 250, 278, 500, 500, 500,
275    500, 500, 500, 500, 500, 500, 500, 333, 333, 570, 570, 570, 500, 832, 667, 667, 667, 722, 667,
276    667, 722, 778, 389, 500, 667, 611, 889, 722, 722, 611, 722, 667, 556, 611, 722, 667, 889, 667,
277    611, 611, 333, 278, 333, 570, 500, 333, 500, 500, 444, 500, 444, 333, 500, 556, 278, 278, 500,
278    278, 778, 556, 500, 500, 500, 389, 389, 278, 556, 444, 667, 500, 444, 389, 348, 220, 348, 570,
279    0, 500, 0, 333, 500, 500, 1000, 500, 500, 333, 1000, 556, 333, 944, 0, 611, 0, 0, 333, 333,
280    500, 500, 350, 500, 1000, 333, 1000, 389, 333, 722, 0, 389, 611, 250, 389, 500, 500, 167, 500,
281    500, 500, 500, 278, 500, 500, 333, 333, 556, 556, 400, 500, 500, 500, 250, 576, 500, 350, 333,
282    500, 500, 500, 1000, 1000, 750, 500, 667, 333, 333, 333, 333, 333, 333, 333, 333, 667, 333,
283    333, 389, 333, 333, 333, 1000, 722, 722, 722, 722, 722, 722, 570, 722, 722, 722, 722, 722, 611,
284    611, 500, 500, 944, 500, 266, 500, 500, 722, 444, 611, 722, 944, 300, 278, 278, 278, 278, 500,
285    722, 500, 500, 500, 278, 500, 570, 278, 500, 722, 500, 556, 444, 500, 444,
286];
287
288static COURIER_WIDTHS: [u16; 224] = [
289    600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600,
290    600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600,
291    600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600,
292    600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600,
293    600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600,
294    0, 600, 0, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 0, 600, 0, 0, 600, 600, 600,
295    600, 600, 600, 600, 600, 600, 600, 600, 600, 0, 600, 600, 600, 600, 600, 600, 600, 600, 600,
296    600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600,
297    600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600,
298    600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600,
299    600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600,
300    600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600,
301];
302
303static COURIER_BOLD_WIDTHS: [u16; 224] = [
304    600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600,
305    600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600,
306    600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600,
307    600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600,
308    600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600,
309    0, 600, 0, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 0, 600, 0, 0, 600, 600, 600,
310    600, 600, 600, 600, 600, 600, 600, 600, 600, 0, 600, 600, 600, 600, 600, 600, 600, 600, 600,
311    600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600,
312    600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600,
313    600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600,
314    600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600,
315    600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600,
316];
317
318static COURIER_OBLIQUE_WIDTHS: [u16; 224] = [
319    600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600,
320    600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600,
321    600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600,
322    600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600,
323    600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600,
324    0, 600, 0, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 0, 600, 0, 0, 600, 600, 600,
325    600, 600, 600, 600, 600, 600, 600, 600, 600, 0, 600, 600, 600, 600, 600, 600, 600, 600, 600,
326    600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600,
327    600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600,
328    600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600,
329    600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600,
330    600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600,
331];
332
333static COURIER_BOLD_OBLIQUE_WIDTHS: [u16; 224] = [
334    600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600,
335    600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600,
336    600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600,
337    600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600,
338    600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600,
339    0, 600, 0, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 0, 600, 0, 0, 600, 600, 600,
340    600, 600, 600, 600, 600, 600, 600, 600, 600, 0, 600, 600, 600, 600, 600, 600, 600, 600, 600,
341    600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600,
342    600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600,
343    600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600,
344    600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600,
345    600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600, 600,
346];
347
348static SYMBOL_WIDTHS: [u16; 224] = [
349    250, 333, 713, 500, 549, 833, 778, 439, 333, 333, 500, 549, 250, 549, 250, 278, 500, 500, 500,
350    500, 500, 500, 500, 500, 500, 500, 278, 278, 549, 549, 549, 444, 549, 722, 667, 722, 612, 611,
351    763, 603, 722, 333, 631, 722, 686, 889, 722, 722, 768, 741, 556, 592, 611, 690, 439, 768, 645,
352    795, 611, 333, 863, 333, 658, 500, 500, 631, 549, 549, 494, 439, 521, 411, 603, 329, 603, 549,
353    549, 576, 521, 549, 549, 521, 549, 603, 439, 576, 713, 686, 493, 686, 494, 480, 200, 480, 549,
354    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
355    0, 750, 620, 247, 549, 167, 713, 500, 753, 753, 753, 753, 1042, 987, 603, 987, 603, 400, 549,
356    411, 549, 549, 713, 494, 460, 549, 549, 549, 549, 1000, 603, 1000, 658, 823, 686, 795, 987,
357    768, 768, 823, 768, 768, 713, 713, 713, 713, 713, 713, 713, 768, 713, 790, 790, 890, 823, 549,
358    250, 713, 603, 603, 1042, 987, 603, 987, 603, 494, 329, 790, 790, 786, 713, 384, 384, 384, 384,
359    384, 384, 494, 494, 494, 494, 0, 329, 274, 686, 686, 686, 384, 384, 384, 384, 384, 384, 494,
360    494, 494, 0,
361];
362
363static ZAPF_DINGBATS_WIDTHS: [u16; 224] = [
364    278, 974, 961, 974, 980, 719, 789, 790, 791, 690, 960, 939, 549, 855, 911, 933, 911, 945, 974,
365    755, 846, 762, 761, 571, 677, 763, 760, 759, 754, 494, 552, 537, 577, 692, 786, 788, 788, 790,
366    793, 794, 816, 823, 789, 841, 823, 833, 816, 831, 923, 744, 723, 749, 790, 792, 695, 776, 768,
367    792, 759, 707, 708, 682, 701, 826, 815, 789, 789, 707, 687, 696, 689, 786, 787, 713, 791, 785,
368    791, 873, 761, 762, 762, 759, 759, 892, 892, 788, 784, 438, 138, 277, 415, 392, 392, 668, 668,
369    0, 390, 390, 317, 317, 276, 276, 509, 509, 410, 410, 234, 234, 334, 334, 0, 0, 0, 0, 0, 0, 0,
370    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 732, 544, 544, 910, 667, 760, 760, 776, 595, 694, 626, 788,
371    788, 788, 788, 788, 788, 788, 788, 788, 788, 788, 788, 788, 788, 788, 788, 788, 788, 788, 788,
372    788, 788, 788, 788, 788, 788, 788, 788, 788, 788, 788, 788, 788, 788, 788, 788, 788, 788, 788,
373    788, 894, 838, 1016, 458, 748, 924, 748, 918, 927, 928, 928, 834, 873, 828, 924, 924, 917, 930,
374    931, 463, 883, 836, 836, 867, 867, 696, 696, 874, 0, 874, 760, 946, 771, 865, 771, 888, 967,
375    888, 831, 873, 927, 970, 918, 0,
376];
377
378#[cfg(test)]
379mod tests {
380    use super::*;
381
382    #[test]
383    fn test_helvetica_known_widths() {
384        let m = StandardFont::Helvetica.metrics();
385        // space = 278, A = 667, m = 833
386        assert_eq!(m.widths[0], 278); // space (code 32)
387        assert_eq!(m.widths[33], 667); // A (code 65)
388        assert_eq!(m.widths[77], 833); // m (code 109)
389    }
390
391    #[test]
392    fn test_helvetica_bold_wider() {
393        let regular = StandardFont::Helvetica.metrics();
394        let bold = StandardFont::HelveticaBold.metrics();
395        // Bold A = 722 > regular A = 667
396        assert_eq!(bold.widths[33], 722);
397        assert!(bold.widths[33] > regular.widths[33]);
398    }
399
400    #[test]
401    fn test_courier_monospaced() {
402        let m = StandardFont::Courier.metrics();
403        // All Courier glyphs are 600
404        for &w in m.widths.iter() {
405            if w > 0 {
406                assert_eq!(w, 600, "Courier should be monospaced");
407            }
408        }
409    }
410
411    #[test]
412    fn test_char_width_calculation() {
413        let m = StandardFont::Helvetica.metrics();
414        // space at 12pt: 278/1000 * 12 = 3.336
415        let w = m.char_width(' ', 12.0);
416        assert!((w - 3.336).abs() < 0.001);
417    }
418
419    #[test]
420    fn test_measure_string() {
421        let m = StandardFont::Helvetica.metrics();
422        let w = m.measure_string("A", 12.0, 0.0);
423        assert!((w - 8.004).abs() < 0.001); // 667/1000 * 12
424    }
425
426    #[test]
427    fn test_char_width_em_dash_uses_winansi() {
428        let m = StandardFont::Helvetica.metrics();
429        // Em dash (U+2014) maps to WinAnsi 0x97 = 151, index 151-32 = 119
430        // Helvetica em dash width = 1000
431        let w = m.char_width('\u{2014}', 10.0);
432        assert!(
433            (w - 10.0).abs() < 0.001,
434            "em dash should be 1000/1000 * 10 = 10.0, got {}",
435            w
436        );
437    }
438
439    #[test]
440    fn test_char_width_en_dash_uses_winansi() {
441        let m = StandardFont::Helvetica.metrics();
442        // En dash (U+2013) maps to WinAnsi 0x96 = 150, index 150-32 = 118
443        // Helvetica en dash width = 556
444        let w = m.char_width('\u{2013}', 10.0);
445        assert!(
446            (w - 5.56).abs() < 0.001,
447            "en dash should be 556/1000 * 10 = 5.56, got {}",
448            w
449        );
450    }
451
452    #[test]
453    fn test_unicode_to_winansi_mappings() {
454        assert_eq!(unicode_to_winansi('\u{2014}'), Some(0x97)); // Em dash
455        assert_eq!(unicode_to_winansi('\u{2013}'), Some(0x96)); // En dash
456        assert_eq!(unicode_to_winansi('\u{201C}'), Some(0x93)); // Left double quote
457        assert_eq!(unicode_to_winansi('\u{2026}'), Some(0x85)); // Ellipsis
458        assert_eq!(unicode_to_winansi('A'), Some(0x41)); // ASCII
459        assert_eq!(unicode_to_winansi('\u{4E00}'), None); // CJK — not in WinAnsi
460    }
461}