use std::ffi::CStr;
use std::marker::PhantomData;
use std::ops::Deref;
use std::ptr::NonNull;
use std::{ptr, slice};
use tectonic_bridge_graphite2 as gr;
mod font_funcs;
pub mod ot;
#[doc(hidden)]
pub mod sys;
#[cfg(test)]
mod test_util;
pub use font_funcs::{FontFuncs, FontFuncsMut, FontFuncsRef, ImmutFontFuncs};
pub use sys::hb_buffer_content_type_t as BufferContentType;
pub use sys::hb_codepoint_t as Codepoint;
pub use sys::hb_direction_t as Direction;
pub use sys::hb_feature_t as Feature;
pub use sys::hb_glyph_extents_t as GlyphExtents;
pub use sys::hb_glyph_info_t as GlyphInfo;
pub use sys::hb_glyph_position_t as GlyphPosition;
pub use sys::hb_memory_mode_t as MemoryMode;
pub use sys::hb_ot_name_id_t as OtNameId;
pub use sys::hb_position_t as Position;
pub use sys::hb_segment_properties_t as SegmentProperties;
mod linkage {
#[allow(unused_imports)]
use tectonic_bridge_freetype2 as clippyrenamehack1;
#[allow(unused_imports)]
#[cfg(target_os = "macos")]
use tectonic_mac_core as clippyrenamehack2;
}
unsafe extern "C" fn dealloc<T: 'static>(user_data: *mut ()) {
let _ = unsafe { Box::from_raw(user_data.cast::<T>()) };
}
#[repr(transparent)]
pub struct Script(sys::hb_script_t);
impl Script {
pub const INVALID: Script = Script(sys::HB_SCRIPT_INVALID);
pub fn get_horizontal_direction(&self) -> Direction {
unsafe { sys::hb_script_get_horizontal_direction(self.0) }
}
}
#[derive(Copy, Clone, Debug, PartialEq)]
#[repr(transparent)]
pub struct Tag(sys::hb_tag_t);
impl Tag {
pub fn new(val: u32) -> Tag {
Tag(val)
}
#[allow(clippy::should_implement_trait)]
pub fn from_str(val: &str) -> Tag {
Tag(unsafe { sys::hb_tag_from_string(val.as_ptr().cast(), val.len() as libc::c_int) })
}
pub fn from_cstr(val: &CStr) -> Tag {
Tag(unsafe { sys::hb_tag_from_string(val.as_ptr(), -1) })
}
pub fn to_raw(self) -> u32 {
self.0
}
pub fn to_script(self) -> Script {
Script(unsafe { sys::hb_ot_tag_to_script(self.0) })
}
pub fn to_language(self) -> Language {
Language(unsafe { sys::hb_ot_tag_to_language(self.0) })
}
}
#[derive(Copy, Clone)]
pub struct Language(*mut sys::hb_language_impl_t);
impl Language {
pub fn from_string(str: &str) -> Language {
Language(unsafe {
sys::hb_language_from_string(str.as_ptr().cast(), str.len() as libc::c_int)
})
}
pub fn from_cstr(str: &CStr) -> Language {
Language(unsafe { sys::hb_language_from_string(str.as_ptr(), -1) })
}
pub fn to_string(&self) -> Option<&CStr> {
let ptr = unsafe { sys::hb_language_to_string(self.0) };
if ptr.is_null() {
None
} else {
Some(unsafe { CStr::from_ptr(ptr) })
}
}
}
impl Default for Language {
fn default() -> Language {
Language(unsafe { sys::hb_language_from_string(ptr::null(), -1) })
}
}
#[derive(Copy, Clone)]
pub struct BufferRef<'a>(NonNull<sys::hb_buffer_t>, PhantomData<&'a sys::hb_buffer_t>);
impl<'a> BufferRef<'a> {
fn as_ptr(self) -> *mut sys::hb_buffer_t {
self.0.as_ptr()
}
pub fn len(self) -> usize {
unsafe { sys::hb_buffer_get_length(self.as_ptr()) as usize }
}
pub fn is_empty(self) -> bool {
self.len() == 0
}
pub fn glyph_info(self) -> Option<&'a [GlyphInfo]> {
let mut len = 0;
let ptr = unsafe { sys::hb_buffer_get_glyph_infos(self.as_ptr(), &mut len) };
if ptr.is_null() {
None
} else {
Some(unsafe { slice::from_raw_parts(ptr, len as usize) })
}
}
pub fn glyph_positions(self) -> Option<&'a [GlyphPosition]> {
let mut len = 0;
let ptr = unsafe { sys::hb_buffer_get_glyph_positions(self.as_ptr(), &mut len) };
if ptr.is_null() {
None
} else {
Some(unsafe { slice::from_raw_parts(ptr, len as usize) })
}
}
pub fn get_script(self) -> Script {
Script(unsafe { sys::hb_buffer_get_script(self.as_ptr()) })
}
pub fn get_segment_properties(self) -> SegmentProperties {
let mut props = SegmentProperties::default();
unsafe { sys::hb_buffer_get_segment_properties(self.as_ptr(), &mut props) };
props
}
}
pub struct BufferMut<'a>(BufferRef<'a>, PhantomData<&'a mut sys::hb_buffer_t>);
impl BufferMut<'_> {
fn as_ptr_mut(&mut self) -> *mut sys::hb_buffer_t {
self.0.as_ptr()
}
pub fn set_content_type(&mut self, content: BufferContentType) {
unsafe { sys::hb_buffer_set_content_type(self.as_ptr_mut(), content) }
}
pub fn set_direction(&mut self, direction: Direction) {
unsafe { sys::hb_buffer_set_direction(self.as_ptr_mut(), direction) }
}
pub fn set_language(&mut self, lang: Language) {
unsafe { sys::hb_buffer_set_language(self.as_ptr_mut(), lang.0) }
}
pub fn set_script(&mut self, script: Script) {
unsafe { sys::hb_buffer_set_script(self.as_ptr_mut(), script.0) }
}
pub fn add_utf16(&mut self, text: &[u16]) {
unsafe {
sys::hb_buffer_add_utf16(
self.as_ptr_mut(),
text.as_ptr(),
text.len() as libc::c_int,
0,
text.len() as libc::c_int,
)
}
}
pub fn guess_segment_properties(&mut self) {
unsafe { sys::hb_buffer_guess_segment_properties(self.as_ptr_mut()) }
}
pub fn reset(&mut self) {
unsafe { sys::hb_buffer_reset(self.as_ptr_mut()) }
}
}
impl<'a> Deref for BufferMut<'a> {
type Target = BufferRef<'a>;
fn deref(&self) -> &Self::Target {
&self.0
}
}
pub struct Buffer(NonNull<sys::hb_buffer_t>);
impl Buffer {
pub fn new() -> Buffer {
let ptr = unsafe { sys::hb_buffer_create() };
Buffer(NonNull::new(ptr).unwrap())
}
pub fn as_ref(&self) -> BufferRef<'_> {
BufferRef(self.0, PhantomData)
}
pub fn as_mut(&mut self) -> BufferMut<'_> {
BufferMut(self.as_ref(), PhantomData)
}
}
impl Default for Buffer {
fn default() -> Self {
Self::new()
}
}
impl Drop for Buffer {
fn drop(&mut self) {
unsafe { sys::hb_buffer_destroy(self.0.as_ptr()) }
}
}
pub struct Blob(NonNull<sys::hb_blob_t>);
impl Blob {
pub fn new(data: Vec<u8>) -> Blob {
unsafe extern "C" fn blob_dealloc(ptr: *mut ()) {
let slice = Box::from_raw(ptr.cast::<(*mut (), usize)>());
let _ = Box::from_raw(ptr::slice_from_raw_parts_mut(slice.0.cast::<u8>(), slice.1));
}
let len = data.len();
let data = Box::into_raw(data.into_boxed_slice());
let slice_data = Box::into_raw(Box::new((data.cast::<u8>(), len)));
let raw = unsafe {
sys::hb_blob_create(
data.cast(),
len as libc::c_uint,
MemoryMode::Writable,
slice_data.cast(),
Some(blob_dealloc),
)
};
Blob(NonNull::new(raw).unwrap())
}
}
#[derive(Copy, Clone, Debug, PartialEq)]
pub enum GTag {
GPos,
GSub,
}
impl GTag {
fn as_tag(self) -> Tag {
Tag::new(match self {
GTag::GPos => sys::HB_OT_TAG_GPOS,
GTag::GSub => sys::HB_OT_TAG_GSUB,
})
}
}
#[derive(Copy, Clone)]
pub struct FaceRef<'a>(NonNull<sys::hb_face_t>, PhantomData<&'a sys::hb_face_t>);
impl<'a> FaceRef<'a> {
unsafe fn from_raw(ptr: NonNull<sys::hb_face_t>) -> FaceRef<'a> {
FaceRef(ptr, PhantomData)
}
fn as_ptr(self) -> *mut sys::hb_face_t {
self.0.as_ptr()
}
pub fn has_ot_math_data(self) -> bool {
unsafe { sys::hb_ot_math_has_data(self.as_ptr()) != 0 }
}
pub fn ot_layout(self) -> ot::Layout<'a> {
ot::Layout(self)
}
pub fn gr_face(self) -> Option<gr::FaceRef<'a>> {
let ptr = unsafe { sys::hb_graphite2_face_get_gr_face(self.as_ptr()) };
NonNull::new(ptr).map(|ptr| unsafe { gr::FaceRef::from_raw(ptr) })
}
}
pub struct FaceMut<'a>(FaceRef<'a>, PhantomData<&'a mut sys::hb_face_t>);
impl FaceMut<'_> {
fn as_ptr_mut(&mut self) -> *mut sys::hb_face_t {
self.0.as_ptr()
}
pub fn set_index(&mut self, index: u32) {
unsafe { sys::hb_face_set_index(self.as_ptr_mut(), index as libc::c_uint) }
}
pub fn set_upem(&mut self, upem: u32) {
unsafe { sys::hb_face_set_upem(self.as_ptr_mut(), upem as libc::c_uint) }
}
}
pub struct Face(NonNull<sys::hb_face_t>);
impl Face {
pub fn new_tables<T: Fn(FaceRef<'_>, Tag) -> Option<Blob> + 'static>(f: T) -> Face {
unsafe extern "C" fn get_table<T: Fn(FaceRef<'_>, Tag) -> Option<Blob> + 'static>(
face: *mut sys::hb_face_t,
tag: sys::hb_tag_t,
user_data: *mut (),
) -> *mut sys::hb_blob_t {
let f = unsafe { &*user_data.cast::<T>() };
let face = NonNull::new(face).unwrap();
let face = unsafe { FaceRef::from_raw(face) };
match f(face, Tag(tag)) {
Some(blob) => blob.0.as_ptr(),
None => ptr::null_mut(),
}
}
let face = unsafe {
sys::hb_face_create_for_tables(
get_table::<T>,
Box::into_raw(Box::new(f)).cast(),
Some(dealloc::<T>),
)
};
Face(NonNull::new(face).unwrap())
}
pub fn as_ref(&self) -> FaceRef<'_> {
FaceRef(self.0, PhantomData)
}
pub fn as_mut(&mut self) -> FaceMut<'_> {
FaceMut(self.as_ref(), PhantomData)
}
}
impl Drop for Face {
fn drop(&mut self) {
unsafe { sys::hb_face_destroy(self.0.as_ptr()) }
}
}
#[derive(Copy, Clone)]
pub struct FontRef<'a>(NonNull<sys::hb_font_t>, PhantomData<&'a sys::hb_font_t>);
impl<'a> FontRef<'a> {
unsafe fn from_raw(ptr: NonNull<sys::hb_font_t>) -> FontRef<'a> {
FontRef(ptr, PhantomData)
}
#[doc(hidden)]
pub fn as_ptr(self) -> *mut sys::hb_font_t {
self.0.as_ptr()
}
pub fn face(self) -> FaceRef<'a> {
let ptr = unsafe { sys::hb_font_get_face(self.as_ptr()) };
FaceRef(NonNull::new(ptr).unwrap(), PhantomData)
}
pub fn ptem(self) -> f32 {
unsafe { sys::hb_font_get_ptem(self.as_ptr()) }
}
}
pub struct FontMut<'a>(FontRef<'a>, PhantomData<&'a mut sys::hb_font_t>);
impl FontMut<'_> {
fn as_ptr_mut(&mut self) -> *mut sys::hb_font_t {
self.0.as_ptr()
}
pub fn set_scale(&mut self, x: i32, y: i32) {
unsafe { sys::hb_font_set_scale(self.as_ptr_mut(), x as libc::c_int, y as libc::c_int) }
}
pub fn set_ppem(&mut self, x: u32, y: u32) {
unsafe { sys::hb_font_set_ppem(self.as_ptr_mut(), x as libc::c_uint, y as libc::c_uint) }
}
pub fn set_funcs<T>(&mut self, funcs: FontFuncsRef<'static, T>, data: T)
where
T: 'static,
{
let funcs = funcs.as_ptr();
unsafe {
sys::hb_font_set_funcs(
self.as_ptr_mut(),
funcs,
Box::into_raw(Box::new(data)).cast(),
Some(dealloc::<T>),
)
}
}
}
pub struct Font(NonNull<sys::hb_font_t>);
impl Font {
pub fn new(face: FaceRef<'_>) -> Font {
let ptr = unsafe { sys::hb_font_create(face.as_ptr()) };
Font(NonNull::new(ptr).unwrap())
}
pub fn as_ref(&self) -> FontRef<'_> {
FontRef(self.0, PhantomData)
}
pub fn as_mut(&mut self) -> FontMut<'_> {
FontMut(self.as_ref(), PhantomData)
}
}
impl Drop for Font {
fn drop(&mut self) {
unsafe { sys::hb_font_destroy(self.0.as_ptr()) }
}
}
pub struct ShapePlanRef<'a>(
NonNull<sys::hb_shape_plan_t>,
PhantomData<&'a sys::hb_shape_plan_t>,
);
impl ShapePlanRef<'_> {
fn as_ptr(&self) -> *mut sys::hb_shape_plan_t {
self.0.as_ptr()
}
pub fn get_shaper(&self) -> Option<&CStr> {
let ptr = unsafe { sys::hb_shape_plan_get_shaper(self.as_ptr()) };
if ptr.is_null() {
None
} else {
Some(unsafe { CStr::from_ptr(ptr) })
}
}
}
pub struct ShapePlanMut<'a>(ShapePlanRef<'a>, PhantomData<&'a mut sys::hb_shape_plan_t>);
impl ShapePlanMut<'_> {
fn as_ptr_mut(&mut self) -> *mut sys::hb_shape_plan_t {
self.0.as_ptr()
}
pub fn execute(
&mut self,
font: FontRef<'_>,
mut buffer: BufferMut<'_>,
features: &[Feature],
) -> bool {
unsafe {
sys::hb_shape_plan_execute(
self.as_ptr_mut(),
font.as_ptr(),
buffer.as_ptr_mut(),
features.as_ptr(),
features.len() as libc::c_uint,
) != 0
}
}
}
impl<'a> Deref for ShapePlanMut<'a> {
type Target = ShapePlanRef<'a>;
fn deref(&self) -> &Self::Target {
&self.0
}
}
pub struct ShapePlan(NonNull<sys::hb_shape_plan_t>);
impl ShapePlan {
pub fn new(
face: FaceRef<'_>,
props: &SegmentProperties,
features: &[Feature],
shaper_list: Option<&[*const libc::c_char]>,
) -> ShapePlan {
if let Some(list) = shaper_list {
assert!(list.last().unwrap().is_null());
}
let ptr = unsafe {
sys::hb_shape_plan_create(
face.as_ptr(),
props,
features.as_ptr(),
features.len() as libc::c_uint,
shaper_list.map(|s| s.as_ptr()).unwrap_or(ptr::null_mut()),
)
};
ShapePlan(NonNull::new(ptr).unwrap())
}
pub fn new_cached(
face: FaceRef<'_>,
props: &SegmentProperties,
features: &[Feature],
shaper_list: Option<&[*const libc::c_char]>,
) -> ShapePlan {
if let Some(list) = shaper_list {
assert!(list.last().unwrap().is_null());
}
let ptr = unsafe {
sys::hb_shape_plan_create_cached(
face.as_ptr(),
props,
features.as_ptr(),
features.len() as libc::c_uint,
shaper_list.map(|s| s.as_ptr()).unwrap_or(ptr::null_mut()),
)
};
ShapePlan(NonNull::new(ptr).unwrap())
}
pub fn as_ref(&self) -> ShapePlanRef<'_> {
ShapePlanRef(self.0, PhantomData)
}
pub fn as_mut(&mut self) -> ShapePlanMut<'_> {
ShapePlanMut(self.as_ref(), PhantomData)
}
}
impl Drop for ShapePlan {
fn drop(&mut self) {
unsafe { sys::hb_shape_plan_destroy(self.0.as_ptr()) }
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::test_util::{get_font_funcs, test_faces};
#[test]
fn test_tag() {
let raw = u32::from_be_bytes(*b"test");
assert_eq!(Tag::new(raw).to_raw(), raw);
assert_eq!(Tag::from_str("test"), Tag::new(raw));
assert_eq!(Tag::from_cstr(c"test"), Tag::new(raw));
}
#[test]
fn test_language() {
assert_eq!(Language::from_string("en-US").to_string(), Some(c"en-us"));
assert_eq!(Language::from_cstr(c"en-gb").to_string(), Some(c"en-gb"));
assert!(Language::default().to_string().is_none());
}
#[test]
fn test_face_ot_math_data() {
for (_, face) in test_faces() {
assert!(!face.as_ref().has_ot_math_data())
}
}
#[test]
fn test_face_ot_layout() {
for (_, face) in test_faces() {
let layout = face.as_ref().ot_layout();
let size = layout.size_params().unwrap();
assert_eq!(size.design_size, 120);
assert_eq!(size.start, 110);
assert_eq!(size.end, 140);
assert_eq!(size.subfamily_id, 1);
assert_eq!(size.subfamily_name_id, 256);
}
}
#[test]
fn test_font() {
for (_, face) in test_faces() {
let font = Font::new(face.as_ref());
assert_eq!(font.as_ref().ptem(), 0.0);
assert_eq!(font.as_ref().face().0, face.0);
}
}
#[test]
fn test_shape() {
for (ft_face, face) in test_faces() {
let mut font = Font::new(face.as_ref());
font.as_mut().set_funcs(get_font_funcs(), ft_face);
let mut buffer = Buffer::new();
buffer
.as_mut()
.add_utf16(&"Hello World!".encode_utf16().collect::<Vec<_>>());
buffer.as_mut().guess_segment_properties();
let plan1 = ShapePlan::new(
face.as_ref(),
&buffer.as_ref().get_segment_properties(),
&[],
None,
);
let plan2 = ShapePlan::new_cached(
face.as_ref(),
&buffer.as_ref().get_segment_properties(),
&[],
None,
);
for mut plan in [plan1, plan2] {
assert_eq!(plan.as_ref().get_shaper(), Some(c"ot"));
assert!(plan.as_mut().execute(font.as_ref(), buffer.as_mut(), &[]));
assert_eq!(
buffer.as_ref().get_script().get_horizontal_direction(),
Direction::Ltr
);
let glyph_info = buffer.as_ref().glyph_info().unwrap();
assert_eq!(glyph_info.len(), 12);
assert_eq!(glyph_info[0].cluster, 0);
assert_eq!(glyph_info[0].codepoint, 62);
assert_eq!(glyph_info[1].cluster, 1);
assert_eq!(glyph_info[1].codepoint, 50);
let glyph_pos = buffer.as_ref().glyph_positions().unwrap();
assert_eq!(glyph_pos.len(), 12);
assert_eq!(glyph_pos[0].x_advance, 734);
assert_eq!(glyph_pos[0].y_advance, 0);
assert_eq!(glyph_pos[0].x_offset, 0);
assert_eq!(glyph_pos[0].y_offset, 0);
assert_eq!(glyph_pos[1].x_advance, 435);
assert_eq!(glyph_pos[1].y_advance, 0);
assert_eq!(glyph_pos[1].x_offset, 0);
assert_eq!(glyph_pos[1].y_offset, 0);
buffer.as_mut().reset();
buffer
.as_mut()
.add_utf16(&"Hello World!".encode_utf16().collect::<Vec<_>>());
buffer.as_mut().guess_segment_properties();
}
}
}
}