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 => cat = category::X_GROUP,
94
95 0x1060 => cat = category::ML,
96
97 0x103C => cat = category::Y_GROUP,
98
99 0x103D | 0x1082 => cat = category::MW,
100
101 0x103B | 0x105E | 0x105F => cat = category::MY,
102
103 0x1063 | 0x1064 | 0x1069 | 0x106A | 0x106B | 0x106C | 0x106D | 0xAA7B => {
104 cat = category::PT
105 }
106
107 0x1038 | 0x1087 | 0x1088 | 0x1089 | 0x108A | 0x108B | 0x108C | 0x108D | 0x108F
108 | 0x109A | 0x109B | 0x109C => cat = category::SM,
109
110 0x104A | 0x104B => cat = category::P,
111
112 // https://github.com/harfbuzz/harfbuzz/issues/218
113 0xAA74 | 0xAA75 | 0xAA76 => cat = category::C,
114
115 _ => {}
116 }
117
118 // Re-assign position.
119
120 if cat == category::M {
121 match pos {
122 position::PRE_C => {
123 cat = category::V_PRE;
124 pos = position::PRE_M;
125 }
126 position::BELOW_C => cat = category::V_BLW,
127 position::ABOVE_C => cat = category::V_AVB,
128 position::POST_C => cat = category::V_PST,
129 _ => {}
130 }
131 }
132
133 self.set_indic_category(cat);
134 self.set_indic_position(pos);
135 }
136}
137
138fn collect_features(planner: &mut ShapePlanner) {
139 // Do this before any lookups have been applied.
140 planner.ot_map.add_gsub_pause(Some(setup_syllables));
141
142 planner
143 .ot_map
144 .enable_feature(feature::LOCALIZED_FORMS, FeatureFlags::empty(), 1);
145 // The Indic specs do not require ccmp, but we apply it here since if
146 // there is a use of it, it's typically at the beginning.
147 planner.ot_map.enable_feature(
148 feature::GLYPH_COMPOSITION_DECOMPOSITION,
149 FeatureFlags::empty(),
150 1,
151 );
152
153 planner.ot_map.add_gsub_pause(Some(reorder));
154
155 for feature in MYANMAR_FEATURES.iter().take(4) {
156 planner
157 .ot_map
158 .enable_feature(*feature, FeatureFlags::MANUAL_ZWJ, 1);
159 planner.ot_map.add_gsub_pause(None);
160 }
161
162 planner
163 .ot_map
164 .add_gsub_pause(Some(crate::ot::clear_syllables));
165
166 for feature in MYANMAR_FEATURES.iter().skip(4) {
167 planner
168 .ot_map
169 .enable_feature(*feature, FeatureFlags::MANUAL_ZWJ, 1);
170 }
171}
172
173fn setup_syllables(_: &ShapePlan, _: &Face, buffer: &mut Buffer) {
174 super::myanmar_machine::find_syllables_myanmar(buffer);
175
176 let mut start: usize = 0;
177 let mut end: usize = buffer.next_syllable(start:0);
178 while start < buffer.len {
179 buffer.unsafe_to_break(start:Some(start), end:Some(end));
180 start = end;
181 end = buffer.next_syllable(start);
182 }
183}
184
185fn reorder(_: &ShapePlan, face: &Face, buffer: &mut Buffer) {
186 use super::myanmar_machine::SyllableType;
187
188 syllabic::insert_dotted_circles(
189 face,
190 buffer,
191 broken_syllable_type:SyllableType::BrokenCluster as u8,
192 dottedcircle_category:category::PLACEHOLDER,
193 repha_category:None,
194 dottedcircle_position:None,
195 );
196
197 let mut start: usize = 0;
198 let mut end: usize = buffer.next_syllable(start:0);
199 while start < buffer.len {
200 reorder_syllable(start, end, buffer);
201 start = end;
202 end = buffer.next_syllable(start);
203 }
204}
205
206fn reorder_syllable(start: usize, end: usize, buffer: &mut Buffer) {
207 use super::myanmar_machine::SyllableType;
208
209 let syllable_type: SyllableType = match buffer.info[start].syllable() & 0x0F {
210 0 => SyllableType::ConsonantSyllable,
211 1 => SyllableType::PunctuationCluster,
212 2 => SyllableType::BrokenCluster,
213 3 => SyllableType::NonMyanmarCluster,
214 _ => unreachable!(),
215 };
216
217 match syllable_type {
218 // We already inserted dotted-circles, so just call the consonant_syllable.
219 SyllableType::ConsonantSyllable | SyllableType::BrokenCluster => {
220 initial_reordering_consonant_syllable(start, end, buffer);
221 }
222 SyllableType::PunctuationCluster | SyllableType::NonMyanmarCluster => {}
223 }
224}
225
226// Rules from:
227// https://docs.microsoft.com/en-us/typography/script-development/myanmar
228fn initial_reordering_consonant_syllable(start: usize, end: usize, buffer: &mut Buffer) {
229 let mut base = end;
230 let mut has_reph = false;
231
232 {
233 let mut limit = start;
234 if start + 3 <= end
235 && buffer.info[start + 0].indic_category() == category::RA
236 && buffer.info[start + 1].indic_category() == category::SYMBOL
237 && buffer.info[start + 2].indic_category() == category::H
238 {
239 limit += 3;
240 base = start;
241 has_reph = true;
242 }
243
244 {
245 if !has_reph {
246 base = limit;
247 }
248
249 for i in limit..end {
250 if buffer.info[i].is_consonant() {
251 base = i;
252 break;
253 }
254 }
255 }
256 }
257
258 // Reorder!
259 {
260 let mut i = start;
261 while i < start + if has_reph { 3 } else { 0 } {
262 buffer.info[i].set_indic_position(position::AFTER_MAIN);
263 i += 1;
264 }
265
266 while i < base {
267 buffer.info[i].set_indic_position(position::PRE_C);
268 i += 1;
269 }
270
271 if i < end {
272 buffer.info[i].set_indic_position(position::BASE_C);
273 i += 1;
274 }
275
276 let mut pos = position::AFTER_MAIN;
277 // The following loop may be ugly, but it implements all of
278 // Myanmar reordering!
279 for i in i..end {
280 // Pre-base reordering
281 if buffer.info[i].indic_category() == category::Y_GROUP {
282 buffer.info[i].set_indic_position(position::PRE_C);
283 continue;
284 }
285
286 // Left matra
287 if buffer.info[i].indic_position() < position::BASE_C {
288 continue;
289 }
290
291 if buffer.info[i].indic_category() == category::VS {
292 let t = buffer.info[i - 1].indic_position();
293 buffer.info[i].set_indic_position(t);
294 continue;
295 }
296
297 if pos == position::AFTER_MAIN && buffer.info[i].indic_category() == category::V_BLW {
298 pos = position::BELOW_C;
299 buffer.info[i].set_indic_position(pos);
300 continue;
301 }
302
303 if pos == position::BELOW_C && buffer.info[i].indic_category() == category::A {
304 buffer.info[i].set_indic_position(position::BEFORE_SUB);
305 continue;
306 }
307
308 if pos == position::BELOW_C && buffer.info[i].indic_category() == category::V_BLW {
309 buffer.info[i].set_indic_position(pos);
310 continue;
311 }
312
313 if pos == position::BELOW_C && buffer.info[i].indic_category() != category::A {
314 pos = position::AFTER_SUB;
315 buffer.info[i].set_indic_position(pos);
316 continue;
317 }
318
319 buffer.info[i].set_indic_position(pos);
320 }
321 }
322
323 buffer.sort(start, end, |a, b| {
324 a.indic_position().cmp(&b.indic_position()) == core::cmp::Ordering::Greater
325 });
326}
327
328fn setup_masks(_: &ShapePlan, _: &Face, buffer: &mut Buffer) {
329 // We cannot setup masks here. We save information about characters
330 // and setup masks later on in a pause-callback.
331 for info: &mut GlyphInfo in buffer.info_slice_mut() {
332 info.set_myanmar_properties();
333 }
334}
335