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 // In harfbuzz, they just use the number 40 for "hist" but don't give it a name
32 Dummy = 40,
33}
34
35#[derive(Default)]
36pub struct Map {
37 pub chain_flags: Vec<Mask>,
38}
39
40#[derive(Copy, Clone)]
41pub struct FeatureInfo {
42 pub kind: u16,
43 pub setting: u16,
44 pub is_exclusive: bool,
45}
46
47#[derive(Default)]
48pub struct MapBuilder {
49 pub features: Vec<FeatureInfo>,
50}
51
52impl MapBuilder {
53 pub fn add_feature(&mut self, face: &Face, tag: Tag, value: u32) -> Option<()> {
54 const FEATURE_TYPE_CHARACTER_ALTERNATIVES: u16 = 17;
55
56 let feat = face.tables().feat?;
57
58 if tag == Tag::from_bytes(b"aalt") {
59 let exposes_feature = feat
60 .names
61 .find(FEATURE_TYPE_CHARACTER_ALTERNATIVES)
62 .map(|f| f.setting_names.len() != 0)
63 .unwrap_or(false);
64
65 if !exposes_feature {
66 return Some(());
67 }
68
69 self.features.push(FeatureInfo {
70 kind: FEATURE_TYPE_CHARACTER_ALTERNATIVES,
71 setting: value as u16,
72 is_exclusive: true,
73 });
74 }
75
76 let idx = FEATURE_MAPPINGS
77 .binary_search_by(|map| map.ot_feature_tag.cmp(&tag))
78 .ok()?;
79 let mapping = &FEATURE_MAPPINGS[idx];
80
81 let mut feature = feat.names.find(mapping.aat_feature_type as u16);
82
83 match feature {
84 Some(feature) if feature.setting_names.len() != 0 => {}
85 _ => {
86 // Special case: Chain::compile_flags will fall back to the deprecated version of
87 // small-caps if necessary, so we need to check for that possibility.
88 // https://github.com/harfbuzz/harfbuzz/issues/2307
89 if mapping.aat_feature_type == FeatureType::LowerCase
90 && mapping.selector_to_enable == super::feature_selector::LOWER_CASE_SMALL_CAPS
91 {
92 feature = feat.names.find(FeatureType::LetterCase as u16);
93 }
94 }
95 }
96
97 match feature {
98 Some(feature) if feature.setting_names.len() != 0 => {
99 let setting = if value != 0 {
100 mapping.selector_to_enable
101 } else {
102 mapping.selector_to_disable
103 } as u16;
104
105 self.features.push(FeatureInfo {
106 kind: mapping.aat_feature_type as u16,
107 setting,
108 is_exclusive: feature.exclusive,
109 });
110 }
111 _ => {}
112 }
113
114 Some(())
115 }
116
117 pub fn has_feature(&self, kind: u16, setting: u16) -> bool {
118 self.features
119 .binary_search_by(|probe| {
120 if probe.kind != kind {
121 probe.kind.cmp(&kind)
122 } else {
123 probe.setting.cmp(&setting)
124 }
125 })
126 .is_ok()
127 }
128
129 pub fn compile(&mut self, face: &Face) -> Map {
130 // Sort features and merge duplicates.
131 self.features.sort_by(|a, b| {
132 if a.kind != b.kind {
133 a.kind.cmp(&b.kind)
134 } else if !a.is_exclusive && (a.setting & !1) != (b.setting & !1) {
135 a.setting.cmp(&b.setting)
136 } else {
137 core::cmp::Ordering::Equal
138 }
139 });
140
141 let mut j = 0;
142 for i in 0..self.features.len() {
143 // Nonexclusive feature selectors come in even/odd pairs to turn a setting on/off
144 // respectively, so we mask out the low-order bit when checking for "duplicates"
145 // (selectors referring to the same feature setting) here.
146 let non_exclusive = !self.features[i].is_exclusive
147 && (self.features[i].setting & !1) != (self.features[j].setting & !1);
148
149 if self.features[i].kind != self.features[j].kind || non_exclusive {
150 j += 1;
151 self.features[j] = self.features[i];
152 }
153 }
154 self.features.truncate(j + 1);
155
156 super::metamorphosis::compile_flags(face, self).unwrap_or_default()
157 }
158}
159