1use super::style::{GlyphStyle, StyleClass};
4use crate::{charmap::Charmap, collections::SmallVec, FontRef, GlyphId, MetadataProvider};
5use core::ops::Range;
6use raw::{
7 tables::{
8 gsub::{
9 ChainedSequenceContext, Gsub, SequenceContext, SingleSubst, SubstitutionLookupList,
10 SubstitutionSubtables,
11 },
12 layout::{Feature, ScriptTags},
13 varc::CoverageTable,
14 },
15 types::Tag,
16 ReadError, TableProvider,
17};
18
19const MAX_NESTING_DEPTH: usize = 64;
22
23#[derive(Copy, Clone, PartialEq, Eq, Debug)]
32pub(crate) enum ShaperMode {
33 Nominal,
38 #[allow(unused)]
41 BestEffort,
42}
43
44#[derive(Copy, Clone, Default, Debug)]
45pub(crate) struct ShapedGlyph {
46 pub id: GlyphId,
47 pub y_offset: i32,
53}
54
55const SHAPED_CLUSTER_INLINE_SIZE: usize = 16;
58
59pub(crate) type ShapedCluster = SmallVec<ShapedGlyph, SHAPED_CLUSTER_INLINE_SIZE>;
65
66#[derive(Copy, Clone, PartialEq, Eq, Debug)]
67pub(crate) enum ShaperCoverageKind {
68 Script,
70 Default,
74}
75
76pub(crate) struct Shaper<'a> {
81 font: FontRef<'a>,
82 #[allow(unused)]
83 mode: ShaperMode,
84 charmap: Charmap<'a>,
85 gsub: Option<Gsub<'a>>,
86}
87
88impl<'a> Shaper<'a> {
89 pub fn new(font: &FontRef<'a>, mode: ShaperMode) -> Self {
90 let charmap = font.charmap();
91 let gsub = (mode != ShaperMode::Nominal)
92 .then(|| font.gsub().ok())
93 .flatten();
94 Self {
95 font: font.clone(),
96 mode,
97 charmap,
98 gsub,
99 }
100 }
101
102 pub fn font(&self) -> &FontRef<'a> {
103 &self.font
104 }
105
106 pub fn charmap(&self) -> &Charmap<'a> {
107 &self.charmap
108 }
109
110 pub fn lookup_count(&self) -> u16 {
111 self.gsub
112 .as_ref()
113 .and_then(|gsub| gsub.lookup_list().ok())
114 .map(|list| list.lookup_count())
115 .unwrap_or_default()
116 }
117
118 pub fn cluster_shaper(&'a self, style: &StyleClass) -> ClusterShaper<'a> {
119 if self.mode == ShaperMode::BestEffort {
120 if let Some(feature_tag) = style.feature {
123 if let Some((lookup_list, feature)) = self.gsub.as_ref().and_then(|gsub| {
124 let script_list = gsub.script_list().ok()?;
125 let selected_script =
126 script_list.select(&ScriptTags::from_unicode(style.script.tag))?;
127 let script = script_list.get(selected_script.index).ok()?;
128 let lang_sys = script.default_lang_sys()?.ok()?;
129 let feature_list = gsub.feature_list().ok()?;
130 let feature_ix = lang_sys.feature_index_for_tag(&feature_list, feature_tag)?;
131 let feature = feature_list.get(feature_ix).ok()?.element;
132 let lookup_list = gsub.lookup_list().ok()?;
133 Some((lookup_list, feature))
134 }) {
135 return ClusterShaper {
136 shaper: self,
137 lookup_list: Some(lookup_list),
138 kind: ClusterShaperKind::SingleFeature(feature),
139 };
140 }
141 }
142 }
143 ClusterShaper {
144 shaper: self,
145 lookup_list: None,
146 kind: ClusterShaperKind::Nominal,
147 }
148 }
149
150 pub(crate) fn compute_coverage(
156 &self,
157 style: &StyleClass,
158 coverage_kind: ShaperCoverageKind,
159 glyph_styles: &mut [GlyphStyle],
160 visited_set: &mut VisitedLookupSet<'_>,
161 ) -> bool {
162 let Some(gsub) = self.gsub.as_ref() else {
163 return false;
164 };
165 let (Ok(script_list), Ok(feature_list), Ok(lookup_list)) =
166 (gsub.script_list(), gsub.feature_list(), gsub.lookup_list())
167 else {
168 return false;
169 };
170 let mut script_tags: [Option<Tag>; 3] = [None; 3];
171 for (a, b) in script_tags
172 .iter_mut()
173 .zip(ScriptTags::from_unicode(style.script.tag).iter())
174 {
175 *a = Some(*b);
176 }
177 const DEFAULT_SCRIPT: Tag = Tag::new(b"Dflt");
179 if coverage_kind == ShaperCoverageKind::Default {
180 if script_tags[0].is_none() {
181 script_tags[0] = Some(DEFAULT_SCRIPT);
182 } else if script_tags[1].is_none() {
183 script_tags[1] = Some(DEFAULT_SCRIPT);
184 } else if script_tags[1] != Some(DEFAULT_SCRIPT) {
185 script_tags[2] = Some(DEFAULT_SCRIPT);
186 }
187 } else {
188 const NON_STANDARD_TAGS: &[Option<Tag>] = &[
192 Some(Tag::new(b"Khms")),
194 Some(Tag::new(b"Latb")),
196 Some(Tag::new(b"Latp")),
198 ];
199 if NON_STANDARD_TAGS.contains(&script_tags[0]) {
200 return false;
201 }
202 }
203 let mut gsub_handler = GsubHandler::new(
205 &self.charmap,
206 &lookup_list,
207 style,
208 glyph_styles,
209 visited_set,
210 );
211 for script in script_tags.iter().filter_map(|tag| {
212 tag.and_then(|tag| script_list.index_for_tag(tag))
213 .and_then(|ix| script_list.script_records().get(ix as usize))
214 .and_then(|rec| rec.script(script_list.offset_data()).ok())
215 }) {
216 for langsys in script
218 .lang_sys_records()
219 .iter()
220 .filter_map(|rec| rec.lang_sys(script.offset_data()).ok())
221 .chain(script.default_lang_sys().transpose().ok().flatten())
222 {
223 for feature_ix in langsys.feature_indices() {
224 let Some(feature) = feature_list
225 .feature_records()
226 .get(feature_ix.get() as usize)
227 .and_then(|rec| {
228 if style.feature == Some(rec.feature_tag()) || style.feature.is_none() {
231 rec.feature(feature_list.offset_data()).ok()
232 } else {
233 None
234 }
235 })
236 else {
237 continue;
238 };
239 for index in feature.lookup_list_indices().iter() {
241 let _ = gsub_handler.process_lookup(index.get());
243 }
244 }
245 }
246 }
247 if let Some(range) = gsub_handler.finish() {
248 let mut result = false;
251 for glyph_style in &mut glyph_styles[range] {
252 result |= glyph_style.maybe_assign_gsub_output_style(style);
255 }
256 result
257 } else {
258 false
259 }
260 }
261}
262
263pub(crate) struct ClusterShaper<'a> {
264 shaper: &'a Shaper<'a>,
265 lookup_list: Option<SubstitutionLookupList<'a>>,
266 kind: ClusterShaperKind<'a>,
267}
268
269impl ClusterShaper<'_> {
270 pub(crate) fn shape(&mut self, input: &str, output: &mut ShapedCluster) {
271 output.clear();
274 for ch in input.chars() {
275 output.push(ShapedGlyph {
276 id: self.shaper.charmap.map(ch).unwrap_or_default(),
277 y_offset: 0,
278 });
279 }
280 match self.kind.clone() {
281 ClusterShaperKind::Nominal => {
282 if self.shaper.mode == ShaperMode::Nominal && output.len() > 1 {
285 output.clear();
286 }
287 }
288 ClusterShaperKind::SingleFeature(feature) => {
289 let mut did_subst = false;
290 for lookup_ix in feature.lookup_list_indices() {
291 let mut glyph_ix = 0;
292 while glyph_ix < output.len() {
293 did_subst |= self.apply_lookup(lookup_ix.get(), output, glyph_ix, 0);
294 glyph_ix += 1;
295 }
296 }
297 if !did_subst {
302 output.clear();
303 }
304 }
305 }
306 }
307
308 fn apply_lookup(
309 &self,
310 lookup_index: u16,
311 cluster: &mut ShapedCluster,
312 glyph_ix: usize,
313 nesting_depth: usize,
314 ) -> bool {
315 if nesting_depth > MAX_NESTING_DEPTH {
316 return false;
317 }
318 let Some(glyph) = cluster.get_mut(glyph_ix) else {
319 return false;
320 };
321 let Some(subtables) = self
322 .lookup_list
323 .as_ref()
324 .and_then(|list| list.lookups().get(lookup_index as usize).ok())
325 .and_then(|lookup| lookup.subtables().ok())
326 else {
327 return false;
328 };
329 match subtables {
330 SubstitutionSubtables::Single(tables) => {
335 for table in tables.iter().filter_map(|table| table.ok()) {
336 match table {
337 SingleSubst::Format1(table) => {
338 let Some(_) = table.coverage().ok().and_then(|cov| cov.get(glyph.id))
339 else {
340 continue;
341 };
342 let delta = table.delta_glyph_id() as i32;
343 glyph.id = GlyphId::from((glyph.id.to_u32() as i32 + delta) as u16);
344 return true;
345 }
346 SingleSubst::Format2(table) => {
347 let Some(cov_ix) =
348 table.coverage().ok().and_then(|cov| cov.get(glyph.id))
349 else {
350 continue;
351 };
352 let Some(subst) = table.substitute_glyph_ids().get(cov_ix as usize)
353 else {
354 continue;
355 };
356 glyph.id = subst.get().into();
357 return true;
358 }
359 }
360 }
361 }
362 SubstitutionSubtables::Multiple(_tables) => {}
363 SubstitutionSubtables::Ligature(_tables) => {}
364 SubstitutionSubtables::Alternate(_tables) => {}
365 SubstitutionSubtables::Contextual(_tables) => {}
366 SubstitutionSubtables::ChainContextual(_tables) => {}
367 SubstitutionSubtables::Reverse(_tables) => {}
368 }
369 false
370 }
371}
372
373#[derive(Clone)]
374enum ClusterShaperKind<'a> {
375 Nominal,
376 SingleFeature(Feature<'a>),
377}
378
379struct GsubHandler<'a, 'b> {
398 charmap: &'a Charmap<'a>,
399 lookup_list: &'a SubstitutionLookupList<'a>,
400 style: &'a StyleClass,
401 glyph_styles: &'a mut [GlyphStyle],
402 need_blue_substs: bool,
405 min_gid: usize,
407 max_gid: usize,
408 lookup_depth: usize,
409 visited_set: &'a mut VisitedLookupSet<'b>,
410}
411
412impl<'a, 'b> GsubHandler<'a, 'b> {
413 fn new(
414 charmap: &'a Charmap<'a>,
415 lookup_list: &'a SubstitutionLookupList,
416 style: &'a StyleClass,
417 glyph_styles: &'a mut [GlyphStyle],
418 visited_set: &'a mut VisitedLookupSet<'b>,
419 ) -> Self {
420 let min_gid = glyph_styles.len();
421 let need_blue_substs = style.feature.is_some();
425 Self {
426 charmap,
427 lookup_list,
428 style,
429 glyph_styles,
430 need_blue_substs,
431 min_gid,
432 max_gid: 0,
433 lookup_depth: 0,
434 visited_set,
435 }
436 }
437
438 fn process_lookup(&mut self, lookup_index: u16) -> Result<(), ProcessLookupError> {
439 if self.lookup_depth == MAX_NESTING_DEPTH {
441 return Err(ProcessLookupError::ExceededMaxDepth);
442 }
443 if !self.visited_set.insert(lookup_index) {
445 return Ok(());
446 }
447 self.lookup_depth += 1;
448 let result = self.process_lookup_inner(lookup_index);
450 self.lookup_depth -= 1;
452 result
453 }
454
455 #[inline(always)]
456 fn process_lookup_inner(&mut self, lookup_index: u16) -> Result<(), ProcessLookupError> {
457 let Ok(subtables) = self
458 .lookup_list
459 .lookups()
460 .get(lookup_index as usize)
461 .and_then(|lookup| lookup.subtables())
462 else {
463 return Ok(());
464 };
465 match subtables {
466 SubstitutionSubtables::Single(tables) => {
467 for table in tables.iter().filter_map(|table| table.ok()) {
468 match table {
469 SingleSubst::Format1(table) => {
470 let Ok(coverage) = table.coverage() else {
471 continue;
472 };
473 let delta = table.delta_glyph_id() as i32;
474 for gid in coverage.iter() {
475 self.capture_glyph((gid.to_u32() as i32 + delta) as u16 as u32);
476 }
477 if self.need_blue_substs && self.lookup_depth == 1 {
481 self.check_blue_coverage(Ok(coverage));
482 }
483 }
484 SingleSubst::Format2(table) => {
485 for gid in table.substitute_glyph_ids() {
486 self.capture_glyph(gid.get().to_u32());
487 }
488 if self.need_blue_substs && self.lookup_depth == 1 {
490 self.check_blue_coverage(table.coverage());
491 }
492 }
493 }
494 }
495 }
496 SubstitutionSubtables::Multiple(tables) => {
497 for table in tables.iter().filter_map(|table| table.ok()) {
498 for seq in table.sequences().iter().filter_map(|seq| seq.ok()) {
499 for gid in seq.substitute_glyph_ids() {
500 self.capture_glyph(gid.get().to_u32());
501 }
502 }
503 if self.need_blue_substs && self.lookup_depth == 1 {
505 self.check_blue_coverage(table.coverage());
506 }
507 }
508 }
509 SubstitutionSubtables::Ligature(tables) => {
510 for table in tables.iter().filter_map(|table| table.ok()) {
511 for set in table.ligature_sets().iter().filter_map(|set| set.ok()) {
512 for lig in set.ligatures().iter().filter_map(|lig| lig.ok()) {
513 self.capture_glyph(lig.ligature_glyph().to_u32());
514 }
515 }
516 }
517 }
518 SubstitutionSubtables::Alternate(tables) => {
519 for table in tables.iter().filter_map(|table| table.ok()) {
520 for set in table.alternate_sets().iter().filter_map(|set| set.ok()) {
521 for gid in set.alternate_glyph_ids() {
522 self.capture_glyph(gid.get().to_u32());
523 }
524 }
525 }
526 }
527 SubstitutionSubtables::Contextual(tables) => {
528 for table in tables.iter().filter_map(|table| table.ok()) {
529 match table {
530 SequenceContext::Format1(table) => {
531 for set in table
532 .seq_rule_sets()
533 .iter()
534 .filter_map(|set| set.transpose().ok().flatten())
535 {
536 for rule in set.seq_rules().iter().filter_map(|rule| rule.ok()) {
537 for rec in rule.seq_lookup_records() {
538 self.process_lookup(rec.lookup_list_index())?;
539 }
540 }
541 }
542 }
543 SequenceContext::Format2(table) => {
544 for set in table
545 .class_seq_rule_sets()
546 .iter()
547 .filter_map(|set| set.transpose().ok().flatten())
548 {
549 for rule in
550 set.class_seq_rules().iter().filter_map(|rule| rule.ok())
551 {
552 for rec in rule.seq_lookup_records() {
553 self.process_lookup(rec.lookup_list_index())?;
554 }
555 }
556 }
557 }
558 SequenceContext::Format3(table) => {
559 for rec in table.seq_lookup_records() {
560 self.process_lookup(rec.lookup_list_index())?;
561 }
562 }
563 }
564 }
565 }
566 SubstitutionSubtables::ChainContextual(tables) => {
567 for table in tables.iter().filter_map(|table| table.ok()) {
568 match table {
569 ChainedSequenceContext::Format1(table) => {
570 for set in table
571 .chained_seq_rule_sets()
572 .iter()
573 .filter_map(|set| set.transpose().ok().flatten())
574 {
575 for rule in
576 set.chained_seq_rules().iter().filter_map(|rule| rule.ok())
577 {
578 for rec in rule.seq_lookup_records() {
579 self.process_lookup(rec.lookup_list_index())?;
580 }
581 }
582 }
583 }
584 ChainedSequenceContext::Format2(table) => {
585 for set in table
586 .chained_class_seq_rule_sets()
587 .iter()
588 .filter_map(|set| set.transpose().ok().flatten())
589 {
590 for rule in set
591 .chained_class_seq_rules()
592 .iter()
593 .filter_map(|rule| rule.ok())
594 {
595 for rec in rule.seq_lookup_records() {
596 self.process_lookup(rec.lookup_list_index())?;
597 }
598 }
599 }
600 }
601 ChainedSequenceContext::Format3(table) => {
602 for rec in table.seq_lookup_records() {
603 self.process_lookup(rec.lookup_list_index())?;
604 }
605 }
606 }
607 }
608 }
609 SubstitutionSubtables::Reverse(tables) => {
610 for table in tables.iter().filter_map(|table| table.ok()) {
611 for gid in table.substitute_glyph_ids() {
612 self.capture_glyph(gid.get().to_u32());
613 }
614 }
615 }
616 }
617 Ok(())
618 }
619
620 fn finish(self) -> Option<Range<usize>> {
623 self.visited_set.clear();
624 if self.min_gid > self.max_gid {
625 return None;
627 }
628 let range = self.min_gid..self.max_gid + 1;
629 if self.need_blue_substs {
630 for glyph in &mut self.glyph_styles[range] {
634 glyph.clear_from_gsub();
635 }
636 None
637 } else {
638 Some(range)
639 }
640 }
641
642 fn check_blue_coverage(&mut self, coverage: Result<CoverageTable<'a>, ReadError>) {
645 let Ok(coverage) = coverage else {
646 return;
647 };
648 for (blue_str, _) in self.style.script.blues {
649 if blue_str
650 .chars()
651 .filter_map(|ch| self.charmap.map(ch))
652 .filter_map(|gid| coverage.get(gid))
653 .next()
654 .is_some()
655 {
656 self.need_blue_substs = false;
658 return;
659 }
660 }
661 }
662
663 fn capture_glyph(&mut self, gid: u32) {
664 let gid = gid as usize;
665 if let Some(style) = self.glyph_styles.get_mut(gid) {
666 style.set_from_gsub_output();
667 self.min_gid = gid.min(self.min_gid);
668 self.max_gid = gid.max(self.max_gid);
669 }
670 }
671}
672
673pub(crate) struct VisitedLookupSet<'a>(&'a mut [u8]);
674
675impl<'a> VisitedLookupSet<'a> {
676 pub fn new(storage: &'a mut [u8]) -> Self {
677 Self(storage)
678 }
679
680 fn insert(&mut self, lookup_index: u16) -> bool {
685 let byte_ix = lookup_index as usize / 8;
686 let bit_mask = 1 << (lookup_index % 8) as u8;
687 if let Some(byte) = self.0.get_mut(byte_ix) {
688 if *byte & bit_mask == 0 {
689 *byte |= bit_mask;
690 true
691 } else {
692 false
693 }
694 } else {
695 false
696 }
697 }
698
699 fn clear(&mut self) {
700 self.0.fill(0);
701 }
702}
703
704#[derive(PartialEq, Debug)]
705enum ProcessLookupError {
706 ExceededMaxDepth,
707}
708
709#[cfg(test)]
710mod tests {
711 use super::{super::style, *};
712 use font_test_data::bebuffer::BeBuffer;
713 use raw::{FontData, FontRead};
714
715 #[test]
716 fn small_caps_subst() {
717 let font = FontRef::new(font_test_data::NOTOSERIF_AUTOHINT_SHAPING).unwrap();
718 let shaper = Shaper::new(&font, ShaperMode::BestEffort);
719 let style = &style::STYLE_CLASSES[style::StyleClass::LATN_C2SC];
720 let mut cluster_shaper = shaper.cluster_shaper(style);
721 let mut cluster = ShapedCluster::new();
722 cluster_shaper.shape("H", &mut cluster);
723 assert_eq!(cluster.len(), 1);
724 assert_eq!(cluster[0].id, GlyphId::new(8));
726 }
727
728 #[test]
729 fn small_caps_nominal() {
730 let font = FontRef::new(font_test_data::NOTOSERIF_AUTOHINT_SHAPING).unwrap();
731 let shaper = Shaper::new(&font, ShaperMode::Nominal);
732 let style = &style::STYLE_CLASSES[style::StyleClass::LATN_C2SC];
733 let mut cluster_shaper = shaper.cluster_shaper(style);
734 let mut cluster = ShapedCluster::new();
735 cluster_shaper.shape("H", &mut cluster);
736 assert_eq!(cluster.len(), 1);
737 assert_eq!(cluster[0].id, GlyphId::new(1));
739 }
740
741 #[test]
742 fn exceed_max_depth() {
743 let font = FontRef::new(font_test_data::NOTOSERIF_AUTOHINT_SHAPING).unwrap();
744 let shaper = Shaper::new(&font, ShaperMode::BestEffort);
745 let style = &style::STYLE_CLASSES[style::StyleClass::LATN];
746 let mut bad_lookup_builder = BadLookupBuilder::default();
748 for i in 0..MAX_NESTING_DEPTH {
749 bad_lookup_builder.lookups.push(i as u16 + 1);
751 }
752 let lookup_list_buf = bad_lookup_builder.build();
753 let lookup_list = SubstitutionLookupList::read(FontData::new(&lookup_list_buf)).unwrap();
754 let mut set_buf = [0u8; 8192];
755 let mut visited_set = VisitedLookupSet(&mut set_buf);
756 let mut gsub_handler = GsubHandler::new(
757 &shaper.charmap,
758 &lookup_list,
759 style,
760 &mut [],
761 &mut visited_set,
762 );
763 assert_eq!(
764 gsub_handler.process_lookup(0),
765 Err(ProcessLookupError::ExceededMaxDepth)
766 );
767 }
768
769 #[test]
770 fn dont_cycle_forever() {
771 let font = FontRef::new(font_test_data::NOTOSERIF_AUTOHINT_SHAPING).unwrap();
772 let shaper = Shaper::new(&font, ShaperMode::BestEffort);
773 let style = &style::STYLE_CLASSES[style::StyleClass::LATN];
774 let mut bad_lookup_builder = BadLookupBuilder::default();
776 bad_lookup_builder.lookups.push(1);
777 bad_lookup_builder.lookups.push(0);
778 let lookup_list_buf = bad_lookup_builder.build();
779 let lookup_list = SubstitutionLookupList::read(FontData::new(&lookup_list_buf)).unwrap();
780 let mut set_buf = [0u8; 8192];
781 let mut visited_set = VisitedLookupSet(&mut set_buf);
782 let mut gsub_handler = GsubHandler::new(
783 &shaper.charmap,
784 &lookup_list,
785 style,
786 &mut [],
787 &mut visited_set,
788 );
789 gsub_handler.process_lookup(0).unwrap();
790 }
791
792 #[test]
793 fn visited_set() {
794 let count = 2341u16;
795 let n_bytes = (count as usize).div_ceil(8);
796 let mut set_buf = vec![0u8; n_bytes];
797 let mut set = VisitedLookupSet::new(&mut set_buf);
798 for i in 0..count {
799 assert!(set.insert(i));
800 assert!(!set.insert(i));
801 }
802 for byte in &set_buf[0..set_buf.len() - 1] {
803 assert_eq!(*byte, 0xFF);
804 }
805 assert_eq!(*set_buf.last().unwrap(), 0b00011111);
806 }
807
808 #[derive(Default)]
809 struct BadLookupBuilder {
810 lookups: Vec<u16>,
812 }
813
814 impl BadLookupBuilder {
815 fn build(&self) -> Vec<u8> {
816 const CONTEXT3_FULL_SIZE: usize = 18;
819 let mut buf = BeBuffer::default();
820 buf = buf.push(self.lookups.len() as u16);
823 let base_offset = 2 + 2 * self.lookups.len();
825 for i in 0..self.lookups.len() {
826 buf = buf.push((base_offset + i * CONTEXT3_FULL_SIZE) as u16);
827 }
828 for nested_ix in &self.lookups {
830 buf = buf.push(5u16);
832 buf = buf.push(0u16);
834 buf = buf.push(1u16);
836 buf = buf.push(8u16);
838 buf = buf.push(3u16);
840 buf = buf.push(0u16);
842 buf = buf.push(1u16);
844 buf = buf.push(0u16).push(*nested_ix);
847 }
848 buf.to_vec()
849 }
850 }
851}