Skip to main content

harfrust/hb/
ot_shaper_myanmar.rs

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