fonts/shapers/
harfbuzz.rs

1/* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this
3 * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
4
5#![allow(unsafe_code)]
6
7use std::os::raw::{c_char, c_int, c_uint, c_void};
8use std::sync::LazyLock;
9use std::{char, ptr};
10
11use app_units::Au;
12use euclid::default::Point2D;
13// Eventually we would like the shaper to be pluggable, as many operating systems have their own
14// shapers. For now, however, HarfBuzz is a hard dependency.
15use harfbuzz_sys::{
16    HB_DIRECTION_LTR, HB_DIRECTION_RTL, HB_MEMORY_MODE_READONLY, HB_OT_LAYOUT_BASELINE_TAG_HANGING,
17    HB_OT_LAYOUT_BASELINE_TAG_IDEO_EMBOX_BOTTOM_OR_LEFT, HB_OT_LAYOUT_BASELINE_TAG_ROMAN,
18    hb_blob_create, hb_blob_t, hb_bool_t, hb_buffer_add_utf8, hb_buffer_create, hb_buffer_destroy,
19    hb_buffer_get_glyph_infos, hb_buffer_get_glyph_positions, hb_buffer_get_length,
20    hb_buffer_set_direction, hb_buffer_set_script, hb_buffer_t, hb_codepoint_t,
21    hb_face_create_for_tables, hb_face_destroy, hb_face_t, hb_feature_t, hb_font_create,
22    hb_font_destroy, hb_font_funcs_create, hb_font_funcs_set_glyph_h_advance_func,
23    hb_font_funcs_set_nominal_glyph_func, hb_font_funcs_t, hb_font_set_funcs, hb_font_set_ppem,
24    hb_font_set_scale, hb_font_set_variations, hb_font_t, hb_glyph_info_t, hb_glyph_position_t,
25    hb_ot_layout_get_baseline, hb_position_t, hb_script_from_iso15924_tag, hb_shape, hb_tag_t,
26    hb_variation_t,
27};
28use num_traits::Zero;
29use read_fonts::types::Tag;
30
31use super::{HarfBuzzShapedGlyphData, ShapedGlyphEntry, unicode_script_to_iso15924_tag};
32use crate::platform::font::FontTable;
33use crate::{
34    BASE, Font, FontBaseline, FontTableMethods, GlyphId, GlyphStore, KERN, LIGA, ShapingFlags,
35    ShapingOptions, fixed_to_float, float_to_fixed,
36};
37
38const HB_OT_TAG_DEFAULT_SCRIPT: hb_tag_t = u32::from_be_bytes(Tag::new(b"DFLT").to_be_bytes());
39const HB_OT_TAG_DEFAULT_LANGUAGE: hb_tag_t = u32::from_be_bytes(Tag::new(b"dflt").to_be_bytes());
40
41pub(crate) struct ShapedGlyphData {
42    count: usize,
43    buffer: *mut hb_buffer_t,
44    glyph_infos: *mut hb_glyph_info_t,
45    pos_infos: *mut hb_glyph_position_t,
46}
47
48impl ShapedGlyphData {
49    /// Create a new [`ShapedGlyphData`] from the given HarfBuzz buffer.
50    ///
51    /// # Safety
52    ///
53    /// - Passing an invalid buffer pointer to this function results in undefined behavior.
54    /// - This function takes ownership of the buffer and the ShapedGlyphData destroys the buffer when dropped
55    ///   so the pointer must an owned pointer and must not be used after being passed to this function
56    unsafe fn new(buffer: *mut hb_buffer_t) -> ShapedGlyphData {
57        let mut glyph_count = 0;
58        let glyph_infos = unsafe { hb_buffer_get_glyph_infos(buffer, &mut glyph_count) };
59        assert!(!glyph_infos.is_null());
60        let mut pos_count = 0;
61        let pos_infos = unsafe { hb_buffer_get_glyph_positions(buffer, &mut pos_count) };
62        assert!(!pos_infos.is_null());
63        assert_eq!(glyph_count, pos_count);
64
65        ShapedGlyphData {
66            count: glyph_count as usize,
67            buffer,
68            glyph_infos,
69            pos_infos,
70        }
71    }
72}
73
74impl Drop for ShapedGlyphData {
75    fn drop(&mut self) {
76        unsafe { hb_buffer_destroy(self.buffer) }
77    }
78}
79
80impl HarfBuzzShapedGlyphData for ShapedGlyphData {
81    #[inline]
82    fn len(&self) -> usize {
83        self.count
84    }
85
86    #[inline(always)]
87    fn byte_offset_of_glyph(&self, i: usize) -> u32 {
88        assert!(i < self.count);
89
90        unsafe {
91            let glyph_info_i = self.glyph_infos.add(i);
92            (*glyph_info_i).cluster
93        }
94    }
95
96    /// Returns shaped glyph data for one glyph, and updates the y-position of the pen.
97    fn entry_for_glyph(&self, i: usize, y_pos: &mut Au) -> ShapedGlyphEntry {
98        assert!(i < self.count);
99
100        unsafe {
101            let glyph_info_i = self.glyph_infos.add(i);
102            let pos_info_i = self.pos_infos.add(i);
103            let x_offset = Shaper::fixed_to_float((*pos_info_i).x_offset);
104            let y_offset = Shaper::fixed_to_float((*pos_info_i).y_offset);
105            let x_advance = Shaper::fixed_to_float((*pos_info_i).x_advance);
106            let y_advance = Shaper::fixed_to_float((*pos_info_i).y_advance);
107
108            let x_offset = Au::from_f64_px(x_offset);
109            let y_offset = Au::from_f64_px(y_offset);
110            let x_advance = Au::from_f64_px(x_advance);
111            let y_advance = Au::from_f64_px(y_advance);
112
113            let offset = if x_offset.is_zero() && y_offset.is_zero() && y_advance.is_zero() {
114                None
115            } else {
116                // adjust the pen..
117                if y_advance > Au::zero() {
118                    *y_pos -= y_advance;
119                }
120
121                Some(Point2D::new(x_offset, *y_pos - y_offset))
122            };
123
124            ShapedGlyphEntry {
125                codepoint: (*glyph_info_i).codepoint as GlyphId,
126                advance: x_advance,
127                offset,
128            }
129        }
130    }
131}
132
133#[derive(Debug)]
134pub(crate) struct Shaper {
135    hb_face: *mut hb_face_t,
136    hb_font: *mut hb_font_t,
137    font: *const Font,
138}
139
140// The HarfBuzz API is thread safe as well as our `Font`, so we can make the data
141// structures here as thread-safe as well. This doesn't seem to be documented,
142// but was expressed as one of the original goals of the HarfBuzz API.
143unsafe impl Sync for Shaper {}
144unsafe impl Send for Shaper {}
145
146impl Drop for Shaper {
147    fn drop(&mut self) {
148        unsafe {
149            assert!(!self.hb_face.is_null());
150            hb_face_destroy(self.hb_face);
151
152            assert!(!self.hb_font.is_null());
153            hb_font_destroy(self.hb_font);
154        }
155    }
156}
157
158impl Shaper {
159    pub(crate) fn new(font: &Font) -> Shaper {
160        unsafe {
161            let hb_face: *mut hb_face_t = hb_face_create_for_tables(
162                Some(font_table_func),
163                font as *const Font as *mut c_void,
164                None,
165            );
166            let hb_font: *mut hb_font_t = hb_font_create(hb_face);
167
168            // Set points-per-em. if zero, performs no hinting in that direction.
169            let pt_size = font.descriptor.pt_size.to_f64_px();
170            hb_font_set_ppem(hb_font, pt_size as c_uint, pt_size as c_uint);
171
172            // Set scaling. Note that this takes 16.16 fixed point.
173            hb_font_set_scale(
174                hb_font,
175                Shaper::float_to_fixed(pt_size) as c_int,
176                Shaper::float_to_fixed(pt_size) as c_int,
177            );
178
179            // configure static function callbacks.
180            hb_font_set_funcs(
181                hb_font,
182                HB_FONT_FUNCS.0,
183                font as *const Font as *mut c_void,
184                None,
185            );
186
187            if servo_config::pref!(layout_variable_fonts_enabled) {
188                let variations = &font.variations();
189                if !variations.is_empty() {
190                    let variations: Vec<_> = variations
191                        .iter()
192                        .map(|variation| hb_variation_t {
193                            tag: variation.tag,
194
195                            value: variation.value,
196                        })
197                        .collect();
198
199                    hb_font_set_variations(hb_font, variations.as_ptr(), variations.len() as u32);
200                }
201            }
202
203            Shaper {
204                hb_face,
205                hb_font,
206                font,
207            }
208        }
209    }
210
211    /// Calculate the layout metrics associated with the given text with the [`Shaper`]s font.
212    fn shaped_glyph_data(&self, text: &str, options: &ShapingOptions) -> ShapedGlyphData {
213        unsafe {
214            let hb_buffer: *mut hb_buffer_t = hb_buffer_create();
215            hb_buffer_set_direction(
216                hb_buffer,
217                if options.flags.contains(ShapingFlags::RTL_FLAG) {
218                    HB_DIRECTION_RTL
219                } else {
220                    HB_DIRECTION_LTR
221                },
222            );
223
224            let script =
225                hb_script_from_iso15924_tag(unicode_script_to_iso15924_tag(options.script));
226            hb_buffer_set_script(hb_buffer, script);
227
228            hb_buffer_add_utf8(
229                hb_buffer,
230                text.as_ptr() as *const c_char,
231                text.len() as c_int,
232                0,
233                text.len() as c_int,
234            );
235
236            let mut features = Vec::new();
237            if options
238                .flags
239                .contains(ShapingFlags::IGNORE_LIGATURES_SHAPING_FLAG)
240            {
241                features.push(hb_feature_t {
242                    tag: u32::from_be_bytes(LIGA.to_be_bytes()),
243                    value: 0,
244                    start: 0,
245                    end: hb_buffer_get_length(hb_buffer),
246                })
247            }
248            if options
249                .flags
250                .contains(ShapingFlags::DISABLE_KERNING_SHAPING_FLAG)
251            {
252                features.push(hb_feature_t {
253                    tag: u32::from_be_bytes(KERN.to_be_bytes()),
254                    value: 0,
255                    start: 0,
256                    end: hb_buffer_get_length(hb_buffer),
257                })
258            }
259
260            hb_shape(
261                self.hb_font,
262                hb_buffer,
263                features.as_mut_ptr(),
264                features.len() as u32,
265            );
266
267            ShapedGlyphData::new(hb_buffer)
268        }
269    }
270
271    fn font(&self) -> &Font {
272        unsafe { &(*self.font) }
273    }
274
275    pub(crate) fn shape_text(&self, text: &str, options: &ShapingOptions, glyphs: &mut GlyphStore) {
276        let glyph_data = self.shaped_glyph_data(text, options);
277        let font = self.font();
278        super::shape_text_harfbuzz(&glyph_data, font, text, options, glyphs);
279    }
280
281    pub(crate) fn baseline(&self) -> Option<FontBaseline> {
282        unsafe { (*self.font).table_for_tag(BASE)? };
283
284        let mut hanging_baseline = 0;
285        let mut alphabetic_baseline = 0;
286        let mut ideographic_baseline = 0;
287
288        unsafe {
289            hb_ot_layout_get_baseline(
290                self.hb_font,
291                HB_OT_LAYOUT_BASELINE_TAG_ROMAN,
292                HB_DIRECTION_LTR,
293                HB_OT_TAG_DEFAULT_SCRIPT,
294                HB_OT_TAG_DEFAULT_LANGUAGE,
295                &mut alphabetic_baseline as *mut _,
296            );
297
298            hb_ot_layout_get_baseline(
299                self.hb_font,
300                HB_OT_LAYOUT_BASELINE_TAG_HANGING,
301                HB_DIRECTION_LTR,
302                HB_OT_TAG_DEFAULT_SCRIPT,
303                HB_OT_TAG_DEFAULT_LANGUAGE,
304                &mut hanging_baseline as *mut _,
305            );
306
307            hb_ot_layout_get_baseline(
308                self.hb_font,
309                HB_OT_LAYOUT_BASELINE_TAG_IDEO_EMBOX_BOTTOM_OR_LEFT,
310                HB_DIRECTION_LTR,
311                HB_OT_TAG_DEFAULT_SCRIPT,
312                HB_OT_TAG_DEFAULT_LANGUAGE,
313                &mut ideographic_baseline as *mut _,
314            );
315        }
316
317        Some(FontBaseline {
318            ideographic_baseline: Shaper::fixed_to_float(ideographic_baseline) as f32,
319            alphabetic_baseline: Shaper::fixed_to_float(alphabetic_baseline) as f32,
320            hanging_baseline: Shaper::fixed_to_float(hanging_baseline) as f32,
321        })
322    }
323
324    fn float_to_fixed(f: f64) -> i32 {
325        float_to_fixed(16, f)
326    }
327
328    fn fixed_to_float(i: hb_position_t) -> f64 {
329        fixed_to_float(16, i)
330    }
331}
332
333/// Callbacks from Harfbuzz when font map and glyph advance lookup needed.
334struct FontFuncs(*mut hb_font_funcs_t);
335
336unsafe impl Sync for FontFuncs {}
337unsafe impl Send for FontFuncs {}
338
339static HB_FONT_FUNCS: LazyLock<FontFuncs> = LazyLock::new(|| unsafe {
340    let hb_funcs = hb_font_funcs_create();
341    hb_font_funcs_set_nominal_glyph_func(hb_funcs, Some(glyph_func), ptr::null_mut(), None);
342    hb_font_funcs_set_glyph_h_advance_func(
343        hb_funcs,
344        Some(glyph_h_advance_func),
345        ptr::null_mut(),
346        None,
347    );
348
349    FontFuncs(hb_funcs)
350});
351
352extern "C" fn glyph_func(
353    _: *mut hb_font_t,
354    font_data: *mut c_void,
355    unicode: hb_codepoint_t,
356    glyph: *mut hb_codepoint_t,
357    _: *mut c_void,
358) -> hb_bool_t {
359    let font: *const Font = font_data as *const Font;
360    assert!(!font.is_null());
361
362    match unsafe { (*font).glyph_index(char::from_u32(unicode).unwrap()) } {
363        Some(g) => {
364            unsafe { *glyph = g as hb_codepoint_t };
365            true as hb_bool_t
366        },
367        None => false as hb_bool_t,
368    }
369}
370
371extern "C" fn glyph_h_advance_func(
372    _: *mut hb_font_t,
373    font_data: *mut c_void,
374    glyph: hb_codepoint_t,
375    _: *mut c_void,
376) -> hb_position_t {
377    let font: *mut Font = font_data as *mut Font;
378    assert!(!font.is_null());
379
380    let advance = unsafe { (*font).glyph_h_advance(glyph as GlyphId) };
381    Shaper::float_to_fixed(advance)
382}
383
384/// Callback to get a font table out of a font.
385extern "C" fn font_table_func(
386    _: *mut hb_face_t,
387    tag: hb_tag_t,
388    user_data: *mut c_void,
389) -> *mut hb_blob_t {
390    // NB: These asserts have security implications.
391    let font = user_data as *const Font;
392    assert!(!font.is_null());
393
394    // TODO(Issue #197): reuse font table data, which will change the unsound trickery here.
395    let Some(font_table) = (unsafe { (*font).table_for_tag(Tag::from_u32(tag)) }) else {
396        return ptr::null_mut();
397    };
398
399    // `Box::into_raw` intentionally leaks the FontTable so we don't destroy the buffer
400    // while HarfBuzz is using it.  When HarfBuzz is done with the buffer, it will pass
401    // this raw pointer back to `destroy_blob_func` which will deallocate the Box.
402    let font_table_ptr = Box::into_raw(Box::new(font_table));
403
404    let buf = unsafe { (*font_table_ptr).buffer() };
405    // HarfBuzz calls `destroy_blob_func` when the buffer is no longer needed.
406    let blob = unsafe {
407        hb_blob_create(
408            buf.as_ptr() as *const c_char,
409            buf.len() as c_uint,
410            HB_MEMORY_MODE_READONLY,
411            font_table_ptr as *mut c_void,
412            Some(destroy_blob_func),
413        )
414    };
415
416    assert!(!blob.is_null());
417    blob
418}
419
420extern "C" fn destroy_blob_func(font_table_ptr: *mut c_void) {
421    unsafe {
422        drop(Box::from_raw(font_table_ptr as *mut FontTable));
423    }
424}