use std::{marker::PhantomData, ptr::null_mut};
use crate::{
map::Map,
set::{CharSet, Set, TagSet, U32Set},
sys, AllocationError, FontFace, SubsettingError,
};
mod flags;
pub use flags::*;
pub struct SubsetInput(*mut sys::hb_subset_input_t);
impl SubsetInput {
#[doc(alias = "hb_subset_input_create_or_fail")]
pub fn new() -> Result<Self, AllocationError> {
let input = unsafe { sys::hb_subset_input_create_or_fail() };
if input.is_null() {
return Err(AllocationError);
}
Ok(Self(input))
}
#[doc(alias = "hb_subset_input_keep_everything")]
pub fn keep_everything(&mut self) {
unsafe { sys::hb_subset_input_keep_everything(self.as_raw()) }
}
#[doc(alias = "hb_subset_input_set_flags")]
#[doc(alias = "hb_subset_input_get_flags")]
pub fn flags(&mut self) -> FlagRef<'_> {
FlagRef(self, Flags(unsafe { sys::hb_subset_input_get_flags(self.as_raw()) }))
}
#[doc(alias = "hb_subset_input_glyph_set")]
#[doc(alias = "hb_subset_input_set")]
#[doc(alias = "HB_SUBSET_SETS_GLYPH_INDEX")]
pub fn glyph_set(&mut self) -> U32Set<'_> {
unsafe {
Set::from_raw(sys::hb_set_reference(sys::hb_subset_input_glyph_set(self.as_raw())))
}
}
#[doc(alias = "hb_subset_input_unicode_set")]
#[doc(alias = "hb_subset_input_set")]
#[doc(alias = "HB_SUBSET_SETS_UNICODE")]
pub fn unicode_set(&mut self) -> CharSet<'_> {
unsafe {
Set::from_raw(sys::hb_set_reference(sys::hb_subset_input_unicode_set(self.as_raw())))
}
}
#[doc(alias = "hb_subset_input_set")]
#[doc(alias = "HB_SUBSET_SETS_NO_SUBSET_TABLE_TAG")]
pub fn no_subset_table_tag_set(&mut self) -> TagSet<'_> {
unsafe {
Set::from_raw(sys::hb_set_reference(sys::hb_subset_input_set(
self.as_raw(),
sys::hb_subset_sets_t::NO_SUBSET_TABLE_TAG,
)))
}
}
#[doc(alias = "hb_subset_input_set")]
#[doc(alias = "HB_SUBSET_SETS_DROP_TABLE_TAG")]
pub fn drop_table_tag_set(&mut self) -> TagSet<'_> {
unsafe {
Set::from_raw(sys::hb_set_reference(sys::hb_subset_input_set(
self.as_raw(),
sys::hb_subset_sets_t::DROP_TABLE_TAG,
)))
}
}
#[doc(alias = "hb_subset_input_set")]
#[doc(alias = "HB_SUBSET_SETS_NAME_ID")]
pub fn name_id_set(&mut self) -> U32Set<'_> {
unsafe {
Set::from_raw(sys::hb_set_reference(sys::hb_subset_input_set(
self.as_raw(),
sys::hb_subset_sets_t::NAME_ID,
)))
}
}
#[doc(alias = "hb_subset_input_set")]
#[doc(alias = "HB_SUBSET_SETS_NAME_LANG_ID")]
pub fn name_lang_id_set(&mut self) -> U32Set<'_> {
unsafe {
Set::from_raw(sys::hb_set_reference(sys::hb_subset_input_set(
self.as_raw(),
sys::hb_subset_sets_t::NAME_LANG_ID,
)))
}
}
#[doc(alias = "hb_subset_input_set")]
#[doc(alias = "HB_SUBSET_SETS_LAYOUT_FEATURE_TAG")]
pub fn layout_feature_tag_set(&mut self) -> TagSet<'_> {
unsafe {
Set::from_raw(sys::hb_set_reference(sys::hb_subset_input_set(
self.as_raw(),
sys::hb_subset_sets_t::LAYOUT_FEATURE_TAG,
)))
}
}
#[doc(alias = "hb_subset_input_set")]
#[doc(alias = "HB_SUBSET_SETS_LAYOUT_SCRIPT_TAG")]
pub fn layout_script_tag_set(&mut self) -> TagSet<'_> {
unsafe {
Set::from_raw(sys::hb_set_reference(sys::hb_subset_input_set(
self.as_raw(),
sys::hb_subset_sets_t::LAYOUT_SCRIPT_TAG,
)))
}
}
#[doc(alias = "hb_subset_input_old_to_new_glyph_mapping")]
pub fn old_to_new_glyph_mapping(&mut self) -> Map<'_, u32, u32> {
unsafe {
Map::from_raw(sys::hb_map_reference(sys::hb_subset_input_old_to_new_glyph_mapping(
self.as_raw(),
)))
}
}
#[doc(alias = "hb_subset_or_fail")]
pub fn subset_font(&self, font: &FontFace<'_>) -> Result<FontFace<'static>, SubsettingError> {
let face = unsafe { sys::hb_subset_or_fail(font.as_raw(), self.as_raw()) };
if face.is_null() {
return Err(SubsettingError);
}
Ok(unsafe { FontFace::from_raw(face) })
}
#[doc(alias = "hb_subset_plan_create_or_fail")]
pub fn plan<'f>(&self, font: &'f FontFace<'_>) -> Result<SubsetPlan<'f, '_>, SubsettingError> {
let plan = unsafe { sys::hb_subset_plan_create_or_fail(font.as_raw(), self.as_raw()) };
if plan.is_null() {
return Err(SubsettingError);
}
Ok(unsafe { SubsetPlan::from_raw(plan) })
}
}
impl SubsetInput {
pub fn into_raw(self) -> *mut sys::hb_subset_input_t {
let ptr = self.0;
std::mem::forget(self);
ptr
}
pub fn as_raw(&self) -> *mut sys::hb_subset_input_t {
self.0
}
pub unsafe fn from_raw(subset: *mut sys::hb_subset_input_t) -> Self {
Self(subset)
}
}
impl Drop for SubsetInput {
#[doc(alias = "hb_subset_input_destroy")]
fn drop(&mut self) {
unsafe { sys::hb_subset_input_destroy(self.0) }
}
}
pub struct SubsetPlan<'f, 'b> {
plan: *mut sys::hb_subset_plan_t,
unicode_to_old_glyph_mapping: Map<'static, char, u32>,
new_to_old_glyph_mapping: Map<'static, u32, u32>,
old_to_new_glyph_mapping: Map<'static, u32, u32>,
_font: PhantomData<&'f FontFace<'b>>,
}
impl<'f, 'b> SubsetPlan<'f, 'b> {
#[doc(alias = "hb_subset_plan_execute_or_fail")]
pub fn subset(&self) -> Result<FontFace<'b>, SubsettingError> {
let font = unsafe { sys::hb_subset_plan_execute_or_fail(self.as_raw()) };
if font.is_null() {
return Err(SubsettingError);
}
Ok(unsafe { FontFace::from_raw(font) })
}
#[doc(alias = "hb_subset_plan_unicode_to_old_glyph_mapping")]
pub fn unicode_to_old_glyph_mapping(&self) -> &'_ Map<'_, char, u32> {
&self.unicode_to_old_glyph_mapping
}
#[doc(alias = "hb_subset_plan_new_to_old_glyph_mapping")]
pub fn new_to_old_glyph_mapping(&self) -> &'_ Map<'_, u32, u32> {
&self.new_to_old_glyph_mapping
}
#[doc(alias = "hb_subset_plan_old_to_new_glyph_mapping")]
pub fn old_to_new_glyph_mapping(&self) -> &'_ Map<'_, u32, u32> {
&self.old_to_new_glyph_mapping
}
}
impl<'f, 'b> SubsetPlan<'f, 'b> {
pub fn into_raw(mut self) -> *mut sys::hb_subset_plan_t {
let ptr = self.plan;
self.plan = null_mut();
ptr
}
pub fn as_raw(&self) -> *mut sys::hb_subset_plan_t {
self.plan
}
pub unsafe fn from_raw(plan: *mut sys::hb_subset_plan_t) -> Self {
let unicode_to_old_glyph_mapping = unsafe {
Map::from_raw(sys::hb_map_reference(sys::hb_subset_plan_unicode_to_old_glyph_mapping(
plan,
)))
};
let new_to_old_glyph_mapping = unsafe {
Map::from_raw(sys::hb_map_reference(sys::hb_subset_plan_new_to_old_glyph_mapping(plan)))
};
let old_to_new_glyph_mapping = unsafe {
Map::from_raw(sys::hb_map_reference(sys::hb_subset_plan_old_to_new_glyph_mapping(plan)))
};
Self {
plan,
unicode_to_old_glyph_mapping,
new_to_old_glyph_mapping,
old_to_new_glyph_mapping,
_font: PhantomData,
}
}
}
impl<'f, 'b> Drop for SubsetPlan<'f, 'b> {
#[doc(alias = "hb_subset_plan_destroy")]
fn drop(&mut self) {
if !self.plan.is_null() {
unsafe { sys::hb_subset_plan_destroy(self.plan) }
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{tests::NOTO_SANS, Blob};
#[test]
fn keep_everything_should_keep_all_codepoints_and_glyphs() {
let mut subset = SubsetInput::new().unwrap();
subset.keep_everything();
assert_eq!(subset.unicode_set().len(), u32::MAX as usize);
assert_eq!(subset.glyph_set().len(), u32::MAX as usize);
let orig = FontFace::new(Blob::from_file(NOTO_SANS).unwrap()).unwrap();
let new = subset.subset_font(&orig).unwrap();
assert_eq!(
orig.covered_codepoints().unwrap().len(),
new.covered_codepoints().unwrap().len()
);
assert_eq!(orig.glyph_count(), new.glyph_count());
}
#[test]
fn keeping_codepoints_should_keep_ligatures() {
let font = FontFace::new(Blob::from_file(NOTO_SANS).unwrap()).unwrap();
let mut subset = SubsetInput::new().unwrap();
subset.unicode_set().insert('f');
subset.unicode_set().insert('i');
let font = subset.subset_font(&font).unwrap();
assert_eq!(font.covered_codepoints().unwrap().len(), 2);
assert_eq!(font.glyph_count(), 6); }
#[test]
fn old_to_new_glyph_mapping() {
let font = FontFace::new(Blob::from_file(NOTO_SANS).unwrap()).unwrap();
let char_to_glyph = font.nominal_glyph_mapping().unwrap();
let mut subset = SubsetInput::new().unwrap();
subset
.old_to_new_glyph_mapping()
.insert(char_to_glyph.get('a').unwrap(), 5);
subset
.old_to_new_glyph_mapping()
.insert(char_to_glyph.get('b').unwrap(), 709);
subset.unicode_set().insert('a');
subset.unicode_set().insert('b');
let font = subset.subset_font(&font).unwrap();
assert_eq!(font.glyph_count(), 710);
let char_to_glyph = font.nominal_glyph_mapping().unwrap();
assert_eq!(char_to_glyph.get('a').unwrap(), 5);
assert_eq!(char_to_glyph.get('b').unwrap(), 709);
}
#[test]
fn convert_subset_into_raw_and_back() {
let subset = SubsetInput::new().unwrap();
let subset_ptr = subset.into_raw();
let subset = unsafe { SubsetInput::from_raw(subset_ptr) };
drop(subset);
}
#[test]
fn convert_plan_into_raw_and_back() {
let font = FontFace::new(Blob::from_file(NOTO_SANS).unwrap()).unwrap();
let subset = SubsetInput::new().unwrap();
let plan = subset.plan(&font).unwrap();
let plan_ptr = plan.into_raw();
let plan = unsafe { SubsetPlan::from_raw(plan_ptr) };
drop(plan);
}
}