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