Skip to main content

wuff_capi/
lib.rs

1//! C API for the [wuff](https://docs.rs/wuff) WOFF2 decoder.
2//!
3//! This crate exposes `extern "C"` symbols wrapping wuff's WOFF2 decoder, plus
4//! (in its `include` directory) C++ headers (`woff2/decode.h` and
5//! `woff2/output.h`) which reimplement the decoding API of the
6//! [woff2](https://github.com/google/woff2) C++ library on top of those
7//! symbols. This allows the crate to be used as a drop-in replacement for the
8//! woff2 library by C/C++ code (such as the `ots` sanitiser) that consumes
9//! only its decoding API.
10//!
11//! Build scripts of dependent crates can locate the headers via the
12//! `DEP_WUFF_INCLUDE_DIR` environment variable.
13
14use std::panic::catch_unwind;
15
16/// Compute the size of the final uncompressed font, or 0 on error.
17///
18/// This reads the `totalSfntSize` field of the WOFF2 header. It is the
19/// C equivalent of the woff2 library's `woff2::ComputeWOFF2FinalSize`.
20///
21/// # Safety
22///
23/// `data` must either be null (in which case 0 is returned) or point to
24/// `length` bytes of readable memory.
25#[unsafe(no_mangle)]
26pub unsafe extern "C" fn wuff_woff2_compute_final_size(data: *const u8, length: usize) -> usize {
27    if data.is_null() || length < 20 {
28        return 0;
29    }
30    let bytes = unsafe { std::slice::from_raw_parts(data, length) };
31    u32::from_be_bytes(bytes[16..20].try_into().unwrap()) as usize
32}
33
34/// Decompress a WOFF2 font into a newly-allocated buffer.
35///
36/// On success, returns a pointer to the decompressed font and stores its
37/// length in `*result_length`. The buffer must be freed by passing the
38/// returned pointer and length to [`wuff_woff2_free`].
39///
40/// On failure, returns null and stores 0 in `*result_length`.
41///
42/// # Safety
43///
44/// - `data` must either be null (in which case null is returned) or point to
45///   `length` bytes of readable memory.
46/// - `result_length` must be a valid pointer to a writable `size_t`.
47#[unsafe(no_mangle)]
48pub unsafe extern "C" fn wuff_woff2_decode(
49    data: *const u8,
50    length: usize,
51    result_length: *mut usize,
52) -> *mut u8 {
53    unsafe { *result_length = 0 };
54    if data.is_null() {
55        return std::ptr::null_mut();
56    }
57    let bytes = unsafe { std::slice::from_raw_parts(data, length) };
58
59    // Catch panics: unwinding across an `extern "C"` boundary would abort.
60    let result = catch_unwind(|| wuff::decompress_woff2(bytes));
61    match result {
62        Ok(Ok(decompressed)) => {
63            let boxed: Box<[u8]> = decompressed.into_boxed_slice();
64            let len = boxed.len();
65            let ptr = Box::into_raw(boxed) as *mut u8;
66            unsafe { *result_length = len };
67            ptr
68        }
69        Ok(Err(_)) | Err(_) => std::ptr::null_mut(),
70    }
71}
72
73/// Free a buffer previously returned by [`wuff_woff2_decode`].
74///
75/// # Safety
76///
77/// - `ptr` must either be null (in which case this is a no-op) or a pointer
78///   previously returned by [`wuff_woff2_decode`], with `length` being the
79///   value stored in `*result_length` by that call.
80/// - The buffer must not have already been freed.
81#[unsafe(no_mangle)]
82pub unsafe extern "C" fn wuff_woff2_free(ptr: *mut u8, length: usize) {
83    if ptr.is_null() {
84        return;
85    }
86    let slice = std::ptr::slice_from_raw_parts_mut(ptr, length);
87    drop(unsafe { Box::from_raw(slice) });
88}
89
90#[cfg(test)]
91mod tests {
92    use super::*;
93
94    #[test]
95    fn compute_final_size_null_and_short() {
96        unsafe {
97            assert_eq!(wuff_woff2_compute_final_size(std::ptr::null(), 0), 0);
98            let short = [0u8; 19];
99            assert_eq!(
100                wuff_woff2_compute_final_size(short.as_ptr(), short.len()),
101                0
102            );
103        }
104    }
105
106    #[test]
107    fn compute_final_size_reads_total_sfnt_size() {
108        let mut header = [0u8; 48];
109        header[16..20].copy_from_slice(&0x0001_2345u32.to_be_bytes());
110        unsafe {
111            assert_eq!(
112                wuff_woff2_compute_final_size(header.as_ptr(), header.len()),
113                0x0001_2345
114            );
115        }
116    }
117
118    #[test]
119    fn decode_invalid_data_returns_null() {
120        let garbage = [0u8; 64];
121        let mut len = usize::MAX;
122        let ptr = unsafe { wuff_woff2_decode(garbage.as_ptr(), garbage.len(), &mut len) };
123        assert!(ptr.is_null());
124        assert_eq!(len, 0);
125    }
126
127    #[test]
128    fn free_null_is_noop() {
129        unsafe { wuff_woff2_free(std::ptr::null_mut(), 0) };
130    }
131}