Skip to main content

pdfium_render/pdf/
font.rs

1//! Defines the [PdfFont] struct, exposing functionality related to a single font used to
2//! render text in a `PdfDocument`.
3
4pub mod glyph;
5pub mod glyphs;
6
7use crate::bindgen::FPDF_FONT;
8use crate::error::{PdfiumError, PdfiumInternalError};
9use crate::pdf::document::fonts::PdfFontBuiltin;
10use crate::pdf::font::glyphs::PdfFontGlyphs;
11use crate::pdf::points::PdfPoints;
12use crate::pdfium::PdfiumLibraryBindingsAccessor;
13use crate::utils::mem::create_byte_buffer;
14use bitflags::bitflags;
15use std::marker::PhantomData;
16use std::os::raw::{c_char, c_int};
17
18#[cfg(doc)]
19use crate::pdf::document::PdfDocument;
20
21// The following dummy declaration is used only when running cargo doc.
22// It allows documentation of WASM-specific functionality to be included
23// in documentation generated on non-WASM targets.
24
25#[cfg(doc)]
26struct Blob;
27
28bitflags! {
29    pub(crate) struct FpdfFontDescriptorFlags: u32 {
30        const FIXED_PITCH_BIT_1 =  0b00000000000000000000000000000001;
31        const SERIF_BIT_2 =        0b00000000000000000000000000000010;
32        const SYMBOLIC_BIT_3 =     0b00000000000000000000000000000100;
33        const SCRIPT_BIT_4 =       0b00000000000000000000000000001000;
34        const NON_SYMBOLIC_BIT_6 = 0b00000000000000000000000000100000;
35        const ITALIC_BIT_7 =       0b00000000000000000000000001000000;
36        const ALL_CAP_BIT_17 =     0b00000000000000010000000000000000;
37        const SMALL_CAP_BIT_18 =   0b00000000000000100000000000000000;
38        const FORCE_BOLD_BIT_19 =  0b00000000000001000000000000000000;
39    }
40}
41
42/// The weight of a [PdfFont]. Typical values are 400 (normal) and 700 (bold).
43#[derive(Copy, Clone, Debug, PartialEq)]
44pub enum PdfFontWeight {
45    Weight100,
46    Weight200,
47    Weight300,
48    Weight400Normal,
49    Weight500,
50    Weight600,
51    Weight700Bold,
52    Weight800,
53    Weight900,
54
55    /// Any font weight value that falls outside the typical 100 - 900 value range.
56    Custom(u32),
57}
58
59impl PdfFontWeight {
60    pub(crate) fn from_pdfium(value: c_int) -> Option<PdfFontWeight> {
61        match value {
62            -1 => None,
63            100 => Some(PdfFontWeight::Weight100),
64            200 => Some(PdfFontWeight::Weight200),
65            300 => Some(PdfFontWeight::Weight300),
66            400 => Some(PdfFontWeight::Weight400Normal),
67            500 => Some(PdfFontWeight::Weight500),
68            600 => Some(PdfFontWeight::Weight600),
69            700 => Some(PdfFontWeight::Weight700Bold),
70            800 => Some(PdfFontWeight::Weight800),
71            900 => Some(PdfFontWeight::Weight900),
72            other => Some(PdfFontWeight::Custom(other as u32)),
73        }
74    }
75}
76
77/// A single font used to render text in a [PdfDocument].
78///
79/// The PDF specification defines 14 built-in fonts that can be used in any PDF file without
80/// font embedding. Additionally, custom fonts can be directly embedded into any PDF file as
81/// a data stream.
82pub struct PdfFont<'a> {
83    built_in: Option<PdfFontBuiltin>,
84    handle: FPDF_FONT,
85    glyphs: PdfFontGlyphs<'a>,
86    is_font_memory_loaded: bool,
87    lifetime: PhantomData<&'a FPDF_FONT>,
88}
89
90impl<'a> PdfFont<'a> {
91    #[inline]
92    pub(crate) fn from_pdfium(
93        handle: FPDF_FONT,
94        built_in: Option<PdfFontBuiltin>,
95        is_font_memory_loaded: bool,
96    ) -> Self {
97        PdfFont {
98            built_in,
99            handle,
100            glyphs: PdfFontGlyphs::from_pdfium(handle),
101            is_font_memory_loaded,
102            lifetime: PhantomData,
103        }
104    }
105
106    /// Returns the internal `FPDF_FONT` handle for this [PdfFont].
107    #[inline]
108    pub(crate) fn handle(&self) -> FPDF_FONT {
109        self.handle
110    }
111
112    #[cfg(any(
113        feature = "pdfium_future",
114        feature = "pdfium_7543",
115        feature = "pdfium_7350",
116        feature = "pdfium_7215",
117        feature = "pdfium_7123",
118        feature = "pdfium_6996",
119        feature = "pdfium_6721",
120        feature = "pdfium_6666"
121    ))]
122    /// Returns the name of this [PdfFont].
123    pub fn name(&self) -> String {
124        // Retrieving the font name from Pdfium is a two-step operation. First, we call
125        // FPDFFont_GetBaseFontName() with a null buffer; this will retrieve the length of
126        // the font name in bytes. If the length is zero, then there is no font name.
127
128        // If the length is non-zero, then we reserve a byte buffer of the given
129        // length and call FPDFFont_GetBaseFontName() again with a pointer to the buffer;
130        // this will write the font name into the buffer. Unlike most text handling in
131        // Pdfium, font names are returned in UTF-8 format.
132
133        let buffer_length = unsafe {
134            self.bindings()
135                .FPDFFont_GetBaseFontName(self.handle, std::ptr::null_mut(), 0)
136        };
137
138        if buffer_length == 0 {
139            // The font name is not present.
140
141            return String::new();
142        }
143
144        let mut buffer = create_byte_buffer(buffer_length as usize);
145
146        let result = unsafe {
147            self.bindings().FPDFFont_GetBaseFontName(
148                self.handle,
149                buffer.as_mut_ptr() as *mut c_char,
150                buffer_length,
151            )
152        };
153
154        assert_eq!(result, buffer_length);
155
156        String::from_utf8(buffer)
157            // Trim any trailing nulls. All strings returned from Pdfium are generally terminated
158            // by one null byte.
159            .map(|str| str.trim_end_matches(char::from(0)).to_owned())
160            .unwrap_or_else(|_| String::new())
161    }
162
163    /// Returns the family of this [PdfFont].
164    pub fn family(&self) -> String {
165        // Retrieving the family name from Pdfium is a two-step operation. First, we call
166        // FPDFFont_GetFamilyName() with a null buffer; this will retrieve the length of
167        // the font name in bytes. If the length is zero, then there is no font name.
168
169        // If the length is non-zero, then we reserve a byte buffer of the given
170        // length and call FPDFFont_GetFamilyName() again with a pointer to the buffer;
171        // this will write the font name into the buffer. Unlike most text handling in
172        // Pdfium, font names are returned in UTF-8 format.
173
174        #[cfg(any(
175            feature = "pdfium_future",
176            feature = "pdfium_7543",
177            feature = "pdfium_7350",
178            feature = "pdfium_7215",
179            feature = "pdfium_7123",
180            feature = "pdfium_6996",
181            feature = "pdfium_6721",
182            feature = "pdfium_6666",
183            feature = "pdfium_6611"
184        ))]
185        let buffer_length = unsafe {
186            self.bindings()
187                .FPDFFont_GetFamilyName(self.handle, std::ptr::null_mut(), 0)
188        };
189
190        #[cfg(any(
191            feature = "pdfium_6569",
192            feature = "pdfium_6555",
193            feature = "pdfium_6490",
194            feature = "pdfium_6406",
195            feature = "pdfium_6337",
196            feature = "pdfium_6295",
197            feature = "pdfium_6259",
198            feature = "pdfium_6164",
199            feature = "pdfium_6124",
200            feature = "pdfium_6110",
201            feature = "pdfium_6084",
202            feature = "pdfium_6043",
203            feature = "pdfium_6015",
204            feature = "pdfium_5961"
205        ))]
206        let buffer_length = unsafe {
207            self.bindings()
208                .FPDFFont_GetFontName(self.handle, std::ptr::null_mut(), 0)
209        };
210
211        if buffer_length == 0 {
212            // The font name is not present.
213
214            return String::new();
215        }
216
217        let mut buffer = create_byte_buffer(buffer_length as usize);
218
219        #[cfg(any(
220            feature = "pdfium_future",
221            feature = "pdfium_7543",
222            feature = "pdfium_7350",
223            feature = "pdfium_7215",
224            feature = "pdfium_7123",
225            feature = "pdfium_6996",
226            feature = "pdfium_6721",
227            feature = "pdfium_6666",
228            feature = "pdfium_6611"
229        ))]
230        let result = unsafe {
231            self.bindings().FPDFFont_GetFamilyName(
232                self.handle,
233                buffer.as_mut_ptr() as *mut c_char,
234                buffer_length,
235            )
236        };
237
238        #[cfg(any(
239            feature = "pdfium_6569",
240            feature = "pdfium_6555",
241            feature = "pdfium_6490",
242            feature = "pdfium_6406",
243            feature = "pdfium_6337",
244            feature = "pdfium_6295",
245            feature = "pdfium_6259",
246            feature = "pdfium_6164",
247            feature = "pdfium_6124",
248            feature = "pdfium_6110",
249            feature = "pdfium_6084",
250            feature = "pdfium_6043",
251            feature = "pdfium_6015",
252            feature = "pdfium_5961"
253        ))]
254        let result = unsafe {
255            self.bindings().FPDFFont_GetFontName(
256                self.handle,
257                buffer.as_mut_ptr() as *mut c_char,
258                buffer_length,
259            )
260        };
261
262        assert_eq!(result, buffer_length);
263
264        String::from_utf8(buffer)
265            // Trim any trailing nulls. All strings returned from Pdfium are generally terminated
266            // by one null byte.
267            .map(|str| str.trim_end_matches(char::from(0)).to_owned())
268            .unwrap_or_else(|_| String::new())
269    }
270
271    /// Returns the weight of this [PdfFont].
272    ///
273    /// Pdfium may not reliably return the correct value of this property for built-in fonts.
274    pub fn weight(&self) -> Result<PdfFontWeight, PdfiumError> {
275        PdfFontWeight::from_pdfium(unsafe { self.bindings().FPDFFont_GetWeight(self.handle) })
276            .ok_or(PdfiumError::PdfiumLibraryInternalError(
277                PdfiumInternalError::Unknown,
278            ))
279    }
280
281    /// Returns the italic angle of this [PdfFont]. The italic angle is the angle,
282    /// expressed in degrees counter-clockwise from the vertical, of the dominant vertical
283    /// strokes of the font. The value is zero for non-italic fonts, and negative for fonts
284    /// that slope to the right (as almost all italic fonts do).
285    ///
286    /// Pdfium may not reliably return the correct value of this property for built-in fonts.
287    pub fn italic_angle(&self) -> Result<i32, PdfiumError> {
288        let mut angle = 0;
289
290        if self.bindings().is_true(unsafe {
291            self.bindings()
292                .FPDFFont_GetItalicAngle(self.handle, &mut angle)
293        }) {
294            Ok(angle)
295        } else {
296            Err(PdfiumError::PdfiumLibraryInternalError(
297                PdfiumInternalError::Unknown,
298            ))
299        }
300    }
301
302    /// Returns the ascent of this [PdfFont] for the given font size. The ascent is the maximum
303    /// height above the baseline reached by glyphs in this font, excluding the height of glyphs
304    /// for accented characters.
305    pub fn ascent(&self, font_size: PdfPoints) -> Result<PdfPoints, PdfiumError> {
306        let mut ascent = 0.0;
307
308        if self.bindings().is_true(unsafe {
309            self.bindings()
310                .FPDFFont_GetAscent(self.handle, font_size.value, &mut ascent)
311        }) {
312            Ok(PdfPoints::new(ascent))
313        } else {
314            Err(PdfiumError::PdfiumLibraryInternalError(
315                PdfiumInternalError::Unknown,
316            ))
317        }
318    }
319
320    /// Returns the descent of this [PdfFont] for the given font size. The descent is the
321    /// maximum distance below the baseline reached by glyphs in this font, expressed as a
322    /// negative points value.
323    pub fn descent(&self, font_size: PdfPoints) -> Result<PdfPoints, PdfiumError> {
324        let mut descent = 0.0;
325
326        if self.bindings().is_true(unsafe {
327            self.bindings()
328                .FPDFFont_GetDescent(self.handle, font_size.value, &mut descent)
329        }) {
330            Ok(PdfPoints::new(descent))
331        } else {
332            Err(PdfiumError::PdfiumLibraryInternalError(
333                PdfiumInternalError::Unknown,
334            ))
335        }
336    }
337
338    /// Returns the raw font descriptor bitflags for the containing [PdfFont].
339    #[inline]
340    fn get_flags_bits(&self) -> FpdfFontDescriptorFlags {
341        FpdfFontDescriptorFlags::from_bits_truncate(unsafe {
342            self.bindings().FPDFFont_GetFlags(self.handle)
343        } as u32)
344    }
345
346    /// Returns `true` if all the glyphs in this [PdfFont] have the same width.
347    ///
348    /// Pdfium may not reliably return the correct value of this flag for built-in fonts.
349    pub fn is_fixed_pitch(&self) -> bool {
350        self.get_flags_bits()
351            .contains(FpdfFontDescriptorFlags::FIXED_PITCH_BIT_1)
352    }
353
354    /// Returns `true` if the glyphs in this [PdfFont] have variable widths.
355    ///
356    /// Pdfium may not reliably return the correct value of this flag for built-in fonts.
357    #[inline]
358    pub fn is_proportional_pitch(&self) -> bool {
359        !self.is_fixed_pitch()
360    }
361
362    /// Returns `true` if one or more glyphs in this [PdfFont] have serifs - short strokes
363    /// drawn at an angle on the top or bottom of glyph stems to decorate the glyphs.
364    /// For example, Times New Roman is a serif font.
365    ///
366    /// Pdfium may not reliably return the correct value of this flag for built-in fonts.
367    pub fn is_serif(&self) -> bool {
368        self.get_flags_bits()
369            .contains(FpdfFontDescriptorFlags::SERIF_BIT_2)
370    }
371
372    /// Returns `true` if no glyphs in this [PdfFont] have serifs - short strokes
373    /// drawn at an angle on the top or bottom of glyph stems to decorate the glyphs.
374    /// For example, Helvetica is a sans-serif font.
375    ///
376    /// Pdfium may not reliably return the correct value of this flag for built-in fonts.
377    #[inline]
378    pub fn is_sans_serif(&self) -> bool {
379        !self.is_serif()
380    }
381
382    /// Returns `true` if this [PdfFont] contains glyphs outside the Adobe standard Latin
383    /// character set.
384    ///
385    /// This classification of non-symbolic and symbolic fonts is peculiar to PDF. A font may
386    /// contain additional characters that are used in Latin writing systems but are outside the
387    /// Adobe standard Latin character set; PDF considers such a font to be symbolic.
388    ///
389    /// Pdfium may not reliably return the correct value of this flag for built-in fonts.
390    pub fn is_symbolic(&self) -> bool {
391        // This flag bit and the non-symbolic flag bit cannot both be set or both be clear.
392
393        self.get_flags_bits()
394            .contains(FpdfFontDescriptorFlags::SYMBOLIC_BIT_3)
395    }
396
397    /// Returns `true` if this [PdfFont] does not contain glyphs outside the Adobe standard
398    /// Latin character set.
399    ///
400    /// This classification of non-symbolic and symbolic fonts is peculiar to PDF. A font may
401    /// contain additional characters that are used in Latin writing systems but are outside the
402    /// Adobe standard Latin character set; PDF considers such a font to be symbolic.
403    ///
404    /// Pdfium may not reliably return the correct value of this flag for built-in fonts.
405    pub fn is_non_symbolic(&self) -> bool {
406        // This flag bit and the symbolic flag bit cannot both be set or both be clear.
407
408        self.get_flags_bits()
409            .contains(FpdfFontDescriptorFlags::NON_SYMBOLIC_BIT_6)
410    }
411
412    /// Returns `true` if the glyphs in this [PdfFont] are designed to resemble cursive handwriting.
413    ///
414    /// Pdfium may not reliably return the correct value of this flag for built-in fonts.
415    pub fn is_cursive(&self) -> bool {
416        self.get_flags_bits()
417            .contains(FpdfFontDescriptorFlags::SCRIPT_BIT_4)
418    }
419
420    /// Returns `true` if the glyphs in this [PdfFont] include dominant vertical strokes
421    /// that are slanted.
422    ///
423    /// The designed vertical stroke angle can be retrieved using the [PdfFont::italic_angle()] function.
424    ///
425    /// Pdfium may not reliably return the correct value of this flag for built-in fonts.
426    pub fn is_italic(&self) -> bool {
427        self.get_flags_bits()
428            .contains(FpdfFontDescriptorFlags::ITALIC_BIT_7)
429    }
430
431    /// Returns `true` if this [PdfFont] contains no lowercase letters by design.
432    ///
433    /// Pdfium may not reliably return the correct value of this flag for built-in fonts.
434    pub fn is_all_caps(&self) -> bool {
435        self.get_flags_bits()
436            .contains(FpdfFontDescriptorFlags::ALL_CAP_BIT_17)
437    }
438
439    /// Returns `true` if the lowercase letters in this [PdfFont] have the same shapes as the
440    /// corresponding uppercase letters but are sized proportionally so they have the same size
441    /// and stroke weight as lowercase glyphs in the same typeface family.
442    ///
443    /// Pdfium may not reliably return the correct value of this flag for built-in fonts.
444    pub fn is_small_caps(&self) -> bool {
445        self.get_flags_bits()
446            .contains(FpdfFontDescriptorFlags::SMALL_CAP_BIT_18)
447    }
448
449    /// Returns `true` if bold glyphs in this [PdfFont] are painted with extra pixels
450    /// at very small font sizes.
451    ///
452    /// Typically when glyphs are painted at small sizes on low-resolution devices, individual strokes
453    /// of bold glyphs may appear only one pixel wide. Because this is the minimum width of a pixel
454    /// based device, individual strokes of non-bold glyphs may also appear as one pixel wide
455    /// and therefore cannot be distinguished from bold glyphs. If this flag is set, individual
456    /// strokes of bold glyphs may be thickened at small font sizes.
457    ///
458    /// Pdfium may not reliably return the correct value of this flag for built-in fonts.
459    pub fn is_bold_reenforced(&self) -> bool {
460        self.get_flags_bits()
461            .contains(FpdfFontDescriptorFlags::FORCE_BOLD_BIT_19)
462    }
463
464    /// Returns `true` if this [PdfFont] is an instance of one of the 14 built-in fonts
465    /// provided as part of the PDF specification.
466    #[inline]
467    pub fn is_built_in(&self) -> bool {
468        self.built_in.is_some()
469    }
470
471    /// Returns the [PdfFontBuiltin] type of this built-in font, or `None` if this font is
472    /// not one of the 14 built-in fonts provided as part of the PDF specification.
473    #[inline]
474    pub fn built_in(&self) -> Option<PdfFontBuiltin> {
475        self.built_in
476    }
477
478    /// Returns `true` if the data for this [PdfFont] is embedded in the containing [PdfDocument].
479    pub fn is_embedded(&self) -> Result<bool, PdfiumError> {
480        let result = unsafe { self.bindings().FPDFFont_GetIsEmbedded(self.handle) };
481
482        match result {
483            1 => Ok(true),
484            0 => Ok(false),
485            _ => Err(PdfiumError::PdfiumLibraryInternalError(
486                PdfiumInternalError::Unknown,
487            )),
488        }
489    }
490
491    /// Writes this [PdfFont] to a new byte buffer, returning the byte buffer.
492    ///
493    /// If this [PdfFont] is not embedded in the containing [PdfDocument], then the data
494    /// returned will be for the substitution font instead.
495    pub fn data(&self) -> Result<Vec<u8>, PdfiumError> {
496        // Retrieving the font data from Pdfium is a two-step operation. First, we call
497        // FPDFFont_GetFontData() with a null buffer; this will retrieve the length of
498        // the data in bytes. If the length is zero, then there is no data associated
499        // with this font.
500
501        // If the length is non-zero, then we reserve a byte buffer of the given
502        // length and call FPDFFont_GetFontData() again with a pointer to the buffer;
503        // this will write the font data to the buffer.
504
505        let mut out_buflen: usize = 0;
506
507        if self.bindings().is_true(unsafe {
508            self.bindings().FPDFFont_GetFontData(
509                self.handle,
510                std::ptr::null_mut(),
511                0,
512                &mut out_buflen,
513            )
514        }) {
515            // out_buflen now contains the length of the font data.
516
517            let buffer_length = out_buflen;
518
519            let mut buffer = create_byte_buffer(buffer_length);
520
521            let result = unsafe {
522                self.bindings().FPDFFont_GetFontData(
523                    self.handle,
524                    buffer.as_mut_ptr(),
525                    buffer_length,
526                    &mut out_buflen,
527                )
528            };
529
530            assert!(self.bindings().is_true(result));
531            assert_eq!(buffer_length, out_buflen);
532
533            Ok(buffer)
534        } else {
535            Err(PdfiumError::PdfiumLibraryInternalError(
536                PdfiumInternalError::Unknown,
537            ))
538        }
539    }
540
541    /// Returns a collection of all the [PdfFontGlyphs] defined for this [PdfFont] in the containing
542    /// `PdfDocument`.
543    ///
544    /// Note that documents typically include only the specific glyphs they need from any given font,
545    /// not the entire font glyphset. This is a PDF feature known as font subsetting. The collection
546    /// of glyphs returned by this function may therefore not cover the entire font glyphset.
547    #[inline]
548    pub fn glyphs(&self) -> &PdfFontGlyphs<'_> {
549        self.glyphs.initialize_len();
550        &self.glyphs
551    }
552}
553
554impl<'a> Drop for PdfFont<'a> {
555    /// Closes this [PdfFont], releasing held memory.
556    #[inline]
557    fn drop(&mut self) {
558        // The documentation for FPDFText_LoadFont() and FPDFText_LoadStandardFont() both state
559        // that the font loaded by the function can be closed by calling FPDFFont_Close().
560        // I had taken this to mean that _any_ FPDF_Font handle returned from a Pdfium function
561        // should be closed via FPDFFont_Close(), but testing suggests this is not the case;
562        // rather, it is only fonts specifically loaded by calling FPDFText_LoadFont() or
563        // FPDFText_LoadStandardFont() that need to be actively closed.
564
565        // In other words, retrieving a handle to a font that already exists in a document evidently
566        // does not allocate any additional resources, so we don't need to free anything.
567        // (Indeed, if we try to, Pdfium segfaults.)
568
569        if self.is_font_memory_loaded {
570            unsafe {
571                self.bindings().FPDFFont_Close(self.handle);
572            }
573        }
574    }
575}
576
577impl<'a> PdfiumLibraryBindingsAccessor<'a> for PdfFont<'a> {}
578
579#[cfg(feature = "thread_safe")]
580unsafe impl<'a> Send for PdfFont<'a> {}
581
582#[cfg(feature = "thread_safe")]
583unsafe impl<'a> Sync for PdfFont<'a> {}