ferroui_harfbuzz/
harf_buzz_text_shaper.rs1use crate::harf_buzz_typeface::HarfBuzzTypeface;
2use crate::hb;
3use ferroui_base::media::fonts::OpenTypeTag;
4use ferroui_base::media::text_formatting::unicode::Codepoint;
5use ferroui_base::media::text_formatting::{GlyphInfo, ShapedBuffer, TextShaperOptions};
6use ferroui_base::media::{GlyphTypeface, ITextShaperTypeface};
7use ferroui_base::platform::ITextShaperImpl;
8use ferroui_base::utilities::{CultureInfo, ReadOnlyMemory};
9use ferroui_base::Vector;
10use std::cell::RefCell;
11use std::rc::Rc;
12
13ferroui_base::ferro_thread_local! {
14 static BUFFER: RefCell<hb::Buffer> = RefCell::new(hb::Buffer::new());
16}
17
18const ZERO_WIDTH_NON_JOINER: u32 = 0x200C;
19
20#[derive(Default)]
22pub struct HarfBuzzTextShaper;
23
24impl HarfBuzzTextShaper {
25 pub fn new() -> Self {
27 Self
28 }
29
30 fn merge_break_pair(buffer: &mut hb::Buffer) {
33 let glyph_infos = buffer.glyph_infos_mut();
34 let length = glyph_infos.len();
35
36 if length == 0 {
37 return;
38 }
39
40 let second = glyph_infos[length - 1];
41
42 if !Codepoint::new(second.codepoint).is_break_char() {
43 return;
44 }
45
46 if length > 1 && glyph_infos[length - 2].codepoint == '\r' as u32 && second.codepoint == '\n' as u32 {
47 let first_cluster = glyph_infos[length - 2].cluster;
48
49 glyph_infos[length - 2].codepoint = ZERO_WIDTH_NON_JOINER;
50
51 glyph_infos[length - 1].codepoint = ZERO_WIDTH_NON_JOINER;
52 glyph_infos[length - 1].cluster = first_cluster;
53 } else {
54 glyph_infos[length - 1].codepoint = ZERO_WIDTH_NON_JOINER;
55 }
56 }
57
58 fn get_glyph_offset(position: &hb::GlyphPosition, text_scale: f64) -> Vector {
59 let offset_x = position.x_offset as f64 * text_scale;
60
61 let offset_y = -(position.y_offset as f64) * text_scale;
62
63 Vector::new(offset_x, offset_y)
64 }
65
66 fn get_glyph_advance(position: &hb::GlyphPosition, text_scale: f64) -> f64 {
67 position.x_advance as f64 * text_scale
70 }
71
72 fn get_features(options: &TextShaperOptions) -> Vec<hb::Feature> {
73 let Some(font_features) = options.font_features() else {
74 return Vec::new();
75 };
76
77 font_features
78 .iter()
79 .map(|font_feature| hb::Feature {
80 tag: OpenTypeTag::parse(&font_feature.tag).value(),
81 value: font_feature.value as u32,
82 start: font_feature.start as u32,
83 end: font_feature.end as u32,
84 })
85 .collect()
86 }
87}
88
89impl ITextShaperImpl for HarfBuzzTextShaper {
90 fn shape_text(&self, text: &ReadOnlyMemory<u16>, options: &TextShaperOptions) -> Rc<ShapedBuffer> {
91 let glyph_typeface = options.glyph_typeface();
92 let font_rendering_em_size = options.font_rendering_em_size();
93 let bidi_level = options.bidi_level();
94
95 if text.is_empty() {
96 return ShapedBuffer::new(text.clone(), 0, glyph_typeface.clone(), font_rendering_em_size, bidi_level);
97 }
98
99 let text_shaper_typeface = glyph_typeface.text_shaper_typeface();
100 let harf_buzz_typeface = text_shaper_typeface
101 .as_any()
102 .downcast_ref::<HarfBuzzTypeface>()
103 .unwrap_or_else(|| panic!("The provided GlyphTypeface is not supported by this text shaper."));
104
105 let culture = options.culture();
106
107 let (containing_text, start): (&[u16], usize) = match text.owner() {
110 Some(owner) => (owner, text.offset_in_owner()),
111 None => (text.span(), 0),
112 };
113 let length = text.len();
114
115 BUFFER.with(|buffer| {
116 let mut buffer = buffer.borrow_mut();
117
118 buffer.reset();
119
120 buffer.add_utf16(containing_text, start, length);
121
122 Self::merge_break_pair(&mut buffer);
123
124 buffer.guess_segment_properties();
125
126 buffer.set_direction(if (bidi_level & 1) == 0 {
127 hb::Direction::LeftToRight
128 } else {
129 hb::Direction::RightToLeft
130 });
131
132 let current_culture;
133 let used_culture = match culture {
134 Some(culture) => culture,
135 None => {
136 current_culture = CultureInfo::current_culture();
137 ¤t_culture
138 }
139 };
140
141 buffer.set_language(used_culture.two_letter_iso_language_name());
142
143 let features = Self::get_features(options);
144
145 let scale_x = harf_buzz_typeface.with_font(|font| {
153 font.shape(&mut buffer, &features);
154 font.scale().0
155 });
156
157 let text_scale = font_rendering_em_size / scale_x as f64;
158
159 let buffer_length = buffer.len();
160
161 let shaped_buffer =
162 ShapedBuffer::new(text.clone(), buffer_length, glyph_typeface.clone(), font_rendering_em_size, bidi_level);
163
164 let glyph_infos = buffer.glyph_infos();
165 let glyph_positions = buffer.glyph_positions();
166
167 for i in 0..buffer_length {
168 let source_info = &glyph_infos[i];
169
170 let mut glyph_index = source_info.codepoint as u16;
171
172 let original_cluster = source_info.cluster as usize;
173
174 let glyph_cluster = original_cluster as i32 - start as i32;
175
176 let mut glyph_advance =
177 Self::get_glyph_advance(&glyph_positions[i], text_scale) + options.letter_spacing();
178
179 let glyph_offset = Self::get_glyph_offset(&glyph_positions[i], text_scale);
180
181 if original_cluster < containing_text.len() && containing_text[original_cluster] == '\t' as u16 {
182 glyph_index = glyph_typeface.character_to_glyph_map().get_glyph(' ' as i32);
183
184 if options.incremental_tab_width() > 0.0 {
185 glyph_advance = options.incremental_tab_width();
186 } else {
187 let advance = glyph_typeface.try_get_horizontal_glyph_advance(glyph_index).unwrap_or(0);
188 glyph_advance = 4.0 * advance as f64 * text_scale;
189 }
190 }
191
192 shaped_buffer.set(i, GlyphInfo::with_offset(glyph_index, glyph_cluster, glyph_advance, glyph_offset));
193 }
194
195 shaped_buffer
196 })
197 }
198
199 fn create_typeface(&self, glyph_typeface: &GlyphTypeface) -> Rc<dyn ITextShaperTypeface> {
200 Rc::new(HarfBuzzTypeface::new(glyph_typeface))
201 }
202}