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