1#![expect(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;
13use harfbuzz_sys::{
16 HB_BUFFER_CLUSTER_LEVEL_MONOTONE_CHARACTERS, HB_DIRECTION_LTR, HB_DIRECTION_RTL,
17 HB_MEMORY_MODE_READONLY, HB_OT_LAYOUT_BASELINE_TAG_HANGING,
18 HB_OT_LAYOUT_BASELINE_TAG_IDEO_EMBOX_BOTTOM_OR_LEFT, HB_OT_LAYOUT_BASELINE_TAG_ROMAN,
19 hb_blob_create, hb_blob_t, hb_bool_t, hb_buffer_add_utf8, hb_buffer_create, hb_buffer_destroy,
20 hb_buffer_get_glyph_infos, hb_buffer_get_glyph_positions, hb_buffer_get_length,
21 hb_buffer_set_cluster_level, hb_buffer_set_direction, hb_buffer_set_language,
22 hb_buffer_set_script, hb_buffer_t, hb_codepoint_t, hb_face_create_for_tables, hb_face_destroy,
23 hb_face_t, hb_feature_t, hb_font_create, hb_font_create_sub_font, hb_font_destroy,
24 hb_font_funcs_create, hb_font_funcs_set_glyph_h_advance_func,
25 hb_font_funcs_set_nominal_glyph_func, hb_font_funcs_t, hb_font_set_funcs, hb_font_set_ppem,
26 hb_font_set_scale, hb_font_set_variations, hb_font_t, hb_glyph_info_t, hb_glyph_position_t,
27 hb_language_from_string, hb_ot_layout_get_baseline, hb_position_t, hb_script_from_iso15924_tag,
28 hb_shape, hb_tag_t, hb_variation_t,
29};
30use num_traits::Zero;
31use read_fonts::types::Tag;
32
33use super::{GlyphShapingResult, ShapedGlyph, unicode_script_to_iso15924_tag};
34use crate::platform::font::FontTable;
35use crate::{
36 BASE, Font, FontBaseline, FontTableMethods, GlyphId, ShapedText, ShapingFlags, ShapingOptions,
37 fixed_to_float, float_to_fixed,
38};
39
40const HB_OT_TAG_DEFAULT_SCRIPT: hb_tag_t = u32::from_be_bytes(Tag::new(b"DFLT").to_be_bytes());
41const HB_OT_TAG_DEFAULT_LANGUAGE: hb_tag_t = u32::from_be_bytes(Tag::new(b"dflt").to_be_bytes());
42
43pub(crate) struct HarfbuzzGlyphShapingResult {
44 count: usize,
45 buffer: *mut hb_buffer_t,
46 glyph_infos: *mut hb_glyph_info_t,
47 pos_infos: *mut hb_glyph_position_t,
48}
49
50impl HarfbuzzGlyphShapingResult {
51 unsafe fn new(buffer: *mut hb_buffer_t) -> HarfbuzzGlyphShapingResult {
59 let mut glyph_count = 0;
60 let glyph_infos = unsafe { hb_buffer_get_glyph_infos(buffer, &mut glyph_count) };
61 assert!(!glyph_infos.is_null());
62 let mut pos_count = 0;
63 let pos_infos = unsafe { hb_buffer_get_glyph_positions(buffer, &mut pos_count) };
64 assert!(!pos_infos.is_null());
65 assert_eq!(glyph_count, pos_count);
66
67 HarfbuzzGlyphShapingResult {
68 count: glyph_count as usize,
69 buffer,
70 glyph_infos,
71 pos_infos,
72 }
73 }
74}
75
76impl Drop for HarfbuzzGlyphShapingResult {
77 fn drop(&mut self) {
78 unsafe { hb_buffer_destroy(self.buffer) }
79 }
80}
81
82struct ShapedGlyphIterator<'a> {
83 shaped_glyph_data: &'a HarfbuzzGlyphShapingResult,
84 current_glyph_offset: usize,
85 y_position: Au,
86}
87
88impl<'a> Iterator for ShapedGlyphIterator<'a> {
89 type Item = ShapedGlyph;
90
91 fn next(&mut self) -> Option<Self::Item> {
92 if self.current_glyph_offset >= self.shaped_glyph_data.count {
93 return None;
94 }
95
96 unsafe {
97 let glyph_info_i = self
98 .shaped_glyph_data
99 .glyph_infos
100 .add(self.current_glyph_offset);
101 let pos_info_i = self
102 .shaped_glyph_data
103 .pos_infos
104 .add(self.current_glyph_offset);
105 let x_offset = Shaper::fixed_to_float((*pos_info_i).x_offset);
106 let y_offset = Shaper::fixed_to_float((*pos_info_i).y_offset);
107 let x_advance = Shaper::fixed_to_float((*pos_info_i).x_advance);
108 let y_advance = Shaper::fixed_to_float((*pos_info_i).y_advance);
109
110 let x_offset = Au::from_f64_px(x_offset);
111 let y_offset = Au::from_f64_px(y_offset);
112 let x_advance = Au::from_f64_px(x_advance);
113 let y_advance = Au::from_f64_px(y_advance);
114
115 let offset = if x_offset.is_zero() && y_offset.is_zero() && y_advance.is_zero() {
116 None
117 } else {
118 if y_advance > Au::zero() {
120 self.y_position -= y_advance;
121 }
122
123 Some(Point2D::new(x_offset, self.y_position - y_offset))
124 };
125
126 self.current_glyph_offset += 1;
127 Some(ShapedGlyph {
128 glyph_id: (*glyph_info_i).codepoint as GlyphId,
129 string_byte_offset: (*glyph_info_i).cluster as usize,
130 advance: x_advance,
131 offset,
132 })
133 }
134 }
135}
136
137impl GlyphShapingResult for HarfbuzzGlyphShapingResult {
138 #[inline]
139 fn len(&self) -> usize {
140 self.count
141 }
142
143 fn iter(&self) -> impl Iterator<Item = ShapedGlyph> {
144 ShapedGlyphIterator {
145 shaped_glyph_data: self,
146 current_glyph_offset: 0,
147 y_position: Au::zero(),
148 }
149 }
150
151 fn is_rtl(&self) -> bool {
152 if self.count == 0 {
153 return false;
154 }
155 unsafe {
159 let first_glyph_info = self.glyph_infos.add(0);
160 let last_glyph_info = self.glyph_infos.add(self.count - 1);
161 (*last_glyph_info).cluster < (*first_glyph_info).cluster
162 }
163 }
164}
165
166#[derive(Debug)]
167pub(crate) struct Shaper {
168 hb_face: *mut hb_face_t,
169 hb_font: *mut hb_font_t,
170 font: *const Font,
171}
172
173unsafe impl Sync for Shaper {}
177unsafe impl Send for Shaper {}
178
179impl Drop for Shaper {
180 fn drop(&mut self) {
181 unsafe {
182 assert!(!self.hb_face.is_null());
183 hb_face_destroy(self.hb_face);
184
185 assert!(!self.hb_font.is_null());
186 hb_font_destroy(self.hb_font);
187 }
188 }
189}
190
191impl Shaper {
192 pub(crate) fn new(font: &Font) -> Shaper {
193 unsafe {
194 let hb_face: *mut hb_face_t = hb_face_create_for_tables(
195 Some(font_table_func),
196 font as *const Font as *mut c_void,
197 None,
198 );
199 let hb_font: *mut hb_font_t = hb_font_create(hb_face);
200
201 let pt_size = font.descriptor.pt_size.to_f64_px();
203 hb_font_set_ppem(hb_font, pt_size as c_uint, pt_size as c_uint);
204
205 hb_font_set_scale(
207 hb_font,
208 Shaper::float_to_fixed(pt_size) as c_int,
209 Shaper::float_to_fixed(pt_size) as c_int,
210 );
211
212 if servo_config::pref!(layout_variable_fonts_enabled) {
213 let variations = &font.variations();
214 if !variations.is_empty() {
215 let variations: Vec<_> = variations
216 .iter()
217 .map(|variation| hb_variation_t {
218 tag: variation.tag,
219
220 value: variation.value,
221 })
222 .collect();
223
224 hb_font_set_variations(hb_font, variations.as_ptr(), variations.len() as u32);
225 }
226 }
227
228 let hb_font = {
231 let sub_font = hb_font_create_sub_font(hb_font);
232 hb_font_destroy(hb_font);
233 sub_font
234 };
235
236 hb_font_set_funcs(
238 hb_font,
239 HB_FONT_FUNCS.0,
240 font as *const Font as *mut c_void,
241 None,
242 );
243
244 Shaper {
245 hb_face,
246 hb_font,
247 font,
248 }
249 }
250 }
251
252 fn shaped_glyph_data(
254 &self,
255 text: &str,
256 options: &ShapingOptions,
257 font_features: &[(Tag, u32)],
258 ) -> HarfbuzzGlyphShapingResult {
259 unsafe {
260 let hb_buffer: *mut hb_buffer_t = hb_buffer_create();
261 hb_buffer_set_cluster_level(hb_buffer, HB_BUFFER_CLUSTER_LEVEL_MONOTONE_CHARACTERS);
262 hb_buffer_set_direction(
263 hb_buffer,
264 if options.flags.contains(ShapingFlags::RTL_FLAG) {
265 HB_DIRECTION_RTL
266 } else {
267 HB_DIRECTION_LTR
268 },
269 );
270
271 let script =
272 hb_script_from_iso15924_tag(unicode_script_to_iso15924_tag(options.script));
273 hb_buffer_set_script(hb_buffer, script);
274
275 hb_buffer_add_utf8(
276 hb_buffer,
277 text.as_ptr() as *const c_char,
278 text.len() as c_int,
279 0,
280 text.len() as c_int,
281 );
282
283 let language = options.language;
284 let hb_language = hb_language_from_string(
285 language.as_str().as_ptr() as *const c_char,
286 language.as_str().len() as c_int,
287 );
288 hb_buffer_set_language(hb_buffer, hb_language);
289
290 let mut features: Vec<_> = font_features
291 .iter()
292 .map(|(tag, value)| hb_feature_t {
293 tag: u32::from_be_bytes(tag.to_be_bytes()),
294 value: *value,
295 start: 0,
296 end: hb_buffer_get_length(hb_buffer),
297 })
298 .collect();
299 hb_shape(
300 self.hb_font,
301 hb_buffer,
302 features.as_mut_ptr(),
303 features.len() as u32,
304 );
305
306 HarfbuzzGlyphShapingResult::new(hb_buffer)
307 }
308 }
309
310 pub(crate) fn shape_text(
311 &self,
312 text: &str,
313 options: &ShapingOptions,
314 font_features: &[(Tag, u32)],
315 ) -> ShapedText {
316 ShapedText::with_shaped_glyph_data(
317 text,
318 options,
319 &self.shaped_glyph_data(text, options, font_features),
320 )
321 }
322
323 pub(crate) fn baseline(&self) -> Option<FontBaseline> {
324 unsafe { (*self.font).table_for_tag(BASE)? };
325
326 let mut hanging_baseline = 0;
327 let mut alphabetic_baseline = 0;
328 let mut ideographic_baseline = 0;
329
330 unsafe {
331 hb_ot_layout_get_baseline(
332 self.hb_font,
333 HB_OT_LAYOUT_BASELINE_TAG_ROMAN,
334 HB_DIRECTION_LTR,
335 HB_OT_TAG_DEFAULT_SCRIPT,
336 HB_OT_TAG_DEFAULT_LANGUAGE,
337 &mut alphabetic_baseline as *mut _,
338 );
339
340 hb_ot_layout_get_baseline(
341 self.hb_font,
342 HB_OT_LAYOUT_BASELINE_TAG_HANGING,
343 HB_DIRECTION_LTR,
344 HB_OT_TAG_DEFAULT_SCRIPT,
345 HB_OT_TAG_DEFAULT_LANGUAGE,
346 &mut hanging_baseline as *mut _,
347 );
348
349 hb_ot_layout_get_baseline(
350 self.hb_font,
351 HB_OT_LAYOUT_BASELINE_TAG_IDEO_EMBOX_BOTTOM_OR_LEFT,
352 HB_DIRECTION_LTR,
353 HB_OT_TAG_DEFAULT_SCRIPT,
354 HB_OT_TAG_DEFAULT_LANGUAGE,
355 &mut ideographic_baseline as *mut _,
356 );
357 }
358
359 Some(FontBaseline {
360 ideographic_baseline: Shaper::fixed_to_float(ideographic_baseline) as f32,
361 alphabetic_baseline: Shaper::fixed_to_float(alphabetic_baseline) as f32,
362 hanging_baseline: Shaper::fixed_to_float(hanging_baseline) as f32,
363 })
364 }
365
366 fn float_to_fixed(f: f64) -> i32 {
367 float_to_fixed(16, f)
368 }
369
370 fn fixed_to_float(i: hb_position_t) -> f64 {
371 fixed_to_float(16, i)
372 }
373}
374
375struct FontFuncs(*mut hb_font_funcs_t);
377
378unsafe impl Sync for FontFuncs {}
379unsafe impl Send for FontFuncs {}
380
381static HB_FONT_FUNCS: LazyLock<FontFuncs> = LazyLock::new(|| unsafe {
382 let hb_funcs = hb_font_funcs_create();
383 hb_font_funcs_set_nominal_glyph_func(hb_funcs, Some(glyph_func), ptr::null_mut(), None);
384 hb_font_funcs_set_glyph_h_advance_func(
385 hb_funcs,
386 Some(glyph_h_advance_func),
387 ptr::null_mut(),
388 None,
389 );
390
391 FontFuncs(hb_funcs)
392});
393
394extern "C" fn glyph_func(
395 _: *mut hb_font_t,
396 font_data: *mut c_void,
397 unicode: hb_codepoint_t,
398 glyph: *mut hb_codepoint_t,
399 _: *mut c_void,
400) -> hb_bool_t {
401 let font: *const Font = font_data as *const Font;
402 assert!(!font.is_null());
403
404 match unsafe { (*font).glyph_index(char::from_u32(unicode).unwrap()) } {
405 Some(g) => {
406 unsafe { *glyph = g as hb_codepoint_t };
407 true as hb_bool_t
408 },
409 None => false as hb_bool_t,
410 }
411}
412
413extern "C" fn glyph_h_advance_func(
414 _: *mut hb_font_t,
415 font_data: *mut c_void,
416 glyph: hb_codepoint_t,
417 _: *mut c_void,
418) -> hb_position_t {
419 let font: *mut Font = font_data as *mut Font;
420 assert!(!font.is_null());
421
422 let advance = unsafe { (*font).glyph_h_advance(glyph as GlyphId) };
423 Shaper::float_to_fixed(advance)
424}
425
426extern "C" fn font_table_func(
428 _: *mut hb_face_t,
429 tag: hb_tag_t,
430 user_data: *mut c_void,
431) -> *mut hb_blob_t {
432 let font = user_data as *const Font;
434 assert!(!font.is_null());
435
436 let Some(font_table) = (unsafe { (*font).table_for_tag(Tag::from_u32(tag)) }) else {
438 return ptr::null_mut();
439 };
440
441 let font_table_ptr = Box::into_raw(Box::new(font_table));
445
446 let buf = unsafe { (*font_table_ptr).buffer() };
447 let blob = unsafe {
449 hb_blob_create(
450 buf.as_ptr() as *const c_char,
451 buf.len() as c_uint,
452 HB_MEMORY_MODE_READONLY,
453 font_table_ptr as *mut c_void,
454 Some(destroy_blob_func),
455 )
456 };
457
458 assert!(!blob.is_null());
459 blob
460}
461
462extern "C" fn destroy_blob_func(font_table_ptr: *mut c_void) {
463 unsafe {
464 drop(Box::from_raw(font_table_ptr as *mut FontTable));
465 }
466}