pdfium_render/pdf/font.rs
1//! Defines the [PdfFont] struct, exposing functionality related to a single font used to
2//! render text in a `PdfDocument`.
3
4pub mod glyph;
5pub mod glyphs;
6
7use crate::bindgen::FPDF_FONT;
8use crate::error::{PdfiumError, PdfiumInternalError};
9use crate::pdf::document::fonts::PdfFontBuiltin;
10use crate::pdf::font::glyphs::PdfFontGlyphs;
11use crate::pdf::points::PdfPoints;
12use crate::pdfium::PdfiumLibraryBindingsAccessor;
13use crate::utils::mem::create_byte_buffer;
14use bitflags::bitflags;
15use std::marker::PhantomData;
16use std::os::raw::{c_char, c_int};
17
18#[cfg(doc)]
19use crate::pdf::document::PdfDocument;
20
21// The following dummy declaration is used only when running cargo doc.
22// It allows documentation of WASM-specific functionality to be included
23// in documentation generated on non-WASM targets.
24
25#[cfg(doc)]
26struct Blob;
27
28bitflags! {
29 pub(crate) struct FpdfFontDescriptorFlags: u32 {
30 const FIXED_PITCH_BIT_1 = 0b00000000000000000000000000000001;
31 const SERIF_BIT_2 = 0b00000000000000000000000000000010;
32 const SYMBOLIC_BIT_3 = 0b00000000000000000000000000000100;
33 const SCRIPT_BIT_4 = 0b00000000000000000000000000001000;
34 const NON_SYMBOLIC_BIT_6 = 0b00000000000000000000000000100000;
35 const ITALIC_BIT_7 = 0b00000000000000000000000001000000;
36 const ALL_CAP_BIT_17 = 0b00000000000000010000000000000000;
37 const SMALL_CAP_BIT_18 = 0b00000000000000100000000000000000;
38 const FORCE_BOLD_BIT_19 = 0b00000000000001000000000000000000;
39 }
40}
41
42/// The weight of a [PdfFont]. Typical values are 400 (normal) and 700 (bold).
43#[derive(Copy, Clone, Debug, PartialEq)]
44pub enum PdfFontWeight {
45 Weight100,
46 Weight200,
47 Weight300,
48 Weight400Normal,
49 Weight500,
50 Weight600,
51 Weight700Bold,
52 Weight800,
53 Weight900,
54
55 /// Any font weight value that falls outside the typical 100 - 900 value range.
56 Custom(u32),
57}
58
59impl PdfFontWeight {
60 pub(crate) fn from_pdfium(value: c_int) -> Option<PdfFontWeight> {
61 match value {
62 -1 => None,
63 100 => Some(PdfFontWeight::Weight100),
64 200 => Some(PdfFontWeight::Weight200),
65 300 => Some(PdfFontWeight::Weight300),
66 400 => Some(PdfFontWeight::Weight400Normal),
67 500 => Some(PdfFontWeight::Weight500),
68 600 => Some(PdfFontWeight::Weight600),
69 700 => Some(PdfFontWeight::Weight700Bold),
70 800 => Some(PdfFontWeight::Weight800),
71 900 => Some(PdfFontWeight::Weight900),
72 other => Some(PdfFontWeight::Custom(other as u32)),
73 }
74 }
75}
76
77/// A single font used to render text in a [PdfDocument].
78///
79/// The PDF specification defines 14 built-in fonts that can be used in any PDF file without
80/// font embedding. Additionally, custom fonts can be directly embedded into any PDF file as
81/// a data stream.
82pub struct PdfFont<'a> {
83 built_in: Option<PdfFontBuiltin>,
84 handle: FPDF_FONT,
85 glyphs: PdfFontGlyphs<'a>,
86 is_font_memory_loaded: bool,
87 lifetime: PhantomData<&'a FPDF_FONT>,
88}
89
90impl<'a> PdfFont<'a> {
91 #[inline]
92 pub(crate) fn from_pdfium(
93 handle: FPDF_FONT,
94 built_in: Option<PdfFontBuiltin>,
95 is_font_memory_loaded: bool,
96 ) -> Self {
97 PdfFont {
98 built_in,
99 handle,
100 glyphs: PdfFontGlyphs::from_pdfium(handle),
101 is_font_memory_loaded,
102 lifetime: PhantomData,
103 }
104 }
105
106 /// Returns the internal `FPDF_FONT` handle for this [PdfFont].
107 #[inline]
108 pub(crate) fn handle(&self) -> FPDF_FONT {
109 self.handle
110 }
111
112 #[cfg(any(
113 feature = "pdfium_future",
114 feature = "pdfium_7763",
115 feature = "pdfium_7543",
116 feature = "pdfium_7350",
117 feature = "pdfium_7215",
118 feature = "pdfium_7123",
119 feature = "pdfium_6996",
120 feature = "pdfium_6721",
121 feature = "pdfium_6666"
122 ))]
123 /// Returns the name of this [PdfFont].
124 pub fn name(&self) -> String {
125 // Retrieving the font name from Pdfium is a two-step operation. First, we call
126 // FPDFFont_GetBaseFontName() with a null buffer; this will retrieve the length of
127 // the font name in bytes. If the length is zero, then there is no font name.
128
129 // If the length is non-zero, then we reserve a byte buffer of the given
130 // length and call FPDFFont_GetBaseFontName() again with a pointer to the buffer;
131 // this will write the font name into the buffer. Unlike most text handling in
132 // Pdfium, font names are returned in UTF-8 format.
133
134 let buffer_length = unsafe {
135 self.bindings()
136 .FPDFFont_GetBaseFontName(self.handle, std::ptr::null_mut(), 0)
137 };
138
139 if buffer_length == 0 {
140 // The font name is not present.
141
142 return String::new();
143 }
144
145 let mut buffer = create_byte_buffer(buffer_length as usize);
146
147 let result = unsafe {
148 self.bindings().FPDFFont_GetBaseFontName(
149 self.handle,
150 buffer.as_mut_ptr() as *mut c_char,
151 buffer_length,
152 )
153 };
154
155 assert_eq!(result, buffer_length);
156
157 String::from_utf8(buffer)
158 // Trim any trailing nulls. All strings returned from Pdfium are generally terminated
159 // by one null byte.
160 .map(|str| str.trim_end_matches(char::from(0)).to_owned())
161 .unwrap_or_else(|_| String::new())
162 }
163
164 /// Returns the family of this [PdfFont].
165 pub fn family(&self) -> String {
166 // Retrieving the family name from Pdfium is a two-step operation. First, we call
167 // FPDFFont_GetFamilyName() with a null buffer; this will retrieve the length of
168 // the font name in bytes. If the length is zero, then there is no font name.
169
170 // If the length is non-zero, then we reserve a byte buffer of the given
171 // length and call FPDFFont_GetFamilyName() again with a pointer to the buffer;
172 // this will write the font name into the buffer. Unlike most text handling in
173 // Pdfium, font names are returned in UTF-8 format.
174
175 #[cfg(any(
176 feature = "pdfium_future",
177 feature = "pdfium_7763",
178 feature = "pdfium_7543",
179 feature = "pdfium_7350",
180 feature = "pdfium_7215",
181 feature = "pdfium_7123",
182 feature = "pdfium_6996",
183 feature = "pdfium_6721",
184 feature = "pdfium_6666",
185 feature = "pdfium_6611"
186 ))]
187 let buffer_length = unsafe {
188 self.bindings()
189 .FPDFFont_GetFamilyName(self.handle, std::ptr::null_mut(), 0)
190 };
191
192 #[cfg(any(
193 feature = "pdfium_6569",
194 feature = "pdfium_6555",
195 feature = "pdfium_6490",
196 feature = "pdfium_6406",
197 feature = "pdfium_6337",
198 feature = "pdfium_6295",
199 feature = "pdfium_6259",
200 feature = "pdfium_6164",
201 feature = "pdfium_6124",
202 feature = "pdfium_6110",
203 feature = "pdfium_6084",
204 feature = "pdfium_6043",
205 feature = "pdfium_6015",
206 feature = "pdfium_5961"
207 ))]
208 let buffer_length = unsafe {
209 self.bindings()
210 .FPDFFont_GetFontName(self.handle, std::ptr::null_mut(), 0)
211 };
212
213 if buffer_length == 0 {
214 // The font name is not present.
215
216 return String::new();
217 }
218
219 let mut buffer = create_byte_buffer(buffer_length as usize);
220
221 #[cfg(any(
222 feature = "pdfium_future",
223 feature = "pdfium_7763",
224 feature = "pdfium_7543",
225 feature = "pdfium_7350",
226 feature = "pdfium_7215",
227 feature = "pdfium_7123",
228 feature = "pdfium_6996",
229 feature = "pdfium_6721",
230 feature = "pdfium_6666",
231 feature = "pdfium_6611"
232 ))]
233 let result = unsafe {
234 self.bindings().FPDFFont_GetFamilyName(
235 self.handle,
236 buffer.as_mut_ptr() as *mut c_char,
237 buffer_length,
238 )
239 };
240
241 #[cfg(any(
242 feature = "pdfium_6569",
243 feature = "pdfium_6555",
244 feature = "pdfium_6490",
245 feature = "pdfium_6406",
246 feature = "pdfium_6337",
247 feature = "pdfium_6295",
248 feature = "pdfium_6259",
249 feature = "pdfium_6164",
250 feature = "pdfium_6124",
251 feature = "pdfium_6110",
252 feature = "pdfium_6084",
253 feature = "pdfium_6043",
254 feature = "pdfium_6015",
255 feature = "pdfium_5961"
256 ))]
257 let result = unsafe {
258 self.bindings().FPDFFont_GetFontName(
259 self.handle,
260 buffer.as_mut_ptr() as *mut c_char,
261 buffer_length,
262 )
263 };
264
265 assert_eq!(result, buffer_length);
266
267 String::from_utf8(buffer)
268 // Trim any trailing nulls. All strings returned from Pdfium are generally terminated
269 // by one null byte.
270 .map(|str| str.trim_end_matches(char::from(0)).to_owned())
271 .unwrap_or_else(|_| String::new())
272 }
273
274 /// Returns the weight of this [PdfFont].
275 ///
276 /// Pdfium may not reliably return the correct value of this property for built-in fonts.
277 pub fn weight(&self) -> Result<PdfFontWeight, PdfiumError> {
278 PdfFontWeight::from_pdfium(unsafe { self.bindings().FPDFFont_GetWeight(self.handle) })
279 .ok_or(PdfiumError::PdfiumLibraryInternalError(
280 PdfiumInternalError::Unknown,
281 ))
282 }
283
284 /// Returns the italic angle of this [PdfFont]. The italic angle is the angle,
285 /// expressed in degrees counter-clockwise from the vertical, of the dominant vertical
286 /// strokes of the font. The value is zero for non-italic fonts, and negative for fonts
287 /// that slope to the right (as almost all italic fonts do).
288 ///
289 /// Pdfium may not reliably return the correct value of this property for built-in fonts.
290 pub fn italic_angle(&self) -> Result<i32, PdfiumError> {
291 let mut angle = 0;
292
293 if self.bindings().is_true(unsafe {
294 self.bindings()
295 .FPDFFont_GetItalicAngle(self.handle, &mut angle)
296 }) {
297 Ok(angle)
298 } else {
299 Err(PdfiumError::PdfiumLibraryInternalError(
300 PdfiumInternalError::Unknown,
301 ))
302 }
303 }
304
305 /// Returns the ascent of this [PdfFont] for the given font size. The ascent is the maximum
306 /// height above the baseline reached by glyphs in this font, excluding the height of glyphs
307 /// for accented characters.
308 pub fn ascent(&self, font_size: PdfPoints) -> Result<PdfPoints, PdfiumError> {
309 let mut ascent = 0.0;
310
311 if self.bindings().is_true(unsafe {
312 self.bindings()
313 .FPDFFont_GetAscent(self.handle, font_size.value, &mut ascent)
314 }) {
315 Ok(PdfPoints::new(ascent))
316 } else {
317 Err(PdfiumError::PdfiumLibraryInternalError(
318 PdfiumInternalError::Unknown,
319 ))
320 }
321 }
322
323 /// Returns the descent of this [PdfFont] for the given font size. The descent is the
324 /// maximum distance below the baseline reached by glyphs in this font, expressed as a
325 /// negative points value.
326 pub fn descent(&self, font_size: PdfPoints) -> Result<PdfPoints, PdfiumError> {
327 let mut descent = 0.0;
328
329 if self.bindings().is_true(unsafe {
330 self.bindings()
331 .FPDFFont_GetDescent(self.handle, font_size.value, &mut descent)
332 }) {
333 Ok(PdfPoints::new(descent))
334 } else {
335 Err(PdfiumError::PdfiumLibraryInternalError(
336 PdfiumInternalError::Unknown,
337 ))
338 }
339 }
340
341 /// Returns the raw font descriptor bitflags for the containing [PdfFont].
342 #[inline]
343 fn get_flags_bits(&self) -> FpdfFontDescriptorFlags {
344 FpdfFontDescriptorFlags::from_bits_truncate(unsafe {
345 self.bindings().FPDFFont_GetFlags(self.handle)
346 } as u32)
347 }
348
349 /// Returns `true` if all the glyphs in this [PdfFont] have the same width.
350 ///
351 /// Pdfium may not reliably return the correct value of this flag for built-in fonts.
352 pub fn is_fixed_pitch(&self) -> bool {
353 self.get_flags_bits()
354 .contains(FpdfFontDescriptorFlags::FIXED_PITCH_BIT_1)
355 }
356
357 /// Returns `true` if the glyphs in this [PdfFont] have variable widths.
358 ///
359 /// Pdfium may not reliably return the correct value of this flag for built-in fonts.
360 #[inline]
361 pub fn is_proportional_pitch(&self) -> bool {
362 !self.is_fixed_pitch()
363 }
364
365 /// Returns `true` if one or more glyphs in this [PdfFont] have serifs - short strokes
366 /// drawn at an angle on the top or bottom of glyph stems to decorate the glyphs.
367 /// For example, Times New Roman is a serif font.
368 ///
369 /// Pdfium may not reliably return the correct value of this flag for built-in fonts.
370 pub fn is_serif(&self) -> bool {
371 self.get_flags_bits()
372 .contains(FpdfFontDescriptorFlags::SERIF_BIT_2)
373 }
374
375 /// Returns `true` if no glyphs in this [PdfFont] have serifs - short strokes
376 /// drawn at an angle on the top or bottom of glyph stems to decorate the glyphs.
377 /// For example, Helvetica is a sans-serif font.
378 ///
379 /// Pdfium may not reliably return the correct value of this flag for built-in fonts.
380 #[inline]
381 pub fn is_sans_serif(&self) -> bool {
382 !self.is_serif()
383 }
384
385 /// Returns `true` if this [PdfFont] contains glyphs outside the Adobe standard Latin
386 /// character set.
387 ///
388 /// This classification of non-symbolic and symbolic fonts is peculiar to PDF. A font may
389 /// contain additional characters that are used in Latin writing systems but are outside the
390 /// Adobe standard Latin character set; PDF considers such a font to be symbolic.
391 ///
392 /// Pdfium may not reliably return the correct value of this flag for built-in fonts.
393 pub fn is_symbolic(&self) -> bool {
394 // This flag bit and the non-symbolic flag bit cannot both be set or both be clear.
395
396 self.get_flags_bits()
397 .contains(FpdfFontDescriptorFlags::SYMBOLIC_BIT_3)
398 }
399
400 /// Returns `true` if this [PdfFont] does not contain glyphs outside the Adobe standard
401 /// Latin character set.
402 ///
403 /// This classification of non-symbolic and symbolic fonts is peculiar to PDF. A font may
404 /// contain additional characters that are used in Latin writing systems but are outside the
405 /// Adobe standard Latin character set; PDF considers such a font to be symbolic.
406 ///
407 /// Pdfium may not reliably return the correct value of this flag for built-in fonts.
408 pub fn is_non_symbolic(&self) -> bool {
409 // This flag bit and the symbolic flag bit cannot both be set or both be clear.
410
411 self.get_flags_bits()
412 .contains(FpdfFontDescriptorFlags::NON_SYMBOLIC_BIT_6)
413 }
414
415 /// Returns `true` if the glyphs in this [PdfFont] are designed to resemble cursive handwriting.
416 ///
417 /// Pdfium may not reliably return the correct value of this flag for built-in fonts.
418 pub fn is_cursive(&self) -> bool {
419 self.get_flags_bits()
420 .contains(FpdfFontDescriptorFlags::SCRIPT_BIT_4)
421 }
422
423 /// Returns `true` if the glyphs in this [PdfFont] include dominant vertical strokes
424 /// that are slanted.
425 ///
426 /// The designed vertical stroke angle can be retrieved using the [PdfFont::italic_angle()] function.
427 ///
428 /// Pdfium may not reliably return the correct value of this flag for built-in fonts.
429 pub fn is_italic(&self) -> bool {
430 self.get_flags_bits()
431 .contains(FpdfFontDescriptorFlags::ITALIC_BIT_7)
432 }
433
434 /// Returns `true` if this [PdfFont] contains no lowercase letters by design.
435 ///
436 /// Pdfium may not reliably return the correct value of this flag for built-in fonts.
437 pub fn is_all_caps(&self) -> bool {
438 self.get_flags_bits()
439 .contains(FpdfFontDescriptorFlags::ALL_CAP_BIT_17)
440 }
441
442 /// Returns `true` if the lowercase letters in this [PdfFont] have the same shapes as the
443 /// corresponding uppercase letters but are sized proportionally so they have the same size
444 /// and stroke weight as lowercase glyphs in the same typeface family.
445 ///
446 /// Pdfium may not reliably return the correct value of this flag for built-in fonts.
447 pub fn is_small_caps(&self) -> bool {
448 self.get_flags_bits()
449 .contains(FpdfFontDescriptorFlags::SMALL_CAP_BIT_18)
450 }
451
452 /// Returns `true` if bold glyphs in this [PdfFont] are painted with extra pixels
453 /// at very small font sizes.
454 ///
455 /// Typically when glyphs are painted at small sizes on low-resolution devices, individual strokes
456 /// of bold glyphs may appear only one pixel wide. Because this is the minimum width of a pixel
457 /// based device, individual strokes of non-bold glyphs may also appear as one pixel wide
458 /// and therefore cannot be distinguished from bold glyphs. If this flag is set, individual
459 /// strokes of bold glyphs may be thickened at small font sizes.
460 ///
461 /// Pdfium may not reliably return the correct value of this flag for built-in fonts.
462 pub fn is_bold_reenforced(&self) -> bool {
463 self.get_flags_bits()
464 .contains(FpdfFontDescriptorFlags::FORCE_BOLD_BIT_19)
465 }
466
467 /// Returns `true` if this [PdfFont] is an instance of one of the 14 built-in fonts
468 /// provided as part of the PDF specification.
469 #[inline]
470 pub fn is_built_in(&self) -> bool {
471 self.built_in.is_some()
472 }
473
474 /// Returns the [PdfFontBuiltin] type of this built-in font, or `None` if this font is
475 /// not one of the 14 built-in fonts provided as part of the PDF specification.
476 #[inline]
477 pub fn built_in(&self) -> Option<PdfFontBuiltin> {
478 self.built_in
479 }
480
481 /// Returns `true` if the data for this [PdfFont] is embedded in the containing [PdfDocument].
482 pub fn is_embedded(&self) -> Result<bool, PdfiumError> {
483 let result = unsafe { self.bindings().FPDFFont_GetIsEmbedded(self.handle) };
484
485 match result {
486 1 => Ok(true),
487 0 => Ok(false),
488 _ => Err(PdfiumError::PdfiumLibraryInternalError(
489 PdfiumInternalError::Unknown,
490 )),
491 }
492 }
493
494 /// Writes this [PdfFont] to a new byte buffer, returning the byte buffer.
495 ///
496 /// If this [PdfFont] is not embedded in the containing [PdfDocument], then the data
497 /// returned will be for the substitution font instead.
498 pub fn data(&self) -> Result<Vec<u8>, PdfiumError> {
499 // Retrieving the font data from Pdfium is a two-step operation. First, we call
500 // FPDFFont_GetFontData() with a null buffer; this will retrieve the length of
501 // the data in bytes. If the length is zero, then there is no data associated
502 // with this font.
503
504 // If the length is non-zero, then we reserve a byte buffer of the given
505 // length and call FPDFFont_GetFontData() again with a pointer to the buffer;
506 // this will write the font data to the buffer.
507
508 let mut out_buflen: usize = 0;
509
510 if self.bindings().is_true(unsafe {
511 self.bindings().FPDFFont_GetFontData(
512 self.handle,
513 std::ptr::null_mut(),
514 0,
515 &mut out_buflen,
516 )
517 }) {
518 // out_buflen now contains the length of the font data.
519
520 let buffer_length = out_buflen;
521
522 let mut buffer = create_byte_buffer(buffer_length);
523
524 let result = unsafe {
525 self.bindings().FPDFFont_GetFontData(
526 self.handle,
527 buffer.as_mut_ptr(),
528 buffer_length,
529 &mut out_buflen,
530 )
531 };
532
533 assert!(self.bindings().is_true(result));
534 assert_eq!(buffer_length, out_buflen);
535
536 Ok(buffer)
537 } else {
538 Err(PdfiumError::PdfiumLibraryInternalError(
539 PdfiumInternalError::Unknown,
540 ))
541 }
542 }
543
544 /// Returns a collection of all the [PdfFontGlyphs] defined for this [PdfFont] in the containing
545 /// `PdfDocument`.
546 ///
547 /// Note that documents typically include only the specific glyphs they need from any given font,
548 /// not the entire font glyphset. This is a PDF feature known as font subsetting. The collection
549 /// of glyphs returned by this function may therefore not cover the entire font glyphset.
550 #[inline]
551 pub fn glyphs(&self) -> &PdfFontGlyphs<'_> {
552 self.glyphs.initialize_len();
553 &self.glyphs
554 }
555}
556
557impl<'a> Drop for PdfFont<'a> {
558 /// Closes this [PdfFont], releasing held memory.
559 #[inline]
560 fn drop(&mut self) {
561 // The documentation for FPDFText_LoadFont() and FPDFText_LoadStandardFont() both state
562 // that the font loaded by the function can be closed by calling FPDFFont_Close().
563 // I had taken this to mean that _any_ FPDF_Font handle returned from a Pdfium function
564 // should be closed via FPDFFont_Close(), but testing suggests this is not the case;
565 // rather, it is only fonts specifically loaded by calling FPDFText_LoadFont() or
566 // FPDFText_LoadStandardFont() that need to be actively closed.
567
568 // In other words, retrieving a handle to a font that already exists in a document evidently
569 // does not allocate any additional resources, so we don't need to free anything.
570 // (Indeed, if we try to, Pdfium segfaults.)
571
572 if self.is_font_memory_loaded {
573 unsafe {
574 self.bindings().FPDFFont_Close(self.handle);
575 }
576 }
577 }
578}
579
580impl<'a> PdfiumLibraryBindingsAccessor<'a> for PdfFont<'a> {}
581
582#[cfg(feature = "thread_safe")]
583unsafe impl<'a> Send for PdfFont<'a> {}
584
585#[cfg(feature = "thread_safe")]
586unsafe impl<'a> Sync for PdfFont<'a> {}