1use ttf_parser::{apple_layout, morx, FromData, GlyphId, LazyArray32};
2
3use super::aat_layout::*;
4use super::aat_map::{hb_aat_map_builder_t, hb_aat_map_t};
5use super::buffer::hb_buffer_t;
6use super::ot_shape_plan::hb_ot_shape_plan_t;
7use super::{hb_font_t, hb_glyph_info_t};
8
9pub fn compile_flags(face: &hb_font_t, builder: &hb_aat_map_builder_t) -> Option<hb_aat_map_t> {
11 let mut map = hb_aat_map_t::default();
12
13 for chain in face.tables().morx.as_ref()?.chains {
14 let mut flags = chain.default_flags;
15 for feature in chain.features {
16 if builder.has_feature(feature.kind, feature.setting) {
20 flags &= feature.disable_flags;
21 flags |= feature.enable_flags;
22 } else if feature.kind == HB_AAT_LAYOUT_FEATURE_TYPE_LETTER_CASE as u16
23 && feature.setting == u16::from(HB_AAT_LAYOUT_FEATURE_SELECTOR_SMALL_CAPS)
24 {
25 let ok = builder.has_feature(
27 HB_AAT_LAYOUT_FEATURE_TYPE_LOWER_CASE as u16,
28 u16::from(HB_AAT_LAYOUT_FEATURE_SELECTOR_LOWER_CASE_SMALL_CAPS),
29 );
30 if ok {
31 flags &= feature.disable_flags;
32 flags |= feature.enable_flags;
33 }
34 }
35 }
36
37 map.chain_flags.push(flags);
38 }
39
40 Some(map)
41}
42
43pub fn apply(plan: &hb_ot_shape_plan_t, face: &hb_font_t, buffer: &mut hb_buffer_t) -> Option<()> {
45 for (chain_idx, chain) in face.tables().morx.as_ref()?.chains.into_iter().enumerate() {
46 let flags = plan.aat_map.chain_flags[chain_idx];
47 for subtable in chain.subtables {
48 if subtable.feature_flags & flags == 0 {
49 continue;
50 }
51
52 if !subtable.coverage.is_all_directions()
53 && buffer.direction.is_vertical() != subtable.coverage.is_vertical()
54 {
55 continue;
56 }
57
58 let reverse = if subtable.coverage.is_logical() {
86 subtable.coverage.is_backwards()
87 } else {
88 subtable.coverage.is_backwards() != buffer.direction.is_backward()
89 };
90
91 if reverse {
92 buffer.reverse();
93 }
94
95 apply_subtable(&subtable.kind, buffer, face);
96
97 if reverse {
98 buffer.reverse();
99 }
100 }
101 }
102
103 Some(())
104}
105
106trait driver_context_t<T: FromData> {
107 fn in_place(&self) -> bool;
108 fn can_advance(&self, entry: &apple_layout::GenericStateEntry<T>) -> bool;
109 fn is_actionable(
110 &self,
111 entry: &apple_layout::GenericStateEntry<T>,
112 buffer: &hb_buffer_t,
113 ) -> bool;
114 fn transition(
115 &mut self,
116 entry: &apple_layout::GenericStateEntry<T>,
117 buffer: &mut hb_buffer_t,
118 ) -> Option<()>;
119}
120
121const START_OF_TEXT: u16 = 0;
122
123fn drive<T: FromData>(
124 machine: &apple_layout::ExtendedStateTable<T>,
125 c: &mut dyn driver_context_t<T>,
126 buffer: &mut hb_buffer_t,
127) {
128 if !c.in_place() {
129 buffer.clear_output();
130 }
131
132 let mut state = START_OF_TEXT;
133 buffer.idx = 0;
134 loop {
135 let class = if buffer.idx < buffer.len {
136 machine
137 .class(buffer.info[buffer.idx].as_glyph())
138 .unwrap_or(1)
139 } else {
140 u16::from(apple_layout::class::END_OF_TEXT)
141 };
142
143 let entry: apple_layout::GenericStateEntry<T> = match machine.entry(state, class) {
144 Some(v) => v,
145 None => break,
146 };
147
148 let next_state = entry.new_state;
149
150 let is_safe_to_break_extra = || {
179 let wouldbe_entry = match machine.entry(START_OF_TEXT, class) {
181 Some(v) => v,
182 None => return false,
183 };
184
185 if c.is_actionable(&wouldbe_entry, &buffer) {
187 return false;
188 }
189
190 return next_state == wouldbe_entry.new_state
192 && c.can_advance(&entry) == c.can_advance(&wouldbe_entry);
193 };
194
195 let is_safe_to_break = || {
196 if c.is_actionable(&entry, &buffer) {
198 return false;
199 }
200
201 let ok = state == START_OF_TEXT
203 || (!c.can_advance(&entry) && next_state == START_OF_TEXT)
204 || is_safe_to_break_extra();
205 if !ok {
206 return false;
207 }
208
209 let end_entry = match machine.entry(state, u16::from(apple_layout::class::END_OF_TEXT))
211 {
212 Some(v) => v,
213 None => return false,
214 };
215 return !c.is_actionable(&end_entry, &buffer);
216 };
217
218 if !is_safe_to_break() && buffer.backtrack_len() > 0 && buffer.idx < buffer.len {
219 buffer.unsafe_to_break_from_outbuffer(
220 Some(buffer.backtrack_len() - 1),
221 Some(buffer.idx + 1),
222 );
223 }
224
225 c.transition(&entry, buffer);
226
227 state = next_state;
228
229 if buffer.idx >= buffer.len || !buffer.successful {
230 break;
231 }
232
233 if c.can_advance(&entry) {
234 buffer.next_glyph();
235 } else {
236 if buffer.max_ops <= 0 {
237 buffer.next_glyph();
238 }
239 buffer.max_ops -= 1;
240 }
241 }
242
243 if !c.in_place() {
244 buffer.sync();
245 }
246}
247
248fn apply_subtable(kind: &morx::SubtableKind, buffer: &mut hb_buffer_t, face: &hb_font_t) {
249 match kind {
250 morx::SubtableKind::Rearrangement(ref table) => {
251 let mut c = RearrangementCtx { start: 0, end: 0 };
252
253 drive::<()>(table, &mut c, buffer);
254 }
255 morx::SubtableKind::Contextual(ref table) => {
256 let mut c = ContextualCtx {
257 mark_set: false,
258 face_if_has_glyph_classes:
259 matches!(face.tables().gdef, Some(gdef) if gdef.has_glyph_classes())
260 .then_some(face),
261 mark: 0,
262 table,
263 };
264
265 drive::<morx::ContextualEntryData>(&table.state, &mut c, buffer);
266 }
267 morx::SubtableKind::Ligature(ref table) => {
268 let mut c = LigatureCtx {
269 table,
270 match_length: 0,
271 match_positions: [0; LIGATURE_MAX_MATCHES],
272 };
273
274 drive::<u16>(&table.state, &mut c, buffer);
275 }
276 morx::SubtableKind::NonContextual(ref lookup) => {
277 let face_if_has_glyph_classes =
278 matches!(face.tables().gdef, Some(gdef) if gdef.has_glyph_classes())
279 .then_some(face);
280 for info in &mut buffer.info {
281 if let Some(replacement) = lookup.value(info.as_glyph()) {
282 info.glyph_id = u32::from(replacement);
283 if let Some(face) = face_if_has_glyph_classes {
284 info.set_glyph_props(face.glyph_props(GlyphId(replacement)));
285 }
286 }
287 }
288 }
289 morx::SubtableKind::Insertion(ref table) => {
290 let mut c = InsertionCtx {
291 mark: 0,
292 glyphs: table.glyphs,
293 };
294
295 drive::<morx::InsertionEntryData>(&table.state, &mut c, buffer);
296 }
297 }
298}
299
300struct RearrangementCtx {
301 start: usize,
302 end: usize,
303}
304
305impl RearrangementCtx {
306 const MARK_FIRST: u16 = 0x8000;
307 const DONT_ADVANCE: u16 = 0x4000;
308 const MARK_LAST: u16 = 0x2000;
309 const VERB: u16 = 0x000F;
310}
311
312impl driver_context_t<()> for RearrangementCtx {
313 fn in_place(&self) -> bool {
314 true
315 }
316
317 fn can_advance(&self, entry: &apple_layout::GenericStateEntry<()>) -> bool {
318 entry.flags & Self::DONT_ADVANCE == 0
319 }
320
321 fn is_actionable(&self, entry: &apple_layout::GenericStateEntry<()>, _: &hb_buffer_t) -> bool {
322 entry.flags & Self::VERB != 0 && self.start < self.end
323 }
324
325 fn transition(
326 &mut self,
327 entry: &apple_layout::GenericStateEntry<()>,
328 buffer: &mut hb_buffer_t,
329 ) -> Option<()> {
330 let flags = entry.flags;
331
332 if flags & Self::MARK_FIRST != 0 {
333 self.start = buffer.idx;
334 }
335
336 if flags & Self::MARK_LAST != 0 {
337 self.end = (buffer.idx + 1).min(buffer.len);
338 }
339
340 if flags & Self::VERB != 0 && self.start < self.end {
341 const MAP: [u8; 16] = [
346 0x00, 0x10, 0x01, 0x11, 0x20, 0x30, 0x02, 0x03, 0x12, 0x13, 0x21, 0x31, 0x22, 0x32, 0x23, 0x33, ];
363
364 let m = MAP[usize::from(flags & Self::VERB)];
365 let l = 2.min(m >> 4) as usize;
366 let r = 2.min(m & 0x0F) as usize;
367 let reverse_l = 3 == (m >> 4);
368 let reverse_r = 3 == (m & 0x0F);
369
370 if self.end - self.start >= l + r {
371 buffer.merge_clusters(self.start, (buffer.idx + 1).min(buffer.len));
372 buffer.merge_clusters(self.start, self.end);
373
374 let mut buf = [hb_glyph_info_t::default(); 4];
375
376 for (i, glyph_info) in buf[..l].iter_mut().enumerate() {
377 *glyph_info = buffer.info[self.start + i];
378 }
379
380 for i in 0..r {
381 buf[i + 2] = buffer.info[self.end - r + i];
382 }
383
384 if l > r {
385 for i in 0..(self.end - self.start - l - r) {
386 buffer.info[self.start + r + i] = buffer.info[self.start + l + i];
387 }
388 } else if l < r {
389 for i in (0..(self.end - self.start - l - r)).rev() {
390 buffer.info[self.start + r + i] = buffer.info[self.start + l + i];
391 }
392 }
393
394 for i in 0..r {
395 buffer.info[self.start + i] = buf[2 + i];
396 }
397
398 for i in 0..l {
399 buffer.info[self.end - l + i] = buf[i];
400 }
401
402 if reverse_l {
403 buffer.info.swap(self.end - 1, self.end - 2);
404 }
405
406 if reverse_r {
407 buffer.info.swap(self.start, self.start + 1);
408 }
409 }
410 }
411
412 Some(())
413 }
414}
415
416struct ContextualCtx<'a> {
417 mark_set: bool,
418 face_if_has_glyph_classes: Option<&'a hb_font_t<'a>>,
419 mark: usize,
420 table: &'a morx::ContextualSubtable<'a>,
421}
422
423impl ContextualCtx<'_> {
424 const SET_MARK: u16 = 0x8000;
425 const DONT_ADVANCE: u16 = 0x4000;
426}
427
428impl driver_context_t<morx::ContextualEntryData> for ContextualCtx<'_> {
429 fn in_place(&self) -> bool {
430 true
431 }
432
433 fn can_advance(
434 &self,
435 entry: &apple_layout::GenericStateEntry<morx::ContextualEntryData>,
436 ) -> bool {
437 entry.flags & Self::DONT_ADVANCE == 0
438 }
439
440 fn is_actionable(
441 &self,
442 entry: &apple_layout::GenericStateEntry<morx::ContextualEntryData>,
443 buffer: &hb_buffer_t,
444 ) -> bool {
445 if buffer.idx == buffer.len && !self.mark_set {
446 return false;
447 }
448
449 entry.extra.mark_index != 0xFFFF || entry.extra.current_index != 0xFFFF
450 }
451
452 fn transition(
453 &mut self,
454 entry: &apple_layout::GenericStateEntry<morx::ContextualEntryData>,
455 buffer: &mut hb_buffer_t,
456 ) -> Option<()> {
457 if buffer.idx == buffer.len && !self.mark_set {
460 return Some(());
461 }
462
463 let mut replacement = None;
464
465 if entry.extra.mark_index != 0xFFFF {
466 let lookup = self.table.lookup(u32::from(entry.extra.mark_index))?;
467 replacement = lookup.value(buffer.info[self.mark].as_glyph());
468 }
469
470 if let Some(replacement) = replacement {
471 buffer.unsafe_to_break(Some(self.mark), Some((buffer.idx + 1).min(buffer.len)));
472 buffer.info[self.mark].glyph_id = u32::from(replacement);
473
474 if let Some(face) = self.face_if_has_glyph_classes {
475 buffer.info[self.mark].set_glyph_props(face.glyph_props(GlyphId(replacement)));
476 }
477 }
478
479 replacement = None;
480 let idx = buffer.idx.min(buffer.len - 1);
481 if entry.extra.current_index != 0xFFFF {
482 let lookup = self.table.lookup(u32::from(entry.extra.current_index))?;
483 replacement = lookup.value(buffer.info[idx].as_glyph());
484 }
485
486 if let Some(replacement) = replacement {
487 buffer.info[idx].glyph_id = u32::from(replacement);
488
489 if let Some(face) = self.face_if_has_glyph_classes {
490 buffer.info[self.mark].set_glyph_props(face.glyph_props(GlyphId(replacement)));
491 }
492 }
493
494 if entry.flags & Self::SET_MARK != 0 {
495 self.mark_set = true;
496 self.mark = buffer.idx;
497 }
498
499 Some(())
500 }
501}
502
503struct InsertionCtx<'a> {
504 mark: u32,
505 glyphs: LazyArray32<'a, GlyphId>,
506}
507
508impl InsertionCtx<'_> {
509 const SET_MARK: u16 = 0x8000;
510 const DONT_ADVANCE: u16 = 0x4000;
511 const CURRENT_INSERT_BEFORE: u16 = 0x0800;
512 const MARKED_INSERT_BEFORE: u16 = 0x0400;
513 const CURRENT_INSERT_COUNT: u16 = 0x03E0;
514 const MARKED_INSERT_COUNT: u16 = 0x001F;
515}
516
517impl driver_context_t<morx::InsertionEntryData> for InsertionCtx<'_> {
518 fn in_place(&self) -> bool {
519 false
520 }
521
522 fn can_advance(
523 &self,
524 entry: &apple_layout::GenericStateEntry<morx::InsertionEntryData>,
525 ) -> bool {
526 entry.flags & Self::DONT_ADVANCE == 0
527 }
528
529 fn is_actionable(
530 &self,
531 entry: &apple_layout::GenericStateEntry<morx::InsertionEntryData>,
532 _: &hb_buffer_t,
533 ) -> bool {
534 (entry.flags & (Self::CURRENT_INSERT_COUNT | Self::MARKED_INSERT_COUNT) != 0)
535 && (entry.extra.current_insert_index != 0xFFFF
536 || entry.extra.marked_insert_index != 0xFFFF)
537 }
538
539 fn transition(
540 &mut self,
541 entry: &apple_layout::GenericStateEntry<morx::InsertionEntryData>,
542 buffer: &mut hb_buffer_t,
543 ) -> Option<()> {
544 let flags = entry.flags;
545 let mark_loc = buffer.out_len;
546
547 if entry.extra.marked_insert_index != 0xFFFF {
548 let count = flags & Self::MARKED_INSERT_COUNT;
549 buffer.max_ops -= i32::from(count);
550 if buffer.max_ops <= 0 {
551 return Some(());
552 }
553
554 let start = entry.extra.marked_insert_index;
555 let before = flags & Self::MARKED_INSERT_BEFORE != 0;
556
557 let end = buffer.out_len;
558 buffer.move_to(self.mark as usize);
559
560 if buffer.idx < buffer.len && !before {
561 buffer.copy_glyph();
562 }
563
564 for i in 0..count {
566 let i = u32::from(start + i);
567 buffer.output_glyph(u32::from(self.glyphs.get(i)?.0));
568 }
569
570 if buffer.idx < buffer.len && !before {
571 buffer.skip_glyph();
572 }
573
574 buffer.move_to(end + usize::from(count));
575
576 buffer.unsafe_to_break_from_outbuffer(
577 Some(self.mark as usize),
578 Some((buffer.idx + 1).min(buffer.len)),
579 );
580 }
581
582 if flags & Self::SET_MARK != 0 {
583 self.mark = mark_loc as u32;
584 }
585
586 if entry.extra.current_insert_index != 0xFFFF {
587 let count = (flags & Self::CURRENT_INSERT_COUNT) >> 5;
588 buffer.max_ops -= i32::from(count);
589 if buffer.max_ops < 0 {
590 return Some(());
591 }
592
593 let start = entry.extra.current_insert_index;
594 let before = flags & Self::CURRENT_INSERT_BEFORE != 0;
595 let end = buffer.out_len;
596
597 if buffer.idx < buffer.len && !before {
598 buffer.copy_glyph();
599 }
600
601 for i in 0..count {
603 let i = u32::from(start + i);
604 buffer.output_glyph(u32::from(self.glyphs.get(i)?.0));
605 }
606
607 if buffer.idx < buffer.len && !before {
608 buffer.skip_glyph();
609 }
610
611 buffer.move_to(if flags & Self::DONT_ADVANCE != 0 {
626 end
627 } else {
628 end + usize::from(count)
629 });
630 }
631
632 Some(())
633 }
634}
635
636const LIGATURE_MAX_MATCHES: usize = 64;
637
638struct LigatureCtx<'a> {
639 table: &'a morx::LigatureSubtable<'a>,
640 match_length: usize,
641 match_positions: [usize; LIGATURE_MAX_MATCHES],
642}
643
644impl LigatureCtx<'_> {
645 const SET_COMPONENT: u16 = 0x8000;
646 const DONT_ADVANCE: u16 = 0x4000;
647 const PERFORM_ACTION: u16 = 0x2000;
648
649 const LIG_ACTION_LAST: u32 = 0x80000000;
650 const LIG_ACTION_STORE: u32 = 0x40000000;
651 const LIG_ACTION_OFFSET: u32 = 0x3FFFFFFF;
652}
653
654impl driver_context_t<u16> for LigatureCtx<'_> {
655 fn in_place(&self) -> bool {
656 false
657 }
658
659 fn can_advance(&self, entry: &apple_layout::GenericStateEntry<u16>) -> bool {
660 entry.flags & Self::DONT_ADVANCE == 0
661 }
662
663 fn is_actionable(&self, entry: &apple_layout::GenericStateEntry<u16>, _: &hb_buffer_t) -> bool {
664 entry.flags & Self::PERFORM_ACTION != 0
665 }
666
667 fn transition(
668 &mut self,
669 entry: &apple_layout::GenericStateEntry<u16>,
670 buffer: &mut hb_buffer_t,
671 ) -> Option<()> {
672 if entry.flags & Self::SET_COMPONENT != 0 {
673 if self.match_length != 0
675 && self.match_positions[(self.match_length - 1) % LIGATURE_MAX_MATCHES]
676 == buffer.out_len
677 {
678 self.match_length -= 1;
679 }
680
681 self.match_positions[self.match_length % LIGATURE_MAX_MATCHES] = buffer.out_len;
682 self.match_length += 1;
683 }
684
685 if entry.flags & Self::PERFORM_ACTION != 0 {
686 let end = buffer.out_len;
687
688 if self.match_length == 0 {
689 return Some(());
690 }
691
692 if buffer.idx >= buffer.len {
693 return Some(()); }
695
696 let mut cursor = self.match_length;
697
698 let mut ligature_actions_index = entry.extra;
699 let mut ligature_idx = 0;
700 loop {
701 if cursor == 0 {
702 self.match_length = 0;
704 break;
705 }
706
707 cursor -= 1;
708 buffer.move_to(self.match_positions[cursor % LIGATURE_MAX_MATCHES]);
709
710 let action = match self
713 .table
714 .ligature_actions
715 .get(u32::from(ligature_actions_index))
716 {
717 Some(v) => v,
718 None => break,
719 };
720
721 let mut uoffset = action & Self::LIG_ACTION_OFFSET;
722 if uoffset & 0x20000000 != 0 {
723 uoffset |= 0xC0000000; }
725
726 let offset = uoffset as i32;
727 let component_idx = (buffer.cur(0).glyph_id as i32 + offset) as u32;
728 ligature_idx += match self.table.components.get(component_idx) {
729 Some(v) => v,
730 None => break,
731 };
732
733 if (action & (Self::LIG_ACTION_STORE | Self::LIG_ACTION_LAST)) != 0 {
734 let lig = match self.table.ligatures.get(u32::from(ligature_idx)) {
735 Some(v) => v,
736 None => break,
737 };
738
739 buffer.replace_glyph(u32::from(lig.0));
740
741 let lig_end =
742 self.match_positions[(self.match_length - 1) % LIGATURE_MAX_MATCHES] + 1;
743 while self.match_length - 1 > cursor {
745 self.match_length -= 1;
746 buffer.move_to(
747 self.match_positions[self.match_length % LIGATURE_MAX_MATCHES],
748 );
749 buffer.replace_glyph(0xFFFF);
750 }
751
752 buffer.move_to(lig_end);
753 buffer.merge_out_clusters(
754 self.match_positions[cursor % LIGATURE_MAX_MATCHES],
755 buffer.out_len,
756 );
757 }
758
759 ligature_actions_index += 1;
760
761 if action & Self::LIG_ACTION_LAST != 0 {
762 break;
763 }
764 }
765
766 buffer.move_to(end);
767 }
768
769 Some(())
770 }
771}