Skip to main content

pdfium_render/pdf/font/
provider.rs

1//! Defines the [PdfiumCustomFontProvider] trait, used to define and configure custom
2//! font providers for a Pdfium instance to use in place of the default system font provider.
3
4use crate::bindgen::{
5    FPDF_BOOL, FPDF_SYSFONTINFO, FXFONT_FF_FIXEDPITCH, FXFONT_FF_ROMAN, FXFONT_FF_SCRIPT,
6};
7use crate::pdf::font::{PdfFontCharacterSet, PdfFontWeight};
8use std::collections::HashMap;
9use std::ffi::{CStr, CString};
10use std::os::raw::{c_char, c_int, c_uchar, c_uint, c_ulong, c_void};
11use std::panic;
12use std::str::FromStr;
13
14/// A single custom font lookup request from Pdfium.
15pub struct PdfiumCustomFontProviderRequest {
16    /// The font face of the requested custom font.
17    pub font_face: String,
18
19    /// The character set of the requested custom font.
20    pub character_set: PdfFontCharacterSet,
21
22    /// The weight of the requested custom font.
23    pub weight: PdfFontWeight,
24
25    /// `true` if the glyphs in the requested custom font should include dominant vertical
26    /// strokes that are slanted.
27    pub is_italic: bool,
28
29    /// `true` if all the glyphs in the requested custom font should have the same width.
30    pub is_fixed_pitch: bool,
31
32    /// `true` if the glyphs in the requested custom font should have serifs - short strokes
33    /// drawn at an angle on the top or bottom of glyph stems to decorate the glyphs.
34    /// For example, Times New Roman is a serif font.
35    pub is_serif: bool,
36
37    /// `true` if the glyphs in the requested custom font should be designed to resemble
38    /// cursive handwriting.
39    pub is_cursive: bool,
40}
41
42#[cfg(feature = "thread_safe")]
43unsafe impl Sync for PdfiumCustomFontProviderRequest {}
44
45#[cfg(feature = "thread_safe")]
46unsafe impl Send for PdfiumCustomFontProviderRequest {}
47
48/// The unique ID of a single custom font lookup result.
49pub type PdfiumCustomFontHandle = u64;
50
51/// The response to a single custom font lookup request from Pdfium.
52pub struct PdfiumCustomFontProviderResponse {
53    /// A unique ID for the custom font provided in this response. Pdfium will use this
54    /// value as a font handle in all subsequent calls related to this font.
55    pub id: PdfiumCustomFontHandle,
56
57    /// The font face of the custom font provided in this response.
58    pub font_face: String,
59
60    /// The character set of the custom font provided in this response.
61    pub character_set: PdfFontCharacterSet,
62
63    /// The raw font byte data for the custom font provided in this response, in either
64    /// OpenType or TrueType format.
65    pub data: Vec<u8>,
66}
67
68#[cfg(feature = "thread_safe")]
69unsafe impl Sync for PdfiumCustomFontProviderResponse {}
70
71#[cfg(feature = "thread_safe")]
72unsafe impl Send for PdfiumCustomFontProviderResponse {}
73
74/// At trait that responds to a single custom font lookup request from Pdfium.
75pub trait PdfiumCustomFontProvider: Send + Sync {
76    /// Responds to a single custom font lookup request from Pdfium, returning either a valid
77    /// response if the font is available or `None` if the font is not available to this
78    /// provider implementation.
79    fn provide(
80        &mut self,
81        request: PdfiumCustomFontProviderRequest,
82    ) -> Option<PdfiumCustomFontProviderResponse>;
83}
84
85#[repr(C)]
86#[allow(non_snake_case)]
87pub(crate) struct PdfiumCustomFontProviderExt {
88    // An extension of Pdfium's FPDF_SYSFONTINFO struct that adds extra fields to carry the
89    // user-provided PdfiumCustomFontProvider trait implementation and a cache of responses
90    // from the PdfiumCustomFontProvider implementation.
91    version: c_int,
92    Release: Option<unsafe extern "C" fn(pThis: *mut FPDF_SYSFONTINFO)>,
93    EnumFonts: Option<unsafe extern "C" fn(pThis: *mut FPDF_SYSFONTINFO, pMapper: *mut c_void)>,
94    MapFont: Option<
95        unsafe extern "C" fn(
96            pThis: *mut FPDF_SYSFONTINFO,
97            weight: c_int,
98            bItalic: FPDF_BOOL,
99            charset: c_int,
100            pitch_family: c_int,
101            face: *const c_char,
102            bExact: *mut FPDF_BOOL,
103        ) -> *mut c_void,
104    >,
105    GetFont: Option<
106        unsafe extern "C" fn(pThis: *mut FPDF_SYSFONTINFO, face: *const c_char) -> *mut c_void,
107    >,
108    GetFontData: Option<
109        unsafe extern "C" fn(
110            pThis: *mut FPDF_SYSFONTINFO,
111            hFont: *mut c_void,
112            table: c_uint,
113            buffer: *mut c_uchar,
114            buf_size: c_ulong,
115        ) -> c_ulong,
116    >,
117    GetFaceName: Option<
118        unsafe extern "C" fn(
119            pThis: *mut FPDF_SYSFONTINFO,
120            hFont: *mut c_void,
121            buffer: *mut c_char,
122            buf_size: c_ulong,
123        ) -> c_ulong,
124    >,
125    GetFontCharset:
126        Option<unsafe extern "C" fn(pThis: *mut FPDF_SYSFONTINFO, hFont: *mut c_void) -> c_int>,
127    DeleteFont: Option<unsafe extern "C" fn(pThis: *mut FPDF_SYSFONTINFO, hFont: *mut c_void)>,
128    provider: Box<dyn PdfiumCustomFontProvider>,
129    cache: HashMap<PdfiumCustomFontHandle, PdfiumCustomFontProviderResponse>,
130}
131
132impl PdfiumCustomFontProviderExt {
133    pub(crate) fn new(provider: Box<dyn PdfiumCustomFontProvider>) -> Self {
134        PdfiumCustomFontProviderExt {
135            version: 2,
136            EnumFonts: None, // not used in interface version 2
137            Release: Some(fpdf_sys_font_info_release),
138            MapFont: Some(fpdf_sys_font_info_map_font),
139            GetFont: None,
140            GetFontData: Some(fpdf_sys_font_info_get_font_data),
141            GetFaceName: Some(fpdf_sys_font_info_get_face_name),
142            GetFontCharset: Some(fpdf_sys_font_info_get_font_charset),
143            DeleteFont: Some(fpdf_sys_font_info_delete_font),
144            provider,
145            cache: HashMap::new(),
146        }
147    }
148
149    /// Returns an `FPDF_SYSFONTINFO` pointer suitable for passing to `FPDF_SetSystemFontInfo()`.
150    #[inline]
151    pub(crate) fn as_fpdf_sys_font_info_mut_ptr(&mut self) -> &mut FPDF_SYSFONTINFO {
152        unsafe { &mut *(self as *mut PdfiumCustomFontProviderExt as *mut FPDF_SYSFONTINFO) }
153    }
154}
155
156/// Unwraps a mutable reference to the underlying [PdfiumCustomFontProvider] from a `FPDF_SYSFONTINFO` pointer.
157#[allow(non_snake_case)]
158unsafe fn fpdf_sys_font_info_to_custom_font_provider<'a>(
159    pThis: *mut FPDF_SYSFONTINFO,
160) -> &'a mut PdfiumCustomFontProviderExt {
161    &mut *(pThis as *mut PdfiumCustomFontProviderExt)
162}
163
164// The `FPDF_SYSFONTINFO::Release` callback function invoked by Pdfium.
165#[allow(non_snake_case)]
166unsafe extern "C" fn fpdf_sys_font_info_release(pThis: *mut FPDF_SYSFONTINFO) {
167    fpdf_sys_font_info_to_custom_font_provider(pThis)
168        .cache
169        .clear();
170}
171
172// The `FPDF_SYSFONTINFO::MapFont` callback function invoked by Pdfium.
173#[allow(non_snake_case)]
174unsafe extern "C" fn fpdf_sys_font_info_map_font(
175    pThis: *mut FPDF_SYSFONTINFO,
176    weight: c_int,
177    bItalic: FPDF_BOOL,
178    charset: c_int,
179    pitch_family: c_int,
180    face: *const c_char,
181    _bExact: *mut FPDF_BOOL, // unused field
182) -> *mut c_void {
183    if pThis.is_null() || face.is_null() {
184        return std::ptr::null_mut();
185    }
186
187    let provider = fpdf_sys_font_info_to_custom_font_provider(pThis);
188
189    let result = provider.provider.provide(PdfiumCustomFontProviderRequest {
190        font_face: match CStr::from_ptr(face).to_str() {
191            Ok(font_face) => font_face.to_owned(),
192            Err(_) => return std::ptr::null_mut(),
193        },
194        character_set: match PdfFontCharacterSet::from_pdfium(charset) {
195            Some(character_set) => character_set,
196            None => return std::ptr::null_mut(),
197        },
198        weight: match PdfFontWeight::from_pdfium(weight) {
199            Some(weight) => weight,
200            None => return std::ptr::null_mut(),
201        },
202        is_italic: bItalic != 0,
203        is_fixed_pitch: pitch_family & (FXFONT_FF_FIXEDPITCH as i32) == 1,
204        is_serif: pitch_family & (FXFONT_FF_ROMAN as i32) == 1,
205        is_cursive: pitch_family & (FXFONT_FF_SCRIPT as i32) == 1,
206    });
207
208    match result {
209        Some(response) => {
210            let id = response.id;
211
212            provider.cache.insert(id, response);
213
214            id as *mut c_void
215        }
216        None => std::ptr::null_mut(),
217    }
218}
219
220// The `FPDF_SYSFONTINFO::GetFontData` callback function invoked by Pdfium.
221#[allow(non_snake_case)]
222unsafe extern "C" fn fpdf_sys_font_info_get_font_data(
223    pThis: *mut FPDF_SYSFONTINFO,
224    hFont: *mut c_void,
225    table: c_uint,
226    buffer: *mut c_uchar,
227    buf_size: c_ulong,
228) -> c_ulong {
229    if pThis.is_null() || hFont.is_null() {
230        return 0;
231    }
232
233    if table != 0 {
234        // We only support table == 0, i.e. returning the full font file.
235
236        return 0;
237    }
238
239    if let Some(response) = fpdf_sys_font_info_to_custom_font_provider(pThis)
240        .cache
241        .get(&(hFont as PdfiumCustomFontHandle))
242    {
243        let font_data = &response.data;
244
245        if !buffer.is_null() && buf_size as usize >= font_data.len() {
246            buffer.copy_from_nonoverlapping(font_data.as_ptr(), font_data.len());
247        }
248
249        font_data.len() as c_ulong
250    } else {
251        // Undefined behaviour: Pdfium called us with an opaque font handle that doesn't
252        // correspond to any cached response. This should never happen; we cannot directly
253        // communicate the failure to Pdfium, but we can at least safely return no data.
254
255        0
256    }
257}
258
259// The `FPDF_SYSFONTINFO::GetFaceName` callback function invoked by Pdfium.
260#[allow(non_snake_case)]
261unsafe extern "C" fn fpdf_sys_font_info_get_face_name(
262    pThis: *mut FPDF_SYSFONTINFO,
263    hFont: *mut c_void,
264    buffer: *mut c_char,
265    buf_size: c_ulong,
266) -> c_ulong {
267    if let Some(response) = fpdf_sys_font_info_to_custom_font_provider(pThis)
268        .cache
269        .get(&(hFont as PdfiumCustomFontHandle))
270    {
271        if let Ok(face_name) = CString::from_str(&response.font_face) {
272            let chars = face_name.as_bytes_with_nul();
273
274            if !buffer.is_null() && buf_size as usize >= chars.len() {
275                buffer.copy_from_nonoverlapping(chars.as_ptr() as *const i8, chars.len());
276            }
277
278            chars.len() as c_ulong
279        } else {
280            // Undefined behaviour: font face name cannot be converted into a C string.
281            // There is no mechanism for reporting the failure back to Pdfium, so we must abort.
282
283            panic!(
284                "Unable to convert face name to C string in fpdf_sys_font_info_get_face_name: {:?}",
285                &response.font_face
286            );
287        }
288    } else {
289        // Undefined behaviour: Pdfium called us with an opaque font handle that doesn't
290        // correspond to any cached response. This should never happen, there is no mechanism
291        // for reporting the failure back to Pdfium, and so we must abort.
292
293        panic!(
294            "Unknown font handle received from Pdfium in fpdf_sys_font_info_get_face_name: {:?}",
295            hFont
296        );
297    }
298}
299
300// The `FPDF_SYSFONTINFO::GetFontCharset` callback function invoked by Pdfium.
301#[allow(non_snake_case)]
302unsafe extern "C" fn fpdf_sys_font_info_get_font_charset(
303    pThis: *mut FPDF_SYSFONTINFO,
304    hFont: *mut c_void,
305) -> c_int {
306    if let Some(response) = fpdf_sys_font_info_to_custom_font_provider(pThis)
307        .cache
308        .get(&(hFont as PdfiumCustomFontHandle))
309    {
310        response.character_set.as_pdfium()
311    } else {
312        // Undefined behaviour: Pdfium called us with an opaque font handle that doesn't
313        // correspond to any cached response. This should never happen, there is no mechanism
314        // for reporting the failure back to Pdfium, and so we must abort.
315
316        panic!(
317            "Unknown font handle received from Pdfium in fpdf_sys_font_info_get_font_charset: {:?}",
318            hFont
319        );
320    }
321}
322
323// The `FPDF_SYSFONTINFO::DeleteFont` callback function invoked by Pdfium.
324#[allow(non_snake_case)]
325unsafe extern "C" fn fpdf_sys_font_info_delete_font(
326    pThis: *mut FPDF_SYSFONTINFO,
327    hFont: *mut c_void,
328) {
329    fpdf_sys_font_info_to_custom_font_provider(pThis)
330        .cache
331        .remove(&(hFont as PdfiumCustomFontHandle));
332}