Skip to main content

harfrust/hb/
ot_shaper_use.rs

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; // OTHER
53
54    pub const B: u8 = 1; // BASE
55
56    // pub const IND: u8     = 3;    // BASE_IND
57
58    pub const N: u8 = 4; // BASE_NUM
59    pub const GB: u8 = 5; // BASE_OTHER
60    pub const CGJ: u8 = 6;
61
62    // pub const CGJ: u8     = 6;    // CGJ
63    // pub const F: u8       = 7;    // CONS_FINAL
64    // pub const FM: u8 = 8;         // CONS_FINAL_MOD
65    // pub const M: u8       = 9;    // CONS_MED
66    // pub const CM: u8      = 10;   // CONS_MOD
67
68    pub const SUB: u8 = 11; // CONS_SUB
69    pub const H: u8 = 12; // HALANT
70
71    pub const HN: u8 = 13; // HALANT_NUM
72    pub const ZWNJ: u8 = 14; // Zero width non-joiner
73
74    // pub const ZWJ: u8     = 15;   // Zero width joiner
75    pub const WJ: u8 = 16; // Word joiner
76
77    pub const RSV: u8 = 17; // Reserved characters
78    pub const R: u8 = 18; // REPHA
79    pub const S: u8 = 19; // SYM
80
81    // pub const SM: u8      = 20;   // SYM_MOD
82    // pub const VS: u8      = 21;   // VARIATION_SELECTOR
83    // pub const V: u8       = 36;   // VOWEL
84    // pub const VM: u8      = 40;   // VOWEL_MOD
85
86    pub const CS: u8 = 43; // CONS_WITH_STACKER
87
88    // https://github.com/harfbuzz/harfbuzz/issues/1102
89    pub const IS: u8 = 44; // HALANT_OR_VOWEL_MODIFIER
90
91    pub const Sk: u8 = 48; // SAKOT
92
93    pub const FAbv: u8 = 24; // CONS_FINAL_ABOVE
94    pub const FBlw: u8 = 25; // CONS_FINAL_BELOW
95    pub const FPst: u8 = 26; // CONS_FINAL_POST
96    pub const MAbv: u8 = 27; // CONS_MED_ABOVE
97    pub const MBlw: u8 = 28; // CONS_MED_BELOW
98    pub const MPst: u8 = 29; // CONS_MED_POST
99    pub const MPre: u8 = 30; // CONS_MED_PRE
100    pub const CMAbv: u8 = 31; // CONS_MOD_ABOVE
101    pub const CMBlw: u8 = 32; // CONS_MOD_BELOW
102    pub const VAbv: u8 = 33; // VOWEL_ABOVE / VOWEL_ABOVE_BELOW / VOWEL_ABOVE_BELOW_POST / VOWEL_ABOVE_POST
103    pub const VBlw: u8 = 34; // VOWEL_BELOW / VOWEL_BELOW_POST
104    pub const VPst: u8 = 35; // VOWEL_POST UIPC = Right
105    pub const VPre: u8 = 22; // VOWEL_PRE / VOWEL_PRE_ABOVE / VOWEL_PRE_ABOVE_POST / VOWEL_PRE_POST
106    pub const VMAbv: u8 = 37; // VOWEL_MOD_ABOVE
107    pub const VMBlw: u8 = 38; // VOWEL_MOD_BELOW
108    pub const VMPst: u8 = 39; // VOWEL_MOD_POST
109    pub const VMPre: u8 = 23; // VOWEL_MOD_PRE
110    pub const SMAbv: u8 = 41; // SYM_MOD_ABOVE
111    pub const SMBlw: u8 = 42; // SYM_MOD_BELOW
112    pub const FMAbv: u8 = 45; // CONS_FINAL_MOD UIPC = Top
113    pub const FMBlw: u8 = 46; // CONS_FINAL_MOD UIPC = Bottom
114    pub const FMPst: u8 = 47; // CONS_FINAL_MOD UIPC = Not_Applicable
115    pub const G: u8 = 49; // HIEROGLYPH
116    pub const J: u8 = 50; // HIEROGLYPH_JOINER
117    pub const SB: u8 = 51; // HIEROGLYPH_SEGMENT_BEGIN
118    pub const SE: u8 = 52; // HIEROGLYPH_SEGMENT_END
119    pub const HVM: u8 = 53; // HIEROGLYPH_SEGMENT_END
120    pub const HM: u8 = 54; // HIEROGLYPH_MOD
121    pub const HR: u8 = 55; // HIEROGLYPH_MIRROR
122    pub const RK: u8 = 56; // REORDERING_KILLER
123}
124
125// These features are applied all at once, before reordering,
126// constrained to the syllable.
127static 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// Same order as use_topographical_features.
145#[derive(Clone, Copy, PartialEq)]
146enum JoiningForm {
147    Isolated = 0,
148    Initial,
149    Medial,
150    Terminal,
151}
152
153// These features are applied all at once, after reordering and clearing syllables.
154static 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    // Do this before any lookups have been applied.
184    planner.ot_map.add_gsub_pause(Some(setup_syllables));
185
186    // Default glyph pre-processing group
187    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    // Reordering group
201    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    // Orthographic unit shaping group
217    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)); // Don't need syllables anymore.
225
226    // Topographical features
227    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    // Standard typographic presentation
233    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                // Fixup previous syllable's form.
325                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            // Form for this syllable.
338            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        // Mark a substituted repha as USE_R.
371        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    // Only a few syllable types need reordering.
454    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    // Move things forward.
466    if buffer.info[start].use_category() == category::R && end - start > 1 {
467        // Got a repha.  Reorder it towards the end, but before the first post-base glyph.
468        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                // If we hit a post-base glyph, move before it; otherwise move to the
475                // end. Shift things in between backward.
476
477                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    // Move things back.
495    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            // If we hit a halant, move after it; otherwise move to the beginning, and
500            // shift things in between forward.
501            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            // Only move the first component of a MultipleSubst.
507            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        // Mark a substituted pref as VPre, as they behave the same way.
526        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    // List of scripts that have data in arabic-table.
542    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    // Avoid recomposing split matras.
566    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    // Do this before allocating use_category().
577    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    // We cannot setup masks here. We save information about characters
584    // and setup masks later on in a pause-callback.
585    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}