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 PDF name for this font
153    pub fn pdf_name(&self) -> String {
154        match self {
155            Font::Helvetica => "Helvetica".to_string(),
156            Font::HelveticaBold => "Helvetica-Bold".to_string(),
157            Font::HelveticaOblique => "Helvetica-Oblique".to_string(),
158            Font::HelveticaBoldOblique => "Helvetica-BoldOblique".to_string(),
159            Font::TimesRoman => "Times-Roman".to_string(),
160            Font::TimesBold => "Times-Bold".to_string(),
161            Font::TimesItalic => "Times-Italic".to_string(),
162            Font::TimesBoldItalic => "Times-BoldItalic".to_string(),
163            Font::Courier => "Courier".to_string(),
164            Font::CourierBold => "Courier-Bold".to_string(),
165            Font::CourierOblique => "Courier-Oblique".to_string(),
166            Font::CourierBoldOblique => "Courier-BoldOblique".to_string(),
167            Font::Symbol => "Symbol".to_string(),
168            Font::ZapfDingbats => "ZapfDingbats".to_string(),
169            Font::Custom(name) => name.clone(),
170        }
171    }
172
173    /// Check if this font is symbolic (doesn't use text encodings)
174    pub fn is_symbolic(&self) -> bool {
175        matches!(self, Font::Symbol | Font::ZapfDingbats)
176    }
177
178    /// Create this font with a specific encoding
179    pub fn with_encoding(self, encoding: FontEncoding) -> FontWithEncoding {
180        FontWithEncoding::with_encoding(self, encoding)
181    }
182
183    /// Create this font with recommended encoding
184    pub fn with_recommended_encoding(self) -> FontWithEncoding {
185        FontWithEncoding::with_recommended_encoding(self)
186    }
187
188    /// Create this font without explicit encoding
189    pub fn without_encoding(self) -> FontWithEncoding {
190        FontWithEncoding::without_encoding(self)
191    }
192
193    /// Check if this is a custom font
194    pub fn is_custom(&self) -> bool {
195        matches!(self, Font::Custom(_))
196    }
197
198    /// Create a custom font reference
199    pub fn custom(name: impl Into<String>) -> Self {
200        Font::Custom(name.into())
201    }
202}
203
204#[derive(Debug, Clone, Copy, PartialEq)]
205pub enum FontFamily {
206    Helvetica,
207    Times,
208    Courier,
209}
210
211impl FontFamily {
212    pub fn regular(self) -> Font {
213        match self {
214            FontFamily::Helvetica => Font::Helvetica,
215            FontFamily::Times => Font::TimesRoman,
216            FontFamily::Courier => Font::Courier,
217        }
218    }
219
220    pub fn bold(self) -> Font {
221        match self {
222            FontFamily::Helvetica => Font::HelveticaBold,
223            FontFamily::Times => Font::TimesBold,
224            FontFamily::Courier => Font::CourierBold,
225        }
226    }
227
228    pub fn italic(self) -> Font {
229        match self {
230            FontFamily::Helvetica => Font::HelveticaOblique,
231            FontFamily::Times => Font::TimesItalic,
232            FontFamily::Courier => Font::CourierOblique,
233        }
234    }
235
236    pub fn bold_italic(self) -> Font {
237        match self {
238            FontFamily::Helvetica => Font::HelveticaBoldOblique,
239            FontFamily::Times => Font::TimesBoldItalic,
240            FontFamily::Courier => Font::CourierBoldOblique,
241        }
242    }
243}
244
245#[cfg(test)]
246mod tests {
247    use super::*;
248
249    #[test]
250    fn test_font_pdf_names() {
251        assert_eq!(Font::Helvetica.pdf_name(), "Helvetica");
252        assert_eq!(Font::HelveticaBold.pdf_name(), "Helvetica-Bold");
253        assert_eq!(Font::HelveticaOblique.pdf_name(), "Helvetica-Oblique");
254        assert_eq!(
255            Font::HelveticaBoldOblique.pdf_name(),
256            "Helvetica-BoldOblique"
257        );
258
259        assert_eq!(Font::TimesRoman.pdf_name(), "Times-Roman");
260        assert_eq!(Font::TimesBold.pdf_name(), "Times-Bold");
261        assert_eq!(Font::TimesItalic.pdf_name(), "Times-Italic");
262        assert_eq!(Font::TimesBoldItalic.pdf_name(), "Times-BoldItalic");
263
264        assert_eq!(Font::Courier.pdf_name(), "Courier");
265        assert_eq!(Font::CourierBold.pdf_name(), "Courier-Bold");
266        assert_eq!(Font::CourierOblique.pdf_name(), "Courier-Oblique");
267        assert_eq!(Font::CourierBoldOblique.pdf_name(), "Courier-BoldOblique");
268
269        assert_eq!(Font::Symbol.pdf_name(), "Symbol");
270        assert_eq!(Font::ZapfDingbats.pdf_name(), "ZapfDingbats");
271    }
272
273    #[test]
274    fn test_font_is_symbolic() {
275        assert!(!Font::Helvetica.is_symbolic());
276        assert!(!Font::HelveticaBold.is_symbolic());
277        assert!(!Font::TimesRoman.is_symbolic());
278        assert!(!Font::Courier.is_symbolic());
279
280        assert!(Font::Symbol.is_symbolic());
281        assert!(Font::ZapfDingbats.is_symbolic());
282    }
283
284    #[test]
285    fn test_font_equality() {
286        assert_eq!(Font::Helvetica, Font::Helvetica);
287        assert_ne!(Font::Helvetica, Font::HelveticaBold);
288        assert_ne!(Font::TimesRoman, Font::TimesBold);
289    }
290
291    #[test]
292    fn test_font_debug() {
293        let font = Font::HelveticaBold;
294        let debug_str = format!("{:?}", font);
295        assert_eq!(debug_str, "HelveticaBold");
296    }
297
298    #[test]
299    fn test_font_clone() {
300        let font1 = Font::TimesItalic;
301        let font2 = font1.clone();
302        assert_eq!(font1, font2);
303    }
304
305    #[test]
306    fn test_font_hash() {
307        use std::collections::HashSet;
308
309        let mut fonts = HashSet::new();
310        fonts.insert(Font::Helvetica);
311        fonts.insert(Font::HelveticaBold);
312        fonts.insert(Font::Helvetica); // Duplicate
313
314        assert_eq!(fonts.len(), 2);
315        assert!(fonts.contains(&Font::Helvetica));
316        assert!(fonts.contains(&Font::HelveticaBold));
317        assert!(!fonts.contains(&Font::TimesRoman));
318    }
319
320    #[test]
321    fn test_font_family_regular() {
322        assert_eq!(FontFamily::Helvetica.regular(), Font::Helvetica);
323        assert_eq!(FontFamily::Times.regular(), Font::TimesRoman);
324        assert_eq!(FontFamily::Courier.regular(), Font::Courier);
325    }
326
327    #[test]
328    fn test_font_family_bold() {
329        assert_eq!(FontFamily::Helvetica.bold(), Font::HelveticaBold);
330        assert_eq!(FontFamily::Times.bold(), Font::TimesBold);
331        assert_eq!(FontFamily::Courier.bold(), Font::CourierBold);
332    }
333
334    #[test]
335    fn test_font_family_italic() {
336        assert_eq!(FontFamily::Helvetica.italic(), Font::HelveticaOblique);
337        assert_eq!(FontFamily::Times.italic(), Font::TimesItalic);
338        assert_eq!(FontFamily::Courier.italic(), Font::CourierOblique);
339    }
340
341    #[test]
342    fn test_font_family_bold_italic() {
343        assert_eq!(
344            FontFamily::Helvetica.bold_italic(),
345            Font::HelveticaBoldOblique
346        );
347        assert_eq!(FontFamily::Times.bold_italic(), Font::TimesBoldItalic);
348        assert_eq!(FontFamily::Courier.bold_italic(), Font::CourierBoldOblique);
349    }
350
351    #[test]
352    fn test_font_family_equality() {
353        assert_eq!(FontFamily::Helvetica, FontFamily::Helvetica);
354        assert_ne!(FontFamily::Helvetica, FontFamily::Times);
355        assert_ne!(FontFamily::Times, FontFamily::Courier);
356    }
357
358    #[test]
359    fn test_font_family_debug() {
360        let family = FontFamily::Times;
361        let debug_str = format!("{:?}", family);
362        assert_eq!(debug_str, "Times");
363    }
364
365    #[test]
366    fn test_font_family_clone() {
367        let family1 = FontFamily::Courier;
368        let family2 = family1;
369        assert_eq!(family1, family2);
370    }
371
372    #[test]
373    fn test_font_family_copy() {
374        let family1 = FontFamily::Helvetica;
375        let family2 = family1; // Copy semantics
376        assert_eq!(family1, family2);
377
378        // Both variables should still be usable
379        assert_eq!(family1, FontFamily::Helvetica);
380        assert_eq!(family2, FontFamily::Helvetica);
381    }
382
383    #[test]
384    fn test_all_helvetica_variants() {
385        let helvetica = FontFamily::Helvetica;
386
387        assert_eq!(helvetica.regular(), Font::Helvetica);
388        assert_eq!(helvetica.bold(), Font::HelveticaBold);
389        assert_eq!(helvetica.italic(), Font::HelveticaOblique);
390        assert_eq!(helvetica.bold_italic(), Font::HelveticaBoldOblique);
391    }
392
393    #[test]
394    fn test_all_times_variants() {
395        let times = FontFamily::Times;
396
397        assert_eq!(times.regular(), Font::TimesRoman);
398        assert_eq!(times.bold(), Font::TimesBold);
399        assert_eq!(times.italic(), Font::TimesItalic);
400        assert_eq!(times.bold_italic(), Font::TimesBoldItalic);
401    }
402
403    #[test]
404    fn test_all_courier_variants() {
405        let courier = FontFamily::Courier;
406
407        assert_eq!(courier.regular(), Font::Courier);
408        assert_eq!(courier.bold(), Font::CourierBold);
409        assert_eq!(courier.italic(), Font::CourierOblique);
410        assert_eq!(courier.bold_italic(), Font::CourierBoldOblique);
411    }
412
413    // FontEncoding tests
414
415    #[test]
416    fn test_font_encoding_pdf_names() {
417        assert_eq!(FontEncoding::WinAnsiEncoding.pdf_name(), "WinAnsiEncoding");
418        assert_eq!(
419            FontEncoding::MacRomanEncoding.pdf_name(),
420            "MacRomanEncoding"
421        );
422        assert_eq!(
423            FontEncoding::StandardEncoding.pdf_name(),
424            "StandardEncoding"
425        );
426        assert_eq!(
427            FontEncoding::MacExpertEncoding.pdf_name(),
428            "MacExpertEncoding"
429        );
430        assert_eq!(FontEncoding::Custom("MyEncoding").pdf_name(), "MyEncoding");
431    }
432
433    #[test]
434    fn test_font_encoding_recommended_for_font() {
435        // Text fonts should have recommended encoding
436        assert_eq!(
437            FontEncoding::recommended_for_font(&Font::Helvetica),
438            Some(FontEncoding::WinAnsiEncoding)
439        );
440        assert_eq!(
441            FontEncoding::recommended_for_font(&Font::TimesRoman),
442            Some(FontEncoding::WinAnsiEncoding)
443        );
444        assert_eq!(
445            FontEncoding::recommended_for_font(&Font::CourierBold),
446            Some(FontEncoding::WinAnsiEncoding)
447        );
448
449        // Symbol fonts should not have recommended encoding
450        assert_eq!(FontEncoding::recommended_for_font(&Font::Symbol), None);
451        assert_eq!(
452            FontEncoding::recommended_for_font(&Font::ZapfDingbats),
453            None
454        );
455    }
456
457    #[test]
458    fn test_font_encoding_equality() {
459        assert_eq!(FontEncoding::WinAnsiEncoding, FontEncoding::WinAnsiEncoding);
460        assert_ne!(
461            FontEncoding::WinAnsiEncoding,
462            FontEncoding::MacRomanEncoding
463        );
464        assert_eq!(FontEncoding::Custom("Test"), FontEncoding::Custom("Test"));
465        assert_ne!(FontEncoding::Custom("Test1"), FontEncoding::Custom("Test2"));
466    }
467
468    // FontWithEncoding tests
469
470    #[test]
471    fn test_font_with_encoding_new() {
472        let font_enc = FontWithEncoding::new(Font::Helvetica, Some(FontEncoding::WinAnsiEncoding));
473        assert_eq!(font_enc.font, Font::Helvetica);
474        assert_eq!(font_enc.encoding, Some(FontEncoding::WinAnsiEncoding));
475
476        let font_no_enc = FontWithEncoding::new(Font::Symbol, None);
477        assert_eq!(font_no_enc.font, Font::Symbol);
478        assert_eq!(font_no_enc.encoding, None);
479    }
480
481    #[test]
482    fn test_font_with_encoding_with_recommended() {
483        let helvetica = FontWithEncoding::with_recommended_encoding(Font::Helvetica);
484        assert_eq!(helvetica.font, Font::Helvetica);
485        assert_eq!(helvetica.encoding, Some(FontEncoding::WinAnsiEncoding));
486
487        let symbol = FontWithEncoding::with_recommended_encoding(Font::Symbol);
488        assert_eq!(symbol.font, Font::Symbol);
489        assert_eq!(symbol.encoding, None);
490    }
491
492    #[test]
493    fn test_font_with_encoding_with_specific() {
494        let font_enc =
495            FontWithEncoding::with_encoding(Font::TimesRoman, FontEncoding::MacRomanEncoding);
496        assert_eq!(font_enc.font, Font::TimesRoman);
497        assert_eq!(font_enc.encoding, Some(FontEncoding::MacRomanEncoding));
498    }
499
500    #[test]
501    fn test_font_with_encoding_without_encoding() {
502        let font_no_enc = FontWithEncoding::without_encoding(Font::Courier);
503        assert_eq!(font_no_enc.font, Font::Courier);
504        assert_eq!(font_no_enc.encoding, None);
505    }
506
507    #[test]
508    fn test_font_with_encoding_from_font() {
509        let font_enc: FontWithEncoding = Font::HelveticaBold.into();
510        assert_eq!(font_enc.font, Font::HelveticaBold);
511        assert_eq!(font_enc.encoding, None);
512    }
513
514    #[test]
515    fn test_font_convenience_methods() {
516        let helvetica_with_enc = Font::Helvetica.with_encoding(FontEncoding::MacRomanEncoding);
517        assert_eq!(helvetica_with_enc.font, Font::Helvetica);
518        assert_eq!(
519            helvetica_with_enc.encoding,
520            Some(FontEncoding::MacRomanEncoding)
521        );
522
523        let times_recommended = Font::TimesRoman.with_recommended_encoding();
524        assert_eq!(times_recommended.font, Font::TimesRoman);
525        assert_eq!(
526            times_recommended.encoding,
527            Some(FontEncoding::WinAnsiEncoding)
528        );
529
530        let courier_no_enc = Font::Courier.without_encoding();
531        assert_eq!(courier_no_enc.font, Font::Courier);
532        assert_eq!(courier_no_enc.encoding, None);
533    }
534
535    #[test]
536    fn test_font_with_encoding_equality() {
537        let font1 = FontWithEncoding::with_encoding(Font::Helvetica, FontEncoding::WinAnsiEncoding);
538        let font2 = FontWithEncoding::with_encoding(Font::Helvetica, FontEncoding::WinAnsiEncoding);
539        let font3 =
540            FontWithEncoding::with_encoding(Font::Helvetica, FontEncoding::MacRomanEncoding);
541        let font4 =
542            FontWithEncoding::with_encoding(Font::TimesRoman, FontEncoding::WinAnsiEncoding);
543
544        assert_eq!(font1, font2);
545        assert_ne!(font1, font3);
546        assert_ne!(font1, font4);
547    }
548
549    #[test]
550    fn test_font_with_encoding_debug() {
551        let font_enc =
552            FontWithEncoding::with_encoding(Font::Helvetica, FontEncoding::WinAnsiEncoding);
553        let debug_str = format!("{:?}", font_enc);
554        assert!(debug_str.contains("Helvetica"));
555        assert!(debug_str.contains("WinAnsiEncoding"));
556    }
557
558    #[test]
559    fn test_font_with_encoding_clone() {
560        let font1 =
561            FontWithEncoding::with_encoding(Font::TimesRoman, FontEncoding::StandardEncoding);
562        let font2 = font1.clone();
563        assert_eq!(font1, font2);
564    }
565
566    #[test]
567    fn test_font_with_encoding_copy() {
568        let font1 = FontWithEncoding::with_encoding(Font::Courier, FontEncoding::WinAnsiEncoding);
569        let font2 = font1.clone(); // Clone instead of Copy
570        assert_eq!(font1, font2);
571
572        // Both variables should still be usable
573        assert_eq!(font1.font, Font::Courier);
574        assert_eq!(font2.font, Font::Courier);
575    }
576
577    #[test]
578    fn test_custom_encoding() {
579        let custom_enc = FontEncoding::Custom("MyCustomEncoding");
580        assert_eq!(custom_enc.pdf_name(), "MyCustomEncoding");
581
582        let font_with_custom = FontWithEncoding::with_encoding(Font::Helvetica, custom_enc);
583        assert_eq!(font_with_custom.encoding, Some(custom_enc));
584    }
585}