1use super::buffer::*;
2use super::font_funcs::FontFuncsDispatch;
3use super::ot_map::*;
4use super::ot_shape::*;
5use super::ot_shape_normalize::*;
6use super::ot_shape_plan::hb_ot_shape_plan_t;
7use super::ot_shaper::*;
8use super::ot_shaper_indic::ot_category_t;
9use super::ot_shaper_syllabic::*;
10use super::unicode::CharExt;
11use super::unicode::Codepoint;
12use super::{hb_mask_t, hb_tag_t, GlyphInfo};
13use alloc::boxed::Box;
14
15pub const KHMER_SHAPER: hb_ot_shaper_t = hb_ot_shaper_t {
16 collect_features: Some(collect_features),
17 override_features: Some(override_features),
18 create_data: Some(|plan| Box::new(KhmerShapePlan::new(plan))),
19 preprocess_text: None,
20 postprocess_glyphs: None,
21 normalization_preference: HB_OT_SHAPE_NORMALIZATION_MODE_COMPOSED_DIACRITICS_NO_SHORT_CIRCUIT,
22 decompose: Some(decompose),
23 compose: Some(compose),
24 setup_masks: Some(setup_masks),
25 gpos_tag: None,
26 reorder_marks: None,
27 zero_width_marks: HB_OT_SHAPE_ZERO_WIDTH_MARKS_NONE,
28 fallback_position: false,
29};
30
31impl GlyphInfo {
32 declare_buffer_var_alias!(
33 OT_SHAPER_VAR_U8_CATEGORY_VAR,
34 u8,
35 KHMER_CATEGORY_VAR,
36 khmer_category,
37 set_khmer_category
38 );
39}
40
41const KHMER_FEATURES: &[(hb_tag_t, hb_ot_map_feature_flags_t)] = &[
42 (hb_tag_t::new(b"pref"), F_MANUAL_JOINERS | F_PER_SYLLABLE),
46 (hb_tag_t::new(b"blwf"), F_MANUAL_JOINERS | F_PER_SYLLABLE),
47 (hb_tag_t::new(b"abvf"), F_MANUAL_JOINERS | F_PER_SYLLABLE),
48 (hb_tag_t::new(b"pstf"), F_MANUAL_JOINERS | F_PER_SYLLABLE),
49 (hb_tag_t::new(b"cfar"), F_MANUAL_JOINERS | F_PER_SYLLABLE),
50 (hb_tag_t::new(b"pres"), F_GLOBAL_MANUAL_JOINERS),
53 (hb_tag_t::new(b"abvs"), F_GLOBAL_MANUAL_JOINERS),
54 (hb_tag_t::new(b"blws"), F_GLOBAL_MANUAL_JOINERS),
55 (hb_tag_t::new(b"psts"), F_GLOBAL_MANUAL_JOINERS),
56];
57
58mod khmer_feature {
60 pub const PREF: usize = 0;
61 pub const BLWF: usize = 1;
62 pub const ABVF: usize = 2;
63 pub const PSTF: usize = 3;
64 pub const CFAR: usize = 4;
65}
66
67impl GlyphInfo {
68 fn set_khmer_properties(&mut self) {
69 let u = self.glyph_id;
70 let (cat, _) = crate::hb::ot_shaper_indic_table::get_categories(u);
71
72 self.set_khmer_category(cat);
73 }
74}
75
76struct KhmerShapePlan {
77 mask_array: [hb_mask_t; KHMER_FEATURES.len()],
78}
79
80impl KhmerShapePlan {
81 fn new(plan: &hb_ot_shape_plan_t) -> Self {
82 let mut mask_array = [0; KHMER_FEATURES.len()];
83 for (i, feature) in KHMER_FEATURES.iter().enumerate() {
84 mask_array[i] = if feature.1 & F_GLOBAL != 0 {
85 0
86 } else {
87 plan.ot_map.get_1_mask(feature.0)
88 }
89 }
90
91 KhmerShapePlan { mask_array }
92 }
93}
94
95fn collect_features(planner: &mut hb_ot_shape_planner_t) {
96 planner.ot_map.add_gsub_pause(Some(setup_syllables));
98 planner.ot_map.add_gsub_pause(Some(reorder_khmer));
99
100 planner
110 .ot_map
111 .enable_feature(hb_tag_t::new(b"locl"), F_PER_SYLLABLE, 1);
112 planner
113 .ot_map
114 .enable_feature(hb_tag_t::new(b"ccmp"), F_PER_SYLLABLE, 1);
115
116 for feature in KHMER_FEATURES.iter().take(5) {
117 planner.ot_map.add_feature(feature.0, feature.1, 1);
118 }
119
120 planner.ot_map.add_gsub_pause(Some(syllabic_clear_var)); for feature in KHMER_FEATURES.iter().skip(5) {
124 planner.ot_map.add_feature(feature.0, feature.1, 1);
125 }
126}
127
128fn setup_syllables(
129 _: &hb_ot_shape_plan_t,
130 _: &mut FontFuncsDispatch,
131 buffer: &mut hb_buffer_t,
132) -> bool {
133 buffer.allocate_var(GlyphInfo::SYLLABLE_VAR);
134
135 super::ot_shaper_khmer_machine::find_syllables_khmer(buffer);
136
137 let mut start = 0;
138 let mut end = buffer.next_syllable(0);
139 while start < buffer.len {
140 buffer.unsafe_to_break(Some(start), Some(end));
141 start = end;
142 end = buffer.next_syllable(start);
143 }
144
145 false
146}
147
148fn reorder_khmer(
149 plan: &hb_ot_shape_plan_t,
150 face: &mut FontFuncsDispatch,
151 buffer: &mut hb_buffer_t,
152) -> bool {
153 use super::ot_shaper_khmer_machine::SyllableType;
154
155 let mut ret = false;
156
157 if insert_dotted_circles(
158 face,
159 buffer,
160 SyllableType::BrokenCluster as u8,
161 ot_category_t::OT_DOTTEDCIRCLE,
162 Some(ot_category_t::OT_Repha),
163 None,
164 ) {
165 ret = true;
166 }
167
168 let khmer_plan = plan.data::<KhmerShapePlan>();
169
170 let mut start = 0;
171 let mut end = buffer.next_syllable(0);
172 while start < buffer.len {
173 reorder_syllable_khmer(khmer_plan, start, end, buffer);
174 start = end;
175 end = buffer.next_syllable(start);
176 }
177
178 buffer.deallocate_var(GlyphInfo::KHMER_CATEGORY_VAR);
179
180 ret
181}
182
183fn reorder_syllable_khmer(
184 khmer_plan: &KhmerShapePlan,
185 start: usize,
186 end: usize,
187 buffer: &mut hb_buffer_t,
188) {
189 use super::ot_shaper_khmer_machine::SyllableType;
190
191 let syllable_type = match buffer.info[start].syllable() & 0x0F {
192 0 => SyllableType::ConsonantSyllable,
193 1 => SyllableType::BrokenCluster,
194 2 => SyllableType::NonKhmerCluster,
195 _ => unreachable!(),
196 };
197
198 match syllable_type {
199 SyllableType::ConsonantSyllable | SyllableType::BrokenCluster => {
200 reorder_consonant_syllable(khmer_plan, start, end, buffer);
201 }
202 SyllableType::NonKhmerCluster => {}
203 }
204}
205
206fn reorder_consonant_syllable(
209 plan: &KhmerShapePlan,
210 start: usize,
211 end: usize,
212 buffer: &mut hb_buffer_t,
213) {
214 {
216 let mask = plan.mask_array[khmer_feature::BLWF]
218 | plan.mask_array[khmer_feature::ABVF]
219 | plan.mask_array[khmer_feature::PSTF];
220 for info in &mut buffer.info[start + 1..end] {
221 info.mask |= mask;
222 }
223 }
224
225 let mut num_coengs = 0;
226 for i in start + 1..end {
227 if buffer.info[i].indic_category() == ot_category_t::OT_H && num_coengs <= 2 && i + 1 < end
237 {
238 num_coengs += 1;
239
240 if buffer.info[i + 1].indic_category() == ot_category_t::OT_Ra {
241 for j in 0..2 {
242 buffer.info[i + j].mask |= plan.mask_array[khmer_feature::PREF];
243 }
244
245 buffer.merge_clusters(start, i + 2);
247 let t0 = buffer.info[i];
248 let t1 = buffer.info[i + 1];
249 for k in (0..i - start).rev() {
250 buffer.info[k + start + 2] = buffer.info[k + start];
251 }
252
253 buffer.info[start] = t0;
254 buffer.info[start + 1] = t1;
255
256 if plan.mask_array[khmer_feature::CFAR] != 0 {
262 for j in i + 2..end {
263 buffer.info[j].mask |= plan.mask_array[khmer_feature::CFAR];
264 }
265 }
266
267 num_coengs = 2; }
269 } else if buffer.info[i].indic_category() == ot_category_t::OT_VPre {
270 buffer.merge_clusters(start, i + 1);
274 let t = buffer.info[i];
275 for k in (0..i - start).rev() {
276 buffer.info[k + start + 1] = buffer.info[k + start];
277 }
278 buffer.info[start] = t;
279 }
280 }
281}
282
283fn override_features(planner: &mut hb_ot_shape_planner_t) {
284 planner
288 .ot_map
289 .enable_feature(hb_tag_t::new(b"clig"), F_NONE, 1);
290
291 planner.ot_map.disable_feature(hb_tag_t::new(b"liga"));
292}
293
294fn decompose(_: &hb_ot_shape_normalize_context_t, ab: Codepoint) -> Option<(Codepoint, Codepoint)> {
295 match ab {
297 0x17BE | 0x17BF | 0x17C0 | 0x17C4 | 0x17C5 => Some((0x17C1, ab)),
298 _ => crate::hb::unicode::decompose(ab),
299 }
300}
301
302fn compose(_: &hb_ot_shape_normalize_context_t, a: Codepoint, b: Codepoint) -> Option<Codepoint> {
303 if a.general_category().is_mark() {
305 return None;
306 }
307
308 crate::hb::unicode::compose(a, b)
309}
310
311fn setup_masks(_: &hb_ot_shape_plan_t, _: &mut FontFuncsDispatch, buffer: &mut hb_buffer_t) {
312 buffer.allocate_var(GlyphInfo::KHMER_CATEGORY_VAR);
313
314 for info in buffer.info_slice_mut() {
317 info.set_khmer_properties();
318 }
319}