swash/shape/
engine.rs

1use super::{aat, at};
2
3use super::buffer::*;
4use super::internal::{self, at::Gdef, raw_tag, Bytes, RawFont, RawTag};
5use crate::font::FontRef;
6use crate::text::{Language, Script};
7
8use alloc::vec::Vec;
9use core::ops::Range;
10
11/// Shaping engine that handles the various methods available in
12/// an OpenType font.
13pub struct Engine<'a> {
14    pub data: Bytes<'a>,
15    pub gdef: Gdef<'a>,
16    pub gsub: at::StageOffsets,
17    pub gpos: at::StageOffsets,
18    pub morx: u32,
19    pub kerx: u32,
20    pub ankr: u32,
21    pub kern: u32,
22    pub storage: at::Storage,
23    pub coords: &'a [i16],
24    pub script: Script,
25    pub tags: [RawTag; 4],
26    pub sub_mode: SubMode,
27    pub pos_mode: PosMode,
28    pub use_ot: bool,
29    pub mode: EngineMode,
30}
31
32impl<'a> Engine<'a> {
33    /// Creates a new shaping engine from precreated metadata.
34    pub fn new(
35        metadata: &EngineMetadata,
36        font_data: &'a [u8],
37        coords: &'a [i16],
38        script: Script,
39        lang: Option<Language>,
40    ) -> Self {
41        let data = Bytes::new(font_data);
42        let gdef = Gdef::from_offset(font_data, metadata.gdef).unwrap_or_else(Gdef::empty);
43        let script_tag = script.to_opentype();
44        let lang_tag = lang.and_then(|l| l.to_opentype());
45        let (gsub, stags) = if metadata.sub_mode == SubMode::Gsub {
46            at::StageOffsets::new(&data, metadata.gsub, script_tag, lang_tag).unwrap_or_default()
47        } else {
48            (at::StageOffsets::default(), [0, 0])
49        };
50        let (gpos, ptags) = if metadata.pos_mode == PosMode::Gpos {
51            at::StageOffsets::new(&data, metadata.gpos, script_tag, lang_tag).unwrap_or_default()
52        } else {
53            (at::StageOffsets::default(), [0, 0])
54        };
55        let tags = [stags[0], stags[1], ptags[0], ptags[1]];
56        let use_ot = gsub.lang != 0 || gpos.lang != 0;
57        let mode = if gsub.lang != 0 && script.is_complex() {
58            if script == Script::Myanmar {
59                EngineMode::Myanmar
60            } else {
61                EngineMode::Complex
62            }
63        } else {
64            EngineMode::Simple
65        };
66        let mut sub_mode = metadata.sub_mode;
67        let mut pos_mode = metadata.pos_mode;
68        if sub_mode == SubMode::Gsub && gsub.lang == 0 {
69            sub_mode = SubMode::None;
70        }
71        if pos_mode == PosMode::Gpos && gpos.lang == 0 {
72            pos_mode = PosMode::None;
73        }
74        Self {
75            data,
76            gdef,
77            gsub,
78            gpos,
79            morx: metadata.morx,
80            kerx: metadata.kerx,
81            ankr: metadata.ankr,
82            kern: metadata.kern,
83            storage: at::Storage::default(),
84            coords,
85            script,
86            tags,
87            sub_mode,
88            pos_mode,
89            use_ot,
90            mode,
91        }
92    }
93}
94
95/// OpenType shaping.
96impl<'a> Engine<'a> {
97    /// Returns the script and language tags that have been selected for
98    /// the GSUB and GPOS tables.
99    pub fn tags(&self) -> &[RawTag; 4] {
100        &self.tags
101    }
102
103    /// Builds a feature store for the current engine configuration.
104    pub fn collect_features(
105        &self,
106        builder: &mut at::FeatureStoreBuilder,
107        store: &mut at::FeatureStore,
108    ) {
109        builder.build(
110            store,
111            self.data.data(),
112            self.coords,
113            &self.gdef,
114            &self.gsub,
115            &self.gpos,
116        );
117        store.groups = store.groups(self.script);
118    }
119
120    /// Returns true if feature variations are supported.
121    pub fn has_feature_vars(&self) -> bool {
122        self.gsub.var != 0 || self.gpos.var != 0
123    }
124
125    /// Sets glyph and mark classes for the specified range of the buffer.
126    pub fn set_classes(&self, buffer: &mut Buffer, range: Option<Range<usize>>) {
127        if !self.gdef.ok() {
128            return;
129        }
130        let slice = if let Some(range) = range {
131            &mut buffer.glyphs[range]
132        } else {
133            &mut buffer.glyphs[..]
134        };
135        let gdef = &self.gdef;
136        if gdef.has_mark_classes() {
137            for g in slice.iter_mut() {
138                g.class = gdef.class(g.id) as u8;
139                g.mark_type = gdef.mark_class(g.id) as u8;
140            }
141        } else {
142            for g in slice.iter_mut() {
143                g.class = gdef.class(g.id) as u8;
144            }
145        }
146    }
147
148    /// Applies the GSUB features to the specified range of the buffer.
149    pub fn gsub(
150        &mut self,
151        store: &at::FeatureStore,
152        feature_mask: impl Into<at::FeatureMask>,
153        buffer: &mut Buffer,
154        buffer_range: Option<Range<usize>>,
155    ) -> bool {
156        at::apply(
157            0,
158            &self.data,
159            self.gsub.base,
160            self.coords,
161            &self.gdef,
162            &mut self.storage,
163            store,
164            feature_mask.into(),
165            buffer,
166            buffer_range,
167        ) == Some(true)
168    }
169
170    /// Applies the GPOS features to the specified range of the buffer.
171    pub fn gpos(
172        &mut self,
173        store: &at::FeatureStore,
174        feature_mask: impl Into<at::FeatureMask>,
175        buffer: &mut Buffer,
176        buffer_range: Option<Range<usize>>,
177    ) -> bool {
178        at::apply(
179            1,
180            &self.data,
181            self.gpos.base,
182            self.coords,
183            &self.gdef,
184            &mut self.storage,
185            store,
186            feature_mask.into(),
187            buffer,
188            buffer_range,
189        ) == Some(true)
190    }
191}
192
193/// Apple shaping.
194impl<'a> Engine<'a> {
195    /// Converts a feature list into a sorted collection of AAT selectors.
196    pub fn collect_selectors(&self, features: &[(RawTag, u16)], selectors: &mut Vec<(u16, u16)>) {
197        use internal::aat::morx::feature_from_tag;
198        selectors.clear();
199        for (tag, value) in features {
200            if let Some((selector, [on, off])) = feature_from_tag(*tag) {
201                let setting = if *value == 0 { off } else { on };
202                selectors.push((selector, setting));
203            }
204        }
205        selectors.sort_unstable();
206    }
207
208    /// Applies the extended metamorphosis table.
209    pub fn morx(&self, buffer: &mut Buffer, selectors: &[(u16, u16)]) {
210        if self.morx != 0 {
211            aat::apply_morx(self.data.data(), self.morx, buffer, selectors);
212            buffer.ensure_order(false);
213        }
214    }
215
216    /// Applies the extended kerning table.
217    pub fn kerx(&self, buffer: &mut Buffer, disable_kern: bool) {
218        if self.kerx != 0 {
219            aat::apply_kerx(self.data.data(), self.kerx, self.ankr, buffer, disable_kern);
220            buffer.ensure_order(false);
221        }
222    }
223
224    /// Applies the kerning table.
225    pub fn kern(&self, buffer: &mut Buffer) {
226        if self.kern != 0 {
227            aat::apply_kern(self.data.data(), self.kern, buffer);
228        }
229    }
230}
231
232/// The overall mode of the engine based on a combination of the
233/// supported tables and the selected script.
234#[derive(Copy, Clone, PartialEq, Eq, Debug)]
235pub enum EngineMode {
236    Simple,
237    Myanmar,
238    Complex,
239}
240
241/// The substitution mode supported by the engine.
242#[derive(Copy, Clone, PartialEq, Eq, Debug)]
243pub enum SubMode {
244    None,
245    Gsub,
246    Morx,
247}
248
249/// The positioning mode supported by the engine.
250#[derive(Copy, Clone, PartialEq, Eq, Debug)]
251pub enum PosMode {
252    None,
253    Gpos,
254    Kerx,
255    Kern,
256}
257
258/// Metadata for creating a shaping engine.
259#[derive(Copy, Clone)]
260pub struct EngineMetadata {
261    pub gdef: u32,
262    pub gsub: u32,
263    pub gpos: u32,
264    pub morx: u32,
265    pub kerx: u32,
266    pub ankr: u32,
267    pub kern: u32,
268    pub sub_mode: SubMode,
269    pub pos_mode: PosMode,
270}
271
272impl EngineMetadata {
273    pub fn from_font(font: &FontRef) -> Self {
274        let mut this = Self {
275            gdef: font.table_offset(raw_tag(b"GDEF")),
276            gsub: font.table_offset(raw_tag(b"GSUB")),
277            gpos: font.table_offset(raw_tag(b"GPOS")),
278            morx: font.table_offset(raw_tag(b"morx")),
279            // ltag: font.table_offset(raw_tag(b"ltag")),
280            kerx: font.table_offset(raw_tag(b"kerx")),
281            ankr: font.table_offset(raw_tag(b"ankr")),
282            kern: font.table_offset(raw_tag(b"kern")),
283            sub_mode: SubMode::None,
284            pos_mode: PosMode::None,
285        };
286        if this.gsub != 0 {
287            this.sub_mode = SubMode::Gsub;
288        } else if this.morx != 0 {
289            this.sub_mode = SubMode::Morx;
290        }
291        if this.gpos != 0 {
292            this.pos_mode = PosMode::Gpos;
293        } else if this.kerx != 0 {
294            this.pos_mode = PosMode::Kerx;
295        } else if this.kern != 0 {
296            this.pos_mode = PosMode::Kern;
297        }
298        this
299    }
300}