1use crate::Direction;
2use alloc::boxed::Box;
3
4use super::algs::*;
5use super::buffer::*;
6use super::ot_layout::*;
7use super::ot_map::*;
8use super::ot_shape::*;
9use super::ot_shape_normalize::HB_OT_SHAPE_NORMALIZATION_MODE_AUTO;
10use super::ot_shape_plan::hb_ot_shape_plan_t;
11use super::ot_shaper::*;
12use super::unicode::*;
13use super::{hb_font_t, hb_glyph_info_t, hb_mask_t, hb_tag_t, script, Script};
14
15const HB_BUFFER_SCRATCH_FLAG_ARABIC_HAS_STCH: hb_buffer_scratch_flags_t =
16    HB_BUFFER_SCRATCH_FLAG_SHAPER0;
17
18fn is_word_category(gc: hb_unicode_general_category_t) -> bool {
21    (rb_flag_unsafe(gc.to_rb())
22        & (rb_flag(hb_gc::RB_UNICODE_GENERAL_CATEGORY_UNASSIGNED)
23            | rb_flag(hb_gc::RB_UNICODE_GENERAL_CATEGORY_PRIVATE_USE)
24            | rb_flag(hb_gc::RB_UNICODE_GENERAL_CATEGORY_MODIFIER_LETTER)
25            | rb_flag(hb_gc::RB_UNICODE_GENERAL_CATEGORY_OTHER_LETTER)
26            | rb_flag(hb_gc::RB_UNICODE_GENERAL_CATEGORY_SPACING_MARK)
27            | rb_flag(hb_gc::RB_UNICODE_GENERAL_CATEGORY_ENCLOSING_MARK)
28            | rb_flag(hb_gc::RB_UNICODE_GENERAL_CATEGORY_NON_SPACING_MARK)
29            | rb_flag(hb_gc::RB_UNICODE_GENERAL_CATEGORY_DECIMAL_NUMBER)
30            | rb_flag(hb_gc::RB_UNICODE_GENERAL_CATEGORY_LETTER_NUMBER)
31            | rb_flag(hb_gc::RB_UNICODE_GENERAL_CATEGORY_OTHER_NUMBER)
32            | rb_flag(hb_gc::RB_UNICODE_GENERAL_CATEGORY_CURRENCY_SYMBOL)
33            | rb_flag(hb_gc::RB_UNICODE_GENERAL_CATEGORY_MODIFIER_SYMBOL)
34            | rb_flag(hb_gc::RB_UNICODE_GENERAL_CATEGORY_MATH_SYMBOL)
35            | rb_flag(hb_gc::RB_UNICODE_GENERAL_CATEGORY_OTHER_SYMBOL)))
36        != 0
37}
38
39#[derive(Clone, Copy, PartialEq, PartialOrd, Debug)]
40pub enum hb_arabic_joining_type_t {
41    U = 0,
42    L = 1,
43    R = 2,
44    D = 3,
45    GroupAlaph = 4,
47    GroupDalathRish = 5,
48    T = 7,
49    X = 8, }
51
52fn get_joining_type(u: char, gc: hb_unicode_general_category_t) -> hb_arabic_joining_type_t {
53    let j_type = super::ot_shaper_arabic_table::joining_type(u);
54    if j_type != hb_arabic_joining_type_t::X {
55        return j_type;
56    }
57
58    let ok = rb_flag_unsafe(gc.to_rb())
59        & (rb_flag(hb_gc::RB_UNICODE_GENERAL_CATEGORY_NON_SPACING_MARK)
60            | rb_flag(hb_gc::RB_UNICODE_GENERAL_CATEGORY_ENCLOSING_MARK)
61            | rb_flag(hb_gc::RB_UNICODE_GENERAL_CATEGORY_FORMAT));
62
63    if ok != 0 {
64        hb_arabic_joining_type_t::T
65    } else {
66        hb_arabic_joining_type_t::U
67    }
68}
69
70fn feature_is_syriac(tag: hb_tag_t) -> bool {
71    matches!(tag.to_bytes()[3], b'2' | b'3')
72}
73
74const ARABIC_FEATURES: &[hb_tag_t] = &[
75    hb_tag_t::from_bytes(b"isol"),
76    hb_tag_t::from_bytes(b"fina"),
77    hb_tag_t::from_bytes(b"fin2"),
78    hb_tag_t::from_bytes(b"fin3"),
79    hb_tag_t::from_bytes(b"medi"),
80    hb_tag_t::from_bytes(b"med2"),
81    hb_tag_t::from_bytes(b"init"),
82];
83
84mod arabic_action_t {
85    pub const ISOL: u8 = 0;
86    pub const FINA: u8 = 1;
87    pub const FIN2: u8 = 2;
88    pub const FIN3: u8 = 3;
89    pub const MEDI: u8 = 4;
90    pub const MED2: u8 = 5;
91    pub const INIT: u8 = 6;
92    pub const NONE: u8 = 7;
93
94    pub const STRETCHING_FIXED: u8 = 8;
96    pub const STRETCHING_REPEATING: u8 = 9;
97
98    #[inline]
99    pub fn is_stch(n: u8) -> bool {
100        matches!(n, STRETCHING_FIXED | STRETCHING_REPEATING)
101    }
102}
103
104const STATE_TABLE: &[[(u8, u8, u16); 6]] = &[
105    [
110        (arabic_action_t::NONE, arabic_action_t::NONE, 0),
111        (arabic_action_t::NONE, arabic_action_t::ISOL, 2),
112        (arabic_action_t::NONE, arabic_action_t::ISOL, 1),
113        (arabic_action_t::NONE, arabic_action_t::ISOL, 2),
114        (arabic_action_t::NONE, arabic_action_t::ISOL, 1),
115        (arabic_action_t::NONE, arabic_action_t::ISOL, 6),
116    ],
117    [
119        (arabic_action_t::NONE, arabic_action_t::NONE, 0),
120        (arabic_action_t::NONE, arabic_action_t::ISOL, 2),
121        (arabic_action_t::NONE, arabic_action_t::ISOL, 1),
122        (arabic_action_t::NONE, arabic_action_t::ISOL, 2),
123        (arabic_action_t::NONE, arabic_action_t::FIN2, 5),
124        (arabic_action_t::NONE, arabic_action_t::ISOL, 6),
125    ],
126    [
128        (arabic_action_t::NONE, arabic_action_t::NONE, 0),
129        (arabic_action_t::NONE, arabic_action_t::ISOL, 2),
130        (arabic_action_t::INIT, arabic_action_t::FINA, 1),
131        (arabic_action_t::INIT, arabic_action_t::FINA, 3),
132        (arabic_action_t::INIT, arabic_action_t::FINA, 4),
133        (arabic_action_t::INIT, arabic_action_t::FINA, 6),
134    ],
135    [
137        (arabic_action_t::NONE, arabic_action_t::NONE, 0),
138        (arabic_action_t::NONE, arabic_action_t::ISOL, 2),
139        (arabic_action_t::MEDI, arabic_action_t::FINA, 1),
140        (arabic_action_t::MEDI, arabic_action_t::FINA, 3),
141        (arabic_action_t::MEDI, arabic_action_t::FINA, 4),
142        (arabic_action_t::MEDI, arabic_action_t::FINA, 6),
143    ],
144    [
146        (arabic_action_t::NONE, arabic_action_t::NONE, 0),
147        (arabic_action_t::NONE, arabic_action_t::ISOL, 2),
148        (arabic_action_t::MED2, arabic_action_t::ISOL, 1),
149        (arabic_action_t::MED2, arabic_action_t::ISOL, 2),
150        (arabic_action_t::MED2, arabic_action_t::FIN2, 5),
151        (arabic_action_t::MED2, arabic_action_t::ISOL, 6),
152    ],
153    [
155        (arabic_action_t::NONE, arabic_action_t::NONE, 0),
156        (arabic_action_t::NONE, arabic_action_t::ISOL, 2),
157        (arabic_action_t::ISOL, arabic_action_t::ISOL, 1),
158        (arabic_action_t::ISOL, arabic_action_t::ISOL, 2),
159        (arabic_action_t::ISOL, arabic_action_t::FIN2, 5),
160        (arabic_action_t::ISOL, arabic_action_t::ISOL, 6),
161    ],
162    [
164        (arabic_action_t::NONE, arabic_action_t::NONE, 0),
165        (arabic_action_t::NONE, arabic_action_t::ISOL, 2),
166        (arabic_action_t::NONE, arabic_action_t::ISOL, 1),
167        (arabic_action_t::NONE, arabic_action_t::ISOL, 2),
168        (arabic_action_t::NONE, arabic_action_t::FIN3, 5),
169        (arabic_action_t::NONE, arabic_action_t::ISOL, 6),
170    ],
171];
172
173impl hb_glyph_info_t {
174    fn arabic_shaping_action(&self) -> u8 {
175        self.ot_shaper_var_u8_auxiliary()
176    }
177
178    fn set_arabic_shaping_action(&mut self, action: u8) {
179        self.set_ot_shaper_var_u8_auxiliary(action)
180    }
181}
182
183fn collect_features(planner: &mut hb_ot_shape_planner_t) {
184    planner
206        .ot_map
207        .enable_feature(hb_tag_t::from_bytes(b"stch"), F_NONE, 1);
208    planner.ot_map.add_gsub_pause(Some(record_stch));
209
210    planner
211        .ot_map
212        .enable_feature(hb_tag_t::from_bytes(b"ccmp"), F_MANUAL_ZWJ, 1);
213    planner
214        .ot_map
215        .enable_feature(hb_tag_t::from_bytes(b"locl"), F_MANUAL_ZWJ, 1);
216
217    planner.ot_map.add_gsub_pause(None);
218
219    for feature in ARABIC_FEATURES {
220        let has_fallback = planner.script == Some(script::ARABIC) && !feature_is_syriac(*feature);
221        let flags = if has_fallback { F_HAS_FALLBACK } else { F_NONE };
222        planner
223            .ot_map
224            .add_feature(*feature, F_MANUAL_ZWJ | flags, 1);
225        planner.ot_map.add_gsub_pause(None);
226    }
227
228    planner.ot_map.enable_feature(
233        hb_tag_t::from_bytes(b"rlig"),
234        F_MANUAL_ZWJ | F_HAS_FALLBACK,
235        1,
236    );
237
238    if planner.script == Some(script::ARABIC) {
239        planner.ot_map.add_gsub_pause(Some(arabic_fallback_shape));
240    }
241
242    planner
245        .ot_map
246        .enable_feature(hb_tag_t::from_bytes(b"calt"), F_MANUAL_ZWJ, 1);
247    if !planner.ot_map.has_feature(hb_tag_t::from_bytes(b"rclt")) {
249        planner.ot_map.add_gsub_pause(None);
250    }
251
252    planner
253        .ot_map
254        .enable_feature(hb_tag_t::from_bytes(b"liga"), F_MANUAL_ZWJ, 1);
255    planner
256        .ot_map
257        .enable_feature(hb_tag_t::from_bytes(b"clig"), F_MANUAL_ZWJ, 1);
258
259    planner
270        .ot_map
271        .enable_feature(hb_tag_t::from_bytes(b"mset"), F_MANUAL_ZWJ, 1);
272}
273
274pub struct arabic_shape_plan_t {
275    mask_array: [hb_mask_t; ARABIC_FEATURES.len() + 1],
280    has_stch: bool,
281}
282
283pub fn data_create_arabic(plan: &hb_ot_shape_plan_t) -> arabic_shape_plan_t {
284    let has_stch = plan.ot_map.get_1_mask(hb_tag_t::from_bytes(b"stch")) != 0;
285
286    let mut mask_array = [0; ARABIC_FEATURES.len() + 1];
287    for i in 0..ARABIC_FEATURES.len() {
288        mask_array[i] = plan.ot_map.get_1_mask(ARABIC_FEATURES[i]);
289    }
290
291    arabic_shape_plan_t {
292        mask_array,
293        has_stch,
294    }
295}
296
297fn arabic_joining(buffer: &mut hb_buffer_t) {
298    let mut prev: Option<usize> = None;
299    let mut state = 0;
300
301    for i in 0..buffer.context_len[0] {
303        let c = buffer.context[0][i];
304        let this_type = get_joining_type(c, c.general_category());
305        if this_type == hb_arabic_joining_type_t::T {
306            continue;
307        }
308
309        state = STATE_TABLE[state][this_type as usize].2 as usize;
310        break;
311    }
312
313    for i in 0..buffer.len {
314        let this_type = get_joining_type(
315            buffer.info[i].as_char(),
316            _hb_glyph_info_get_general_category(&buffer.info[i]),
317        );
318        if this_type == hb_arabic_joining_type_t::T {
319            buffer.info[i].set_arabic_shaping_action(arabic_action_t::NONE);
320            continue;
321        }
322
323        let entry = &STATE_TABLE[state][this_type as usize];
324        if entry.0 != arabic_action_t::NONE && prev.is_some() {
325            if let Some(prev) = prev {
326                buffer.info[prev].set_arabic_shaping_action(entry.0);
327                buffer.safe_to_insert_tatweel(Some(prev), Some(i + 1));
328            }
329        }
330        else {
332            if let Some(prev) = prev {
333                if this_type >= hb_arabic_joining_type_t::R || (2 <= state && state <= 5) {
334                    buffer.unsafe_to_concat(Some(prev), Some(i + 1));
335                }
336            } else {
337                if this_type >= hb_arabic_joining_type_t::R {
338                    buffer.unsafe_to_concat_from_outbuffer(Some(0), Some(i + 1));
339                }
340            }
341        }
342
343        buffer.info[i].set_arabic_shaping_action(entry.1);
344
345        prev = Some(i);
346        state = entry.2 as usize;
347    }
348
349    for i in 0..buffer.context_len[1] {
350        let c = buffer.context[1][i];
351        let this_type = get_joining_type(c, c.general_category());
352        if this_type == hb_arabic_joining_type_t::T {
353            continue;
354        }
355
356        let entry = &STATE_TABLE[state][this_type as usize];
357        if entry.0 != arabic_action_t::NONE && prev.is_some() {
358            if let Some(prev) = prev {
359                buffer.info[prev].set_arabic_shaping_action(entry.0);
360                buffer.safe_to_insert_tatweel(Some(prev), Some(buffer.len));
361            }
362        }
363        else if 2 <= state && state <= 5 {
365            if let Some(prev) = prev {
366                buffer.unsafe_to_concat(Some(prev), Some(buffer.len));
367            }
368        }
369
370        break;
371    }
372}
373
374fn mongolian_variation_selectors(buffer: &mut hb_buffer_t) {
375    let len = buffer.len;
377    let info = &mut buffer.info;
378    for i in 1..len {
379        if (0x180B..=0x180D).contains(&info[i].glyph_id) || info[i].glyph_id == 0x180F {
380            let a = info[i - 1].arabic_shaping_action();
381            info[i].set_arabic_shaping_action(a);
382        }
383    }
384}
385
386fn setup_masks_arabic_plan(plan: &hb_ot_shape_plan_t, _: &hb_font_t, buffer: &mut hb_buffer_t) {
387    let arabic_plan = plan.data::<arabic_shape_plan_t>();
388    setup_masks_inner(arabic_plan, plan.script, buffer)
389}
390
391pub fn setup_masks_inner(
392    arabic_plan: &arabic_shape_plan_t,
393    script: Option<Script>,
394    buffer: &mut hb_buffer_t,
395) {
396    arabic_joining(buffer);
397    if script == Some(script::MONGOLIAN) {
398        mongolian_variation_selectors(buffer);
399    }
400
401    for info in buffer.info_slice_mut() {
402        info.mask |= arabic_plan.mask_array[info.arabic_shaping_action() as usize];
403    }
404}
405
406fn arabic_fallback_shape(_: &hb_ot_shape_plan_t, _: &hb_font_t, _: &mut hb_buffer_t) -> bool {
407    false
408}
409
410fn record_stch(plan: &hb_ot_shape_plan_t, _: &hb_font_t, buffer: &mut hb_buffer_t) -> bool {
416    let arabic_plan = plan.data::<arabic_shape_plan_t>();
417    if !arabic_plan.has_stch {
418        return false;
419    }
420
421    let len = buffer.len;
427    let info = &mut buffer.info;
428    let mut has_stch = false;
429    for glyph_info in &mut info[..len] {
430        if _hb_glyph_info_multiplied(glyph_info) {
431            let comp = if _hb_glyph_info_get_lig_comp(glyph_info) % 2 != 0 {
432                arabic_action_t::STRETCHING_REPEATING
433            } else {
434                arabic_action_t::STRETCHING_FIXED
435            };
436
437            glyph_info.set_arabic_shaping_action(comp);
438            has_stch = true;
439        }
440    }
441
442    if has_stch {
443        buffer.scratch_flags |= HB_BUFFER_SCRATCH_FLAG_ARABIC_HAS_STCH;
444    }
445
446    false
447}
448
449fn apply_stch(face: &hb_font_t, buffer: &mut hb_buffer_t) {
450    if buffer.scratch_flags & HB_BUFFER_SCRATCH_FLAG_ARABIC_HAS_STCH == 0 {
451        return;
452    }
453
454    let rtl = buffer.direction == Direction::RightToLeft;
455
456    if !rtl {
457        buffer.reverse();
458    }
459
460    let mut extra_glyphs_needed: usize = 0; const MEASURE: usize = 0;
467    const CUT: usize = 1;
468
469    for step in 0..2 {
470        let new_len = buffer.len + extra_glyphs_needed; let mut i = buffer.len;
472        let mut j = new_len;
473        while i != 0 {
474            if !arabic_action_t::is_stch(buffer.info[i - 1].arabic_shaping_action()) {
475                if step == CUT {
476                    j -= 1;
477                    buffer.info[j] = buffer.info[i - 1];
478                    buffer.pos[j] = buffer.pos[i - 1];
479                }
480
481                i -= 1;
482                continue;
483            }
484
485            let mut w_total = 0; let mut w_fixed = 0; let mut w_repeating = 0; let mut n_repeating: i32 = 0;
491
492            let end = i;
493            while i != 0 && arabic_action_t::is_stch(buffer.info[i - 1].arabic_shaping_action()) {
494                i -= 1;
495                let width = face.glyph_h_advance(buffer.info[i].as_glyph());
496
497                if buffer.info[i].arabic_shaping_action() == arabic_action_t::STRETCHING_FIXED {
498                    w_fixed += width;
499                } else {
500                    w_repeating += width;
501                    n_repeating += 1;
502                }
503            }
504
505            let start = i;
506            let mut context = i;
507            while context != 0
508                && !arabic_action_t::is_stch(buffer.info[context - 1].arabic_shaping_action())
509                && (_hb_glyph_info_is_default_ignorable(&buffer.info[context - 1])
510                    || is_word_category(_hb_glyph_info_get_general_category(
511                        &buffer.info[context - 1],
512                    )))
513            {
514                context -= 1;
515                w_total += buffer.pos[context].x_advance;
516            }
517
518            i += 1; let mut n_copies: i32 = 0;
522
523            let mut w_remaining = w_total - w_fixed;
524            if w_remaining > w_repeating && w_repeating > 0 {
525                n_copies = w_remaining / (w_repeating) - 1;
526            }
527
528            let mut extra_repeat_overlap = 0;
530            let shortfall = w_remaining - w_repeating * (n_copies + 1);
531            if shortfall > 0 && n_repeating > 0 {
532                n_copies += 1;
533                let excess = (n_copies + 1) * w_repeating - w_remaining;
534                if excess > 0 {
535                    extra_repeat_overlap = excess / (n_copies * n_repeating);
536                    w_remaining = 0;
537                }
538            }
539
540            if step == MEASURE {
541                extra_glyphs_needed += (n_copies * n_repeating) as usize;
542            } else {
543                buffer.unsafe_to_break(Some(context), Some(end));
544                let mut x_offset = w_remaining / 2;
545                for k in (start + 1..=end).rev() {
546                    let width = face.glyph_h_advance(buffer.info[k - 1].as_glyph());
547
548                    let mut repeat = 1;
549                    if buffer.info[k - 1].arabic_shaping_action()
550                        == arabic_action_t::STRETCHING_REPEATING
551                    {
552                        repeat += n_copies;
553                    }
554
555                    buffer.pos[k - 1].x_advance = 0;
556
557                    for n in 0..repeat {
558                        if rtl {
559                            x_offset -= width;
560                            if n > 0 {
561                                x_offset += extra_repeat_overlap;
562                            }
563                        }
564
565                        buffer.pos[k - 1].x_offset = x_offset;
566
567                        j -= 1;
569                        buffer.info[j] = buffer.info[k - 1];
570                        buffer.pos[j] = buffer.pos[k - 1];
571
572                        if !rtl {
573                            x_offset += width;
574
575                            if n > 0 {
576                                x_offset -= extra_repeat_overlap;
577                            }
578                        }
579                    }
580                }
581            }
582
583            i -= 1;
584        }
585
586        if step == MEASURE {
587            if !buffer.ensure(buffer.len + extra_glyphs_needed) {
588                break;
589            }
590        } else {
591            debug_assert_eq!(j, 0);
592            buffer.len = new_len;
593        }
594    }
595
596    if !rtl {
597        buffer.reverse();
598    }
599}
600
601fn postprocess_glyphs_arabic(_: &hb_ot_shape_plan_t, face: &hb_font_t, buffer: &mut hb_buffer_t) {
602    apply_stch(face, buffer)
603}
604
605const MODIFIER_COMBINING_MARKS: &[u32] = &[
607    0x0654, 0x0655, 0x0658, 0x06DC, 0x06E3, 0x06E7, 0x06E8, 0x08CA, 0x08CB, 0x08CD, 0x08CE, 0x08CF, 0x08D3, 0x08F3, ];
622
623fn reorder_marks_arabic(
624    _: &hb_ot_shape_plan_t,
625    buffer: &mut hb_buffer_t,
626    mut start: usize,
627    end: usize,
628) {
629    let mut i = start;
630    for cc in [220u8, 230].iter().cloned() {
631        while i < end && _hb_glyph_info_get_modified_combining_class(&buffer.info[i]) < cc {
632            i += 1;
633        }
634
635        if i == end {
636            break;
637        }
638
639        if _hb_glyph_info_get_modified_combining_class(&buffer.info[i]) > cc {
640            continue;
641        }
642
643        let mut j = i;
644        while j < end
645            && _hb_glyph_info_get_modified_combining_class(&buffer.info[j]) == cc
646            && MODIFIER_COMBINING_MARKS.contains(&buffer.info[j].glyph_id)
647        {
648            j += 1;
649        }
650
651        if i == j {
652            continue;
653        }
654
655        let mut temp = [hb_glyph_info_t::default(); MAX_COMBINING_MARKS];
657        debug_assert!(j - i <= MAX_COMBINING_MARKS);
658        buffer.merge_clusters(start, j);
659
660        temp[..j - i].copy_from_slice(&buffer.info[i..j]);
661
662        for k in (0..i - start).rev() {
663            buffer.info[k + start + j - i] = buffer.info[k + start];
664        }
665
666        buffer.info[start..][..j - i].copy_from_slice(&temp[..j - i]);
667
668        let new_start = start + j - i;
680        let new_cc = if cc == 220 {
681            modified_combining_class::CCC22
682        } else {
683            modified_combining_class::CCC26
684        };
685
686        while start < new_start {
687            _hb_glyph_info_set_modified_combining_class(&mut buffer.info[start], new_cc);
688            start += 1;
689        }
690
691        i = j;
692    }
693}
694
695pub const ARABIC_SHAPER: hb_ot_shaper_t = hb_ot_shaper_t {
696    collect_features: Some(collect_features),
697    override_features: None,
698    create_data: Some(|plan| Box::new(data_create_arabic(plan))),
699    preprocess_text: None,
700    postprocess_glyphs: Some(postprocess_glyphs_arabic),
701    normalization_preference: HB_OT_SHAPE_NORMALIZATION_MODE_AUTO,
702    decompose: None,
703    compose: None,
704    setup_masks: Some(setup_masks_arabic_plan),
705    gpos_tag: None,
706    reorder_marks: Some(reorder_marks_arabic),
707    zero_width_marks: HB_OT_SHAPE_ZERO_WIDTH_MARKS_BY_GDEF_LATE,
708    fallback_position: true,
709};