skrifa/outline/glyf/hint/engine/
misc.rs

1//! Miscellaneous instructions.
2//!
3//! Implements 3 instructions.
4//!
5//! See <https://learn.microsoft.com/en-us/typography/opentype/spec/tt_instructions#miscellaneous-instructions>
6
7use super::{Engine, OpResult};
8
9impl Engine<'_> {
10    /// Get information.
11    ///
12    /// GETINFO[] (0x88)
13    ///
14    /// Pops: selector: integer
15    /// Pushes: result: integer
16    ///
17    /// GETINFO is used to obtain data about the font scaler version and the
18    /// characteristics of the current glyph. The instruction pops a selector
19    /// used to determine the type of information desired and pushes a result
20    /// onto the stack.
21    ///
22    /// See <https://learn.microsoft.com/en-us/typography/opentype/spec/tt_instructions#get-information>
23    /// and <https://gitlab.freedesktop.org/freetype/freetype/-/blob/57617782464411201ce7bbc93b086c1b4d7d84a5/src/truetype/ttinterp.c#L6689>
24    pub(super) fn op_getinfo(&mut self) -> OpResult {
25        use getinfo::*;
26        let selector = self.value_stack.pop()?;
27        let mut result = 0;
28        // Interpreter version (selector bit: 0, result bits: 0-7)
29        if (selector & VERSION_SELECTOR_BIT) != 0 {
30            result = 40;
31        }
32        // Glyph rotated (selector bit: 1, result bit: 8)
33        if (selector & GLYPH_ROTATED_SELECTOR_BIT) != 0 && self.graphics.is_rotated {
34            result |= GLYPH_ROTATED_RESULT_BIT;
35        }
36        // Glyph stretched (selector bit: 2, result bit: 9)
37        if (selector & GLYPH_STRETCHED_SELECTOR_BIT) != 0 && self.graphics.is_stretched {
38            result |= GLYPH_STRETCHED_RESULT_BIT;
39        }
40        // Font variations (selector bit: 3, result bit: 10)
41        if (selector & FONT_VARIATIONS_SELECTOR_BIT) != 0 && self.axis_count != 0 {
42            result |= FONT_VARIATIONS_RESULT_BIT;
43        }
44        // The following only apply for smooth hinting.
45        if self.graphics.target.is_smooth() {
46            // Subpixel hinting [cleartype enabled] (selector bit: 6, result bit: 13)
47            // (always enabled)
48            if (selector & SUBPIXEL_HINTING_SELECTOR_BIT) != 0 {
49                result |= SUBPIXEL_HINTING_RESULT_BIT;
50            }
51            // Vertical LCD subpixels? (selector bit: 8, result bit: 15)
52            if (selector & VERTICAL_LCD_SELECTOR_BIT) != 0 && self.graphics.target.is_vertical_lcd()
53            {
54                result |= VERTICAL_LCD_RESULT_BIT;
55            }
56            // Subpixel positioned? (selector bit: 10, result bit: 17)
57            // (always enabled)
58            if (selector & SUBPIXEL_POSITIONED_SELECTOR_BIT) != 0 {
59                result |= SUBPIXEL_POSITIONED_RESULT_BIT;
60            }
61            // Symmetrical smoothing (selector bit: 11, result bit: 18)
62            // Note: FreeType always enables this but we allow direct control
63            // with our own flag.
64            // See <https://github.com/googlefonts/fontations/issues/1080>
65            if (selector & SYMMETRICAL_SMOOTHING_SELECTOR_BIT) != 0
66                && self.graphics.target.symmetric_rendering()
67            {
68                result |= SYMMETRICAL_SMOOTHING_RESULT_BIT;
69            }
70            // ClearType hinting and grayscale rendering (selector bit: 12, result bit: 19)
71            if (selector & GRAYSCALE_CLEARTYPE_SELECTOR_BIT) != 0
72                && self.graphics.target.is_grayscale_cleartype()
73            {
74                result |= GRAYSCALE_CLEARTYPE_RESULT_BIT;
75            }
76        }
77        self.value_stack.push(result)
78    }
79
80    /// Get variation.
81    ///
82    /// GETVARIATION[] (0x91)
83    ///
84    /// Pushes: Normalized axes coordinates, one for each axis in the font.
85    ///
86    /// GETVARIATION is used to obtain the current normalized variation
87    /// coordinates for each axis. The coordinate for the first axis, as
88    /// defined in the 'fvar' table, is pushed first on the stack, followed
89    /// by each consecutive axis until the coordinate for the last axis is
90    /// on the stack.
91    ///
92    /// See <https://learn.microsoft.com/en-us/typography/opentype/spec/tt_instructions#get-variation>
93    /// and <https://gitlab.freedesktop.org/freetype/freetype/-/blob/57617782464411201ce7bbc93b086c1b4d7d84a5/src/truetype/ttinterp.c#L6813>
94    pub(super) fn op_getvariation(&mut self) -> OpResult {
95        // For non-variable fonts, this falls back to IDEF resolution.
96        let axis_count = self.axis_count as usize;
97        if axis_count != 0 {
98            // Make sure we push `axis_count` coords regardless of the value
99            // provided by the user.
100            for coord in self
101                .coords
102                .iter()
103                .copied()
104                .chain(std::iter::repeat(Default::default()))
105                .take(axis_count)
106            {
107                self.value_stack.push(coord.to_bits() as i32)?;
108            }
109            Ok(())
110        } else {
111            self.op_unknown(0x91)
112        }
113    }
114
115    /// Get data.
116    ///
117    /// GETDATA[] (0x92)
118    ///
119    /// Pushes: 17
120    ///
121    /// Undocumented and nobody knows what this does. FreeType just
122    /// returns 17 for variable fonts and falls back to IDEF lookup
123    /// otherwise.
124    ///
125    /// See <https://gitlab.freedesktop.org/freetype/freetype/-/blob/57617782464411201ce7bbc93b086c1b4d7d84a5/src/truetype/ttinterp.c#L6851>
126    pub(super) fn op_getdata(&mut self) -> OpResult {
127        if self.axis_count != 0 {
128            self.value_stack.push(17)
129        } else {
130            self.op_unknown(0x92)
131        }
132    }
133}
134
135/// Constants for the GETINFO instruction. Extracted here
136/// to enable access from tests.
137mod getinfo {
138    // Interpreter version (selector bit: 0, result bits: 0-7)
139    pub const VERSION_SELECTOR_BIT: i32 = 1 << 0;
140
141    // Glyph rotated (selector bit: 1, result bit: 8)
142    pub const GLYPH_ROTATED_SELECTOR_BIT: i32 = 1 << 1;
143    pub const GLYPH_ROTATED_RESULT_BIT: i32 = 1 << 8;
144
145    // Glyph stretched (selector bit: 2, result bit: 9)
146    pub const GLYPH_STRETCHED_SELECTOR_BIT: i32 = 1 << 2;
147    pub const GLYPH_STRETCHED_RESULT_BIT: i32 = 1 << 9;
148
149    // Font variations (selector bit: 3, result bit: 10)
150    pub const FONT_VARIATIONS_SELECTOR_BIT: i32 = 1 << 3;
151    pub const FONT_VARIATIONS_RESULT_BIT: i32 = 1 << 10;
152
153    // Subpixel hinting [cleartype enabled] (selector bit: 6, result bit: 13)
154    // (always enabled)
155    pub const SUBPIXEL_HINTING_SELECTOR_BIT: i32 = 1 << 6;
156    pub const SUBPIXEL_HINTING_RESULT_BIT: i32 = 1 << 13;
157
158    // Vertical LCD subpixels? (selector bit: 8, result bit: 15)
159    pub const VERTICAL_LCD_SELECTOR_BIT: i32 = 1 << 8;
160    pub const VERTICAL_LCD_RESULT_BIT: i32 = 1 << 15;
161
162    // Subpixel positioned? (selector bit: 10, result bit: 17)
163    // (always enabled)
164    pub const SUBPIXEL_POSITIONED_SELECTOR_BIT: i32 = 1 << 10;
165    pub const SUBPIXEL_POSITIONED_RESULT_BIT: i32 = 1 << 17;
166
167    // Symmetrical smoothing (selector bit: 11, result bit: 18)
168    // Note: FreeType always enables this but we deviate when our own
169    // preserve linear metrics flag is enabled.
170    pub const SYMMETRICAL_SMOOTHING_SELECTOR_BIT: i32 = 1 << 11;
171    pub const SYMMETRICAL_SMOOTHING_RESULT_BIT: i32 = 1 << 18;
172
173    // ClearType hinting and grayscale rendering (selector bit: 12, result bit: 19)
174    pub const GRAYSCALE_CLEARTYPE_SELECTOR_BIT: i32 = 1 << 12;
175    pub const GRAYSCALE_CLEARTYPE_RESULT_BIT: i32 = 1 << 19;
176}
177
178#[cfg(test)]
179mod tests {
180    use super::super::{
181        super::super::super::{SmoothMode, Target},
182        Engine, HintErrorKind, MockEngine,
183    };
184    use raw::types::F2Dot14;
185    use read_fonts::tables::glyf::bytecode::Opcode;
186
187    #[test]
188    fn getinfo() {
189        use super::getinfo::*;
190        let mut mock = MockEngine::new();
191        let mut engine = mock.engine();
192        // version
193        engine.getinfo_test(VERSION_SELECTOR_BIT, 40);
194        // not rotated
195        engine.getinfo_test(GLYPH_ROTATED_SELECTOR_BIT, 0);
196        // rotated
197        engine.graphics.is_rotated = true;
198        engine.getinfo_test(GLYPH_ROTATED_SELECTOR_BIT, GLYPH_ROTATED_RESULT_BIT);
199        // not stretched
200        engine.getinfo_test(GLYPH_STRETCHED_SELECTOR_BIT, 0);
201        // stretched
202        engine.graphics.is_stretched = true;
203        engine.getinfo_test(GLYPH_STRETCHED_SELECTOR_BIT, GLYPH_STRETCHED_RESULT_BIT);
204        // stretched and rotated
205        engine.getinfo_test(
206            GLYPH_ROTATED_SELECTOR_BIT | GLYPH_STRETCHED_SELECTOR_BIT,
207            GLYPH_ROTATED_RESULT_BIT | GLYPH_STRETCHED_RESULT_BIT,
208        );
209        // not variable
210        engine.getinfo_test(FONT_VARIATIONS_SELECTOR_BIT, 0);
211        // variable
212        engine.axis_count = 1;
213        engine.getinfo_test(FONT_VARIATIONS_SELECTOR_BIT, FONT_VARIATIONS_RESULT_BIT);
214        // in strong hinting mode, the following selectors are always disabled
215        engine.graphics.target = Target::Mono;
216        for selector in [
217            SUBPIXEL_HINTING_SELECTOR_BIT,
218            VERTICAL_LCD_SELECTOR_BIT,
219            SUBPIXEL_POSITIONED_SELECTOR_BIT,
220            SYMMETRICAL_SMOOTHING_SELECTOR_BIT,
221            GRAYSCALE_CLEARTYPE_SELECTOR_BIT,
222        ] {
223            engine.getinfo_test(selector, 0);
224        }
225        // set back to smooth mode
226        engine.graphics.target = Target::default();
227        for (selector, result) in [
228            // default smooth mode is grayscale cleartype
229            (
230                GRAYSCALE_CLEARTYPE_SELECTOR_BIT,
231                GRAYSCALE_CLEARTYPE_RESULT_BIT,
232            ),
233            // always enabled in smooth mode
234            (SUBPIXEL_HINTING_SELECTOR_BIT, SUBPIXEL_HINTING_RESULT_BIT),
235            (
236                SUBPIXEL_POSITIONED_SELECTOR_BIT,
237                SUBPIXEL_POSITIONED_RESULT_BIT,
238            ),
239        ] {
240            engine.getinfo_test(selector, result);
241        }
242        // vertical lcd
243        engine.graphics.target = Target::Smooth {
244            mode: SmoothMode::VerticalLcd,
245            preserve_linear_metrics: true,
246            symmetric_rendering: false,
247        };
248        engine.getinfo_test(VERTICAL_LCD_SELECTOR_BIT, VERTICAL_LCD_RESULT_BIT);
249        // symmetical smoothing is disabled
250        engine.getinfo_test(SYMMETRICAL_SMOOTHING_SELECTOR_BIT, 0);
251        // grayscale cleartype is disabled when lcd_subpixel is not None
252        engine.getinfo_test(GRAYSCALE_CLEARTYPE_SELECTOR_BIT, 0);
253        // reset to default to disable preserve linear metrics
254        engine.graphics.target = Target::default();
255        // now symmetrical smoothing is enabled
256        engine.getinfo_test(
257            SYMMETRICAL_SMOOTHING_SELECTOR_BIT,
258            SYMMETRICAL_SMOOTHING_RESULT_BIT,
259        );
260    }
261
262    #[test]
263    fn getvariation() {
264        let mut mock = MockEngine::new();
265        let mut engine = mock.engine();
266        // no variations should trigger unknown opcode
267        assert!(matches!(
268            engine.op_getvariation(),
269            Err(HintErrorKind::UnhandledOpcode(Opcode::GETVARIATION))
270        ));
271        // set the axis count to a non-zero value to enable variations
272        engine.axis_count = 2;
273        // and creates some coords
274        let coords = [
275            F2Dot14::from_f32(-1.0),
276            F2Dot14::from_f32(0.5),
277            F2Dot14::from_f32(1.0),
278        ];
279        let coords_bits = coords.map(|x| x.to_bits() as i32);
280        // too few, pad with zeros
281        engine.coords = &coords[0..1];
282        engine.op_getvariation().unwrap();
283        assert_eq!(engine.value_stack.len(), 2);
284        assert_eq!(engine.value_stack.values(), &[coords_bits[0], 0]);
285        engine.value_stack.clear();
286        // too many, truncate
287        engine.coords = &coords[0..3];
288        engine.op_getvariation().unwrap();
289        assert_eq!(engine.value_stack.len(), 2);
290        assert_eq!(engine.value_stack.values(), &coords_bits[0..2]);
291        engine.value_stack.clear();
292        // just right
293        engine.coords = &coords[0..2];
294        engine.op_getvariation().unwrap();
295        assert_eq!(engine.value_stack.len(), 2);
296        assert_eq!(engine.value_stack.values(), &coords_bits[0..2]);
297    }
298
299    #[test]
300    fn getdata() {
301        let mut mock = MockEngine::new();
302        let mut engine = mock.engine();
303        // no variations should trigger unknown opcode
304        assert!(matches!(
305            engine.op_getdata(),
306            Err(HintErrorKind::UnhandledOpcode(Opcode::GETDATA))
307        ));
308        // set the axis count to a non-zero value to enable variations
309        engine.axis_count = 1;
310        engine.op_getdata().unwrap();
311        // :shrug:
312        assert_eq!(engine.value_stack.pop().unwrap(), 17);
313    }
314
315    impl Engine<'_> {
316        fn getinfo_test(&mut self, selector: i32, expected: i32) {
317            self.value_stack.push(selector).unwrap();
318            self.op_getinfo().unwrap();
319            assert_eq!(self.value_stack.pop().unwrap(), expected);
320        }
321    }
322}