read_fonts/tables/postscript/
charstring.rs

1//! Parsing for PostScript charstrings.
2
3use super::{BlendState, Error, Index, Stack};
4use crate::{
5    types::{Fixed, Point},
6    Cursor,
7};
8
9/// Maximum nesting depth for subroutine calls.
10///
11/// See "Appendix B Type 2 Charstring Implementation Limits" at
12/// <https://adobe-type-tools.github.io/font-tech-notes/pdfs/5177.Type2.pdf#page=33>
13pub const NESTING_DEPTH_LIMIT: u32 = 10;
14
15/// Trait for processing commands resulting from charstring evaluation.
16///
17/// During processing, the path construction operators (see "4.1 Path
18/// Construction Operators" at <https://adobe-type-tools.github.io/font-tech-notes/pdfs/5177.Type2.pdf#page=15>)
19/// are simplified into the basic move, line, curve and close commands.
20///
21/// This also has optional callbacks for processing hint operators. See "4.3
22/// Hint Operators" at <https://adobe-type-tools.github.io/font-tech-notes/pdfs/5177.Type2.pdf#page=21>
23/// for more detail.
24#[allow(unused_variables)]
25pub trait CommandSink {
26    // Path construction operators.
27    fn move_to(&mut self, x: Fixed, y: Fixed);
28    fn line_to(&mut self, x: Fixed, y: Fixed);
29    fn curve_to(&mut self, cx0: Fixed, cy0: Fixed, cx1: Fixed, cy1: Fixed, x: Fixed, y: Fixed);
30    fn close(&mut self);
31    // Hint operators.
32    /// Horizontal stem hint at `y` with height `dy`.
33    fn hstem(&mut self, y: Fixed, dy: Fixed) {}
34    /// Vertical stem hint at `x` with width `dx`.
35    fn vstem(&mut self, x: Fixed, dx: Fixed) {}
36    /// Bitmask defining the hints that should be made active for the
37    /// commands that follow.
38    fn hint_mask(&mut self, mask: &[u8]) {}
39    /// Bitmask defining the counter hints that should be made active for the
40    /// commands that follow.
41    fn counter_mask(&mut self, mask: &[u8]) {}
42}
43
44/// Evaluates the given charstring and emits the resulting commands to the
45/// specified sink.
46///
47/// If the Private DICT associated with this charstring contains local
48/// subroutines, then the `subrs` index must be provided, otherwise
49/// `Error::MissingSubroutines` will be returned if a callsubr operator
50/// is present.
51///
52/// If evaluating a CFF2 charstring and the top-level table contains an
53/// item variation store, then `blend_state` must be provided, otherwise
54/// `Error::MissingBlendState` will be returned if a blend operator is
55/// present.
56pub fn evaluate(
57    charstring_data: &[u8],
58    global_subrs: Index,
59    subrs: Option<Index>,
60    blend_state: Option<BlendState>,
61    sink: &mut impl CommandSink,
62) -> Result<(), Error> {
63    let mut evaluator = Evaluator::new(global_subrs, subrs, blend_state, sink);
64    evaluator.evaluate(charstring_data, 0)?;
65    Ok(())
66}
67
68/// Transient state for evaluating a charstring and handling recursive
69/// subroutine calls.
70struct Evaluator<'a, S> {
71    global_subrs: Index<'a>,
72    subrs: Option<Index<'a>>,
73    blend_state: Option<BlendState<'a>>,
74    sink: &'a mut S,
75    is_open: bool,
76    have_read_width: bool,
77    stem_count: usize,
78    x: Fixed,
79    y: Fixed,
80    stack: Stack,
81    stack_ix: usize,
82}
83
84impl<'a, S> Evaluator<'a, S>
85where
86    S: CommandSink,
87{
88    fn new(
89        global_subrs: Index<'a>,
90        subrs: Option<Index<'a>>,
91        blend_state: Option<BlendState<'a>>,
92        sink: &'a mut S,
93    ) -> Self {
94        Self {
95            global_subrs,
96            subrs,
97            blend_state,
98            sink,
99            is_open: false,
100            have_read_width: false,
101            stem_count: 0,
102            stack: Stack::new(),
103            x: Fixed::ZERO,
104            y: Fixed::ZERO,
105            stack_ix: 0,
106        }
107    }
108
109    fn evaluate(&mut self, charstring_data: &[u8], nesting_depth: u32) -> Result<(), Error> {
110        if nesting_depth > NESTING_DEPTH_LIMIT {
111            return Err(Error::CharstringNestingDepthLimitExceeded);
112        }
113        let mut cursor = crate::FontData::new(charstring_data).cursor();
114        while cursor.remaining_bytes() != 0 {
115            let b0 = cursor.read::<u8>()?;
116            match b0 {
117                // See "3.2 Charstring Number Encoding" <https://adobe-type-tools.github.io/font-tech-notes/pdfs/5177.Type2.pdf#page=12>
118                //
119                // Push an integer to the stack
120                28 | 32..=254 => {
121                    self.stack.push(super::dict::parse_int(&mut cursor, b0)?)?;
122                }
123                // Push a fixed point value to the stack
124                255 => {
125                    let num = Fixed::from_bits(cursor.read::<i32>()?);
126                    self.stack.push(num)?;
127                }
128                _ => {
129                    let operator = Operator::read(&mut cursor, b0)?;
130                    if !self.evaluate_operator(operator, &mut cursor, nesting_depth)? {
131                        break;
132                    }
133                }
134            }
135        }
136        Ok(())
137    }
138
139    /// Evaluates a single charstring operator.
140    ///
141    /// Returns `Ok(true)` if evaluation should continue.
142    fn evaluate_operator(
143        &mut self,
144        operator: Operator,
145        cursor: &mut Cursor,
146        nesting_depth: u32,
147    ) -> Result<bool, Error> {
148        use Operator::*;
149        use PointMode::*;
150        match operator {
151            // The following "flex" operators are intended to emit
152            // either two curves or a straight line depending on
153            // a "flex depth" parameter and the distance from the
154            // joining point to the chord connecting the two
155            // end points. In practice, we just emit the two curves,
156            // following FreeType:
157            // <https://gitlab.freedesktop.org/freetype/freetype/-/blob/80a507a6b8e3d2906ad2c8ba69329bd2fb2a85ef/src/psaux/psintrp.c#L335>
158            //
159            // Spec: <https://adobe-type-tools.github.io/font-tech-notes/pdfs/5177.Type2.pdf#page=18>
160            Flex => {
161                self.emit_curves([DxDy; 6])?;
162                self.reset_stack();
163            }
164            // Spec: <https://adobe-type-tools.github.io/font-tech-notes/pdfs/5177.Type2.pdf#page=19>
165            HFlex => {
166                self.emit_curves([DxY, DxDy, DxY, DxY, DxInitialY, DxY])?;
167                self.reset_stack();
168            }
169            // Spec: <https://adobe-type-tools.github.io/font-tech-notes/pdfs/5177.Type2.pdf#page=19>
170            HFlex1 => {
171                self.emit_curves([DxDy, DxDy, DxY, DxY, DxDy, DxInitialY])?;
172                self.reset_stack();
173            }
174            // Spec: <https://adobe-type-tools.github.io/font-tech-notes/pdfs/5177.Type2.pdf#page=20>
175            Flex1 => {
176                self.emit_curves([DxDy, DxDy, DxDy, DxDy, DxDy, DLargerCoordDist])?;
177                self.reset_stack();
178            }
179            // Set the variation store index
180            // <https://learn.microsoft.com/en-us/typography/opentype/spec/cff2charstr#syntax-for-font-variations-support-operators>
181            VariationStoreIndex => {
182                let blend_state = self.blend_state.as_mut().ok_or(Error::MissingBlendState)?;
183                let store_index = self.stack.pop_i32()? as u16;
184                blend_state.set_store_index(store_index)?;
185            }
186            // Apply blending to the current operand stack
187            // <https://learn.microsoft.com/en-us/typography/opentype/spec/cff2charstr#syntax-for-font-variations-support-operators>
188            Blend => {
189                let blend_state = self.blend_state.as_ref().ok_or(Error::MissingBlendState)?;
190                self.stack.apply_blend(blend_state)?;
191            }
192            // Return from the current subroutine
193            // Spec: <https://adobe-type-tools.github.io/font-tech-notes/pdfs/5177.Type2.pdf#page=29>
194            Return => {
195                return Ok(false);
196            }
197            // End the current charstring
198            // TODO: handle implied 'seac' operator
199            // Spec: <https://adobe-type-tools.github.io/font-tech-notes/pdfs/5177.Type2.pdf#page=21>
200            // FT: <https://gitlab.freedesktop.org/freetype/freetype/-/blob/80a507a6b8e3d2906ad2c8ba69329bd2fb2a85ef/src/psaux/psintrp.c#L2463>
201            EndChar => {
202                if !self.stack.is_empty() && !self.have_read_width {
203                    self.have_read_width = true;
204                    self.stack.clear();
205                }
206                if self.is_open {
207                    self.is_open = false;
208                    self.sink.close();
209                }
210                return Ok(false);
211            }
212            // Emits a sequence of stem hints
213            // Spec: <https://adobe-type-tools.github.io/font-tech-notes/pdfs/5177.Type2.pdf#page=21>
214            // FT: <https://gitlab.freedesktop.org/freetype/freetype/-/blob/80a507a6b8e3d2906ad2c8ba69329bd2fb2a85ef/src/psaux/psintrp.c#L777>
215            HStem | VStem | HStemHm | VStemHm => {
216                let mut i = 0;
217                let len = if self.stack.len_is_odd() && !self.have_read_width {
218                    self.have_read_width = true;
219                    i = 1;
220                    self.stack.len() - 1
221                } else {
222                    self.stack.len()
223                };
224                let is_horizontal = matches!(operator, HStem | HStemHm);
225                let mut u = Fixed::ZERO;
226                while i < self.stack.len() {
227                    let args = self.stack.fixed_array::<2>(i)?;
228                    u += args[0];
229                    let w = args[1];
230                    let v = u.wrapping_add(w);
231                    if is_horizontal {
232                        self.sink.hstem(u, v);
233                    } else {
234                        self.sink.vstem(u, v);
235                    }
236                    u = v;
237                    i += 2;
238                }
239                self.stem_count += len / 2;
240                self.reset_stack();
241            }
242            // Applies a hint or counter mask.
243            // If there are arguments on the stack, this is also an
244            // implied series of VSTEMHM operators.
245            // Hint and counter masks are bitstrings that determine
246            // the currently active set of hints.
247            // Spec: <https://adobe-type-tools.github.io/font-tech-notes/pdfs/5177.Type2.pdf#page=24>
248            // FT: <https://gitlab.freedesktop.org/freetype/freetype/-/blob/80a507a6b8e3d2906ad2c8ba69329bd2fb2a85ef/src/psaux/psintrp.c#L2580>
249            HintMask | CntrMask => {
250                let mut i = 0;
251                let len = if self.stack.len_is_odd() && !self.have_read_width {
252                    self.have_read_width = true;
253                    i = 1;
254                    self.stack.len() - 1
255                } else {
256                    self.stack.len()
257                };
258                let mut u = Fixed::ZERO;
259                while i < self.stack.len() {
260                    let args = self.stack.fixed_array::<2>(i)?;
261                    u += args[0];
262                    let w = args[1];
263                    let v = u + w;
264                    self.sink.vstem(u, v);
265                    u = v;
266                    i += 2;
267                }
268                self.stem_count += len / 2;
269                let count = (self.stem_count + 7) / 8;
270                let mask = cursor.read_array::<u8>(count)?;
271                if operator == HintMask {
272                    self.sink.hint_mask(mask);
273                } else {
274                    self.sink.counter_mask(mask);
275                }
276                self.reset_stack();
277            }
278            // Starts a new subpath
279            // Spec: <https://adobe-type-tools.github.io/font-tech-notes/pdfs/5177.Type2.pdf#page=16>
280            // FT: <https://gitlab.freedesktop.org/freetype/freetype/-/blob/80a507a6b8e3d2906ad2c8ba69329bd2fb2a85ef/src/psaux/psintrp.c#L2653>
281            RMoveTo => {
282                let mut i = 0;
283                if self.stack.len() == 3 && !self.have_read_width {
284                    self.have_read_width = true;
285                    i = 1;
286                }
287                if !self.is_open {
288                    self.is_open = true;
289                } else {
290                    self.sink.close();
291                }
292                let [dx, dy] = self.stack.fixed_array::<2>(i)?;
293                self.x += dx;
294                self.y += dy;
295                self.sink.move_to(self.x, self.y);
296                self.reset_stack();
297            }
298            // Starts a new subpath by moving the current point in the
299            // horizontal or vertical direction
300            // Spec: <https://adobe-type-tools.github.io/font-tech-notes/pdfs/5177.Type2.pdf#page=16>
301            // FT: <https://gitlab.freedesktop.org/freetype/freetype/-/blob/80a507a6b8e3d2906ad2c8ba69329bd2fb2a85ef/src/psaux/psintrp.c#L839>
302            HMoveTo | VMoveTo => {
303                let mut i = 0;
304                if self.stack.len() == 2 && !self.have_read_width {
305                    self.have_read_width = true;
306                    i = 1;
307                }
308                if !self.is_open {
309                    self.is_open = true;
310                } else {
311                    self.sink.close();
312                }
313                let delta = self.stack.get_fixed(i)?;
314                if operator == HMoveTo {
315                    self.x += delta;
316                } else {
317                    self.y += delta;
318                }
319                self.sink.move_to(self.x, self.y);
320                self.reset_stack();
321            }
322            // Emits a sequence of lines
323            // Spec: <https://adobe-type-tools.github.io/font-tech-notes/pdfs/5177.Type2.pdf#page=16>
324            // FT: <https://gitlab.freedesktop.org/freetype/freetype/-/blob/80a507a6b8e3d2906ad2c8ba69329bd2fb2a85ef/src/psaux/psintrp.c#L863>
325            RLineTo => {
326                let mut i = 0;
327                while i < self.stack.len() {
328                    let [dx, dy] = self.stack.fixed_array::<2>(i)?;
329                    self.x += dx;
330                    self.y += dy;
331                    self.sink.line_to(self.x, self.y);
332                    i += 2;
333                }
334                self.reset_stack();
335            }
336            // Emits a sequence of alternating horizontal and vertical
337            // lines
338            // Spec: <https://adobe-type-tools.github.io/font-tech-notes/pdfs/5177.Type2.pdf#page=16>
339            // FT: <https://gitlab.freedesktop.org/freetype/freetype/-/blob/80a507a6b8e3d2906ad2c8ba69329bd2fb2a85ef/src/psaux/psintrp.c#L885>
340            HLineTo | VLineTo => {
341                let mut is_x = operator == HLineTo;
342                for i in 0..self.stack.len() {
343                    let delta = self.stack.get_fixed(i)?;
344                    if is_x {
345                        self.x += delta;
346                    } else {
347                        self.y += delta;
348                    }
349                    is_x = !is_x;
350                    self.sink.line_to(self.x, self.y);
351                }
352                self.reset_stack();
353            }
354            // Emits curves that start and end horizontal, unless
355            // the stack count is odd, in which case the first
356            // curve may start with a vertical tangent
357            // Spec: <https://adobe-type-tools.github.io/font-tech-notes/pdfs/5177.Type2.pdf#page=17>
358            // FT: <https://gitlab.freedesktop.org/freetype/freetype/-/blob/80a507a6b8e3d2906ad2c8ba69329bd2fb2a85ef/src/psaux/psintrp.c#L2789>
359            HhCurveTo => {
360                if self.stack.len_is_odd() {
361                    self.y += self.stack.get_fixed(0)?;
362                    self.stack_ix = 1;
363                }
364                // We need at least 4 coordinates to emit these curves
365                while self.coords_remaining() >= 4 {
366                    self.emit_curves([DxY, DxDy, DxY])?;
367                }
368                self.reset_stack();
369            }
370            // Alternates between curves with horizontal and vertical
371            // tangents
372            // Spec: <https://adobe-type-tools.github.io/font-tech-notes/pdfs/5177.Type2.pdf#page=17>
373            // FT: <https://gitlab.freedesktop.org/freetype/freetype/-/blob/80a507a6b8e3d2906ad2c8ba69329bd2fb2a85ef/src/psaux/psintrp.c#L2834>
374            HvCurveTo | VhCurveTo => {
375                let count1 = self.stack.len();
376                let count = count1 & !2;
377                let mut is_horizontal = operator == HvCurveTo;
378                self.stack_ix = count1 - count;
379                while self.stack_ix < count {
380                    let do_last_delta = count - self.stack_ix == 5;
381                    if is_horizontal {
382                        self.emit_curves([DxY, DxDy, MaybeDxDy(do_last_delta)])?;
383                    } else {
384                        self.emit_curves([XDy, DxDy, DxMaybeDy(do_last_delta)])?;
385                    }
386                    is_horizontal = !is_horizontal;
387                }
388                self.reset_stack();
389            }
390            // Emits a sequence of curves possibly followed by a line
391            // Spec: <https://adobe-type-tools.github.io/font-tech-notes/pdfs/5177.Type2.pdf#page=17>
392            // FT: <https://gitlab.freedesktop.org/freetype/freetype/-/blob/80a507a6b8e3d2906ad2c8ba69329bd2fb2a85ef/src/psaux/psintrp.c#L915>
393            RrCurveTo | RCurveLine => {
394                while self.coords_remaining() >= 6 {
395                    self.emit_curves([DxDy; 3])?;
396                }
397                if operator == RCurveLine {
398                    let [dx, dy] = self.stack.fixed_array::<2>(self.stack_ix)?;
399                    self.x += dx;
400                    self.y += dy;
401                    self.sink.line_to(self.x, self.y);
402                }
403                self.reset_stack();
404            }
405            // Emits a sequence of lines followed by a curve
406            // Spec: <https://adobe-type-tools.github.io/font-tech-notes/pdfs/5177.Type2.pdf#page=18>
407            // FT: <https://gitlab.freedesktop.org/freetype/freetype/-/blob/80a507a6b8e3d2906ad2c8ba69329bd2fb2a85ef/src/psaux/psintrp.c#L2702>
408            RLineCurve => {
409                while self.coords_remaining() > 6 {
410                    let [dx, dy] = self.stack.fixed_array::<2>(self.stack_ix)?;
411                    self.x += dx;
412                    self.y += dy;
413                    self.sink.line_to(self.x, self.y);
414                    self.stack_ix += 2;
415                }
416                self.emit_curves([DxDy; 3])?;
417                self.reset_stack();
418            }
419            // Emits curves that start and end vertical, unless
420            // the stack count is odd, in which case the first
421            // curve may start with a horizontal tangent
422            // Spec: <https://adobe-type-tools.github.io/font-tech-notes/pdfs/5177.Type2.pdf#page=18>
423            // FT: <https://gitlab.freedesktop.org/freetype/freetype/-/blob/80a507a6b8e3d2906ad2c8ba69329bd2fb2a85ef/src/psaux/psintrp.c#L2744>
424            VvCurveTo => {
425                if self.stack.len_is_odd() {
426                    self.x += self.stack.get_fixed(0)?;
427                    self.stack_ix = 1;
428                }
429                while self.coords_remaining() > 0 {
430                    self.emit_curves([XDy, DxDy, XDy])?;
431                }
432                self.reset_stack();
433            }
434            // Call local or global subroutine
435            // Spec: <https://adobe-type-tools.github.io/font-tech-notes/pdfs/5177.Type2.pdf#page=29>
436            // FT: <https://gitlab.freedesktop.org/freetype/freetype/-/blob/80a507a6b8e3d2906ad2c8ba69329bd2fb2a85ef/src/psaux/psintrp.c#L972>
437            CallSubr | CallGsubr => {
438                let subrs_index = if operator == CallSubr {
439                    self.subrs.as_ref().ok_or(Error::MissingSubroutines)?
440                } else {
441                    &self.global_subrs
442                };
443                let biased_index = (self.stack.pop_i32()? + subrs_index.subr_bias()) as usize;
444                let subr_charstring_data = subrs_index.get(biased_index)?;
445                self.evaluate(subr_charstring_data, nesting_depth + 1)?;
446            }
447        }
448        Ok(true)
449    }
450
451    fn coords_remaining(&self) -> usize {
452        // This is overly defensive to avoid overflow but in the case of
453        // broken fonts, just return 0 when stack_ix > stack_len to prevent
454        // potential runaway while loops in the evaluator if this wraps
455        self.stack.len().saturating_sub(self.stack_ix)
456    }
457
458    fn emit_curves<const N: usize>(&mut self, modes: [PointMode; N]) -> Result<(), Error> {
459        use PointMode::*;
460        let initial_x = self.x;
461        let initial_y = self.y;
462        let mut count = 0;
463        let mut points = [Point::default(); 2];
464        for mode in modes {
465            let stack_used = match mode {
466                DxDy => {
467                    self.x += self.stack.get_fixed(self.stack_ix)?;
468                    self.y += self.stack.get_fixed(self.stack_ix + 1)?;
469                    2
470                }
471                XDy => {
472                    self.y += self.stack.get_fixed(self.stack_ix)?;
473                    1
474                }
475                DxY => {
476                    self.x += self.stack.get_fixed(self.stack_ix)?;
477                    1
478                }
479                DxInitialY => {
480                    self.x += self.stack.get_fixed(self.stack_ix)?;
481                    self.y = initial_y;
482                    1
483                }
484                // Emits a delta for the coordinate with the larger distance
485                // from the original value. Sets the other coordinate to the
486                // original value.
487                DLargerCoordDist => {
488                    let delta = self.stack.get_fixed(self.stack_ix)?;
489                    if (self.x - initial_x).abs() > (self.y - initial_y).abs() {
490                        self.x += delta;
491                        self.y = initial_y;
492                    } else {
493                        self.y += delta;
494                        self.x = initial_x;
495                    }
496                    1
497                }
498                // Apply delta to y if `do_dy` is true.
499                DxMaybeDy(do_dy) => {
500                    self.x += self.stack.get_fixed(self.stack_ix)?;
501                    if do_dy {
502                        self.y += self.stack.get_fixed(self.stack_ix + 1)?;
503                        2
504                    } else {
505                        1
506                    }
507                }
508                // Apply delta to x if `do_dx` is true.
509                MaybeDxDy(do_dx) => {
510                    self.y += self.stack.get_fixed(self.stack_ix)?;
511                    if do_dx {
512                        self.x += self.stack.get_fixed(self.stack_ix + 1)?;
513                        2
514                    } else {
515                        1
516                    }
517                }
518            };
519            self.stack_ix += stack_used;
520            if count == 2 {
521                self.sink.curve_to(
522                    points[0].x,
523                    points[0].y,
524                    points[1].x,
525                    points[1].y,
526                    self.x,
527                    self.y,
528                );
529                count = 0;
530            } else {
531                points[count] = Point::new(self.x, self.y);
532                count += 1;
533            }
534        }
535        Ok(())
536    }
537
538    fn reset_stack(&mut self) {
539        self.stack.clear();
540        self.stack_ix = 0;
541    }
542}
543
544/// Specifies how point coordinates for a curve are computed.
545#[derive(Copy, Clone)]
546enum PointMode {
547    DxDy,
548    XDy,
549    DxY,
550    DxInitialY,
551    DLargerCoordDist,
552    DxMaybeDy(bool),
553    MaybeDxDy(bool),
554}
555
556/// PostScript charstring operator.
557///
558/// See <https://learn.microsoft.com/en-us/typography/opentype/spec/cff2charstr#appendix-a-cff2-charstring-command-codes>
559// TODO: This is currently missing legacy math and logical operators.
560// fonttools doesn't even implement these: <https://github.com/fonttools/fonttools/blob/65598197c8afd415781f6667a7fb647c2c987fff/Lib/fontTools/misc/psCharStrings.py#L409>
561#[derive(Copy, Clone, PartialEq, Eq, Debug)]
562enum Operator {
563    HStem,
564    VStem,
565    VMoveTo,
566    RLineTo,
567    HLineTo,
568    VLineTo,
569    RrCurveTo,
570    CallSubr,
571    Return,
572    EndChar,
573    VariationStoreIndex,
574    Blend,
575    HStemHm,
576    HintMask,
577    CntrMask,
578    RMoveTo,
579    HMoveTo,
580    VStemHm,
581    RCurveLine,
582    RLineCurve,
583    VvCurveTo,
584    HhCurveTo,
585    CallGsubr,
586    VhCurveTo,
587    HvCurveTo,
588    HFlex,
589    Flex,
590    HFlex1,
591    Flex1,
592}
593
594impl Operator {
595    fn read(cursor: &mut Cursor, b0: u8) -> Result<Self, Error> {
596        // Escape opcode for accessing two byte operators
597        const ESCAPE: u8 = 12;
598        let (opcode, operator) = if b0 == ESCAPE {
599            let b1 = cursor.read::<u8>()?;
600            (b1, Self::from_two_byte_opcode(b1))
601        } else {
602            (b0, Self::from_opcode(b0))
603        };
604        operator.ok_or(Error::InvalidCharstringOperator(opcode))
605    }
606
607    /// Creates an operator from the given opcode.
608    fn from_opcode(opcode: u8) -> Option<Self> {
609        use Operator::*;
610        Some(match opcode {
611            1 => HStem,
612            3 => VStem,
613            4 => VMoveTo,
614            5 => RLineTo,
615            6 => HLineTo,
616            7 => VLineTo,
617            8 => RrCurveTo,
618            10 => CallSubr,
619            11 => Return,
620            14 => EndChar,
621            15 => VariationStoreIndex,
622            16 => Blend,
623            18 => HStemHm,
624            19 => HintMask,
625            20 => CntrMask,
626            21 => RMoveTo,
627            22 => HMoveTo,
628            23 => VStemHm,
629            24 => RCurveLine,
630            25 => RLineCurve,
631            26 => VvCurveTo,
632            27 => HhCurveTo,
633            29 => CallGsubr,
634            30 => VhCurveTo,
635            31 => HvCurveTo,
636            _ => return None,
637        })
638    }
639
640    /// Creates an operator from the given extended opcode.
641    ///
642    /// These are preceded by a byte containing the escape value of 12.
643    pub fn from_two_byte_opcode(opcode: u8) -> Option<Self> {
644        use Operator::*;
645        Some(match opcode {
646            34 => HFlex,
647            35 => Flex,
648            36 => HFlex1,
649            37 => Flex1,
650            _ => return None,
651        })
652    }
653}
654
655#[cfg(test)]
656mod tests {
657    use super::*;
658    use crate::{tables::variations::ItemVariationStore, types::F2Dot14, FontData, FontRead};
659
660    #[derive(Copy, Clone, PartialEq, Debug)]
661    #[allow(clippy::enum_variant_names)]
662    enum Command {
663        MoveTo(Fixed, Fixed),
664        LineTo(Fixed, Fixed),
665        CurveTo(Fixed, Fixed, Fixed, Fixed, Fixed, Fixed),
666    }
667
668    #[derive(PartialEq, Default, Debug)]
669    struct CaptureCommandSink(Vec<Command>);
670
671    impl CommandSink for CaptureCommandSink {
672        fn move_to(&mut self, x: Fixed, y: Fixed) {
673            self.0.push(Command::MoveTo(x, y))
674        }
675
676        fn line_to(&mut self, x: Fixed, y: Fixed) {
677            self.0.push(Command::LineTo(x, y))
678        }
679
680        fn curve_to(&mut self, cx0: Fixed, cy0: Fixed, cx1: Fixed, cy1: Fixed, x: Fixed, y: Fixed) {
681            self.0.push(Command::CurveTo(cx0, cy0, cx1, cy1, x, y))
682        }
683
684        fn close(&mut self) {
685            // For testing purposes, replace the close command
686            // with a line to the most recent move or (0, 0)
687            // if none exists
688            let mut last_move = [Fixed::ZERO; 2];
689            for command in self.0.iter().rev() {
690                if let Command::MoveTo(x, y) = command {
691                    last_move = [*x, *y];
692                    break;
693                }
694            }
695            self.0.push(Command::LineTo(last_move[0], last_move[1]));
696        }
697    }
698
699    #[test]
700    fn cff2_example_subr() {
701        use Command::*;
702        let charstring = &font_test_data::cff2::EXAMPLE[0xc8..=0xe1];
703        let empty_index_bytes = [0u8; 8];
704        let store =
705            ItemVariationStore::read(FontData::new(&font_test_data::cff2::EXAMPLE[18..])).unwrap();
706        let global_subrs = Index::new(&empty_index_bytes, true).unwrap();
707        let coords = &[F2Dot14::from_f32(0.0)];
708        let blend_state = BlendState::new(store, coords, 0).unwrap();
709        let mut commands = CaptureCommandSink::default();
710        evaluate(
711            charstring,
712            global_subrs,
713            None,
714            Some(blend_state),
715            &mut commands,
716        )
717        .unwrap();
718        // 50 50 100 1 blend 0 rmoveto
719        // 500 -100 -200 1 blend hlineto
720        // 500 vlineto
721        // -500 100 200 1 blend hlineto
722        //
723        // applying blends at default location results in:
724        // 50 0 rmoveto
725        // 500 hlineto
726        // 500 vlineto
727        // -500 hlineto
728        //
729        // applying relative operators:
730        // 50 0 moveto
731        // 550 0 lineto
732        // 550 500 lineto
733        // 50 500 lineto
734        let expected = &[
735            MoveTo(Fixed::from_f64(50.0), Fixed::ZERO),
736            LineTo(Fixed::from_f64(550.0), Fixed::ZERO),
737            LineTo(Fixed::from_f64(550.0), Fixed::from_f64(500.0)),
738            LineTo(Fixed::from_f64(50.0), Fixed::from_f64(500.0)),
739        ];
740        assert_eq!(&commands.0, expected);
741    }
742
743    #[test]
744    fn all_path_ops() {
745        // This charstring was manually constructed in
746        // font-test-data/test_data/ttx/charstring_path_ops.ttx
747        //
748        // The encoded version was extracted from the font and inlined below
749        // for simplicity.
750        //
751        // The geometry is arbitrary but includes the full set of path
752        // construction operators:
753        // --------------------------------------------------------------------
754        // -137 -632 rmoveto
755        // 34 -5 20 -6 rlineto
756        // 1 2 3 hlineto
757        // -179 -10 3 vlineto
758        // -30 15 22 8 -50 26 -14 -42 -41 19 -15 25 rrcurveto
759        // -30 15 22 8 hhcurveto
760        // 8 -30 15 22 8 hhcurveto
761        // 24 20 15 41 42 -20 14 -24 -25 -19 -14 -42 -41 19 -15 25 hvcurveto
762        // 20 vmoveto
763        // -20 14 -24 -25 -19 -14 4 5 rcurveline
764        // -20 14 -24 -25 -19 -14 4 5 rlinecurve
765        // -55 -23 -22 -59 vhcurveto
766        // -30 15 22 8 vvcurveto
767        // 8 -30 15 22 8 vvcurveto
768        // 24 20 15 41 42 -20 14 -24 -25 -19 -14 -42 23 flex
769        // 24 20 15 41 42 -20 14 hflex
770        // 13 hmoveto
771        // 41 42 -20 14 -24 -25 -19 -14 -42 hflex1
772        // 15 41 42 -20 14 -24 -25 -19 -14 -42 8 flex1
773        // endchar
774        let charstring = &[
775            251, 29, 253, 12, 21, 173, 134, 159, 133, 5, 140, 141, 142, 6, 251, 71, 129, 142, 7,
776            109, 154, 161, 147, 89, 165, 125, 97, 98, 158, 124, 164, 8, 109, 154, 161, 147, 27,
777            147, 109, 154, 161, 147, 27, 163, 159, 154, 180, 181, 119, 153, 115, 114, 120, 125, 97,
778            98, 158, 124, 164, 31, 159, 4, 119, 153, 115, 114, 120, 125, 143, 144, 24, 119, 153,
779            115, 114, 120, 125, 143, 144, 25, 84, 116, 117, 80, 30, 109, 154, 161, 147, 26, 147,
780            109, 154, 161, 147, 26, 163, 159, 154, 180, 181, 119, 153, 115, 114, 120, 125, 97, 162,
781            12, 35, 163, 159, 154, 180, 181, 119, 153, 12, 34, 152, 22, 180, 181, 119, 153, 115,
782            114, 120, 125, 97, 12, 36, 154, 180, 181, 119, 153, 115, 114, 120, 125, 97, 147, 12,
783            37, 14,
784        ];
785        let empty_index_bytes = [0u8; 8];
786        let global_subrs = Index::new(&empty_index_bytes, false).unwrap();
787        use Command::*;
788        let mut commands = CaptureCommandSink::default();
789        evaluate(charstring, global_subrs, None, None, &mut commands).unwrap();
790        // Expected results from extracted glyph data in
791        // font-test-data/test_data/extracted/charstring_path_ops-glyphs.txt
792        // --------------------------------------------------------------------
793        // m  -137,-632
794        // l  -103,-637
795        // l  -83,-643
796        // l  -82,-643
797        // l  -82,-641
798        // l  -79,-641
799        // l  -79,-820
800        // l  -89,-820
801        // l  -89,-817
802        // c  -119,-802 -97,-794 -147,-768
803        // c  -161,-810 -202,-791 -217,-766
804        // c  -247,-766 -232,-744 -224,-744
805        // c  -254,-736 -239,-714 -231,-714
806        // c  -207,-714 -187,-699 -187,-658
807        // c  -187,-616 -207,-602 -231,-602
808        // c  -256,-602 -275,-616 -275,-658
809        // c  -275,-699 -256,-714 -231,-714
810        // l  -137,-632
811        // m  -231,-694
812        // c  -251,-680 -275,-705 -294,-719
813        // l  -290,-714
814        // l  -310,-700
815        // c  -334,-725 -353,-739 -349,-734
816        // c  -349,-789 -372,-811 -431,-811
817        // c  -431,-841 -416,-819 -416,-811
818        // c  -408,-841 -393,-819 -393,-811
819        // c  -369,-791 -354,-750 -312,-770
820        // c  -298,-794 -323,-813 -337,-855
821        // c  -313,-855 -293,-840 -252,-840
822        // c  -210,-840 -230,-855 -216,-855
823        // l  -231,-694
824        // m  -203,-855
825        // c  -162,-813 -182,-799 -206,-799
826        // c  -231,-799 -250,-813 -292,-855
827        // c  -277,-814 -235,-834 -221,-858
828        // c  -246,-877 -260,-919 -292,-911
829        // l  -203,-855
830        let expected = &[
831            MoveTo(Fixed::from_i32(-137), Fixed::from_i32(-632)),
832            LineTo(Fixed::from_i32(-103), Fixed::from_i32(-637)),
833            LineTo(Fixed::from_i32(-83), Fixed::from_i32(-643)),
834            LineTo(Fixed::from_i32(-82), Fixed::from_i32(-643)),
835            LineTo(Fixed::from_i32(-82), Fixed::from_i32(-641)),
836            LineTo(Fixed::from_i32(-79), Fixed::from_i32(-641)),
837            LineTo(Fixed::from_i32(-79), Fixed::from_i32(-820)),
838            LineTo(Fixed::from_i32(-89), Fixed::from_i32(-820)),
839            LineTo(Fixed::from_i32(-89), Fixed::from_i32(-817)),
840            CurveTo(
841                Fixed::from_i32(-119),
842                Fixed::from_i32(-802),
843                Fixed::from_i32(-97),
844                Fixed::from_i32(-794),
845                Fixed::from_i32(-147),
846                Fixed::from_i32(-768),
847            ),
848            CurveTo(
849                Fixed::from_i32(-161),
850                Fixed::from_i32(-810),
851                Fixed::from_i32(-202),
852                Fixed::from_i32(-791),
853                Fixed::from_i32(-217),
854                Fixed::from_i32(-766),
855            ),
856            CurveTo(
857                Fixed::from_i32(-247),
858                Fixed::from_i32(-766),
859                Fixed::from_i32(-232),
860                Fixed::from_i32(-744),
861                Fixed::from_i32(-224),
862                Fixed::from_i32(-744),
863            ),
864            CurveTo(
865                Fixed::from_i32(-254),
866                Fixed::from_i32(-736),
867                Fixed::from_i32(-239),
868                Fixed::from_i32(-714),
869                Fixed::from_i32(-231),
870                Fixed::from_i32(-714),
871            ),
872            CurveTo(
873                Fixed::from_i32(-207),
874                Fixed::from_i32(-714),
875                Fixed::from_i32(-187),
876                Fixed::from_i32(-699),
877                Fixed::from_i32(-187),
878                Fixed::from_i32(-658),
879            ),
880            CurveTo(
881                Fixed::from_i32(-187),
882                Fixed::from_i32(-616),
883                Fixed::from_i32(-207),
884                Fixed::from_i32(-602),
885                Fixed::from_i32(-231),
886                Fixed::from_i32(-602),
887            ),
888            CurveTo(
889                Fixed::from_i32(-256),
890                Fixed::from_i32(-602),
891                Fixed::from_i32(-275),
892                Fixed::from_i32(-616),
893                Fixed::from_i32(-275),
894                Fixed::from_i32(-658),
895            ),
896            CurveTo(
897                Fixed::from_i32(-275),
898                Fixed::from_i32(-699),
899                Fixed::from_i32(-256),
900                Fixed::from_i32(-714),
901                Fixed::from_i32(-231),
902                Fixed::from_i32(-714),
903            ),
904            LineTo(Fixed::from_i32(-137), Fixed::from_i32(-632)),
905            MoveTo(Fixed::from_i32(-231), Fixed::from_i32(-694)),
906            CurveTo(
907                Fixed::from_i32(-251),
908                Fixed::from_i32(-680),
909                Fixed::from_i32(-275),
910                Fixed::from_i32(-705),
911                Fixed::from_i32(-294),
912                Fixed::from_i32(-719),
913            ),
914            LineTo(Fixed::from_i32(-290), Fixed::from_i32(-714)),
915            LineTo(Fixed::from_i32(-310), Fixed::from_i32(-700)),
916            CurveTo(
917                Fixed::from_i32(-334),
918                Fixed::from_i32(-725),
919                Fixed::from_i32(-353),
920                Fixed::from_i32(-739),
921                Fixed::from_i32(-349),
922                Fixed::from_i32(-734),
923            ),
924            CurveTo(
925                Fixed::from_i32(-349),
926                Fixed::from_i32(-789),
927                Fixed::from_i32(-372),
928                Fixed::from_i32(-811),
929                Fixed::from_i32(-431),
930                Fixed::from_i32(-811),
931            ),
932            CurveTo(
933                Fixed::from_i32(-431),
934                Fixed::from_i32(-841),
935                Fixed::from_i32(-416),
936                Fixed::from_i32(-819),
937                Fixed::from_i32(-416),
938                Fixed::from_i32(-811),
939            ),
940            CurveTo(
941                Fixed::from_i32(-408),
942                Fixed::from_i32(-841),
943                Fixed::from_i32(-393),
944                Fixed::from_i32(-819),
945                Fixed::from_i32(-393),
946                Fixed::from_i32(-811),
947            ),
948            CurveTo(
949                Fixed::from_i32(-369),
950                Fixed::from_i32(-791),
951                Fixed::from_i32(-354),
952                Fixed::from_i32(-750),
953                Fixed::from_i32(-312),
954                Fixed::from_i32(-770),
955            ),
956            CurveTo(
957                Fixed::from_i32(-298),
958                Fixed::from_i32(-794),
959                Fixed::from_i32(-323),
960                Fixed::from_i32(-813),
961                Fixed::from_i32(-337),
962                Fixed::from_i32(-855),
963            ),
964            CurveTo(
965                Fixed::from_i32(-313),
966                Fixed::from_i32(-855),
967                Fixed::from_i32(-293),
968                Fixed::from_i32(-840),
969                Fixed::from_i32(-252),
970                Fixed::from_i32(-840),
971            ),
972            CurveTo(
973                Fixed::from_i32(-210),
974                Fixed::from_i32(-840),
975                Fixed::from_i32(-230),
976                Fixed::from_i32(-855),
977                Fixed::from_i32(-216),
978                Fixed::from_i32(-855),
979            ),
980            LineTo(Fixed::from_i32(-231), Fixed::from_i32(-694)),
981            MoveTo(Fixed::from_i32(-203), Fixed::from_i32(-855)),
982            CurveTo(
983                Fixed::from_i32(-162),
984                Fixed::from_i32(-813),
985                Fixed::from_i32(-182),
986                Fixed::from_i32(-799),
987                Fixed::from_i32(-206),
988                Fixed::from_i32(-799),
989            ),
990            CurveTo(
991                Fixed::from_i32(-231),
992                Fixed::from_i32(-799),
993                Fixed::from_i32(-250),
994                Fixed::from_i32(-813),
995                Fixed::from_i32(-292),
996                Fixed::from_i32(-855),
997            ),
998            CurveTo(
999                Fixed::from_i32(-277),
1000                Fixed::from_i32(-814),
1001                Fixed::from_i32(-235),
1002                Fixed::from_i32(-834),
1003                Fixed::from_i32(-221),
1004                Fixed::from_i32(-858),
1005            ),
1006            CurveTo(
1007                Fixed::from_i32(-246),
1008                Fixed::from_i32(-877),
1009                Fixed::from_i32(-260),
1010                Fixed::from_i32(-919),
1011                Fixed::from_i32(-292),
1012                Fixed::from_i32(-911),
1013            ),
1014            LineTo(Fixed::from_i32(-203), Fixed::from_i32(-855)),
1015        ];
1016        assert_eq!(&commands.0, expected);
1017    }
1018
1019    /// Fuzzer caught subtract with overflow
1020    /// <https://g-issues.oss-fuzz.com/issues/383609770>
1021    #[test]
1022    fn coords_remaining_avoid_overflow() {
1023        // Test case:
1024        // Evaluate HHCURVETO operator with 2 elements on the stack
1025        let mut commands = CaptureCommandSink::default();
1026        let mut evaluator = Evaluator::new(Index::Empty, None, None, &mut commands);
1027        evaluator.stack.push(0).unwrap();
1028        evaluator.stack.push(0).unwrap();
1029        let mut cursor = FontData::new(&[]).cursor();
1030        // Just don't panic
1031        let _ = evaluator.evaluate_operator(Operator::HhCurveTo, &mut cursor, 0);
1032    }
1033}