1 | use super::indic::{category, position}; |
2 | use super::*; |
3 | use crate::buffer::Buffer; |
4 | use crate::ot::{feature, FeatureFlags}; |
5 | use crate::plan::{ShapePlan, ShapePlanner}; |
6 | use crate::{Face, GlyphInfo, Tag}; |
7 | |
8 | pub 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 |
27 | pub 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 | |
43 | const 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 | |
59 | impl 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 | |
138 | fn 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 | |
173 | fn 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 | |
185 | fn 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 | |
206 | fn 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 |
228 | fn 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 | |
328 | fn 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 | |