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