Skip to main content

harfrust/hb/aat/
map.rs

1use crate::hb::common::{HB_FEATURE_GLOBAL_END, HB_FEATURE_GLOBAL_START};
2use crate::Feature;
3use alloc::vec;
4use alloc::vec::Vec;
5use core::cmp::Ordering;
6
7use super::layout::*;
8use crate::hb::{hb_font_t, hb_mask_t, hb_tag_t};
9
10/// HB: hb_aat_map_t
11///
12/// See <https://github.com/harfbuzz/harfbuzz/blob/2c22a65f0cb99544c36580b9703a43b5dc97a9e1/src/hb-aat-map.hh#L33>
13#[doc(alias = "hb_aat_map_t")]
14#[derive(Default)]
15pub struct AatMap {
16    pub chain_flags: Vec<Vec<RangeFlags>>,
17}
18
19/// HB: hb_aat_map_t::range_flags_t
20///
21/// See <https://github.com/harfbuzz/harfbuzz/blob/2c22a65f0cb99544c36580b9703a43b5dc97a9e1/src/hb-aat-map.hh#L38>
22#[derive(Copy, Clone)]
23pub struct RangeFlags {
24    pub flags: hb_mask_t,
25    pub cluster_first: u32,
26    pub cluster_last: u32, // end - 1
27}
28
29/// HB: hb_aat_map_builder_t
30///
31/// See <https://github.com/harfbuzz/harfbuzz/blob/2c22a65f0cb99544c36580b9703a43b5dc97a9e1/src/hb-aat-map.hh#L49>
32#[doc(alias = "hb_aat_map_builder_t")]
33pub struct AatMapBuilder {
34    pub current_features: Vec<FeatureInfo>,
35    pub features: Vec<FeatureRange>,
36    pub range_first: usize,
37    pub range_last: usize,
38}
39
40impl Default for AatMapBuilder {
41    fn default() -> Self {
42        Self {
43            range_first: HB_FEATURE_GLOBAL_START as usize,
44            range_last: HB_FEATURE_GLOBAL_END as usize,
45            current_features: Vec::default(),
46            features: Vec::default(),
47        }
48    }
49}
50
51impl AatMapBuilder {
52    pub fn add_feature(&mut self, face: &hb_font_t, feature: &Feature) -> Option<()> {
53        let feat = face.aat_tables.feat.as_ref()?;
54
55        if feature.tag == hb_tag_t::new(b"aalt") {
56            let exposes_feature = feat
57                .find(FEATURE_TYPE_CHARACTER_ALTERNATIVES as u16)
58                .is_some_and(|f| f.n_settings() != 0);
59
60            if !exposes_feature {
61                return Some(());
62            }
63
64            self.features.push(FeatureRange {
65                start: feature.start,
66                end: feature.end,
67                info: FeatureInfo {
68                    kind: FEATURE_TYPE_CHARACTER_ALTERNATIVES as u16,
69                    setting: u16::try_from(feature.value).unwrap(),
70                    is_exclusive: true,
71                },
72            });
73        }
74
75        let idx = feature_mappings
76            .binary_search_by(|map| map.ot_feature_tag.cmp(&feature.tag))
77            .ok()?;
78        let mapping = &feature_mappings[idx];
79
80        let mut feature_name = feat.find(mapping.aat_feature_type as u16);
81
82        match feature_name {
83            Some(feature) if feature.n_settings() != 0 => {}
84            _ => {
85                // Special case: Chain::compile_flags will fall back to the deprecated version of
86                // small-caps if necessary, so we need to check for that possibility.
87                // https://github.com/harfbuzz/harfbuzz/issues/2307
88                if mapping.aat_feature_type == FEATURE_TYPE_LOWER_CASE
89                    && mapping.selector_to_enable == FEATURE_SELECTOR_LOWER_CASE_SMALL_CAPS
90                {
91                    feature_name = feat.find(FEATURE_TYPE_LETTER_CASE as u16);
92                }
93            }
94        }
95
96        match feature_name {
97            Some(feature_name) if feature_name.n_settings() != 0 => {
98                let setting = if feature.value != 0 {
99                    mapping.selector_to_enable
100                } else {
101                    mapping.selector_to_disable
102                } as u16;
103
104                self.features.push(FeatureRange {
105                    start: feature.start,
106                    end: feature.end,
107                    info: FeatureInfo {
108                        kind: mapping.aat_feature_type as u16,
109                        setting,
110                        is_exclusive: feature_name.is_exclusive(),
111                    },
112                });
113            }
114            _ => {}
115        }
116
117        Some(())
118    }
119
120    pub fn compile(&mut self, face: &hb_font_t, m: &mut AatMap) {
121        // Compute active features per range, and compile each.
122        let mut feature_events = vec![];
123        for feature in &self.features {
124            if feature.start == feature.end {
125                continue;
126            }
127
128            feature_events.push(FeatureEvent {
129                index: feature.start as usize,
130                start: true,
131                feature: feature.info,
132            });
133
134            feature_events.push(FeatureEvent {
135                index: feature.end as usize,
136                start: false,
137                feature: feature.info,
138            });
139        }
140
141        feature_events.sort();
142
143        // Add a strategic final event.
144        feature_events.push(FeatureEvent {
145            index: u32::MAX as usize,
146            start: false,
147            feature: FeatureInfo::default(),
148        });
149
150        // Scan events and save features for each range.
151        let mut active_features = vec![];
152        let mut last_index = 0;
153
154        for event in &feature_events {
155            if event.index != last_index {
156                // Save a snapshot of active features and the range.
157                // Sort features and merge duplicates.
158                self.current_features.clone_from(&active_features);
159                self.range_first = last_index;
160                self.range_last = event.index.wrapping_sub(1);
161
162                if !self.current_features.is_empty() {
163                    self.current_features.sort();
164                    let mut j = 0;
165                    for i in 1..self.current_features.len() {
166                        // Nonexclusive feature selectors come in even/odd pairs to turn a setting on/off
167                        // respectively, so we mask out the low-order bit when checking for "duplicates"
168                        // (selectors referring to the same feature setting) here.
169                        let non_exclusive = !self.current_features[i].is_exclusive
170                            && (self.current_features[i].setting & !1)
171                                != (self.current_features[j].setting & !1);
172
173                        if self.current_features[i].kind != self.current_features[j].kind
174                            || non_exclusive
175                        {
176                            j += 1;
177                            self.current_features[j] = self.current_features[i];
178                        }
179                    }
180                    self.current_features.truncate(j + 1);
181                }
182
183                super::layout_morx_table::compile_flags(face, self, m);
184                last_index = event.index;
185            }
186
187            if event.start {
188                active_features.push(event.feature);
189            } else {
190                if let Some(index) = active_features.iter().position(|&f| f == event.feature) {
191                    active_features.remove(index);
192                }
193            }
194        }
195
196        for chain_flags in &mut m.chain_flags {
197            if let Some(last) = chain_flags.last_mut() {
198                last.cluster_last = HB_FEATURE_GLOBAL_END;
199            }
200        }
201    }
202}
203
204/// HB: hb_aat_map_builder_t::feature_info_t
205///
206/// See <https://github.com/harfbuzz/harfbuzz/blob/2c22a65f0cb99544c36580b9703a43b5dc97a9e1/src/hb-aat-map.hh#L63>
207#[derive(Copy, Clone, PartialEq, Eq, Default)]
208pub struct FeatureInfo {
209    pub kind: u16,
210    pub setting: u16,
211    pub is_exclusive: bool,
212}
213
214impl Ord for FeatureInfo {
215    fn cmp(&self, other: &Self) -> Ordering {
216        self.partial_cmp(other).unwrap()
217    }
218}
219
220impl PartialOrd for FeatureInfo {
221    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
222        if self.kind != other.kind {
223            Some(self.kind.cmp(&other.kind))
224        } else if !self.is_exclusive && (self.setting & !1) != (other.setting & !1) {
225            Some(self.setting.cmp(&other.setting))
226        } else {
227            Some(Ordering::Equal)
228        }
229    }
230}
231
232/// HB: hb_aat_map_builder_t::feature_range_t
233///
234/// See <https://github.com/harfbuzz/harfbuzz/blob/2c22a65f0cb99544c36580b9703a43b5dc97a9e1/src/hb-aat-map.hh#L88>
235#[derive(Copy, Clone, PartialEq, Eq)]
236pub struct FeatureRange {
237    pub info: FeatureInfo,
238    pub start: u32,
239    pub end: u32,
240}
241
242/// HB: hb_aat_map_builder_t::feature_event_t
243///
244/// See <https://github.com/harfbuzz/harfbuzz/blob/2c22a65f0cb99544c36580b9703a43b5dc97a9e1/src/hb-aat-map.hh#L96>
245#[derive(Copy, Clone, Eq, PartialEq)]
246struct FeatureEvent {
247    pub index: usize,
248    pub start: bool,
249    pub feature: FeatureInfo,
250}
251
252impl Ord for FeatureEvent {
253    fn cmp(&self, other: &Self) -> Ordering {
254        self.partial_cmp(other).unwrap()
255    }
256}
257
258impl PartialOrd for FeatureEvent {
259    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
260        if self.index != other.index {
261            Some(self.index.cmp(&other.index))
262        } else if self.start != other.start {
263            Some(self.start.cmp(&other.start))
264        } else {
265            Some(Ordering::Equal)
266        }
267    }
268}