use std::{
ffi::{c_char, CString},
marker::PhantomData,
ops::Deref,
path::Path,
ptr::null_mut,
slice,
};
use crate::{sys, AllocationError};
pub struct Blob<'a>(*mut sys::hb_blob_t, PhantomData<&'a [u8]>);
impl Blob<'static> {
#[doc(alias = "hb_blob_create_from_file")]
#[doc(alias = "hb_blob_create_from_file_or_fail")]
pub fn from_file(path: impl AsRef<Path>) -> Result<Self, AllocationError> {
let path = path.as_ref().to_string_lossy(); let path = CString::new(path.as_bytes()).map_err(|_| AllocationError)?;
let blob = unsafe { sys::hb_blob_create_from_file_or_fail(path.as_ptr()) };
if blob.is_null() {
return Err(AllocationError);
}
Ok(Self(blob, PhantomData))
}
}
impl<'a> Blob<'a> {
#[doc(alias = "hb_blob_create")]
#[doc(alias = "hb_blob_create_or_fail")]
pub fn from_bytes(buffer: &'a [u8]) -> Result<Self, AllocationError> {
let blob = unsafe {
sys::hb_blob_create_or_fail(
buffer.as_ptr() as *const c_char,
buffer.len().try_into().map_err(|_| AllocationError)?,
sys::hb_memory_mode_t_HB_MEMORY_MODE_READONLY,
null_mut(),
None,
)
};
if blob.is_null() {
return Err(AllocationError);
}
Ok(Self(blob, PhantomData))
}
pub fn is_empty(&self) -> bool {
self.len() == 0
}
#[doc(alias = "hb_blob_get_length")]
pub fn len(&self) -> usize {
(unsafe { sys::hb_blob_get_length(self.0) }) as usize
}
}
impl<'a> Blob<'a> {
pub fn into_raw(self) -> *mut sys::hb_blob_t {
let ptr = self.0;
std::mem::forget(self);
ptr
}
pub fn as_raw(&self) -> *mut sys::hb_blob_t {
self.0
}
pub unsafe fn from_raw(blob: *mut sys::hb_blob_t) -> Self {
Self(blob, PhantomData)
}
}
impl Deref for Blob<'_> {
type Target = [u8];
#[doc(alias = "hb_blob_get_data")]
fn deref(&self) -> &Self::Target {
let mut len = 0u32;
let data = unsafe { sys::hb_blob_get_data(self.0, &mut len as *mut u32) } as *const u8;
if data.is_null() {
return &[];
}
unsafe { slice::from_raw_parts(data, len as usize) }
}
}
impl<'a> Drop for Blob<'a> {
#[doc(alias = "hb_blob_destroy")]
fn drop(&mut self) {
unsafe { sys::hb_blob_destroy(self.0) }
}
}
impl<'a> Clone for Blob<'a> {
#[doc(alias = "hb_blob_reference")]
fn clone(&self) -> Self {
Self(unsafe { sys::hb_blob_reference(self.0) }, PhantomData)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::tests::NOTO_SANS;
#[test]
fn empty_is_empty() {
assert!(Blob::from_bytes(&[]).unwrap().is_empty());
}
#[test]
fn non_empty_is_not_empty() {
assert!(!Blob::from_bytes(&[1, 2, 3]).unwrap().is_empty());
}
#[test]
fn len_works() {
assert_eq!(Blob::from_bytes(&[]).unwrap().len(), 0);
assert_eq!(Blob::from_bytes(&[1, 2, 3]).unwrap().len(), 3);
}
#[test]
fn content_is_correct() {
assert_eq!(&*Blob::from_bytes(&[1, 2, 3]).unwrap(), &[1, 2, 3]);
}
#[test]
fn from_file_loads_file() {
let correct = std::fs::read(NOTO_SANS).unwrap();
let blob = Blob::from_file(NOTO_SANS).unwrap();
assert_eq!(correct, &*blob);
}
#[test]
fn clone_refers_to_same_object() {
let b1 = Blob::from_bytes(&[1, 2, 3]).unwrap();
let b2 = b1.clone();
assert_eq!(&*b1, &[1, 2, 3]);
assert_eq!(&*b2, &[1, 2, 3]);
drop(b1);
assert_eq!(&*b2, &[1, 2, 3]);
}
#[test]
fn convert_into_raw_and_back() {
let blob = Blob::from_bytes(&[1, 2, 3]).unwrap();
let blob_ptr = blob.into_raw();
let blob = unsafe { Blob::from_raw(blob_ptr) };
drop(blob);
}
}