read_fonts/tables/postscript/
charstring.rs

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