Skip to main content

pdfium_render/pdf/document/
fonts.rs

1//! Defines the [PdfFonts] struct, a collection of all the `PdfFont` objects in a
2//! `PdfDocument`.
3
4use crate::bindgen::{FPDF_DOCUMENT, FPDF_FONT, FPDF_FONT_TRUETYPE, FPDF_FONT_TYPE1};
5use crate::bindings::PdfiumLibraryBindings;
6use crate::error::{PdfiumError, PdfiumInternalError};
7use crate::pdf::font::PdfFont;
8use std::collections::HashMap;
9use std::io::Read;
10use std::os::raw::{c_int, c_uint};
11
12#[cfg(not(target_arch = "wasm32"))]
13use std::fs::File;
14
15#[cfg(not(target_arch = "wasm32"))]
16use std::path::Path;
17
18#[cfg(target_arch = "wasm32")]
19use wasm_bindgen::JsCast;
20
21#[cfg(target_arch = "wasm32")]
22use wasm_bindgen_futures::JsFuture;
23
24#[cfg(target_arch = "wasm32")]
25use js_sys::{ArrayBuffer, Uint8Array};
26
27#[cfg(target_arch = "wasm32")]
28use web_sys::{Blob, Response, window};
29
30#[cfg(doc)]
31struct Blob;
32
33/// The 14 built-in fonts provided as part of the PDF specification.
34#[derive(Copy, Clone, Debug, PartialEq)]
35pub enum PdfFontBuiltin {
36    TimesRoman,
37    TimesBold,
38    TimesItalic,
39    TimesBoldItalic,
40    Helvetica,
41    HelveticaBold,
42    HelveticaOblique,
43    HelveticaBoldOblique,
44    Courier,
45    CourierBold,
46    CourierOblique,
47    CourierBoldOblique,
48    Symbol,
49    ZapfDingbats,
50}
51
52impl PdfFontBuiltin {
53    /// Returns the PostScript name of this built-in PDF font, as listed on page 416
54    /// of the PDF 1.7 specification.
55    pub fn to_pdf_font_name(&self) -> &str {
56        match self {
57            PdfFontBuiltin::TimesRoman => "Times-Roman",
58            PdfFontBuiltin::TimesBold => "Times-Bold",
59            PdfFontBuiltin::TimesItalic => "Times-Italic",
60            PdfFontBuiltin::TimesBoldItalic => "Times-BoldItalic",
61            PdfFontBuiltin::Helvetica => "Helvetica",
62            PdfFontBuiltin::HelveticaBold => "Helvetica-Bold",
63            PdfFontBuiltin::HelveticaOblique => "Helvetica-Oblique",
64            PdfFontBuiltin::HelveticaBoldOblique => "Helvetica-BoldOblique",
65            PdfFontBuiltin::Courier => "Courier",
66            PdfFontBuiltin::CourierBold => "Courier-Bold",
67            PdfFontBuiltin::CourierOblique => "Courier-Oblique",
68            PdfFontBuiltin::CourierBoldOblique => "Courier-BoldOblique",
69            PdfFontBuiltin::Symbol => "Symbol",
70            PdfFontBuiltin::ZapfDingbats => "ZapfDingbats",
71        }
72    }
73}
74
75/// A reusable token referencing a [PdfFont] previously added to the [PdfFonts] collection
76/// of a `PdfDocument`.
77#[derive(Copy, Clone, Debug, Eq, Hash, PartialEq)]
78pub struct PdfFontToken(FPDF_FONT);
79
80impl PdfFontToken {
81    #[inline]
82    pub(crate) fn from_pdfium(handle: FPDF_FONT) -> Self {
83        Self(handle)
84    }
85
86    #[inline]
87    pub(crate) fn from_font(font: &PdfFont) -> Self {
88        Self::from_pdfium(font.handle())
89    }
90
91    #[inline]
92    pub(crate) fn handle(&self) -> FPDF_FONT {
93        self.0
94    }
95}
96
97/// Allows font-handling functions to take either a [PdfFont] owned instance, a [PdfFont] reference,
98/// or a [PdfFontToken].
99pub trait ToPdfFontToken {
100    fn token(&self) -> PdfFontToken;
101}
102
103impl ToPdfFontToken for PdfFontToken {
104    #[inline]
105    fn token(&self) -> PdfFontToken {
106        *self
107    }
108}
109
110impl<'a> ToPdfFontToken for PdfFont<'a> {
111    #[inline]
112    fn token(&self) -> PdfFontToken {
113        PdfFontToken::from_font(self)
114    }
115}
116
117impl<'a> ToPdfFontToken for &'a PdfFont<'a> {
118    #[inline]
119    fn token(&self) -> PdfFontToken {
120        PdfFontToken::from_font(self)
121    }
122}
123
124/// A collection of all the `PdfFont` objects in a `PdfDocument`.
125pub struct PdfFonts<'a> {
126    document_handle: FPDF_DOCUMENT,
127    fonts: HashMap<PdfFontToken, PdfFont<'a>>,
128    bindings: &'a dyn PdfiumLibraryBindings,
129}
130
131impl<'a> PdfFonts<'a> {
132    #[inline]
133    pub(crate) fn from_pdfium(document_handle: FPDF_DOCUMENT, bindings: &'a dyn PdfiumLibraryBindings) -> Self {
134        PdfFonts {
135            document_handle,
136            fonts: HashMap::new(),
137            bindings,
138        }
139    }
140
141    /// Returns the [PdfiumLibraryBindings] used by this [PdfFonts] collection.
142    #[inline]
143    pub fn bindings(&self) -> &'a dyn PdfiumLibraryBindings {
144        self.bindings
145    }
146
147    /// Returns a reusable [PdfFontToken] for the given built-in font.
148    #[inline]
149    pub fn new_built_in(&mut self, font: PdfFontBuiltin) -> PdfFontToken {
150        let font = PdfFont::from_pdfium(
151            self.bindings
152                .FPDFText_LoadStandardFont(self.document_handle, font.to_pdf_font_name()),
153            self.bindings,
154            Some(font),
155            true,
156        );
157
158        let token = PdfFontToken(font.handle());
159
160        self.fonts.insert(token, font);
161
162        token
163    }
164
165    /// Returns a reusable [PdfFontToken] for the built-in "Times-Roman" font.
166    #[inline]
167    pub fn times_roman(&mut self) -> PdfFontToken {
168        self.new_built_in(PdfFontBuiltin::TimesRoman)
169    }
170
171    /// Returns a reusable [PdfFontToken] for the built-in "Times-Bold" font.
172    #[inline]
173    pub fn times_bold(&mut self) -> PdfFontToken {
174        self.new_built_in(PdfFontBuiltin::TimesBold)
175    }
176
177    /// Returns a reusable [PdfFontToken] for the built-in "Times-Italic" font.
178    #[inline]
179    pub fn times_italic(&mut self) -> PdfFontToken {
180        self.new_built_in(PdfFontBuiltin::TimesItalic)
181    }
182
183    /// Returns a reusable [PdfFontToken] for the built-in "Times-BoldItalic" font.
184    #[inline]
185    pub fn times_bold_italic(&mut self) -> PdfFontToken {
186        self.new_built_in(PdfFontBuiltin::TimesBoldItalic)
187    }
188
189    /// Returns a reusable [PdfFontToken] for the built-in "Helvetica" font.
190    #[inline]
191    pub fn helvetica(&mut self) -> PdfFontToken {
192        self.new_built_in(PdfFontBuiltin::Helvetica)
193    }
194
195    /// Returns a reusable [PdfFontToken] for the built-in "Helvetica-Bold" font.
196    #[inline]
197    pub fn helvetica_bold(&mut self) -> PdfFontToken {
198        self.new_built_in(PdfFontBuiltin::HelveticaBold)
199    }
200
201    /// Returns a reusable [PdfFontToken] for the built-in "Helvetica-Oblique" font.
202    #[inline]
203    pub fn helvetica_oblique(&mut self) -> PdfFontToken {
204        self.new_built_in(PdfFontBuiltin::HelveticaOblique)
205    }
206
207    /// Returns a reusable [PdfFontToken] for the built-in "Helvetica-BoldOblique" font.
208    #[inline]
209    pub fn helvetica_bold_oblique(&mut self) -> PdfFontToken {
210        self.new_built_in(PdfFontBuiltin::HelveticaBoldOblique)
211    }
212
213    /// Returns a reusable [PdfFontToken] for the built-in "Courier" font.
214    #[inline]
215    pub fn courier(&mut self) -> PdfFontToken {
216        self.new_built_in(PdfFontBuiltin::Courier)
217    }
218
219    /// Returns a reusable [PdfFontToken] for the built-in "Courier-Bold" font.
220    #[inline]
221    pub fn courier_bold(&mut self) -> PdfFontToken {
222        self.new_built_in(PdfFontBuiltin::CourierBold)
223    }
224
225    /// Returns a reusable [PdfFontToken] for the built-in "Courier-Oblique" font.
226    #[inline]
227    pub fn courier_oblique(&mut self) -> PdfFontToken {
228        self.new_built_in(PdfFontBuiltin::CourierOblique)
229    }
230
231    /// Returns a reusable [PdfFontToken] for the built-in "Courier-BoldOblique" font.
232    #[inline]
233    pub fn courier_bold_oblique(&mut self) -> PdfFontToken {
234        self.new_built_in(PdfFontBuiltin::CourierBoldOblique)
235    }
236
237    /// Returns a reusable [PdfFontToken] for the built-in "Symbol" font.
238    #[inline]
239    pub fn symbol(&mut self) -> PdfFontToken {
240        self.new_built_in(PdfFontBuiltin::Symbol)
241    }
242
243    /// Returns a reusable [PdfFontToken] for the built-in "ZapfDingbats" font.
244    #[inline]
245    pub fn zapf_dingbats(&mut self) -> PdfFontToken {
246        self.new_built_in(PdfFontBuiltin::ZapfDingbats)
247    }
248
249    /// Attempts to load a Type 1 font file from the given file path, returning a reusable
250    /// [PdfFontToken] if the font was successfully loaded.
251    ///
252    /// Set the `is_cid_font` parameter to `true` if the given font is keyed by
253    /// 16-bit character ID (CID), indicating that it supports an extended glyphset of
254    /// 65,535 glyphs. This is typically the case with fonts that support Asian character sets
255    /// or right-to-left languages.
256    ///
257    /// This function is not available when compiling to WASM. You have several options for
258    /// loading font data in WASM:
259    /// * Use the [PdfFont::load_type1_from_fetch()] function to download font data from a
260    ///   URL using the browser's built-in `fetch()` API. This function is only available when
261    ///   compiling to WASM.
262    /// * Use the [PdfFont::load_type1_from_blob()] function to load font data from a
263    ///   Javascript File or Blob object (such as a File object returned from an HTML
264    ///   `<input type="file">` element). This function is only available when compiling to WASM.
265    /// * Use the [PdfFont::load_type1_from_reader()] function to load font data from any
266    ///   valid Rust reader.
267    /// * Use another method to retrieve the bytes of the target font over the network,
268    ///   then load those bytes into Pdfium using the [PdfFont::new_type1_from_bytes()] function.
269    /// * Embed the bytes of the desired font directly into the compiled WASM module
270    ///   using the `include_bytes!()` macro.
271    #[cfg(not(target_arch = "wasm32"))]
272    pub fn load_type1_from_file(
273        &mut self,
274        path: &(impl AsRef<Path> + ?Sized),
275        is_cid_font: bool,
276    ) -> Result<PdfFontToken, PdfiumError> {
277        self.load_type1_from_reader(File::open(path).map_err(PdfiumError::IoError)?, is_cid_font)
278    }
279
280    /// Attempts to load a Type 1 font file from the given reader, returning a reusable
281    /// [PdfFontToken] if the font was successfully loaded.
282    ///
283    /// Set the `is_cid_font` parameter to `true` if the given font is keyed by
284    /// 16-bit character ID (CID), indicating that it supports an extended glyphset of
285    /// 65,535 glyphs. This is typically the case with fonts that support Asian character sets
286    /// or right-to-left languages.
287    pub fn load_type1_from_reader(
288        &mut self,
289        mut reader: impl Read,
290        is_cid_font: bool,
291    ) -> Result<PdfFontToken, PdfiumError> {
292        let mut bytes = Vec::new();
293
294        reader.read_to_end(&mut bytes).map_err(PdfiumError::IoError)?;
295
296        self.load_type1_from_bytes(bytes.as_slice(), is_cid_font)
297    }
298
299    /// Attempts to load a Type 1 font file from the given URL, returning a reusable
300    /// [PdfFontToken] if the font was successfully loaded.
301    ///
302    /// The Javascript `fetch()` API is used to download data over the network.
303    ///
304    /// Set the `is_cid_font` parameter to `true` if the given font is keyed by
305    /// 16-bit character ID (CID), indicating that it supports an extended glyphset of
306    /// 65,535 glyphs. This is typically the case with fonts that support Asian character sets
307    /// or right-to-left languages.
308    ///
309    /// This function is only available when compiling to WASM.
310    #[cfg(any(doc, target_arch = "wasm32"))]
311    pub async fn load_type1_from_fetch(
312        &mut self,
313        url: impl ToString,
314        is_cid_font: bool,
315    ) -> Result<PdfFontToken, PdfiumError> {
316        if let Some(window) = window() {
317            let fetch_result = JsFuture::from(window.fetch_with_str(url.to_string().as_str()))
318                .await
319                .map_err(PdfiumError::WebSysFetchError)?;
320
321            debug_assert!(fetch_result.is_instance_of::<Response>());
322
323            let response: Response = fetch_result
324                .dyn_into()
325                .map_err(|_| PdfiumError::WebSysInvalidResponseError)?;
326
327            let blob: Blob = JsFuture::from(response.blob().map_err(PdfiumError::WebSysFetchError)?)
328                .await
329                .map_err(PdfiumError::WebSysFetchError)?
330                .into();
331
332            self.load_type1_from_blob(blob, is_cid_font).await
333        } else {
334            Err(PdfiumError::WebSysWindowObjectNotAvailable)
335        }
336    }
337
338    /// Attempts to load a Type 1 font from the given Blob, returning a reusable
339    /// [PdfFontToken] if the font was successfully loaded.
340    ///
341    /// A File object returned from a FileList is a suitable Blob:
342    ///
343    /// ```text
344    /// <input id="filePicker" type="file">
345    ///
346    /// const file = document.getElementById('filePicker').files[0];
347    /// ```
348    ///
349    /// Set the `is_cid_font` parameter to `true` if the given font is keyed by
350    /// 16-bit character ID (CID), indicating that it supports an extended glyphset of
351    /// 65,535 glyphs. This is typically the case with fonts that support Asian character sets
352    /// or right-to-left languages.
353    ///
354    /// This function is only available when compiling to WASM.
355    #[cfg(any(doc, target_arch = "wasm32"))]
356    pub async fn load_type1_from_blob(&mut self, blob: Blob, is_cid_font: bool) -> Result<PdfFontToken, PdfiumError> {
357        let array_buffer: ArrayBuffer = JsFuture::from(blob.array_buffer())
358            .await
359            .map_err(PdfiumError::WebSysFetchError)?
360            .into();
361
362        let u8_array: Uint8Array = Uint8Array::new(&array_buffer);
363
364        let bytes: Vec<u8> = u8_array.to_vec();
365
366        self.load_type1_from_bytes(bytes.as_slice(), is_cid_font)
367    }
368
369    /// Attempts to load the given byte data as a Type 1 font file, returning a reusable
370    /// [PdfFontToken] if the font was successfully loaded.
371    ///
372    /// Set the `is_cid_font` parameter to `true` if the given font is keyed by
373    /// 16-bit character ID (CID), indicating that it supports an extended glyphset of
374    /// 65,535 glyphs. This is typically the case with fonts that support Asian character sets
375    /// or right-to-left languages.
376    pub fn load_type1_from_bytes(&mut self, font_data: &[u8], is_cid_font: bool) -> Result<PdfFontToken, PdfiumError> {
377        self.new_font_from_bytes(font_data, FPDF_FONT_TYPE1, is_cid_font)
378    }
379
380    /// Attempts to load a TrueType font file from the given file path, returning a reusable
381    /// [PdfFontToken] if the font was successfully loaded.
382    ///
383    /// Set the `is_cid_font` parameter to `true` if the given font is keyed by
384    /// 16-bit character ID (CID), indicating that it supports an extended glyphset of
385    /// 65,535 glyphs. This is typically the case with fonts that support Asian character sets
386    /// or right-to-left languages.
387    ///
388    /// This function is not available when compiling to WASM. You have several options for
389    /// loading font data in WASM:
390    /// * Use the [PdfFont::load_true_type_from_fetch()] function to download font data from a
391    ///   URL using the browser's built-in `fetch()` API. This function is only available when
392    ///   compiling to WASM.
393    /// * Use the [PdfFont::load_true_type_from_blob()] function to load font data from a
394    ///   Javascript `File` or `Blob` object (such as a `File` object returned from an HTML
395    ///   `<input type="file">` element). This function is only available when compiling to WASM.
396    /// * Use the [PdfFont::load_true_type_from_reader()] function to load font data from any
397    ///   valid Rust reader.
398    /// * Use another method to retrieve the bytes of the target font over the network,
399    ///   then load those bytes into Pdfium using the [PdfFont::new_true_type_from_bytes()] function.
400    /// * Embed the bytes of the desired font directly into the compiled WASM module
401    ///   using the `include_bytes!()` macro.
402    #[cfg(not(target_arch = "wasm32"))]
403    pub fn load_true_type_from_file(
404        &mut self,
405        path: &(impl AsRef<Path> + ?Sized),
406        is_cid_font: bool,
407    ) -> Result<PdfFontToken, PdfiumError> {
408        self.load_true_type_from_reader(File::open(path).map_err(PdfiumError::IoError)?, is_cid_font)
409    }
410
411    /// Attempts to load a TrueType font file from the given reader, returning a reusable
412    /// [PdfFontToken] if the font was successfully loaded.
413    ///
414    /// Set the `is_cid_font` parameter to `true` if the given font is keyed by
415    /// 16-bit character ID (CID), indicating that it supports an extended glyphset of
416    /// 65,535 glyphs. This is typically the case with fonts that support Asian character sets
417    /// or right-to-left languages.
418    pub fn load_true_type_from_reader(
419        &mut self,
420        mut reader: impl Read,
421        is_cid_font: bool,
422    ) -> Result<PdfFontToken, PdfiumError> {
423        let mut bytes = Vec::new();
424
425        reader.read_to_end(&mut bytes).map_err(PdfiumError::IoError)?;
426
427        self.load_true_type_from_bytes(bytes.as_slice(), is_cid_font)
428    }
429
430    /// Attempts to load a TrueType font file from the given URL, returning a reusable
431    /// [PdfFontToken] if the font was successfully loaded.
432    ///
433    /// The Javascript `fetch()` API is used to download data over the network.
434    ///
435    /// Set the `is_cid_font` parameter to `true` if the given font is keyed by
436    /// 16-bit character ID (CID), indicating that it supports an extended glyphset of
437    /// 65,535 glyphs. This is typically the case with fonts that support Asian character sets
438    /// or right-to-left languages.
439    ///
440    /// This function is only available when compiling to WASM.
441    #[cfg(any(doc, target_arch = "wasm32"))]
442    pub async fn load_true_type_from_fetch(
443        &mut self,
444        url: impl ToString,
445        is_cid_font: bool,
446    ) -> Result<PdfFontToken, PdfiumError> {
447        if let Some(window) = window() {
448            let fetch_result = JsFuture::from(window.fetch_with_str(url.to_string().as_str()))
449                .await
450                .map_err(PdfiumError::WebSysFetchError)?;
451
452            debug_assert!(fetch_result.is_instance_of::<Response>());
453
454            let response: Response = fetch_result
455                .dyn_into()
456                .map_err(|_| PdfiumError::WebSysInvalidResponseError)?;
457
458            let blob: Blob = JsFuture::from(response.blob().map_err(PdfiumError::WebSysFetchError)?)
459                .await
460                .map_err(PdfiumError::WebSysFetchError)?
461                .into();
462
463            self.load_true_type_from_blob(blob, is_cid_font).await
464        } else {
465            Err(PdfiumError::WebSysWindowObjectNotAvailable)
466        }
467    }
468
469    /// Attempts to load a TrueType font from the given `Blob`, returning a reusable
470    /// [PdfFontToken] if the font was successfully loaded.
471    ///
472    /// A `File` object returned from a `FileList` is a suitable `Blob`:
473    ///
474    /// ```text
475    /// <input id="filePicker" type="file">
476    ///
477    /// const file = document.getElementById('filePicker').files[0];
478    /// ```
479    ///
480    /// Set the `is_cid_font` parameter to `true` if the given font is keyed by
481    /// 16-bit character ID (CID), indicating that it supports an extended glyphset of
482    /// 65,535 glyphs. This is typically the case with fonts that support Asian character sets
483    /// or right-to-left languages.
484    ///
485    /// This function is only available when compiling to WASM.
486    #[cfg(any(doc, target_arch = "wasm32"))]
487    pub async fn load_true_type_from_blob(
488        &mut self,
489        blob: Blob,
490        is_cid_font: bool,
491    ) -> Result<PdfFontToken, PdfiumError> {
492        let array_buffer: ArrayBuffer = JsFuture::from(blob.array_buffer())
493            .await
494            .map_err(PdfiumError::WebSysFetchError)?
495            .into();
496
497        let u8_array: Uint8Array = Uint8Array::new(&array_buffer);
498
499        let bytes: Vec<u8> = u8_array.to_vec();
500
501        self.load_true_type_from_bytes(bytes.as_slice(), is_cid_font)
502    }
503
504    /// Attempts to load the given byte data as a TrueType font file, returning a reusable
505    /// [PdfFontToken] if the font was successfully loaded.
506    ///
507    /// Set the `is_cid_font` parameter to `true` if the given font is keyed by
508    /// 16-bit character ID (CID), indicating that it supports an extended glyphset of
509    /// 65,535 glyphs. This is typically the case with fonts that support Asian character sets
510    /// or right-to-left languages.
511    pub fn load_true_type_from_bytes(
512        &mut self,
513        font_data: &[u8],
514        is_cid_font: bool,
515    ) -> Result<PdfFontToken, PdfiumError> {
516        self.new_font_from_bytes(font_data, FPDF_FONT_TRUETYPE, is_cid_font)
517    }
518
519    #[inline]
520    pub(crate) fn new_font_from_bytes(
521        &mut self,
522        font_data: &[u8],
523        font_type: c_uint,
524        is_cid_font: bool,
525    ) -> Result<PdfFontToken, PdfiumError> {
526        let handle = self.bindings.FPDFText_LoadFont(
527            self.document_handle,
528            font_data.as_ptr(),
529            font_data.len() as c_uint,
530            font_type as c_int,
531            self.bindings.bool_to_pdfium(is_cid_font),
532        );
533
534        if handle.is_null() {
535            Err(PdfiumError::PdfiumLibraryInternalError(PdfiumInternalError::Unknown))
536        } else {
537            let font = PdfFont::from_pdfium(handle, self.bindings, None, true);
538
539            let token = PdfFontToken::from_font(&font);
540
541            self.fonts.insert(token, font);
542
543            Ok(token)
544        }
545    }
546
547    /// Returns a reference to the [PdfFont] associated with the given [PdfFontToken], if any.
548    #[inline]
549    pub fn get(&self, token: PdfFontToken) -> Option<&PdfFont<'_>> {
550        self.fonts.get(&token)
551    }
552}