1use super::algs::*;
2use super::buffer::*;
3use super::font_funcs::FontFuncsDispatch;
4use super::ot_layout::*;
5use super::ot_map::*;
6use super::ot_shape::*;
7use super::ot_shape_normalize::*;
8use super::ot_shape_plan::hb_ot_shape_plan_t;
9use super::ot_shaper::*;
10use super::ot_shaper_arabic::arabic_shape_plan_t;
11use super::ot_shaper_syllabic::*;
12use super::unicode::{CharExt, Codepoint};
13use super::{hb_mask_t, hb_tag_t, script, GlyphInfo, Script};
14use alloc::boxed::Box;
15
16pub const UNIVERSAL_SHAPER: hb_ot_shaper_t = hb_ot_shaper_t {
17 collect_features: Some(collect_features),
18 override_features: None,
19 create_data: Some(|plan| Box::new(UniversalShapePlan::new(plan))),
20 preprocess_text: Some(preprocess_text),
21 postprocess_glyphs: None,
22 normalization_preference: HB_OT_SHAPE_NORMALIZATION_MODE_COMPOSED_DIACRITICS_NO_SHORT_CIRCUIT,
23 decompose: None,
24 compose: Some(compose),
25 setup_masks: Some(setup_masks),
26 gpos_tag: None,
27 reorder_marks: None,
28 zero_width_marks: HB_OT_SHAPE_ZERO_WIDTH_MARKS_BY_GDEF_EARLY,
29 fallback_position: false,
30};
31
32impl GlyphInfo {
33 declare_buffer_var_alias!(
34 OT_SHAPER_VAR_U8_CATEGORY_VAR,
35 u8,
36 USE_CATEGORY_VAR,
37 use_category,
38 set_use_category
39 );
40
41 fn is_halant_use(&self) -> bool {
42 matches!(
43 self.use_category(),
44 category::H | category::HVM | category::IS
45 ) && !self.ligated()
46 }
47}
48
49pub type Category = u8;
50#[allow(dead_code)]
51pub mod category {
52 pub const O: u8 = 0; pub const B: u8 = 1; pub const N: u8 = 4; pub const GB: u8 = 5; pub const CGJ: u8 = 6;
61
62 pub const SUB: u8 = 11; pub const H: u8 = 12; pub const HN: u8 = 13; pub const ZWNJ: u8 = 14; pub const WJ: u8 = 16; pub const RSV: u8 = 17; pub const R: u8 = 18; pub const S: u8 = 19; pub const CS: u8 = 43; pub const IS: u8 = 44; pub const Sk: u8 = 48; pub const FAbv: u8 = 24; pub const FBlw: u8 = 25; pub const FPst: u8 = 26; pub const MAbv: u8 = 27; pub const MBlw: u8 = 28; pub const MPst: u8 = 29; pub const MPre: u8 = 30; pub const CMAbv: u8 = 31; pub const CMBlw: u8 = 32; pub const VAbv: u8 = 33; pub const VBlw: u8 = 34; pub const VPst: u8 = 35; pub const VPre: u8 = 22; pub const VMAbv: u8 = 37; pub const VMBlw: u8 = 38; pub const VMPst: u8 = 39; pub const VMPre: u8 = 23; pub const SMAbv: u8 = 41; pub const SMBlw: u8 = 42; pub const FMAbv: u8 = 45; pub const FMBlw: u8 = 46; pub const FMPst: u8 = 47; pub const G: u8 = 49; pub const J: u8 = 50; pub const SB: u8 = 51; pub const SE: u8 = 52; pub const HVM: u8 = 53; pub const HM: u8 = 54; pub const HR: u8 = 55; pub const RK: u8 = 56; }
124
125static BASIC_FEATURES: &[hb_tag_t] = &[
128 hb_tag_t::new(b"rkrf"),
129 hb_tag_t::new(b"abvf"),
130 hb_tag_t::new(b"blwf"),
131 hb_tag_t::new(b"half"),
132 hb_tag_t::new(b"pstf"),
133 hb_tag_t::new(b"vatu"),
134 hb_tag_t::new(b"cjct"),
135];
136
137static TOPOGRAPHICAL_FEATURES: &[hb_tag_t] = &[
138 hb_tag_t::new(b"isol"),
139 hb_tag_t::new(b"init"),
140 hb_tag_t::new(b"medi"),
141 hb_tag_t::new(b"fina"),
142];
143
144#[derive(Clone, Copy, PartialEq)]
146enum JoiningForm {
147 Isolated = 0,
148 Initial,
149 Medial,
150 Terminal,
151}
152
153static OTHER_FEATURES: &[hb_tag_t] = &[
155 hb_tag_t::new(b"abvs"),
156 hb_tag_t::new(b"blws"),
157 hb_tag_t::new(b"haln"),
158 hb_tag_t::new(b"pres"),
159 hb_tag_t::new(b"psts"),
160];
161
162struct UniversalShapePlan {
163 rphf_mask: hb_mask_t,
164 arabic_plan: Option<arabic_shape_plan_t>,
165}
166
167impl UniversalShapePlan {
168 fn new(plan: &hb_ot_shape_plan_t) -> UniversalShapePlan {
169 let mut arabic_plan = None;
170
171 if plan.script.is_some_and(has_arabic_joining) {
172 arabic_plan = Some(crate::hb::ot_shaper_arabic::data_create_arabic(plan));
173 }
174
175 UniversalShapePlan {
176 rphf_mask: plan.ot_map.get_1_mask(hb_tag_t::new(b"rphf")),
177 arabic_plan,
178 }
179 }
180}
181
182fn collect_features(planner: &mut hb_ot_shape_planner_t) {
183 planner.ot_map.add_gsub_pause(Some(setup_syllables));
185
186 planner
188 .ot_map
189 .enable_feature(hb_tag_t::new(b"locl"), F_PER_SYLLABLE, 1);
190 planner
191 .ot_map
192 .enable_feature(hb_tag_t::new(b"ccmp"), F_PER_SYLLABLE, 1);
193 planner
194 .ot_map
195 .enable_feature(hb_tag_t::new(b"nukt"), F_PER_SYLLABLE, 1);
196 planner
197 .ot_map
198 .enable_feature(hb_tag_t::new(b"akhn"), F_MANUAL_ZWJ | F_PER_SYLLABLE, 1);
199
200 planner
202 .ot_map
203 .add_gsub_pause(Some(_hb_clear_substitution_flags));
204 planner
205 .ot_map
206 .add_feature(hb_tag_t::new(b"rphf"), F_MANUAL_ZWJ | F_PER_SYLLABLE, 1);
207 planner.ot_map.add_gsub_pause(Some(record_rphf));
208 planner
209 .ot_map
210 .add_gsub_pause(Some(_hb_clear_substitution_flags));
211 planner
212 .ot_map
213 .enable_feature(hb_tag_t::new(b"pref"), F_MANUAL_ZWJ | F_PER_SYLLABLE, 1);
214 planner.ot_map.add_gsub_pause(Some(record_pref));
215
216 for feature in BASIC_FEATURES {
218 planner
219 .ot_map
220 .enable_feature(*feature, F_MANUAL_ZWJ | F_PER_SYLLABLE, 1);
221 }
222
223 planner.ot_map.add_gsub_pause(Some(reorder_use));
224 planner.ot_map.add_gsub_pause(Some(syllabic_clear_var)); for feature in TOPOGRAPHICAL_FEATURES {
228 planner.ot_map.add_feature(*feature, F_NONE, 1);
229 }
230 planner.ot_map.add_gsub_pause(None);
231
232 for feature in OTHER_FEATURES {
234 planner.ot_map.enable_feature(*feature, F_MANUAL_ZWJ, 1);
235 }
236}
237
238fn setup_syllables(
239 plan: &hb_ot_shape_plan_t,
240 _: &mut FontFuncsDispatch,
241 buffer: &mut hb_buffer_t,
242) -> bool {
243 buffer.allocate_var(GlyphInfo::SYLLABLE_VAR);
244
245 super::ot_shaper_use_machine::find_syllables(buffer);
246
247 foreach_syllable!(buffer, start, end, {
248 buffer.unsafe_to_break(Some(start), Some(end));
249 });
250
251 setup_rphf_mask(plan, buffer);
252 setup_topographical_masks(plan, buffer);
253
254 false
255}
256
257fn setup_rphf_mask(plan: &hb_ot_shape_plan_t, buffer: &mut hb_buffer_t) -> bool {
258 let universal_plan = plan.data::<UniversalShapePlan>();
259
260 let mask = universal_plan.rphf_mask;
261 if mask == 0 {
262 return false;
263 }
264
265 let mut start = 0;
266 let mut end = buffer.next_syllable(0);
267 while start < buffer.len {
268 let limit = if buffer.info[start].use_category() == category::R {
269 1
270 } else {
271 core::cmp::min(3, end - start)
272 };
273
274 for i in start..start + limit {
275 buffer.info[i].mask |= mask;
276 }
277
278 start = end;
279 end = buffer.next_syllable(start);
280 }
281
282 false
283}
284
285fn setup_topographical_masks(plan: &hb_ot_shape_plan_t, buffer: &mut hb_buffer_t) {
286 use super::ot_shaper_use_machine::SyllableType;
287
288 if plan.data::<UniversalShapePlan>().arabic_plan.is_some() {
289 return;
290 }
291
292 let mut masks = [0; 4];
293 let mut all_masks = 0;
294 for i in 0..4 {
295 masks[i] = plan.ot_map.get_1_mask(TOPOGRAPHICAL_FEATURES[i]);
296 if masks[i] == plan.ot_map.get_global_mask() {
297 masks[i] = 0;
298 }
299
300 all_masks |= masks[i];
301 }
302
303 if all_masks == 0 {
304 return;
305 }
306
307 let other_masks = !all_masks;
308
309 let mut last_start = 0;
310 let mut last_form = None;
311 let mut start = 0;
312 let mut end = buffer.next_syllable(0);
313 while start < buffer.len {
314 let syllable = buffer.info[start].syllable() & 0x0F;
315 if syllable == SyllableType::HieroglyphCluster as u8
316 || syllable == SyllableType::NonCluster as u8
317 {
318 last_form = None;
319 } else {
320 let join = last_form == Some(JoiningForm::Terminal)
321 || last_form == Some(JoiningForm::Isolated);
322
323 if join {
324 let form = if last_form == Some(JoiningForm::Terminal) {
326 JoiningForm::Medial
327 } else {
328 JoiningForm::Initial
329 };
330
331 for i in last_start..start {
332 buffer.info[i].mask =
333 (buffer.info[i].mask & other_masks) | masks[form as usize];
334 }
335 }
336
337 let form = if join {
339 JoiningForm::Terminal
340 } else {
341 JoiningForm::Isolated
342 };
343 last_form = Some(form);
344 for i in start..end {
345 buffer.info[i].mask = (buffer.info[i].mask & other_masks) | masks[form as usize];
346 }
347 }
348
349 last_start = start;
350 start = end;
351 end = buffer.next_syllable(start);
352 }
353}
354
355fn record_rphf(
356 plan: &hb_ot_shape_plan_t,
357 _: &mut FontFuncsDispatch,
358 buffer: &mut hb_buffer_t,
359) -> bool {
360 let universal_plan = plan.data::<UniversalShapePlan>();
361
362 let mask = universal_plan.rphf_mask;
363 if mask == 0 {
364 return false;
365 }
366
367 let mut start = 0;
368 let mut end = buffer.next_syllable(0);
369 while start < buffer.len {
370 for i in start..end {
372 if buffer.info[i].mask & mask == 0 {
373 break;
374 }
375
376 if buffer.info[i].substituted() {
377 buffer.info[i].set_use_category(category::R);
378 break;
379 }
380 }
381
382 start = end;
383 end = buffer.next_syllable(start);
384 }
385
386 false
387}
388
389fn reorder_use(
390 _: &hb_ot_shape_plan_t,
391 font: &mut FontFuncsDispatch,
392 buffer: &mut hb_buffer_t,
393) -> bool {
394 use super::ot_shaper_use_machine::SyllableType;
395
396 let mut ret = false;
397
398 if insert_dotted_circles(
399 font,
400 buffer,
401 SyllableType::BrokenCluster as u8,
402 category::B,
403 Some(category::R),
404 None,
405 ) {
406 ret = true;
407 }
408
409 let mut start = 0;
410 let mut end = buffer.next_syllable(0);
411 while start < buffer.len {
412 reorder_syllable_use(start, end, buffer);
413 start = end;
414 end = buffer.next_syllable(start);
415 }
416
417 buffer.deallocate_var(GlyphInfo::USE_CATEGORY_VAR);
418
419 ret
420}
421
422const fn category_flag(c: Category) -> u32 {
423 rb_flag(c as u32)
424}
425
426const fn category_flag64(c: Category) -> u64 {
427 rb_flag64(c as u32)
428}
429
430const POST_BASE_FLAGS: u64 = category_flag64(category::FAbv)
431 | category_flag64(category::FBlw)
432 | category_flag64(category::FPst)
433 | category_flag64(category::FMAbv)
434 | category_flag64(category::FMBlw)
435 | category_flag64(category::FMPst)
436 | category_flag64(category::MAbv)
437 | category_flag64(category::MBlw)
438 | category_flag64(category::MPst)
439 | category_flag64(category::MPre)
440 | category_flag64(category::VAbv)
441 | category_flag64(category::VBlw)
442 | category_flag64(category::VPst)
443 | category_flag64(category::VPre)
444 | category_flag64(category::VMAbv)
445 | category_flag64(category::VMBlw)
446 | category_flag64(category::VMPst)
447 | category_flag64(category::VMPre);
448
449fn reorder_syllable_use(start: usize, end: usize, buffer: &mut hb_buffer_t) {
450 use super::ot_shaper_use_machine::SyllableType;
451
452 let syllable_type = (buffer.info[start].syllable() & 0x0F) as u32;
453 if (rb_flag_unsafe(syllable_type)
455 & (rb_flag(SyllableType::ViramaTerminatedCluster as u32)
456 | rb_flag(SyllableType::SakotTerminatedCluster as u32)
457 | rb_flag(SyllableType::StandardCluster as u32)
458 | rb_flag(SyllableType::BrokenCluster as u32)
459 | 0))
460 == 0
461 {
462 return;
463 }
464
465 if buffer.info[start].use_category() == category::R && end - start > 1 {
467 for i in start + 1..end {
469 let is_post_base_glyph =
470 (rb_flag64_unsafe(buffer.info[i].use_category() as u32) & POST_BASE_FLAGS) != 0
471 || buffer.info[i].is_halant_use();
472
473 if is_post_base_glyph || i == end - 1 {
474 let mut i = i;
478 if is_post_base_glyph {
479 i -= 1;
480 }
481
482 buffer.merge_clusters(start, i + 1);
483 let t = buffer.info[start];
484 for k in 0..i - start {
485 buffer.info[k + start] = buffer.info[k + start + 1];
486 }
487 buffer.info[i] = t;
488
489 break;
490 }
491 }
492 }
493
494 let mut j = start;
496 for i in start..end {
497 let flag = rb_flag_unsafe(buffer.info[i].use_category() as u32);
498 if buffer.info[i].is_halant_use() {
499 j = i + 1;
502 } else if (flag & (category_flag(category::VPre) | category_flag(category::VMPre))) != 0
503 && buffer.info[i].lig_comp() == 0
504 && j < i
505 {
506 buffer.merge_clusters(j, i + 1);
508 let t = buffer.info[i];
509 for k in (0..i - j).rev() {
510 buffer.info[k + j + 1] = buffer.info[k + j];
511 }
512 buffer.info[j] = t;
513 }
514 }
515}
516
517fn record_pref(
518 _: &hb_ot_shape_plan_t,
519 _: &mut FontFuncsDispatch,
520 buffer: &mut hb_buffer_t,
521) -> bool {
522 let mut start = 0;
523 let mut end = buffer.next_syllable(0);
524 while start < buffer.len {
525 for i in start..end {
527 if buffer.info[i].substituted() {
528 buffer.info[i].set_use_category(category::VPre);
529 break;
530 }
531 }
532
533 start = end;
534 end = buffer.next_syllable(start);
535 }
536
537 false
538}
539
540fn has_arabic_joining(script: Script) -> bool {
541 matches!(
543 script,
544 script::ADLAM
545 | script::ARABIC
546 | script::CHORASMIAN
547 | script::HANIFI_ROHINGYA
548 | script::MANDAIC
549 | script::MANICHAEAN
550 | script::MONGOLIAN
551 | script::NKO
552 | script::OLD_UYGHUR
553 | script::PHAGS_PA
554 | script::PSALTER_PAHLAVI
555 | script::SOGDIAN
556 | script::SYRIAC
557 )
558}
559
560fn preprocess_text(_: &hb_ot_shape_plan_t, _: &mut FontFuncsDispatch, buffer: &mut hb_buffer_t) {
561 super::ot_shaper_vowel_constraints::preprocess_text_vowel_constraints(buffer);
562}
563
564fn compose(_: &hb_ot_shape_normalize_context_t, a: Codepoint, b: Codepoint) -> Option<Codepoint> {
565 if a.general_category().is_mark() {
567 return None;
568 }
569
570 crate::hb::unicode::compose(a, b)
571}
572
573fn setup_masks(plan: &hb_ot_shape_plan_t, _: &mut FontFuncsDispatch, buffer: &mut hb_buffer_t) {
574 let universal_plan = plan.data::<UniversalShapePlan>();
575
576 if let Some(ref arabic_plan) = universal_plan.arabic_plan {
578 crate::hb::ot_shaper_arabic::setup_masks_inner(arabic_plan, plan.script, buffer);
579 }
580
581 buffer.allocate_var(GlyphInfo::USE_CATEGORY_VAR);
582
583 for info in buffer.info_slice_mut() {
586 info.set_use_category(super::ot_shaper_use_table::hb_use_get_category(
587 info.glyph_id as usize,
588 ));
589 }
590}