/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ #![allow(unsafe_code)] use std::os::raw::{c_char, c_int, c_uint, c_void}; use std::sync::LazyLock; use std::{char, ptr}; use app_units::Au; use euclid::default::Point2D; // Eventually we would like the shaper to be pluggable, as many operating systems have their own // shapers. For now, however, HarfBuzz is a hard dependency. use harfbuzz_sys::{ HB_DIRECTION_LTR, HB_DIRECTION_RTL, HB_MEMORY_MODE_READONLY, HB_OT_LAYOUT_BASELINE_TAG_HANGING, HB_OT_LAYOUT_BASELINE_TAG_IDEO_EMBOX_BOTTOM_OR_LEFT, HB_OT_LAYOUT_BASELINE_TAG_ROMAN, hb_blob_create, hb_blob_t, hb_bool_t, hb_buffer_add_utf8, hb_buffer_create, hb_buffer_destroy, hb_buffer_get_glyph_infos, hb_buffer_get_glyph_positions, hb_buffer_get_length, hb_buffer_set_direction, hb_buffer_set_script, hb_buffer_t, hb_codepoint_t, hb_face_create_for_tables, hb_face_destroy, hb_face_t, hb_feature_t, hb_font_create, hb_font_destroy, hb_font_funcs_create, hb_font_funcs_set_glyph_h_advance_func, hb_font_funcs_set_nominal_glyph_func, hb_font_funcs_t, hb_font_set_funcs, hb_font_set_ppem, hb_font_set_scale, hb_font_set_variations, hb_font_t, hb_glyph_info_t, hb_glyph_position_t, hb_ot_layout_get_baseline, hb_position_t, hb_script_from_iso15924_tag, hb_shape, hb_tag_t, hb_variation_t, }; use num_traits::Zero; use read_fonts::types::Tag; use super::{HarfBuzzShapedGlyphData, ShapedGlyphEntry, unicode_script_to_iso15924_tag}; use crate::platform::font::FontTable; use crate::{ BASE, Font, FontBaseline, FontTableMethods, GlyphId, GlyphStore, KERN, LIGA, ShapingFlags, ShapingOptions, fixed_to_float, float_to_fixed, }; const HB_OT_TAG_DEFAULT_SCRIPT: hb_tag_t = u32::from_be_bytes(Tag::new(b"DFLT").to_be_bytes()); const HB_OT_TAG_DEFAULT_LANGUAGE: hb_tag_t = u32::from_be_bytes(Tag::new(b"dflt").to_be_bytes()); pub(crate) struct ShapedGlyphData { count: usize, buffer: *mut hb_buffer_t, glyph_infos: *mut hb_glyph_info_t, pos_infos: *mut hb_glyph_position_t, } impl ShapedGlyphData { /// Create a new [`ShapedGlyphData`] from the given HarfBuzz buffer. /// /// # Safety /// /// - Passing an invalid buffer pointer to this function results in undefined behavior. /// - This function takes ownership of the buffer and the ShapedGlyphData destroys the buffer when dropped /// so the pointer must an owned pointer and must not be used after being passed to this function unsafe fn new(buffer: *mut hb_buffer_t) -> ShapedGlyphData { let mut glyph_count = 0; let glyph_infos = unsafe { hb_buffer_get_glyph_infos(buffer, &mut glyph_count) }; assert!(!glyph_infos.is_null()); let mut pos_count = 0; let pos_infos = unsafe { hb_buffer_get_glyph_positions(buffer, &mut pos_count) }; assert!(!pos_infos.is_null()); assert_eq!(glyph_count, pos_count); ShapedGlyphData { count: glyph_count as usize, buffer, glyph_infos, pos_infos, } } } impl Drop for ShapedGlyphData { fn drop(&mut self) { unsafe { hb_buffer_destroy(self.buffer) } } } impl HarfBuzzShapedGlyphData for ShapedGlyphData { #[inline] fn len(&self) -> usize { self.count } #[inline(always)] fn byte_offset_of_glyph(&self, i: usize) -> u32 { assert!(i < self.count); unsafe { let glyph_info_i = self.glyph_infos.add(i); (*glyph_info_i).cluster } } /// Returns shaped glyph data for one glyph, and updates the y-position of the pen. fn entry_for_glyph(&self, i: usize, y_pos: &mut Au) -> ShapedGlyphEntry { assert!(i < self.count); unsafe { let glyph_info_i = self.glyph_infos.add(i); let pos_info_i = self.pos_infos.add(i); let x_offset = Shaper::fixed_to_float((*pos_info_i).x_offset); let y_offset = Shaper::fixed_to_float((*pos_info_i).y_offset); let x_advance = Shaper::fixed_to_float((*pos_info_i).x_advance); let y_advance = Shaper::fixed_to_float((*pos_info_i).y_advance); let x_offset = Au::from_f64_px(x_offset); let y_offset = Au::from_f64_px(y_offset); let x_advance = Au::from_f64_px(x_advance); let y_advance = Au::from_f64_px(y_advance); let offset = if x_offset.is_zero() && y_offset.is_zero() && y_advance.is_zero() { None } else { // adjust the pen.. if y_advance > Au::zero() { *y_pos -= y_advance; } Some(Point2D::new(x_offset, *y_pos - y_offset)) }; ShapedGlyphEntry { codepoint: (*glyph_info_i).codepoint as GlyphId, advance: x_advance, offset, } } } } #[derive(Debug)] pub(crate) struct Shaper { hb_face: *mut hb_face_t, hb_font: *mut hb_font_t, font: *const Font, } // The HarfBuzz API is thread safe as well as our `Font`, so we can make the data // structures here as thread-safe as well. This doesn't seem to be documented, // but was expressed as one of the original goals of the HarfBuzz API. unsafe impl Sync for Shaper {} unsafe impl Send for Shaper {} impl Drop for Shaper { fn drop(&mut self) { unsafe { assert!(!self.hb_face.is_null()); hb_face_destroy(self.hb_face); assert!(!self.hb_font.is_null()); hb_font_destroy(self.hb_font); } } } impl Shaper { pub(crate) fn new(font: &Font) -> Shaper { unsafe { let hb_face: *mut hb_face_t = hb_face_create_for_tables( Some(font_table_func), font as *const Font as *mut c_void, None, ); let hb_font: *mut hb_font_t = hb_font_create(hb_face); // Set points-per-em. if zero, performs no hinting in that direction. let pt_size = font.descriptor.pt_size.to_f64_px(); hb_font_set_ppem(hb_font, pt_size as c_uint, pt_size as c_uint); // Set scaling. Note that this takes 16.16 fixed point. hb_font_set_scale( hb_font, Shaper::float_to_fixed(pt_size) as c_int, Shaper::float_to_fixed(pt_size) as c_int, ); // configure static function callbacks. hb_font_set_funcs( hb_font, HB_FONT_FUNCS.0, font as *const Font as *mut c_void, None, ); if servo_config::pref!(layout_variable_fonts_enabled) { let variations = &font.variations(); if !variations.is_empty() { let variations: Vec<_> = variations .iter() .map(|variation| hb_variation_t { tag: variation.tag, value: variation.value, }) .collect(); hb_font_set_variations(hb_font, variations.as_ptr(), variations.len() as u32); } } Shaper { hb_face, hb_font, font, } } } /// Calculate the layout metrics associated with the given text with the [`Shaper`]s font. fn shaped_glyph_data(&self, text: &str, options: &ShapingOptions) -> ShapedGlyphData { unsafe { let hb_buffer: *mut hb_buffer_t = hb_buffer_create(); hb_buffer_set_direction( hb_buffer, if options.flags.contains(ShapingFlags::RTL_FLAG) { HB_DIRECTION_RTL } else { HB_DIRECTION_LTR }, ); let script = hb_script_from_iso15924_tag(unicode_script_to_iso15924_tag(options.script)); hb_buffer_set_script(hb_buffer, script); hb_buffer_add_utf8( hb_buffer, text.as_ptr() as *const c_char, text.len() as c_int, 0, text.len() as c_int, ); let mut features = Vec::new(); if options .flags .contains(ShapingFlags::IGNORE_LIGATURES_SHAPING_FLAG) { features.push(hb_feature_t { tag: u32::from_be_bytes(LIGA.to_be_bytes()), value: 0, start: 0, end: hb_buffer_get_length(hb_buffer), }) } if options .flags .contains(ShapingFlags::DISABLE_KERNING_SHAPING_FLAG) { features.push(hb_feature_t { tag: u32::from_be_bytes(KERN.to_be_bytes()), value: 0, start: 0, end: hb_buffer_get_length(hb_buffer), }) } hb_shape( self.hb_font, hb_buffer, features.as_mut_ptr(), features.len() as u32, ); ShapedGlyphData::new(hb_buffer) } } fn font(&self) -> &Font { unsafe { &(*self.font) } } pub(crate) fn shape_text(&self, text: &str, options: &ShapingOptions, glyphs: &mut GlyphStore) { let glyph_data = self.shaped_glyph_data(text, options); let font = self.font(); super::shape_text_harfbuzz(&glyph_data, font, text, options, glyphs); } pub(crate) fn baseline(&self) -> Option { unsafe { (*self.font).table_for_tag(BASE)? }; let mut hanging_baseline = 0; let mut alphabetic_baseline = 0; let mut ideographic_baseline = 0; unsafe { hb_ot_layout_get_baseline( self.hb_font, HB_OT_LAYOUT_BASELINE_TAG_ROMAN, HB_DIRECTION_LTR, HB_OT_TAG_DEFAULT_SCRIPT, HB_OT_TAG_DEFAULT_LANGUAGE, &mut alphabetic_baseline as *mut _, ); hb_ot_layout_get_baseline( self.hb_font, HB_OT_LAYOUT_BASELINE_TAG_HANGING, HB_DIRECTION_LTR, HB_OT_TAG_DEFAULT_SCRIPT, HB_OT_TAG_DEFAULT_LANGUAGE, &mut hanging_baseline as *mut _, ); hb_ot_layout_get_baseline( self.hb_font, HB_OT_LAYOUT_BASELINE_TAG_IDEO_EMBOX_BOTTOM_OR_LEFT, HB_DIRECTION_LTR, HB_OT_TAG_DEFAULT_SCRIPT, HB_OT_TAG_DEFAULT_LANGUAGE, &mut ideographic_baseline as *mut _, ); } Some(FontBaseline { ideographic_baseline: Shaper::fixed_to_float(ideographic_baseline) as f32, alphabetic_baseline: Shaper::fixed_to_float(alphabetic_baseline) as f32, hanging_baseline: Shaper::fixed_to_float(hanging_baseline) as f32, }) } fn float_to_fixed(f: f64) -> i32 { float_to_fixed(16, f) } fn fixed_to_float(i: hb_position_t) -> f64 { fixed_to_float(16, i) } } /// Callbacks from Harfbuzz when font map and glyph advance lookup needed. struct FontFuncs(*mut hb_font_funcs_t); unsafe impl Sync for FontFuncs {} unsafe impl Send for FontFuncs {} static HB_FONT_FUNCS: LazyLock = LazyLock::new(|| unsafe { let hb_funcs = hb_font_funcs_create(); hb_font_funcs_set_nominal_glyph_func(hb_funcs, Some(glyph_func), ptr::null_mut(), None); hb_font_funcs_set_glyph_h_advance_func( hb_funcs, Some(glyph_h_advance_func), ptr::null_mut(), None, ); FontFuncs(hb_funcs) }); extern "C" fn glyph_func( _: *mut hb_font_t, font_data: *mut c_void, unicode: hb_codepoint_t, glyph: *mut hb_codepoint_t, _: *mut c_void, ) -> hb_bool_t { let font: *const Font = font_data as *const Font; assert!(!font.is_null()); match unsafe { (*font).glyph_index(char::from_u32(unicode).unwrap()) } { Some(g) => { unsafe { *glyph = g as hb_codepoint_t }; true as hb_bool_t }, None => false as hb_bool_t, } } extern "C" fn glyph_h_advance_func( _: *mut hb_font_t, font_data: *mut c_void, glyph: hb_codepoint_t, _: *mut c_void, ) -> hb_position_t { let font: *mut Font = font_data as *mut Font; assert!(!font.is_null()); let advance = unsafe { (*font).glyph_h_advance(glyph as GlyphId) }; Shaper::float_to_fixed(advance) } /// Callback to get a font table out of a font. extern "C" fn font_table_func( _: *mut hb_face_t, tag: hb_tag_t, user_data: *mut c_void, ) -> *mut hb_blob_t { // NB: These asserts have security implications. let font = user_data as *const Font; assert!(!font.is_null()); // TODO(Issue #197): reuse font table data, which will change the unsound trickery here. let Some(font_table) = (unsafe { (*font).table_for_tag(Tag::from_u32(tag)) }) else { return ptr::null_mut(); }; // `Box::into_raw` intentionally leaks the FontTable so we don't destroy the buffer // while HarfBuzz is using it. When HarfBuzz is done with the buffer, it will pass // this raw pointer back to `destroy_blob_func` which will deallocate the Box. let font_table_ptr = Box::into_raw(Box::new(font_table)); let buf = unsafe { (*font_table_ptr).buffer() }; // HarfBuzz calls `destroy_blob_func` when the buffer is no longer needed. let blob = unsafe { hb_blob_create( buf.as_ptr() as *const c_char, buf.len() as c_uint, HB_MEMORY_MODE_READONLY, font_table_ptr as *mut c_void, Some(destroy_blob_func), ) }; assert!(!blob.is_null()); blob } extern "C" fn destroy_blob_func(font_table_ptr: *mut c_void) { unsafe { drop(Box::from_raw(font_table_ptr as *mut FontTable)); } }