oxidize_pdf/text/
font.rs

1/// PDF font encoding types
2///
3/// Specifies how text characters are encoded in the PDF document.
4/// Different encodings support different character sets.
5#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
6pub enum FontEncoding {
7    /// WinAnsiEncoding - Windows ANSI encoding (CP1252)
8    /// Supports Western European characters, most common for standard fonts
9    WinAnsiEncoding,
10    /// MacRomanEncoding - Apple Macintosh Roman encoding
11    /// Legacy encoding for Macintosh systems
12    MacRomanEncoding,
13    /// StandardEncoding - Adobe Standard encoding
14    /// Basic ASCII plus some additional characters
15    StandardEncoding,
16    /// MacExpertEncoding - Macintosh Expert encoding
17    /// For expert typography with additional symbols
18    MacExpertEncoding,
19    /// Custom encoding specified by name
20    /// Use this for custom or non-standard encodings
21    Custom(&'static str),
22}
23
24impl FontEncoding {
25    /// Get the PDF name for this encoding
26    pub fn pdf_name(&self) -> &'static str {
27        match self {
28            FontEncoding::WinAnsiEncoding => "WinAnsiEncoding",
29            FontEncoding::MacRomanEncoding => "MacRomanEncoding",
30            FontEncoding::StandardEncoding => "StandardEncoding",
31            FontEncoding::MacExpertEncoding => "MacExpertEncoding",
32            FontEncoding::Custom(name) => name,
33        }
34    }
35
36    /// Get the recommended encoding for a specific font
37    /// Returns None if the font doesn't typically need explicit encoding
38    pub fn recommended_for_font(font: &Font) -> Option<Self> {
39        match font {
40            // Text fonts typically use WinAnsiEncoding for broad compatibility
41            Font::Helvetica
42            | Font::HelveticaBold
43            | Font::HelveticaOblique
44            | Font::HelveticaBoldOblique
45            | Font::TimesRoman
46            | Font::TimesBold
47            | Font::TimesItalic
48            | Font::TimesBoldItalic
49            | Font::Courier
50            | Font::CourierBold
51            | Font::CourierOblique
52            | Font::CourierBoldOblique => Some(FontEncoding::WinAnsiEncoding),
53            // Symbol fonts don't use text encodings
54            Font::Symbol | Font::ZapfDingbats => None,
55            // Custom fonts typically use Identity-H for full Unicode support
56            Font::Custom(_) => Some(FontEncoding::Custom("Identity-H")),
57        }
58    }
59}
60
61/// A font with optional encoding specification
62///
63/// This allows specifying encoding for fonts when needed, while maintaining
64/// backward compatibility with the simple Font enum.
65#[derive(Debug, Clone, PartialEq, Eq, Hash)]
66pub struct FontWithEncoding {
67    /// The font to use
68    pub font: Font,
69    /// Optional encoding specification
70    /// If None, no encoding will be set in the PDF (reader's default)
71    pub encoding: Option<FontEncoding>,
72}
73
74impl FontWithEncoding {
75    /// Create a new font with encoding
76    pub fn new(font: Font, encoding: Option<FontEncoding>) -> Self {
77        Self { font, encoding }
78    }
79
80    /// Create a font with recommended encoding
81    pub fn with_recommended_encoding(font: Font) -> Self {
82        Self {
83            font: font.clone(),
84            encoding: FontEncoding::recommended_for_font(&font),
85        }
86    }
87
88    /// Create a font with specific encoding
89    pub fn with_encoding(font: Font, encoding: FontEncoding) -> Self {
90        Self {
91            font,
92            encoding: Some(encoding),
93        }
94    }
95
96    /// Create a font without encoding (reader's default)
97    pub fn without_encoding(font: Font) -> Self {
98        Self {
99            font,
100            encoding: None,
101        }
102    }
103}
104
105// Implement From trait for easy conversion
106impl From<Font> for FontWithEncoding {
107    fn from(font: Font) -> Self {
108        Self::without_encoding(font)
109    }
110}
111
112/// PDF fonts - either standard Type 1 fonts or custom fonts.
113///
114/// Standard fonts are guaranteed to be available in all PDF readers
115/// and don't need to be embedded. Custom fonts must be loaded and embedded.
116#[derive(Debug, Clone, PartialEq, Eq, Hash)]
117pub enum Font {
118    // Standard 14 PDF fonts
119    /// Helvetica (sans-serif)
120    Helvetica,
121    /// Helvetica Bold
122    HelveticaBold,
123    /// Helvetica Oblique (italic)
124    HelveticaOblique,
125    /// Helvetica Bold Oblique
126    HelveticaBoldOblique,
127    /// Times Roman (serif)
128    TimesRoman,
129    /// Times Bold
130    TimesBold,
131    /// Times Italic
132    TimesItalic,
133    /// Times Bold Italic
134    TimesBoldItalic,
135    /// Courier (monospace)
136    Courier,
137    /// Courier Bold
138    CourierBold,
139    /// Courier Oblique
140    CourierOblique,
141    /// Courier Bold Oblique
142    CourierBoldOblique,
143    /// Symbol font (mathematical symbols)
144    Symbol,
145    /// ZapfDingbats (decorative symbols)
146    ZapfDingbats,
147    /// Custom font loaded from file or bytes
148    Custom(String),
149}
150
151impl Font {
152    /// Get the metrics for this font if it's a standard font
153    pub fn get_metrics(&self) -> Option<&'static crate::text::fonts::StandardFontMetrics> {
154        crate::text::fonts::get_standard_font_metrics(self)
155    }
156
157    /// Get the width of a character in font units (1000 units = 1 em)
158    pub fn get_char_width(&self, ch: u8) -> Option<i32> {
159        self.get_metrics().map(|m| m.get_char_width(ch))
160    }
161
162    /// Get the width of a string in user space units at the given font size
163    pub fn get_string_width(&self, text: &str, font_size: f64) -> Option<f64> {
164        self.get_metrics().map(|m| {
165            let width_units = m.get_string_width(text);
166            m.to_user_space(width_units, font_size)
167        })
168    }
169
170    /// Get the PDF name for this font
171    pub fn pdf_name(&self) -> String {
172        match self {
173            Font::Helvetica => "Helvetica".to_string(),
174            Font::HelveticaBold => "Helvetica-Bold".to_string(),
175            Font::HelveticaOblique => "Helvetica-Oblique".to_string(),
176            Font::HelveticaBoldOblique => "Helvetica-BoldOblique".to_string(),
177            Font::TimesRoman => "Times-Roman".to_string(),
178            Font::TimesBold => "Times-Bold".to_string(),
179            Font::TimesItalic => "Times-Italic".to_string(),
180            Font::TimesBoldItalic => "Times-BoldItalic".to_string(),
181            Font::Courier => "Courier".to_string(),
182            Font::CourierBold => "Courier-Bold".to_string(),
183            Font::CourierOblique => "Courier-Oblique".to_string(),
184            Font::CourierBoldOblique => "Courier-BoldOblique".to_string(),
185            Font::Symbol => "Symbol".to_string(),
186            Font::ZapfDingbats => "ZapfDingbats".to_string(),
187            Font::Custom(name) => name.clone(),
188        }
189    }
190
191    /// Check if this font is symbolic (doesn't use text encodings)
192    pub fn is_symbolic(&self) -> bool {
193        matches!(self, Font::Symbol | Font::ZapfDingbats)
194    }
195
196    /// Create this font with a specific encoding
197    pub fn with_encoding(self, encoding: FontEncoding) -> FontWithEncoding {
198        FontWithEncoding::with_encoding(self, encoding)
199    }
200
201    /// Create this font with recommended encoding
202    pub fn with_recommended_encoding(self) -> FontWithEncoding {
203        FontWithEncoding::with_recommended_encoding(self)
204    }
205
206    /// Create this font without explicit encoding
207    pub fn without_encoding(self) -> FontWithEncoding {
208        FontWithEncoding::without_encoding(self)
209    }
210
211    /// Check if this is a custom font
212    pub fn is_custom(&self) -> bool {
213        matches!(self, Font::Custom(_))
214    }
215
216    /// Create a custom font reference
217    pub fn custom(name: impl Into<String>) -> Self {
218        Font::Custom(name.into())
219    }
220}
221
222#[derive(Debug, Clone, Copy, PartialEq)]
223pub enum FontFamily {
224    Helvetica,
225    Times,
226    Courier,
227}
228
229impl FontFamily {
230    pub fn regular(self) -> Font {
231        match self {
232            FontFamily::Helvetica => Font::Helvetica,
233            FontFamily::Times => Font::TimesRoman,
234            FontFamily::Courier => Font::Courier,
235        }
236    }
237
238    pub fn bold(self) -> Font {
239        match self {
240            FontFamily::Helvetica => Font::HelveticaBold,
241            FontFamily::Times => Font::TimesBold,
242            FontFamily::Courier => Font::CourierBold,
243        }
244    }
245
246    pub fn italic(self) -> Font {
247        match self {
248            FontFamily::Helvetica => Font::HelveticaOblique,
249            FontFamily::Times => Font::TimesItalic,
250            FontFamily::Courier => Font::CourierOblique,
251        }
252    }
253
254    pub fn bold_italic(self) -> Font {
255        match self {
256            FontFamily::Helvetica => Font::HelveticaBoldOblique,
257            FontFamily::Times => Font::TimesBoldItalic,
258            FontFamily::Courier => Font::CourierBoldOblique,
259        }
260    }
261}
262
263#[cfg(test)]
264mod tests {
265    use super::*;
266
267    #[test]
268    fn test_font_pdf_names() {
269        assert_eq!(Font::Helvetica.pdf_name(), "Helvetica");
270        assert_eq!(Font::HelveticaBold.pdf_name(), "Helvetica-Bold");
271        assert_eq!(Font::HelveticaOblique.pdf_name(), "Helvetica-Oblique");
272        assert_eq!(
273            Font::HelveticaBoldOblique.pdf_name(),
274            "Helvetica-BoldOblique"
275        );
276
277        assert_eq!(Font::TimesRoman.pdf_name(), "Times-Roman");
278        assert_eq!(Font::TimesBold.pdf_name(), "Times-Bold");
279        assert_eq!(Font::TimesItalic.pdf_name(), "Times-Italic");
280        assert_eq!(Font::TimesBoldItalic.pdf_name(), "Times-BoldItalic");
281
282        assert_eq!(Font::Courier.pdf_name(), "Courier");
283        assert_eq!(Font::CourierBold.pdf_name(), "Courier-Bold");
284        assert_eq!(Font::CourierOblique.pdf_name(), "Courier-Oblique");
285        assert_eq!(Font::CourierBoldOblique.pdf_name(), "Courier-BoldOblique");
286
287        assert_eq!(Font::Symbol.pdf_name(), "Symbol");
288        assert_eq!(Font::ZapfDingbats.pdf_name(), "ZapfDingbats");
289    }
290
291    #[test]
292    fn test_font_is_symbolic() {
293        assert!(!Font::Helvetica.is_symbolic());
294        assert!(!Font::HelveticaBold.is_symbolic());
295        assert!(!Font::TimesRoman.is_symbolic());
296        assert!(!Font::Courier.is_symbolic());
297
298        assert!(Font::Symbol.is_symbolic());
299        assert!(Font::ZapfDingbats.is_symbolic());
300    }
301
302    #[test]
303    fn test_font_equality() {
304        assert_eq!(Font::Helvetica, Font::Helvetica);
305        assert_ne!(Font::Helvetica, Font::HelveticaBold);
306        assert_ne!(Font::TimesRoman, Font::TimesBold);
307    }
308
309    #[test]
310    fn test_font_debug() {
311        let font = Font::HelveticaBold;
312        let debug_str = format!("{font:?}");
313        assert_eq!(debug_str, "HelveticaBold");
314    }
315
316    #[test]
317    fn test_font_clone() {
318        let font1 = Font::TimesItalic;
319        let font2 = font1.clone();
320        assert_eq!(font1, font2);
321    }
322
323    #[test]
324    fn test_font_hash() {
325        use std::collections::HashSet;
326
327        let mut fonts = HashSet::new();
328        fonts.insert(Font::Helvetica);
329        fonts.insert(Font::HelveticaBold);
330        fonts.insert(Font::Helvetica); // Duplicate
331
332        assert_eq!(fonts.len(), 2);
333        assert!(fonts.contains(&Font::Helvetica));
334        assert!(fonts.contains(&Font::HelveticaBold));
335        assert!(!fonts.contains(&Font::TimesRoman));
336    }
337
338    #[test]
339    fn test_font_family_regular() {
340        assert_eq!(FontFamily::Helvetica.regular(), Font::Helvetica);
341        assert_eq!(FontFamily::Times.regular(), Font::TimesRoman);
342        assert_eq!(FontFamily::Courier.regular(), Font::Courier);
343    }
344
345    #[test]
346    fn test_font_family_bold() {
347        assert_eq!(FontFamily::Helvetica.bold(), Font::HelveticaBold);
348        assert_eq!(FontFamily::Times.bold(), Font::TimesBold);
349        assert_eq!(FontFamily::Courier.bold(), Font::CourierBold);
350    }
351
352    #[test]
353    fn test_font_family_italic() {
354        assert_eq!(FontFamily::Helvetica.italic(), Font::HelveticaOblique);
355        assert_eq!(FontFamily::Times.italic(), Font::TimesItalic);
356        assert_eq!(FontFamily::Courier.italic(), Font::CourierOblique);
357    }
358
359    #[test]
360    fn test_font_family_bold_italic() {
361        assert_eq!(
362            FontFamily::Helvetica.bold_italic(),
363            Font::HelveticaBoldOblique
364        );
365        assert_eq!(FontFamily::Times.bold_italic(), Font::TimesBoldItalic);
366        assert_eq!(FontFamily::Courier.bold_italic(), Font::CourierBoldOblique);
367    }
368
369    #[test]
370    fn test_font_family_equality() {
371        assert_eq!(FontFamily::Helvetica, FontFamily::Helvetica);
372        assert_ne!(FontFamily::Helvetica, FontFamily::Times);
373        assert_ne!(FontFamily::Times, FontFamily::Courier);
374    }
375
376    #[test]
377    fn test_font_family_debug() {
378        let family = FontFamily::Times;
379        let debug_str = format!("{family:?}");
380        assert_eq!(debug_str, "Times");
381    }
382
383    #[test]
384    fn test_font_family_clone() {
385        let family1 = FontFamily::Courier;
386        let family2 = family1;
387        assert_eq!(family1, family2);
388    }
389
390    #[test]
391    fn test_font_family_copy() {
392        let family1 = FontFamily::Helvetica;
393        let family2 = family1; // Copy semantics
394        assert_eq!(family1, family2);
395
396        // Both variables should still be usable
397        assert_eq!(family1, FontFamily::Helvetica);
398        assert_eq!(family2, FontFamily::Helvetica);
399    }
400
401    #[test]
402    fn test_all_helvetica_variants() {
403        let helvetica = FontFamily::Helvetica;
404
405        assert_eq!(helvetica.regular(), Font::Helvetica);
406        assert_eq!(helvetica.bold(), Font::HelveticaBold);
407        assert_eq!(helvetica.italic(), Font::HelveticaOblique);
408        assert_eq!(helvetica.bold_italic(), Font::HelveticaBoldOblique);
409    }
410
411    #[test]
412    fn test_all_times_variants() {
413        let times = FontFamily::Times;
414
415        assert_eq!(times.regular(), Font::TimesRoman);
416        assert_eq!(times.bold(), Font::TimesBold);
417        assert_eq!(times.italic(), Font::TimesItalic);
418        assert_eq!(times.bold_italic(), Font::TimesBoldItalic);
419    }
420
421    #[test]
422    fn test_all_courier_variants() {
423        let courier = FontFamily::Courier;
424
425        assert_eq!(courier.regular(), Font::Courier);
426        assert_eq!(courier.bold(), Font::CourierBold);
427        assert_eq!(courier.italic(), Font::CourierOblique);
428        assert_eq!(courier.bold_italic(), Font::CourierBoldOblique);
429    }
430
431    // FontEncoding tests
432
433    #[test]
434    fn test_font_encoding_pdf_names() {
435        assert_eq!(FontEncoding::WinAnsiEncoding.pdf_name(), "WinAnsiEncoding");
436        assert_eq!(
437            FontEncoding::MacRomanEncoding.pdf_name(),
438            "MacRomanEncoding"
439        );
440        assert_eq!(
441            FontEncoding::StandardEncoding.pdf_name(),
442            "StandardEncoding"
443        );
444        assert_eq!(
445            FontEncoding::MacExpertEncoding.pdf_name(),
446            "MacExpertEncoding"
447        );
448        assert_eq!(FontEncoding::Custom("MyEncoding").pdf_name(), "MyEncoding");
449    }
450
451    #[test]
452    fn test_font_encoding_recommended_for_font() {
453        // Text fonts should have recommended encoding
454        assert_eq!(
455            FontEncoding::recommended_for_font(&Font::Helvetica),
456            Some(FontEncoding::WinAnsiEncoding)
457        );
458        assert_eq!(
459            FontEncoding::recommended_for_font(&Font::TimesRoman),
460            Some(FontEncoding::WinAnsiEncoding)
461        );
462        assert_eq!(
463            FontEncoding::recommended_for_font(&Font::CourierBold),
464            Some(FontEncoding::WinAnsiEncoding)
465        );
466
467        // Symbol fonts should not have recommended encoding
468        assert_eq!(FontEncoding::recommended_for_font(&Font::Symbol), None);
469        assert_eq!(
470            FontEncoding::recommended_for_font(&Font::ZapfDingbats),
471            None
472        );
473    }
474
475    #[test]
476    fn test_font_encoding_equality() {
477        assert_eq!(FontEncoding::WinAnsiEncoding, FontEncoding::WinAnsiEncoding);
478        assert_ne!(
479            FontEncoding::WinAnsiEncoding,
480            FontEncoding::MacRomanEncoding
481        );
482        assert_eq!(FontEncoding::Custom("Test"), FontEncoding::Custom("Test"));
483        assert_ne!(FontEncoding::Custom("Test1"), FontEncoding::Custom("Test2"));
484    }
485
486    // FontWithEncoding tests
487
488    #[test]
489    fn test_font_with_encoding_new() {
490        let font_enc = FontWithEncoding::new(Font::Helvetica, Some(FontEncoding::WinAnsiEncoding));
491        assert_eq!(font_enc.font, Font::Helvetica);
492        assert_eq!(font_enc.encoding, Some(FontEncoding::WinAnsiEncoding));
493
494        let font_no_enc = FontWithEncoding::new(Font::Symbol, None);
495        assert_eq!(font_no_enc.font, Font::Symbol);
496        assert_eq!(font_no_enc.encoding, None);
497    }
498
499    #[test]
500    fn test_font_with_encoding_with_recommended() {
501        let helvetica = FontWithEncoding::with_recommended_encoding(Font::Helvetica);
502        assert_eq!(helvetica.font, Font::Helvetica);
503        assert_eq!(helvetica.encoding, Some(FontEncoding::WinAnsiEncoding));
504
505        let symbol = FontWithEncoding::with_recommended_encoding(Font::Symbol);
506        assert_eq!(symbol.font, Font::Symbol);
507        assert_eq!(symbol.encoding, None);
508    }
509
510    #[test]
511    fn test_font_with_encoding_with_specific() {
512        let font_enc =
513            FontWithEncoding::with_encoding(Font::TimesRoman, FontEncoding::MacRomanEncoding);
514        assert_eq!(font_enc.font, Font::TimesRoman);
515        assert_eq!(font_enc.encoding, Some(FontEncoding::MacRomanEncoding));
516    }
517
518    #[test]
519    fn test_font_with_encoding_without_encoding() {
520        let font_no_enc = FontWithEncoding::without_encoding(Font::Courier);
521        assert_eq!(font_no_enc.font, Font::Courier);
522        assert_eq!(font_no_enc.encoding, None);
523    }
524
525    #[test]
526    fn test_font_with_encoding_from_font() {
527        let font_enc: FontWithEncoding = Font::HelveticaBold.into();
528        assert_eq!(font_enc.font, Font::HelveticaBold);
529        assert_eq!(font_enc.encoding, None);
530    }
531
532    #[test]
533    fn test_font_convenience_methods() {
534        let helvetica_with_enc = Font::Helvetica.with_encoding(FontEncoding::MacRomanEncoding);
535        assert_eq!(helvetica_with_enc.font, Font::Helvetica);
536        assert_eq!(
537            helvetica_with_enc.encoding,
538            Some(FontEncoding::MacRomanEncoding)
539        );
540
541        let times_recommended = Font::TimesRoman.with_recommended_encoding();
542        assert_eq!(times_recommended.font, Font::TimesRoman);
543        assert_eq!(
544            times_recommended.encoding,
545            Some(FontEncoding::WinAnsiEncoding)
546        );
547
548        let courier_no_enc = Font::Courier.without_encoding();
549        assert_eq!(courier_no_enc.font, Font::Courier);
550        assert_eq!(courier_no_enc.encoding, None);
551    }
552
553    #[test]
554    fn test_font_with_encoding_equality() {
555        let font1 = FontWithEncoding::with_encoding(Font::Helvetica, FontEncoding::WinAnsiEncoding);
556        let font2 = FontWithEncoding::with_encoding(Font::Helvetica, FontEncoding::WinAnsiEncoding);
557        let font3 =
558            FontWithEncoding::with_encoding(Font::Helvetica, FontEncoding::MacRomanEncoding);
559        let font4 =
560            FontWithEncoding::with_encoding(Font::TimesRoman, FontEncoding::WinAnsiEncoding);
561
562        assert_eq!(font1, font2);
563        assert_ne!(font1, font3);
564        assert_ne!(font1, font4);
565    }
566
567    #[test]
568    fn test_font_with_encoding_debug() {
569        let font_enc =
570            FontWithEncoding::with_encoding(Font::Helvetica, FontEncoding::WinAnsiEncoding);
571        let debug_str = format!("{font_enc:?}");
572        assert!(debug_str.contains("Helvetica"));
573        assert!(debug_str.contains("WinAnsiEncoding"));
574    }
575
576    #[test]
577    fn test_font_with_encoding_clone() {
578        let font1 =
579            FontWithEncoding::with_encoding(Font::TimesRoman, FontEncoding::StandardEncoding);
580        let font2 = font1.clone();
581        assert_eq!(font1, font2);
582    }
583
584    #[test]
585    fn test_font_with_encoding_copy() {
586        let font1 = FontWithEncoding::with_encoding(Font::Courier, FontEncoding::WinAnsiEncoding);
587        let font2 = font1.clone(); // Clone instead of Copy
588        assert_eq!(font1, font2);
589
590        // Both variables should still be usable
591        assert_eq!(font1.font, Font::Courier);
592        assert_eq!(font2.font, Font::Courier);
593    }
594
595    #[test]
596    fn test_custom_encoding() {
597        let custom_enc = FontEncoding::Custom("MyCustomEncoding");
598        assert_eq!(custom_enc.pdf_name(), "MyCustomEncoding");
599
600        let font_with_custom = FontWithEncoding::with_encoding(Font::Helvetica, custom_enc);
601        assert_eq!(font_with_custom.encoding, Some(custom_enc));
602    }
603}