1
//! CFF CharString (glyph) processing.
2

            
3
use std::fmt::{self, Write};
4

            
5
use rustc_hash::FxHashSet;
6

            
7
pub use argstack::ArgumentsStack;
8

            
9
use crate::binary::read::{ReadCtxt, ReadScope};
10
use crate::binary::write::{WriteBinary, WriteBuffer, WriteContext};
11
use crate::binary::{I16Be, U8};
12
use crate::cff;
13
use crate::cff::cff2::BlendOperand;
14
use crate::error::{ParseError, WriteError};
15
use crate::tables::variable_fonts::{ItemVariationStore, OwnedTuple};
16
use crate::tables::Fixed;
17
use crate::GlyphId;
18

            
19
use super::{cff2, CFFError, CFFFont, CFFVariant, MaybeOwnedIndex, Operator};
20

            
21
mod argstack;
22

            
23
// Stack limit according to the Adobe Technical Note #5177 Appendix B and CFF2 Appended B:
24
//
25
// https://learn.microsoft.com/en-us/typography/opentype/spec/cff2charstr#appendix-b-cff2-charstring-implementation-limits
26
pub(crate) const STACK_LIMIT: u8 = 10;
27

            
28
pub(crate) const TWO_BYTE_OPERATOR_MARK: u8 = 12;
29

            
30
pub(crate) trait IsEven {
31
    fn is_even(&self) -> bool;
32
    fn is_odd(&self) -> bool;
33
}
34

            
35
pub(crate) struct UsedSubrs {
36
    pub(crate) global_subr_used: FxHashSet<usize>,
37
    pub(crate) local_subr_used: FxHashSet<usize>,
38
}
39

            
40
/// Context for traversing a CFF CharString.
41
pub struct CharStringVisitorContext<'a, 'data> {
42
    glyph_id: GlyphId, // Required to parse local subroutine in CID fonts.
43
    char_strings_index: &'a MaybeOwnedIndex<'data>,
44
    local_subr_index: Option<&'a MaybeOwnedIndex<'data>>,
45
    global_subr_index: &'a MaybeOwnedIndex<'data>,
46
    // Required for variable fonts
47
    variable: Option<VariableCharStringVisitorContext<'a, 'data>>,
48
    width_parsed: bool,
49
    stems_len: u32,
50
    has_endchar: bool,
51
    has_seac: bool,
52
    seen_blend: bool,
53
    vsindex: Option<u16>,
54
    scalars: Option<Vec<Option<f32>>>,
55
}
56

            
57
/// Variable font data for a [CharStringVisitorContext]. Required if the CharString to be
58
/// traversed is variable.
59
#[derive(Copy, Clone)]
60
pub struct VariableCharStringVisitorContext<'a, 'data> {
61
    pub vstore: &'a ItemVariationStore<'data>,
62
    pub instance: &'a OwnedTuple,
63
}
64

            
65
/// A local or global subroutine index.
66
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
67
pub enum SubroutineIndex {
68
    Local(usize),
69
    Global(usize),
70
}
71

            
72
/// Flag indicating the component of an accented character.
73
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
74
pub enum SeacChar {
75
    Base,
76
    Accent,
77
}
78

            
79
/// A debug implementation of [CharStringVisitor] that just prints the operators and
80
/// their operands.
81
///
82
/// ### Example
83
///
84
/// ```
85
/// use allsorts::binary::read::ReadScope;
86
/// use allsorts::cff::charstring::{ArgumentsStack, CharStringVisitorContext, DebugVisitor};
87
/// use allsorts::cff::CFFError;
88
/// use allsorts::cff::{self, CFFFont, CFFVariant, CFF};
89
/// use allsorts::font::MatchingPresentation;
90
/// use allsorts::font_data::FontData;
91
/// use allsorts::tables::{OpenTypeData, OpenTypeFont};
92
/// use allsorts::{tag, Font};
93
///
94
/// fn main() -> Result<(), CFFError> {
95
///     // Read the font
96
///     let buffer = std::fs::read("tests/fonts/opentype/Klei.otf").unwrap();
97
///     let otf = ReadScope::new(&buffer).read::<OpenTypeFont<'_>>()?;
98
///     let offset_table = match otf.data {
99
///         OpenTypeData::Single(ttf) => ttf,
100
///         OpenTypeData::Collection(_) => unreachable!(),
101
///     };
102
///
103
///     let cff_table_data = offset_table
104
///         .read_table(&otf.scope, tag::CFF)?
105
///         .expect("missing CFF table");
106
///     let cff = cff_table_data.read::<CFF<'_>>()?;
107
///
108
///     // Glyph to visit
109
///     let glyph_id = 741; // U+2116 NUMERO SIGN
110
///
111
///     // Set up CharString visitor
112
///     let font = &cff.fonts[0];
113
///     let local_subrs = match &font.data {
114
///         CFFVariant::CID(_) => None, // Will be resolved on request.
115
///         CFFVariant::Type1(type1) => type1.local_subr_index.as_ref(),
116
///     };
117
///
118
///     let mut visitor = DebugVisitor;
119
///     let variable = None;
120
///     let mut ctx = CharStringVisitorContext::new(
121
///         glyph_id,
122
///         &font.char_strings_index,
123
///         local_subrs,
124
///         &cff.global_subr_index,
125
///         variable,
126
///     );
127
///     let mut stack = ArgumentsStack {
128
///         data: &mut [0.0; cff::MAX_OPERANDS],
129
///         len: 0,
130
///         max_len: cff::MAX_OPERANDS,
131
///     };
132
///     ctx.visit(CFFFont::CFF(font), &mut stack, &mut visitor)?;
133
///     Ok(())
134
/// }
135
/// ```
136
pub struct DebugVisitor;
137

            
138
/// Trait for types that can be used to traverse a CFF CharString.
139
///
140
/// Used in conjunction with [CharStringVisitorContext]. The visitor will receive
141
/// a `visit` call for each operator with its operands. All methods are optional
142
/// as they have default no-op implementations.
143
pub trait CharStringVisitor<T: fmt::Debug, E: std::error::Error> {
144
    /// Called for each operator in the CharString, except for `callsubr` and `callgsubr`
145
    /// — these are handled by `enter/exit_subr`.
146
    fn visit(&mut self, _op: VisitOp, _stack: &ArgumentsStack<'_, T>) -> Result<(), E> {
147
        Ok(())
148
    }
149

            
150
    /// Called prior to entering a subroutine.
151
    ///
152
    /// The index argument indicates the type of subroutine (local/global) and holds its index.
153
    fn enter_subr(&mut self, _index: SubroutineIndex) -> Result<(), E> {
154
        Ok(())
155
    }
156

            
157
    /// Called when returning from a subroutine.
158
    fn exit_subr(&mut self) -> Result<(), E> {
159
        Ok(())
160
    }
161

            
162
    /// Called before entering a component of an accented character.
163
    ///
164
    /// The `seac` argument indicates if it's the base or accent character. `dx` and `dy`
165
    /// are the x and y position of the accent character. The same values will be supplied
166
    /// for both the base and accent.
167
    fn enter_seac(&mut self, _seac: SeacChar, _dx: T, _dy: T) -> Result<(), E> {
168
        Ok(())
169
    }
170

            
171
    /// Called when returning from a component of an accented character.
172
    ///
173
    /// `seac` indicates whether it's the base or accent.
174
    fn exit_seac(&mut self, _seac: SeacChar) -> Result<(), E> {
175
        Ok(())
176
    }
177

            
178
    /// Called with the hint data that follows the `hintmask` and `cntrmask` operators.
179
    ///
180
    /// This function will be called after a `visit` invocation for the operators.
181
    fn hint_data(&mut self, _op: VisitOp, _hints: &[u8]) -> Result<(), E> {
182
        Ok(())
183
    }
184
}
185

            
186
pub(crate) fn char_string_used_subrs<'a, 'data>(
187
    font: CFFFont<'a, 'data>,
188
    char_strings_index: &'a MaybeOwnedIndex<'data>,
189
    global_subr_index: &'a MaybeOwnedIndex<'data>,
190
    glyph_id: GlyphId,
191
) -> Result<UsedSubrs, CFFError> {
192
    let (local_subrs, max_len) = match font {
193
        CFFFont::CFF(font) => match &font.data {
194
            CFFVariant::CID(_) => (None, cff::MAX_OPERANDS), // local subrs will be resolved on request.
195
            CFFVariant::Type1(type1) => (type1.local_subr_index.as_ref(), cff::MAX_OPERANDS),
196
        },
197
        CFFFont::CFF2(cff2) => (cff2.local_subr_index.as_ref(), cff2::MAX_OPERANDS),
198
    };
199

            
200
    let mut ctx = CharStringVisitorContext::new(
201
        glyph_id,
202
        char_strings_index,
203
        local_subrs,
204
        global_subr_index,
205
        // This function should not be called on variable CFF2 fonts. If the CFF2 font is variable
206
        // it should be instanced first.
207
        None,
208
    );
209

            
210
    let mut used_subrs = UsedSubrs {
211
        global_subr_used: FxHashSet::default(),
212
        local_subr_used: FxHashSet::default(),
213
    };
214

            
215
    let mut stack = ArgumentsStack {
216
        // We use CFF2 max operands as it is the bigger of the two, and it has to be a const value
217
        // at compile time to init the array.
218
        data: &mut [0.0; cff2::MAX_OPERANDS],
219
        len: 0,
220
        max_len,
221
    };
222
    ctx.visit(font, &mut stack, &mut used_subrs)?;
223

            
224
    if matches!(font, CFFFont::CFF(_)) && !ctx.has_endchar {
225
        return Err(CFFError::MissingEndChar);
226
    }
227

            
228
    Ok(used_subrs)
229
}
230

            
231
pub(crate) fn convert_cff2_to_cff<'a, 'data>(
232
    font: CFFFont<'a, 'data>,
233
    char_strings_index: &'a MaybeOwnedIndex<'data>,
234
    global_subr_index: &'a MaybeOwnedIndex<'data>,
235
    glyph_id: GlyphId,
236
    width: u16,
237
    default_width: u16,
238
    nominal_width: u16,
239
) -> Result<Vec<u8>, CharStringConversionError> {
240
    let (local_subrs, max_len) = match font {
241
        CFFFont::CFF(font) => match &font.data {
242
            CFFVariant::CID(_) => (None, cff::MAX_OPERANDS), // local subrs will be resolved on request.
243
            CFFVariant::Type1(type1) => (type1.local_subr_index.as_ref(), cff::MAX_OPERANDS),
244
        },
245
        CFFFont::CFF2(cff2) => (cff2.local_subr_index.as_ref(), cff2::MAX_OPERANDS),
246
    };
247

            
248
    let mut ctx = CharStringVisitorContext::new(
249
        glyph_id,
250
        char_strings_index,
251
        local_subrs,
252
        global_subr_index,
253
        None,
254
    );
255

            
256
    // If the width is not equal to defaultWidthX then the width is stored as the difference from
257
    // nominalWidthX.
258
    let char_string_width =
259
        i16::try_from(i32::from(width) - i32::from(nominal_width)).map_err(ParseError::from)?;
260

            
261
    let mut converter = CharStringConverter {
262
        buffer: WriteBuffer::new(),
263
        width: char_string_width,
264
        // If the width is equal to the default width then it does not need to be written,
265
        // so we mark it as already written to inhibit the converter from outputting it.
266
        width_written: width == default_width,
267
    };
268

            
269
    let mut stack = ArgumentsStack {
270
        // We use CFF2 max operands as it is the bigger of the two, and it has to be a const value
271
        // at compile time to init the array.
272
        data: &mut [cff2::StackValue::Int(0); cff2::MAX_OPERANDS],
273
        len: 0,
274
        max_len,
275
    };
276
    ctx.visit(font, &mut stack, &mut converter)?;
277

            
278
    // CFF CharStrings must end with an endchar operator
279
    //
280
    // > A character that does not have a path (e.g. a space character) may
281
    // > consist of an endchar operator preceded only by a width value.
282
    // > Although the width must be specified in the font, it may be specified as
283
    // > the defaultWidthX in the CFF data, in which case it should not be
284
    // > specified in the charstring. Also, it may appear in the charstring as the
285
    // > difference from nominalWidthX. Thus the smallest legal charstring
286
    // > consists of a single endchar operator.
287
    converter.maybe_write_width()?;
288
    U8::write(&mut converter.buffer, operator::ENDCHAR)?;
289

            
290
    Ok(converter.buffer.into_inner())
291
}
292

            
293
struct CharStringConverter {
294
    buffer: WriteBuffer,
295
    width_written: bool,
296
    width: i16,
297
}
298

            
299
impl CharStringConverter {
300
    fn maybe_write_width(&mut self) -> Result<(), WriteError> {
301
        if !self.width_written {
302
            cff2::write_stack_value(cff2::StackValue::Int(self.width), &mut self.buffer)?;
303
            self.width_written = true;
304
        }
305
        Ok(())
306
    }
307
}
308

            
309
#[derive(Debug)]
310
pub(crate) enum CharStringConversionError {
311
    Write(WriteError),
312
    Cff(CFFError),
313
}
314

            
315
impl From<CFFError> for CharStringConversionError {
316
    fn from(err: CFFError) -> Self {
317
        CharStringConversionError::Cff(err)
318
    }
319
}
320

            
321
impl From<ParseError> for CharStringConversionError {
322
    fn from(err: ParseError) -> Self {
323
        CharStringConversionError::Cff(CFFError::ParseError(err))
324
    }
325
}
326

            
327
impl From<WriteError> for CharStringConversionError {
328
    fn from(err: WriteError) -> Self {
329
        CharStringConversionError::Write(err)
330
    }
331
}
332

            
333
impl fmt::Display for CharStringConversionError {
334
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
335
        match self {
336
            CharStringConversionError::Write(err) => {
337
                write!(f, "unable to convert charstring: {err}")
338
            }
339
            CharStringConversionError::Cff(err) => write!(f, "unable to convert charstring: {err}"),
340
        }
341
    }
342
}
343

            
344
impl std::error::Error for CharStringConversionError {}
345

            
346
impl CharStringVisitor<cff2::StackValue, CharStringConversionError> for CharStringConverter {
347
    fn visit(
348
        &mut self,
349
        op: VisitOp,
350
        stack: &ArgumentsStack<'_, cff2::StackValue>,
351
    ) -> Result<(), CharStringConversionError> {
352
        // The first stack-clearing operator, which must be one of hstem,
353
        // hstemhm, vstem, vstemhm, cntrmask, hintmask, hmoveto, vmoveto,
354
        // rmoveto, or endchar, takes an additional argument — the width (as
355
        // described earlier), which may be expressed as zero or one numeric
356
        // argument.
357

            
358
        // Width: If the charstring has a width other than that of
359
        // defaultWidthX (see Technical Note #5176, “The Compact
360
        // Font Format Specification”), it must be specified as the first
361
        // number in the charstring, and encoded as the difference
362
        // from nominalWidthX.
363
        match op {
364
            VisitOp::HorizontalStem
365
            | VisitOp::HorizontalStemHintMask
366
            | VisitOp::VerticalStem
367
            | VisitOp::VerticalStemHintMask
368
            | VisitOp::VerticalMoveTo
369
            | VisitOp::HintMask
370
            | VisitOp::CounterMask
371
            | VisitOp::MoveTo
372
            | VisitOp::HorizontalMoveTo => {
373
                self.maybe_write_width()?;
374
                cff2::write_stack(&mut self.buffer, stack)?;
375
                Ok(U8::write(&mut self.buffer, op)?)
376
            }
377

            
378
            VisitOp::Return | VisitOp::Endchar => {
379
                // Should not be encountered in CFF2
380
                Err(CFFError::InvalidOperator.into())
381
            }
382

            
383
            VisitOp::LineTo
384
            | VisitOp::HorizontalLineTo
385
            | VisitOp::VerticalLineTo
386
            | VisitOp::CurveTo
387
            | VisitOp::CurveLine
388
            | VisitOp::LineCurve
389
            | VisitOp::VvCurveTo
390
            | VisitOp::HhCurveTo
391
            | VisitOp::VhCurveTo
392
            | VisitOp::HvCurveTo => {
393
                if !self.width_written {
394
                    return Err(CFFError::MissingMoveTo.into());
395
                }
396
                cff2::write_stack(&mut self.buffer, stack)?;
397
                Ok(U8::write(&mut self.buffer, op)?)
398
            }
399

            
400
            VisitOp::Hflex | VisitOp::Flex | VisitOp::Hflex1 | VisitOp::Flex1 => {
401
                cff2::write_stack(&mut self.buffer, stack)?;
402
                U8::write(&mut self.buffer, TWO_BYTE_OPERATOR_MARK)?;
403
                Ok(U8::write(&mut self.buffer, op)?)
404
            }
405
            VisitOp::VsIndex | VisitOp::Blend => {
406
                // Should not be encountered when converting CharStrings. CharString should
407
                // not be variable.
408
                Err(CFFError::InvalidOperator.into())
409
            }
410
        }
411
    }
412

            
413
    fn enter_subr(&mut self, index: SubroutineIndex) -> Result<(), CharStringConversionError> {
414
        // Emit callsubr op
415
        match index {
416
            SubroutineIndex::Local(index) => {
417
                cff2::write_stack_value(
418
                    cff2::StackValue::Int(index.try_into().map_err(ParseError::from)?),
419
                    &mut self.buffer,
420
                )?;
421
                Ok(U8::write(
422
                    &mut self.buffer,
423
                    operator::CALL_LOCAL_SUBROUTINE,
424
                )?)
425
            }
426
            SubroutineIndex::Global(index) => {
427
                cff2::write_stack_value(
428
                    cff2::StackValue::Int(index.try_into().map_err(ParseError::from)?),
429
                    &mut self.buffer,
430
                )?;
431
                Ok(U8::write(
432
                    &mut self.buffer,
433
                    operator::CALL_GLOBAL_SUBROUTINE,
434
                )?)
435
            }
436
        }
437
    }
438

            
439
    fn exit_subr(&mut self) -> Result<(), CharStringConversionError> {
440
        Ok(U8::write(&mut self.buffer, operator::RETURN)?)
441
    }
442

            
443
    fn hint_data(&mut self, _op: VisitOp, hints: &[u8]) -> Result<(), CharStringConversionError> {
444
        Ok(self.buffer.write_bytes(hints)?)
445
    }
446
}
447

            
448
impl CharStringVisitor<f32, CFFError> for UsedSubrs {
449
    fn enter_subr(&mut self, index: SubroutineIndex) -> Result<(), CFFError> {
450
        match index {
451
            SubroutineIndex::Local(index) => self.local_subr_used.insert(index),
452
            SubroutineIndex::Global(index) => self.global_subr_used.insert(index),
453
        };
454

            
455
        Ok(())
456
    }
457
}
458

            
459
impl CharStringVisitor<f32, CFFError> for DebugVisitor {
460
    fn visit(&mut self, op: VisitOp, stack: &ArgumentsStack<'_, f32>) -> Result<(), CFFError> {
461
        let mut operands = String::new();
462
        stack.all().iter().enumerate().for_each(|(i, operand)| {
463
            if i > 0 {
464
                operands.push(' ')
465
            }
466
            write!(operands, "{}", operand).unwrap()
467
        });
468
        println!("{op} {operands}");
469
        Ok(())
470
    }
471

            
472
    fn enter_subr(&mut self, index: SubroutineIndex) -> Result<(), CFFError> {
473
        match index {
474
            SubroutineIndex::Local(index) => println!("callsubr {index}"),
475
            SubroutineIndex::Global(index) => println!("callgsubr {index}"),
476
        }
477
        Ok(())
478
    }
479
}
480

            
481
#[derive(Copy, Clone)]
482
pub enum VisitOp {
483
    HorizontalStem,
484
    VerticalStem,
485
    VerticalMoveTo,
486
    LineTo,
487
    HorizontalLineTo,
488
    VerticalLineTo,
489
    CurveTo,
490
    Return,
491
    Endchar,
492
    VsIndex,
493
    Blend,
494
    HorizontalStemHintMask,
495
    HintMask,
496
    CounterMask,
497
    MoveTo,
498
    HorizontalMoveTo,
499
    VerticalStemHintMask,
500
    CurveLine,
501
    LineCurve,
502
    VvCurveTo,
503
    HhCurveTo,
504
    VhCurveTo,
505
    HvCurveTo,
506
    Hflex,
507
    Flex,
508
    Hflex1,
509
    Flex1,
510
}
511

            
512
impl TryFrom<u8> for VisitOp {
513
    type Error = ();
514

            
515
    fn try_from(value: u8) -> Result<Self, Self::Error> {
516
        match value {
517
            operator::HORIZONTAL_STEM => Ok(VisitOp::HorizontalStem),
518
            operator::VERTICAL_STEM => Ok(VisitOp::VerticalStem),
519
            operator::VERTICAL_MOVE_TO => Ok(VisitOp::VerticalMoveTo),
520
            operator::LINE_TO => Ok(VisitOp::LineTo),
521
            operator::HORIZONTAL_LINE_TO => Ok(VisitOp::HorizontalLineTo),
522
            operator::VERTICAL_LINE_TO => Ok(VisitOp::VerticalLineTo),
523
            operator::CURVE_TO => Ok(VisitOp::CurveTo),
524
            // operator::CALL_LOCAL_SUBROUTINE not yielded as VisitOp
525
            operator::RETURN => Ok(VisitOp::Return),
526
            operator::ENDCHAR => Ok(VisitOp::Endchar),
527
            operator::VS_INDEX => Ok(VisitOp::VsIndex),
528
            operator::BLEND => Ok(VisitOp::Blend),
529
            operator::HORIZONTAL_STEM_HINT_MASK => Ok(VisitOp::HorizontalStemHintMask),
530
            operator::HINT_MASK => Ok(VisitOp::HintMask),
531
            operator::COUNTER_MASK => Ok(VisitOp::CounterMask),
532
            operator::MOVE_TO => Ok(VisitOp::MoveTo),
533
            operator::HORIZONTAL_MOVE_TO => Ok(VisitOp::HorizontalMoveTo),
534
            operator::VERTICAL_STEM_HINT_MASK => Ok(VisitOp::VerticalStemHintMask),
535
            operator::CURVE_LINE => Ok(VisitOp::CurveLine),
536
            operator::LINE_CURVE => Ok(VisitOp::LineCurve),
537
            operator::VV_CURVE_TO => Ok(VisitOp::VvCurveTo),
538
            operator::HH_CURVE_TO => Ok(VisitOp::HhCurveTo),
539
            // operator::SHORT_INT not yielded as VisitOp
540
            // operator::CALL_GLOBAL_SUBROUTINE not yielded as VisitOp
541
            operator::VH_CURVE_TO => Ok(VisitOp::VhCurveTo),
542
            operator::HV_CURVE_TO => Ok(VisitOp::HvCurveTo),
543
            // Flex operators are two-byte ops. This assumes the first byte has already been read
544
            operator::HFLEX => Ok(VisitOp::Hflex),
545
            operator::FLEX => Ok(VisitOp::Flex),
546
            operator::HFLEX1 => Ok(VisitOp::Hflex1),
547
            operator::FLEX1 => Ok(VisitOp::Flex1),
548
            // operator::FIXED_16_16 not yielded as VisitOp
549
            _ => Err(()),
550
        }
551
    }
552
}
553

            
554
impl From<VisitOp> for u8 {
555
    fn from(op: VisitOp) -> Self {
556
        match op {
557
            VisitOp::HorizontalStem => operator::HORIZONTAL_STEM,
558
            VisitOp::VerticalStem => operator::VERTICAL_STEM,
559
            VisitOp::VerticalMoveTo => operator::VERTICAL_MOVE_TO,
560
            VisitOp::LineTo => operator::LINE_TO,
561
            VisitOp::HorizontalLineTo => operator::HORIZONTAL_LINE_TO,
562
            VisitOp::VerticalLineTo => operator::VERTICAL_LINE_TO,
563
            VisitOp::CurveTo => operator::CURVE_TO,
564
            VisitOp::Return => operator::RETURN,
565
            VisitOp::Endchar => operator::ENDCHAR,
566
            VisitOp::VsIndex => operator::VS_INDEX,
567
            VisitOp::Blend => operator::BLEND,
568
            VisitOp::HorizontalStemHintMask => operator::HORIZONTAL_STEM_HINT_MASK,
569
            VisitOp::HintMask => operator::HINT_MASK,
570
            VisitOp::CounterMask => operator::COUNTER_MASK,
571
            VisitOp::MoveTo => operator::MOVE_TO,
572
            VisitOp::HorizontalMoveTo => operator::HORIZONTAL_MOVE_TO,
573
            VisitOp::VerticalStemHintMask => operator::VERTICAL_STEM_HINT_MASK,
574
            VisitOp::CurveLine => operator::CURVE_LINE,
575
            VisitOp::LineCurve => operator::LINE_CURVE,
576
            VisitOp::VvCurveTo => operator::VV_CURVE_TO,
577
            VisitOp::HhCurveTo => operator::HH_CURVE_TO,
578
            VisitOp::VhCurveTo => operator::VH_CURVE_TO,
579
            VisitOp::HvCurveTo => operator::HV_CURVE_TO,
580
            // Flex operators are two-byte ops. This returns the second byte
581
            VisitOp::Hflex => operator::HFLEX,
582
            VisitOp::Flex => operator::FLEX,
583
            VisitOp::Hflex1 => operator::HFLEX1,
584
            VisitOp::Flex1 => operator::FLEX1,
585
        }
586
    }
587
}
588

            
589
impl fmt::Display for VisitOp {
590
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
591
        match self {
592
            VisitOp::HorizontalStem => f.write_str("hstem"),
593
            VisitOp::VerticalStem => f.write_str("vstem"),
594
            VisitOp::VerticalMoveTo => f.write_str("vmoveto"),
595
            VisitOp::LineTo => f.write_str("rlineto"),
596
            VisitOp::HorizontalLineTo => f.write_str("hlineto"),
597
            VisitOp::VerticalLineTo => f.write_str("vlineto"),
598
            VisitOp::CurveTo => f.write_str("rrcurveto"),
599
            VisitOp::Return => f.write_str("return"),
600
            VisitOp::Endchar => f.write_str("endchar"),
601
            VisitOp::VsIndex => f.write_str("vsindex"),
602
            VisitOp::Blend => f.write_str("blend"),
603
            VisitOp::HorizontalStemHintMask => f.write_str("hstemhm"),
604
            VisitOp::HintMask => f.write_str("hintmask"),
605
            VisitOp::CounterMask => f.write_str("cntrmask"),
606
            VisitOp::MoveTo => f.write_str("rmoveto"),
607
            VisitOp::HorizontalMoveTo => f.write_str("hmoveto"),
608
            VisitOp::VerticalStemHintMask => f.write_str("vstemhm"),
609
            VisitOp::CurveLine => f.write_str("rcurveline"),
610
            VisitOp::LineCurve => f.write_str("rlinecurve"),
611
            VisitOp::VvCurveTo => f.write_str("vvcurveto"),
612
            VisitOp::HhCurveTo => f.write_str("hhcurveto"),
613
            VisitOp::VhCurveTo => f.write_str("vhcurveto"),
614
            VisitOp::HvCurveTo => f.write_str("hvcurveto"),
615
            VisitOp::Hflex => f.write_str("hflex"),
616
            VisitOp::Flex => f.write_str("flex"),
617
            VisitOp::Hflex1 => f.write_str("hflex1"),
618
            VisitOp::Flex1 => f.write_str("flex1"),
619
        }
620
    }
621
}
622

            
623
impl<'a, 'data> CharStringVisitorContext<'a, 'data> {
624
    /// Construct a new context for traversing a CharString.
625
    ///
626
    /// Used with a `CharStringVisitor` impl supplied to `visit` to traverse the CharString.
627
    pub fn new(
628
        glyph_id: GlyphId,
629
        char_strings_index: &'a MaybeOwnedIndex<'data>,
630
        local_subr_index: Option<&'a MaybeOwnedIndex<'data>>,
631
        global_subr_index: &'a MaybeOwnedIndex<'data>,
632
        variable: Option<VariableCharStringVisitorContext<'a, 'data>>,
633
    ) -> CharStringVisitorContext<'a, 'data> {
634
        CharStringVisitorContext {
635
            glyph_id,
636
            char_strings_index,
637
            local_subr_index,
638
            global_subr_index,
639
            variable,
640
            width_parsed: false,
641
            stems_len: 0,
642
            has_endchar: false,
643
            has_seac: false,
644
            seen_blend: false,
645
            vsindex: None,
646
            scalars: None,
647
        }
648
    }
649

            
650
    /// Visit the operators of the CharString with a `CharStringVisitor` implementation.
651
    pub fn visit<S, V, E>(
652
        &mut self,
653
        font: CFFFont<'a, 'data>,
654
        stack: &mut ArgumentsStack<'_, S>,
655
        visitor: &mut V,
656
    ) -> Result<(), E>
657
    where
658
        V: CharStringVisitor<S, E>,
659
        S: BlendOperand,
660
        E: std::error::Error + From<CFFError> + From<ParseError>,
661
    {
662
        let char_string = self
663
            .char_strings_index
664
            .read_object(usize::from(self.glyph_id))
665
            .ok_or(CFFError::ParseError(ParseError::BadIndex))?;
666
        self.visit_impl(font, char_string, 0, stack, visitor)
667
    }
668

            
669
    fn visit_impl<S, V, E>(
670
        &mut self,
671
        font: CFFFont<'a, 'data>,
672
        char_string: &[u8],
673
        depth: u8,
674
        stack: &mut ArgumentsStack<'_, S>,
675
        visitor: &mut V,
676
    ) -> Result<(), E>
677
    where
678
        V: CharStringVisitor<S, E>,
679
        S: BlendOperand,
680
        E: std::error::Error + From<CFFError> + From<ParseError>,
681
    {
682
        let mut s = ReadScope::new(char_string).ctxt();
683
        while s.bytes_available() {
684
            let op = s.read::<U8>()?;
685
            match op {
686
                0 | 2 | 9 | 13 | 17 => {
687
                    // Reserved.
688
                    return Err(CFFError::InvalidOperator.into());
689
                }
690
                operator::HORIZONTAL_STEM
691
                | operator::VERTICAL_STEM
692
                | operator::HORIZONTAL_STEM_HINT_MASK
693
                | operator::VERTICAL_STEM_HINT_MASK => {
694
                    // If the stack length is uneven, then the first value is a `width`.
695
                    let len = if stack.len().is_odd() && !self.width_parsed {
696
                        self.width_parsed = true;
697
                        stack.len() - 1
698
                    } else {
699
                        stack.len()
700
                    };
701

            
702
                    self.stems_len += len as u32 >> 1;
703

            
704
                    // We are ignoring the hint operators.
705
                    visitor.visit(op.try_into().unwrap(), stack)?;
706
                    stack.clear();
707
                }
708
                operator::VERTICAL_MOVE_TO => {
709
                    let offset = self.handle_width(stack.len() == 2 && !self.width_parsed);
710
                    stack.offset(offset, |stack| visitor.visit(op.try_into().unwrap(), stack))?;
711
                    stack.clear();
712
                }
713
                operator::LINE_TO
714
                | operator::HORIZONTAL_LINE_TO
715
                | operator::VERTICAL_LINE_TO
716
                | operator::CURVE_TO => {
717
                    visitor.visit(op.try_into().unwrap(), stack)?;
718
                    stack.clear();
719
                }
720
                operator::CALL_LOCAL_SUBROUTINE => {
721
                    if stack.is_empty() {
722
                        return Err(CFFError::InvalidArgumentsStackLength.into());
723
                    }
724

            
725
                    if depth == STACK_LIMIT {
726
                        return Err(CFFError::NestingLimitReached.into());
727
                    }
728

            
729
                    // Parse and remember the local subroutine for the current glyph.
730
                    // Since it's a pretty complex task, we're doing it only when
731
                    // a local subroutine is actually requested by the glyphs charstring.
732
                    if self.local_subr_index.is_none() {
733
                        // Only match on this as the other variants were populated at the beginning of the function
734
                        if let CFFFont::CFF(super::Font {
735
                            data: CFFVariant::CID(ref cid),
736
                            ..
737
                        }) = font
738
                        {
739
                            // Choose the local subroutine index corresponding to the glyph/CID
740
                            self.local_subr_index = cid
741
                                .fd_select
742
                                .font_dict_index(self.glyph_id)
743
                                .and_then(|font_dict_index| {
744
                                    match cid.local_subr_indices.get(usize::from(font_dict_index)) {
745
                                        Some(Some(index)) => Some(index),
746
                                        _ => None,
747
                                    }
748
                                });
749
                        }
750
                    }
751

            
752
                    if let Some(local_subrs) = self.local_subr_index {
753
                        let subroutine_bias = calc_subroutine_bias(local_subrs.len());
754
                        let index = conv_subroutine_index(stack.pop(), subroutine_bias)?;
755
                        let char_string = local_subrs
756
                            .read_object(index)
757
                            .ok_or(CFFError::InvalidSubroutineIndex)?;
758
                        visitor.enter_subr(SubroutineIndex::Local(index))?;
759
                        self.visit_impl(font, char_string, depth + 1, stack, visitor)?;
760
                        visitor.exit_subr()?;
761
                    } else {
762
                        return Err(CFFError::NoLocalSubroutines.into());
763
                    }
764

            
765
                    if self.has_endchar && !self.has_seac {
766
                        if s.bytes_available() {
767
                            return Err(CFFError::DataAfterEndChar.into());
768
                        }
769

            
770
                        break;
771
                    }
772
                }
773
                operator::RETURN => {
774
                    match font {
775
                        CFFFont::CFF(_) => {
776
                            visitor.visit(op.try_into().unwrap(), stack)?;
777
                            break;
778
                        }
779
                        CFFFont::CFF2(_) => {
780
                            // Removed in CFF2
781
                            return Err(CFFError::InvalidOperator.into());
782
                        }
783
                    }
784
                }
785
                TWO_BYTE_OPERATOR_MARK => {
786
                    // flex
787
                    let op2 = s.read::<U8>()?;
788
                    match op2 {
789
                        operator::HFLEX | operator::FLEX | operator::HFLEX1 | operator::FLEX1 => {
790
                            visitor.visit(op2.try_into().unwrap(), stack)?;
791
                            stack.clear()
792
                        }
793
                        _ => return Err(CFFError::UnsupportedOperator.into()),
794
                    }
795
                }
796
                operator::ENDCHAR => {
797
                    match font {
798
                        CFFFont::CFF(cff) => {
799
                            if stack.len() == 4 || (!self.width_parsed && stack.len() == 5) {
800
                                // Process 'seac'.
801
                                let accent_char = stack
802
                                    .pop()
803
                                    .try_as_u8()
804
                                    .and_then(|code| cff.seac_code_to_glyph_id(code))
805
                                    .ok_or(CFFError::InvalidSeacCode)?;
806
                                let base_char = stack
807
                                    .pop()
808
                                    .try_as_u8()
809
                                    .and_then(|code| cff.seac_code_to_glyph_id(code))
810
                                    .ok_or(CFFError::InvalidSeacCode)?;
811
                                let dy = stack.pop();
812
                                let dx = stack.pop();
813

            
814
                                if !self.width_parsed {
815
                                    stack.pop();
816
                                    self.width_parsed = true;
817
                                }
818

            
819
                                self.has_seac = true;
820

            
821
                                let base_char_string = self
822
                                    .char_strings_index
823
                                    .read_object(usize::from(base_char))
824
                                    .ok_or(CFFError::InvalidSeacCode)?;
825
                                visitor.enter_seac(SeacChar::Base, dx, dy)?;
826
                                self.visit_impl(font, base_char_string, depth + 1, stack, visitor)?;
827
                                visitor.exit_seac(SeacChar::Base)?;
828

            
829
                                let accent_char_string = self
830
                                    .char_strings_index
831
                                    .read_object(usize::from(accent_char))
832
                                    .ok_or(CFFError::InvalidSeacCode)?;
833
                                visitor.enter_seac(SeacChar::Accent, dx, dy)?;
834
                                self.visit_impl(
835
                                    font,
836
                                    accent_char_string,
837
                                    depth + 1,
838
                                    stack,
839
                                    visitor,
840
                                )?;
841
                                visitor.exit_seac(SeacChar::Accent)?;
842
                            } else if stack.len() == 1 && !self.width_parsed {
843
                                stack.pop();
844
                                self.width_parsed = true;
845
                            }
846

            
847
                            if s.bytes_available() {
848
                                return Err(CFFError::DataAfterEndChar.into());
849
                            }
850

            
851
                            self.has_endchar = true;
852
                            visitor.visit(op.try_into().unwrap(), stack)?;
853
                            break;
854
                        }
855
                        CFFFont::CFF2(_) => {
856
                            // Removed in CFF2
857
                            return Err(CFFError::InvalidOperator.into());
858
                        }
859
                    }
860
                }
861
                operator::VS_INDEX => {
862
                    match font {
863
                        CFFFont::CFF(_) => {
864
                            // Added in CFF2
865
                            return Err(CFFError::InvalidOperator.into());
866
                        }
867
                        CFFFont::CFF2(_) => {
868
                            // When used, vsindex must precede the first blend operator,
869
                            // and may occur only once in the CharString.
870
                            if self.vsindex.is_some() {
871
                                return Err(CFFError::DuplicateVsIndex.into());
872
                            } else if self.seen_blend {
873
                                return Err(CFFError::VsIndexAfterBlend.into());
874
                            } else {
875
                                if stack.len() != 1 {
876
                                    return Err(CFFError::InvalidArgumentsStackLength.into());
877
                                }
878
                                visitor.visit(op.try_into().unwrap(), stack)?;
879
                                let item_variation_data_index = stack
880
                                    .pop()
881
                                    .try_as_u16()
882
                                    .ok_or(CFFError::InvalidArgumentsStackLength)?;
883
                                self.vsindex = Some(item_variation_data_index);
884
                            }
885
                        }
886
                    }
887
                }
888
                operator::BLEND => {
889
                    match font {
890
                        CFFFont::CFF(_) => {
891
                            // Added in CFF2
892
                            return Err(CFFError::InvalidOperator.into());
893
                        }
894
                        CFFFont::CFF2(font) => {
895
                            let Some(var) = self.variable else {
896
                                return Err(CFFError::MissingVariationStore.into());
897
                            };
898

            
899
                            if !stack.is_empty() {
900
                                visitor.visit(op.try_into().unwrap(), stack)?;
901

            
902
                                // Lookup the ItemVariationStore data to get the variation regions
903
                                let scalars = match &self.scalars {
904
                                    Some(scalars) => scalars,
905
                                    None => {
906
                                        let vs_index =
907
                                            self.vsindex.map(Ok).unwrap_or_else(|| {
908
                                                font.private_dict
909
                                                    .get_i32(Operator::VSIndex)
910
                                                    .ok_or(ParseError::BadValue)?
911
                                                    .and_then(|val| {
912
                                                        u16::try_from(val).map_err(ParseError::from)
913
                                                    })
914
                                            })?;
915

            
916
                                        self.scalars = Some(cff2::scalars(
917
                                            vs_index,
918
                                            var.vstore,
919
                                            var.instance,
920
                                        )?);
921
                                        self.scalars.as_ref().unwrap()
922
                                    }
923
                                };
924

            
925
                                cff2::blend(scalars, stack)?;
926
                            } else {
927
                                return Err(CFFError::InvalidArgumentsStackLength.into());
928
                            }
929
                        }
930
                    }
931
                }
932
                operator::HINT_MASK | operator::COUNTER_MASK => {
933
                    let mut len = stack.len();
934
                    let visit_op = op.try_into().unwrap();
935
                    visitor.visit(visit_op, stack)?;
936
                    stack.clear();
937

            
938
                    // If the stack length is uneven, then the first value is a `width`.
939
                    if len.is_odd() && !self.width_parsed {
940
                        len -= 1;
941
                        self.width_parsed = true;
942
                    }
943

            
944
                    self.stems_len += len as u32 >> 1;
945

            
946
                    // Yield the hints
947
                    let hints = s
948
                        .read_slice(
949
                            usize::try_from((self.stems_len + 7) >> 3)
950
                                .map_err(|_| ParseError::BadValue)?,
951
                        )
952
                        .map_err(|_| ParseError::BadOffset)?;
953
                    visitor.hint_data(visit_op, hints)?;
954
                }
955
                operator::MOVE_TO => {
956
                    let offset = self.handle_width(stack.len() == 3 && !self.width_parsed);
957
                    stack.offset(offset, |stack| visitor.visit(op.try_into().unwrap(), stack))?;
958
                    stack.clear();
959
                }
960
                operator::HORIZONTAL_MOVE_TO => {
961
                    let offset = self.handle_width(stack.len() == 2 && !self.width_parsed);
962
                    stack.offset(offset, |stack| visitor.visit(op.try_into().unwrap(), stack))?;
963
                    stack.clear();
964
                }
965
                operator::CURVE_LINE
966
                | operator::LINE_CURVE
967
                | operator::VV_CURVE_TO
968
                | operator::HH_CURVE_TO
969
                | operator::VH_CURVE_TO
970
                | operator::HV_CURVE_TO => {
971
                    visitor.visit(op.try_into().unwrap(), stack)?;
972
                    stack.clear();
973
                }
974
                operator::SHORT_INT => {
975
                    let n = s.read::<I16Be>()?;
976
                    stack.push(S::from(n))?;
977
                }
978
                operator::CALL_GLOBAL_SUBROUTINE => {
979
                    if stack.is_empty() {
980
                        return Err(CFFError::InvalidArgumentsStackLength.into());
981
                    }
982

            
983
                    if depth == STACK_LIMIT {
984
                        return Err(CFFError::NestingLimitReached.into());
985
                    }
986

            
987
                    let subroutine_bias = calc_subroutine_bias(self.global_subr_index.len());
988
                    let index = conv_subroutine_index(stack.pop(), subroutine_bias)?;
989
                    let char_string = self
990
                        .global_subr_index
991
                        .read_object(index)
992
                        .ok_or(CFFError::InvalidSubroutineIndex)?;
993
                    visitor.enter_subr(SubroutineIndex::Global(index))?;
994
                    self.visit_impl(font, char_string, depth + 1, stack, visitor)?;
995
                    visitor.exit_subr()?;
996

            
997
                    if self.has_endchar && !self.has_seac {
998
                        if s.bytes_available() {
999
                            return Err(CFFError::DataAfterEndChar.into());
                        }
                        break;
                    }
                }
                32..=246 => {
                    stack.push(parse_int1(op)?)?;
                }
                247..=250 => {
                    stack.push(parse_int2(op, &mut s)?)?;
                }
                251..=254 => {
                    stack.push(parse_int3(op, &mut s)?)?;
                }
                operator::FIXED_16_16 => {
                    stack.push(parse_fixed(&mut s)?)?;
                }
            }
        }
        Ok(())
    }
    fn handle_width(&mut self, cond: bool) -> usize {
        if cond {
            self.width_parsed = true;
            1
        } else {
            0
        }
    }
}
// CharString number parsing functions
fn parse_int1<S: BlendOperand>(op: u8) -> Result<S, CFFError> {
    let n = i16::from(op) - 139;
    Ok(S::from(n))
}
fn parse_int2<S: BlendOperand>(op: u8, s: &mut ReadCtxt<'_>) -> Result<S, CFFError> {
    let b1 = s.read::<U8>()?;
    let n = (i16::from(op) - 247) * 256 + i16::from(b1) + 108;
    debug_assert!((108..=1131).contains(&n));
    Ok(S::from(n))
}
fn parse_int3<S: BlendOperand>(op: u8, s: &mut ReadCtxt<'_>) -> Result<S, CFFError> {
    let b1 = s.read::<U8>()?;
    let n = -(i16::from(op) - 251) * 256 - i16::from(b1) - 108;
    debug_assert!((-1131..=-108).contains(&n));
    Ok(S::from(n))
}
fn parse_fixed<S: BlendOperand>(s: &mut ReadCtxt<'_>) -> Result<S, CFFError> {
    let n = s.read::<Fixed>()?;
    Ok(S::from(n))
}
// Conversions from biased subr index operands to unbiased value
pub(crate) fn conv_subroutine_index<S: BlendOperand>(
    index: S,
    bias: u16,
) -> Result<usize, CFFError> {
    let index = index.try_as_i32().ok_or(CFFError::InvalidSubroutineIndex)?;
    conv_subroutine_index_impl(index, bias).ok_or(CFFError::InvalidSubroutineIndex)
}
pub(crate) fn conv_subroutine_index_impl(index: i32, bias: u16) -> Option<usize> {
    let bias = i32::from(bias);
    let index = index.checked_add(bias)?;
    usize::try_from(index).ok()
}
// Adobe Technical Note #5176, Chapter 16 "Local / Global Subrs INDEXes"
pub(crate) fn calc_subroutine_bias(len: usize) -> u16 {
    if len < 1240 {
        107
    } else if len < 33900 {
        1131
    } else {
        32768
    }
}
impl IsEven for usize {
    fn is_even(&self) -> bool {
        (*self) & 1 == 0
    }
    fn is_odd(&self) -> bool {
        !self.is_even()
    }
}
impl From<ParseError> for CFFError {
    fn from(error: ParseError) -> CFFError {
        CFFError::ParseError(error)
    }
}
impl fmt::Display for CFFError {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            CFFError::ParseError(parse_error) => {
                write!(f, "parse error: ")?;
                parse_error.fmt(f)
            }
            CFFError::InvalidOperator => write!(f, "an invalid operator occurred"),
            CFFError::UnsupportedOperator => write!(f, "an unsupported operator occurred"),
            CFFError::MissingEndChar => write!(f, "the 'endchar' operator is missing"),
            CFFError::DataAfterEndChar => write!(f, "unused data left after 'endchar' operator"),
            CFFError::NestingLimitReached => write!(f, "subroutines nesting limit reached"),
            CFFError::ArgumentsStackLimitReached => write!(f, "arguments stack limit reached"),
            CFFError::InvalidArgumentsStackLength => {
                write!(f, "an invalid amount of items are in an arguments stack")
            }
            CFFError::BboxOverflow => write!(f, "outline's bounding box is too large"),
            CFFError::MissingMoveTo => write!(f, "missing moveto operator"),
            CFFError::DuplicateVsIndex => write!(f, "duplicate vsindex operator"),
            CFFError::InvalidSubroutineIndex => write!(f, "an invalid subroutine index"),
            CFFError::NoLocalSubroutines => write!(f, "no local subroutines"),
            CFFError::InvalidSeacCode => write!(f, "invalid seac code"),
            CFFError::InvalidOperand => write!(f, "operand was out of range or invalid"),
            CFFError::InvalidFontIndex => write!(f, "invalid font index"),
            CFFError::VsIndexAfterBlend => write!(f, "vsindex operator encountered after blend"),
            CFFError::MissingVariationStore => write!(f, "missing variation store"),
        }
    }
}
impl std::error::Error for CFFError {}
/// Operators defined in Adobe Technical Note #5177, The Type  2 Charstring Format.
pub(crate) mod operator {
    pub const HORIZONTAL_STEM: u8 = 1;
    pub const VERTICAL_STEM: u8 = 3;
    pub const VERTICAL_MOVE_TO: u8 = 4;
    pub const LINE_TO: u8 = 5;
    pub const HORIZONTAL_LINE_TO: u8 = 6;
    pub const VERTICAL_LINE_TO: u8 = 7;
    pub const CURVE_TO: u8 = 8;
    pub const CALL_LOCAL_SUBROUTINE: u8 = 10;
    pub const RETURN: u8 = 11;
    pub const ENDCHAR: u8 = 14;
    pub const VS_INDEX: u8 = 15; // CFF2
    pub const BLEND: u8 = 16; // CFF2
    pub const HORIZONTAL_STEM_HINT_MASK: u8 = 18;
    pub const HINT_MASK: u8 = 19;
    pub const COUNTER_MASK: u8 = 20;
    pub const MOVE_TO: u8 = 21;
    pub const HORIZONTAL_MOVE_TO: u8 = 22;
    pub const VERTICAL_STEM_HINT_MASK: u8 = 23;
    pub const CURVE_LINE: u8 = 24;
    pub const LINE_CURVE: u8 = 25;
    pub const VV_CURVE_TO: u8 = 26;
    pub const HH_CURVE_TO: u8 = 27;
    pub const SHORT_INT: u8 = 28;
    pub const CALL_GLOBAL_SUBROUTINE: u8 = 29;
    pub const VH_CURVE_TO: u8 = 30;
    pub const HV_CURVE_TO: u8 = 31;
    pub const HFLEX: u8 = 34;
    pub const FLEX: u8 = 35;
    pub const HFLEX1: u8 = 36;
    pub const FLEX1: u8 = 37;
    pub const FIXED_16_16: u8 = 255;
}
#[cfg(test)]
mod tests {
    use crate::cff::cff2::{self, CFF2};
    use crate::error::ReadWriteError;
    use crate::tables::variable_fonts::avar::AvarTable;
    use crate::tables::variable_fonts::fvar::FvarTable;
    use crate::tables::{OpenTypeData, OpenTypeFont};
    use crate::tag;
    use crate::tests::read_fixture;
    use super::*;
    struct TraverseCharString;
    impl CharStringVisitor<f32, CFFError> for TraverseCharString {}
    #[test]
    fn traverse_cff2_charstring() -> Result<(), ReadWriteError> {
        let buffer = read_fixture("tests/fonts/opentype/cff2/SourceSansVariable-Roman.abc.otf");
        let otf = ReadScope::new(&buffer).read::<OpenTypeFont<'_>>().unwrap();
        let offset_table = match otf.data {
            OpenTypeData::Single(ttf) => ttf,
            OpenTypeData::Collection(_) => unreachable!(),
        };
        let cff_table_data = offset_table.read_table(&otf.scope, tag::CFF2)?.unwrap();
        let cff = cff_table_data
            .read::<CFF2<'_>>()
            .expect("error parsing CFF2 table");
        let fvar_data = offset_table.read_table(&otf.scope, tag::FVAR)?.unwrap();
        let fvar = fvar_data.read::<FvarTable<'_>>()?;
        let avar_data = offset_table.read_table(&otf.scope, tag::AVAR)?;
        let avar = avar_data
            .as_ref()
            .map(|avar_data| avar_data.read::<AvarTable<'_>>())
            .transpose()?;
        // Traverse a CharString
        let glyph_id = 1;
        let font_dict_index = cff
            .fd_select
            .map(|fd_select| fd_select.font_dict_index(glyph_id).unwrap())
            .unwrap_or(0);
        let font = &cff.fonts[usize::from(font_dict_index)];
        let user_tuple = [Fixed::from(650.0)];
        let instance = fvar
            .normalize(user_tuple.iter().copied(), avar.as_ref())
            .unwrap();
        let variable = VariableCharStringVisitorContext {
            vstore: cff.vstore.as_ref().unwrap(),
            instance: &instance,
        };
        let mut ctx = CharStringVisitorContext::new(
            1,
            &cff.char_strings_index,
            font.local_subr_index.as_ref(),
            &cff.global_subr_index,
            Some(variable),
        );
        let mut stack = ArgumentsStack {
            data: &mut [0.0; cff2::MAX_OPERANDS],
            len: 0,
            max_len: cff2::MAX_OPERANDS,
        };
        let res = ctx.visit(CFFFont::CFF2(font), &mut stack, &mut TraverseCharString);
        assert!(res.is_ok());
        Ok(())
    }
}