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}