1use alloc::vec::Vec;
2
3use super::feature_mappings::FEATURE_MAPPINGS;
4use crate::{Face, Mask, Tag};
5
6#[repr(u8)]
7#[derive(Clone, Copy, PartialEq)]
8pub enum FeatureType {
9 Ligatures = 1,
10 LetterCase = 3,
11 VerticalSubstitution = 4,
12 NumberSpacing = 6,
13 VerticalPosition = 10,
14 Fractions = 11,
15 TypographicExtras = 14,
16 MathematicalExtras = 15,
17 StyleOptions = 19,
18 CharacterShape = 20,
19 NumberCase = 21,
20 TextSpacing = 22,
21 Transliteration = 23,
22 RubyKana = 28,
23 ItalicCjkRoman = 32,
24 CaseSensitiveLayout = 33,
25 AlternateKana = 34,
26 StylisticAlternatives = 35,
27 ContextualAlternatives = 36,
28 LowerCase = 37,
29 UpperCase = 38,
30}
31
32#[derive(Default)]
33pub struct Map {
34 pub chain_flags: Vec<Mask>,
35}
36
37#[derive(Copy, Clone)]
38pub struct FeatureInfo {
39 pub kind: u16,
40 pub setting: u16,
41 pub is_exclusive: bool,
42}
43
44#[derive(Default)]
45pub struct MapBuilder {
46 pub features: Vec<FeatureInfo>,
47}
48
49impl MapBuilder {
50 pub fn add_feature(&mut self, face: &Face, tag: Tag, value: u32) -> Option<()> {
51 const FEATURE_TYPE_CHARACTER_ALTERNATIVES: u16 = 17;
52
53 let feat = face.tables().feat?;
54
55 if tag == Tag::from_bytes(b"aalt") {
56 let exposes_feature = feat
57 .names
58 .find(FEATURE_TYPE_CHARACTER_ALTERNATIVES)
59 .map(|f| f.setting_names.len() != 0)
60 .unwrap_or(false);
61
62 if !exposes_feature {
63 return Some(());
64 }
65
66 self.features.push(FeatureInfo {
67 kind: FEATURE_TYPE_CHARACTER_ALTERNATIVES,
68 setting: value as u16,
69 is_exclusive: true,
70 });
71 }
72
73 let idx = FEATURE_MAPPINGS
74 .binary_search_by(|map| map.ot_feature_tag.cmp(&tag))
75 .ok()?;
76 let mapping = &FEATURE_MAPPINGS[idx];
77
78 let mut feature = feat.names.find(mapping.aat_feature_type as u16);
79
80 match feature {
81 Some(feature) if feature.setting_names.len() != 0 => {}
82 _ => {
83 // Special case: Chain::compile_flags will fall back to the deprecated version of
84 // small-caps if necessary, so we need to check for that possibility.
85 // https://github.com/harfbuzz/harfbuzz/issues/2307
86 if mapping.aat_feature_type == FeatureType::LowerCase
87 && mapping.selector_to_enable == super::feature_selector::LOWER_CASE_SMALL_CAPS
88 {
89 feature = feat.names.find(FeatureType::LetterCase as u16);
90 }
91 }
92 }
93
94 match feature {
95 Some(feature) if feature.setting_names.len() != 0 => {
96 let setting = if value != 0 {
97 mapping.selector_to_enable
98 } else {
99 mapping.selector_to_disable
100 } as u16;
101
102 self.features.push(FeatureInfo {
103 kind: mapping.aat_feature_type as u16,
104 setting,
105 is_exclusive: feature.exclusive,
106 });
107 }
108 _ => {}
109 }
110
111 Some(())
112 }
113
114 pub fn has_feature(&self, kind: u16, setting: u16) -> bool {
115 self.features
116 .binary_search_by(|probe| {
117 if probe.kind != kind {
118 probe.kind.cmp(&kind)
119 } else {
120 probe.setting.cmp(&setting)
121 }
122 })
123 .is_ok()
124 }
125
126 pub fn compile(&mut self, face: &Face) -> Map {
127 // Sort features and merge duplicates.
128 self.features.sort_by(|a, b| {
129 if a.kind != b.kind {
130 a.kind.cmp(&b.kind)
131 } else if !a.is_exclusive && (a.setting & !1) != (b.setting & !1) {
132 a.setting.cmp(&b.setting)
133 } else {
134 core::cmp::Ordering::Equal
135 }
136 });
137
138 let mut j = 0;
139 for i in 0..self.features.len() {
140 // Nonexclusive feature selectors come in even/odd pairs to turn a setting on/off
141 // respectively, so we mask out the low-order bit when checking for "duplicates"
142 // (selectors referring to the same feature setting) here.
143 let non_exclusive = !self.features[i].is_exclusive
144 && (self.features[i].setting & !1) != (self.features[j].setting & !1);
145
146 if self.features[i].kind != self.features[j].kind || non_exclusive {
147 j += 1;
148 self.features[j] = self.features[i];
149 }
150 }
151 self.features.truncate(j + 1);
152
153 super::metamorphosis::compile_flags(face, self).unwrap_or_default()
154 }
155}
156