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