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 SubstitutionSubtables::EmptyExtension => {}
369 }
370 false
371 }
372}
373
374#[derive(Clone)]
375enum ClusterShaperKind<'a> {
376 Nominal,
377 SingleFeature(Feature<'a>),
378}
379
380struct GsubHandler<'a, 'b> {
399 charmap: &'a Charmap<'a>,
400 lookup_list: &'a SubstitutionLookupList<'a>,
401 style: &'a StyleClass,
402 glyph_styles: &'a mut [GlyphStyle],
403 need_blue_substs: bool,
406 min_gid: usize,
408 max_gid: usize,
409 lookup_depth: usize,
410 visited_set: &'a mut VisitedLookupSet<'b>,
411}
412
413impl<'a, 'b> GsubHandler<'a, 'b> {
414 fn new(
415 charmap: &'a Charmap<'a>,
416 lookup_list: &'a SubstitutionLookupList,
417 style: &'a StyleClass,
418 glyph_styles: &'a mut [GlyphStyle],
419 visited_set: &'a mut VisitedLookupSet<'b>,
420 ) -> Self {
421 let min_gid = glyph_styles.len();
422 let need_blue_substs = style.feature.is_some();
426 Self {
427 charmap,
428 lookup_list,
429 style,
430 glyph_styles,
431 need_blue_substs,
432 min_gid,
433 max_gid: 0,
434 lookup_depth: 0,
435 visited_set,
436 }
437 }
438
439 fn process_lookup(&mut self, lookup_index: u16) -> Result<(), ProcessLookupError> {
440 if self.lookup_depth == MAX_NESTING_DEPTH {
442 return Err(ProcessLookupError::ExceededMaxDepth);
443 }
444 if !self.visited_set.insert(lookup_index) {
446 return Ok(());
447 }
448 self.lookup_depth += 1;
449 let result = self.process_lookup_inner(lookup_index);
451 self.lookup_depth -= 1;
453 result
454 }
455
456 #[inline(always)]
457 fn process_lookup_inner(&mut self, lookup_index: u16) -> Result<(), ProcessLookupError> {
458 let Ok(subtables) = self
459 .lookup_list
460 .lookups()
461 .get(lookup_index as usize)
462 .and_then(|lookup| lookup.subtables())
463 else {
464 return Ok(());
465 };
466 match subtables {
467 SubstitutionSubtables::Single(tables) => {
468 for table in tables.iter().filter_map(|table| table.ok()) {
469 match table {
470 SingleSubst::Format1(table) => {
471 let Ok(coverage) = table.coverage() else {
472 continue;
473 };
474 let delta = table.delta_glyph_id() as i32;
475 for gid in coverage.iter() {
476 self.capture_glyph((gid.to_u32() as i32 + delta) as u16 as u32);
477 }
478 if self.need_blue_substs && self.lookup_depth == 1 {
482 self.check_blue_coverage(Ok(coverage));
483 }
484 }
485 SingleSubst::Format2(table) => {
486 for gid in table.substitute_glyph_ids() {
487 self.capture_glyph(gid.get().to_u32());
488 }
489 if self.need_blue_substs && self.lookup_depth == 1 {
491 self.check_blue_coverage(table.coverage());
492 }
493 }
494 }
495 }
496 }
497 SubstitutionSubtables::Multiple(tables) => {
498 for table in tables.iter().filter_map(|table| table.ok()) {
499 for seq in table.sequences().iter().filter_map(|seq| seq.ok()) {
500 for gid in seq.substitute_glyph_ids() {
501 self.capture_glyph(gid.get().to_u32());
502 }
503 }
504 if self.need_blue_substs && self.lookup_depth == 1 {
506 self.check_blue_coverage(table.coverage());
507 }
508 }
509 }
510 SubstitutionSubtables::Ligature(tables) => {
511 for table in tables.iter().filter_map(|table| table.ok()) {
512 for set in table.ligature_sets().iter().filter_map(|set| set.ok()) {
513 for lig in set.ligatures().iter().filter_map(|lig| lig.ok()) {
514 self.capture_glyph(lig.ligature_glyph().to_u32());
515 }
516 }
517 }
518 }
519 SubstitutionSubtables::Alternate(tables) => {
520 for table in tables.iter().filter_map(|table| table.ok()) {
521 for set in table.alternate_sets().iter().filter_map(|set| set.ok()) {
522 for gid in set.alternate_glyph_ids() {
523 self.capture_glyph(gid.get().to_u32());
524 }
525 }
526 }
527 }
528 SubstitutionSubtables::Contextual(tables) => {
529 for table in tables.iter().filter_map(|table| table.ok()) {
530 match table {
531 SequenceContext::Format1(table) => {
532 for set in table
533 .seq_rule_sets()
534 .iter()
535 .filter_map(|set| set.transpose().ok().flatten())
536 {
537 for rule in set.seq_rules().iter().filter_map(|rule| rule.ok()) {
538 for rec in rule.seq_lookup_records() {
539 self.process_lookup(rec.lookup_list_index())?;
540 }
541 }
542 }
543 }
544 SequenceContext::Format2(table) => {
545 for set in table
546 .class_seq_rule_sets()
547 .iter()
548 .filter_map(|set| set.transpose().ok().flatten())
549 {
550 for rule in
551 set.class_seq_rules().iter().filter_map(|rule| rule.ok())
552 {
553 for rec in rule.seq_lookup_records() {
554 self.process_lookup(rec.lookup_list_index())?;
555 }
556 }
557 }
558 }
559 SequenceContext::Format3(table) => {
560 for rec in table.seq_lookup_records() {
561 self.process_lookup(rec.lookup_list_index())?;
562 }
563 }
564 }
565 }
566 }
567 SubstitutionSubtables::ChainContextual(tables) => {
568 for table in tables.iter().filter_map(|table| table.ok()) {
569 match table {
570 ChainedSequenceContext::Format1(table) => {
571 for set in table
572 .chained_seq_rule_sets()
573 .iter()
574 .filter_map(|set| set.transpose().ok().flatten())
575 {
576 for rule in
577 set.chained_seq_rules().iter().filter_map(|rule| rule.ok())
578 {
579 for rec in rule.seq_lookup_records() {
580 self.process_lookup(rec.lookup_list_index())?;
581 }
582 }
583 }
584 }
585 ChainedSequenceContext::Format2(table) => {
586 for set in table
587 .chained_class_seq_rule_sets()
588 .iter()
589 .filter_map(|set| set.transpose().ok().flatten())
590 {
591 for rule in set
592 .chained_class_seq_rules()
593 .iter()
594 .filter_map(|rule| rule.ok())
595 {
596 for rec in rule.seq_lookup_records() {
597 self.process_lookup(rec.lookup_list_index())?;
598 }
599 }
600 }
601 }
602 ChainedSequenceContext::Format3(table) => {
603 for rec in table.seq_lookup_records() {
604 self.process_lookup(rec.lookup_list_index())?;
605 }
606 }
607 }
608 }
609 }
610 SubstitutionSubtables::Reverse(tables) => {
611 for table in tables.iter().filter_map(|table| table.ok()) {
612 for gid in table.substitute_glyph_ids() {
613 self.capture_glyph(gid.get().to_u32());
614 }
615 }
616 }
617 SubstitutionSubtables::EmptyExtension => (),
618 }
619 Ok(())
620 }
621
622 fn finish(self) -> Option<Range<usize>> {
625 self.visited_set.clear();
626 if self.min_gid > self.max_gid {
627 return None;
629 }
630 let range = self.min_gid..self.max_gid + 1;
631 if self.need_blue_substs {
632 for glyph in &mut self.glyph_styles[range] {
636 glyph.clear_from_gsub();
637 }
638 None
639 } else {
640 Some(range)
641 }
642 }
643
644 fn check_blue_coverage(&mut self, coverage: Result<CoverageTable<'a>, ReadError>) {
647 let Ok(coverage) = coverage else {
648 return;
649 };
650 for (blue_str, _) in self.style.script.blues {
651 if blue_str
652 .chars()
653 .filter_map(|ch| self.charmap.map(ch))
654 .filter_map(|gid| coverage.get(gid))
655 .next()
656 .is_some()
657 {
658 self.need_blue_substs = false;
660 return;
661 }
662 }
663 }
664
665 fn capture_glyph(&mut self, gid: u32) {
666 let gid = gid as usize;
667 if let Some(style) = self.glyph_styles.get_mut(gid) {
668 style.set_from_gsub_output();
669 self.min_gid = gid.min(self.min_gid);
670 self.max_gid = gid.max(self.max_gid);
671 }
672 }
673}
674
675pub(crate) struct VisitedLookupSet<'a>(&'a mut [u8]);
676
677impl<'a> VisitedLookupSet<'a> {
678 pub fn new(storage: &'a mut [u8]) -> Self {
679 Self(storage)
680 }
681
682 fn insert(&mut self, lookup_index: u16) -> bool {
687 let byte_ix = lookup_index as usize / 8;
688 let bit_mask = 1 << (lookup_index % 8) as u8;
689 if let Some(byte) = self.0.get_mut(byte_ix) {
690 if *byte & bit_mask == 0 {
691 *byte |= bit_mask;
692 true
693 } else {
694 false
695 }
696 } else {
697 false
698 }
699 }
700
701 fn clear(&mut self) {
702 self.0.fill(0);
703 }
704}
705
706#[derive(PartialEq, Debug)]
707enum ProcessLookupError {
708 ExceededMaxDepth,
709}
710
711#[cfg(test)]
712mod tests {
713 use super::{super::style, *};
714 use font_test_data::bebuffer::BeBuffer;
715 use raw::{FontData, FontRead};
716
717 #[test]
718 fn small_caps_subst() {
719 let font = FontRef::new(font_test_data::NOTOSERIF_AUTOHINT_SHAPING).unwrap();
720 let shaper = Shaper::new(&font, ShaperMode::BestEffort);
721 let style = &style::STYLE_CLASSES[style::StyleClass::LATN_C2SC];
722 let mut cluster_shaper = shaper.cluster_shaper(style);
723 let mut cluster = ShapedCluster::new();
724 cluster_shaper.shape("H", &mut cluster);
725 assert_eq!(cluster.len(), 1);
726 assert_eq!(cluster[0].id, GlyphId::new(8));
728 }
729
730 #[test]
731 fn small_caps_nominal() {
732 let font = FontRef::new(font_test_data::NOTOSERIF_AUTOHINT_SHAPING).unwrap();
733 let shaper = Shaper::new(&font, ShaperMode::Nominal);
734 let style = &style::STYLE_CLASSES[style::StyleClass::LATN_C2SC];
735 let mut cluster_shaper = shaper.cluster_shaper(style);
736 let mut cluster = ShapedCluster::new();
737 cluster_shaper.shape("H", &mut cluster);
738 assert_eq!(cluster.len(), 1);
739 assert_eq!(cluster[0].id, GlyphId::new(1));
741 }
742
743 #[test]
744 fn exceed_max_depth() {
745 let font = FontRef::new(font_test_data::NOTOSERIF_AUTOHINT_SHAPING).unwrap();
746 let shaper = Shaper::new(&font, ShaperMode::BestEffort);
747 let style = &style::STYLE_CLASSES[style::StyleClass::LATN];
748 let mut bad_lookup_builder = BadLookupBuilder::default();
750 for i in 0..MAX_NESTING_DEPTH {
751 bad_lookup_builder.lookups.push(i as u16 + 1);
753 }
754 let lookup_list_buf = bad_lookup_builder.build();
755 let lookup_list = SubstitutionLookupList::read(FontData::new(&lookup_list_buf)).unwrap();
756 let mut set_buf = [0u8; 8192];
757 let mut visited_set = VisitedLookupSet(&mut set_buf);
758 let mut gsub_handler = GsubHandler::new(
759 &shaper.charmap,
760 &lookup_list,
761 style,
762 &mut [],
763 &mut visited_set,
764 );
765 assert_eq!(
766 gsub_handler.process_lookup(0),
767 Err(ProcessLookupError::ExceededMaxDepth)
768 );
769 }
770
771 #[test]
772 fn dont_cycle_forever() {
773 let font = FontRef::new(font_test_data::NOTOSERIF_AUTOHINT_SHAPING).unwrap();
774 let shaper = Shaper::new(&font, ShaperMode::BestEffort);
775 let style = &style::STYLE_CLASSES[style::StyleClass::LATN];
776 let mut bad_lookup_builder = BadLookupBuilder::default();
778 bad_lookup_builder.lookups.push(1);
779 bad_lookup_builder.lookups.push(0);
780 let lookup_list_buf = bad_lookup_builder.build();
781 let lookup_list = SubstitutionLookupList::read(FontData::new(&lookup_list_buf)).unwrap();
782 let mut set_buf = [0u8; 8192];
783 let mut visited_set = VisitedLookupSet(&mut set_buf);
784 let mut gsub_handler = GsubHandler::new(
785 &shaper.charmap,
786 &lookup_list,
787 style,
788 &mut [],
789 &mut visited_set,
790 );
791 gsub_handler.process_lookup(0).unwrap();
792 }
793
794 #[test]
795 fn visited_set() {
796 let count = 2341u16;
797 let n_bytes = (count as usize).div_ceil(8);
798 let mut set_buf = vec![0u8; n_bytes];
799 let mut set = VisitedLookupSet::new(&mut set_buf);
800 for i in 0..count {
801 assert!(set.insert(i));
802 assert!(!set.insert(i));
803 }
804 for byte in &set_buf[0..set_buf.len() - 1] {
805 assert_eq!(*byte, 0xFF);
806 }
807 assert_eq!(*set_buf.last().unwrap(), 0b00011111);
808 }
809
810 #[derive(Default)]
811 struct BadLookupBuilder {
812 lookups: Vec<u16>,
814 }
815
816 impl BadLookupBuilder {
817 fn build(&self) -> Vec<u8> {
818 const CONTEXT3_FULL_SIZE: usize = 18;
821 let mut buf = BeBuffer::default();
822 buf = buf.push(self.lookups.len() as u16);
825 let base_offset = 2 + 2 * self.lookups.len();
827 for i in 0..self.lookups.len() {
828 buf = buf.push((base_offset + i * CONTEXT3_FULL_SIZE) as u16);
829 }
830 for nested_ix in &self.lookups {
832 buf = buf.push(5u16);
834 buf = buf.push(0u16);
836 buf = buf.push(1u16);
838 buf = buf.push(8u16);
840 buf = buf.push(3u16);
842 buf = buf.push(0u16);
844 buf = buf.push(1u16);
846 buf = buf.push(0u16).push(*nested_ix);
849 }
850 buf.to_vec()
851 }
852 }
853}