Skip to main content

read_fonts/ps/
charstring.rs

1//! Parsing and evaluation of charstrings.
2
3use crate::model::Error;
4use crate::{
5    model::glyph::outline::PathSink,
6    ps::{
7        cff::{blend::BlendState, charset::Charset, index::Index, stack::Stack},
8        num,
9        string::Sid,
10        transform::{FontMatrix, Transform},
11    },
12    tables::cff::Cff,
13    types::{BoundingBox, Fixed, Point},
14    Cursor, FontData, FontRead,
15};
16
17/// Maximum nesting depth for subroutine calls.
18///
19/// See "Appendix B Type 2 Charstring Implementation Limits" at
20/// <https://adobe-type-tools.github.io/font-tech-notes/pdfs/5177.Type2.pdf#page=33>
21pub const NESTING_DEPTH_LIMIT: u32 = 10;
22
23/// Maximum number of operations that can be processed during charstring
24/// evaluation.
25///
26/// HarfBuzz limits this to 200,000
27/// (<https://github.com/harfbuzz/harfbuzz/blob/a6357ca3f7e73165ba8b201b65f1130059e34255/src/hb-limits.hh#L108>)
28/// and threat analysis suggested 2,000,000 so a value of 1,000,000
29/// was chosen to match our instruction limit for the TrueType interpreter.
30///
31/// FreeType only limits depth, not number of operations.
32const MAX_OPERATIONS: u32 = 1_000_000;
33
34/// Size of the transient array that `put` and `get` address.
35///
36/// <https://gitlab.freedesktop.org/freetype/freetype/-/blob/80a507a6b8e3d2906ad2c8ba69329bd2fb2a85ef/src/psaux/psfont.h#L59>
37const STORAGE_SIZE: usize = 32;
38
39/// The number of elements of a multiple master font's `BuildCharArray`
40/// held inline. A font that declares a longer array has it on the heap.
41const INLINE_BUILD_CHAR_LEN: usize = 32;
42
43/// The type of a PostScript charstring.
44#[derive(Copy, Clone, PartialEq, Eq, Debug)]
45pub enum CharstringKind {
46    /// Type1 charstring.
47    ///
48    /// See reference at <https://adobe-type-tools.github.io/font-tech-notes/pdfs/T1_SPEC.pdf>.
49    Type1,
50    /// Type2 charstring.
51    ///
52    /// See reference at <https://adobe-type-tools.github.io/font-tech-notes/pdfs/5177.Type2.pdf>.
53    Type2,
54}
55
56/// Trait that provides context for charstring evaluation.
57pub trait CharstringContext {
58    /// Returns the type of the charstring.
59    fn kind(&self) -> CharstringKind;
60
61    /// Returns the base and accent charstrings for the `seac` (standard
62    /// encoded accented character) operator.
63    fn seac_components(&self, base_code: i32, accent_code: i32) -> Result<[&[u8]; 2], Error>;
64
65    /// Returns the charstring for the global subroutine at the given index as
66    /// encoded in the calling charstring.
67    fn global_subr(&self, index: i32) -> Result<&[u8], Error>;
68
69    /// Returns the charstring for the local subroutine at the given index as
70    /// encoded in the calling charstring.
71    fn subr(&self, index: i32) -> Result<&[u8], Error>;
72
73    /// Returns the current active weight vector for a multiple master font.
74    fn weight_vector(&self) -> &[Fixed] {
75        &[]
76    }
77
78    /// Returns the number of elements in the `BuildCharArray` of a multiple
79    /// master font, which othersubrs read and write.
80    fn build_char_len(&self) -> usize {
81        0
82    }
83}
84
85// Ugly temporary impl to support existing skrifa code until it is replaced
86// with CffFontRef.
87//
88// Types are (cff_blob, charstrings, global_subrs, subrs)
89impl<'a> CharstringContext for (&'a [u8], &'a Index<'a>, &'a Index<'a>, &'a Index<'a>) {
90    fn kind(&self) -> CharstringKind {
91        CharstringKind::Type2
92    }
93
94    fn seac_components(&self, base_code: i32, accent_code: i32) -> Result<[&[u8]; 2], Error> {
95        let cff = Cff::read(FontData::new(self.0))?;
96        let charset = cff
97            .charset(0)
98            .or_else(|| Charset::new(FontData::default(), 0, self.1.count()))
99            .ok_or(Error::MissingResource)?;
100        let seac_to_gid = |code: i32| {
101            let code: u8 = code.try_into().ok()?;
102            let sid = *super::encoding::STANDARD_ENCODING.get(code as usize)?;
103            charset.glyph_id(Sid::new(sid as u16))
104        };
105        let accent_gid = seac_to_gid(accent_code).ok_or(Error::InvalidOperation)?;
106        let base_gid = seac_to_gid(base_code).ok_or(Error::InvalidOperation)?;
107        let accent_charstring = self
108            .1
109            .get(accent_gid.to_u32() as usize)
110            .ok_or(Error::Malformed)?;
111        let base_charstring = self
112            .1
113            .get(base_gid.to_u32() as usize)
114            .ok_or(Error::Malformed)?;
115        Ok([base_charstring, accent_charstring])
116    }
117
118    fn global_subr(&self, index: i32) -> Result<&[u8], Error> {
119        self.2
120            .get((index + self.2.subr_bias()) as usize)
121            .ok_or(Error::Malformed)
122    }
123
124    fn subr(&self, index: i32) -> Result<&[u8], Error> {
125        self.3
126            .get((index + self.3.subr_bias()) as usize)
127            .ok_or(Error::Malformed)
128    }
129}
130
131/// Trait for processing commands resulting from charstring evaluation.
132///
133/// During processing, the path construction operators (see "4.1 Path
134/// Construction Operators" at <https://adobe-type-tools.github.io/font-tech-notes/pdfs/5177.Type2.pdf#page=15>)
135/// are simplified into the basic move, line, curve and close commands.
136///
137/// This also has optional callbacks for processing hint operators. See "4.3
138/// Hint Operators" at <https://adobe-type-tools.github.io/font-tech-notes/pdfs/5177.Type2.pdf#page=21>
139/// for more detail.
140#[allow(unused_variables)]
141pub trait CommandSink {
142    // Path construction operators.
143    fn move_to(&mut self, x: Fixed, y: Fixed);
144    fn line_to(&mut self, x: Fixed, y: Fixed);
145    fn curve_to(&mut self, cx0: Fixed, cy0: Fixed, cx1: Fixed, cy1: Fixed, x: Fixed, y: Fixed);
146    fn close(&mut self);
147    // Hint operators.
148    /// Horizontal stem hint at `y` with height `dy`.
149    fn hstem(&mut self, y: Fixed, dy: Fixed) {}
150    /// Vertical stem hint at `x` with width `dx`.
151    fn vstem(&mut self, x: Fixed, dx: Fixed) {}
152    /// Bitmask defining the hints that should be made active for the
153    /// commands that follow.
154    fn hint_mask(&mut self, mask: &[u8]) {}
155    /// Bitmask defining the counter hints that should be made active for the
156    /// commands that follow.
157    fn counter_mask(&mut self, mask: &[u8]) {}
158    /// Clear accumulated stem hints and all data derived from them.
159    fn clear_hints(&mut self) {}
160    /// Called when charstring evaluation is complete.
161    fn finish(&mut self) {}
162}
163
164/// Sink that drops all drawing output into the ether.
165pub struct NullSink;
166
167impl CommandSink for NullSink {
168    fn move_to(&mut self, _x: Fixed, _y: Fixed) {}
169    fn line_to(&mut self, _x: Fixed, _y: Fixed) {}
170    fn curve_to(
171        &mut self,
172        _cx0: Fixed,
173        _cy0: Fixed,
174        _cx1: Fixed,
175        _cy1: Fixed,
176        _x: Fixed,
177        _y: Fixed,
178    ) {
179    }
180    fn close(&mut self) {}
181}
182
183/// Sink that measures the box a drawing stays within.
184///
185/// A curve counts through its control points rather than the path they
186/// describe, so one bulging inside its hull measures larger than it draws.
187/// That is a control box, which is what HarfBuzz reports for a charstring and
188/// what costs no arithmetic to find. Solving each curve would tighten the box
189/// and is not worth what it costs to a caller asking where a glyph roughly
190/// sits.
191///
192/// Nothing drawn leaves [`bounding_box`](Self::bounding_box) empty, which is
193/// the answer for a glyph with no ink rather than a box of no size at the
194/// origin. The pen that measures a drawn outline the same way is
195/// [`ControlBoundsSink`](crate::model::glyph::outline::ControlBoundsSink).
196#[derive(Clone, Default, Debug)]
197pub struct ControlBoundsSink {
198    bounds: Option<BoundingBox<Fixed>>,
199}
200
201impl ControlBoundsSink {
202    /// Creates a sink that has measured nothing.
203    pub fn new() -> Self {
204        Self::default()
205    }
206
207    /// Returns the bounding box collected by this sink.
208    pub fn bounding_box(&self) -> Option<BoundingBox<Fixed>> {
209        self.bounds
210    }
211
212    fn update_bounds(&mut self, x: Fixed, y: Fixed) {
213        self.bounds = Some(match self.bounds {
214            Some(b) => BoundingBox {
215                x_min: b.x_min.min(x),
216                y_min: b.y_min.min(y),
217                x_max: b.x_max.max(x),
218                y_max: b.y_max.max(y),
219            },
220            None => BoundingBox {
221                x_min: x,
222                y_min: y,
223                x_max: x,
224                y_max: y,
225            },
226        });
227    }
228}
229
230impl CommandSink for ControlBoundsSink {
231    fn move_to(&mut self, x: Fixed, y: Fixed) {
232        self.update_bounds(x, y);
233    }
234
235    fn line_to(&mut self, x: Fixed, y: Fixed) {
236        self.update_bounds(x, y);
237    }
238
239    fn curve_to(&mut self, cx0: Fixed, cy0: Fixed, cx1: Fixed, cy1: Fixed, x: Fixed, y: Fixed) {
240        self.update_bounds(cx0, cy0);
241        self.update_bounds(cx1, cy1);
242        self.update_bounds(x, y);
243    }
244
245    fn close(&mut self) {}
246}
247
248/// Evaluates the given charstring and emits the resulting commands to the
249/// specified sink.
250///
251/// If the Private DICT associated with this charstring contains local
252/// subroutines, then the `subrs` index must be provided, otherwise
253/// `Error::MissingResource` will be returned if a callsubr operator
254/// is present.
255///
256/// If evaluating a CFF2 charstring and the top-level table contains an
257/// item variation store, then `blend_state` must be provided, otherwise
258/// `Error::MissingResource` will be returned if a blend operator is
259/// present.
260pub fn evaluate<'a>(
261    context: &'a impl CharstringContext,
262    blend_state: Option<BlendState<'a>>,
263    charstring_data: &[u8],
264    sink: &'a mut impl CommandSink,
265) -> Result<Option<Fixed>, Error> {
266    let mut evaluator = Evaluator::new(context, blend_state, sink);
267    evaluator.evaluate(charstring_data)?;
268    let width = evaluator.have_read_width.then_some(evaluator.wx);
269    sink.finish();
270    Ok(width)
271}
272
273/// Returns the advance width the charstring states, without drawing it.
274///
275/// A charstring states its width before it draws anything: Type 1 in the
276/// `hsbw` or `sbw` that must open it, Type 2 as an optional extra argument
277/// on the first operator that clears the stack. Evaluation therefore stops
278/// as soon as that operator has been seen, which for most glyphs is a
279/// handful of bytes out of hundreds.
280///
281/// `None` where the charstring states no width of its own. For Type 2 that
282/// is the common case and means the font's default width applies; the
283/// caller supplies it, since the charstring does not know it.
284pub fn evaluate_width<'a>(
285    context: &'a impl CharstringContext,
286    blend_state: Option<BlendState<'a>>,
287    charstring_data: &[u8],
288) -> Result<Option<Fixed>, Error> {
289    let mut sink = NullSink;
290    let mut evaluator = Evaluator::new(context, blend_state, &mut sink);
291    evaluator.width_only = true;
292    // A charstring that ends before saying anything is not an error; it
293    // simply states no width.
294    evaluator.evaluate(charstring_data)?;
295    Ok(evaluator.have_read_width.then_some(evaluator.wx))
296}
297
298/// The square root of `value`, by the method FreeType uses.
299///
300/// Zero for anything that is not positive, so a caller never sees an
301/// imaginary answer.
302///
303/// <https://gitlab.freedesktop.org/freetype/freetype/-/blob/80a507a6b8e3d2906ad2c8ba69329bd2fb2a85ef/src/psaux/psintrp.c#L1420>
304fn fixed_sqrt(value: Fixed) -> Fixed {
305    if value.to_bits() <= 0 {
306        return Fixed::ZERO;
307    }
308    // A start value small enough that the addition below cannot overflow.
309    let mut root = if value.to_bits() < 10 {
310        value
311    } else {
312        Fixed::from_bits(value.to_bits() >> 1)
313    };
314    // Babylonian method.
315    loop {
316        let next = Fixed::from_bits((root.to_bits() + (value / root).to_bits() + 1) >> 1);
317        if next == root {
318            return next;
319        }
320        root = next;
321    }
322}
323
324/// Specifies how the seac operation was invoked.
325#[derive(PartialEq)]
326enum SeacMode {
327    /// Through the `seac` operator.
328    Explicit,
329    /// Implicitly with extra arguments on the stack through the
330    /// `endchar` operator.
331    Implicit,
332}
333
334/// Transient state for evaluating a charstring and handling recursive
335/// subroutine calls.
336struct Evaluator<'a> {
337    context: &'a dyn CharstringContext,
338    is_type1: bool,
339    blend_state: Option<BlendState<'a>>,
340    sink: &'a mut dyn CommandSink,
341    is_open: bool,
342    /// When the flex state is active, moveto commands simply
343    /// accumulate vectors on the stack which will be used
344    /// to emit curves when the flex is finalized
345    is_flexing: bool,
346    /// True if we've seen a command that might read width
347    seen_width_command: bool,
348    /// Stop as soon as the width is settled, rather than drawing the glyph.
349    width_only: bool,
350    /// True if we've actually read a width
351    have_read_width: bool,
352    stem_count: usize,
353    x: Fixed,
354    y: Fixed,
355    /// X side-bearing
356    sbx: Fixed,
357    /// X width
358    wx: Fixed,
359    stack: Stack,
360    stack_ix: usize,
361    in_seac: bool,
362    /// Number of operators or numbers processed so far
363    ops_done: u32,
364    /// The transient array that `put` and `get` address.
365    storage: [Fixed; STORAGE_SIZE],
366    /// The `BuildCharArray` of a multiple master font, sized on first use.
367    build_char: smallvec::SmallVec<[Fixed; INLINE_BUILD_CHAR_LEN]>,
368}
369
370impl<'a> Evaluator<'a> {
371    fn new(
372        context: &'a dyn CharstringContext,
373        blend_state: Option<BlendState<'a>>,
374        sink: &'a mut dyn CommandSink,
375    ) -> Self {
376        let is_type1 = context.kind() == CharstringKind::Type1;
377        Self {
378            context,
379            is_type1,
380            blend_state,
381            sink,
382            is_open: false,
383            is_flexing: false,
384            seen_width_command: false,
385            width_only: false,
386            have_read_width: false,
387            stem_count: 0,
388            stack: Stack::new(),
389            x: Fixed::ZERO,
390            y: Fixed::ZERO,
391            sbx: Fixed::ZERO,
392            wx: Fixed::ZERO,
393            stack_ix: 0,
394            in_seac: false,
395            ops_done: 0,
396            storage: [Fixed::ZERO; STORAGE_SIZE],
397            build_char: smallvec::SmallVec::new(),
398        }
399    }
400
401    fn evaluate(&mut self, charstring_data: &[u8]) -> Result<(), Error> {
402        let seen_endchar = self.evaluate_impl(charstring_data, 0)?;
403        if !self.is_type1 && !seen_endchar {
404            // FreeType simulates an endchar operator for CFF and CFF2
405            // <https://gitlab.freedesktop.org/freetype/freetype/-/blob/80a507a6b8e3d2906ad2c8ba69329bd2fb2a85ef/src/psaux/psintrp.c#L632>
406            self.evaluate_operator(
407                Operator::EndChar,
408                &mut crate::FontData::default().cursor(),
409                0,
410            )?;
411        }
412        if self.is_open {
413            self.sink.close();
414        }
415        Ok(())
416    }
417
418    fn evaluate_impl(&mut self, charstring_data: &[u8], nesting_depth: u32) -> Result<bool, Error> {
419        if nesting_depth > NESTING_DEPTH_LIMIT {
420            return Err(Error::LimitExceeded);
421        }
422        let mut cursor = crate::FontData::new(charstring_data).cursor();
423        let mut seen_endchar = false;
424        while cursor.remaining_bytes() != 0 {
425            self.ops_done += 1;
426            if self.ops_done > MAX_OPERATIONS {
427                return Err(Error::LimitExceeded);
428            }
429            let b0 = cursor.read::<u8>()?;
430            match b0 {
431                // See "3.2 Charstring Number Encoding" <https://adobe-type-tools.github.io/font-tech-notes/pdfs/5177.Type2.pdf#page=12>
432                //
433                // Push an integer to the stack
434                28 | 32..=254 => {
435                    self.stack.push(num::parse_int(&mut cursor, b0)?)?;
436                }
437                // Push a fixed point value to the stack
438                255 => {
439                    let val = cursor.read::<i32>()?;
440                    if self.is_type1 {
441                        // Type1 interprets this as an integer
442                        self.stack.push(val)?;
443                    } else {
444                        // Type2 interprets this as a raw 16.16 fixed point
445                        // value
446                        self.stack.push(Fixed::from_bits(val))?;
447                    }
448                }
449                _ => {
450                    // FreeType ignores reserved (unknown) operators.
451                    // See <https://gitlab.freedesktop.org/freetype/freetype/-/blob/80a507a6b8e3d2906ad2c8ba69329bd2fb2a85ef/src/psaux/psintrp.c#L703>
452                    // and fontations issue <https://github.com/googlefonts/fontations/issues/1680>
453                    if let Some(operator) = Operator::read(&mut cursor, b0) {
454                        seen_endchar |= operator == Operator::EndChar;
455                        if !self.evaluate_operator(operator, &mut cursor, nesting_depth)? {
456                            break;
457                        }
458                        // The first stack-clearing operator settles the
459                        // width, whether or not it carried one, so there is
460                        // nothing further to learn from the rest.
461                        if self.width_only && self.seen_width_command {
462                            break;
463                        }
464                    } else {
465                        // Clear the stack for unknown operators
466                        self.reset_stack();
467                    }
468                }
469            }
470        }
471        Ok(seen_endchar)
472    }
473
474    /// Evaluates a single charstring operator.
475    ///
476    /// Returns `Ok(true)` if evaluation should continue.
477    fn evaluate_operator(
478        &mut self,
479        operator: Operator,
480        cursor: &mut Cursor,
481        nesting_depth: u32,
482    ) -> Result<bool, Error> {
483        use Operator::*;
484        use PointMode::*;
485        match operator {
486            // The following "flex" operators are intended to emit
487            // either two curves or a straight line depending on
488            // a "flex depth" parameter and the distance from the
489            // joining point to the chord connecting the two
490            // end points. In practice, we just emit the two curves,
491            // following FreeType:
492            // <https://gitlab.freedesktop.org/freetype/freetype/-/blob/80a507a6b8e3d2906ad2c8ba69329bd2fb2a85ef/src/psaux/psintrp.c#L335>
493            //
494            // Spec: <https://adobe-type-tools.github.io/font-tech-notes/pdfs/5177.Type2.pdf#page=18>
495            Flex => {
496                self.emit_curves([DxDy; 6])?;
497                self.reset_stack();
498            }
499            // Spec: <https://adobe-type-tools.github.io/font-tech-notes/pdfs/5177.Type2.pdf#page=19>
500            HFlex => {
501                self.emit_curves([DxY, DxDy, DxY, DxY, DxInitialY, DxY])?;
502                self.reset_stack();
503            }
504            // Spec: <https://adobe-type-tools.github.io/font-tech-notes/pdfs/5177.Type2.pdf#page=19>
505            HFlex1 => {
506                self.emit_curves([DxDy, DxDy, DxY, DxY, DxDy, DxInitialY])?;
507                self.reset_stack();
508            }
509            // Spec: <https://adobe-type-tools.github.io/font-tech-notes/pdfs/5177.Type2.pdf#page=20>
510            Flex1 => {
511                self.emit_curves([DxDy, DxDy, DxDy, DxDy, DxDy, DLargerCoordDist])?;
512                self.reset_stack();
513            }
514            // Set the variation store index
515            // <https://learn.microsoft.com/en-us/typography/opentype/spec/cff2charstr#syntax-for-font-variations-support-operators>
516            VariationStoreIndex => {
517                if !self.is_type1 {
518                    let blend_state = self.blend_state.as_mut().ok_or(Error::MissingResource)?;
519                    let store_index = self.stack.pop_i32()? as u16;
520                    blend_state.set_store_index(store_index)?;
521                }
522            }
523            // Apply blending to the current operand stack
524            // <https://learn.microsoft.com/en-us/typography/opentype/spec/cff2charstr#syntax-for-font-variations-support-operators>
525            Blend => {
526                if !self.is_type1 {
527                    let blend_state = self.blend_state.as_ref().ok_or(Error::MissingResource)?;
528                    self.stack.apply_blend(blend_state)?;
529                }
530            }
531            // Return from the current subroutine
532            // Spec: <https://adobe-type-tools.github.io/font-tech-notes/pdfs/5177.Type2.pdf#page=29>
533            Return => {
534                return Ok(false);
535            }
536            // End the current charstring
537            // Spec: <https://adobe-type-tools.github.io/font-tech-notes/pdfs/5177.Type2.pdf#page=21>
538            // FT: <https://gitlab.freedesktop.org/freetype/freetype/-/blob/80a507a6b8e3d2906ad2c8ba69329bd2fb2a85ef/src/psaux/psintrp.c#L2463>
539            EndChar => {
540                let stack_len = self.stack.len();
541                if (stack_len == 1 || stack_len == 5) && !self.seen_width_command {
542                    self.read_width()?;
543                }
544                self.seen_width_command = true;
545                if stack_len > 1 && !self.width_only {
546                    self.handle_seac(SeacMode::Implicit, nesting_depth)?;
547                }
548                return Ok(false);
549            }
550            // Emits a sequence of stem hints
551            // Spec: <https://adobe-type-tools.github.io/font-tech-notes/pdfs/5177.Type2.pdf#page=21>
552            // FT: <https://gitlab.freedesktop.org/freetype/freetype/-/blob/80a507a6b8e3d2906ad2c8ba69329bd2fb2a85ef/src/psaux/psintrp.c#L777>
553            HStem | VStem | HStemHm | VStemHm => {
554                let mut i = 0;
555                let len = if self.stack.len_is_odd() && !self.seen_width_command {
556                    self.read_width()?;
557                    i = 1;
558                    self.stack.len() - 1
559                } else {
560                    self.stack.len()
561                };
562                self.seen_width_command = true;
563                let is_horizontal = matches!(operator, HStem | HStemHm);
564                let mut u = Fixed::ZERO;
565                while i < self.stack.len() {
566                    let args = self.stack.fixed_array::<2>(i)?;
567                    u += args[0];
568                    let w = args[1];
569                    let v = u.wrapping_add(w);
570                    if is_horizontal {
571                        self.sink.hstem(u, v);
572                    } else {
573                        self.sink.vstem(u, v);
574                    }
575                    u = v;
576                    i += 2;
577                }
578                self.stem_count += len / 2;
579                self.reset_stack();
580            }
581            // Applies a hint or counter mask.
582            // If there are arguments on the stack, this is also an
583            // implied series of VSTEMHM operators.
584            // Hint and counter masks are bitstrings that determine
585            // the currently active set of hints.
586            // Spec: <https://adobe-type-tools.github.io/font-tech-notes/pdfs/5177.Type2.pdf#page=24>
587            // FT: <https://gitlab.freedesktop.org/freetype/freetype/-/blob/80a507a6b8e3d2906ad2c8ba69329bd2fb2a85ef/src/psaux/psintrp.c#L2580>
588            HintMask | CntrMask => {
589                let mut i = 0;
590                let len = if self.stack.len_is_odd() && !self.seen_width_command {
591                    self.read_width()?;
592                    i = 1;
593                    self.stack.len() - 1
594                } else {
595                    self.stack.len()
596                };
597                self.seen_width_command = true;
598                let mut u = Fixed::ZERO;
599                while i < self.stack.len() {
600                    let args = self.stack.fixed_array::<2>(i)?;
601                    u += args[0];
602                    let w = args[1];
603                    let v = u + w;
604                    self.sink.vstem(u, v);
605                    u = v;
606                    i += 2;
607                }
608                self.stem_count += len / 2;
609                let count = self.stem_count.div_ceil(8);
610                let mask = cursor.read_array::<u8>(count)?;
611                if operator == HintMask {
612                    self.sink.hint_mask(mask);
613                } else {
614                    self.sink.counter_mask(mask);
615                }
616                self.reset_stack();
617            }
618            // Starts a new subpath
619            // Spec: <https://adobe-type-tools.github.io/font-tech-notes/pdfs/5177.Type2.pdf#page=16>
620            // FT: <https://gitlab.freedesktop.org/freetype/freetype/-/blob/80a507a6b8e3d2906ad2c8ba69329bd2fb2a85ef/src/psaux/psintrp.c#L2653>
621            RMoveTo => {
622                if self.stack.len() > 2 && !self.seen_width_command {
623                    self.read_width()?;
624                }
625                self.seen_width_command = true;
626                if !self.is_flexing {
627                    let dy = self.stack.pop_fixed()?;
628                    let dx = self.stack.pop_fixed()?;
629                    self.x += dx;
630                    self.y += dy;
631                    if !self.is_open {
632                        self.is_open = true;
633                    } else {
634                        self.sink.close();
635                    }
636                    self.sink.move_to(self.x, self.y);
637                    self.reset_stack();
638                }
639            }
640            // Starts a new subpath by moving the current point in the
641            // horizontal or vertical direction
642            // Spec: <https://adobe-type-tools.github.io/font-tech-notes/pdfs/5177.Type2.pdf#page=16>
643            // FT: <https://gitlab.freedesktop.org/freetype/freetype/-/blob/80a507a6b8e3d2906ad2c8ba69329bd2fb2a85ef/src/psaux/psintrp.c#L839>
644            HMoveTo | VMoveTo => {
645                if self.stack.len() > 1 && !self.seen_width_command {
646                    self.read_width()?;
647                }
648                self.seen_width_command = true;
649                if self.is_flexing {
650                    // We need to add the other coordinate to the stack so we
651                    // have a full flex vector
652                    self.stack.push(0)?;
653                    if operator == VMoveTo {
654                        // For vertical move, the coordinates are in the wrong
655                        // order so swap them
656                        self.stack.exch()?;
657                    }
658                } else {
659                    let delta = self.stack.pop_fixed()?;
660                    if operator == HMoveTo {
661                        self.x += delta;
662                    } else {
663                        self.y += delta;
664                    }
665                    if !self.is_open {
666                        self.is_open = true;
667                    } else {
668                        self.sink.close();
669                    }
670                    self.sink.move_to(self.x, self.y);
671                    self.reset_stack();
672                }
673            }
674            // Emits a sequence of lines
675            // Spec: <https://adobe-type-tools.github.io/font-tech-notes/pdfs/5177.Type2.pdf#page=16>
676            // FT: <https://gitlab.freedesktop.org/freetype/freetype/-/blob/80a507a6b8e3d2906ad2c8ba69329bd2fb2a85ef/src/psaux/psintrp.c#L863>
677            RLineTo => {
678                let mut i = 0;
679                while i < self.stack.len() {
680                    let [dx, dy] = self.stack.fixed_array::<2>(i)?;
681                    self.x += dx;
682                    self.y += dy;
683                    self.emit_line(self.x, self.y);
684                    i += 2;
685                }
686                self.reset_stack();
687            }
688            // Emits a sequence of alternating horizontal and vertical
689            // lines
690            // Spec: <https://adobe-type-tools.github.io/font-tech-notes/pdfs/5177.Type2.pdf#page=16>
691            // FT: <https://gitlab.freedesktop.org/freetype/freetype/-/blob/80a507a6b8e3d2906ad2c8ba69329bd2fb2a85ef/src/psaux/psintrp.c#L885>
692            HLineTo | VLineTo => {
693                let mut is_x = operator == HLineTo;
694                for i in 0..self.stack.len() {
695                    let delta = self.stack.get_fixed(i)?;
696                    if is_x {
697                        self.x += delta;
698                    } else {
699                        self.y += delta;
700                    }
701                    is_x = !is_x;
702                    self.emit_line(self.x, self.y);
703                }
704                self.reset_stack();
705            }
706            // Emits curves that start and end horizontal, unless
707            // the stack count is odd, in which case the first
708            // curve may start with a vertical tangent
709            // Spec: <https://adobe-type-tools.github.io/font-tech-notes/pdfs/5177.Type2.pdf#page=17>
710            // FT: <https://gitlab.freedesktop.org/freetype/freetype/-/blob/80a507a6b8e3d2906ad2c8ba69329bd2fb2a85ef/src/psaux/psintrp.c#L2789>
711            HhCurveTo => {
712                let count1 = self.stack.len();
713                let count = count1 & !2;
714                self.stack_ix = count1 - count;
715                while self.stack_ix < count {
716                    if (count - self.stack_ix) & 1 != 0 {
717                        self.y += self.stack.get_fixed(self.stack_ix)?;
718                        self.stack_ix += 1;
719                    }
720                    self.emit_curves([DxY, DxDy, DxY])?;
721                }
722                self.reset_stack();
723            }
724            // Alternates between curves with horizontal and vertical
725            // tangents
726            // Spec: <https://adobe-type-tools.github.io/font-tech-notes/pdfs/5177.Type2.pdf#page=17>
727            // FT: <https://gitlab.freedesktop.org/freetype/freetype/-/blob/80a507a6b8e3d2906ad2c8ba69329bd2fb2a85ef/src/psaux/psintrp.c#L2834>
728            HvCurveTo | VhCurveTo => {
729                let count1 = self.stack.len();
730                let count = count1 & !2;
731                let mut is_horizontal = operator == HvCurveTo;
732                self.stack_ix = count1 - count;
733                while self.stack_ix < count {
734                    let do_last_delta = count - self.stack_ix == 5;
735                    if is_horizontal {
736                        self.emit_curves([DxY, DxDy, MaybeDxDy(do_last_delta)])?;
737                    } else {
738                        self.emit_curves([XDy, DxDy, DxMaybeDy(do_last_delta)])?;
739                    }
740                    is_horizontal = !is_horizontal;
741                }
742                self.reset_stack();
743            }
744            // Emits a sequence of curves possibly followed by a line
745            // Spec: <https://adobe-type-tools.github.io/font-tech-notes/pdfs/5177.Type2.pdf#page=17>
746            // FT: <https://gitlab.freedesktop.org/freetype/freetype/-/blob/80a507a6b8e3d2906ad2c8ba69329bd2fb2a85ef/src/psaux/psintrp.c#L915>
747            RrCurveTo | RCurveLine => {
748                while self.coords_remaining() >= 6 {
749                    self.emit_curves([DxDy; 3])?;
750                }
751                if operator == RCurveLine {
752                    let [dx, dy] = self.stack.fixed_array::<2>(self.stack_ix)?;
753                    self.x += dx;
754                    self.y += dy;
755                    self.emit_line(self.x, self.y);
756                }
757                self.reset_stack();
758            }
759            // Emits a sequence of lines followed by a curve
760            // Spec: <https://adobe-type-tools.github.io/font-tech-notes/pdfs/5177.Type2.pdf#page=18>
761            // FT: <https://gitlab.freedesktop.org/freetype/freetype/-/blob/80a507a6b8e3d2906ad2c8ba69329bd2fb2a85ef/src/psaux/psintrp.c#L2702>
762            RLineCurve => {
763                while self.coords_remaining() > 6 {
764                    let [dx, dy] = self.stack.fixed_array::<2>(self.stack_ix)?;
765                    self.x += dx;
766                    self.y += dy;
767                    self.emit_line(self.x, self.y);
768                    self.stack_ix += 2;
769                }
770                while self.coords_remaining() >= 6 {
771                    self.emit_curves([DxDy; 3])?;
772                }
773                self.reset_stack();
774            }
775            // Emits curves that start and end vertical, unless
776            // the stack count is odd, in which case the first
777            // curve may start with a horizontal tangent
778            // Spec: <https://adobe-type-tools.github.io/font-tech-notes/pdfs/5177.Type2.pdf#page=18>
779            // FT: <https://gitlab.freedesktop.org/freetype/freetype/-/blob/80a507a6b8e3d2906ad2c8ba69329bd2fb2a85ef/src/psaux/psintrp.c#L2744>
780            VvCurveTo => {
781                let count1 = self.stack.len();
782                let count = count1 & !2;
783                self.stack_ix = count1 - count;
784                while self.stack_ix < count {
785                    if (count - self.stack_ix) & 1 != 0 {
786                        self.x += self.stack.get_fixed(self.stack_ix)?;
787                        self.stack_ix += 1;
788                    }
789                    self.emit_curves([XDy, DxDy, XDy])?;
790                }
791                self.reset_stack();
792            }
793            // Call local or global subroutine
794            // Spec: <https://adobe-type-tools.github.io/font-tech-notes/pdfs/5177.Type2.pdf#page=29>
795            // FT: <https://gitlab.freedesktop.org/freetype/freetype/-/blob/80a507a6b8e3d2906ad2c8ba69329bd2fb2a85ef/src/psaux/psintrp.c#L972>
796            CallSubr | CallGsubr => {
797                let index = self.stack.pop_i32()?;
798                let subr_charstring = if operator == CallSubr {
799                    self.context.subr(index)?
800                } else {
801                    self.context.global_subr(index)?
802                };
803                self.evaluate_impl(subr_charstring, nesting_depth + 1)?;
804            }
805            // Sets the left sidebearing point to (sbx, 0) and the character
806            // width vector to (wx, 0) in character space. Also sets current
807            // point to (sbx, 0).
808            // Spec: <https://adobe-type-tools.github.io/font-tech-notes/pdfs/T1_SPEC.pdf#page=56>
809            // FT: <https://gitlab.freedesktop.org/freetype/freetype/-/blob/80a507a6b8e3d2906ad2c8ba69329bd2fb2a85ef/src/psaux/psintrp.c#L2429>
810            Hsbw => {
811                if self.is_type1 {
812                    let [sbx, wx] = self.stack.fixed_array(0)?;
813                    self.sbx += sbx;
814                    self.x += sbx;
815                    self.wx = wx;
816                    self.seen_width_command = true;
817                    self.have_read_width = true;
818                    self.reset_stack();
819                }
820            }
821            // Standard Encoding Accented Character.
822            // Makes an accented character from two other characters in the
823            // font program.
824            // Spec: <https://adobe-type-tools.github.io/font-tech-notes/pdfs/T1_SPEC.pdf#page=56>
825            // FT: <https://gitlab.freedesktop.org/freetype/freetype/-/blob/80a507a6b8e3d2906ad2c8ba69329bd2fb2a85ef/src/psaux/psintrp.c#L1294>
826            Seac => {
827                if !self.width_only {
828                    self.handle_seac(SeacMode::Explicit, nesting_depth)?;
829                }
830            }
831            // Sets the left sidebearing point to (sbx, sby) and the character
832            // width vector to (wx, wy) in character space. Also sets current
833            // point to (sbx, sby).
834            // Spec: <https://adobe-type-tools.github.io/font-tech-notes/pdfs/T1_SPEC.pdf#page=57>
835            // FT: <https://gitlab.freedesktop.org/freetype/freetype/-/blob/80a507a6b8e3d2906ad2c8ba69329bd2fb2a85ef/src/psaux/psintrp.c#L1496>
836            Sbw => {
837                if self.is_type1 {
838                    let [x, y, wx, _wy] = self.stack.fixed_array(0)?;
839                    self.x += x;
840                    self.y += y;
841                    self.sbx += x;
842                    self.wx = wx;
843                    self.seen_width_command = true;
844                    self.have_read_width = true;
845                    self.reset_stack();
846                }
847            }
848            // Brackets an outline section for dots in letters such as 'i',
849            // 'j' and '!'. Purely metadata that a hinter can use.
850            // Spec: <https://adobe-type-tools.github.io/font-tech-notes/pdfs/T1_SPEC.pdf#page=58>
851            DotSection => {
852                // Nothing to do.
853            }
854            // Declares ranges for three horizontal or vertical stem zones.
855            // Spec: <https://adobe-type-tools.github.io/font-tech-notes/pdfs/T1_SPEC.pdf#page=59>
856            // FT: <https://gitlab.freedesktop.org/freetype/freetype/-/blob/80a507a6b8e3d2906ad2c8ba69329bd2fb2a85ef/src/psaux/psintrp.c#L1199>
857            HStem3 | VStem3 => {
858                // Currently unimplemented.
859                self.reset_stack();
860            }
861            // Division operator.
862            // Spec: <https://adobe-type-tools.github.io/font-tech-notes/pdfs/T1_SPEC.pdf#page=60>
863            // FT: <https://gitlab.freedesktop.org/freetype/freetype/-/blob/80a507a6b8e3d2906ad2c8ba69329bd2fb2a85ef/src/psaux/psintrp.c#L1586>
864            Div => {
865                self.stack.div(self.is_type1)?;
866            }
867            // Arithmetic, logic, storage and stack operators. Deprecated in
868            // Type 2 and forbidden in CFF2, but a font may still state them,
869            // and FreeType evaluates them all.
870            //
871            // None of these clear the stack: they compute a value for the
872            // operator that follows.
873            //
874            // Spec: <https://adobe-type-tools.github.io/font-tech-notes/pdfs/5177.Type2.pdf#page=27>
875            // FT: <https://gitlab.freedesktop.org/freetype/freetype/-/blob/80a507a6b8e3d2906ad2c8ba69329bd2fb2a85ef/src/psaux/psintrp.c#L1252>
876            And | Or | Eq => {
877                let b = self.stack.pop_fixed()?;
878                let a = self.stack.pop_fixed()?;
879                // These answer with an integer, not a fixed point value.
880                let answer = match operator {
881                    And => a != Fixed::ZERO && b != Fixed::ZERO,
882                    Or => a != Fixed::ZERO || b != Fixed::ZERO,
883                    _ => a == b,
884                };
885                self.stack.push(answer as i32)?;
886            }
887            Not => {
888                let a = self.stack.pop_fixed()?;
889                self.stack.push((a == Fixed::ZERO) as i32)?;
890            }
891            Abs | Neg => {
892                let a = self.stack.pop_fixed()?;
893                // The most negative value has no positive counterpart, so it
894                // saturates rather than wrapping back to itself.
895                let answer = if a.to_bits() < -Fixed::MAX.to_bits() {
896                    Fixed::MAX
897                } else if operator == Abs {
898                    Fixed::from_bits(a.to_bits().abs())
899                } else {
900                    Fixed::from_bits(-a.to_bits())
901                };
902                self.stack.push(answer)?;
903            }
904            Add | Sub => {
905                let b = self.stack.pop_fixed()?;
906                let a = self.stack.pop_fixed()?;
907                // Wrapping, as FreeType's ADD_INT32 and SUB_INT32 are.
908                let answer = if operator == Add {
909                    a.to_bits().wrapping_add(b.to_bits())
910                } else {
911                    a.to_bits().wrapping_sub(b.to_bits())
912                };
913                self.stack.push(Fixed::from_bits(answer))?;
914            }
915            Mul => {
916                let b = self.stack.pop_fixed()?;
917                let a = self.stack.pop_fixed()?;
918                self.stack.push(a * b)?;
919            }
920            Sqrt => {
921                let a = self.stack.pop_fixed()?;
922                self.stack.push(fixed_sqrt(a))?;
923            }
924            Drop => {
925                self.stack.pop_fixed()?;
926            }
927            Dup => {
928                let a = self.stack.pop_fixed()?;
929                self.stack.push(a)?;
930                self.stack.push(a)?;
931            }
932            Exch => {
933                self.stack.exch()?;
934            }
935            Index => {
936                let index = self.stack.pop_i32()?;
937                let len = self.stack.len();
938                if len > 0 {
939                    // A negative index names the top, and one past the end
940                    // names the bottom, rather than either being an error.
941                    let from_bottom = if index < 0 {
942                        len - 1
943                    } else if index as usize >= len {
944                        0
945                    } else {
946                        len - 1 - index as usize
947                    };
948                    let value = self.stack.get_fixed(from_bottom)?;
949                    self.stack.push(value)?;
950                }
951            }
952            Roll => {
953                let shift = self.stack.pop_i32()?;
954                let count = self.stack.pop_i32()?;
955                self.stack.roll(count, shift)?;
956            }
957            Put => {
958                let index = self.stack.pop_i32()?;
959                let value = self.stack.pop_fixed()?;
960                // An index the array cannot hold is dropped, not an error.
961                if let Some(slot) = usize::try_from(index)
962                    .ok()
963                    .and_then(|i| self.storage.get_mut(i))
964                {
965                    *slot = value;
966                }
967            }
968            Get => {
969                let index = self.stack.pop_i32()?;
970                // An index the array cannot hold pushes nothing, leaving the
971                // stack one shorter than the charstring expects.
972                if let Some(value) = usize::try_from(index)
973                    .ok()
974                    .and_then(|i| self.storage.get(i))
975                {
976                    self.stack.push(*value)?;
977                }
978            }
979            IfElse => {
980                let cond2 = self.stack.pop_fixed()?;
981                let cond1 = self.stack.pop_fixed()?;
982                let arg2 = self.stack.pop_fixed()?;
983                let arg1 = self.stack.pop_fixed()?;
984                self.stack.push(if cond1 <= cond2 { arg1 } else { arg2 })?;
985            }
986            // Mechanism for making calls into the PostScript interpreter.
987            // Spec: <https://adobe-type-tools.github.io/font-tech-notes/pdfs/T1_SPEC.pdf#page=61>
988            // FT: <https://gitlab.freedesktop.org/freetype/freetype/-/blob/80a507a6b8e3d2906ad2c8ba69329bd2fb2a85ef/src/psaux/psintrp.c#L1644>
989            CallOtherSubr => {
990                let subr_idx = self.stack.pop_i32()?;
991                let num_args = self.stack.pop_i32()? as usize;
992                let weight_vector = self.context.weight_vector();
993                match (subr_idx, num_args) {
994                    // End flex. Emit curves from accumulated vectors on the
995                    // stack.
996                    (0, 3) => {
997                        self.is_flexing = false;
998                        self.ensure_open();
999                        self.handle_flex()?;
1000                    }
1001                    // Begin flex. Accumulate vectors from moveto operators.
1002                    (1, 0) => {
1003                        self.is_flexing = true;
1004                    }
1005                    // Counter control hints.
1006                    // <https://gitlab.freedesktop.org/freetype/freetype/-/blob/80a507a6b8e3d2906ad2c8ba69329bd2fb2a85ef/src/psaux/psintrp.c#L1817>
1007                    (12 | 13, _) => {
1008                        self.reset_stack();
1009                    }
1010                    // Handle blends for multiple masters.
1011                    // <https://gitlab.freedesktop.org/freetype/freetype/-/blob/80a507a6b8e3d2906ad2c8ba69329bd2fb2a85ef/src/psaux/psintrp.c#L1823>
1012                    (14..=18, _) => {
1013                        // A font with no masters has nothing to blend, and
1014                        // FreeType fails the glyph.
1015                        if weight_vector.is_empty() {
1016                            return Err(Error::InvalidOperation);
1017                        }
1018                        self.handle_mm_blend(subr_idx, num_args)?;
1019                    }
1020                    (19..=25 | 27, _) => {
1021                        self.handle_build_char(subr_idx, num_args)?;
1022                    }
1023                    _ => {
1024                        // Unknown othersubr, so simply drop the arguments
1025                        // from the stack and hopefully we can keep going
1026                        self.stack.drop(num_args);
1027                    }
1028                }
1029            }
1030            // Removes a number from the PostScript interpreter stack and
1031            // pushes that number to the BuildChar stack. Only used to
1032            // retrieve results from OtherSubrs procedures and those are
1033            // handled explicitly so this is a nop.
1034            // Spec: <https://adobe-type-tools.github.io/font-tech-notes/pdfs/T1_SPEC.pdf#page=61>
1035            Pop => {
1036                // Nothing to do.
1037            }
1038            // Sets the current point without performing a move command.
1039            // Spec: <https://adobe-type-tools.github.io/font-tech-notes/pdfs/T1_SPEC.pdf#page=62>
1040            // FT: <https://gitlab.freedesktop.org/freetype/freetype/-/blob/80a507a6b8e3d2906ad2c8ba69329bd2fb2a85ef/src/psaux/psintrp.c#L2379>
1041            SetCurrentPoint => {
1042                if self.is_type1 {
1043                    let [x, y] = self.stack.fixed_array(0)?;
1044                    self.x = x;
1045                    self.y = y;
1046                    self.reset_stack();
1047                }
1048            }
1049        }
1050        Ok(true)
1051    }
1052
1053    fn read_width(&mut self) -> Result<(), Error> {
1054        self.wx = self.stack.get_fixed(0)?;
1055        self.seen_width_command = true;
1056        self.have_read_width = true;
1057        Ok(())
1058    }
1059
1060    /// See `endchar` in Appendix C at <https://adobe-type-tools.github.io/font-tech-notes/pdfs/5177.Type2.pdf#page=35>
1061    fn handle_seac(&mut self, mode: SeacMode, nesting_depth: u32) -> Result<(), Error> {
1062        // handle seac operator
1063        if self.in_seac {
1064            return Err(Error::LimitExceeded);
1065        }
1066        self.in_seac = true;
1067        let accent_code = self.stack.pop_i32()?;
1068        let base_code = self.stack.pop_i32()?;
1069        let [base_charstring, accent_charstring] =
1070            self.context.seac_components(base_code, accent_code)?;
1071        let dy = self.stack.pop_fixed()?;
1072        let dx = self.stack.pop_fixed()?;
1073        let sb = if self.is_type1 {
1074            // Type1 has an additional side bearing argument
1075            self.stack.pop_fixed()?
1076        } else if !self.stack.is_empty() && !self.seen_width_command {
1077            self.wx = self.stack.pop_fixed()?;
1078            self.seen_width_command = true;
1079            Fixed::ZERO
1080        } else {
1081            Fixed::ZERO
1082        };
1083        // Save metrics to potentially restore later.
1084        let mut sbx = self.sbx;
1085        let mut wx = self.wx;
1086        let seen_width = self.seen_width_command;
1087        let read_width = self.have_read_width;
1088        struct Component<'a> {
1089            charstring: &'a [u8],
1090            x: Fixed,
1091            y: Fixed,
1092            /// True if we want to use metrics from this component
1093            /// if the original charstring does not provide any
1094            maybe_use_metrics: bool,
1095        }
1096        let x = self.x;
1097        let y = self.y;
1098        // Base components for explicit seac are always 0 in FreeType
1099        let [bx, by] = if mode == SeacMode::Explicit {
1100            [Fixed::ZERO; 2]
1101        } else {
1102            [x, y]
1103        };
1104        let mut components = [
1105            Component {
1106                charstring: base_charstring,
1107                x: bx,
1108                y: by,
1109                // In explicit seac mode, use the metrics of the base component
1110                // if the original charstring didn't provide any
1111                maybe_use_metrics: mode == SeacMode::Explicit,
1112            },
1113            Component {
1114                charstring: accent_charstring,
1115                // Adjustments only for type1 but these will be 0 for type2
1116                // anyway
1117                x: dx + self.sbx - sb,
1118                y: dy,
1119                maybe_use_metrics: false,
1120            },
1121        ];
1122        // FreeType evaluates accent first for implicit seac but base first
1123        // for explicit so swap if necessary.
1124        if mode == SeacMode::Implicit {
1125            components.swap(0, 1);
1126        }
1127        // FreeType calls cf2_interpT2CharString for each component
1128        // which uses a fresh set of stem hints. Since our hinter is in
1129        // a separate crate, we signal this through the sink. Also
1130        // reset our own stem count so we read the correct number of
1131        // bytes for each hint mask instruction.
1132        // See <https://gitlab.freedesktop.org/freetype/freetype/-/blob/80a507a6b8e3d2906ad2c8ba69329bd2fb2a85ef/src/psaux/psintrp.c#L1443>
1133        // and <https://gitlab.freedesktop.org/freetype/freetype/-/blob/80a507a6b8e3d2906ad2c8ba69329bd2fb2a85ef/src/psaux/psintrp.c#L540>
1134        for component in components {
1135            self.reset_stack();
1136            self.seen_width_command = false;
1137            self.sink.clear_hints();
1138            self.stem_count = 0;
1139            self.x = component.x;
1140            self.y = component.y;
1141            self.evaluate_impl(component.charstring, nesting_depth + 1)?;
1142            if component.maybe_use_metrics && !seen_width {
1143                sbx = self.sbx;
1144                wx = self.wx;
1145            }
1146        }
1147        self.seen_width_command = seen_width;
1148        self.have_read_width = read_width;
1149        self.sbx = sbx;
1150        self.wx = wx;
1151        self.in_seac = false;
1152        Ok(())
1153    }
1154
1155    /// Emit two curves for the accumulated flex vectors.
1156    fn handle_flex(&mut self) -> Result<(), Error> {
1157        // FreeType does weird accounting for flex vectors
1158        // that we don't wish to copy so do the equivalent
1159        // thing from fonttools instead:
1160        // <https://github.com/fonttools/fonttools/blob/9cec77d49bdb1a1ca346ac5fefdc5e7c30929026/Lib/fontTools/misc/psCharStrings.py#L1066>
1161        let final_y = self.stack.pop_fixed()?;
1162        let final_x = self.stack.pop_fixed()?;
1163        // Flex height is unused
1164        let _ = self.stack.pop_fixed()?;
1165        let p3y = self.stack.pop_fixed()?;
1166        let p3x = self.stack.pop_fixed()?;
1167        let bcp4y = self.stack.pop_fixed()?;
1168        let bcp4x = self.stack.pop_fixed()?;
1169        let bcp3y = self.stack.pop_fixed()?;
1170        let bcp3x = self.stack.pop_fixed()?;
1171        let p2y = self.stack.pop_fixed()?;
1172        let p2x = self.stack.pop_fixed()?;
1173        let bcp2y = self.stack.pop_fixed()?;
1174        let bcp2x = self.stack.pop_fixed()?;
1175        let bcp1y = self.stack.pop_fixed()?;
1176        let bcp1x = self.stack.pop_fixed()?;
1177        let rpy = self.stack.pop_fixed()?;
1178        let rpx = self.stack.pop_fixed()?;
1179        self.reset_stack();
1180        self.stack.push(bcp1x + rpx)?;
1181        self.stack.push(bcp1y + rpy)?;
1182        self.stack.push(bcp2x)?;
1183        self.stack.push(bcp2y)?;
1184        self.stack.push(p2x)?;
1185        self.stack.push(p2y)?;
1186        self.emit_curves([PointMode::DxDy; 3])?;
1187        self.reset_stack();
1188        self.stack.push(bcp3x)?;
1189        self.stack.push(bcp3y)?;
1190        self.stack.push(bcp4x)?;
1191        self.stack.push(bcp4y)?;
1192        self.stack.push(p3x)?;
1193        self.stack.push(p3y)?;
1194        self.emit_curves([PointMode::DxDy; 3])?;
1195        self.reset_stack();
1196        // Push final position back on the stack
1197        self.stack.push(final_x)?;
1198        self.stack.push(final_y)?;
1199        Ok(())
1200    }
1201
1202    /// Handle point blending for multiple master fonts.
1203    ///
1204    /// See <https://gitlab.freedesktop.org/freetype/freetype/-/blob/80a507a6b8e3d2906ad2c8ba69329bd2fb2a85ef/src/psaux/psintrp.c#L1823>
1205    fn handle_mm_blend(&mut self, subr_idx: i32, num_args: usize) -> Result<(), Error> {
1206        let weight_vector = self.context.weight_vector();
1207        let num_points = (subr_idx - 13) as usize + (subr_idx == 18) as usize;
1208        if num_args != num_points * weight_vector.len() {
1209            return Err(Error::Malformed);
1210        }
1211        // The stack is setup to contain `num_points` values followed
1212        // by `num_points * (num_weights - 1)` deltas for each point.
1213        //
1214        // The blend algorithm is p[0] + d[0]*w[1] + d[1]*w[2]...
1215        // where p = points, d = deltas and w = weights
1216        //
1217        // The first weight is always ignored per FT:
1218        // <https://gitlab.freedesktop.org/freetype/freetype/-/blob/80a507a6b8e3d2906ad2c8ba69329bd2fb2a85ef/src/psaux/psintrp.c#L1880>
1219        let stack_base = self
1220            .stack
1221            .len()
1222            .checked_sub(num_args)
1223            .ok_or(Error::InvalidOperation)?;
1224        let mut delta_idx = stack_base + num_points;
1225        for i in 0..num_points {
1226            let mut val = self.stack.get_fixed(stack_base + i)?;
1227            for &weight in &weight_vector[1..] {
1228                val += self.stack.get_fixed(delta_idx)? * weight;
1229                delta_idx += 1;
1230            }
1231            self.stack.set(stack_base + i, val)?;
1232        }
1233        self.stack.drop(num_args.saturating_sub(num_points));
1234        Ok(())
1235    }
1236
1237    /// Handles the othersubrs that compute with the `BuildCharArray` and the
1238    /// blend, 19 to 25 and 27.
1239    ///
1240    /// Each takes its arguments from the stack and leaves its result there,
1241    /// for the `pop` that follows. A wrong number of arguments, an index the
1242    /// array cannot hold or a division by zero fails the glyph, as in
1243    /// FreeType.
1244    ///
1245    /// The array starts out zeroed for each glyph. FreeType keeps one array
1246    /// for the whole font, so a font that reads an element no earlier
1247    /// othersubr in the glyph wrote can differ.
1248    ///
1249    /// See <https://gitlab.freedesktop.org/freetype/freetype/-/blob/80a507a6b8e3d2906ad2c8ba69329bd2fb2a85ef/src/psaux/psintrp.c#L1911>
1250    fn handle_build_char(&mut self, subr_idx: i32, num_args: usize) -> Result<(), Error> {
1251        let weights = self.context.weight_vector();
1252        let has_blend = !weights.is_empty();
1253        let len = self.context.build_char_len();
1254        match (subr_idx, num_args) {
1255            // `<idx> 1 19 callothersubr` copies the weights into the array,
1256            // starting at `idx`.
1257            (19, 1) if has_blend => {
1258                let idx = self.stack.pop_i32()? as u32 as usize;
1259                if len < weights.len() || len - weights.len() < idx {
1260                    return Err(Error::InvalidOperation);
1261                }
1262                self.build_char(len)[idx..idx + weights.len()].copy_from_slice(weights);
1263            }
1264            // Arithmetic: `<a> <b> 2 n callothersubr pop` pushes the result.
1265            (20..=23, 2) => {
1266                let b = self.stack.pop_fixed()?;
1267                let a = self.stack.pop_fixed()?;
1268                let result = match subr_idx {
1269                    20 => Fixed::from_bits(a.to_bits().wrapping_add(b.to_bits())),
1270                    21 => Fixed::from_bits(a.to_bits().wrapping_sub(b.to_bits())),
1271                    22 => a * b,
1272                    _ if b == Fixed::ZERO => return Err(Error::InvalidOperation),
1273                    _ => a / b,
1274                };
1275                self.stack.push(result)?;
1276            }
1277            // `<val> <idx> 2 24 callothersubr` stores `val` at `idx`.
1278            (24, 2) if has_blend => {
1279                let idx = self.stack.pop_i32()? as u32 as usize;
1280                if idx >= len {
1281                    return Err(Error::InvalidOperation);
1282                }
1283                let value = self.stack.pop_fixed()?;
1284                self.build_char(len)[idx] = value;
1285            }
1286            // `<idx> 1 25 callothersubr pop` pushes the value at `idx`.
1287            (25, 1) if has_blend => {
1288                let idx = self.stack.pop_i32()? as u32 as usize;
1289                if idx >= len {
1290                    return Err(Error::InvalidOperation);
1291                }
1292                let value = self.build_char(len)[idx];
1293                self.stack.push(value)?;
1294            }
1295            // `<a> <b> <c> <d> 4 27 callothersubr pop` pushes `a` if `c <=
1296            // d`, and `b` otherwise.
1297            (27, 4) => {
1298                let d = self.stack.pop_fixed()?;
1299                let c = self.stack.pop_fixed()?;
1300                let b = self.stack.pop_fixed()?;
1301                let a = self.stack.pop_fixed()?;
1302                self.stack.push(if c <= d { a } else { b })?;
1303            }
1304            _ => return Err(Error::InvalidOperation),
1305        }
1306        Ok(())
1307    }
1308
1309    /// Returns the `BuildCharArray`, sized to `len` zeroed elements on first
1310    /// use.
1311    fn build_char(&mut self, len: usize) -> &mut [Fixed] {
1312        if self.build_char.len() != len {
1313            self.build_char.resize(len, Fixed::ZERO);
1314        }
1315        &mut self.build_char
1316    }
1317
1318    fn coords_remaining(&self) -> usize {
1319        // This is overly defensive to avoid overflow but in the case of
1320        // broken fonts, just return 0 when stack_ix > stack_len to prevent
1321        // potential runaway while loops in the evaluator if this wraps
1322        self.stack.len().saturating_sub(self.stack_ix)
1323    }
1324
1325    fn ensure_open(&mut self) {
1326        if !self.is_open {
1327            self.sink.move_to(Fixed::ZERO, Fixed::ZERO);
1328            self.is_open = true;
1329        }
1330    }
1331
1332    fn emit_line(&mut self, x: Fixed, y: Fixed) {
1333        self.ensure_open();
1334        self.sink.line_to(x, y);
1335    }
1336
1337    fn emit_curves<const N: usize>(&mut self, modes: [PointMode; N]) -> Result<(), Error> {
1338        use PointMode::*;
1339        let initial_x = self.x;
1340        let initial_y = self.y;
1341        let mut count = 0;
1342        let mut points = [Point::default(); 2];
1343        self.ensure_open();
1344        for mode in modes {
1345            let stack_used = match mode {
1346                DxDy => {
1347                    self.x += self.stack.get_fixed(self.stack_ix)?;
1348                    self.y += self.stack.get_fixed(self.stack_ix + 1)?;
1349                    2
1350                }
1351                XDy => {
1352                    self.y += self.stack.get_fixed(self.stack_ix)?;
1353                    1
1354                }
1355                DxY => {
1356                    self.x += self.stack.get_fixed(self.stack_ix)?;
1357                    1
1358                }
1359                DxInitialY => {
1360                    self.x += self.stack.get_fixed(self.stack_ix)?;
1361                    self.y = initial_y;
1362                    1
1363                }
1364                // Emits a delta for the coordinate with the larger distance
1365                // from the original value. Sets the other coordinate to the
1366                // original value.
1367                DLargerCoordDist => {
1368                    let delta = self.stack.get_fixed(self.stack_ix)?;
1369                    if (self.x - initial_x).abs() > (self.y - initial_y).abs() {
1370                        self.x += delta;
1371                        self.y = initial_y;
1372                    } else {
1373                        self.y += delta;
1374                        self.x = initial_x;
1375                    }
1376                    1
1377                }
1378                // Apply delta to y if `do_dy` is true.
1379                DxMaybeDy(do_dy) => {
1380                    self.x += self.stack.get_fixed(self.stack_ix)?;
1381                    if do_dy {
1382                        self.y += self.stack.get_fixed(self.stack_ix + 1)?;
1383                        2
1384                    } else {
1385                        1
1386                    }
1387                }
1388                // Apply delta to x if `do_dx` is true.
1389                MaybeDxDy(do_dx) => {
1390                    self.y += self.stack.get_fixed(self.stack_ix)?;
1391                    if do_dx {
1392                        self.x += self.stack.get_fixed(self.stack_ix + 1)?;
1393                        2
1394                    } else {
1395                        1
1396                    }
1397                }
1398            };
1399            self.stack_ix += stack_used;
1400            if count == 2 {
1401                self.sink.curve_to(
1402                    points[0].x,
1403                    points[0].y,
1404                    points[1].x,
1405                    points[1].y,
1406                    self.x,
1407                    self.y,
1408                );
1409                count = 0;
1410            } else {
1411                points[count] = Point::new(self.x, self.y);
1412                count += 1;
1413            }
1414        }
1415        Ok(())
1416    }
1417
1418    fn reset_stack(&mut self) {
1419        self.stack.clear();
1420        self.stack_ix = 0;
1421    }
1422}
1423
1424/// Specifies how point coordinates for a curve are computed.
1425#[derive(Copy, Clone)]
1426enum PointMode {
1427    DxDy,
1428    XDy,
1429    DxY,
1430    DxInitialY,
1431    DLargerCoordDist,
1432    DxMaybeDy(bool),
1433    MaybeDxDy(bool),
1434}
1435
1436/// PostScript charstring operator.
1437///
1438/// See <https://learn.microsoft.com/en-us/typography/opentype/spec/cff2charstr#appendix-a-cff2-charstring-command-codes>
1439// TODO: This is currently missing legacy math and logical operators.
1440// fonttools doesn't even implement these: <https://github.com/fonttools/fonttools/blob/65598197c8afd415781f6667a7fb647c2c987fff/Lib/fontTools/misc/psCharStrings.py#L409>
1441#[derive(Copy, Clone, PartialEq, Eq, Debug)]
1442enum Operator {
1443    HStem,
1444    VStem,
1445    VMoveTo,
1446    RLineTo,
1447    HLineTo,
1448    VLineTo,
1449    RrCurveTo,
1450    CallSubr,
1451    Return,
1452    Hsbw,
1453    EndChar,
1454    VariationStoreIndex,
1455    Blend,
1456    HStemHm,
1457    HintMask,
1458    CntrMask,
1459    RMoveTo,
1460    HMoveTo,
1461    VStemHm,
1462    RCurveLine,
1463    RLineCurve,
1464    VvCurveTo,
1465    HhCurveTo,
1466    CallGsubr,
1467    VhCurveTo,
1468    HvCurveTo,
1469    DotSection,
1470    VStem3,
1471    HStem3,
1472    Seac,
1473    Sbw,
1474    Div,
1475    CallOtherSubr,
1476    Pop,
1477    SetCurrentPoint,
1478    HFlex,
1479    Flex,
1480    HFlex1,
1481    Flex1,
1482    And,
1483    Or,
1484    Not,
1485    Abs,
1486    Add,
1487    Sub,
1488    Neg,
1489    Eq,
1490    Drop,
1491    Put,
1492    Get,
1493    IfElse,
1494    Mul,
1495    Sqrt,
1496    Dup,
1497    Exch,
1498    Index,
1499    Roll,
1500}
1501
1502impl Operator {
1503    #[inline]
1504    fn read(cursor: &mut Cursor, b0: u8) -> Option<Self> {
1505        // Escape opcode for accessing two byte operators
1506        const ESCAPE: u8 = 12;
1507        if b0 == ESCAPE {
1508            Self::from_two_byte_opcode(cursor.read::<u8>().ok()?)
1509        } else {
1510            Self::from_opcode(b0)
1511        }
1512    }
1513
1514    /// Creates an operator from the given opcode.
1515    fn from_opcode(opcode: u8) -> Option<Self> {
1516        use Operator::*;
1517        Some(match opcode {
1518            1 => HStem,
1519            3 => VStem,
1520            4 => VMoveTo,
1521            5 => RLineTo,
1522            6 => HLineTo,
1523            7 => VLineTo,
1524            8 => RrCurveTo,
1525            10 => CallSubr,
1526            11 => Return,
1527            13 => Hsbw,
1528            14 => EndChar,
1529            15 => VariationStoreIndex,
1530            16 => Blend,
1531            18 => HStemHm,
1532            19 => HintMask,
1533            20 => CntrMask,
1534            21 => RMoveTo,
1535            22 => HMoveTo,
1536            23 => VStemHm,
1537            24 => RCurveLine,
1538            25 => RLineCurve,
1539            26 => VvCurveTo,
1540            27 => HhCurveTo,
1541            29 => CallGsubr,
1542            30 => VhCurveTo,
1543            31 => HvCurveTo,
1544            _ => return None,
1545        })
1546    }
1547
1548    /// Creates an operator from the given extended opcode.
1549    ///
1550    /// These are preceded by a byte containing the escape value of 12.
1551    pub fn from_two_byte_opcode(opcode: u8) -> Option<Self> {
1552        use Operator::*;
1553        Some(match opcode {
1554            // `random` (12 23) is deliberately absent. It answers from a
1555            // generator whose state belongs to the subfont and persists
1556            // across glyphs, and its answers are meant to differ from run to
1557            // run, so a charstring cannot depend on any particular one.
1558            0 => DotSection,
1559            1 => VStem3,
1560            2 => HStem3,
1561            3 => And,
1562            4 => Or,
1563            5 => Not,
1564            6 => Seac,
1565            7 => Sbw,
1566            9 => Abs,
1567            10 => Add,
1568            11 => Sub,
1569            12 => Div,
1570            14 => Neg,
1571            15 => Eq,
1572            18 => Drop,
1573            20 => Put,
1574            21 => Get,
1575            22 => IfElse,
1576            24 => Mul,
1577            26 => Sqrt,
1578            27 => Dup,
1579            28 => Exch,
1580            29 => Index,
1581            30 => Roll,
1582            16 => CallOtherSubr,
1583            17 => Pop,
1584            33 => SetCurrentPoint,
1585            34 => HFlex,
1586            35 => Flex,
1587            36 => HFlex1,
1588            37 => Flex1,
1589            _ => return None,
1590        })
1591    }
1592}
1593
1594// Used for scaling sink below
1595const ONE_OVER_64: Fixed = Fixed::from_bits(0x400);
1596
1597/// Command sink adapter that applies a matrix and optional scale.
1598pub struct TransformSink<'a, S> {
1599    inner: &'a mut S,
1600    matrix: Option<FontMatrix>,
1601    scale: Option<Fixed>,
1602}
1603
1604impl<'a, S> TransformSink<'a, S> {
1605    /// Creates a new sink for the given transform.
1606    pub fn new(sink: &'a mut S, transform: Transform) -> Self {
1607        Self::from_matrix_scale(sink, transform.matrix, transform.scale)
1608    }
1609
1610    /// Creates a new sink for the given matrix and optional scale.
1611    pub fn from_matrix_scale(sink: &'a mut S, matrix: FontMatrix, scale: Option<Fixed>) -> Self {
1612        Self {
1613            inner: sink,
1614            matrix: (matrix != FontMatrix::IDENTITY).then_some(matrix),
1615            scale,
1616        }
1617    }
1618
1619    fn transform(&self, x: Fixed, y: Fixed) -> (Fixed, Fixed) {
1620        // The following dance is necessary to exactly match FreeType's
1621        // application of scaling factors. This seems to be the result
1622        // of merging the contributed Adobe code while not breaking the
1623        // FreeType public API.
1624        //
1625        // The first two steps apply to both scaled and unscaled outlines:
1626        //
1627        // 1. Multiply by 1/64
1628        // <https://gitlab.freedesktop.org/freetype/freetype/-/blob/80a507a6b8e3d2906ad2c8ba69329bd2fb2a85ef/src/psaux/psft.c#L284>
1629        let ax = x * ONE_OVER_64;
1630        let ay = y * ONE_OVER_64;
1631        // 2. Truncate the bottom 10 bits. Combined with the division by 64,
1632        // converts to font units.
1633        // <https://gitlab.freedesktop.org/freetype/freetype/-/blob/80a507a6b8e3d2906ad2c8ba69329bd2fb2a85ef/src/psaux/psobjs.c#L2219>
1634        let bx = Fixed::from_bits(ax.to_bits() >> 10);
1635        let by = Fixed::from_bits(ay.to_bits() >> 10);
1636        // 3. Apply the transform. It must be done here to match FreeType.
1637        let (cx, cy) = self
1638            .matrix
1639            .as_ref()
1640            .map(|mat| mat.transform(bx, by))
1641            .unwrap_or((bx, by));
1642        if let Some(scale) = self.scale {
1643            // Scaled case:
1644            // 4. Multiply by the original scale factor (to 26.6)
1645            // <https://gitlab.freedesktop.org/freetype/freetype/-/blob/80a507a6b8e3d2906ad2c8ba69329bd2fb2a85ef/src/cff/cffgload.c#L721>
1646            let dx = cx * scale;
1647            let dy = cy * scale;
1648            // 5. Convert from 26.6 to 16.16
1649            (
1650                Fixed::from_bits(dx.to_bits() << 10),
1651                Fixed::from_bits(dy.to_bits() << 10),
1652            )
1653        } else {
1654            // Unscaled case:
1655            // 4. Convert from integer to 16.16
1656            (
1657                Fixed::from_bits(cx.to_bits() << 16),
1658                Fixed::from_bits(cy.to_bits() << 16),
1659            )
1660        }
1661    }
1662}
1663
1664impl<S: CommandSink> CommandSink for TransformSink<'_, S> {
1665    fn hstem(&mut self, y: Fixed, dy: Fixed) {
1666        self.inner.hstem(y, dy);
1667    }
1668
1669    fn vstem(&mut self, x: Fixed, dx: Fixed) {
1670        self.inner.vstem(x, dx);
1671    }
1672
1673    fn hint_mask(&mut self, mask: &[u8]) {
1674        self.inner.hint_mask(mask);
1675    }
1676
1677    fn counter_mask(&mut self, mask: &[u8]) {
1678        self.inner.counter_mask(mask);
1679    }
1680
1681    fn clear_hints(&mut self) {
1682        self.inner.clear_hints();
1683    }
1684
1685    fn move_to(&mut self, x: Fixed, y: Fixed) {
1686        let (x, y) = self.transform(x, y);
1687        self.inner.move_to(x, y);
1688    }
1689
1690    fn line_to(&mut self, x: Fixed, y: Fixed) {
1691        let (x, y) = self.transform(x, y);
1692        self.inner.line_to(x, y);
1693    }
1694
1695    fn curve_to(&mut self, cx1: Fixed, cy1: Fixed, cx2: Fixed, cy2: Fixed, x: Fixed, y: Fixed) {
1696        let (cx1, cy1) = self.transform(cx1, cy1);
1697        let (cx2, cy2) = self.transform(cx2, cy2);
1698        let (x, y) = self.transform(x, y);
1699        self.inner.curve_to(cx1, cy1, cx2, cy2, x, y);
1700    }
1701
1702    fn close(&mut self) {
1703        self.inner.close();
1704    }
1705
1706    fn finish(&mut self) {
1707        self.inner.finish();
1708    }
1709}
1710
1711#[derive(Copy, Clone)]
1712enum PendingElement {
1713    Move([Fixed; 2]),
1714    Line([Fixed; 2]),
1715    Curve([Fixed; 6]),
1716}
1717
1718impl PendingElement {
1719    fn target_point(&self) -> [Fixed; 2] {
1720        match self {
1721            Self::Move(xy) | Self::Line(xy) => *xy,
1722            Self::Curve([.., x, y]) => [*x, *y],
1723        }
1724    }
1725}
1726
1727/// Command sink adapter that suppresses degenerate move and line commands.
1728///
1729/// FreeType avoids emitting empty contours and zero length lines to prevent
1730/// artifacts when stem darkening is enabled. We don't support stem darkening
1731/// because it's not enabled by any of our clients but we remove the degenerate
1732/// elements regardless to match the output.
1733///
1734/// See <https://gitlab.freedesktop.org/freetype/freetype/-/blob/80a507a6b8e3d2906ad2c8ba69329bd2fb2a85ef/src/psaux/pshints.c#L1786>
1735pub struct NopFilterSink<'a, S> {
1736    is_open: bool,
1737    start: Option<(Fixed, Fixed)>,
1738    pending_element: Option<PendingElement>,
1739    inner: &'a mut S,
1740}
1741
1742impl<'a, S> NopFilterSink<'a, S>
1743where
1744    S: CommandSink,
1745{
1746    /// Creates a new sink that suppresses degenerate move and line commands
1747    /// before forwarding the result to the given inner sink.
1748    pub fn new(inner: &'a mut S) -> Self {
1749        Self {
1750            is_open: false,
1751            start: None,
1752            pending_element: None,
1753            inner,
1754        }
1755    }
1756
1757    fn flush_pending(&mut self, for_close: bool) {
1758        if let Some(pending) = self.pending_element.take() {
1759            match pending {
1760                PendingElement::Move([x, y]) => {
1761                    if !for_close {
1762                        self.is_open = true;
1763                        self.inner.move_to(x, y);
1764                        self.start = Some((x, y));
1765                    }
1766                }
1767                PendingElement::Line([x, y]) => {
1768                    if !for_close || self.start != Some((x, y)) {
1769                        self.inner.line_to(x, y);
1770                    }
1771                }
1772                PendingElement::Curve([cx0, cy0, cx1, cy1, x, y]) => {
1773                    self.inner.curve_to(cx0, cy0, cx1, cy1, x, y);
1774                }
1775            }
1776        }
1777    }
1778}
1779
1780impl<S> CommandSink for NopFilterSink<'_, S>
1781where
1782    S: CommandSink,
1783{
1784    fn hstem(&mut self, y: Fixed, dy: Fixed) {
1785        self.inner.hstem(y, dy);
1786    }
1787
1788    fn vstem(&mut self, x: Fixed, dx: Fixed) {
1789        self.inner.vstem(x, dx);
1790    }
1791
1792    fn hint_mask(&mut self, mask: &[u8]) {
1793        self.inner.hint_mask(mask);
1794    }
1795
1796    fn counter_mask(&mut self, mask: &[u8]) {
1797        self.inner.counter_mask(mask);
1798    }
1799
1800    fn clear_hints(&mut self) {
1801        self.inner.clear_hints();
1802    }
1803
1804    fn move_to(&mut self, x: Fixed, y: Fixed) {
1805        self.pending_element = Some(PendingElement::Move([x, y]));
1806    }
1807
1808    fn line_to(&mut self, x: Fixed, y: Fixed) {
1809        // Omit the line if we're already at the given position
1810        if self
1811            .pending_element
1812            .map(|element| element.target_point() == [x, y])
1813            .unwrap_or_default()
1814        {
1815            return;
1816        }
1817        self.flush_pending(false);
1818        self.pending_element = Some(PendingElement::Line([x, y]));
1819    }
1820
1821    fn curve_to(&mut self, cx1: Fixed, cy1: Fixed, cx2: Fixed, cy2: Fixed, x: Fixed, y: Fixed) {
1822        self.flush_pending(false);
1823        self.pending_element = Some(PendingElement::Curve([cx1, cy1, cx2, cy2, x, y]));
1824    }
1825
1826    fn close(&mut self) {
1827        self.flush_pending(true);
1828        if self.is_open {
1829            self.inner.close();
1830            self.is_open = false;
1831        }
1832    }
1833
1834    fn finish(&mut self) {
1835        self.close();
1836        self.inner.finish();
1837    }
1838}
1839
1840impl<P: PathSink> CommandSink for P {
1841    fn move_to(&mut self, x: Fixed, y: Fixed) {
1842        self.move_to(x.to_f32(), y.to_f32());
1843    }
1844
1845    fn line_to(&mut self, x: Fixed, y: Fixed) {
1846        self.line_to(x.to_f32(), y.to_f32());
1847    }
1848
1849    fn curve_to(&mut self, cx0: Fixed, cy0: Fixed, cx1: Fixed, cy1: Fixed, x: Fixed, y: Fixed) {
1850        self.curve_to(
1851            cx0.to_f32(),
1852            cy0.to_f32(),
1853            cx1.to_f32(),
1854            cy1.to_f32(),
1855            x.to_f32(),
1856            y.to_f32(),
1857        );
1858    }
1859
1860    fn close(&mut self) {
1861        self.close()
1862    }
1863}
1864
1865#[cfg(test)]
1866pub(crate) mod test_helpers {
1867    use super::{CommandSink, Fixed};
1868
1869    #[derive(Copy, Clone, PartialEq, Debug)]
1870    #[allow(clippy::enum_variant_names)]
1871    pub enum Command {
1872        MoveTo(Fixed, Fixed),
1873        LineTo(Fixed, Fixed),
1874        CurveTo(Fixed, Fixed, Fixed, Fixed, Fixed, Fixed),
1875    }
1876
1877    #[derive(PartialEq, Default, Debug)]
1878    pub struct CaptureCommandSink(pub Vec<Command>);
1879
1880    impl CommandSink for CaptureCommandSink {
1881        fn move_to(&mut self, x: Fixed, y: Fixed) {
1882            self.0.push(Command::MoveTo(x, y))
1883        }
1884
1885        fn line_to(&mut self, x: Fixed, y: Fixed) {
1886            self.0.push(Command::LineTo(x, y))
1887        }
1888
1889        fn curve_to(&mut self, cx0: Fixed, cy0: Fixed, cx1: Fixed, cy1: Fixed, x: Fixed, y: Fixed) {
1890            self.0.push(Command::CurveTo(cx0, cy0, cx1, cy1, x, y))
1891        }
1892
1893        fn close(&mut self) {
1894            // For testing purposes, replace the close command
1895            // with a line to the most recent move or (0, 0)
1896            // if none exists
1897            let mut last_move = [Fixed::ZERO; 2];
1898            for command in self.0.iter().rev() {
1899                if let Command::MoveTo(x, y) = command {
1900                    last_move = [*x, *y];
1901                    break;
1902                }
1903            }
1904            self.0.push(Command::LineTo(last_move[0], last_move[1]));
1905        }
1906    }
1907
1908    impl CaptureCommandSink {
1909        pub fn to_svg(&self) -> String {
1910            use core::fmt::Write;
1911            let mut buf = String::default();
1912            for cmd in &self.0 {
1913                if !buf.is_empty() {
1914                    buf.push(' ');
1915                }
1916                match cmd {
1917                    Command::MoveTo(x, y) => write!(buf, "M{},{}", x.to_f32(), y.to_f32()).unwrap(),
1918                    Command::LineTo(x, y) => write!(buf, "L{},{}", x.to_f32(), y.to_f32()).unwrap(),
1919                    Command::CurveTo(x0, y0, x1, y1, x, y) => write!(
1920                        buf,
1921                        "C{},{} {},{} {},{}",
1922                        x0.to_f32(),
1923                        y0.to_f32(),
1924                        x1.to_f32(),
1925                        y1.to_f32(),
1926                        x.to_f32(),
1927                        y.to_f32()
1928                    )
1929                    .unwrap(),
1930                }
1931            }
1932            buf
1933        }
1934    }
1935
1936    #[derive(Default)]
1937    pub struct CharstringCommandCounter(pub usize);
1938
1939    impl CommandSink for CharstringCommandCounter {
1940        fn move_to(&mut self, _x: Fixed, _y: Fixed) {
1941            self.0 += 1;
1942        }
1943
1944        fn line_to(&mut self, _x: Fixed, _y: Fixed) {
1945            self.0 += 1;
1946        }
1947
1948        fn curve_to(
1949            &mut self,
1950            _cx0: Fixed,
1951            _cy0: Fixed,
1952            _cx1: Fixed,
1953            _cy1: Fixed,
1954            _x: Fixed,
1955            _y: Fixed,
1956        ) {
1957            self.0 += 1;
1958        }
1959
1960        fn close(&mut self) {
1961            self.0 += 1;
1962        }
1963    }
1964}
1965
1966#[cfg(test)]
1967mod tests {
1968    use super::{test_helpers::*, *};
1969    use crate::{tables::variations::ItemVariationStore, types::F2Dot14, FontData, FontRead};
1970
1971    #[test]
1972    fn cff2_example_subr() {
1973        use Command::*;
1974        let charstring = &font_test_data::cff2::EXAMPLE[0xc8..=0xe1];
1975        let store =
1976            ItemVariationStore::read(FontData::new(&font_test_data::cff2::EXAMPLE[18..])).unwrap();
1977        let coords = &[F2Dot14::from_f32(0.0)];
1978        let blend_state = BlendState::new(store, coords, 0).unwrap();
1979        let mut commands = CaptureCommandSink::default();
1980        evaluate(
1981            &NullContext(CharstringKind::Type2),
1982            Some(blend_state),
1983            charstring,
1984            &mut commands,
1985        )
1986        .unwrap();
1987        // 50 50 100 1 blend 0 rmoveto
1988        // 500 -100 -200 1 blend hlineto
1989        // 500 vlineto
1990        // -500 100 200 1 blend hlineto
1991        //
1992        // applying blends at default location results in:
1993        // 50 0 rmoveto
1994        // 500 hlineto
1995        // 500 vlineto
1996        // -500 hlineto
1997        //
1998        // applying relative operators:
1999        // 50 0 moveto
2000        // 550 0 lineto
2001        // 550 500 lineto
2002        // 50 500 lineto
2003        let expected = &[
2004            MoveTo(Fixed::from_f64(50.0), Fixed::ZERO),
2005            LineTo(Fixed::from_f64(550.0), Fixed::ZERO),
2006            LineTo(Fixed::from_f64(550.0), Fixed::from_f64(500.0)),
2007            LineTo(Fixed::from_f64(50.0), Fixed::from_f64(500.0)),
2008            LineTo(Fixed::from_f64(50.0), Fixed::ZERO),
2009        ];
2010        assert_eq!(&commands.0, expected);
2011    }
2012
2013    #[test]
2014    fn all_path_ops() {
2015        // This charstring was manually constructed in
2016        // font-test-data/test_data/ttx/charstring_path_ops.ttx
2017        //
2018        // The encoded version was extracted from the font and inlined below
2019        // for simplicity.
2020        //
2021        // The geometry is arbitrary but includes the full set of path
2022        // construction operators:
2023        // --------------------------------------------------------------------
2024        // -137 -632 rmoveto
2025        // 34 -5 20 -6 rlineto
2026        // 1 2 3 hlineto
2027        // -179 -10 3 vlineto
2028        // -30 15 22 8 -50 26 -14 -42 -41 19 -15 25 rrcurveto
2029        // -30 15 22 8 hhcurveto
2030        // 8 -30 15 22 8 hhcurveto
2031        // 24 20 15 41 42 -20 14 -24 -25 -19 -14 -42 -41 19 -15 25 hvcurveto
2032        // 20 vmoveto
2033        // -20 14 -24 -25 -19 -14 4 5 rcurveline
2034        // -20 14 -24 -25 -19 -14 4 5 rlinecurve
2035        // -55 -23 -22 -59 vhcurveto
2036        // -30 15 22 8 vvcurveto
2037        // 8 -30 15 22 8 vvcurveto
2038        // 24 20 15 41 42 -20 14 -24 -25 -19 -14 -42 23 flex
2039        // 24 20 15 41 42 -20 14 hflex
2040        // 13 hmoveto
2041        // 41 42 -20 14 -24 -25 -19 -14 -42 hflex1
2042        // 15 41 42 -20 14 -24 -25 -19 -14 -42 8 flex1
2043        // endchar
2044        let charstring = &[
2045            251, 29, 253, 12, 21, 173, 134, 159, 133, 5, 140, 141, 142, 6, 251, 71, 129, 142, 7,
2046            109, 154, 161, 147, 89, 165, 125, 97, 98, 158, 124, 164, 8, 109, 154, 161, 147, 27,
2047            147, 109, 154, 161, 147, 27, 163, 159, 154, 180, 181, 119, 153, 115, 114, 120, 125, 97,
2048            98, 158, 124, 164, 31, 159, 4, 119, 153, 115, 114, 120, 125, 143, 144, 24, 119, 153,
2049            115, 114, 120, 125, 143, 144, 25, 84, 116, 117, 80, 30, 109, 154, 161, 147, 26, 147,
2050            109, 154, 161, 147, 26, 163, 159, 154, 180, 181, 119, 153, 115, 114, 120, 125, 97, 162,
2051            12, 35, 163, 159, 154, 180, 181, 119, 153, 12, 34, 152, 22, 180, 181, 119, 153, 115,
2052            114, 120, 125, 97, 12, 36, 154, 180, 181, 119, 153, 115, 114, 120, 125, 97, 147, 12,
2053            37, 14,
2054        ];
2055        use Command::*;
2056        let mut commands = CaptureCommandSink::default();
2057        evaluate(
2058            &NullContext(CharstringKind::Type2),
2059            None,
2060            charstring,
2061            &mut commands,
2062        )
2063        .unwrap();
2064        // Expected results from extracted glyph data in
2065        // font-test-data/test_data/extracted/charstring_path_ops-glyphs.txt
2066        // --------------------------------------------------------------------
2067        // m  -137,-632
2068        // l  -103,-637
2069        // l  -83,-643
2070        // l  -82,-643
2071        // l  -82,-641
2072        // l  -79,-641
2073        // l  -79,-820
2074        // l  -89,-820
2075        // l  -89,-817
2076        // c  -119,-802 -97,-794 -147,-768
2077        // c  -161,-810 -202,-791 -217,-766
2078        // c  -247,-766 -232,-744 -224,-744
2079        // c  -254,-736 -239,-714 -231,-714
2080        // c  -207,-714 -187,-699 -187,-658
2081        // c  -187,-616 -207,-602 -231,-602
2082        // c  -256,-602 -275,-616 -275,-658
2083        // c  -275,-699 -256,-714 -231,-714
2084        // l  -137,-632
2085        // m  -231,-694
2086        // c  -251,-680 -275,-705 -294,-719
2087        // l  -290,-714
2088        // l  -310,-700
2089        // c  -334,-725 -353,-739 -349,-734
2090        // c  -349,-789 -372,-811 -431,-811
2091        // c  -431,-841 -416,-819 -416,-811
2092        // c  -408,-841 -393,-819 -393,-811
2093        // c  -369,-791 -354,-750 -312,-770
2094        // c  -298,-794 -323,-813 -337,-855
2095        // c  -313,-855 -293,-840 -252,-840
2096        // c  -210,-840 -230,-855 -216,-855
2097        // l  -231,-694
2098        // m  -203,-855
2099        // c  -162,-813 -182,-799 -206,-799
2100        // c  -231,-799 -250,-813 -292,-855
2101        // c  -277,-814 -235,-834 -221,-858
2102        // c  -246,-877 -260,-919 -292,-911
2103        // l  -203,-855
2104        let expected = &[
2105            MoveTo(Fixed::from_i32(-137), Fixed::from_i32(-632)),
2106            LineTo(Fixed::from_i32(-103), Fixed::from_i32(-637)),
2107            LineTo(Fixed::from_i32(-83), Fixed::from_i32(-643)),
2108            LineTo(Fixed::from_i32(-82), Fixed::from_i32(-643)),
2109            LineTo(Fixed::from_i32(-82), Fixed::from_i32(-641)),
2110            LineTo(Fixed::from_i32(-79), Fixed::from_i32(-641)),
2111            LineTo(Fixed::from_i32(-79), Fixed::from_i32(-820)),
2112            LineTo(Fixed::from_i32(-89), Fixed::from_i32(-820)),
2113            LineTo(Fixed::from_i32(-89), Fixed::from_i32(-817)),
2114            CurveTo(
2115                Fixed::from_i32(-119),
2116                Fixed::from_i32(-802),
2117                Fixed::from_i32(-97),
2118                Fixed::from_i32(-794),
2119                Fixed::from_i32(-147),
2120                Fixed::from_i32(-768),
2121            ),
2122            CurveTo(
2123                Fixed::from_i32(-161),
2124                Fixed::from_i32(-810),
2125                Fixed::from_i32(-202),
2126                Fixed::from_i32(-791),
2127                Fixed::from_i32(-217),
2128                Fixed::from_i32(-766),
2129            ),
2130            CurveTo(
2131                Fixed::from_i32(-247),
2132                Fixed::from_i32(-766),
2133                Fixed::from_i32(-232),
2134                Fixed::from_i32(-744),
2135                Fixed::from_i32(-224),
2136                Fixed::from_i32(-744),
2137            ),
2138            CurveTo(
2139                Fixed::from_i32(-254),
2140                Fixed::from_i32(-736),
2141                Fixed::from_i32(-239),
2142                Fixed::from_i32(-714),
2143                Fixed::from_i32(-231),
2144                Fixed::from_i32(-714),
2145            ),
2146            CurveTo(
2147                Fixed::from_i32(-207),
2148                Fixed::from_i32(-714),
2149                Fixed::from_i32(-187),
2150                Fixed::from_i32(-699),
2151                Fixed::from_i32(-187),
2152                Fixed::from_i32(-658),
2153            ),
2154            CurveTo(
2155                Fixed::from_i32(-187),
2156                Fixed::from_i32(-616),
2157                Fixed::from_i32(-207),
2158                Fixed::from_i32(-602),
2159                Fixed::from_i32(-231),
2160                Fixed::from_i32(-602),
2161            ),
2162            CurveTo(
2163                Fixed::from_i32(-256),
2164                Fixed::from_i32(-602),
2165                Fixed::from_i32(-275),
2166                Fixed::from_i32(-616),
2167                Fixed::from_i32(-275),
2168                Fixed::from_i32(-658),
2169            ),
2170            CurveTo(
2171                Fixed::from_i32(-275),
2172                Fixed::from_i32(-699),
2173                Fixed::from_i32(-256),
2174                Fixed::from_i32(-714),
2175                Fixed::from_i32(-231),
2176                Fixed::from_i32(-714),
2177            ),
2178            LineTo(Fixed::from_i32(-137), Fixed::from_i32(-632)),
2179            MoveTo(Fixed::from_i32(-231), Fixed::from_i32(-694)),
2180            CurveTo(
2181                Fixed::from_i32(-251),
2182                Fixed::from_i32(-680),
2183                Fixed::from_i32(-275),
2184                Fixed::from_i32(-705),
2185                Fixed::from_i32(-294),
2186                Fixed::from_i32(-719),
2187            ),
2188            LineTo(Fixed::from_i32(-290), Fixed::from_i32(-714)),
2189            LineTo(Fixed::from_i32(-310), Fixed::from_i32(-700)),
2190            CurveTo(
2191                Fixed::from_i32(-334),
2192                Fixed::from_i32(-725),
2193                Fixed::from_i32(-353),
2194                Fixed::from_i32(-739),
2195                Fixed::from_i32(-349),
2196                Fixed::from_i32(-734),
2197            ),
2198            CurveTo(
2199                Fixed::from_i32(-349),
2200                Fixed::from_i32(-789),
2201                Fixed::from_i32(-372),
2202                Fixed::from_i32(-811),
2203                Fixed::from_i32(-431),
2204                Fixed::from_i32(-811),
2205            ),
2206            CurveTo(
2207                Fixed::from_i32(-431),
2208                Fixed::from_i32(-841),
2209                Fixed::from_i32(-416),
2210                Fixed::from_i32(-819),
2211                Fixed::from_i32(-416),
2212                Fixed::from_i32(-811),
2213            ),
2214            CurveTo(
2215                Fixed::from_i32(-408),
2216                Fixed::from_i32(-841),
2217                Fixed::from_i32(-393),
2218                Fixed::from_i32(-819),
2219                Fixed::from_i32(-393),
2220                Fixed::from_i32(-811),
2221            ),
2222            CurveTo(
2223                Fixed::from_i32(-369),
2224                Fixed::from_i32(-791),
2225                Fixed::from_i32(-354),
2226                Fixed::from_i32(-750),
2227                Fixed::from_i32(-312),
2228                Fixed::from_i32(-770),
2229            ),
2230            CurveTo(
2231                Fixed::from_i32(-298),
2232                Fixed::from_i32(-794),
2233                Fixed::from_i32(-323),
2234                Fixed::from_i32(-813),
2235                Fixed::from_i32(-337),
2236                Fixed::from_i32(-855),
2237            ),
2238            CurveTo(
2239                Fixed::from_i32(-313),
2240                Fixed::from_i32(-855),
2241                Fixed::from_i32(-293),
2242                Fixed::from_i32(-840),
2243                Fixed::from_i32(-252),
2244                Fixed::from_i32(-840),
2245            ),
2246            CurveTo(
2247                Fixed::from_i32(-210),
2248                Fixed::from_i32(-840),
2249                Fixed::from_i32(-230),
2250                Fixed::from_i32(-855),
2251                Fixed::from_i32(-216),
2252                Fixed::from_i32(-855),
2253            ),
2254            LineTo(Fixed::from_i32(-231), Fixed::from_i32(-694)),
2255            MoveTo(Fixed::from_i32(-203), Fixed::from_i32(-855)),
2256            CurveTo(
2257                Fixed::from_i32(-162),
2258                Fixed::from_i32(-813),
2259                Fixed::from_i32(-182),
2260                Fixed::from_i32(-799),
2261                Fixed::from_i32(-206),
2262                Fixed::from_i32(-799),
2263            ),
2264            CurveTo(
2265                Fixed::from_i32(-231),
2266                Fixed::from_i32(-799),
2267                Fixed::from_i32(-250),
2268                Fixed::from_i32(-813),
2269                Fixed::from_i32(-292),
2270                Fixed::from_i32(-855),
2271            ),
2272            CurveTo(
2273                Fixed::from_i32(-277),
2274                Fixed::from_i32(-814),
2275                Fixed::from_i32(-235),
2276                Fixed::from_i32(-834),
2277                Fixed::from_i32(-221),
2278                Fixed::from_i32(-858),
2279            ),
2280            CurveTo(
2281                Fixed::from_i32(-246),
2282                Fixed::from_i32(-877),
2283                Fixed::from_i32(-260),
2284                Fixed::from_i32(-919),
2285                Fixed::from_i32(-292),
2286                Fixed::from_i32(-911),
2287            ),
2288            LineTo(Fixed::from_i32(-203), Fixed::from_i32(-855)),
2289        ];
2290        assert_eq!(&commands.0, expected);
2291    }
2292
2293    /// Fuzzer caught subtract with overflow
2294    /// <https://g-issues.oss-fuzz.com/issues/383609770>
2295    #[test]
2296    fn coords_remaining_avoid_overflow() {
2297        // Test case:
2298        // Evaluate HHCURVETO operator with 2 elements on the stack
2299        let mut commands = CaptureCommandSink::default();
2300        let mut evaluator =
2301            Evaluator::new(&NullContext(CharstringKind::Type2), None, &mut commands);
2302        evaluator.stack.push(0).unwrap();
2303        evaluator.stack.push(0).unwrap();
2304        let mut cursor = FontData::new(&[]).cursor();
2305        // Just don't panic
2306        let _ = evaluator.evaluate_operator(Operator::HhCurveTo, &mut cursor, 0);
2307    }
2308
2309    #[test]
2310    fn ignore_reserved_operators() {
2311        let charstring = &[
2312            0u8, // reserved
2313            32,  // push -107
2314            22,  // hmoveto
2315            2,   // reserved
2316        ];
2317        let mut commands = CaptureCommandSink::default();
2318        evaluate(
2319            &NullContext(CharstringKind::Type2),
2320            None,
2321            charstring,
2322            &mut commands,
2323        )
2324        .unwrap();
2325        let x = Fixed::from_i32(-107);
2326        assert_eq!(
2327            commands.0,
2328            [
2329                Command::MoveTo(x, Fixed::ZERO),
2330                Command::LineTo(x, Fixed::ZERO)
2331            ]
2332        );
2333    }
2334
2335    #[test]
2336    fn operation_limit() {
2337        // Reserved operators are ignored but still count toward total operations.
2338        // This verifies the guard using a realistic charstring stream
2339        let charstring = vec![0u8; MAX_OPERATIONS as usize + 1];
2340        let mut commands = CaptureCommandSink::default();
2341        // This one should succeed
2342        evaluate(
2343            &NullContext(CharstringKind::Type2),
2344            None,
2345            &charstring[..MAX_OPERATIONS as usize],
2346            &mut commands,
2347        )
2348        .unwrap();
2349        // And this one should fail
2350        let err = evaluate(
2351            &NullContext(CharstringKind::Type2),
2352            None,
2353            &charstring,
2354            &mut commands,
2355        )
2356        .unwrap_err();
2357        assert!(matches!(err, Error::LimitExceeded));
2358    }
2359
2360    #[test]
2361    fn op_div() {
2362        let mut commands = CaptureCommandSink::default();
2363        let mut eval = Evaluator::new(&NullContext(CharstringKind::Type2), None, &mut commands);
2364        let mut cursor = FontData::new(&[]).cursor();
2365        eval.stack.push(Fixed::from_f64(512.5)).unwrap();
2366        eval.stack.push(2).unwrap();
2367        eval.evaluate_operator(Operator::Div, &mut cursor, 0)
2368            .unwrap();
2369        assert_eq!(
2370            eval.stack.pop_fixed().unwrap(),
2371            Fixed::from_f64(512.5 / 2.0)
2372        );
2373    }
2374
2375    #[test]
2376    fn op_div_type1_large_int() {
2377        let mut commands = CaptureCommandSink::default();
2378        let mut eval = Evaluator::new(&NullContext(CharstringKind::Type1), None, &mut commands);
2379        let mut cursor = FontData::new(&[]).cursor();
2380        // Greater than 32,000 which triggers "large int div" behavior for type1.
2381        eval.stack.push(32001).unwrap();
2382        eval.stack.push(2).unwrap();
2383        eval.evaluate_operator(Operator::Div, &mut cursor, 0)
2384            .unwrap();
2385        assert_eq!(
2386            eval.stack.pop_fixed().unwrap(),
2387            Fixed::from_f64(32001.0 / 2.0)
2388        );
2389    }
2390
2391    /// Shared code for the (h)sbw tests.
2392    ///
2393    /// Returns [sbx, wx]
2394    fn eval_h_sbw(operator: Operator, kind: CharstringKind) -> [Fixed; 2] {
2395        let mut commands = CaptureCommandSink::default();
2396        let ctx = &NullContext(kind);
2397        let mut eval = Evaluator::new(ctx, None, &mut commands);
2398        let mut cursor = FontData::new(&[]).cursor();
2399        eval.stack.push(Fixed::from_f64(42.5)).unwrap();
2400        if operator == Operator::Sbw {
2401            // sbw includes y coords
2402            eval.stack.push(0).unwrap();
2403        }
2404        eval.stack.push(501).unwrap();
2405        eval.stack.push(1000).unwrap();
2406        eval.evaluate_operator(operator, &mut cursor, 0).unwrap();
2407        [eval.sbx, eval.wx]
2408    }
2409
2410    #[test]
2411    fn op_sbw_type1() {
2412        let [sbx, wx] = eval_h_sbw(Operator::Sbw, CharstringKind::Type1);
2413        assert_eq!(sbx, Fixed::from_f64(42.5));
2414        assert_eq!(wx, Fixed::from_f64(501.0));
2415    }
2416
2417    #[test]
2418    fn op_hsbw_type1() {
2419        let [sbx, wx] = eval_h_sbw(Operator::Hsbw, CharstringKind::Type1);
2420        assert_eq!(sbx, Fixed::from_f64(42.5));
2421        assert_eq!(wx, Fixed::from_f64(501.0));
2422    }
2423
2424    /// sbw is ignored in type 2
2425    #[test]
2426    fn op_sbw_type2_no_effect() {
2427        let [sbx, wx] = eval_h_sbw(Operator::Sbw, CharstringKind::Type2);
2428        assert_eq!(sbx, Fixed::ZERO);
2429        assert_eq!(wx, Fixed::ZERO);
2430    }
2431
2432    /// hsbw is ignored in type 2
2433    #[test]
2434    fn op_hsbw_type2_no_effect() {
2435        let [sbx, wx] = eval_h_sbw(Operator::Hsbw, CharstringKind::Type2);
2436        assert_eq!(sbx, Fixed::ZERO);
2437        assert_eq!(wx, Fixed::ZERO);
2438    }
2439
2440    #[test]
2441    fn op_callothersubr_flex() {
2442        let mut commands = CaptureCommandSink::default();
2443        let mut eval = Evaluator::new(&NullContext(CharstringKind::Type1), None, &mut commands);
2444        let mut cursor = FontData::new(&[]).cursor();
2445        // push some numbers and optionally evaluate an operator
2446        macro_rules! op {
2447            ($nums:expr) => {
2448                for n in $nums {
2449                    eval.stack.push(n).unwrap();
2450                }
2451            };
2452            ($nums:expr, $op:ident) => {
2453                op!($nums);
2454                eval.evaluate_operator(Operator::$op, &mut cursor, 0)
2455                    .unwrap();
2456            };
2457        }
2458        // Emulate a flex vector call
2459        // begin flex
2460        op!([0, 1], CallOtherSubr);
2461        // emit flex vectors with a series of moves
2462        for vec in [[1, 2]; 7] {
2463            op!(vec, RMoveTo);
2464        }
2465        // flex_height, final_x, final_y
2466        op!([0, 100, 200]);
2467        // end flex
2468        op!([3, 0], CallOtherSubr);
2469        // flex usually ends with a subr call to setcurrentpoint
2470        // which makes use of the final coords pushed to the stack
2471        let none: [i32; 0] = [];
2472        op!(none, SetCurrentPoint);
2473        let expected = [
2474            Command::MoveTo(Fixed::ZERO, Fixed::ZERO),
2475            Command::CurveTo(
2476                Fixed::from_i32(2),
2477                Fixed::from_i32(4),
2478                Fixed::from_i32(3),
2479                Fixed::from_i32(6),
2480                Fixed::from_i32(4),
2481                Fixed::from_i32(8),
2482            ),
2483            Command::CurveTo(
2484                Fixed::from_i32(5),
2485                Fixed::from_i32(10),
2486                Fixed::from_i32(6),
2487                Fixed::from_i32(12),
2488                Fixed::from_i32(7),
2489                Fixed::from_i32(14),
2490            ),
2491        ];
2492        assert_eq!(eval.x, Fixed::from_i32(100));
2493        assert_eq!(eval.y, Fixed::from_i32(200));
2494        assert_eq!(commands.0, expected);
2495    }
2496
2497    struct NullContext(CharstringKind);
2498
2499    impl CharstringContext for NullContext {
2500        fn kind(&self) -> CharstringKind {
2501            self.0
2502        }
2503
2504        fn seac_components(&self, _base_code: i32, _accent_code: i32) -> Result<[&[u8]; 2], Error> {
2505            Err(Error::InvalidOperation)
2506        }
2507
2508        fn global_subr(&self, _index: i32) -> Result<&[u8], Error> {
2509            Err(Error::MissingResource)
2510        }
2511
2512        fn subr(&self, _index: i32) -> Result<&[u8], Error> {
2513            Err(Error::MissingResource)
2514        }
2515    }
2516
2517    #[test]
2518    fn nop_filter_sink() {
2519        let mut commands = CaptureCommandSink::default();
2520        let mut nop_filter = NopFilterSink::new(&mut commands);
2521        let (sx, sy) = (Fixed::from_f64(10.2), Fixed::from_f64(20.4));
2522        // filtered
2523        nop_filter.move_to(Fixed::from_f64(0.0), Fixed::from_f64(0.0));
2524        nop_filter.move_to(sx, sy);
2525        // filtered
2526        nop_filter.line_to(sx, sy);
2527        nop_filter.curve_to(
2528            Fixed::from_f64(5.0),
2529            Fixed::from_f64(-5.0),
2530            Fixed::from_f64(1.5),
2531            Fixed::from_f64(2.0),
2532            Fixed::from_f64(4.5),
2533            Fixed::from_f64(-10.0),
2534        );
2535        // filtered
2536        nop_filter.line_to(Fixed::from_f64(4.5), Fixed::from_f64(-10.0));
2537        // filtered due to next close
2538        nop_filter.line_to(sx, sy);
2539        nop_filter.close();
2540        assert_eq!(
2541            commands.0,
2542            [
2543                Command::MoveTo(sx, sy),
2544                Command::CurveTo(
2545                    Fixed::from_f64(5.0),
2546                    Fixed::from_f64(-5.0),
2547                    Fixed::from_f64(1.5),
2548                    Fixed::from_f64(2.0),
2549                    Fixed::from_f64(4.5),
2550                    Fixed::from_f64(-10.0),
2551                ),
2552                Command::LineTo(sx, sy),
2553            ]
2554        )
2555    }
2556
2557    #[test]
2558    fn scaled_matrix_transform_sink() {
2559        // A few points taken from the test font in <https://github.com/googlefonts/fontations/issues/1581>
2560        // Inputs and expected values extracted from FreeType
2561        let input = [(150i32, 46i32), (176, 8), (217, -13), (267, -13)]
2562            .map(|(x, y)| (Fixed::from_bits(x << 16), Fixed::from_bits(y << 16)));
2563        let expected = [(404, 118i32), (453, 20), (550, -33), (678, -33)]
2564            .map(|(x, y)| (Fixed::from_bits(x << 10), Fixed::from_bits(y << 10)));
2565        let mut dummy = ();
2566        let sink =
2567            TransformSink::from_matrix_scale(&mut dummy, TRANSFORM, Some(Fixed::from_bits(167772)));
2568        let transformed = input.map(|(x, y)| sink.transform(x, y));
2569        assert_eq!(transformed, expected);
2570    }
2571
2572    #[test]
2573    fn unscaled_matrix_transform_sink() {
2574        // A few points taken from the test font in <https://github.com/googlefonts/fontations/issues/1581>
2575        // Inputs and expected values extracted from FreeType
2576        let input = [(150i32, 46i32), (176, 8), (217, -13), (267, -13)]
2577            .map(|(x, y)| (Fixed::from_bits(x << 16), Fixed::from_bits(y << 16)));
2578        let expected = [(158, 46i32), (177, 8), (215, -13), (265, -13)]
2579            .map(|(x, y)| (Fixed::from_bits(x << 16), Fixed::from_bits(y << 16)));
2580        let mut dummy = ();
2581        let sink = TransformSink::from_matrix_scale(&mut dummy, TRANSFORM, None);
2582        let transformed = input.map(|(x, y)| sink.transform(x, y));
2583        assert_eq!(transformed, expected);
2584    }
2585
2586    const TRANSFORM: FontMatrix = FontMatrix::from_elements([
2587        Fixed::ONE,
2588        Fixed::ZERO,
2589        // 0.167007446289062
2590        Fixed::from_bits(10945),
2591        Fixed::ONE,
2592        Fixed::ZERO,
2593        Fixed::ZERO,
2594    ]);
2595
2596    #[test]
2597    fn unscaled_transform_sink_produces_integers() {
2598        let nothing = &mut ();
2599        let sink = TransformSink::new(nothing, Transform::default());
2600        for coord in [50.0, 50.1, 50.125, 50.5, 50.9] {
2601            assert_eq!(
2602                sink.transform(Fixed::from_f64(coord), Fixed::ZERO)
2603                    .0
2604                    .to_f32(),
2605                50.0
2606            );
2607        }
2608    }
2609
2610    #[test]
2611    fn scaled_transform_sink() {
2612        let ppem = 20.0;
2613        let upem = 1000.0;
2614        // match FreeType scaling with intermediate conversion to 26.6
2615        let scale = Fixed::from_bits((ppem * 64.) as i32) / Fixed::from_bits(upem as i32);
2616        let nothing = &mut ();
2617        let sink = TransformSink::from_matrix_scale(nothing, FontMatrix::IDENTITY, Some(scale));
2618        let inputs = [
2619            // input coord, expected scaled output
2620            (0.0, 0.0),
2621            (8.0, 0.15625),
2622            (16.0, 0.3125),
2623            (32.0, 0.640625),
2624            (72.0, 1.4375),
2625            (128.0, 2.5625),
2626        ];
2627        for (coord, expected) in inputs {
2628            assert_eq!(
2629                sink.transform(Fixed::from_f64(coord), Fixed::ZERO)
2630                    .0
2631                    .to_f32(),
2632                expected,
2633                "scaling coord {coord}"
2634            );
2635        }
2636    }
2637
2638    #[test]
2639    fn mm_blend() {
2640        let mut commands = CaptureCommandSink::default();
2641        let ctx = MmContext([0.0, -0.25, 1.0].map(Fixed::from_f64));
2642        let mut eval = Evaluator::new(&ctx, None, &mut commands);
2643        let mut cursor = FontData::new(&[]).cursor();
2644        // First two values are base coords. Next four are deltas
2645        // for those coords (two each). Last two values are arg
2646        // count and othersubr number.
2647        for i in [[1, 0], [2, 3], [4, -8], [6, 15]].into_iter().flatten() {
2648            eval.stack.push(i).unwrap();
2649        }
2650        eval.evaluate_operator(Operator::CallOtherSubr, &mut cursor, 0)
2651            .unwrap();
2652        let [a, b] = eval.stack.fixed_array(0).unwrap();
2653        // a = 1 + -0.25*2 + 1*3 = 3.5
2654        assert_eq!(a.to_f32(), 3.5);
2655        // b = 0 + -0.25*4 + 1*-8 = -9.0
2656        assert_eq!(b.to_f32(), -9.0);
2657    }
2658
2659    struct MmContext([Fixed; 3]);
2660
2661    impl CharstringContext for MmContext {
2662        fn kind(&self) -> CharstringKind {
2663            CharstringKind::Type1
2664        }
2665
2666        fn seac_components(&self, _base_code: i32, _accent_code: i32) -> Result<[&[u8]; 2], Error> {
2667            Err(Error::InvalidOperation)
2668        }
2669
2670        fn global_subr(&self, _index: i32) -> Result<&[u8], Error> {
2671            Err(Error::MissingResource)
2672        }
2673
2674        fn subr(&self, _index: i32) -> Result<&[u8], Error> {
2675            Err(Error::MissingResource)
2676        }
2677
2678        fn weight_vector(&self) -> &[Fixed] {
2679            &self.0
2680        }
2681
2682        fn build_char_len(&self) -> usize {
2683            8
2684        }
2685    }
2686
2687    /// Runs one `callothersubr` after pushing `args`, which end with the
2688    /// argument count and the othersubr number, and returns the stack.
2689    fn othersubr(eval: &mut Evaluator, args: &[f64]) -> Result<Vec<f64>, Error> {
2690        let mut cursor = FontData::new(&[]).cursor();
2691        for &arg in args {
2692            if arg.fract() == 0.0 {
2693                eval.stack.push(arg as i32)?;
2694            } else {
2695                eval.stack.push(Fixed::from_f64(arg))?;
2696            }
2697        }
2698        eval.evaluate_operator(Operator::CallOtherSubr, &mut cursor, 0)?;
2699        let stack = eval.stack.fixed_values().map(|v| v.to_f64()).collect();
2700        Ok(stack)
2701    }
2702
2703    // Othersubrs 20 to 23 and 27 compute without a blend.
2704    #[test]
2705    fn othersubr_arithmetic() {
2706        let ctx = MmContext([Fixed::ZERO; 3]);
2707        for (args, result) in [
2708            (&[7.0, 2.0, 2.0, 20.0][..], 9.0),
2709            (&[7.0, 2.0, 2.0, 21.0], 5.0),
2710            (&[7.0, 2.0, 2.0, 22.0], 14.0),
2711            (&[7.0, 2.0, 2.0, 23.0], 3.5),
2712            (&[1.0, 2.0, 3.0, 4.0, 4.0, 27.0], 1.0),
2713            (&[1.0, 2.0, 5.0, 4.0, 4.0, 27.0], 2.0),
2714        ] {
2715            let mut commands = CaptureCommandSink::default();
2716            let mut eval = Evaluator::new(&ctx, None, &mut commands);
2717            assert_eq!(othersubr(&mut eval, args).unwrap(), [result], "{args:?}");
2718        }
2719        let mut commands = CaptureCommandSink::default();
2720        let mut eval = Evaluator::new(&ctx, None, &mut commands);
2721        // Division by zero and a wrong argument count fail the glyph.
2722        assert!(othersubr(&mut eval, &[7.0, 0.0, 2.0, 23.0]).is_err());
2723        assert!(othersubr(&mut eval, &[7.0, 2.0, 1.0, 20.0]).is_err());
2724    }
2725
2726    // Othersubrs 19, 24 and 25 read and write the `BuildCharArray`.
2727    #[test]
2728    fn othersubr_build_char_array() {
2729        let ctx = MmContext([0.25, 0.5, 0.25].map(Fixed::from_f64));
2730        let mut commands = CaptureCommandSink::default();
2731        let mut eval = Evaluator::new(&ctx, None, &mut commands);
2732        // Store 5 at 3, then read it back.
2733        assert!(othersubr(&mut eval, &[5.0, 3.0, 2.0, 24.0])
2734            .unwrap()
2735            .is_empty());
2736        assert_eq!(othersubr(&mut eval, &[3.0, 1.0, 25.0]).unwrap(), [5.0]);
2737        eval.stack.clear();
2738        // Copy the weights to 2, 3 and 4.
2739        assert!(othersubr(&mut eval, &[2.0, 1.0, 19.0]).unwrap().is_empty());
2740        assert_eq!(othersubr(&mut eval, &[3.0, 1.0, 25.0]).unwrap(), [0.5]);
2741        eval.stack.clear();
2742        // The array has eight elements.
2743        assert!(othersubr(&mut eval, &[1.0, 8.0, 2.0, 24.0]).is_err());
2744        assert!(othersubr(&mut eval, &[8.0, 1.0, 25.0]).is_err());
2745        assert!(othersubr(&mut eval, &[6.0, 1.0, 19.0]).is_err());
2746    }
2747
2748    // A font with no masters has no blend and no array to use.
2749    #[test]
2750    fn othersubrs_without_a_blend_fail() {
2751        struct Plain;
2752        impl CharstringContext for Plain {
2753            fn kind(&self) -> CharstringKind {
2754                CharstringKind::Type1
2755            }
2756            fn seac_components(&self, _: i32, _: i32) -> Result<[&[u8]; 2], Error> {
2757                Err(Error::InvalidOperation)
2758            }
2759            fn global_subr(&self, _: i32) -> Result<&[u8], Error> {
2760                Err(Error::MissingResource)
2761            }
2762            fn subr(&self, _: i32) -> Result<&[u8], Error> {
2763                Err(Error::MissingResource)
2764            }
2765        }
2766        for args in [
2767            &[1.0, 2.0, 2.0, 14.0][..],
2768            &[0.0, 1.0, 19.0],
2769            &[5.0, 0.0, 2.0, 24.0],
2770            &[0.0, 1.0, 25.0],
2771        ] {
2772            let mut commands = CaptureCommandSink::default();
2773            let mut eval = Evaluator::new(&Plain, None, &mut commands);
2774            assert!(othersubr(&mut eval, args).is_err(), "{args:?}");
2775        }
2776        let mut commands = CaptureCommandSink::default();
2777        let mut eval = Evaluator::new(&Plain, None, &mut commands);
2778        assert_eq!(othersubr(&mut eval, &[7.0, 2.0, 2.0, 20.0]).unwrap(), [9.0]);
2779    }
2780
2781    /// Builds a Type 2 charstring from `ops`, then moves by whatever the
2782    /// stack holds so the result is observable as a coordinate.
2783    fn eval(ops: &[u8]) -> Vec<Command> {
2784        let mut charstring = ops.to_vec();
2785        charstring.push(21); // rmoveto
2786        charstring.push(14); // endchar
2787        let mut commands = CaptureCommandSink::default();
2788        evaluate(
2789            &NullContext(CharstringKind::Type2),
2790            None,
2791            &charstring,
2792            &mut commands,
2793        )
2794        .unwrap();
2795        commands.0
2796    }
2797
2798    /// A single number, in the one-byte encoding where it fits.
2799    fn n(v: i32) -> Vec<u8> {
2800        assert!((-107..=107).contains(&v));
2801        std::vec![(v + 139) as u8]
2802    }
2803
2804    fn esc(op: u8) -> Vec<u8> {
2805        std::vec![12, op]
2806    }
2807
2808    /// Runs `a op b` and reports where the move ended up.
2809    fn binary(a: i32, b: i32, op: u8) -> (Fixed, Fixed) {
2810        let mut cs = n(0);
2811        cs.extend(n(a));
2812        cs.extend(n(b));
2813        cs.extend(esc(op));
2814        match eval(&cs).first() {
2815            Some(Command::MoveTo(x, y)) => (*x, *y),
2816            other => panic!("expected a move, got {other:?}"),
2817        }
2818    }
2819
2820    #[test]
2821    fn arithmetic() {
2822        // dx is 0, so the second coordinate carries the answer.
2823        assert_eq!(binary(7, 5, 10).1, Fixed::from_i32(12)); // add
2824        assert_eq!(binary(7, 5, 11).1, Fixed::from_i32(2)); // sub
2825        assert_eq!(binary(7, 5, 24).1, Fixed::from_i32(35)); // mul
2826        assert_eq!(binary(10, 4, 12).1, Fixed::from_f64(2.5)); // div
2827    }
2828
2829    #[test]
2830    fn logic_answers_with_one_or_zero() {
2831        for (a, b, op, expected) in [
2832            (1, 1, 3, 1),
2833            (1, 0, 3, 0),
2834            (0, 0, 3, 0), // and
2835            (1, 0, 4, 1),
2836            (0, 0, 4, 0), // or
2837            (5, 5, 15, 1),
2838            (5, 6, 15, 0), // eq
2839        ] {
2840            assert_eq!(
2841                binary(a, b, op).1,
2842                Fixed::from_i32(expected),
2843                "12 {op} on {a}, {b}"
2844            );
2845        }
2846    }
2847
2848    #[test]
2849    fn unary() {
2850        // not
2851        let mut cs = n(0);
2852        cs.extend(n(0));
2853        cs.extend(esc(5));
2854        assert_eq!(
2855            match eval(&cs).first() {
2856                Some(Command::MoveTo(_, y)) => *y,
2857                _ => panic!(),
2858            },
2859            Fixed::from_i32(1)
2860        );
2861        // abs and neg
2862        for (v, op, expected) in [(-5, 9, 5), (5, 9, 5), (5, 14, -5), (-5, 14, 5)] {
2863            let mut cs = n(0);
2864            cs.extend(n(v));
2865            cs.extend(esc(op));
2866            assert_eq!(
2867                match eval(&cs).first() {
2868                    Some(Command::MoveTo(_, y)) => *y,
2869                    _ => panic!(),
2870                },
2871                Fixed::from_i32(expected),
2872                "12 {op} on {v}"
2873            );
2874        }
2875    }
2876
2877    #[test]
2878    fn sqrt_of_a_square_is_the_root() {
2879        for v in [1i32, 4, 9, 16, 100] {
2880            let mut cs = n(0);
2881            cs.extend(n(v));
2882            cs.extend(esc(26));
2883            let got = match eval(&cs).first() {
2884                Some(Command::MoveTo(_, y)) => *y,
2885                _ => panic!(),
2886            };
2887            // The Babylonian method lands within a hair of the true root.
2888            let want = Fixed::from_f64((v as f64).sqrt());
2889            assert!(
2890                (got.to_bits() - want.to_bits()).abs() <= 2,
2891                "sqrt({v}) = {got:?}, wanted about {want:?}"
2892            );
2893        }
2894        // Nothing that is not positive has a root here.
2895        for v in [0i32, -4] {
2896            let mut cs = n(0);
2897            cs.extend(n(v));
2898            cs.extend(esc(26));
2899            assert_eq!(
2900                match eval(&cs).first() {
2901                    Some(Command::MoveTo(_, y)) => *y,
2902                    _ => panic!(),
2903                },
2904                Fixed::ZERO
2905            );
2906        }
2907    }
2908
2909    #[test]
2910    fn the_stack_operators_rearrange_it() {
2911        // dup: 0 7 dup -> 0 7 7, so the move takes the last two
2912        let mut cs = n(0);
2913        cs.extend(n(7));
2914        cs.extend(esc(27));
2915        assert_eq!(
2916            eval(&cs).first().cloned(),
2917            Some(Command::MoveTo(Fixed::from_i32(7), Fixed::from_i32(7)))
2918        );
2919        // exch: 3 9 exch -> 9 3
2920        let mut cs = n(3);
2921        cs.extend(n(9));
2922        cs.extend(esc(28));
2923        assert_eq!(
2924            eval(&cs).first().cloned(),
2925            Some(Command::MoveTo(Fixed::from_i32(9), Fixed::from_i32(3)))
2926        );
2927        // drop: 4 5 6 drop -> 4 5
2928        let mut cs = n(4);
2929        cs.extend(n(5));
2930        cs.extend(n(6));
2931        cs.extend(esc(18));
2932        assert_eq!(
2933            eval(&cs).first().cloned(),
2934            Some(Command::MoveTo(Fixed::from_i32(4), Fixed::from_i32(5)))
2935        );
2936        // index 0 copies the top: 0 8 0 index -> 0 8 8
2937        let mut cs = n(0);
2938        cs.extend(n(8));
2939        cs.extend(n(0));
2940        cs.extend(esc(29));
2941        assert_eq!(
2942            eval(&cs).first().cloned(),
2943            Some(Command::MoveTo(Fixed::from_i32(8), Fixed::from_i32(8)))
2944        );
2945    }
2946
2947    #[test]
2948    fn roll_rotates_the_values_it_is_given() {
2949        // 1 2 3, roll 3 by 1 -> 3 1 2, so the move takes 1 2
2950        let mut cs = n(1);
2951        cs.extend(n(2));
2952        cs.extend(n(3));
2953        cs.extend(n(3));
2954        cs.extend(n(1));
2955        cs.extend(esc(30));
2956        assert_eq!(
2957            eval(&cs).first().cloned(),
2958            Some(Command::MoveTo(Fixed::from_i32(1), Fixed::from_i32(2)))
2959        );
2960    }
2961
2962    #[test]
2963    fn roll_leaves_the_values_below_alone() {
2964        // 99 1 2, roll 2 by 1 -> 99 2 1, so 99 is the width and the move
2965        // takes 2 1
2966        let mut cs = n(99);
2967        cs.extend(n(1));
2968        cs.extend(n(2));
2969        cs.extend(n(2));
2970        cs.extend(n(1));
2971        cs.extend(esc(30));
2972        assert_eq!(
2973            eval(&cs).first().cloned(),
2974            Some(Command::MoveTo(Fixed::from_i32(2), Fixed::from_i32(1)))
2975        );
2976    }
2977
2978    #[test]
2979    fn the_transient_array_remembers_between_operators() {
2980        // 0, then 42 into slot 3, then read it back
2981        let mut cs = n(0);
2982        cs.extend(n(42));
2983        cs.extend(n(3));
2984        cs.extend(esc(20)); // put
2985        cs.extend(n(3));
2986        cs.extend(esc(21)); // get
2987        assert_eq!(
2988            eval(&cs).first().cloned(),
2989            Some(Command::MoveTo(Fixed::ZERO, Fixed::from_i32(42)))
2990        );
2991    }
2992
2993    #[test]
2994    fn a_slot_the_array_does_not_have_is_ignored() {
2995        // Storing out of range must not disturb the stack or panic.
2996        let mut cs = n(3);
2997        cs.extend(n(9));
2998        cs.extend(n(99));
2999        cs.extend(esc(20)); // put, out of range: drops the value
3000        cs.extend(n(4));
3001        assert_eq!(
3002            eval(&cs).first().cloned(),
3003            Some(Command::MoveTo(Fixed::from_i32(3), Fixed::from_i32(4)))
3004        );
3005    }
3006
3007    #[test]
3008    fn ifelse_picks_the_first_when_the_conditions_are_ordered() {
3009        // args 11 22, conditions 1 2: 1 <= 2, so 11
3010        for (c1, c2, expected) in [(1, 2, 11), (2, 1, 22), (2, 2, 11)] {
3011            let mut cs = n(0);
3012            cs.extend(n(11));
3013            cs.extend(n(22));
3014            cs.extend(n(c1));
3015            cs.extend(n(c2));
3016            cs.extend(esc(22));
3017            assert_eq!(
3018                match eval(&cs).first() {
3019                    Some(Command::MoveTo(_, y)) => *y,
3020                    _ => panic!(),
3021                },
3022                Fixed::from_i32(expected),
3023                "conditions {c1}, {c2}"
3024            );
3025        }
3026    }
3027}