1use alloc::boxed::Box;
2use core::cmp;
3use core::ops::Range;
4
5use read_fonts::types::GlyphId;
6
7use super::algs::*;
8use super::buffer::*;
9use super::font_funcs::FontFuncsDispatch;
10use super::ot_layout::*;
11use super::ot_layout_gsubgpos::WouldApplyContext;
12use super::ot_map::*;
13use super::ot_shape::*;
14use super::ot_shape_normalize::*;
15use super::ot_shape_plan::hb_ot_shape_plan_t;
16use super::ot_shaper::*;
17use super::ot_shaper_syllabic::*;
18use super::unicode::Codepoint;
19use super::unicode::{hb_gc, CharExt};
20use super::{hb_font_t, hb_mask_t, hb_tag_t, script, GlyphInfo, Script};
21
22pub const INDIC_SHAPER: hb_ot_shaper_t = hb_ot_shaper_t {
23 collect_features: Some(collect_features),
24 override_features: Some(override_features),
25 create_data: Some(|plan| Box::new(IndicShapePlan::new(plan))),
26 preprocess_text: Some(preprocess_text),
27 postprocess_glyphs: None,
28 normalization_preference: HB_OT_SHAPE_NORMALIZATION_MODE_COMPOSED_DIACRITICS_NO_SHORT_CIRCUIT,
29 decompose: Some(decompose),
30 compose: Some(compose),
31 setup_masks: Some(setup_masks),
32 gpos_tag: None,
33 reorder_marks: None,
34 zero_width_marks: HB_OT_SHAPE_ZERO_WIDTH_MARKS_NONE,
35 fallback_position: false,
36};
37
38impl GlyphInfo {
39 declare_buffer_var_alias!(
40 OT_SHAPER_VAR_U8_CATEGORY_VAR,
41 u8,
42 INDIC_CATEGORY_VAR,
43 indic_category,
44 set_indic_category
45 );
46 declare_buffer_var_alias!(
47 OT_SHAPER_VAR_U8_AUXILIARY_VAR,
48 u8,
49 INDIC_POSITION_VAR,
50 indic_position,
51 set_indic_position
52 );
53
54 fn is_one_of(&self, flags: u32) -> bool {
55 if self.ligated() {
57 return false;
58 }
59
60 rb_flag_unsafe(self.indic_category() as u32) & flags != 0
61 }
62
63 fn is_joiner(&self) -> bool {
64 self.is_one_of(JOINER_FLAGS)
65 }
66
67 pub(crate) fn is_consonant(&self) -> bool {
68 self.is_one_of(CONSONANT_FLAGS_INDIC)
69 }
70
71 fn is_halant(&self) -> bool {
72 self.is_one_of(rb_flag(ot_category_t::OT_H as u32))
73 }
74
75 fn set_indic_properties(&mut self) {
76 let u = self.glyph_id;
77 let (cat, pos) = crate::hb::ot_shaper_indic_table::get_categories(u);
78
79 self.set_indic_category(cat);
80 self.set_indic_position(pos);
81 }
82}
83
84pub type Category = u8;
85
86#[allow(dead_code)]
90pub mod ot_category_t {
91 pub const OT_X: u8 = 0;
92 pub const OT_C: u8 = 1;
93 pub const OT_V: u8 = 2;
94 pub const OT_N: u8 = 3;
95 pub const OT_H: u8 = 4;
96 pub const OT_ZWNJ: u8 = 5;
97 pub const OT_ZWJ: u8 = 6;
98 pub const OT_M: u8 = 7;
99 pub const OT_SM: u8 = 8;
100 pub const OT_A: u8 = 9;
101 pub const OT_VD: u8 = OT_A;
102 pub const OT_PLACEHOLDER: u8 = 10;
103 pub const OT_GB: u8 = OT_PLACEHOLDER;
104 pub const OT_DOTTEDCIRCLE: u8 = 11;
105 pub const OT_RS: u8 = 12; pub const OT_MPst: u8 = 13;
107 pub const OT_Repha: u8 = 14; pub const OT_Ra: u8 = 15;
109 pub const OT_CM: u8 = 16; pub const OT_Symbol: u8 = 17; pub const OT_CS: u8 = 18;
112
113 pub const OT_VAbv: u8 = 20;
115 pub const OT_VBlw: u8 = 21;
116 pub const OT_VPre: u8 = 22;
117 pub const OT_VPst: u8 = 23;
118
119 pub const OT_Robatic: u8 = 25;
121 pub const OT_Xgroup: u8 = 26;
122 pub const OT_Ygroup: u8 = 27;
123
124 pub const OT_As: u8 = 32; pub const OT_MH: u8 = 35; pub const OT_MR: u8 = 36; pub const OT_MW: u8 = 37; pub const OT_MY: u8 = 38; pub const OT_PT: u8 = 39; pub const OT_VS: u8 = 40; pub const OT_ML: u8 = 41; pub const OT_SMPst: u8 = 57; pub const IV: u8 = 2;
141}
142
143pub mod ot_position_t {
144 pub const POS_START: u8 = 0;
145
146 pub const POS_RA_TO_BECOME_REPH: u8 = 1;
147 pub const POS_PRE_M: u8 = 2;
148 pub const POS_PRE_C: u8 = 3;
149
150 pub const POS_BASE_C: u8 = 4;
151 pub const POS_AFTER_MAIN: u8 = 5;
152
153 pub const POS_ABOVE_C: u8 = 6;
154
155 pub const POS_BEFORE_SUB: u8 = 7;
156 pub const POS_BELOW_C: u8 = 8;
157 pub const POS_AFTER_SUB: u8 = 9;
158
159 pub const POS_BEFORE_POST: u8 = 10;
160 pub const POS_POST_C: u8 = 11;
161 pub const POS_AFTER_POST: u8 = 12;
162
163 pub const POS_SMVD: u8 = 13;
164
165 pub const POS_END: u8 = 14;
166}
167
168const INDIC_FEATURES: &[(hb_tag_t, hb_ot_map_feature_flags_t)] = &[
169 (
173 hb_tag_t::new(b"nukt"),
174 F_GLOBAL_MANUAL_JOINERS | F_PER_SYLLABLE,
175 ),
176 (
177 hb_tag_t::new(b"akhn"),
178 F_GLOBAL_MANUAL_JOINERS | F_PER_SYLLABLE,
179 ),
180 (hb_tag_t::new(b"rphf"), F_MANUAL_JOINERS | F_PER_SYLLABLE),
181 (
182 hb_tag_t::new(b"rkrf"),
183 F_GLOBAL_MANUAL_JOINERS | F_PER_SYLLABLE,
184 ),
185 (hb_tag_t::new(b"pref"), F_MANUAL_JOINERS | F_PER_SYLLABLE),
186 (hb_tag_t::new(b"blwf"), F_MANUAL_JOINERS | F_PER_SYLLABLE),
187 (hb_tag_t::new(b"abvf"), F_MANUAL_JOINERS | F_PER_SYLLABLE),
188 (hb_tag_t::new(b"half"), F_MANUAL_JOINERS | F_PER_SYLLABLE),
189 (hb_tag_t::new(b"pstf"), F_MANUAL_JOINERS | F_PER_SYLLABLE),
190 (
191 hb_tag_t::new(b"vatu"),
192 F_GLOBAL_MANUAL_JOINERS | F_PER_SYLLABLE,
193 ),
194 (
195 hb_tag_t::new(b"cjct"),
196 F_GLOBAL_MANUAL_JOINERS | F_PER_SYLLABLE,
197 ),
198 (hb_tag_t::new(b"init"), F_MANUAL_JOINERS | F_PER_SYLLABLE),
204 (
205 hb_tag_t::new(b"pres"),
206 F_GLOBAL_MANUAL_JOINERS | F_PER_SYLLABLE,
207 ),
208 (
209 hb_tag_t::new(b"abvs"),
210 F_GLOBAL_MANUAL_JOINERS | F_PER_SYLLABLE,
211 ),
212 (
213 hb_tag_t::new(b"blws"),
214 F_GLOBAL_MANUAL_JOINERS | F_PER_SYLLABLE,
215 ),
216 (
217 hb_tag_t::new(b"psts"),
218 F_GLOBAL_MANUAL_JOINERS | F_PER_SYLLABLE,
219 ),
220 (
221 hb_tag_t::new(b"haln"),
222 F_GLOBAL_MANUAL_JOINERS | F_PER_SYLLABLE,
223 ),
224];
225
226#[allow(dead_code)]
228mod indic_feature {
229 pub const NUKT: usize = 0;
230 pub const AKHN: usize = 1;
231 pub const RPHF: usize = 2;
232 pub const RKRF: usize = 3;
233 pub const PREF: usize = 4;
234 pub const BLWF: usize = 5;
235 pub const ABVF: usize = 6;
236 pub const HALF: usize = 7;
237 pub const PSTF: usize = 8;
238 pub const VATU: usize = 9;
239 pub const CJCT: usize = 10;
240 pub const INIT: usize = 11;
241 pub const PRES: usize = 12;
242 pub const ABVS: usize = 13;
243 pub const BLWS: usize = 14;
244 pub const PSTS: usize = 15;
245 pub const HALN: usize = 16;
246}
247
248pub(crate) const fn category_flag(c: Category) -> u32 {
249 rb_flag(c as u32)
250}
251
252const CONSONANT_FLAGS_INDIC: u32 = category_flag(ot_category_t::OT_C)
258 | category_flag(ot_category_t::OT_CS)
259 | category_flag(ot_category_t::OT_Ra)
260 | category_flag(ot_category_t::OT_CM)
261 | category_flag(ot_category_t::OT_V)
262 | category_flag(ot_category_t::OT_PLACEHOLDER)
263 | category_flag(ot_category_t::OT_DOTTEDCIRCLE);
264
265const JOINER_FLAGS: u32 =
266 category_flag(ot_category_t::OT_ZWJ) | category_flag(ot_category_t::OT_ZWNJ);
267
268#[derive(Clone, Copy, PartialEq)]
269enum RephPosition {
270 AfterMain = ot_position_t::POS_AFTER_MAIN as isize,
271 BeforeSub = ot_position_t::POS_BEFORE_SUB as isize,
272 AfterSub = ot_position_t::POS_AFTER_SUB as isize,
273 BeforePost = ot_position_t::POS_BEFORE_POST as isize,
274 AfterPost = ot_position_t::POS_AFTER_POST as isize,
275}
276
277#[derive(Clone, Copy, PartialEq)]
278enum RephMode {
279 Implicit,
281 Explicit,
283 LogRepha,
285}
286
287#[derive(Clone, Copy, PartialEq)]
288enum BlwfMode {
289 PreAndPost,
291 PostOnly,
293}
294
295#[derive(Clone, Copy)]
296struct IndicConfig {
297 script: Option<Script>,
298 has_old_spec: bool,
299 virama: u32,
300 reph_pos: RephPosition,
301 reph_mode: RephMode,
302 blwf_mode: BlwfMode,
303}
304
305impl IndicConfig {
306 const fn new(
307 script: Option<Script>,
308 has_old_spec: bool,
309 virama: u32,
310 reph_pos: RephPosition,
311 reph_mode: RephMode,
312 blwf_mode: BlwfMode,
313 ) -> Self {
314 IndicConfig {
315 script,
316 has_old_spec,
317 virama,
318 reph_pos,
319 reph_mode,
320 blwf_mode,
321 }
322 }
323}
324
325static INDIC_CONFIGS: &[IndicConfig] = &[
326 IndicConfig::new(
327 None,
328 false,
329 0,
330 RephPosition::BeforePost,
331 RephMode::Implicit,
332 BlwfMode::PreAndPost,
333 ),
334 IndicConfig::new(
335 Some(script::DEVANAGARI),
336 true,
337 0x094D,
338 RephPosition::BeforePost,
339 RephMode::Implicit,
340 BlwfMode::PreAndPost,
341 ),
342 IndicConfig::new(
343 Some(script::BENGALI),
344 true,
345 0x09CD,
346 RephPosition::AfterSub,
347 RephMode::Implicit,
348 BlwfMode::PreAndPost,
349 ),
350 IndicConfig::new(
351 Some(script::GURMUKHI),
352 true,
353 0x0A4D,
354 RephPosition::BeforeSub,
355 RephMode::Implicit,
356 BlwfMode::PreAndPost,
357 ),
358 IndicConfig::new(
359 Some(script::GUJARATI),
360 true,
361 0x0ACD,
362 RephPosition::BeforePost,
363 RephMode::Implicit,
364 BlwfMode::PreAndPost,
365 ),
366 IndicConfig::new(
367 Some(script::ORIYA),
368 true,
369 0x0B4D,
370 RephPosition::AfterMain,
371 RephMode::Implicit,
372 BlwfMode::PreAndPost,
373 ),
374 IndicConfig::new(
375 Some(script::TAMIL),
376 true,
377 0x0BCD,
378 RephPosition::AfterPost,
379 RephMode::Implicit,
380 BlwfMode::PreAndPost,
381 ),
382 IndicConfig::new(
383 Some(script::TELUGU),
384 true,
385 0x0C4D,
386 RephPosition::AfterPost,
387 RephMode::Explicit,
388 BlwfMode::PostOnly,
389 ),
390 IndicConfig::new(
391 Some(script::KANNADA),
392 true,
393 0x0CCD,
394 RephPosition::AfterPost,
395 RephMode::Implicit,
396 BlwfMode::PostOnly,
397 ),
398 IndicConfig::new(
399 Some(script::MALAYALAM),
400 true,
401 0x0D4D,
402 RephPosition::AfterMain,
403 RephMode::LogRepha,
404 BlwfMode::PreAndPost,
405 ),
406 IndicConfig::new(
407 Some(script::SINHALA),
408 false,
409 0x0DCA,
410 RephPosition::AfterPost,
411 RephMode::Explicit,
412 BlwfMode::PreAndPost,
413 ),
414];
415
416struct IndicWouldSubstituteFeature {
417 lookups: Range<usize>,
418 zero_context: bool,
419}
420
421impl IndicWouldSubstituteFeature {
422 pub fn new(map: &hb_ot_map_t, feature_tag: hb_tag_t, zero_context: bool) -> Self {
423 IndicWouldSubstituteFeature {
424 lookups: match map.get_feature_stage(TableIndex::GSUB, feature_tag) {
425 Some(stage) => map.stage_lookup_range(TableIndex::GSUB, stage),
426 None => 0..0,
427 },
428 zero_context,
429 }
430 }
431
432 pub fn would_substitute(
433 &self,
434 map: &hb_ot_map_t,
435 face: &hb_font_t,
436 glyphs: &[GlyphId],
437 ) -> bool {
438 for index in self.lookups.clone() {
439 let lookup = map.lookup(TableIndex::GSUB, index);
440 let ctx = WouldApplyContext {
441 glyphs,
442 zero_context: self.zero_context,
443 };
444 if face
445 .ot_tables
446 .gsub
447 .as_ref()
448 .and_then(|table| table.get_lookup(lookup.index))
449 .is_some_and(|lookup| lookup.would_apply(face, &ctx) == Some(true))
450 {
451 return true;
452 }
453 }
454
455 false
456 }
457}
458
459struct IndicShapePlan {
460 config: IndicConfig,
461 is_old_spec: bool,
462 rphf: IndicWouldSubstituteFeature,
464 pref: IndicWouldSubstituteFeature,
465 blwf: IndicWouldSubstituteFeature,
466 pstf: IndicWouldSubstituteFeature,
467 vatu: IndicWouldSubstituteFeature,
468 mask_array: [hb_mask_t; INDIC_FEATURES.len()],
469}
470
471impl IndicShapePlan {
472 fn new(plan: &hb_ot_shape_plan_t) -> Self {
473 let script = plan.script;
474 let config = if let Some(c) = INDIC_CONFIGS.iter().skip(1).find(|c| c.script == script) {
475 *c
476 } else {
477 INDIC_CONFIGS[0]
478 };
479
480 let is_old_spec = config.has_old_spec
481 && plan
482 .ot_map
483 .chosen_script(TableIndex::GSUB)
484 .is_none_or(|tag| tag.to_be_bytes()[3] != b'2');
485
486 let zero_context = is_old_spec && script != Some(script::MALAYALAM);
495
496 let mut mask_array = [0; INDIC_FEATURES.len()];
497 for (i, feature) in INDIC_FEATURES.iter().enumerate() {
498 mask_array[i] = if feature.1 & F_GLOBAL != 0 {
499 0
500 } else {
501 plan.ot_map.get_1_mask(feature.0)
502 }
503 }
504
505 IndicShapePlan {
514 config,
515 is_old_spec,
516 rphf: IndicWouldSubstituteFeature::new(
518 &plan.ot_map,
519 hb_tag_t::new(b"rphf"),
520 zero_context,
521 ),
522 pref: IndicWouldSubstituteFeature::new(
523 &plan.ot_map,
524 hb_tag_t::new(b"pref"),
525 zero_context,
526 ),
527 blwf: IndicWouldSubstituteFeature::new(
528 &plan.ot_map,
529 hb_tag_t::new(b"blwf"),
530 zero_context,
531 ),
532 pstf: IndicWouldSubstituteFeature::new(
533 &plan.ot_map,
534 hb_tag_t::new(b"pstf"),
535 zero_context,
536 ),
537 vatu: IndicWouldSubstituteFeature::new(
538 &plan.ot_map,
539 hb_tag_t::new(b"vatu"),
540 zero_context,
541 ),
542 mask_array,
543 }
544 }
545}
546
547fn collect_features(planner: &mut hb_ot_shape_planner_t) {
548 planner.ot_map.add_gsub_pause(Some(setup_syllables));
550
551 planner
552 .ot_map
553 .enable_feature(hb_tag_t::new(b"locl"), F_PER_SYLLABLE, 1);
554 planner
557 .ot_map
558 .enable_feature(hb_tag_t::new(b"ccmp"), F_PER_SYLLABLE, 1);
559
560 planner.ot_map.add_gsub_pause(Some(initial_reordering));
561
562 for feature in INDIC_FEATURES.iter().take(11) {
563 planner.ot_map.add_feature(feature.0, feature.1, 1);
564 planner.ot_map.add_gsub_pause(None);
565 }
566
567 planner.ot_map.add_gsub_pause(Some(final_reordering));
568
569 for feature in INDIC_FEATURES.iter().skip(11) {
570 planner.ot_map.add_feature(feature.0, feature.1, 1);
571 }
572}
573
574fn override_features(planner: &mut hb_ot_shape_planner_t) {
575 planner.ot_map.disable_feature(hb_tag_t::new(b"liga"));
576 planner.ot_map.add_gsub_pause(Some(syllabic_clear_var)); }
578
579fn preprocess_text(_: &hb_ot_shape_plan_t, _: &mut FontFuncsDispatch, buffer: &mut hb_buffer_t) {
580 super::ot_shaper_vowel_constraints::preprocess_text_vowel_constraints(buffer);
581}
582
583fn decompose(_: &hb_ot_shape_normalize_context_t, ab: Codepoint) -> Option<(Codepoint, Codepoint)> {
584 match ab {
586 0x0931 | 0x09DC | 0x09DD | 0x0B94 => return None, _ => {}
592 }
593
594 crate::hb::unicode::decompose(ab)
595}
596
597fn compose(_: &hb_ot_shape_normalize_context_t, a: Codepoint, b: Codepoint) -> Option<Codepoint> {
598 if a.general_category().is_mark() {
600 return None;
601 }
602
603 if a == 0x09AF && b == 0x09BC {
605 return Some(0x09DF);
606 }
607
608 crate::hb::unicode::compose(a, b)
609}
610
611fn setup_masks(_: &hb_ot_shape_plan_t, _: &mut FontFuncsDispatch, buffer: &mut hb_buffer_t) {
612 buffer.allocate_var(GlyphInfo::INDIC_CATEGORY_VAR);
613 buffer.allocate_var(GlyphInfo::INDIC_POSITION_VAR);
614
615 for info in buffer.info_slice_mut() {
618 info.set_indic_properties();
619 }
620}
621
622fn setup_syllables(
623 _: &hb_ot_shape_plan_t,
624 _: &mut FontFuncsDispatch,
625 buffer: &mut hb_buffer_t,
626) -> bool {
627 buffer.allocate_var(GlyphInfo::SYLLABLE_VAR);
628
629 super::ot_shaper_indic_machine::find_syllables_indic(buffer);
630
631 let mut start = 0;
632 let mut end = buffer.next_syllable(0);
633 while start < buffer.len {
634 buffer.unsafe_to_break(Some(start), Some(end));
635 start = end;
636 end = buffer.next_syllable(start);
637 }
638
639 false
640}
641
642fn initial_reordering(
643 plan: &hb_ot_shape_plan_t,
644 font_funcs: &mut FontFuncsDispatch,
645 buffer: &mut hb_buffer_t,
646) -> bool {
647 use super::ot_shaper_indic_machine::SyllableType;
648
649 let mut ret = false;
650
651 let indic_plan = plan.data::<IndicShapePlan>();
652
653 update_consonant_positions(plan, indic_plan, font_funcs, buffer);
654 if insert_dotted_circles(
655 font_funcs,
656 buffer,
657 SyllableType::BrokenCluster as u8,
658 ot_category_t::OT_DOTTEDCIRCLE,
659 Some(ot_category_t::OT_Repha),
660 Some(ot_position_t::POS_END),
661 ) {
662 ret = true;
663 }
664
665 let mut start = 0;
666 let mut end = buffer.next_syllable(0);
667 while start < buffer.len {
668 initial_reordering_syllable(plan, indic_plan, font_funcs, start, end, buffer);
669 start = end;
670 end = buffer.next_syllable(start);
671 }
672
673 ret
674}
675
676fn update_consonant_positions(
677 plan: &hb_ot_shape_plan_t,
678 indic_plan: &IndicShapePlan,
679 font_funcs: &mut FontFuncsDispatch,
680 buffer: &mut hb_buffer_t,
681) {
682 let mut virama_glyph = None;
683 if indic_plan.config.virama != 0 {
684 virama_glyph = font_funcs.nominal_glyph(indic_plan.config.virama);
685 }
686
687 if let Some(virama) = virama_glyph {
688 let face = font_funcs.font();
689 for info in buffer.info_slice_mut() {
690 if info.indic_position() == ot_position_t::POS_BASE_C {
691 let consonant = info.as_glyph();
692 info.set_indic_position(consonant_position_from_face(
693 plan, indic_plan, face, consonant, virama,
694 ));
695 }
696 }
697 }
698}
699
700fn consonant_position_from_face(
701 plan: &hb_ot_shape_plan_t,
702 indic_plan: &IndicShapePlan,
703 face: &hb_font_t,
704 consonant: GlyphId,
705 virama: GlyphId,
706) -> u8 {
707 if indic_plan
722 .blwf
723 .would_substitute(&plan.ot_map, face, &[virama, consonant])
724 || indic_plan
725 .blwf
726 .would_substitute(&plan.ot_map, face, &[consonant, virama])
727 || indic_plan
728 .vatu
729 .would_substitute(&plan.ot_map, face, &[virama, consonant])
730 || indic_plan
731 .vatu
732 .would_substitute(&plan.ot_map, face, &[consonant, virama])
733 {
734 return ot_position_t::POS_BELOW_C;
735 }
736
737 if indic_plan
738 .pstf
739 .would_substitute(&plan.ot_map, face, &[virama, consonant])
740 || indic_plan
741 .pstf
742 .would_substitute(&plan.ot_map, face, &[consonant, virama])
743 {
744 return ot_position_t::POS_POST_C;
745 }
746
747 if indic_plan
748 .pref
749 .would_substitute(&plan.ot_map, face, &[virama, consonant])
750 || indic_plan
751 .pref
752 .would_substitute(&plan.ot_map, face, &[consonant, virama])
753 {
754 return ot_position_t::POS_POST_C;
755 }
756
757 ot_position_t::POS_BASE_C
758}
759
760fn initial_reordering_syllable(
761 plan: &hb_ot_shape_plan_t,
762 indic_plan: &IndicShapePlan,
763 font_funcs: &mut FontFuncsDispatch,
764 start: usize,
765 end: usize,
766 buffer: &mut hb_buffer_t,
767) {
768 use super::ot_shaper_indic_machine::SyllableType;
769
770 let syllable_type = match buffer.info[start].syllable() & 0x0F {
771 0 => SyllableType::ConsonantSyllable,
772 1 => SyllableType::VowelSyllable,
773 2 => SyllableType::StandaloneCluster,
774 3 => SyllableType::SymbolCluster,
775 4 => SyllableType::BrokenCluster,
776 5 => SyllableType::NonIndicCluster,
777 _ => unreachable!(),
778 };
779
780 match syllable_type {
781 SyllableType::VowelSyllable | SyllableType::ConsonantSyllable => {
783 initial_reordering_consonant_syllable(plan, indic_plan, font_funcs, start, end, buffer);
784 }
785 SyllableType::BrokenCluster | SyllableType::StandaloneCluster => {
787 initial_reordering_standalone_cluster(plan, indic_plan, font_funcs, start, end, buffer);
788 }
789 SyllableType::SymbolCluster | SyllableType::NonIndicCluster => {}
790 }
791}
792
793fn initial_reordering_consonant_syllable(
796 plan: &hb_ot_shape_plan_t,
797 indic_plan: &IndicShapePlan,
798 font_funcs: &mut FontFuncsDispatch,
799 start: usize,
800 end: usize,
801 buffer: &mut hb_buffer_t,
802) {
803 let face = font_funcs.font();
804 if buffer.script == Some(script::KANNADA)
808 && start + 3 <= end
809 && buffer.info[start].is_one_of(category_flag(ot_category_t::OT_Ra))
810 && buffer.info[start + 1].is_one_of(category_flag(ot_category_t::OT_H))
811 && buffer.info[start + 2].is_one_of(category_flag(ot_category_t::OT_ZWJ))
812 {
813 buffer.merge_clusters(start + 1, start + 3);
814 buffer.info.swap(start + 1, start + 2);
815 }
816
817 let mut base = end;
831 let mut has_reph = false;
832
833 {
834 let mut limit = start;
838 if indic_plan.mask_array[indic_feature::RPHF] != 0
839 && start + 3 <= end
840 && ((indic_plan.config.reph_mode == RephMode::Implicit
841 && !buffer.info[start + 2].is_joiner())
842 || (indic_plan.config.reph_mode == RephMode::Explicit
843 && buffer.info[start + 2].indic_category() == ot_category_t::OT_ZWJ))
844 {
845 let glyphs = &[
847 buffer.info[start].as_glyph(),
848 buffer.info[start + 1].as_glyph(),
849 if indic_plan.config.reph_mode == RephMode::Explicit {
850 buffer.info[start + 2].as_glyph()
851 } else {
852 GlyphId::NOTDEF
853 },
854 ];
855 if indic_plan
856 .rphf
857 .would_substitute(&plan.ot_map, face, &glyphs[0..2])
858 || (indic_plan.config.reph_mode == RephMode::Explicit
859 && indic_plan.rphf.would_substitute(&plan.ot_map, face, glyphs))
860 {
861 limit += 2;
862 while limit < end && buffer.info[limit].is_joiner() {
863 limit += 1;
864 }
865 base = start;
866 has_reph = true;
867 }
868 } else if indic_plan.config.reph_mode == RephMode::LogRepha
869 && buffer.info[start].indic_category() == ot_category_t::OT_Repha
870 {
871 limit += 1;
872 while limit < end && buffer.info[limit].is_joiner() {
873 limit += 1;
874 }
875 base = start;
876 has_reph = true;
877 }
878
879 {
880 let mut i = end;
882 let mut seen_below = false;
883 loop {
884 i -= 1;
885 if buffer.info[i].is_consonant() {
887 if buffer.info[i].indic_position() != ot_position_t::POS_BELOW_C
890 && (buffer.info[i].indic_position() != ot_position_t::POS_POST_C
891 || seen_below)
892 {
893 base = i;
894 break;
895 }
896 if buffer.info[i].indic_position() == ot_position_t::POS_BELOW_C {
897 seen_below = true;
898 }
899
900 base = i;
910 } else {
911 if start < i
917 && buffer.info[i].indic_category() == ot_category_t::OT_ZWJ
918 && buffer.info[i - 1].indic_category() == ot_category_t::OT_H
919 {
920 break;
921 }
922 }
923
924 if i <= limit {
925 break;
926 }
927 }
928 }
929
930 if has_reph && base == start && limit - base <= 2 {
936 has_reph = false;
938 }
939 }
940
941 for i in start..base {
972 let pos = buffer.info[i].indic_position();
973 buffer.info[i].set_indic_position(cmp::min(ot_position_t::POS_PRE_C, pos));
974 }
975
976 if base < end {
977 buffer.info[base].set_indic_position(ot_position_t::POS_BASE_C);
978 }
979
980 if has_reph {
982 buffer.info[start].set_indic_position(ot_position_t::POS_RA_TO_BECOME_REPH);
983 }
984
985 if indic_plan.is_old_spec {
1013 let disallow_double_halants = buffer.script == Some(script::KANNADA);
1014 for i in base + 1..end {
1015 if buffer.info[i].indic_category() == ot_category_t::OT_H {
1016 let mut j = end - 1;
1017 while j > i {
1018 if buffer.info[j].is_consonant()
1019 || (disallow_double_halants
1020 && buffer.info[j].indic_category() == ot_category_t::OT_H)
1021 {
1022 break;
1023 }
1024
1025 j -= 1;
1026 }
1027
1028 if buffer.info[j].indic_category() != ot_category_t::OT_H && j > i {
1029 let t = buffer.info[i];
1031 for k in 0..j - i {
1032 buffer.info[k + i] = buffer.info[k + i + 1];
1033 }
1034 buffer.info[j] = t;
1035 }
1036
1037 break;
1038 }
1039 }
1040 }
1041
1042 {
1044 let mut last_pos = ot_position_t::POS_START;
1045 for i in start..end {
1046 let ok = rb_flag_unsafe(buffer.info[i].indic_category() as u32)
1047 & (category_flag(ot_category_t::OT_ZWJ)
1048 | category_flag(ot_category_t::OT_ZWNJ)
1049 | category_flag(ot_category_t::OT_N)
1050 | category_flag(ot_category_t::OT_RS)
1051 | category_flag(ot_category_t::OT_CM)
1052 | category_flag(ot_category_t::OT_H))
1053 != 0;
1054 if ok {
1055 buffer.info[i].set_indic_position(last_pos);
1056
1057 if buffer.info[i].indic_category() == ot_category_t::OT_H
1058 && buffer.info[i].indic_position() == ot_position_t::POS_PRE_M
1059 {
1060 for j in (start + 1..=i).rev() {
1064 if buffer.info[j - 1].indic_position() != ot_position_t::POS_PRE_M {
1065 let pos = buffer.info[j - 1].indic_position();
1066 buffer.info[i].set_indic_position(pos);
1067 break;
1068 }
1069 }
1070 }
1071 } else if buffer.info[i].indic_position() != ot_position_t::POS_SMVD {
1072 if buffer.info[i].indic_category() == ot_category_t::OT_MPst
1073 && i > start
1074 && buffer.info[i - 1].indic_category() == ot_category_t::OT_SM
1075 {
1076 let val = buffer.info[i].indic_position();
1077 buffer.info[i - 1].set_indic_position(val);
1078 }
1079
1080 last_pos = buffer.info[i].indic_position();
1081 }
1082 }
1083 }
1084 {
1087 let mut last = base;
1088 for i in base + 1..end {
1089 if buffer.info[i].is_consonant() {
1090 for j in last + 1..i {
1091 if buffer.info[j].indic_position() < ot_position_t::POS_SMVD {
1092 let pos = buffer.info[i].indic_position();
1093 buffer.info[j].set_indic_position(pos);
1094 }
1095 }
1096
1097 last = i;
1098 } else if (rb_flag_unsafe(buffer.info[i].indic_category() as u32)
1099 & (rb_flag(ot_category_t::OT_M as u32) | rb_flag(ot_category_t::OT_MPst as u32)))
1100 != 0
1101 {
1102 last = i;
1103 }
1104 }
1105 }
1106
1107 {
1108 let syllable = buffer.info[start].syllable();
1110 for i in start..end {
1111 buffer.info[i].set_syllable((i - start) as u8);
1114 }
1115
1116 buffer.info[start..end].sort_by_key(|a| a.indic_position());
1117
1118 let mut first_left_mantra = end;
1120 let mut last_left_mantra = end;
1121 base = end;
1122
1123 for i in start..end {
1124 if buffer.info[i].indic_position() == ot_position_t::POS_BASE_C {
1125 base = i;
1126 break;
1127 } else if buffer.info[i].indic_position() == ot_position_t::POS_PRE_M {
1128 if first_left_mantra == end {
1129 first_left_mantra = i;
1130 }
1131
1132 last_left_mantra = i;
1133 }
1134 }
1135
1136 if first_left_mantra < last_left_mantra {
1138 buffer.reverse_range(first_left_mantra, last_left_mantra + 1);
1140 let mut i = first_left_mantra;
1142
1143 for j in i..=last_left_mantra {
1144 if (rb_flag_unsafe(buffer.info[j].indic_category() as u32)
1145 & (rb_flag(ot_category_t::OT_M as u32)
1146 | rb_flag(ot_category_t::OT_MPst as u32)))
1147 != 0
1148 {
1149 buffer.reverse_range(i, j + 1);
1150 i = j + 1;
1151 }
1152 }
1153 }
1154
1155 if indic_plan.is_old_spec || end - start > 127 {
1187 buffer.merge_clusters(base, end);
1188 } else {
1189 for i in base..end {
1191 if buffer.info[i].syllable() != 255 {
1192 let mut min = i;
1193 let mut max = i;
1194 let mut j = start + buffer.info[i].syllable() as usize;
1195 while j != i {
1196 min = cmp::min(min, j);
1197 max = cmp::max(max, j);
1198 let next = start + buffer.info[j].syllable() as usize;
1199 buffer.info[j].set_syllable(255); j = next;
1201 }
1202
1203 buffer.merge_clusters(cmp::max(base, min), max + 1);
1204 }
1205 }
1206 }
1207
1208 for info in &mut buffer.info[start..end] {
1210 info.set_syllable(syllable);
1211 }
1212 }
1213
1214 {
1217 for info in &mut buffer.info[start..end] {
1219 if info.indic_position() != ot_position_t::POS_RA_TO_BECOME_REPH {
1220 break;
1221 }
1222
1223 info.mask |= indic_plan.mask_array[indic_feature::RPHF];
1224 }
1225
1226 let mut mask = indic_plan.mask_array[indic_feature::HALF];
1228 if !indic_plan.is_old_spec && indic_plan.config.blwf_mode == BlwfMode::PreAndPost {
1229 mask |= indic_plan.mask_array[indic_feature::BLWF];
1230 }
1231
1232 for info in &mut buffer.info[start..base] {
1233 info.mask |= mask;
1234 }
1235
1236 mask = 0;
1238 if base < end {
1239 buffer.info[base].mask |= mask;
1240 }
1241
1242 mask = indic_plan.mask_array[indic_feature::BLWF]
1244 | indic_plan.mask_array[indic_feature::ABVF]
1245 | indic_plan.mask_array[indic_feature::PSTF];
1246 for i in base + 1..end {
1247 buffer.info[i].mask |= mask;
1248 }
1249 }
1250
1251 if indic_plan.is_old_spec && buffer.script == Some(script::DEVANAGARI) {
1252 for i in start..base.saturating_sub(1) {
1270 if buffer.info[i].indic_category() == ot_category_t::OT_Ra
1271 && buffer.info[i + 1].indic_category() == ot_category_t::OT_H
1272 && (i + 2 == base || buffer.info[i + 2].indic_category() != ot_category_t::OT_ZWJ)
1273 {
1274 buffer.info[i].mask |= indic_plan.mask_array[indic_feature::BLWF];
1275 buffer.info[i + 1].mask |= indic_plan.mask_array[indic_feature::BLWF];
1276 }
1277 }
1278 }
1279
1280 let pref_len = 2;
1281 if indic_plan.mask_array[indic_feature::PREF] != 0 && base + pref_len < end {
1282 for i in base + 1..end - pref_len + 1 {
1284 let glyphs = &[buffer.info[i + 0].as_glyph(), buffer.info[i + 1].as_glyph()];
1285 if indic_plan.pref.would_substitute(&plan.ot_map, face, glyphs) {
1286 buffer.info[i + 0].mask |= indic_plan.mask_array[indic_feature::PREF];
1287 buffer.info[i + 1].mask |= indic_plan.mask_array[indic_feature::PREF];
1288 break;
1289 }
1290 }
1291 }
1292
1293 for i in start + 1..end {
1295 if buffer.info[i].is_joiner() {
1296 let non_joiner = buffer.info[i].indic_category() == ot_category_t::OT_ZWNJ;
1297 let mut j = i;
1298
1299 loop {
1300 j -= 1;
1301
1302 if non_joiner {
1308 buffer.info[j].mask &= !indic_plan.mask_array[indic_feature::HALF];
1309 }
1310
1311 if j <= start || buffer.info[j].is_consonant() {
1312 break;
1313 }
1314 }
1315 }
1316 }
1317}
1318
1319fn initial_reordering_standalone_cluster(
1320 plan: &hb_ot_shape_plan_t,
1321 indic_plan: &IndicShapePlan,
1322 face: &mut FontFuncsDispatch,
1323 start: usize,
1324 end: usize,
1325 buffer: &mut hb_buffer_t,
1326) {
1327 initial_reordering_consonant_syllable(plan, indic_plan, face, start, end, buffer);
1330}
1331
1332fn final_reordering(
1333 plan: &hb_ot_shape_plan_t,
1334 face: &mut FontFuncsDispatch,
1335 buffer: &mut hb_buffer_t,
1336) -> bool {
1337 if buffer.is_empty() {
1338 return false;
1339 }
1340
1341 foreach_syllable!(buffer, start, end, {
1342 final_reordering_impl(plan, face, start, end, buffer);
1343 });
1344
1345 buffer.deallocate_var(GlyphInfo::INDIC_CATEGORY_VAR);
1346 buffer.deallocate_var(GlyphInfo::INDIC_POSITION_VAR);
1347
1348 false
1349}
1350
1351fn final_reordering_impl(
1352 plan: &hb_ot_shape_plan_t,
1353 face: &mut FontFuncsDispatch,
1354 start: usize,
1355 end: usize,
1356 buffer: &mut hb_buffer_t,
1357) {
1358 let indic_plan = plan.data::<IndicShapePlan>();
1359
1360 let mut virama_glyph = None;
1368 if indic_plan.config.virama != 0 {
1369 if let Some(g) = face.nominal_glyph(indic_plan.config.virama) {
1370 virama_glyph = Some(g.to_u32());
1371 }
1372 }
1373
1374 if let Some(virama_glyph) = virama_glyph {
1375 for info in &mut buffer.info[start..end] {
1376 if info.glyph_id == virama_glyph && info.ligated() && info.multiplied() {
1377 info.set_indic_category(ot_category_t::OT_H);
1379 info.clear_ligated_and_multiplied();
1380 }
1381 }
1382 }
1383
1384 let mut try_pref = indic_plan.mask_array[indic_feature::PREF] != 0;
1392
1393 let mut base = start;
1394 while base < end {
1395 if buffer.info[base].indic_position() as u32 >= ot_position_t::POS_BASE_C as u32 {
1396 if try_pref && base + 1 < end {
1397 for i in base + 1..end {
1398 if (buffer.info[i].mask & indic_plan.mask_array[indic_feature::PREF]) != 0 {
1399 if !(buffer.info[i].substituted()
1400 && buffer.info[i].ligated_and_didnt_multiply())
1401 {
1402 base = i;
1405 while base < end && buffer.info[base].is_halant() {
1406 base += 1;
1407 }
1408
1409 if base < end {
1410 buffer.info[base].set_indic_position(ot_position_t::POS_BASE_C);
1411 }
1412
1413 try_pref = false;
1414 }
1415
1416 break;
1417 }
1418
1419 if base == end {
1420 break;
1421 }
1422 }
1423 }
1424
1425 if buffer.script == Some(script::MALAYALAM) {
1427 let mut i = base + 1;
1428 while i < end {
1429 while i < end && buffer.info[i].is_joiner() {
1430 i += 1;
1431 }
1432
1433 if i == end || !buffer.info[i].is_halant() {
1434 break;
1435 }
1436
1437 i += 1; while i < end && buffer.info[i].is_joiner() {
1440 i += 1;
1441 }
1442
1443 if i < end
1444 && buffer.info[i].is_consonant()
1445 && buffer.info[i].indic_position() == ot_position_t::POS_BELOW_C
1446 {
1447 base = i;
1448 buffer.info[base].set_indic_position(ot_position_t::POS_BASE_C);
1449 }
1450
1451 i += 1;
1452 }
1453 }
1454
1455 if start < base
1456 && buffer.info[base].indic_position() as u32 > ot_position_t::POS_BASE_C as u32
1457 {
1458 base -= 1;
1459 }
1460
1461 break;
1462 }
1463
1464 base += 1;
1465 }
1466
1467 if base == end
1468 && start < base
1469 && buffer.info[base - 1].is_one_of(rb_flag(ot_category_t::OT_ZWJ as u32))
1470 {
1471 base -= 1;
1472 }
1473
1474 if base < end {
1475 while start < base
1476 && buffer.info[base].is_one_of(
1477 rb_flag(ot_category_t::OT_N as u32) | rb_flag(ot_category_t::OT_H as u32),
1478 )
1479 {
1480 base -= 1;
1481 }
1482 }
1483
1484 if start + 1 < end && start < base {
1513 let mut new_pos = if base == end { base - 2 } else { base - 1 };
1515
1516 if buffer.script != Some(script::MALAYALAM) && buffer.script != Some(script::TAMIL) {
1520 loop {
1521 while new_pos > start
1522 && !buffer.info[new_pos].is_one_of(
1523 rb_flag(ot_category_t::OT_M as u32)
1524 | rb_flag(ot_category_t::OT_MPst as u32)
1525 | rb_flag(ot_category_t::OT_H as u32),
1526 )
1527 {
1528 new_pos -= 1;
1529 }
1530
1531 if buffer.info[new_pos].is_halant()
1535 && buffer.info[new_pos].indic_position() != ot_position_t::POS_PRE_M
1536 {
1537 if new_pos + 1 < end {
1538 if buffer.info[new_pos + 1].indic_category() == ot_category_t::OT_ZWJ {
1540 if new_pos > start {
1542 new_pos -= 1;
1543 continue;
1544 }
1545 }
1546
1547 }
1555 } else {
1556 new_pos = start; }
1558
1559 break;
1560 }
1561 }
1562
1563 if start < new_pos && buffer.info[new_pos].indic_position() != ot_position_t::POS_PRE_M {
1564 for i in (start + 1..=new_pos).rev() {
1566 if buffer.info[i - 1].indic_position() == ot_position_t::POS_PRE_M {
1567 let old_pos = i - 1;
1568 if old_pos < base && base <= new_pos {
1570 base -= 1;
1571 }
1572
1573 let tmp = buffer.info[old_pos];
1574 for i in 0..new_pos - old_pos {
1575 buffer.info[i + old_pos] = buffer.info[i + old_pos + 1];
1576 }
1577 buffer.info[new_pos] = tmp;
1578
1579 buffer.merge_clusters(new_pos, cmp::min(end, base + 1));
1582
1583 new_pos -= 1;
1584 }
1585 }
1586 } else {
1587 for i in start..base {
1588 if buffer.info[i].indic_position() == ot_position_t::POS_PRE_M {
1589 buffer.merge_clusters(i, cmp::min(end, base + 1));
1590 break;
1591 }
1592 }
1593 }
1594 }
1595
1596 if start + 1 < end
1614 && buffer.info[start].indic_position() == ot_position_t::POS_RA_TO_BECOME_REPH
1615 && (buffer.info[start].indic_category() == ot_category_t::OT_Repha)
1616 ^ buffer.info[start].ligated_and_didnt_multiply()
1617 {
1618 let mut new_reph_pos;
1619 'reph: {
1620 let reph_pos = indic_plan.config.reph_pos;
1621
1622 if reph_pos != RephPosition::AfterPost {
1625 {
1636 new_reph_pos = start + 1;
1637 while new_reph_pos < base && !buffer.info[new_reph_pos].is_halant() {
1638 new_reph_pos += 1;
1639 }
1640
1641 if new_reph_pos < base && buffer.info[new_reph_pos].is_halant() {
1642 if new_reph_pos + 1 < base && buffer.info[new_reph_pos + 1].is_joiner() {
1644 new_reph_pos += 1;
1645 }
1646
1647 break 'reph;
1648 }
1649 }
1650
1651 if reph_pos == RephPosition::AfterMain {
1655 new_reph_pos = base;
1656 while new_reph_pos + 1 < end
1657 && buffer.info[new_reph_pos + 1].indic_position()
1658 <= ot_position_t::POS_AFTER_MAIN
1659 {
1660 new_reph_pos += 1;
1661 }
1662
1663 if new_reph_pos < end {
1664 break 'reph;
1665 }
1666 }
1667
1668 if reph_pos == RephPosition::AfterSub {
1675 new_reph_pos = base;
1676 while new_reph_pos + 1 < end
1677 && (rb_flag_unsafe(buffer.info[new_reph_pos + 1].indic_position() as u32)
1678 & (rb_flag(ot_position_t::POS_POST_C as u32)
1679 | rb_flag(ot_position_t::POS_AFTER_POST as u32)
1680 | rb_flag(ot_position_t::POS_SMVD as u32)))
1681 == 0
1682 {
1683 new_reph_pos += 1;
1684 }
1685
1686 if new_reph_pos < end {
1687 break 'reph;
1688 }
1689 }
1690 }
1691
1692 new_reph_pos = start + 1;
1701 while new_reph_pos < base && !buffer.info[new_reph_pos].is_halant() {
1702 new_reph_pos += 1;
1703 }
1704
1705 if new_reph_pos < base && buffer.info[new_reph_pos].is_halant() {
1706 if new_reph_pos + 1 < base && buffer.info[new_reph_pos + 1].is_joiner() {
1708 new_reph_pos += 1;
1709 }
1710
1711 break 'reph;
1712 }
1713 {
1717 new_reph_pos = end - 1;
1718 while new_reph_pos > start
1719 && buffer.info[new_reph_pos].indic_position() == ot_position_t::POS_SMVD
1720 {
1721 new_reph_pos -= 1;
1722 }
1723
1724 if buffer.info[new_reph_pos].is_halant() {
1730 for info in &buffer.info[base + 1..new_reph_pos] {
1731 if (rb_flag_unsafe(info.indic_category() as u32)
1732 & (rb_flag(ot_category_t::OT_M as u32)
1733 | rb_flag(ot_category_t::OT_MPst as u32)))
1734 != 0
1735 {
1736 new_reph_pos -= 1;
1738 }
1739 }
1740 }
1741 }
1742
1743 break 'reph;
1744 }
1745
1746 buffer.merge_clusters(start, new_reph_pos + 1);
1748
1749 let reph = buffer.info[start];
1750 for i in 0..new_reph_pos - start {
1751 buffer.info[i + start] = buffer.info[i + start + 1];
1752 }
1753 buffer.info[new_reph_pos] = reph;
1754
1755 if start < base && base <= new_reph_pos {
1756 base -= 1;
1757 }
1758 }
1759
1760 if try_pref && base + 1 < end {
1767 for i in base + 1..end {
1768 if (buffer.info[i].mask & indic_plan.mask_array[indic_feature::PREF]) != 0 {
1769 if buffer.info[i].ligated_and_didnt_multiply() {
1778 let mut new_pos = base;
1784 if buffer.script != Some(script::MALAYALAM)
1788 && buffer.script != Some(script::TAMIL)
1789 {
1790 while new_pos > start
1791 && !buffer.info[new_pos - 1].is_one_of(
1792 rb_flag(ot_category_t::OT_M as u32)
1793 | rb_flag(ot_category_t::OT_MPst as u32)
1794 | rb_flag(ot_category_t::OT_H as u32),
1795 )
1796 {
1797 new_pos -= 1;
1798 }
1799 }
1800
1801 if new_pos > start && buffer.info[new_pos - 1].is_halant() {
1802 if new_pos < end && buffer.info[new_pos].is_joiner() {
1804 new_pos += 1;
1805 }
1806 }
1807
1808 {
1809 let old_pos = i;
1810
1811 buffer.merge_clusters(new_pos, old_pos + 1);
1812 let tmp = buffer.info[old_pos];
1813 for i in (0..old_pos - new_pos).rev() {
1814 buffer.info[i + new_pos + 1] = buffer.info[i + new_pos];
1815 }
1816 buffer.info[new_pos] = tmp;
1817
1818 if new_pos <= base && base < old_pos {
1819 #[allow(unused_assignments)]
1821 {
1822 base += 1;
1823 }
1824 }
1825 }
1826 }
1827
1828 break;
1829 }
1830 }
1831 }
1832
1833 if buffer.info[start].indic_position() == ot_position_t::POS_PRE_M {
1835 if start == 0
1836 || (rb_flag_unsafe(buffer.info[start - 1].general_category().to_u8() as u32)
1837 & rb_flag_range(
1838 hb_gc::HB_UNICODE_GENERAL_CATEGORY_FORMAT,
1839 hb_gc::HB_UNICODE_GENERAL_CATEGORY_NON_SPACING_MARK,
1840 ))
1841 == 0
1842 {
1843 buffer.info[start].mask |= indic_plan.mask_array[indic_feature::INIT];
1844 } else {
1845 buffer.unsafe_to_break(Some(start - 1), Some(start + 1));
1846 }
1847 }
1848}