rustybuzz/hb/
ot_shape_complex_myanmar.rs

1use super::buffer::hb_buffer_t;
2use super::ot_map::*;
3use super::ot_shape::*;
4use super::ot_shape_complex::*;
5use super::ot_shape_complex_indic::{category, position};
6use super::ot_shape_normalize::*;
7use super::ot_shape_plan::hb_ot_shape_plan_t;
8use super::{hb_font_t, hb_glyph_info_t, hb_tag_t};
9
10pub const MYANMAR_SHAPER: hb_ot_complex_shaper_t = hb_ot_complex_shaper_t {
11    collect_features: Some(collect_features),
12    override_features: None,
13    create_data: None,
14    preprocess_text: None,
15    postprocess_glyphs: None,
16    normalization_preference: HB_OT_SHAPE_NORMALIZATION_MODE_COMPOSED_DIACRITICS_NO_SHORT_CIRCUIT,
17    decompose: None,
18    compose: None,
19    setup_masks: Some(setup_masks),
20    gpos_tag: None,
21    reorder_marks: None,
22    zero_width_marks: HB_OT_SHAPE_ZERO_WIDTH_MARKS_BY_GDEF_EARLY,
23    fallback_position: false,
24};
25
26// Ugly Zawgyi encoding.
27// Disable all auto processing.
28// https://github.com/harfbuzz/harfbuzz/issues/1162
29pub const MYANMAR_ZAWGYI_SHAPER: hb_ot_complex_shaper_t = hb_ot_complex_shaper_t {
30    collect_features: None,
31    override_features: None,
32    create_data: None,
33    preprocess_text: None,
34    postprocess_glyphs: None,
35    normalization_preference: HB_OT_SHAPE_NORMALIZATION_MODE_NONE,
36    decompose: None,
37    compose: None,
38    setup_masks: None,
39    gpos_tag: None,
40    reorder_marks: None,
41    zero_width_marks: HB_OT_SHAPE_ZERO_WIDTH_MARKS_NONE,
42    fallback_position: false,
43};
44
45const MYANMAR_FEATURES: &[hb_tag_t] = &[
46    // Basic features.
47    // These features are applied in order, one at a time, after reordering,
48    // constrained to the syllable.
49    hb_tag_t::from_bytes(b"rphf"),
50    hb_tag_t::from_bytes(b"pref"),
51    hb_tag_t::from_bytes(b"blwf"),
52    hb_tag_t::from_bytes(b"pstf"),
53    // Other features.
54    // These features are applied all at once after clearing syllables.
55    hb_tag_t::from_bytes(b"pres"),
56    hb_tag_t::from_bytes(b"abvs"),
57    hb_tag_t::from_bytes(b"blws"),
58    hb_tag_t::from_bytes(b"psts"),
59];
60
61impl hb_glyph_info_t {
62    fn set_myanmar_properties(&mut self) {
63        let u = self.glyph_id;
64        let (mut cat, mut pos) = crate::hb::ot_shape_complex_indic::get_category_and_position(u);
65
66        // Myanmar
67        // https://docs.microsoft.com/en-us/typography/script-development/myanmar#analyze
68
69        if (0xFE00..=0xFE0F).contains(&u) {
70            cat = category::VS;
71        }
72
73        match u {
74            // The spec says C, IndicSyllableCategory doesn't have.
75            0x104E => cat = category::C,
76
77            0x002D | 0x00A0 | 0x00D7 | 0x2012 | 0x2013 | 0x2014 | 0x2015 | 0x2022 | 0x25CC
78            | 0x25FB | 0x25FC | 0x25FD | 0x25FE => cat = category::PLACEHOLDER,
79
80            0x1004 | 0x101B | 0x105A => cat = category::RA,
81
82            0x1032 | 0x1036 => cat = category::A,
83
84            0x1039 => cat = category::H,
85
86            0x103A => cat = category::SYMBOL,
87
88            0x1041 | 0x1042 | 0x1043 | 0x1044 | 0x1045 | 0x1046 | 0x1047 | 0x1048 | 0x1049
89            | 0x1090 | 0x1091 | 0x1092 | 0x1093 | 0x1094 | 0x1095 | 0x1096 | 0x1097 | 0x1098
90            | 0x1099 => cat = category::D,
91
92            // XXX The spec says D0, but Uniscribe doesn't seem to do.
93            0x1040 => cat = category::D,
94
95            0x103E => cat = category::X_GROUP,
96
97            0x1060 => cat = category::ML,
98
99            0x103C => cat = category::Y_GROUP,
100
101            0x103D | 0x1082 => cat = category::MW,
102
103            0x103B | 0x105E | 0x105F => cat = category::MY,
104
105            0x1063 | 0x1064 | 0x1069 | 0x106A | 0x106B | 0x106C | 0x106D | 0xAA7B => {
106                cat = category::PT
107            }
108
109            0x1038 | 0x1087 | 0x1088 | 0x1089 | 0x108A | 0x108B | 0x108C | 0x108D | 0x108F
110            | 0x109A | 0x109B | 0x109C => cat = category::SM,
111
112            0x104A | 0x104B => cat = category::P,
113
114            // https://github.com/harfbuzz/harfbuzz/issues/218
115            0xAA74 | 0xAA75 | 0xAA76 => cat = category::C,
116
117            _ => {}
118        }
119
120        // Re-assign position.
121
122        if cat == category::M {
123            match pos {
124                position::PRE_C => {
125                    cat = category::V_PRE;
126                    pos = position::PRE_M;
127                }
128                position::BELOW_C => cat = category::V_BLW,
129                position::ABOVE_C => cat = category::V_AVB,
130                position::POST_C => cat = category::V_PST,
131                _ => {}
132            }
133        }
134
135        self.set_indic_category(cat);
136        self.set_indic_position(pos);
137    }
138}
139
140fn collect_features(planner: &mut hb_ot_shape_planner_t) {
141    // Do this before any lookups have been applied.
142    planner.ot_map.add_gsub_pause(Some(setup_syllables));
143
144    planner
145        .ot_map
146        .enable_feature(hb_tag_t::from_bytes(b"locl"), F_PER_SYLLABLE, 1);
147    // The Indic specs do not require ccmp, but we apply it here since if
148    // there is a use of it, it's typically at the beginning.
149    planner
150        .ot_map
151        .enable_feature(hb_tag_t::from_bytes(b"ccmp"), F_PER_SYLLABLE, 1);
152
153    planner.ot_map.add_gsub_pause(Some(reorder));
154
155    for feature in MYANMAR_FEATURES.iter().take(4) {
156        planner.ot_map.enable_feature(*feature, F_MANUAL_ZWJ, 1);
157        planner.ot_map.add_gsub_pause(None);
158    }
159
160    for feature in MYANMAR_FEATURES.iter().skip(4) {
161        planner
162            .ot_map
163            .enable_feature(*feature, F_MANUAL_ZWJ | F_PER_SYLLABLE, 1);
164    }
165}
166
167fn setup_syllables(_: &hb_ot_shape_plan_t, _: &hb_font_t, buffer: &mut hb_buffer_t) {
168    super::ot_shape_complex_myanmar_machine::find_syllables_myanmar(buffer);
169
170    let mut start = 0;
171    let mut end = buffer.next_syllable(0);
172    while start < buffer.len {
173        buffer.unsafe_to_break(Some(start), Some(end));
174        start = end;
175        end = buffer.next_syllable(start);
176    }
177}
178
179fn reorder(_: &hb_ot_shape_plan_t, face: &hb_font_t, buffer: &mut hb_buffer_t) {
180    use super::ot_shape_complex_myanmar_machine::SyllableType;
181
182    super::ot_shape_complex_syllabic::insert_dotted_circles(
183        face,
184        buffer,
185        SyllableType::BrokenCluster as u8,
186        category::PLACEHOLDER,
187        None,
188        None,
189    );
190
191    let mut start = 0;
192    let mut end = buffer.next_syllable(0);
193    while start < buffer.len {
194        reorder_syllable(start, end, buffer);
195        start = end;
196        end = buffer.next_syllable(start);
197    }
198}
199
200fn reorder_syllable(start: usize, end: usize, buffer: &mut hb_buffer_t) {
201    use super::ot_shape_complex_myanmar_machine::SyllableType;
202
203    let syllable_type = match buffer.info[start].syllable() & 0x0F {
204        0 => SyllableType::ConsonantSyllable,
205        1 => SyllableType::PunctuationCluster,
206        2 => SyllableType::BrokenCluster,
207        3 => SyllableType::NonMyanmarCluster,
208        _ => unreachable!(),
209    };
210
211    match syllable_type {
212        // We already inserted dotted-circles, so just call the consonant_syllable.
213        SyllableType::ConsonantSyllable | SyllableType::BrokenCluster => {
214            initial_reordering_consonant_syllable(start, end, buffer);
215        }
216        SyllableType::PunctuationCluster | SyllableType::NonMyanmarCluster => {}
217    }
218}
219
220// Rules from:
221// https://docs.microsoft.com/en-us/typography/script-development/myanmar
222fn initial_reordering_consonant_syllable(start: usize, end: usize, buffer: &mut hb_buffer_t) {
223    let mut base = end;
224    let mut has_reph = false;
225
226    {
227        let mut limit = start;
228        if start + 3 <= end
229            && buffer.info[start + 0].indic_category() == category::RA
230            && buffer.info[start + 1].indic_category() == category::SYMBOL
231            && buffer.info[start + 2].indic_category() == category::H
232        {
233            limit += 3;
234            base = start;
235            has_reph = true;
236        }
237
238        {
239            if !has_reph {
240                base = limit;
241            }
242
243            for i in limit..end {
244                if buffer.info[i].is_consonant() {
245                    base = i;
246                    break;
247                }
248            }
249        }
250    }
251
252    // Reorder!
253    {
254        let mut i = start;
255        while i < start + if has_reph { 3 } else { 0 } {
256            buffer.info[i].set_indic_position(position::AFTER_MAIN);
257            i += 1;
258        }
259
260        while i < base {
261            buffer.info[i].set_indic_position(position::PRE_C);
262            i += 1;
263        }
264
265        if i < end {
266            buffer.info[i].set_indic_position(position::BASE_C);
267            i += 1;
268        }
269
270        let mut pos = position::AFTER_MAIN;
271        // The following loop may be ugly, but it implements all of
272        // Myanmar reordering!
273        for i in i..end {
274            // Pre-base reordering
275            if buffer.info[i].indic_category() == category::Y_GROUP {
276                buffer.info[i].set_indic_position(position::PRE_C);
277                continue;
278            }
279
280            // Left matra
281            if buffer.info[i].indic_position() < position::BASE_C {
282                continue;
283            }
284
285            if buffer.info[i].indic_category() == category::VS {
286                let t = buffer.info[i - 1].indic_position();
287                buffer.info[i].set_indic_position(t);
288                continue;
289            }
290
291            if pos == position::AFTER_MAIN && buffer.info[i].indic_category() == category::V_BLW {
292                pos = position::BELOW_C;
293                buffer.info[i].set_indic_position(pos);
294                continue;
295            }
296
297            if pos == position::BELOW_C && buffer.info[i].indic_category() == category::A {
298                buffer.info[i].set_indic_position(position::BEFORE_SUB);
299                continue;
300            }
301
302            if pos == position::BELOW_C && buffer.info[i].indic_category() == category::V_BLW {
303                buffer.info[i].set_indic_position(pos);
304                continue;
305            }
306
307            if pos == position::BELOW_C && buffer.info[i].indic_category() != category::A {
308                pos = position::AFTER_SUB;
309                buffer.info[i].set_indic_position(pos);
310                continue;
311            }
312
313            buffer.info[i].set_indic_position(pos);
314        }
315    }
316
317    buffer.sort(start, end, |a, b| {
318        a.indic_position().cmp(&b.indic_position()) == core::cmp::Ordering::Greater
319    });
320}
321
322fn setup_masks(_: &hb_ot_shape_plan_t, _: &hb_font_t, buffer: &mut hb_buffer_t) {
323    // No masks, we just save information about characters.
324    for info in buffer.info_slice_mut() {
325        info.set_myanmar_properties();
326    }
327}