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}