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