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