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