rustybuzz/hb/
ot_shaper_myanmar.rs

1use super::buffer::hb_buffer_t;
2use super::ot_map::*;
3use super::ot_shape::*;
4use super::ot_shape_normalize::*;
5use super::ot_shape_plan::hb_ot_shape_plan_t;
6use super::ot_shaper::*;
7use super::ot_shaper_indic::{ot_category_t, ot_position_t};
8use super::{hb_font_t, hb_glyph_info_t, hb_tag_t};
9use crate::hb::ot_shaper_indic::ot_category_t::OT_VPre;
10
11pub const MYANMAR_SHAPER: hb_ot_shaper_t = hb_ot_shaper_t {
12    collect_features: Some(collect_features),
13    override_features: None,
14    create_data: None,
15    preprocess_text: None,
16    postprocess_glyphs: None,
17    normalization_preference: HB_OT_SHAPE_NORMALIZATION_MODE_COMPOSED_DIACRITICS_NO_SHORT_CIRCUIT,
18    decompose: None,
19    compose: None,
20    setup_masks: Some(setup_masks),
21    gpos_tag: None,
22    reorder_marks: None,
23    zero_width_marks: HB_OT_SHAPE_ZERO_WIDTH_MARKS_BY_GDEF_EARLY,
24    fallback_position: false,
25};
26
27// Ugly Zawgyi encoding.
28// Disable all auto processing.
29// https://github.com/harfbuzz/harfbuzz/issues/1162
30pub const MYANMAR_ZAWGYI_SHAPER: hb_ot_shaper_t = hb_ot_shaper_t {
31    collect_features: None,
32    override_features: None,
33    create_data: None,
34    preprocess_text: None,
35    postprocess_glyphs: None,
36    normalization_preference: HB_OT_SHAPE_NORMALIZATION_MODE_NONE,
37    decompose: None,
38    compose: None,
39    setup_masks: None,
40    gpos_tag: None,
41    reorder_marks: None,
42    zero_width_marks: HB_OT_SHAPE_ZERO_WIDTH_MARKS_NONE,
43    fallback_position: false,
44};
45
46const MYANMAR_FEATURES: &[hb_tag_t] = &[
47    // Basic features.
48    // These features are applied in order, one at a time, after reordering,
49    // constrained to the syllable.
50    hb_tag_t::from_bytes(b"rphf"),
51    hb_tag_t::from_bytes(b"pref"),
52    hb_tag_t::from_bytes(b"blwf"),
53    hb_tag_t::from_bytes(b"pstf"),
54    // Other features.
55    // These features are applied all at once after clearing syllables.
56    hb_tag_t::from_bytes(b"pres"),
57    hb_tag_t::from_bytes(b"abvs"),
58    hb_tag_t::from_bytes(b"blws"),
59    hb_tag_t::from_bytes(b"psts"),
60];
61
62impl hb_glyph_info_t {
63    fn set_myanmar_properties(&mut self) {
64        let u = self.glyph_id;
65        let (cat, _) = crate::hb::ot_shaper_indic_table::get_categories(u);
66
67        self.set_myanmar_category(cat);
68    }
69}
70
71fn collect_features(planner: &mut hb_ot_shape_planner_t) {
72    // Do this before any lookups have been applied.
73    planner.ot_map.add_gsub_pause(Some(setup_syllables));
74
75    planner
76        .ot_map
77        .enable_feature(hb_tag_t::from_bytes(b"locl"), F_PER_SYLLABLE, 1);
78    // The Indic specs do not require ccmp, but we apply it here since if
79    // there is a use of it, it's typically at the beginning.
80    planner
81        .ot_map
82        .enable_feature(hb_tag_t::from_bytes(b"ccmp"), F_PER_SYLLABLE, 1);
83
84    planner.ot_map.add_gsub_pause(Some(reorder_myanmar));
85
86    for feature in MYANMAR_FEATURES.iter().take(4) {
87        planner
88            .ot_map
89            .enable_feature(*feature, F_MANUAL_ZWJ | F_PER_SYLLABLE, 1);
90        planner.ot_map.add_gsub_pause(None);
91    }
92
93    planner.ot_map.add_gsub_pause(Some(syllabic_clear_var)); // Don't need syllables anymore.
94
95    for feature in MYANMAR_FEATURES.iter().skip(4) {
96        planner.ot_map.enable_feature(*feature, F_MANUAL_ZWJ, 1);
97    }
98}
99
100fn setup_syllables(_: &hb_ot_shape_plan_t, _: &hb_font_t, buffer: &mut hb_buffer_t) -> bool {
101    super::ot_shaper_myanmar_machine::find_syllables_myanmar(buffer);
102
103    let mut start = 0;
104    let mut end = buffer.next_syllable(0);
105    while start < buffer.len {
106        buffer.unsafe_to_break(Some(start), Some(end));
107        start = end;
108        end = buffer.next_syllable(start);
109    }
110
111    false
112}
113
114fn reorder_myanmar(_: &hb_ot_shape_plan_t, face: &hb_font_t, buffer: &mut hb_buffer_t) -> bool {
115    use super::ot_shaper_myanmar_machine::SyllableType;
116
117    let mut ret = false;
118
119    if super::ot_shaper_syllabic::insert_dotted_circles(
120        face,
121        buffer,
122        SyllableType::BrokenCluster as u8,
123        ot_category_t::OT_DOTTEDCIRCLE,
124        None,
125        None,
126    ) {
127        ret = true;
128    }
129
130    let mut start = 0;
131    let mut end = buffer.next_syllable(0);
132    while start < buffer.len {
133        reorder_syllable_myanmar(start, end, buffer);
134        start = end;
135        end = buffer.next_syllable(start);
136    }
137
138    ret
139}
140
141fn reorder_syllable_myanmar(start: usize, end: usize, buffer: &mut hb_buffer_t) {
142    use super::ot_shaper_myanmar_machine::SyllableType;
143
144    let syllable_type = match buffer.info[start].syllable() & 0x0F {
145        0 => SyllableType::ConsonantSyllable,
146        1 => SyllableType::PunctuationCluster,
147        2 => SyllableType::BrokenCluster,
148        3 => SyllableType::NonMyanmarCluster,
149        _ => unreachable!(),
150    };
151
152    match syllable_type {
153        // We already inserted dotted-circles, so just call the consonant_syllable.
154        SyllableType::ConsonantSyllable | SyllableType::BrokenCluster => {
155            initial_reordering_consonant_syllable(start, end, buffer);
156        }
157        SyllableType::PunctuationCluster | SyllableType::NonMyanmarCluster => {}
158    }
159}
160
161// Rules from:
162// https://docs.microsoft.com/en-us/typography/script-development/myanmar
163fn initial_reordering_consonant_syllable(start: usize, end: usize, buffer: &mut hb_buffer_t) {
164    let mut base = end;
165    let mut has_reph = false;
166
167    {
168        let mut limit = start;
169        if start + 3 <= end
170            && buffer.info[start + 0].myanmar_category() == ot_category_t::OT_Ra
171            && buffer.info[start + 1].myanmar_category() == ot_category_t::OT_As
172            && buffer.info[start + 2].myanmar_category() == ot_category_t::OT_H
173        {
174            limit += 3;
175            base = start;
176            has_reph = true;
177        }
178
179        {
180            if !has_reph {
181                base = limit;
182            }
183
184            for i in limit..end {
185                if buffer.info[i].is_consonant() {
186                    base = i;
187                    break;
188                }
189            }
190        }
191    }
192
193    // Reorder!
194    {
195        let mut i = start;
196        while i < start + if has_reph { 3 } else { 0 } {
197            buffer.info[i].set_myanmar_position(ot_position_t::POS_AFTER_MAIN);
198            i += 1;
199        }
200
201        while i < base {
202            buffer.info[i].set_myanmar_position(ot_position_t::POS_PRE_C);
203            i += 1;
204        }
205
206        if i < end {
207            buffer.info[i].set_myanmar_position(ot_position_t::POS_BASE_C);
208            i += 1;
209        }
210
211        let mut pos = ot_position_t::POS_AFTER_MAIN;
212        // The following loop may be ugly, but it implements all of
213        // Myanmar reordering!
214        for i in i..end {
215            // Pre-base reordering
216            if buffer.info[i].myanmar_category() == ot_category_t::OT_MR {
217                buffer.info[i].set_myanmar_position(ot_position_t::POS_PRE_C);
218                continue;
219            }
220
221            // Left matra
222            if buffer.info[i].myanmar_category() == OT_VPre {
223                buffer.info[i].set_myanmar_position(ot_position_t::POS_PRE_M);
224                continue;
225            }
226
227            if buffer.info[i].myanmar_category() == ot_category_t::OT_VS {
228                let t = buffer.info[i - 1].myanmar_position();
229                buffer.info[i].set_myanmar_position(t);
230                continue;
231            }
232
233            if pos == ot_position_t::POS_AFTER_MAIN
234                && buffer.info[i].myanmar_category() == ot_category_t::OT_VBlw
235            {
236                pos = ot_position_t::POS_BELOW_C;
237                buffer.info[i].set_myanmar_position(pos);
238                continue;
239            }
240
241            if pos == ot_position_t::POS_BELOW_C
242                && buffer.info[i].myanmar_category() == ot_category_t::OT_A
243            {
244                buffer.info[i].set_myanmar_position(ot_position_t::POS_BEFORE_SUB);
245                continue;
246            }
247
248            if pos == ot_position_t::POS_BELOW_C
249                && buffer.info[i].myanmar_category() == ot_category_t::OT_VBlw
250            {
251                buffer.info[i].set_myanmar_position(pos);
252                continue;
253            }
254
255            if pos == ot_position_t::POS_BELOW_C
256                && buffer.info[i].myanmar_category() != ot_category_t::OT_A
257            {
258                pos = ot_position_t::POS_AFTER_SUB;
259                buffer.info[i].set_myanmar_position(pos);
260                continue;
261            }
262
263            buffer.info[i].set_myanmar_position(pos);
264        }
265    }
266
267    buffer.sort(start, end, |a, b| {
268        a.myanmar_position().cmp(&b.myanmar_position()) == core::cmp::Ordering::Greater
269    });
270
271    // Flip left-mantra sequence
272    let mut first_left_matra = end;
273    let mut last_left_matra = end;
274
275    for i in start..end {
276        if buffer.info[i].myanmar_position() == ot_position_t::POS_PRE_M {
277            if first_left_matra == end {
278                first_left_matra = i;
279            }
280
281            last_left_matra = i;
282        }
283    }
284
285    // https://github.com/harfbuzz/harfbuzz/issues/3863
286    if first_left_matra < last_left_matra {
287        // No need to merge clusters, done already?
288        buffer.reverse_range(first_left_matra, last_left_matra + 1);
289        // Reverse back VS, etc.
290        let mut i = first_left_matra;
291
292        for j in i..=last_left_matra {
293            if buffer.info[j].myanmar_category() == ot_category_t::OT_VPre {
294                buffer.reverse_range(i, j + 1);
295                i = j + 1;
296            }
297        }
298    }
299}
300
301fn setup_masks(_: &hb_ot_shape_plan_t, _: &hb_font_t, buffer: &mut hb_buffer_t) {
302    // No masks, we just save information about characters.
303    for info in buffer.info_slice_mut() {
304        info.set_myanmar_properties();
305    }
306}