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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
//! Miscellaneous instructions.
//!
//! Implements 3 instructions.
//!
//! See <https://learn.microsoft.com/en-us/typography/opentype/spec/tt_instructions#miscellaneous-instructions>

use super::{Engine, OpResult};

impl<'a> Engine<'a> {
    /// Get information.
    ///
    /// GETINFO[] (0x88)
    ///
    /// Pops: selector: integer
    /// Pushes: result: integer
    ///
    /// GETINFO is used to obtain data about the font scaler version and the
    /// characteristics of the current glyph. The instruction pops a selector
    /// used to determine the type of information desired and pushes a result
    /// onto the stack.    
    ///
    /// See <https://learn.microsoft.com/en-us/typography/opentype/spec/tt_instructions#get-information>
    /// and <https://gitlab.freedesktop.org/freetype/freetype/-/blob/57617782464411201ce7bbc93b086c1b4d7d84a5/src/truetype/ttinterp.c#L6689>
    pub(super) fn op_getinfo(&mut self) -> OpResult {
        use getinfo::*;
        let selector = self.value_stack.pop()?;
        let mut result = 0;
        // Interpreter version (selector bit: 0, result bits: 0-7)
        if (selector & VERSION_SELECTOR_BIT) != 0 {
            result = 40;
        }
        // Glyph rotated (selector bit: 1, result bit: 8)
        if (selector & GLYPH_ROTATED_SELECTOR_BIT) != 0 && self.graphics.is_rotated {
            result |= GLYPH_ROTATED_RESULT_BIT;
        }
        // Glyph stretched (selector bit: 2, result bit: 9)
        if (selector & GLYPH_STRETCHED_SELECTOR_BIT) != 0 && self.graphics.is_stretched {
            result |= GLYPH_STRETCHED_RESULT_BIT;
        }
        // Font variations (selector bit: 3, result bit: 10)
        if (selector & FONT_VARIATIONS_SELECTOR_BIT) != 0 && self.axis_count != 0 {
            result |= FONT_VARIATIONS_RESULT_BIT;
        }
        // The following only apply for smooth hinting.
        if self.graphics.target.is_smooth() {
            // Subpixel hinting [cleartype enabled] (selector bit: 6, result bit: 13)
            // (always enabled)
            if (selector & SUBPIXEL_HINTING_SELECTOR_BIT) != 0 {
                result |= SUBPIXEL_HINTING_RESULT_BIT;
            }
            // Vertical LCD subpixels? (selector bit: 8, result bit: 15)
            if (selector & VERTICAL_LCD_SELECTOR_BIT) != 0 && self.graphics.target.is_vertical_lcd()
            {
                result |= VERTICAL_LCD_RESULT_BIT;
            }
            // Subpixel positioned? (selector bit: 10, result bit: 17)
            // (always enabled)
            if (selector & SUBPIXEL_POSITIONED_SELECTOR_BIT) != 0 {
                result |= SUBPIXEL_POSITIONED_RESULT_BIT;
            }
            // Symmetrical smoothing (selector bit: 11, result bit: 18)
            // Note: FreeType always enables this but we allow direct control
            // with our own flag.
            // See <https://github.com/googlefonts/fontations/issues/1080>
            if (selector & SYMMETRICAL_SMOOTHING_SELECTOR_BIT) != 0
                && self.graphics.target.symmetric_rendering()
            {
                result |= SYMMETRICAL_SMOOTHING_RESULT_BIT;
            }
            // ClearType hinting and grayscale rendering (selector bit: 12, result bit: 19)
            if (selector & GRAYSCALE_CLEARTYPE_SELECTOR_BIT) != 0
                && self.graphics.target.is_grayscale_cleartype()
            {
                result |= GRAYSCALE_CLEARTYPE_RESULT_BIT;
            }
        }
        self.value_stack.push(result)
    }

    /// Get variation.
    ///
    /// GETVARIATION[] (0x91)
    ///
    /// Pushes: Normalized axes coordinates, one for each axis in the font.
    ///
    /// GETVARIATION is used to obtain the current normalized variation
    /// coordinates for each axis. The coordinate for the first axis, as
    /// defined in the 'fvar' table, is pushed first on the stack, followed
    /// by each consecutive axis until the coordinate for the last axis is
    /// on the stack.   
    ///
    /// See <https://learn.microsoft.com/en-us/typography/opentype/spec/tt_instructions#get-variation>
    /// and <https://gitlab.freedesktop.org/freetype/freetype/-/blob/57617782464411201ce7bbc93b086c1b4d7d84a5/src/truetype/ttinterp.c#L6813>
    pub(super) fn op_getvariation(&mut self) -> OpResult {
        // For non-variable fonts, this falls back to IDEF resolution.
        let axis_count = self.axis_count as usize;
        if axis_count != 0 {
            // Make sure we push `axis_count` coords regardless of the value
            // provided by the user.
            for coord in self
                .coords
                .iter()
                .copied()
                .chain(std::iter::repeat(Default::default()))
                .take(axis_count)
            {
                self.value_stack.push(coord.to_bits() as i32)?;
            }
            Ok(())
        } else {
            self.op_unknown(0x91)
        }
    }

    /// Get data.
    ///
    /// GETDATA[] (0x92)
    ///
    /// Pushes: 17
    ///
    /// Undocumented and nobody knows what this does. FreeType just
    /// returns 17 for variable fonts and falls back to IDEF lookup
    /// otherwise.
    ///
    /// See <https://gitlab.freedesktop.org/freetype/freetype/-/blob/57617782464411201ce7bbc93b086c1b4d7d84a5/src/truetype/ttinterp.c#L6851>    
    pub(super) fn op_getdata(&mut self) -> OpResult {
        if self.axis_count != 0 {
            self.value_stack.push(17)
        } else {
            self.op_unknown(0x92)
        }
    }
}

/// Constants for the GETINFO instruction. Extracted here
/// to enable access from tests.
mod getinfo {
    // Interpreter version (selector bit: 0, result bits: 0-7)
    pub const VERSION_SELECTOR_BIT: i32 = 1 << 0;

    // Glyph rotated (selector bit: 1, result bit: 8)
    pub const GLYPH_ROTATED_SELECTOR_BIT: i32 = 1 << 1;
    pub const GLYPH_ROTATED_RESULT_BIT: i32 = 1 << 8;

    // Glyph stretched (selector bit: 2, result bit: 9)
    pub const GLYPH_STRETCHED_SELECTOR_BIT: i32 = 1 << 2;
    pub const GLYPH_STRETCHED_RESULT_BIT: i32 = 1 << 9;

    // Font variations (selector bit: 3, result bit: 10)
    pub const FONT_VARIATIONS_SELECTOR_BIT: i32 = 1 << 3;
    pub const FONT_VARIATIONS_RESULT_BIT: i32 = 1 << 10;

    // Subpixel hinting [cleartype enabled] (selector bit: 6, result bit: 13)
    // (always enabled)
    pub const SUBPIXEL_HINTING_SELECTOR_BIT: i32 = 1 << 6;
    pub const SUBPIXEL_HINTING_RESULT_BIT: i32 = 1 << 13;

    // Vertical LCD subpixels? (selector bit: 8, result bit: 15)
    pub const VERTICAL_LCD_SELECTOR_BIT: i32 = 1 << 8;
    pub const VERTICAL_LCD_RESULT_BIT: i32 = 1 << 15;

    // Subpixel positioned? (selector bit: 10, result bit: 17)
    // (always enabled)
    pub const SUBPIXEL_POSITIONED_SELECTOR_BIT: i32 = 1 << 10;
    pub const SUBPIXEL_POSITIONED_RESULT_BIT: i32 = 1 << 17;

    // Symmetrical smoothing (selector bit: 11, result bit: 18)
    // Note: FreeType always enables this but we deviate when our own
    // preserve linear metrics flag is enabled.
    pub const SYMMETRICAL_SMOOTHING_SELECTOR_BIT: i32 = 1 << 11;
    pub const SYMMETRICAL_SMOOTHING_RESULT_BIT: i32 = 1 << 18;

    // ClearType hinting and grayscale rendering (selector bit: 12, result bit: 19)
    pub const GRAYSCALE_CLEARTYPE_SELECTOR_BIT: i32 = 1 << 12;
    pub const GRAYSCALE_CLEARTYPE_RESULT_BIT: i32 = 1 << 19;
}

#[cfg(test)]
mod tests {
    use super::super::{
        super::super::super::{SmoothMode, Target},
        Engine, HintErrorKind, MockEngine,
    };
    use raw::types::F2Dot14;
    use read_fonts::tables::glyf::bytecode::Opcode;

    #[test]
    fn getinfo() {
        use super::getinfo::*;
        let mut mock = MockEngine::new();
        let mut engine = mock.engine();
        // version
        engine.getinfo_test(VERSION_SELECTOR_BIT, 40);
        // not rotated
        engine.getinfo_test(GLYPH_ROTATED_SELECTOR_BIT, 0);
        // rotated
        engine.graphics.is_rotated = true;
        engine.getinfo_test(GLYPH_ROTATED_SELECTOR_BIT, GLYPH_ROTATED_RESULT_BIT);
        // not stretched
        engine.getinfo_test(GLYPH_STRETCHED_SELECTOR_BIT, 0);
        // stretched
        engine.graphics.is_stretched = true;
        engine.getinfo_test(GLYPH_STRETCHED_SELECTOR_BIT, GLYPH_STRETCHED_RESULT_BIT);
        // stretched and rotated
        engine.getinfo_test(
            GLYPH_ROTATED_SELECTOR_BIT | GLYPH_STRETCHED_SELECTOR_BIT,
            GLYPH_ROTATED_RESULT_BIT | GLYPH_STRETCHED_RESULT_BIT,
        );
        // not variable
        engine.getinfo_test(FONT_VARIATIONS_SELECTOR_BIT, 0);
        // variable
        engine.axis_count = 1;
        engine.getinfo_test(FONT_VARIATIONS_SELECTOR_BIT, FONT_VARIATIONS_RESULT_BIT);
        // in strong hinting mode, the following selectors are always disabled
        engine.graphics.target = Target::Mono;
        for selector in [
            SUBPIXEL_HINTING_SELECTOR_BIT,
            VERTICAL_LCD_SELECTOR_BIT,
            SUBPIXEL_POSITIONED_SELECTOR_BIT,
            SYMMETRICAL_SMOOTHING_SELECTOR_BIT,
            GRAYSCALE_CLEARTYPE_SELECTOR_BIT,
        ] {
            engine.getinfo_test(selector, 0);
        }
        // set back to smooth mode
        engine.graphics.target = Target::default();
        for (selector, result) in [
            // default smooth mode is grayscale cleartype
            (
                GRAYSCALE_CLEARTYPE_SELECTOR_BIT,
                GRAYSCALE_CLEARTYPE_RESULT_BIT,
            ),
            // always enabled in smooth mode
            (SUBPIXEL_HINTING_SELECTOR_BIT, SUBPIXEL_HINTING_RESULT_BIT),
            (
                SUBPIXEL_POSITIONED_SELECTOR_BIT,
                SUBPIXEL_POSITIONED_RESULT_BIT,
            ),
        ] {
            engine.getinfo_test(selector, result);
        }
        // vertical lcd
        engine.graphics.target = Target::Smooth {
            mode: SmoothMode::VerticalLcd,
            preserve_linear_metrics: true,
            symmetric_rendering: false,
        };
        engine.getinfo_test(VERTICAL_LCD_SELECTOR_BIT, VERTICAL_LCD_RESULT_BIT);
        // symmetical smoothing is disabled
        engine.getinfo_test(SYMMETRICAL_SMOOTHING_SELECTOR_BIT, 0);
        // grayscale cleartype is disabled when lcd_subpixel is not None
        engine.getinfo_test(GRAYSCALE_CLEARTYPE_SELECTOR_BIT, 0);
        // reset to default to disable preserve linear metrics
        engine.graphics.target = Target::default();
        // now symmetrical smoothing is enabled
        engine.getinfo_test(
            SYMMETRICAL_SMOOTHING_SELECTOR_BIT,
            SYMMETRICAL_SMOOTHING_RESULT_BIT,
        );
    }

    #[test]
    fn getvariation() {
        let mut mock = MockEngine::new();
        let mut engine = mock.engine();
        // no variations should trigger unknown opcode
        assert!(matches!(
            engine.op_getvariation(),
            Err(HintErrorKind::UnhandledOpcode(Opcode::GETVARIATION))
        ));
        // set the axis count to a non-zero value to enable variations
        engine.axis_count = 2;
        // and creates some coords
        let coords = [
            F2Dot14::from_f32(-1.0),
            F2Dot14::from_f32(0.5),
            F2Dot14::from_f32(1.0),
        ];
        let coords_bits = coords.map(|x| x.to_bits() as i32);
        // too few, pad with zeros
        engine.coords = &coords[0..1];
        engine.op_getvariation().unwrap();
        assert_eq!(engine.value_stack.len(), 2);
        assert_eq!(engine.value_stack.values(), &[coords_bits[0], 0]);
        engine.value_stack.clear();
        // too many, truncate
        engine.coords = &coords[0..3];
        engine.op_getvariation().unwrap();
        assert_eq!(engine.value_stack.len(), 2);
        assert_eq!(engine.value_stack.values(), &coords_bits[0..2]);
        engine.value_stack.clear();
        // just right
        engine.coords = &coords[0..2];
        engine.op_getvariation().unwrap();
        assert_eq!(engine.value_stack.len(), 2);
        assert_eq!(engine.value_stack.values(), &coords_bits[0..2]);
    }

    #[test]
    fn getdata() {
        let mut mock = MockEngine::new();
        let mut engine = mock.engine();
        // no variations should trigger unknown opcode
        assert!(matches!(
            engine.op_getdata(),
            Err(HintErrorKind::UnhandledOpcode(Opcode::GETDATA))
        ));
        // set the axis count to a non-zero value to enable variations
        engine.axis_count = 1;
        engine.op_getdata().unwrap();
        // :shrug:
        assert_eq!(engine.value_stack.pop().unwrap(), 17);
    }

    impl<'a> Engine<'a> {
        fn getinfo_test(&mut self, selector: i32, expected: i32) {
            self.value_stack.push(selector).unwrap();
            self.op_getinfo().unwrap();
            assert_eq!(self.value_stack.pop().unwrap(), expected);
        }
    }
}