allsorts_subset_browser/cff/
charstring.rs

1//! CFF CharString (glyph) processing.
2
3use std::convert::{TryFrom, TryInto};
4use std::fmt::{self, Write};
5
6use rustc_hash::FxHashSet;
7
8pub use argstack::ArgumentsStack;
9
10use crate::binary::read::{ReadCtxt, ReadScope};
11use crate::binary::write::{WriteBinary, WriteBuffer, WriteContext};
12use crate::binary::{I16Be, U8};
13use crate::cff;
14use crate::cff::cff2::BlendOperand;
15use crate::error::{ParseError, WriteError};
16use crate::tables::variable_fonts::{ItemVariationStore, OwnedTuple};
17use crate::tables::Fixed;
18
19use super::{cff2, CFFError, CFFFont, CFFVariant, MaybeOwnedIndex, Operator};
20
21mod 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
26pub(crate) const STACK_LIMIT: u8 = 10;
27
28pub(crate) const TWO_BYTE_OPERATOR_MARK: u8 = 12;
29
30pub(crate) trait IsEven {
31    fn is_even(&self) -> bool;
32    fn is_odd(&self) -> bool;
33}
34
35/// Just like TryFrom<N>, but for numeric types not supported by the Rust's std.
36pub(crate) trait TryNumFrom<T>: Sized {
37    /// Casts between numeric types.
38    fn try_num_from(_: T) -> Option<Self>;
39}
40
41pub(crate) type GlyphId = u16;
42
43pub(crate) struct UsedSubrs {
44    pub(crate) global_subr_used: FxHashSet<usize>,
45    pub(crate) local_subr_used: FxHashSet<usize>,
46}
47
48/// Context for traversing a CFF CharString.
49pub struct CharStringVisitorContext<'a, 'data> {
50    glyph_id: GlyphId, // Required to parse local subroutine in CID fonts.
51    char_strings_index: &'a MaybeOwnedIndex<'data>,
52    local_subr_index: Option<&'a MaybeOwnedIndex<'data>>,
53    global_subr_index: &'a MaybeOwnedIndex<'data>,
54    // Required for variable fonts
55    variable: Option<VariableCharStringVisitorContext<'a, 'data>>,
56    width_parsed: bool,
57    stems_len: u32,
58    has_endchar: bool,
59    has_seac: bool,
60    seen_blend: bool,
61    vsindex: Option<u16>,
62    scalars: Option<Vec<Option<f32>>>,
63}
64
65/// Variable font data for a [CharStringVisitorContext]. Require if the CharString to be
66/// traversed is variable.
67#[derive(Copy, Clone)]
68pub struct VariableCharStringVisitorContext<'a, 'data> {
69    pub vstore: &'a ItemVariationStore<'data>,
70    pub instance: &'a OwnedTuple,
71}
72
73/// A local or global subroutine index.
74#[derive(Debug, Copy, Clone, Eq, PartialEq)]
75pub enum SubroutineIndex {
76    Local(usize),
77    Global(usize),
78}
79
80/// Flag indicating the component of an accented character.
81#[derive(Debug, Copy, Clone, Eq, PartialEq)]
82pub enum SeacChar {
83    Base,
84    Accent,
85}
86
87/// A debug implementation of [CharStringVisitor] that just prints the operators and
88/// their operands.
89///
90/// ### Example
91///
92/// ```
93/// use allsorts::binary::read::ReadScope;
94/// use allsorts::cff::charstring::{ArgumentsStack, CharStringVisitorContext, DebugVisitor};
95/// use allsorts::cff::CFFError;
96/// use allsorts::cff::{self, CFFFont, CFFVariant, CFF};
97/// use allsorts::font::MatchingPresentation;
98/// use allsorts::font_data::FontData;
99/// use allsorts::tables::{OpenTypeData, OpenTypeFont};
100/// use allsorts::{tag, Font};
101///
102/// fn main() -> Result<(), CFFError> {
103///     // Read the font
104///     let buffer = std::fs::read("tests/fonts/opentype/Klei.otf").unwrap();
105///     let otf = ReadScope::new(&buffer).read::<OpenTypeFont<'_>>()?;
106///     let offset_table = match otf.data {
107///         OpenTypeData::Single(ttf) => ttf,
108///         OpenTypeData::Collection(_) => unreachable!(),
109///     };
110///
111///     let cff_table_data = offset_table
112///         .read_table(&otf.scope, tag::CFF)?
113///         .expect("missing CFF table");
114///     let cff = cff_table_data.read::<CFF<'_>>()?;
115///
116///     // Glyph to visit
117///     let glyph_id = 741; // U+2116 NUMERO SIGN
118///
119///     // Set up CharString visitor
120///     let font = &cff.fonts[0];
121///     let local_subrs = match &font.data {
122///         CFFVariant::CID(_) => None, // Will be resolved on request.
123///         CFFVariant::Type1(type1) => type1.local_subr_index.as_ref(),
124///     };
125///
126///     let mut visitor = DebugVisitor;
127///     let variable = None;
128///     let mut ctx = CharStringVisitorContext::new(
129///         glyph_id,
130///         &font.char_strings_index,
131///         local_subrs,
132///         &cff.global_subr_index,
133///         variable,
134///     );
135///     let mut stack = ArgumentsStack {
136///         data: &mut [0.0; cff::MAX_OPERANDS],
137///         len: 0,
138///         max_len: cff::MAX_OPERANDS,
139///     };
140///     ctx.visit(CFFFont::CFF(font), &mut stack, &mut visitor)?;
141///     Ok(())
142/// }
143/// ```
144pub struct DebugVisitor;
145
146/// Trait for types that can be used to traverse a CFF CharString.
147///
148/// Used in conjunction with [CharStringVisitorContext]. The visitor will receive
149/// a `visit` call for each operator with its operands. All methods are optional
150/// as they have default no-op implementations.
151pub trait CharStringVisitor<T: fmt::Debug, E: std::error::Error> {
152    /// Called for each operator in the CharString, except for `callsubr` and `callgsubr`
153    /// — these are handled by `enter/exit_subr`.
154    fn visit(&mut self, _op: VisitOp, _stack: &ArgumentsStack<'_, T>) -> Result<(), E> {
155        Ok(())
156    }
157
158    /// Called prior to entering a subroutine.
159    ///
160    /// The index argument indicates the type of subroutine (local/global) and holds its index.
161    fn enter_subr(&mut self, _index: SubroutineIndex) -> Result<(), E> {
162        Ok(())
163    }
164
165    /// Called when returning from a subroutine.
166    fn exit_subr(&mut self) -> Result<(), E> {
167        Ok(())
168    }
169
170    /// Called before entering a component of an accented character.
171    ///
172    /// The `seac` argument indicates if it's the base or accent character. `dx` and `dy`
173    /// are the x and y position of the accent character. The same values will be supplied
174    /// for both the base and accent.
175    fn enter_seac(&mut self, _seac: SeacChar, _dx: T, _dy: T) -> Result<(), E> {
176        Ok(())
177    }
178
179    /// Called when returning from a component of an accented character.
180    ///
181    /// `seac` indicates whether it's the base or accent.
182    fn exit_seac(&mut self, _seac: SeacChar) -> Result<(), E> {
183        Ok(())
184    }
185
186    /// Called with the hint data that follows the `hintmask` and `cntrmask` operators.
187    ///
188    /// This function will be called after a `visit` invocation for the operators.
189    fn hint_data(&mut self, _op: VisitOp, _hints: &[u8]) -> Result<(), E> {
190        Ok(())
191    }
192}
193
194pub(crate) fn char_string_used_subrs<'a, 'data>(
195    font: CFFFont<'a, 'data>,
196    char_strings_index: &'a MaybeOwnedIndex<'data>,
197    global_subr_index: &'a MaybeOwnedIndex<'data>,
198    glyph_id: GlyphId,
199) -> Result<UsedSubrs, CFFError> {
200    let (local_subrs, max_len) = match font {
201        CFFFont::CFF(font) => match &font.data {
202            CFFVariant::CID(_) => (None, cff::MAX_OPERANDS), // local subrs will be resolved on request.
203            CFFVariant::Type1(type1) => (type1.local_subr_index.as_ref(), cff::MAX_OPERANDS),
204        },
205        CFFFont::CFF2(cff2) => (cff2.local_subr_index.as_ref(), cff2::MAX_OPERANDS),
206    };
207
208    let mut ctx = CharStringVisitorContext::new(
209        glyph_id,
210        char_strings_index,
211        local_subrs,
212        global_subr_index,
213        // This function should not be called on variable CFF2 fonts. If the CFF2 font is variable
214        // it should be instanced first.
215        None,
216    );
217
218    let mut used_subrs = UsedSubrs {
219        global_subr_used: FxHashSet::default(),
220        local_subr_used: FxHashSet::default(),
221    };
222
223    let mut stack = ArgumentsStack {
224        // We use CFF2 max operands as it is the bigger of the two, and it has to be a const value
225        // at compile time to init the array.
226        data: &mut [0.0; cff2::MAX_OPERANDS],
227        len: 0,
228        max_len,
229    };
230    ctx.visit(font, &mut stack, &mut used_subrs)?;
231
232    if matches!(font, CFFFont::CFF(_)) && !ctx.has_endchar {
233        return Err(CFFError::MissingEndChar);
234    }
235
236    Ok(used_subrs)
237}
238
239pub(crate) fn convert_cff2_to_cff<'a, 'data>(
240    font: CFFFont<'a, 'data>,
241    char_strings_index: &'a MaybeOwnedIndex<'data>,
242    global_subr_index: &'a MaybeOwnedIndex<'data>,
243    glyph_id: GlyphId,
244    width: u16,
245    default_width: u16,
246    nominal_width: u16,
247) -> Result<Vec<u8>, CharStringConversionError> {
248    let (local_subrs, max_len) = match font {
249        CFFFont::CFF(font) => match &font.data {
250            CFFVariant::CID(_) => (None, cff::MAX_OPERANDS), // local subrs will be resolved on request.
251            CFFVariant::Type1(type1) => (type1.local_subr_index.as_ref(), cff::MAX_OPERANDS),
252        },
253        CFFFont::CFF2(cff2) => (cff2.local_subr_index.as_ref(), cff2::MAX_OPERANDS),
254    };
255
256    let mut ctx = CharStringVisitorContext::new(
257        glyph_id,
258        char_strings_index,
259        local_subrs,
260        global_subr_index,
261        None,
262    );
263
264    // If the width is not equal to defaultWidthX then the width is stored as the difference from
265    // nominalWidthX.
266    let char_string_width =
267        i16::try_from(i32::from(width) - i32::from(nominal_width)).map_err(ParseError::from)?;
268
269    let mut converter = CharStringConverter {
270        buffer: WriteBuffer::new(),
271        width: char_string_width,
272        // If the width is equal to the default width then it does not need to be written,
273        // so we mark it as already written to inhibit the converter from outputting it.
274        width_written: width == default_width,
275    };
276
277    let mut stack = ArgumentsStack {
278        // We use CFF2 max operands as it is the bigger of the two, and it has to be a const value
279        // at compile time to init the array.
280        data: &mut [cff2::StackValue::Int(0); cff2::MAX_OPERANDS],
281        len: 0,
282        max_len,
283    };
284    ctx.visit(font, &mut stack, &mut converter)?;
285
286    // CFF CharStrings must end with an endchar operator
287    //
288    // > A character that does not have a path (e.g. a space character) may
289    // > consist of an endchar operator preceded only by a width value.
290    // > Although the width must be specified in the font, it may be specified as
291    // > the defaultWidthX in the CFF data, in which case it should not be
292    // > specified in the charstring. Also, it may appear in the charstring as the
293    // > difference from nominalWidthX. Thus the smallest legal charstring
294    // > consists of a single endchar operator.
295    converter.maybe_write_width()?;
296    U8::write(&mut converter.buffer, operator::ENDCHAR)?;
297
298    Ok(converter.buffer.into_inner())
299}
300
301struct CharStringConverter {
302    buffer: WriteBuffer,
303    width_written: bool,
304    width: i16,
305}
306
307impl CharStringConverter {
308    fn maybe_write_width(&mut self) -> Result<(), WriteError> {
309        if !self.width_written {
310            cff2::write_stack_value(cff2::StackValue::Int(self.width), &mut self.buffer)?;
311            self.width_written = true;
312        }
313        Ok(())
314    }
315}
316
317#[derive(Debug)]
318pub(crate) enum CharStringConversionError {
319    Write(WriteError),
320    Cff(CFFError),
321}
322
323impl From<CFFError> for CharStringConversionError {
324    fn from(err: CFFError) -> Self {
325        CharStringConversionError::Cff(err)
326    }
327}
328
329impl From<ParseError> for CharStringConversionError {
330    fn from(err: ParseError) -> Self {
331        CharStringConversionError::Cff(CFFError::ParseError(err))
332    }
333}
334
335impl From<WriteError> for CharStringConversionError {
336    fn from(err: WriteError) -> Self {
337        CharStringConversionError::Write(err)
338    }
339}
340
341impl fmt::Display for CharStringConversionError {
342    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
343        match self {
344            CharStringConversionError::Write(err) => {
345                write!(f, "unable to convert charstring: {err}")
346            }
347            CharStringConversionError::Cff(err) => write!(f, "unable to convert charstring: {err}"),
348        }
349    }
350}
351
352impl std::error::Error for CharStringConversionError {}
353
354impl CharStringVisitor<cff2::StackValue, CharStringConversionError> for CharStringConverter {
355    fn visit(
356        &mut self,
357        op: VisitOp,
358        stack: &ArgumentsStack<'_, cff2::StackValue>,
359    ) -> Result<(), CharStringConversionError> {
360        // The first stack-clearing operator, which must be one of hstem,
361        // hstemhm, vstem, vstemhm, cntrmask, hintmask, hmoveto, vmoveto,
362        // rmoveto, or endchar, takes an additional argument — the width (as
363        // described earlier), which may be expressed as zero or one numeric
364        // argument.
365
366        // Width: If the charstring has a width other than that of
367        // defaultWidthX (see Technical Note #5176, “The Compact
368        // Font Format Specification”), it must be specified as the first
369        // number in the charstring, and encoded as the difference
370        // from nominalWidthX.
371        match op {
372            VisitOp::HorizontalStem
373            | VisitOp::HorizontalStemHintMask
374            | VisitOp::VerticalStem
375            | VisitOp::VerticalStemHintMask
376            | VisitOp::VerticalMoveTo
377            | VisitOp::HintMask
378            | VisitOp::CounterMask
379            | VisitOp::MoveTo
380            | VisitOp::HorizontalMoveTo => {
381                self.maybe_write_width()?;
382                cff2::write_stack(&mut self.buffer, stack)?;
383                Ok(U8::write(&mut self.buffer, op)?)
384            }
385
386            VisitOp::Return | VisitOp::Endchar => {
387                // Should not be encountered in CFF2
388                Err(CFFError::InvalidOperator.into())
389            }
390
391            VisitOp::LineTo
392            | VisitOp::HorizontalLineTo
393            | VisitOp::VerticalLineTo
394            | VisitOp::CurveTo
395            | VisitOp::CurveLine
396            | VisitOp::LineCurve
397            | VisitOp::VvCurveTo
398            | VisitOp::HhCurveTo
399            | VisitOp::VhCurveTo
400            | VisitOp::HvCurveTo => {
401                if !self.width_written {
402                    return Err(CFFError::MissingMoveTo.into());
403                }
404                cff2::write_stack(&mut self.buffer, stack)?;
405                Ok(U8::write(&mut self.buffer, op)?)
406            }
407
408            VisitOp::Hflex | VisitOp::Flex | VisitOp::Hflex1 | VisitOp::Flex1 => {
409                cff2::write_stack(&mut self.buffer, stack)?;
410                U8::write(&mut self.buffer, TWO_BYTE_OPERATOR_MARK)?;
411                Ok(U8::write(&mut self.buffer, op)?)
412            }
413            VisitOp::VsIndex | VisitOp::Blend => {
414                // Should not be encountered when converting CharStrings. CharString should
415                // not be variable.
416                Err(CFFError::InvalidOperator.into())
417            }
418        }
419    }
420
421    fn enter_subr(&mut self, index: SubroutineIndex) -> Result<(), CharStringConversionError> {
422        // Emit callsubr op
423        match index {
424            SubroutineIndex::Local(index) => {
425                cff2::write_stack_value(
426                    cff2::StackValue::Int(index.try_into().map_err(ParseError::from)?),
427                    &mut self.buffer,
428                )?;
429                Ok(U8::write(
430                    &mut self.buffer,
431                    operator::CALL_LOCAL_SUBROUTINE,
432                )?)
433            }
434            SubroutineIndex::Global(index) => {
435                cff2::write_stack_value(
436                    cff2::StackValue::Int(index.try_into().map_err(ParseError::from)?),
437                    &mut self.buffer,
438                )?;
439                Ok(U8::write(
440                    &mut self.buffer,
441                    operator::CALL_GLOBAL_SUBROUTINE,
442                )?)
443            }
444        }
445    }
446
447    fn exit_subr(&mut self) -> Result<(), CharStringConversionError> {
448        Ok(U8::write(&mut self.buffer, operator::RETURN)?)
449    }
450
451    fn hint_data(&mut self, _op: VisitOp, hints: &[u8]) -> Result<(), CharStringConversionError> {
452        Ok(self.buffer.write_bytes(hints)?)
453    }
454}
455
456impl CharStringVisitor<f32, CFFError> for UsedSubrs {
457    fn enter_subr(&mut self, index: SubroutineIndex) -> Result<(), CFFError> {
458        match index {
459            SubroutineIndex::Local(index) => self.local_subr_used.insert(index),
460            SubroutineIndex::Global(index) => self.global_subr_used.insert(index),
461        };
462
463        Ok(())
464    }
465}
466
467impl CharStringVisitor<f32, CFFError> for DebugVisitor {
468    fn visit(&mut self, op: VisitOp, stack: &ArgumentsStack<'_, f32>) -> Result<(), CFFError> {
469        let mut operands = String::new();
470        stack.all().iter().enumerate().for_each(|(i, operand)| {
471            if i > 0 {
472                operands.push(' ')
473            }
474            write!(operands, "{}", operand).unwrap()
475        });
476        println!("{op} {operands}");
477        Ok(())
478    }
479
480    fn enter_subr(&mut self, index: SubroutineIndex) -> Result<(), CFFError> {
481        match index {
482            SubroutineIndex::Local(index) => println!("callsubr {index}"),
483            SubroutineIndex::Global(index) => println!("callgsubr {index}"),
484        }
485        Ok(())
486    }
487}
488
489#[derive(Copy, Clone)]
490pub enum VisitOp {
491    HorizontalStem,
492    VerticalStem,
493    VerticalMoveTo,
494    LineTo,
495    HorizontalLineTo,
496    VerticalLineTo,
497    CurveTo,
498    Return,
499    Endchar,
500    VsIndex,
501    Blend,
502    HorizontalStemHintMask,
503    HintMask,
504    CounterMask,
505    MoveTo,
506    HorizontalMoveTo,
507    VerticalStemHintMask,
508    CurveLine,
509    LineCurve,
510    VvCurveTo,
511    HhCurveTo,
512    VhCurveTo,
513    HvCurveTo,
514    Hflex,
515    Flex,
516    Hflex1,
517    Flex1,
518}
519
520impl TryFrom<u8> for VisitOp {
521    type Error = ();
522
523    fn try_from(value: u8) -> Result<Self, Self::Error> {
524        match value {
525            operator::HORIZONTAL_STEM => Ok(VisitOp::HorizontalStem),
526            operator::VERTICAL_STEM => Ok(VisitOp::VerticalStem),
527            operator::VERTICAL_MOVE_TO => Ok(VisitOp::VerticalMoveTo),
528            operator::LINE_TO => Ok(VisitOp::LineTo),
529            operator::HORIZONTAL_LINE_TO => Ok(VisitOp::HorizontalLineTo),
530            operator::VERTICAL_LINE_TO => Ok(VisitOp::VerticalLineTo),
531            operator::CURVE_TO => Ok(VisitOp::CurveTo),
532            // operator::CALL_LOCAL_SUBROUTINE not yielded as VisitOp
533            operator::RETURN => Ok(VisitOp::Return),
534            operator::ENDCHAR => Ok(VisitOp::Endchar),
535            operator::VS_INDEX => Ok(VisitOp::VsIndex),
536            operator::BLEND => Ok(VisitOp::Blend),
537            operator::HORIZONTAL_STEM_HINT_MASK => Ok(VisitOp::HorizontalStemHintMask),
538            operator::HINT_MASK => Ok(VisitOp::HintMask),
539            operator::COUNTER_MASK => Ok(VisitOp::CounterMask),
540            operator::MOVE_TO => Ok(VisitOp::MoveTo),
541            operator::HORIZONTAL_MOVE_TO => Ok(VisitOp::HorizontalMoveTo),
542            operator::VERTICAL_STEM_HINT_MASK => Ok(VisitOp::VerticalStemHintMask),
543            operator::CURVE_LINE => Ok(VisitOp::CurveLine),
544            operator::LINE_CURVE => Ok(VisitOp::LineCurve),
545            operator::VV_CURVE_TO => Ok(VisitOp::VvCurveTo),
546            operator::HH_CURVE_TO => Ok(VisitOp::HhCurveTo),
547            // operator::SHORT_INT not yielded as VisitOp
548            // operator::CALL_GLOBAL_SUBROUTINE not yielded as VisitOp
549            operator::VH_CURVE_TO => Ok(VisitOp::VhCurveTo),
550            operator::HV_CURVE_TO => Ok(VisitOp::HvCurveTo),
551            // Flex operators are two-byte ops. This assumes the first byte has already been read
552            operator::HFLEX => Ok(VisitOp::Hflex),
553            operator::FLEX => Ok(VisitOp::Flex),
554            operator::HFLEX1 => Ok(VisitOp::Hflex1),
555            operator::FLEX1 => Ok(VisitOp::Flex1),
556            // operator::FIXED_16_16 not yielded as VisitOp
557            _ => Err(()),
558        }
559    }
560}
561
562impl From<VisitOp> for u8 {
563    fn from(op: VisitOp) -> Self {
564        match op {
565            VisitOp::HorizontalStem => operator::HORIZONTAL_STEM,
566            VisitOp::VerticalStem => operator::VERTICAL_STEM,
567            VisitOp::VerticalMoveTo => operator::VERTICAL_MOVE_TO,
568            VisitOp::LineTo => operator::LINE_TO,
569            VisitOp::HorizontalLineTo => operator::HORIZONTAL_LINE_TO,
570            VisitOp::VerticalLineTo => operator::VERTICAL_LINE_TO,
571            VisitOp::CurveTo => operator::CURVE_TO,
572            VisitOp::Return => operator::RETURN,
573            VisitOp::Endchar => operator::ENDCHAR,
574            VisitOp::VsIndex => operator::VS_INDEX,
575            VisitOp::Blend => operator::BLEND,
576            VisitOp::HorizontalStemHintMask => operator::HORIZONTAL_STEM_HINT_MASK,
577            VisitOp::HintMask => operator::HINT_MASK,
578            VisitOp::CounterMask => operator::COUNTER_MASK,
579            VisitOp::MoveTo => operator::MOVE_TO,
580            VisitOp::HorizontalMoveTo => operator::HORIZONTAL_MOVE_TO,
581            VisitOp::VerticalStemHintMask => operator::VERTICAL_STEM_HINT_MASK,
582            VisitOp::CurveLine => operator::CURVE_LINE,
583            VisitOp::LineCurve => operator::LINE_CURVE,
584            VisitOp::VvCurveTo => operator::VV_CURVE_TO,
585            VisitOp::HhCurveTo => operator::HH_CURVE_TO,
586            VisitOp::VhCurveTo => operator::VH_CURVE_TO,
587            VisitOp::HvCurveTo => operator::HV_CURVE_TO,
588            // Flex operators are two-byte ops. This returns the second byte
589            VisitOp::Hflex => operator::HFLEX,
590            VisitOp::Flex => operator::FLEX,
591            VisitOp::Hflex1 => operator::HFLEX1,
592            VisitOp::Flex1 => operator::FLEX1,
593        }
594    }
595}
596
597impl fmt::Display for VisitOp {
598    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
599        match self {
600            VisitOp::HorizontalStem => f.write_str("hstem"),
601            VisitOp::VerticalStem => f.write_str("vstem"),
602            VisitOp::VerticalMoveTo => f.write_str("vmoveto"),
603            VisitOp::LineTo => f.write_str("rlineto"),
604            VisitOp::HorizontalLineTo => f.write_str("hlineto"),
605            VisitOp::VerticalLineTo => f.write_str("vlineto"),
606            VisitOp::CurveTo => f.write_str("rrcurveto"),
607            VisitOp::Return => f.write_str("return"),
608            VisitOp::Endchar => f.write_str("endchar"),
609            VisitOp::VsIndex => f.write_str("vsindex"),
610            VisitOp::Blend => f.write_str("blend"),
611            VisitOp::HorizontalStemHintMask => f.write_str("hstemhm"),
612            VisitOp::HintMask => f.write_str("hintmask"),
613            VisitOp::CounterMask => f.write_str("cntrmask"),
614            VisitOp::MoveTo => f.write_str("rmoveto"),
615            VisitOp::HorizontalMoveTo => f.write_str("hmoveto"),
616            VisitOp::VerticalStemHintMask => f.write_str("vstemhm"),
617            VisitOp::CurveLine => f.write_str("rcurveline"),
618            VisitOp::LineCurve => f.write_str("rlinecurve"),
619            VisitOp::VvCurveTo => f.write_str("vvcurveto"),
620            VisitOp::HhCurveTo => f.write_str("hhcurveto"),
621            VisitOp::VhCurveTo => f.write_str("vhcurveto"),
622            VisitOp::HvCurveTo => f.write_str("hvcurveto"),
623            VisitOp::Hflex => f.write_str("hflex"),
624            VisitOp::Flex => f.write_str("flex"),
625            VisitOp::Hflex1 => f.write_str("hflex1"),
626            VisitOp::Flex1 => f.write_str("flex1"),
627        }
628    }
629}
630
631impl<'a, 'data> CharStringVisitorContext<'a, 'data> {
632    /// Construct a new context for traversing a CharString.
633    ///
634    /// Used with a `CharStringVisitor` impl supplied to `visit` to traverse the CharString.
635    pub fn new(
636        glyph_id: GlyphId,
637        char_strings_index: &'a MaybeOwnedIndex<'data>,
638        local_subr_index: Option<&'a MaybeOwnedIndex<'data>>,
639        global_subr_index: &'a MaybeOwnedIndex<'data>,
640        variable: Option<VariableCharStringVisitorContext<'a, 'data>>,
641    ) -> CharStringVisitorContext<'a, 'data> {
642        CharStringVisitorContext {
643            glyph_id,
644            char_strings_index,
645            local_subr_index,
646            global_subr_index,
647            variable,
648            width_parsed: false,
649            stems_len: 0,
650            has_endchar: false,
651            has_seac: false,
652            seen_blend: false,
653            vsindex: None,
654            scalars: None,
655        }
656    }
657
658    /// Visit the operators of the CharString with a `CharStringVisitor` implementation.
659    pub fn visit<S, V, E>(
660        &mut self,
661        font: CFFFont<'a, 'data>,
662        stack: &mut ArgumentsStack<'_, S>,
663        visitor: &mut V,
664    ) -> Result<(), E>
665    where
666        V: CharStringVisitor<S, E>,
667        S: BlendOperand,
668        E: std::error::Error + From<CFFError> + From<ParseError>,
669    {
670        let char_string = self
671            .char_strings_index
672            .read_object(usize::from(self.glyph_id))
673            .ok_or(CFFError::ParseError(ParseError::BadIndex))?;
674        self.visit_impl(font, char_string, 0, stack, visitor)
675    }
676
677    fn visit_impl<S, V, E>(
678        &mut self,
679        font: CFFFont<'a, 'data>,
680        char_string: &[u8],
681        depth: u8,
682        stack: &mut ArgumentsStack<'_, S>,
683        visitor: &mut V,
684    ) -> Result<(), E>
685    where
686        V: CharStringVisitor<S, E>,
687        S: BlendOperand,
688        E: std::error::Error + From<CFFError> + From<ParseError>,
689    {
690        let mut s = ReadScope::new(char_string).ctxt();
691        while s.bytes_available() {
692            let op = s.read::<U8>()?;
693            match op {
694                0 | 2 | 9 | 13 | 17 => {
695                    // Reserved.
696                    return Err(CFFError::InvalidOperator.into());
697                }
698                operator::HORIZONTAL_STEM
699                | operator::VERTICAL_STEM
700                | operator::HORIZONTAL_STEM_HINT_MASK
701                | operator::VERTICAL_STEM_HINT_MASK => {
702                    // If the stack length is uneven, then the first value is a `width`.
703                    let len = if stack.len().is_odd() && !self.width_parsed {
704                        self.width_parsed = true;
705                        stack.len() - 1
706                    } else {
707                        stack.len()
708                    };
709
710                    self.stems_len += len as u32 >> 1;
711
712                    // We are ignoring the hint operators.
713                    visitor.visit(op.try_into().unwrap(), stack)?;
714                    stack.clear();
715                }
716                operator::VERTICAL_MOVE_TO => {
717                    let offset = self.handle_width(stack.len() == 2 && !self.width_parsed);
718                    stack.offset(offset, |stack| visitor.visit(op.try_into().unwrap(), stack))?;
719                    stack.clear();
720                }
721                operator::LINE_TO
722                | operator::HORIZONTAL_LINE_TO
723                | operator::VERTICAL_LINE_TO
724                | operator::CURVE_TO => {
725                    visitor.visit(op.try_into().unwrap(), stack)?;
726                    stack.clear();
727                }
728                operator::CALL_LOCAL_SUBROUTINE => {
729                    if stack.is_empty() {
730                        return Err(CFFError::InvalidArgumentsStackLength.into());
731                    }
732
733                    if depth == STACK_LIMIT {
734                        return Err(CFFError::NestingLimitReached.into());
735                    }
736
737                    // Parse and remember the local subroutine for the current glyph.
738                    // Since it's a pretty complex task, we're doing it only when
739                    // a local subroutine is actually requested by the glyphs charstring.
740                    if self.local_subr_index.is_none() {
741                        // Only match on this as the other variants were populated at the beginning of the function
742                        if let CFFFont::CFF(super::Font {
743                            data: CFFVariant::CID(ref cid),
744                            ..
745                        }) = font
746                        {
747                            // Choose the local subroutine index corresponding to the glyph/CID
748                            self.local_subr_index = cid
749                                .fd_select
750                                .font_dict_index(self.glyph_id)
751                                .and_then(|font_dict_index| {
752                                    match cid.local_subr_indices.get(usize::from(font_dict_index)) {
753                                        Some(Some(index)) => Some(index),
754                                        _ => None,
755                                    }
756                                });
757                        }
758                    }
759
760                    if let Some(local_subrs) = self.local_subr_index {
761                        let subroutine_bias = calc_subroutine_bias(local_subrs.len());
762                        let index = conv_subroutine_index(stack.pop(), subroutine_bias)?;
763                        let char_string = local_subrs
764                            .read_object(index)
765                            .ok_or(CFFError::InvalidSubroutineIndex)?;
766                        visitor.enter_subr(SubroutineIndex::Local(index))?;
767                        self.visit_impl(font, char_string, depth + 1, stack, visitor)?;
768                        visitor.exit_subr()?;
769                    } else {
770                        return Err(CFFError::NoLocalSubroutines.into());
771                    }
772
773                    if self.has_endchar && !self.has_seac {
774                        if s.bytes_available() {
775                            return Err(CFFError::DataAfterEndChar.into());
776                        }
777
778                        break;
779                    }
780                }
781                operator::RETURN => {
782                    match font {
783                        CFFFont::CFF(_) => {
784                            visitor.visit(op.try_into().unwrap(), stack)?;
785                            break;
786                        }
787                        CFFFont::CFF2(_) => {
788                            // Removed in CFF2
789                            return Err(CFFError::InvalidOperator.into());
790                        }
791                    }
792                }
793                TWO_BYTE_OPERATOR_MARK => {
794                    // flex
795                    let op2 = s.read::<U8>()?;
796                    match op2 {
797                        operator::HFLEX | operator::FLEX | operator::HFLEX1 | operator::FLEX1 => {
798                            visitor.visit(op2.try_into().unwrap(), stack)?;
799                            stack.clear()
800                        }
801                        _ => return Err(CFFError::UnsupportedOperator.into()),
802                    }
803                }
804                operator::ENDCHAR => {
805                    match font {
806                        CFFFont::CFF(cff) => {
807                            if stack.len() == 4 || (!self.width_parsed && stack.len() == 5) {
808                                // Process 'seac'.
809                                let accent_char = stack
810                                    .pop()
811                                    .try_as_u8()
812                                    .and_then(|code| cff.seac_code_to_glyph_id(code))
813                                    .ok_or(CFFError::InvalidSeacCode)?;
814                                let base_char = stack
815                                    .pop()
816                                    .try_as_u8()
817                                    .and_then(|code| cff.seac_code_to_glyph_id(code))
818                                    .ok_or(CFFError::InvalidSeacCode)?;
819                                let dy = stack.pop();
820                                let dx = stack.pop();
821
822                                if !self.width_parsed {
823                                    stack.pop();
824                                    self.width_parsed = true;
825                                }
826
827                                self.has_seac = true;
828
829                                let base_char_string = self
830                                    .char_strings_index
831                                    .read_object(usize::from(base_char))
832                                    .ok_or(CFFError::InvalidSeacCode)?;
833                                visitor.enter_seac(SeacChar::Base, dx, dy)?;
834                                self.visit_impl(font, base_char_string, depth + 1, stack, visitor)?;
835                                visitor.exit_seac(SeacChar::Base)?;
836
837                                let accent_char_string = self
838                                    .char_strings_index
839                                    .read_object(usize::from(accent_char))
840                                    .ok_or(CFFError::InvalidSeacCode)?;
841                                visitor.enter_seac(SeacChar::Accent, dx, dy)?;
842                                self.visit_impl(
843                                    font,
844                                    accent_char_string,
845                                    depth + 1,
846                                    stack,
847                                    visitor,
848                                )?;
849                                visitor.exit_seac(SeacChar::Accent)?;
850                            } else if stack.len() == 1 && !self.width_parsed {
851                                stack.pop();
852                                self.width_parsed = true;
853                            }
854
855                            if s.bytes_available() {
856                                return Err(CFFError::DataAfterEndChar.into());
857                            }
858
859                            self.has_endchar = true;
860                            visitor.visit(op.try_into().unwrap(), stack)?;
861                            break;
862                        }
863                        CFFFont::CFF2(_) => {
864                            // Removed in CFF2
865                            return Err(CFFError::InvalidOperator.into());
866                        }
867                    }
868                }
869                operator::VS_INDEX => {
870                    match font {
871                        CFFFont::CFF(_) => {
872                            // Added in CFF2
873                            return Err(CFFError::InvalidOperator.into());
874                        }
875                        CFFFont::CFF2(_) => {
876                            // When used, vsindex must precede the first blend operator,
877                            // and may occur only once in the CharString.
878                            if self.vsindex.is_some() {
879                                return Err(CFFError::DuplicateVsIndex.into());
880                            } else if self.seen_blend {
881                                return Err(CFFError::VsIndexAfterBlend.into());
882                            } else {
883                                if stack.len() != 1 {
884                                    return Err(CFFError::InvalidArgumentsStackLength.into());
885                                }
886                                visitor.visit(op.try_into().unwrap(), stack)?;
887                                let item_variation_data_index = stack
888                                    .pop()
889                                    .try_as_u16()
890                                    .ok_or(CFFError::InvalidArgumentsStackLength)?;
891                                self.vsindex = Some(item_variation_data_index);
892                            }
893                        }
894                    }
895                }
896                operator::BLEND => {
897                    match font {
898                        CFFFont::CFF(_) => {
899                            // Added in CFF2
900                            return Err(CFFError::InvalidOperator.into());
901                        }
902                        CFFFont::CFF2(font) => {
903                            let Some(var) = self.variable else {
904                                return Err(CFFError::MissingVariationStore.into());
905                            };
906
907                            if stack.len() > 0 {
908                                visitor.visit(op.try_into().unwrap(), stack)?;
909
910                                // Lookup the ItemVariationStore data to get the variation regions
911                                let scalars = match &self.scalars {
912                                    Some(scalars) => scalars,
913                                    None => {
914                                        let vs_index =
915                                            self.vsindex.map(Ok).unwrap_or_else(|| {
916                                                font.private_dict
917                                                    .get_i32(Operator::VSIndex)
918                                                    .ok_or(ParseError::BadValue)?
919                                                    .and_then(|val| {
920                                                        u16::try_from(val).map_err(ParseError::from)
921                                                    })
922                                            })?;
923
924                                        self.scalars = Some(cff2::scalars(
925                                            vs_index,
926                                            var.vstore,
927                                            var.instance,
928                                        )?);
929                                        &self.scalars.as_ref().unwrap()
930                                    }
931                                };
932
933                                cff2::blend(&scalars, stack)?;
934                            } else {
935                                return Err(CFFError::InvalidArgumentsStackLength.into());
936                            }
937                        }
938                    }
939                }
940                operator::HINT_MASK | operator::COUNTER_MASK => {
941                    let mut len = stack.len();
942                    let visit_op = op.try_into().unwrap();
943                    visitor.visit(visit_op, stack)?;
944                    stack.clear();
945
946                    // If the stack length is uneven, then the first value is a `width`.
947                    if len.is_odd() && !self.width_parsed {
948                        len -= 1;
949                        self.width_parsed = true;
950                    }
951
952                    self.stems_len += len as u32 >> 1;
953
954                    // Yield the hints
955                    let hints = s
956                        .read_slice(
957                            usize::try_from((self.stems_len + 7) >> 3)
958                                .map_err(|_| ParseError::BadValue)?,
959                        )
960                        .map_err(|_| ParseError::BadOffset)?;
961                    visitor.hint_data(visit_op, hints)?;
962                }
963                operator::MOVE_TO => {
964                    let offset = self.handle_width(stack.len() == 3 && !self.width_parsed);
965                    stack.offset(offset, |stack| visitor.visit(op.try_into().unwrap(), stack))?;
966                    stack.clear();
967                }
968                operator::HORIZONTAL_MOVE_TO => {
969                    let offset = self.handle_width(stack.len() == 2 && !self.width_parsed);
970                    stack.offset(offset, |stack| visitor.visit(op.try_into().unwrap(), stack))?;
971                    stack.clear();
972                }
973                operator::CURVE_LINE
974                | operator::LINE_CURVE
975                | operator::VV_CURVE_TO
976                | operator::HH_CURVE_TO
977                | operator::VH_CURVE_TO
978                | operator::HV_CURVE_TO => {
979                    visitor.visit(op.try_into().unwrap(), stack)?;
980                    stack.clear();
981                }
982                operator::SHORT_INT => {
983                    let n = s.read::<I16Be>()?;
984                    stack.push(S::from(n))?;
985                }
986                operator::CALL_GLOBAL_SUBROUTINE => {
987                    if stack.is_empty() {
988                        return Err(CFFError::InvalidArgumentsStackLength.into());
989                    }
990
991                    if depth == STACK_LIMIT {
992                        return Err(CFFError::NestingLimitReached.into());
993                    }
994
995                    let subroutine_bias = calc_subroutine_bias(self.global_subr_index.len());
996                    let index = conv_subroutine_index(stack.pop(), subroutine_bias)?;
997                    let char_string = self
998                        .global_subr_index
999                        .read_object(index)
1000                        .ok_or(CFFError::InvalidSubroutineIndex)?;
1001                    visitor.enter_subr(SubroutineIndex::Global(index))?;
1002                    self.visit_impl(font, char_string, depth + 1, stack, visitor)?;
1003                    visitor.exit_subr()?;
1004
1005                    if self.has_endchar && !self.has_seac {
1006                        if s.bytes_available() {
1007                            return Err(CFFError::DataAfterEndChar.into());
1008                        }
1009
1010                        break;
1011                    }
1012                }
1013                32..=246 => {
1014                    stack.push(parse_int1(op)?)?;
1015                }
1016                247..=250 => {
1017                    stack.push(parse_int2(op, &mut s)?)?;
1018                }
1019                251..=254 => {
1020                    stack.push(parse_int3(op, &mut s)?)?;
1021                }
1022                operator::FIXED_16_16 => {
1023                    stack.push(parse_fixed(&mut s)?)?;
1024                }
1025            }
1026        }
1027
1028        Ok(())
1029    }
1030
1031    fn handle_width(&mut self, cond: bool) -> usize {
1032        if cond {
1033            self.width_parsed = true;
1034            1
1035        } else {
1036            0
1037        }
1038    }
1039}
1040
1041// CharString number parsing functions
1042fn parse_int1<S: BlendOperand>(op: u8) -> Result<S, CFFError> {
1043    let n = i16::from(op) - 139;
1044    Ok(S::from(n))
1045}
1046
1047fn parse_int2<S: BlendOperand>(op: u8, s: &mut ReadCtxt<'_>) -> Result<S, CFFError> {
1048    let b1 = s.read::<U8>()?;
1049    let n = (i16::from(op) - 247) * 256 + i16::from(b1) + 108;
1050    debug_assert!((108..=1131).contains(&n));
1051    Ok(S::from(n))
1052}
1053
1054fn parse_int3<S: BlendOperand>(op: u8, s: &mut ReadCtxt<'_>) -> Result<S, CFFError> {
1055    let b1 = s.read::<U8>()?;
1056    let n = -(i16::from(op) - 251) * 256 - i16::from(b1) - 108;
1057    debug_assert!((-1131..=-108).contains(&n));
1058    Ok(S::from(n))
1059}
1060
1061fn parse_fixed<S: BlendOperand>(s: &mut ReadCtxt<'_>) -> Result<S, CFFError> {
1062    let n = s.read::<Fixed>()?;
1063    Ok(S::from(n))
1064}
1065
1066// Conversions from biased subr index operands to unbiased value
1067pub(crate) fn conv_subroutine_index<S: BlendOperand>(
1068    index: S,
1069    bias: u16,
1070) -> Result<usize, CFFError> {
1071    let index = index.try_as_i32().ok_or(CFFError::InvalidSubroutineIndex)?;
1072    conv_subroutine_index_impl(index, bias).ok_or(CFFError::InvalidSubroutineIndex)
1073}
1074
1075pub(crate) fn conv_subroutine_index_impl(index: i32, bias: u16) -> Option<usize> {
1076    let bias = i32::from(bias);
1077
1078    let index = index.checked_add(bias)?;
1079    usize::try_from(index).ok()
1080}
1081
1082// Adobe Technical Note #5176, Chapter 16 "Local / Global Subrs INDEXes"
1083pub(crate) fn calc_subroutine_bias(len: usize) -> u16 {
1084    if len < 1240 {
1085        107
1086    } else if len < 33900 {
1087        1131
1088    } else {
1089        32768
1090    }
1091}
1092
1093impl IsEven for usize {
1094    fn is_even(&self) -> bool {
1095        (*self) & 1 == 0
1096    }
1097
1098    fn is_odd(&self) -> bool {
1099        !self.is_even()
1100    }
1101}
1102
1103impl TryNumFrom<f32> for u8 {
1104    fn try_num_from(v: f32) -> Option<Self> {
1105        i32::try_num_from(v).and_then(|v| u8::try_from(v).ok())
1106    }
1107}
1108
1109impl TryNumFrom<f32> for i16 {
1110    fn try_num_from(v: f32) -> Option<Self> {
1111        i32::try_num_from(v).and_then(|v| i16::try_from(v).ok())
1112    }
1113}
1114
1115impl TryNumFrom<f32> for u16 {
1116    fn try_num_from(v: f32) -> Option<Self> {
1117        i32::try_num_from(v).and_then(|v| u16::try_from(v).ok())
1118    }
1119}
1120
1121impl TryNumFrom<f32> for i32 {
1122    fn try_num_from(v: f32) -> Option<Self> {
1123        // Based on https://github.com/rust-num/num-traits/blob/master/src/cast.rs
1124
1125        // Float as int truncates toward zero, so we want to allow values
1126        // in the exclusive range `(MIN-1, MAX+1)`.
1127
1128        // We can't represent `MIN-1` exactly, but there's no fractional part
1129        // at this magnitude, so we can just use a `MIN` inclusive boundary.
1130        const MIN: f32 = core::i32::MIN as f32;
1131        // We can't represent `MAX` exactly, but it will round up to exactly
1132        // `MAX+1` (a power of two) when we cast it.
1133        const MAX_P1: f32 = core::i32::MAX as f32;
1134        if v >= MIN && v < MAX_P1 {
1135            Some(v as i32)
1136        } else {
1137            None
1138        }
1139    }
1140}
1141
1142impl From<ParseError> for CFFError {
1143    fn from(error: ParseError) -> CFFError {
1144        CFFError::ParseError(error)
1145    }
1146}
1147
1148impl fmt::Display for CFFError {
1149    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1150        match self {
1151            CFFError::ParseError(parse_error) => {
1152                write!(f, "parse error: ")?;
1153                parse_error.fmt(f)
1154            }
1155            CFFError::InvalidOperator => write!(f, "an invalid operator occurred"),
1156            CFFError::UnsupportedOperator => write!(f, "an unsupported operator occurred"),
1157            CFFError::MissingEndChar => write!(f, "the 'endchar' operator is missing"),
1158            CFFError::DataAfterEndChar => write!(f, "unused data left after 'endchar' operator"),
1159            CFFError::NestingLimitReached => write!(f, "subroutines nesting limit reached"),
1160            CFFError::ArgumentsStackLimitReached => write!(f, "arguments stack limit reached"),
1161            CFFError::InvalidArgumentsStackLength => {
1162                write!(f, "an invalid amount of items are in an arguments stack")
1163            }
1164            CFFError::BboxOverflow => write!(f, "outline's bounding box is too large"),
1165            CFFError::MissingMoveTo => write!(f, "missing moveto operator"),
1166            CFFError::DuplicateVsIndex => write!(f, "duplicate vsindex operator"),
1167            CFFError::InvalidSubroutineIndex => write!(f, "an invalid subroutine index"),
1168            CFFError::NoLocalSubroutines => write!(f, "no local subroutines"),
1169            CFFError::InvalidSeacCode => write!(f, "invalid seac code"),
1170            CFFError::InvalidOperand => write!(f, "operand was out of range or invalid"),
1171            CFFError::InvalidFontIndex => write!(f, "invalid font index"),
1172            CFFError::VsIndexAfterBlend => write!(f, "vsindex operator encountered after blend"),
1173            CFFError::MissingVariationStore => write!(f, "missing variation store"),
1174        }
1175    }
1176}
1177
1178impl std::error::Error for CFFError {}
1179
1180/// Operators defined in Adobe Technical Note #5177, The Type  2 Charstring Format.
1181pub(crate) mod operator {
1182    pub const HORIZONTAL_STEM: u8 = 1;
1183    pub const VERTICAL_STEM: u8 = 3;
1184    pub const VERTICAL_MOVE_TO: u8 = 4;
1185    pub const LINE_TO: u8 = 5;
1186    pub const HORIZONTAL_LINE_TO: u8 = 6;
1187    pub const VERTICAL_LINE_TO: u8 = 7;
1188    pub const CURVE_TO: u8 = 8;
1189    pub const CALL_LOCAL_SUBROUTINE: u8 = 10;
1190    pub const RETURN: u8 = 11;
1191    pub const ENDCHAR: u8 = 14;
1192    pub const VS_INDEX: u8 = 15; // CFF2
1193    pub const BLEND: u8 = 16; // CFF2
1194    pub const HORIZONTAL_STEM_HINT_MASK: u8 = 18;
1195    pub const HINT_MASK: u8 = 19;
1196    pub const COUNTER_MASK: u8 = 20;
1197    pub const MOVE_TO: u8 = 21;
1198    pub const HORIZONTAL_MOVE_TO: u8 = 22;
1199    pub const VERTICAL_STEM_HINT_MASK: u8 = 23;
1200    pub const CURVE_LINE: u8 = 24;
1201    pub const LINE_CURVE: u8 = 25;
1202    pub const VV_CURVE_TO: u8 = 26;
1203    pub const HH_CURVE_TO: u8 = 27;
1204    pub const SHORT_INT: u8 = 28;
1205    pub const CALL_GLOBAL_SUBROUTINE: u8 = 29;
1206    pub const VH_CURVE_TO: u8 = 30;
1207    pub const HV_CURVE_TO: u8 = 31;
1208    pub const HFLEX: u8 = 34;
1209    pub const FLEX: u8 = 35;
1210    pub const HFLEX1: u8 = 36;
1211    pub const FLEX1: u8 = 37;
1212    pub const FIXED_16_16: u8 = 255;
1213}
1214
1215#[cfg(test)]
1216mod tests {
1217    use crate::cff::cff2::{self, CFF2};
1218    use crate::error::ReadWriteError;
1219    use crate::tables::variable_fonts::avar::AvarTable;
1220    use crate::tables::variable_fonts::fvar::FvarTable;
1221    use crate::tables::{OpenTypeData, OpenTypeFont};
1222    use crate::tag;
1223    use crate::tests::read_fixture;
1224
1225    use super::*;
1226
1227    struct TraverseCharString;
1228
1229    impl CharStringVisitor<f32, CFFError> for TraverseCharString {}
1230
1231    #[test]
1232    fn traverse_cff2_charstring() -> Result<(), ReadWriteError> {
1233        let buffer = read_fixture("tests/fonts/opentype/cff2/SourceSansVariable-Roman.abc.otf");
1234        let otf = ReadScope::new(&buffer).read::<OpenTypeFont<'_>>().unwrap();
1235
1236        let offset_table = match otf.data {
1237            OpenTypeData::Single(ttf) => ttf,
1238            OpenTypeData::Collection(_) => unreachable!(),
1239        };
1240
1241        let cff_table_data = offset_table.read_table(&otf.scope, tag::CFF2)?.unwrap();
1242        let cff = cff_table_data
1243            .read::<CFF2<'_>>()
1244            .expect("error parsing CFF2 table");
1245        let fvar_data = offset_table.read_table(&otf.scope, tag::FVAR)?.unwrap();
1246        let fvar = fvar_data.read::<FvarTable<'_>>()?;
1247        let avar_data = offset_table.read_table(&otf.scope, tag::AVAR)?;
1248        let avar = avar_data
1249            .as_ref()
1250            .map(|avar_data| avar_data.read::<AvarTable<'_>>())
1251            .transpose()?;
1252
1253        // Traverse a CharString
1254        let glyph_id = 1;
1255        let font_dict_index = cff
1256            .fd_select
1257            .map(|fd_select| fd_select.font_dict_index(glyph_id).unwrap())
1258            .unwrap_or(0);
1259        let font = &cff.fonts[usize::from(font_dict_index)];
1260
1261        let user_tuple = [Fixed::from(650.0)];
1262        let instance = fvar
1263            .normalize(user_tuple.iter().copied(), avar.as_ref())
1264            .unwrap();
1265
1266        let variable = VariableCharStringVisitorContext {
1267            vstore: cff.vstore.as_ref().unwrap(),
1268            instance: &instance,
1269        };
1270        let mut ctx = CharStringVisitorContext::new(
1271            1,
1272            &cff.char_strings_index,
1273            font.local_subr_index.as_ref(),
1274            &cff.global_subr_index,
1275            Some(variable),
1276        );
1277        let mut stack = ArgumentsStack {
1278            data: &mut [0.0; cff2::MAX_OPERANDS],
1279            len: 0,
1280            max_len: cff2::MAX_OPERANDS,
1281        };
1282        let res = ctx.visit(CFFFont::CFF2(font), &mut stack, &mut TraverseCharString);
1283        assert!(res.is_ok());
1284        Ok(())
1285    }
1286}