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> {}