hb_subset/
blob.rs

1use std::{
2    ffi::{c_char, CString},
3    marker::PhantomData,
4    ops::Deref,
5    os::unix::prelude::OsStrExt,
6    path::Path,
7    ptr::null_mut,
8    slice,
9};
10
11use crate::{sys, AllocationError};
12
13/// Blobs wrap a chunk of binary data.
14///
15/// Blob handles lifecycle management of data while it is passed between client and HarfBuzz. Blobs are primarily used
16/// to create font faces, but also to access font face tables, as well as pass around other binary data.
17pub struct Blob<'a>(*mut sys::hb_blob_t, PhantomData<&'a [u8]>);
18
19impl Blob<'static> {
20    /// Creates a new blob containing the data from the specified binary font file.
21    #[doc(alias = "hb_blob_create_from_file")]
22    #[doc(alias = "hb_blob_create_from_file_or_fail")]
23    pub fn from_file(path: impl AsRef<Path>) -> Result<Self, AllocationError> {
24        let path =
25            CString::new(path.as_ref().as_os_str().as_bytes()).map_err(|_| AllocationError)?;
26
27        let blob = unsafe { sys::hb_blob_create_from_file_or_fail(path.as_ptr()) };
28        if blob.is_null() {
29            return Err(AllocationError);
30        }
31        Ok(Self(blob, PhantomData))
32    }
33}
34
35impl<'a> Blob<'a> {
36    /// Creates a new blob object by wrapping a slice.
37    #[doc(alias = "hb_blob_create")]
38    #[doc(alias = "hb_blob_create_or_fail")]
39    pub fn from_bytes(buffer: &'a [u8]) -> Result<Self, AllocationError> {
40        let blob = unsafe {
41            sys::hb_blob_create_or_fail(
42                buffer.as_ptr() as *const c_char,
43                buffer.len().try_into().map_err(|_| AllocationError)?,
44                sys::hb_memory_mode_t_HB_MEMORY_MODE_READONLY,
45                null_mut(),
46                None,
47            )
48        };
49        if blob.is_null() {
50            return Err(AllocationError);
51        }
52        Ok(Self(blob, PhantomData))
53    }
54
55    /// Tests whether the blob is empty, i.e. its length is 0.
56    pub fn is_empty(&self) -> bool {
57        self.len() == 0
58    }
59
60    /// Returns the number of bytes in the blob.
61    #[doc(alias = "hb_blob_get_length")]
62    pub fn len(&self) -> usize {
63        (unsafe { sys::hb_blob_get_length(self.0) }) as usize
64    }
65}
66
67impl<'a> Blob<'a> {
68    /// Converts the blob into raw [`sys::hb_blob_t`] pointer.
69    ///
70    /// This method transfers the ownership of the blob to the caller. It is up to the caller to call
71    /// [`sys::hb_blob_destroy`] to free the pointer, or call [`Self::from_raw`] to convert it back into [`Blob`].
72    pub fn into_raw(self) -> *mut sys::hb_blob_t {
73        let ptr = self.0;
74        std::mem::forget(self);
75        ptr
76    }
77
78    /// Exposes the raw inner pointer without transferring the ownership.
79    ///
80    /// Unlike [`Self::into_raw`], this method does not transfer the ownership of the pointer to the caller.
81    pub fn as_raw(&self) -> *mut sys::hb_blob_t {
82        self.0
83    }
84
85    /// Constructs a blob from raw [`sys::hb_blob_t`] pointer.
86    ///
87    /// # Safety
88    /// The given `blob` pointer must either be constructed by some Harfbuzz function, or be returned from
89    /// [`Self::into_raw`].
90    pub unsafe fn from_raw(blob: *mut sys::hb_blob_t) -> Self {
91        Self(blob, PhantomData)
92    }
93}
94
95impl Deref for Blob<'_> {
96    type Target = [u8];
97
98    #[doc(alias = "hb_blob_get_data")]
99    fn deref(&self) -> &Self::Target {
100        let mut len = 0u32;
101        let data = unsafe { sys::hb_blob_get_data(self.0, &mut len as *mut u32) } as *const u8;
102        if data.is_null() {
103            // TODO: Consider returning an error instead
104            return &[];
105        }
106        unsafe { slice::from_raw_parts(data, len as usize) }
107    }
108}
109
110impl<'a> Drop for Blob<'a> {
111    #[doc(alias = "hb_blob_destroy")]
112    fn drop(&mut self) {
113        unsafe { sys::hb_blob_destroy(self.0) }
114    }
115}
116
117impl<'a> Clone for Blob<'a> {
118    #[doc(alias = "hb_blob_reference")]
119    fn clone(&self) -> Self {
120        Self(unsafe { sys::hb_blob_reference(self.0) }, PhantomData)
121    }
122}
123
124#[cfg(test)]
125mod tests {
126    use super::*;
127    use crate::tests::NOTO_SANS;
128
129    #[test]
130    fn empty_is_empty() {
131        assert!(Blob::from_bytes(&[]).unwrap().is_empty());
132    }
133
134    #[test]
135    fn non_empty_is_not_empty() {
136        assert!(!Blob::from_bytes(&[1, 2, 3]).unwrap().is_empty());
137    }
138
139    #[test]
140    fn len_works() {
141        assert_eq!(Blob::from_bytes(&[]).unwrap().len(), 0);
142        assert_eq!(Blob::from_bytes(&[1, 2, 3]).unwrap().len(), 3);
143    }
144
145    #[test]
146    fn content_is_correct() {
147        assert_eq!(&*Blob::from_bytes(&[1, 2, 3]).unwrap(), &[1, 2, 3]);
148    }
149
150    #[test]
151    fn from_file_loads_file() {
152        let correct = std::fs::read(NOTO_SANS).unwrap();
153        let blob = Blob::from_file(NOTO_SANS).unwrap();
154        assert_eq!(correct, &*blob);
155    }
156
157    #[test]
158    fn clone_refers_to_same_object() {
159        let b1 = Blob::from_bytes(&[1, 2, 3]).unwrap();
160        let b2 = b1.clone();
161        assert_eq!(&*b1, &[1, 2, 3]);
162        assert_eq!(&*b2, &[1, 2, 3]);
163        drop(b1);
164        assert_eq!(&*b2, &[1, 2, 3]);
165    }
166
167    #[test]
168    fn convert_into_raw_and_back() {
169        let blob = Blob::from_bytes(&[1, 2, 3]).unwrap();
170        let blob_ptr = blob.into_raw();
171        let blob = unsafe { Blob::from_raw(blob_ptr) };
172        drop(blob);
173    }
174}