1 | use ttf_parser::{apple_layout, morx, FromData, GlyphId, LazyArray32}; |
2 | |
3 | use crate::aat::feature_selector; |
4 | use crate::aat::{FeatureType, Map, MapBuilder}; |
5 | use crate::buffer::Buffer; |
6 | use crate::plan::ShapePlan; |
7 | use crate::{Face, GlyphInfo}; |
8 | |
9 | pub fn compile_flags(face: &Face, builder: &MapBuilder) -> Option<Map> { |
10 | let mut map = Map::default(); |
11 | |
12 | for chain in face.tables().morx.as_ref()?.chains { |
13 | let mut flags = chain.default_flags; |
14 | for feature in chain.features { |
15 | // Check whether this type/setting pair was requested in the map, |
16 | // and if so, apply its flags. |
17 | |
18 | if builder.has_feature(feature.kind, feature.setting) { |
19 | flags &= feature.disable_flags; |
20 | flags |= feature.enable_flags; |
21 | } else if feature.kind == FeatureType::LetterCase as u16 |
22 | && feature.setting == u16::from(feature_selector::SMALL_CAPS) |
23 | { |
24 | // Deprecated. https://github.com/harfbuzz/harfbuzz/issues/1342 |
25 | let ok = builder.has_feature( |
26 | FeatureType::LowerCase as u16, |
27 | u16::from(feature_selector::LOWER_CASE_SMALL_CAPS), |
28 | ); |
29 | if ok { |
30 | flags &= feature.disable_flags; |
31 | flags |= feature.enable_flags; |
32 | } |
33 | } |
34 | } |
35 | |
36 | map.chain_flags.push(flags); |
37 | } |
38 | |
39 | Some(map) |
40 | } |
41 | |
42 | pub fn apply(plan: &ShapePlan, face: &Face, buffer: &mut Buffer) -> Option<()> { |
43 | for (chain_idx, chain) in face.tables().morx.as_ref()?.chains.into_iter().enumerate() { |
44 | let flags = plan.aat_map.chain_flags[chain_idx]; |
45 | for subtable in chain.subtables { |
46 | if subtable.feature_flags & flags == 0 { |
47 | continue; |
48 | } |
49 | |
50 | if !subtable.coverage.is_all_directions() |
51 | && buffer.direction.is_vertical() != subtable.coverage.is_vertical() |
52 | { |
53 | continue; |
54 | } |
55 | |
56 | // Buffer contents is always in logical direction. Determine if |
57 | // we need to reverse before applying this subtable. We reverse |
58 | // back after if we did reverse indeed. |
59 | // |
60 | // Quoting the spec: |
61 | // """ |
62 | // Bits 28 and 30 of the coverage field control the order in which |
63 | // glyphs are processed when the subtable is run by the layout engine. |
64 | // Bit 28 is used to indicate if the glyph processing direction is |
65 | // the same as logical order or layout order. Bit 30 is used to |
66 | // indicate whether glyphs are processed forwards or backwards within |
67 | // that order. |
68 | // |
69 | // Bit 30 Bit 28 Interpretation for Horizontal Text |
70 | // 0 0 The subtable is processed in layout order |
71 | // (the same order as the glyphs, which is |
72 | // always left-to-right). |
73 | // 1 0 The subtable is processed in reverse layout order |
74 | // (the order opposite that of the glyphs, which is |
75 | // always right-to-left). |
76 | // 0 1 The subtable is processed in logical order |
77 | // (the same order as the characters, which may be |
78 | // left-to-right or right-to-left). |
79 | // 1 1 The subtable is processed in reverse logical order |
80 | // (the order opposite that of the characters, which |
81 | // may be right-to-left or left-to-right). |
82 | |
83 | let reverse = if subtable.coverage.is_logical() { |
84 | subtable.coverage.is_backwards() |
85 | } else { |
86 | subtable.coverage.is_backwards() != buffer.direction.is_backward() |
87 | }; |
88 | |
89 | if reverse { |
90 | buffer.reverse(); |
91 | } |
92 | |
93 | apply_subtable(&subtable.kind, buffer, face); |
94 | |
95 | if reverse { |
96 | buffer.reverse(); |
97 | } |
98 | } |
99 | } |
100 | |
101 | Some(()) |
102 | } |
103 | |
104 | trait Driver<T: FromData> { |
105 | fn in_place(&self) -> bool; |
106 | fn can_advance(&self, entry: &apple_layout::GenericStateEntry<T>) -> bool; |
107 | fn is_actionable(&self, entry: &apple_layout::GenericStateEntry<T>, buffer: &Buffer) -> bool; |
108 | fn transition( |
109 | &mut self, |
110 | entry: &apple_layout::GenericStateEntry<T>, |
111 | buffer: &mut Buffer, |
112 | ) -> Option<()>; |
113 | } |
114 | |
115 | const START_OF_TEXT: u16 = 0; |
116 | |
117 | fn drive<T: FromData>( |
118 | machine: &apple_layout::ExtendedStateTable<T>, |
119 | c: &mut dyn Driver<T>, |
120 | buffer: &mut Buffer, |
121 | ) { |
122 | if !c.in_place() { |
123 | buffer.clear_output(); |
124 | } |
125 | |
126 | let mut state = START_OF_TEXT; |
127 | buffer.idx = 0; |
128 | loop { |
129 | let class = if buffer.idx < buffer.len { |
130 | machine |
131 | .class(buffer.info[buffer.idx].as_glyph()) |
132 | .unwrap_or(1) |
133 | } else { |
134 | u16::from(apple_layout::class::END_OF_TEXT) |
135 | }; |
136 | |
137 | let entry: apple_layout::GenericStateEntry<T> = match machine.entry(state, class) { |
138 | Some(v) => v, |
139 | None => break, |
140 | }; |
141 | |
142 | let next_state = entry.new_state; |
143 | |
144 | // Conditions under which it's guaranteed safe-to-break before current glyph: |
145 | // |
146 | // 1. There was no action in this transition; and |
147 | // |
148 | // 2. If we break before current glyph, the results will be the same. That |
149 | // is guaranteed if: |
150 | // |
151 | // 2a. We were already in start-of-text state; or |
152 | // |
153 | // 2b. We are epsilon-transitioning to start-of-text state; or |
154 | // |
155 | // 2c. Starting from start-of-text state seeing current glyph: |
156 | // |
157 | // 2c'. There won't be any actions; and |
158 | // |
159 | // 2c". We would end up in the same state that we were going to end up |
160 | // in now, including whether epsilon-transitioning. |
161 | // |
162 | // and |
163 | // |
164 | // 3. If we break before current glyph, there won't be any end-of-text action |
165 | // after previous glyph. |
166 | // |
167 | // This triples the transitions we need to look up, but is worth returning |
168 | // granular unsafe-to-break results. See eg.: |
169 | // |
170 | // https://github.com/harfbuzz/harfbuzz/issues/2860 |
171 | |
172 | let is_safe_to_break_extra = || { |
173 | // 2c |
174 | let wouldbe_entry = match machine.entry(START_OF_TEXT, class) { |
175 | Some(v) => v, |
176 | None => return false, |
177 | }; |
178 | |
179 | // 2c' |
180 | if c.is_actionable(&wouldbe_entry, &buffer) { |
181 | return false; |
182 | } |
183 | |
184 | // 2c" |
185 | return next_state == wouldbe_entry.new_state |
186 | && c.can_advance(&entry) == c.can_advance(&wouldbe_entry); |
187 | }; |
188 | |
189 | let is_safe_to_break = || { |
190 | // 1 |
191 | if c.is_actionable(&entry, &buffer) { |
192 | return false; |
193 | } |
194 | |
195 | // 2 |
196 | let ok = state == START_OF_TEXT |
197 | || (!c.can_advance(&entry) && next_state == START_OF_TEXT) |
198 | || is_safe_to_break_extra(); |
199 | if !ok { |
200 | return false; |
201 | } |
202 | |
203 | // 3 |
204 | let end_entry = match machine.entry(state, u16::from(apple_layout::class::END_OF_TEXT)) |
205 | { |
206 | Some(v) => v, |
207 | None => return false, |
208 | }; |
209 | return !c.is_actionable(&end_entry, &buffer); |
210 | }; |
211 | |
212 | if !is_safe_to_break() && buffer.backtrack_len() > 0 && buffer.idx < buffer.len { |
213 | buffer.unsafe_to_break_from_outbuffer(buffer.backtrack_len() - 1, buffer.idx + 1); |
214 | } |
215 | |
216 | c.transition(&entry, buffer); |
217 | |
218 | state = next_state; |
219 | |
220 | if buffer.idx >= buffer.len || !buffer.successful { |
221 | break; |
222 | } |
223 | |
224 | if c.can_advance(&entry) { |
225 | buffer.next_glyph(); |
226 | } else { |
227 | if buffer.max_ops <= 0 { |
228 | buffer.next_glyph(); |
229 | } |
230 | buffer.max_ops -= 1; |
231 | } |
232 | } |
233 | |
234 | if !c.in_place() { |
235 | buffer.swap_buffers(); |
236 | } |
237 | } |
238 | |
239 | fn apply_subtable(kind: &morx::SubtableKind, buffer: &mut Buffer, face: &Face) { |
240 | match kind { |
241 | morx::SubtableKind::Rearrangement(ref table) => { |
242 | let mut c = RearrangementCtx { start: 0, end: 0 }; |
243 | |
244 | drive::<()>(table, &mut c, buffer); |
245 | } |
246 | morx::SubtableKind::Contextual(ref table) => { |
247 | let mut c = ContextualCtx { |
248 | mark_set: false, |
249 | face_if_has_glyph_classes: |
250 | matches!(face.tables().gdef, Some(gdef) if gdef.has_glyph_classes()) |
251 | .then_some(face), |
252 | mark: 0, |
253 | table, |
254 | }; |
255 | |
256 | drive::<morx::ContextualEntryData>(&table.state, &mut c, buffer); |
257 | } |
258 | morx::SubtableKind::Ligature(ref table) => { |
259 | let mut c = LigatureCtx { |
260 | table, |
261 | match_length: 0, |
262 | match_positions: [0; LIGATURE_MAX_MATCHES], |
263 | }; |
264 | |
265 | drive::<u16>(&table.state, &mut c, buffer); |
266 | } |
267 | morx::SubtableKind::NonContextual(ref lookup) => { |
268 | let face_if_has_glyph_classes = |
269 | matches!(face.tables().gdef, Some(gdef) if gdef.has_glyph_classes()) |
270 | .then_some(face); |
271 | for info in &mut buffer.info { |
272 | if let Some(replacement) = lookup.value(info.as_glyph()) { |
273 | info.glyph_id = u32::from(replacement); |
274 | if let Some(face) = face_if_has_glyph_classes { |
275 | info.set_glyph_props(face.glyph_props(GlyphId(replacement))); |
276 | } |
277 | } |
278 | } |
279 | } |
280 | morx::SubtableKind::Insertion(ref table) => { |
281 | let mut c = InsertionCtx { |
282 | mark: 0, |
283 | glyphs: table.glyphs, |
284 | }; |
285 | |
286 | drive::<morx::InsertionEntryData>(&table.state, &mut c, buffer); |
287 | } |
288 | } |
289 | } |
290 | |
291 | struct RearrangementCtx { |
292 | start: usize, |
293 | end: usize, |
294 | } |
295 | |
296 | impl RearrangementCtx { |
297 | const MARK_FIRST: u16 = 0x8000; |
298 | const DONT_ADVANCE: u16 = 0x4000; |
299 | const MARK_LAST: u16 = 0x2000; |
300 | const VERB: u16 = 0x000F; |
301 | } |
302 | |
303 | impl Driver<()> for RearrangementCtx { |
304 | fn in_place(&self) -> bool { |
305 | true |
306 | } |
307 | |
308 | fn can_advance(&self, entry: &apple_layout::GenericStateEntry<()>) -> bool { |
309 | entry.flags & Self::DONT_ADVANCE == 0 |
310 | } |
311 | |
312 | fn is_actionable(&self, entry: &apple_layout::GenericStateEntry<()>, _: &Buffer) -> bool { |
313 | entry.flags & Self::VERB != 0 && self.start < self.end |
314 | } |
315 | |
316 | fn transition( |
317 | &mut self, |
318 | entry: &apple_layout::GenericStateEntry<()>, |
319 | buffer: &mut Buffer, |
320 | ) -> Option<()> { |
321 | let flags = entry.flags; |
322 | |
323 | if flags & Self::MARK_FIRST != 0 { |
324 | self.start = buffer.idx; |
325 | } |
326 | |
327 | if flags & Self::MARK_LAST != 0 { |
328 | self.end = (buffer.idx + 1).min(buffer.len); |
329 | } |
330 | |
331 | if flags & Self::VERB != 0 && self.start < self.end { |
332 | // The following map has two nibbles, for start-side |
333 | // and end-side. Values of 0,1,2 mean move that many |
334 | // to the other side. Value of 3 means move 2 and |
335 | // flip them. |
336 | const MAP: [u8; 16] = [ |
337 | 0x00, // 0 no change |
338 | 0x10, // 1 Ax => xA |
339 | 0x01, // 2 xD => Dx |
340 | 0x11, // 3 AxD => DxA |
341 | 0x20, // 4 ABx => xAB |
342 | 0x30, // 5 ABx => xBA |
343 | 0x02, // 6 xCD => CDx |
344 | 0x03, // 7 xCD => DCx |
345 | 0x12, // 8 AxCD => CDxA |
346 | 0x13, // 9 AxCD => DCxA |
347 | 0x21, // 10 ABxD => DxAB |
348 | 0x31, // 11 ABxD => DxBA |
349 | 0x22, // 12 ABxCD => CDxAB |
350 | 0x32, // 13 ABxCD => CDxBA |
351 | 0x23, // 14 ABxCD => DCxAB |
352 | 0x33, // 15 ABxCD => DCxBA |
353 | ]; |
354 | |
355 | let m = MAP[usize::from(flags & Self::VERB)]; |
356 | let l = 2.min(m >> 4) as usize; |
357 | let r = 2.min(m & 0x0F) as usize; |
358 | let reverse_l = 3 == (m >> 4); |
359 | let reverse_r = 3 == (m & 0x0F); |
360 | |
361 | if self.end - self.start >= l + r { |
362 | buffer.merge_clusters(self.start, (buffer.idx + 1).min(buffer.len)); |
363 | buffer.merge_clusters(self.start, self.end); |
364 | |
365 | let mut buf = [GlyphInfo::default(); 4]; |
366 | |
367 | for (i, glyph_info) in buf[..l].iter_mut().enumerate() { |
368 | *glyph_info = buffer.info[self.start + i]; |
369 | } |
370 | |
371 | for i in 0..r { |
372 | buf[i + 2] = buffer.info[self.end - r + i]; |
373 | } |
374 | |
375 | if l > r { |
376 | for i in 0..(self.end - self.start - l - r) { |
377 | buffer.info[self.start + r + i] = buffer.info[self.start + l + i]; |
378 | } |
379 | } else if l < r { |
380 | for i in (0..(self.end - self.start - l - r)).rev() { |
381 | buffer.info[self.start + r + i] = buffer.info[self.start + l + i]; |
382 | } |
383 | } |
384 | |
385 | for i in 0..r { |
386 | buffer.info[self.start + i] = buf[2 + i]; |
387 | } |
388 | |
389 | for i in 0..l { |
390 | buffer.info[self.end - l + i] = buf[i]; |
391 | } |
392 | |
393 | if reverse_l { |
394 | buffer.info.swap(self.end - 1, self.end - 2); |
395 | } |
396 | |
397 | if reverse_r { |
398 | buffer.info.swap(self.start, self.start + 1); |
399 | } |
400 | } |
401 | } |
402 | |
403 | Some(()) |
404 | } |
405 | } |
406 | |
407 | struct ContextualCtx<'a> { |
408 | mark_set: bool, |
409 | face_if_has_glyph_classes: Option<&'a Face<'a>>, |
410 | mark: usize, |
411 | table: &'a morx::ContextualSubtable<'a>, |
412 | } |
413 | |
414 | impl ContextualCtx<'_> { |
415 | const SET_MARK: u16 = 0x8000; |
416 | const DONT_ADVANCE: u16 = 0x4000; |
417 | } |
418 | |
419 | impl Driver<morx::ContextualEntryData> for ContextualCtx<'_> { |
420 | fn in_place(&self) -> bool { |
421 | true |
422 | } |
423 | |
424 | fn can_advance( |
425 | &self, |
426 | entry: &apple_layout::GenericStateEntry<morx::ContextualEntryData>, |
427 | ) -> bool { |
428 | entry.flags & Self::DONT_ADVANCE == 0 |
429 | } |
430 | |
431 | fn is_actionable( |
432 | &self, |
433 | entry: &apple_layout::GenericStateEntry<morx::ContextualEntryData>, |
434 | buffer: &Buffer, |
435 | ) -> bool { |
436 | if buffer.idx == buffer.len && !self.mark_set { |
437 | return false; |
438 | } |
439 | |
440 | entry.extra.mark_index != 0xFFFF || entry.extra.current_index != 0xFFFF |
441 | } |
442 | |
443 | fn transition( |
444 | &mut self, |
445 | entry: &apple_layout::GenericStateEntry<morx::ContextualEntryData>, |
446 | buffer: &mut Buffer, |
447 | ) -> Option<()> { |
448 | // Looks like CoreText applies neither mark nor current substitution for |
449 | // end-of-text if mark was not explicitly set. |
450 | if buffer.idx == buffer.len && !self.mark_set { |
451 | return Some(()); |
452 | } |
453 | |
454 | let mut replacement = None; |
455 | |
456 | if entry.extra.mark_index != 0xFFFF { |
457 | let lookup = self.table.lookup(u32::from(entry.extra.mark_index))?; |
458 | replacement = lookup.value(buffer.info[self.mark].as_glyph()); |
459 | } |
460 | |
461 | if let Some(replacement) = replacement { |
462 | buffer.unsafe_to_break(self.mark, (buffer.idx + 1).min(buffer.len)); |
463 | buffer.info[self.mark].glyph_id = u32::from(replacement); |
464 | |
465 | if let Some(face) = self.face_if_has_glyph_classes { |
466 | buffer.info[self.mark].set_glyph_props(face.glyph_props(GlyphId(replacement))); |
467 | } |
468 | } |
469 | |
470 | replacement = None; |
471 | let idx = buffer.idx.min(buffer.len - 1); |
472 | if entry.extra.current_index != 0xFFFF { |
473 | let lookup = self.table.lookup(u32::from(entry.extra.current_index))?; |
474 | replacement = lookup.value(buffer.info[idx].as_glyph()); |
475 | } |
476 | |
477 | if let Some(replacement) = replacement { |
478 | buffer.info[idx].glyph_id = u32::from(replacement); |
479 | |
480 | if let Some(face) = self.face_if_has_glyph_classes { |
481 | buffer.info[self.mark].set_glyph_props(face.glyph_props(GlyphId(replacement))); |
482 | } |
483 | } |
484 | |
485 | if entry.flags & Self::SET_MARK != 0 { |
486 | self.mark_set = true; |
487 | self.mark = buffer.idx; |
488 | } |
489 | |
490 | Some(()) |
491 | } |
492 | } |
493 | |
494 | struct InsertionCtx<'a> { |
495 | mark: u32, |
496 | glyphs: LazyArray32<'a, GlyphId>, |
497 | } |
498 | |
499 | impl InsertionCtx<'_> { |
500 | const SET_MARK: u16 = 0x8000; |
501 | const DONT_ADVANCE: u16 = 0x4000; |
502 | const CURRENT_INSERT_BEFORE: u16 = 0x0800; |
503 | const MARKED_INSERT_BEFORE: u16 = 0x0400; |
504 | const CURRENT_INSERT_COUNT: u16 = 0x03E0; |
505 | const MARKED_INSERT_COUNT: u16 = 0x001F; |
506 | } |
507 | |
508 | impl Driver<morx::InsertionEntryData> for InsertionCtx<'_> { |
509 | fn in_place(&self) -> bool { |
510 | false |
511 | } |
512 | |
513 | fn can_advance( |
514 | &self, |
515 | entry: &apple_layout::GenericStateEntry<morx::InsertionEntryData>, |
516 | ) -> bool { |
517 | entry.flags & Self::DONT_ADVANCE == 0 |
518 | } |
519 | |
520 | fn is_actionable( |
521 | &self, |
522 | entry: &apple_layout::GenericStateEntry<morx::InsertionEntryData>, |
523 | _: &Buffer, |
524 | ) -> bool { |
525 | (entry.flags & (Self::CURRENT_INSERT_COUNT | Self::MARKED_INSERT_COUNT) != 0) |
526 | && (entry.extra.current_insert_index != 0xFFFF |
527 | || entry.extra.marked_insert_index != 0xFFFF) |
528 | } |
529 | |
530 | fn transition( |
531 | &mut self, |
532 | entry: &apple_layout::GenericStateEntry<morx::InsertionEntryData>, |
533 | buffer: &mut Buffer, |
534 | ) -> Option<()> { |
535 | let flags = entry.flags; |
536 | let mark_loc = buffer.out_len; |
537 | |
538 | if entry.extra.marked_insert_index != 0xFFFF { |
539 | let count = flags & Self::MARKED_INSERT_COUNT; |
540 | buffer.max_ops -= i32::from(count); |
541 | if buffer.max_ops <= 0 { |
542 | return Some(()); |
543 | } |
544 | |
545 | let start = entry.extra.marked_insert_index; |
546 | let before = flags & Self::MARKED_INSERT_BEFORE != 0; |
547 | |
548 | let end = buffer.out_len; |
549 | buffer.move_to(self.mark as usize); |
550 | |
551 | if buffer.idx < buffer.len && !before { |
552 | buffer.copy_glyph(); |
553 | } |
554 | |
555 | // TODO We ignore KashidaLike setting. |
556 | for i in 0..count { |
557 | let i = u32::from(start + i); |
558 | buffer.output_glyph(u32::from(self.glyphs.get(i)?.0)); |
559 | } |
560 | |
561 | if buffer.idx < buffer.len && !before { |
562 | buffer.skip_glyph(); |
563 | } |
564 | |
565 | buffer.move_to(end + usize::from(count)); |
566 | |
567 | buffer.unsafe_to_break_from_outbuffer( |
568 | self.mark as usize, |
569 | (buffer.idx + 1).min(buffer.len), |
570 | ); |
571 | } |
572 | |
573 | if flags & Self::SET_MARK != 0 { |
574 | self.mark = mark_loc as u32; |
575 | } |
576 | |
577 | if entry.extra.current_insert_index != 0xFFFF { |
578 | let count = (flags & Self::CURRENT_INSERT_COUNT) >> 5; |
579 | buffer.max_ops -= i32::from(count); |
580 | if buffer.max_ops < 0 { |
581 | return Some(()); |
582 | } |
583 | |
584 | let start = entry.extra.current_insert_index; |
585 | let before = flags & Self::CURRENT_INSERT_BEFORE != 0; |
586 | let end = buffer.out_len; |
587 | |
588 | if buffer.idx < buffer.len && !before { |
589 | buffer.copy_glyph(); |
590 | } |
591 | |
592 | // TODO We ignore KashidaLike setting. |
593 | for i in 0..count { |
594 | let i = u32::from(start + i); |
595 | buffer.output_glyph(u32::from(self.glyphs.get(i)?.0)); |
596 | } |
597 | |
598 | if buffer.idx < buffer.len && !before { |
599 | buffer.skip_glyph(); |
600 | } |
601 | |
602 | // Humm. Not sure where to move to. There's this wording under |
603 | // DontAdvance flag: |
604 | // |
605 | // "If set, don't update the glyph index before going to the new state. |
606 | // This does not mean that the glyph pointed to is the same one as |
607 | // before. If you've made insertions immediately downstream of the |
608 | // current glyph, the next glyph processed would in fact be the first |
609 | // one inserted." |
610 | // |
611 | // This suggests that if DontAdvance is NOT set, we should move to |
612 | // end+count. If it *was*, then move to end, such that newly inserted |
613 | // glyphs are now visible. |
614 | // |
615 | // https://github.com/harfbuzz/harfbuzz/issues/1224#issuecomment-427691417 |
616 | buffer.move_to(if flags & Self::DONT_ADVANCE != 0 { |
617 | end |
618 | } else { |
619 | end + usize::from(count) |
620 | }); |
621 | } |
622 | |
623 | Some(()) |
624 | } |
625 | } |
626 | |
627 | const LIGATURE_MAX_MATCHES: usize = 64; |
628 | |
629 | struct LigatureCtx<'a> { |
630 | table: &'a morx::LigatureSubtable<'a>, |
631 | match_length: usize, |
632 | match_positions: [usize; LIGATURE_MAX_MATCHES], |
633 | } |
634 | |
635 | impl LigatureCtx<'_> { |
636 | const SET_COMPONENT: u16 = 0x8000; |
637 | const DONT_ADVANCE: u16 = 0x4000; |
638 | const PERFORM_ACTION: u16 = 0x2000; |
639 | |
640 | const LIG_ACTION_LAST: u32 = 0x80000000; |
641 | const LIG_ACTION_STORE: u32 = 0x40000000; |
642 | const LIG_ACTION_OFFSET: u32 = 0x3FFFFFFF; |
643 | } |
644 | |
645 | impl Driver<u16> for LigatureCtx<'_> { |
646 | fn in_place(&self) -> bool { |
647 | false |
648 | } |
649 | |
650 | fn can_advance(&self, entry: &apple_layout::GenericStateEntry<u16>) -> bool { |
651 | entry.flags & Self::DONT_ADVANCE == 0 |
652 | } |
653 | |
654 | fn is_actionable(&self, entry: &apple_layout::GenericStateEntry<u16>, _: &Buffer) -> bool { |
655 | entry.flags & Self::PERFORM_ACTION != 0 |
656 | } |
657 | |
658 | fn transition( |
659 | &mut self, |
660 | entry: &apple_layout::GenericStateEntry<u16>, |
661 | buffer: &mut Buffer, |
662 | ) -> Option<()> { |
663 | if entry.flags & Self::SET_COMPONENT != 0 { |
664 | // Never mark same index twice, in case DONT_ADVANCE was used... |
665 | if self.match_length != 0 |
666 | && self.match_positions[(self.match_length - 1) % LIGATURE_MAX_MATCHES] |
667 | == buffer.out_len |
668 | { |
669 | self.match_length -= 1; |
670 | } |
671 | |
672 | self.match_positions[self.match_length % LIGATURE_MAX_MATCHES] = buffer.out_len; |
673 | self.match_length += 1; |
674 | } |
675 | |
676 | if entry.flags & Self::PERFORM_ACTION != 0 { |
677 | let end = buffer.out_len; |
678 | |
679 | if self.match_length == 0 { |
680 | return Some(()); |
681 | } |
682 | |
683 | if buffer.idx >= buffer.len { |
684 | return Some(()); // TODO: Work on previous instead? |
685 | } |
686 | |
687 | let mut cursor = self.match_length; |
688 | |
689 | let mut ligature_actions_index = entry.extra; |
690 | let mut ligature_idx = 0; |
691 | loop { |
692 | if cursor == 0 { |
693 | // Stack underflow. Clear the stack. |
694 | self.match_length = 0; |
695 | break; |
696 | } |
697 | |
698 | cursor -= 1; |
699 | buffer.move_to(self.match_positions[cursor % LIGATURE_MAX_MATCHES]); |
700 | |
701 | // We cannot use ? in this loop, because we must call |
702 | // buffer.move_to(end) in the end. |
703 | let action = match self |
704 | .table |
705 | .ligature_actions |
706 | .get(u32::from(ligature_actions_index)) |
707 | { |
708 | Some(v) => v, |
709 | None => break, |
710 | }; |
711 | |
712 | let mut uoffset = action & Self::LIG_ACTION_OFFSET; |
713 | if uoffset & 0x20000000 != 0 { |
714 | uoffset |= 0xC0000000; // Sign-extend. |
715 | } |
716 | |
717 | let offset = uoffset as i32; |
718 | let component_idx = (buffer.cur(0).glyph_id as i32 + offset) as u32; |
719 | ligature_idx += match self.table.components.get(component_idx) { |
720 | Some(v) => v, |
721 | None => break, |
722 | }; |
723 | |
724 | if (action & (Self::LIG_ACTION_STORE | Self::LIG_ACTION_LAST)) != 0 { |
725 | let lig = match self.table.ligatures.get(u32::from(ligature_idx)) { |
726 | Some(v) => v, |
727 | None => break, |
728 | }; |
729 | |
730 | buffer.replace_glyph(u32::from(lig.0)); |
731 | |
732 | let lig_end = |
733 | self.match_positions[(self.match_length - 1) % LIGATURE_MAX_MATCHES] + 1; |
734 | // Now go and delete all subsequent components. |
735 | while self.match_length - 1 > cursor { |
736 | self.match_length -= 1; |
737 | buffer.move_to( |
738 | self.match_positions[self.match_length % LIGATURE_MAX_MATCHES], |
739 | ); |
740 | buffer.replace_glyph(0xFFFF); |
741 | } |
742 | |
743 | buffer.move_to(lig_end); |
744 | buffer.merge_out_clusters( |
745 | self.match_positions[cursor % LIGATURE_MAX_MATCHES], |
746 | buffer.out_len, |
747 | ); |
748 | } |
749 | |
750 | ligature_actions_index += 1; |
751 | |
752 | if action & Self::LIG_ACTION_LAST != 0 { |
753 | break; |
754 | } |
755 | } |
756 | |
757 | buffer.move_to(end); |
758 | } |
759 | |
760 | Some(()) |
761 | } |
762 | } |
763 | |