use std::{slice, ptr, u32, ops::Deref, os::raw::{c_char, c_uint}};
use harfbuzz_sys::{
hb_blob_create, hb_blob_destroy,
hb_font_create, hb_font_destroy,
hb_face_create, hb_face_destroy,
hb_buffer_create, hb_buffer_destroy,
hb_shape, hb_font_set_scale, hb_buffer_add_utf8, hb_ot_font_set_funcs,
hb_buffer_get_glyph_infos, hb_buffer_get_glyph_positions,
hb_buffer_guess_segment_properties, hb_buffer_allocation_successful,
hb_blob_t, hb_memory_mode_t, hb_buffer_t,
hb_glyph_position_t, hb_glyph_info_t, hb_font_t, hb_face_t,
hb_feature_t, hb_tag_t,
HB_MEMORY_MODE_READONLY,
};
use azul_core::{
display_list::GlyphInstance,
app_resources::{GlyphInfo, FontMetrics, GlyphPosition},
};
use azul_css::{LayoutPoint, LayoutSize};
const MEMORY_MODE_READONLY: hb_memory_mode_t = HB_MEMORY_MODE_READONLY;
pub(crate) const HB_SCALE_FACTOR: f32 = 128.0;
const fn create_hb_tag(tag: (char, char, char, char)) -> hb_tag_t {
(((tag.0 as hb_tag_t) & 0xFF) << 24) |
(((tag.1 as hb_tag_t) & 0xFF) << 16) |
(((tag.2 as hb_tag_t) & 0xFF) << 8) |
(((tag.3 as hb_tag_t) & 0xFF) << 0)
}
const KERN_TAG: hb_tag_t = create_hb_tag(('k', 'e', 'r', 'n'));
const LIGA_TAG: hb_tag_t = create_hb_tag(('l', 'i', 'g', 'a'));
const CLIG_TAG: hb_tag_t = create_hb_tag(('c', 'l', 'i', 'g'));
const FEATURE_KERNING_ON: hb_feature_t = hb_feature_t { tag: KERN_TAG, value: 1, start: 0, end: u32::MAX };
const FEATURE_LIGATURE_ON: hb_feature_t = hb_feature_t { tag: LIGA_TAG, value: 1, start: 0, end: u32::MAX };
const FEATURE_CLIG_ON: hb_feature_t = hb_feature_t { tag: CLIG_TAG, value: 1, start: 0, end: u32::MAX };
static ACTIVE_HB_FEATURES: [hb_feature_t;3] = [
FEATURE_KERNING_ON,
FEATURE_LIGATURE_ON,
FEATURE_CLIG_ON,
];
#[derive(Debug, Clone)]
pub struct ShapedWord {
pub glyph_infos: Vec<GlyphInfo>,
pub glyph_positions: Vec<GlyphPosition>,
}
#[derive(Debug)]
pub struct HbFont<'a> {
font_bytes: &'a [u8],
font_index: u32,
hb_face_bytes: *mut hb_blob_t,
hb_face: *mut hb_face_t,
hb_font: *mut hb_font_t,
}
impl<'a> HbFont<'a> {
pub fn from_bytes(font_bytes: &'a [u8], font_index: u32) -> Self {
let user_data_ptr = ptr::null_mut();
let destroy_func = None;
let font_ptr = font_bytes.as_ptr() as *const c_char;
let hb_face_bytes = unsafe {
hb_blob_create(font_ptr, font_bytes.len() as u32, MEMORY_MODE_READONLY, user_data_ptr, destroy_func)
};
let hb_face = unsafe { hb_face_create(hb_face_bytes, font_index as c_uint) };
let hb_font = unsafe { hb_font_create(hb_face) };
unsafe { hb_ot_font_set_funcs(hb_font) };
Self {
font_bytes,
font_index,
hb_face_bytes,
hb_face,
hb_font,
}
}
}
impl<'a> Drop for HbFont<'a> {
fn drop(&mut self) {
unsafe { hb_font_destroy(self.hb_font) };
unsafe { hb_face_destroy(self.hb_face) };
unsafe { hb_blob_destroy(self.hb_face_bytes) };
}
}
#[derive(Debug)]
pub struct HbScaledFont<'a> {
pub font: &'a HbFont<'a>,
pub font_size_px: f32,
}
impl<'a> HbScaledFont<'a> {
pub fn from_font(font: &'a HbFont<'a>, font_size_px: f32) -> Self {
let px = (font_size_px * HB_SCALE_FACTOR) as i32;
unsafe { hb_font_set_scale(font.hb_font, px, px) };
Self {
font,
font_size_px,
}
}
}
#[derive(Debug)]
pub struct HbBuffer<'a> {
words: &'a str,
hb_buffer: *mut hb_buffer_t,
}
impl<'a> HbBuffer<'a> {
pub fn from_str(words: &'a str) -> Self {
let hb_buffer = unsafe { hb_buffer_create() };
unsafe { hb_buffer_allocation_successful(hb_buffer); };
let word_ptr = words.as_ptr() as *const c_char; let word_len = words.len() as i32;
unsafe {
hb_buffer_add_utf8(hb_buffer, word_ptr, word_len, 0, word_len);
hb_buffer_guess_segment_properties(hb_buffer);
}
Self {
words,
hb_buffer,
}
}
}
impl<'a> Drop for HbBuffer<'a> {
fn drop(&mut self) {
unsafe { hb_buffer_destroy(self.hb_buffer) };
}
}
#[derive(Debug)]
pub struct CVec<T> {
ptr: *const T,
len: usize,
}
impl<T> Deref for CVec<T> {
type Target = [T];
fn deref(&self) -> &[T] {
unsafe { slice::from_raw_parts(self.ptr, self.len) }
}
}
pub type HbGlyphInfo = hb_glyph_info_t;
pub type HbGlyphPosition = hb_glyph_position_t;
#[derive(Debug)]
pub struct HbShapedWord<'a> {
pub buf: &'a HbBuffer<'a>,
pub scaled_font: &'a HbScaledFont<'a>,
pub glyph_infos: CVec<HbGlyphInfo>,
pub glyph_positions: CVec<HbGlyphPosition>,
}
pub(crate) fn shape_word_hb<'a>(
text: &'a HbBuffer<'a>,
scaled_font: &'a HbScaledFont<'a>,
) -> HbShapedWord<'a> {
let features = if ACTIVE_HB_FEATURES.is_empty() {
ptr::null()
} else {
&ACTIVE_HB_FEATURES as *const _
};
let num_features = ACTIVE_HB_FEATURES.len() as u32;
unsafe { hb_shape(scaled_font.font.hb_font, text.hb_buffer, features, num_features) };
let mut glyph_count = 0;
let glyph_infos = unsafe { hb_buffer_get_glyph_infos(text.hb_buffer, &mut glyph_count) };
let mut position_count = glyph_count;
let glyph_positions = unsafe { hb_buffer_get_glyph_positions(text.hb_buffer, &mut position_count) };
assert_eq!(glyph_count, position_count);
HbShapedWord {
buf: text,
scaled_font,
glyph_infos: CVec {
ptr: glyph_infos,
len: glyph_count as usize,
},
glyph_positions: CVec {
ptr: glyph_positions,
len: glyph_count as usize,
},
}
}
pub(crate) fn get_word_visual_width_hb(glyph_positions: &[GlyphPosition]) -> f32 {
glyph_positions.iter().map(|pos| pos.x_advance as f32 / HB_SCALE_FACTOR).sum()
}
pub(crate) fn get_glyph_instances_hb(
glyph_infos: &[GlyphInfo],
glyph_positions: &[GlyphPosition],
) -> Vec<GlyphInstance> {
let mut current_cursor_x = 0.0;
let mut current_cursor_y = 0.0;
glyph_infos.iter().zip(glyph_positions.iter()).map(|(glyph_info, glyph_pos)| {
let glyph_index = glyph_info.codepoint;
let x_offset = glyph_pos.x_offset as f32 / HB_SCALE_FACTOR;
let y_offset = glyph_pos.y_offset as f32 / HB_SCALE_FACTOR;
let x_advance = glyph_pos.x_advance as f32 / HB_SCALE_FACTOR;
let y_advance = glyph_pos.y_advance as f32 / HB_SCALE_FACTOR;
let point = LayoutPoint::new(current_cursor_x + x_offset, current_cursor_y + y_offset);
let size = LayoutSize::new(x_advance, y_advance);
current_cursor_x += x_advance;
current_cursor_y += y_advance;
GlyphInstance {
index: glyph_index,
point,
size,
}
}).collect()
}
pub fn get_font_metrics_freetype(font_bytes: &[u8], font_index: i32) -> FontMetrics {
use std::convert::TryInto;
use freetype::freetype::{
FT_Long, FT_F26Dot6,
FT_Init_FreeType, FT_Done_FreeType, FT_New_Memory_Face,
FT_Done_Face, FT_Set_Char_Size, FT_Library, FT_Face,
};
const FT_ERR_OK: i32 = 0;
const FAKE_FONT_SIZE: FT_F26Dot6 = 1000;
let mut baseline = FontMetrics {
font_size: FAKE_FONT_SIZE as usize,
x_ppem: 0,
y_ppem: 0,
x_scale: 0,
y_scale: 0,
ascender: 0,
descender: 0,
height: 0,
max_advance: 0,
};
let buf_len: FT_Long = match font_bytes.len().try_into().ok() {
Some(s) => s,
None => return baseline, };
unsafe {
let mut ft_library: FT_Library = ptr::null_mut();
let error = FT_Init_FreeType(&mut ft_library);
if error != FT_ERR_OK {
return baseline;
}
let mut ft_face: FT_Face = ptr::null_mut();
let error = FT_New_Memory_Face(ft_library, font_bytes.as_ptr(), buf_len, font_index as FT_Long, &mut ft_face);
if error != FT_ERR_OK {
FT_Done_FreeType(ft_library);
return baseline;
}
const DPI: u32 = 72;
let font_size = match FAKE_FONT_SIZE.try_into().ok() { Some(s) => s, None => return baseline };
let error = FT_Set_Char_Size(ft_face, 0, font_size, DPI, DPI);
if error != FT_ERR_OK {
FT_Done_Face(ft_face);
FT_Done_FreeType(ft_library);
return baseline;
}
let ft_face_ref = &*ft_face;
let ft_size_ref = &*ft_face_ref.size;
let metrics = ft_size_ref.metrics;
baseline = FontMetrics {
font_size: FAKE_FONT_SIZE as usize,
x_ppem: metrics.x_ppem,
y_ppem: metrics.y_ppem,
x_scale: metrics.x_scale as i64,
y_scale: metrics.y_scale as i64,
ascender: metrics.ascender as i64,
descender: metrics.descender as i64,
height: metrics.height as i64,
max_advance: metrics.max_advance as i64,
};
FT_Done_Face(ft_face);
FT_Done_FreeType(ft_library);
}
baseline
}