Skip to main content

read_fonts/ps/
cs.rs

1//! Parsing and evaluation of charstrings.
2
3use crate::{
4    model::pen::OutlinePen,
5    ps::{
6        cff::{blend::BlendState, charset::Charset, index::Index, stack::Stack},
7        error::Error,
8        num,
9        string::Sid,
10        transform::{FontMatrix, Transform},
11    },
12    tables::cff::Cff,
13    types::{Fixed, Point},
14    Cursor, FontData, FontRead,
15};
16
17/// Maximum nesting depth for subroutine calls.
18///
19/// See "Appendix B Type 2 Charstring Implementation Limits" at
20/// <https://adobe-type-tools.github.io/font-tech-notes/pdfs/5177.Type2.pdf#page=33>
21pub const NESTING_DEPTH_LIMIT: u32 = 10;
22
23/// The type of a PostScript charstring.
24#[derive(Copy, Clone, PartialEq, Eq, Debug)]
25pub enum CharstringKind {
26    /// Type1 charstring.
27    ///
28    /// See reference at <https://adobe-type-tools.github.io/font-tech-notes/pdfs/T1_SPEC.pdf>.
29    Type1,
30    /// Type2 charstring.
31    ///
32    /// See reference at <https://adobe-type-tools.github.io/font-tech-notes/pdfs/5177.Type2.pdf>.
33    Type2,
34}
35
36/// Trait that provides context for charstring evaluation.
37pub trait CharstringContext {
38    /// Returns the type of the charstring.
39    fn kind(&self) -> CharstringKind;
40
41    /// Returns the base and accent charstrings for the `seac` (standard
42    /// encoded accented character) operator.
43    fn seac_components(&self, base_code: i32, accent_code: i32) -> Result<[&[u8]; 2], Error>;
44
45    /// Returns the charstring for the global subroutine at the given index as
46    /// encoded in the calling charstring.
47    fn global_subr(&self, index: i32) -> Result<&[u8], Error>;
48
49    /// Returns the charstring for the local subroutine at the given index as
50    /// encoded in the calling charstring.
51    fn subr(&self, index: i32) -> Result<&[u8], Error>;
52
53    /// Returns the current active weight vector for a multiple master font.
54    fn weight_vector(&self) -> &[Fixed] {
55        &[]
56    }
57}
58
59// Ugly temporary impl to support existing skrifa code until it is replaced
60// with CffFontRef.
61//
62// Types are (cff_blob, charstrings, global_subrs, subrs)
63impl<'a> CharstringContext for (&'a [u8], &'a Index<'a>, &'a Index<'a>, &'a Index<'a>) {
64    fn kind(&self) -> CharstringKind {
65        CharstringKind::Type2
66    }
67
68    fn seac_components(&self, base_code: i32, accent_code: i32) -> Result<[&[u8]; 2], Error> {
69        let cff = Cff::read(FontData::new(self.0))?;
70        let charset = cff
71            .charset(0)?
72            .or_else(|| Charset::new(FontData::default(), 0, self.1.count()).ok())
73            .ok_or(Error::MissingCharset)?;
74        let seac_to_gid = |code: i32| {
75            let code: u8 = code.try_into().ok()?;
76            let sid = *super::encoding::STANDARD_ENCODING.get(code as usize)?;
77            charset.glyph_id(Sid::new(sid as u16)).ok()
78        };
79        let accent_gid = seac_to_gid(accent_code).ok_or(Error::InvalidSeacCode(accent_code))?;
80        let base_gid = seac_to_gid(base_code).ok_or(Error::InvalidSeacCode(base_code))?;
81        let accent_charstring = self.1.get(accent_gid.to_u32() as usize)?;
82        let base_charstring = self.1.get(base_gid.to_u32() as usize)?;
83        Ok([base_charstring, accent_charstring])
84    }
85
86    fn global_subr(&self, index: i32) -> Result<&[u8], Error> {
87        self.2.get((index + self.2.subr_bias()) as usize)
88    }
89
90    fn subr(&self, index: i32) -> Result<&[u8], Error> {
91        self.3.get((index + self.3.subr_bias()) as usize)
92    }
93}
94
95/// Trait for processing commands resulting from charstring evaluation.
96///
97/// During processing, the path construction operators (see "4.1 Path
98/// Construction Operators" at <https://adobe-type-tools.github.io/font-tech-notes/pdfs/5177.Type2.pdf#page=15>)
99/// are simplified into the basic move, line, curve and close commands.
100///
101/// This also has optional callbacks for processing hint operators. See "4.3
102/// Hint Operators" at <https://adobe-type-tools.github.io/font-tech-notes/pdfs/5177.Type2.pdf#page=21>
103/// for more detail.
104#[allow(unused_variables)]
105pub trait CommandSink {
106    // Path construction operators.
107    fn move_to(&mut self, x: Fixed, y: Fixed);
108    fn line_to(&mut self, x: Fixed, y: Fixed);
109    fn curve_to(&mut self, cx0: Fixed, cy0: Fixed, cx1: Fixed, cy1: Fixed, x: Fixed, y: Fixed);
110    fn close(&mut self);
111    // Hint operators.
112    /// Horizontal stem hint at `y` with height `dy`.
113    fn hstem(&mut self, y: Fixed, dy: Fixed) {}
114    /// Vertical stem hint at `x` with width `dx`.
115    fn vstem(&mut self, x: Fixed, dx: Fixed) {}
116    /// Bitmask defining the hints that should be made active for the
117    /// commands that follow.
118    fn hint_mask(&mut self, mask: &[u8]) {}
119    /// Bitmask defining the counter hints that should be made active for the
120    /// commands that follow.
121    fn counter_mask(&mut self, mask: &[u8]) {}
122    /// Clear accumulated stem hints and all data derived from them.
123    fn clear_hints(&mut self) {}
124    /// Called when charstring evaluation is complete.
125    fn finish(&mut self) {}
126}
127
128/// Evaluates the given charstring and emits the resulting commands to the
129/// specified sink.
130///
131/// If the Private DICT associated with this charstring contains local
132/// subroutines, then the `subrs` index must be provided, otherwise
133/// `Error::MissingSubroutines` will be returned if a callsubr operator
134/// is present.
135///
136/// If evaluating a CFF2 charstring and the top-level table contains an
137/// item variation store, then `blend_state` must be provided, otherwise
138/// `Error::MissingBlendState` will be returned if a blend operator is
139/// present.
140pub fn evaluate<'a>(
141    context: &'a impl CharstringContext,
142    blend_state: Option<BlendState<'a>>,
143    charstring_data: &[u8],
144    sink: &'a mut impl CommandSink,
145) -> Result<Option<Fixed>, Error> {
146    let mut evaluator = Evaluator::new(context, blend_state, sink);
147    evaluator.evaluate(charstring_data)?;
148    let width = evaluator.have_read_width.then_some(evaluator.wx);
149    sink.finish();
150    Ok(width)
151}
152
153/// Specifies how the seac operation was invoked.
154#[derive(PartialEq)]
155enum SeacMode {
156    /// Through the `seac` operator.
157    Explicit,
158    /// Implicitly with extra arguments on the stack through the
159    /// `endchar` operator.
160    Implicit,
161}
162
163/// Transient state for evaluating a charstring and handling recursive
164/// subroutine calls.
165struct Evaluator<'a, S> {
166    context: &'a dyn CharstringContext,
167    is_type1: bool,
168    blend_state: Option<BlendState<'a>>,
169    sink: &'a mut S,
170    is_open: bool,
171    /// When the flex state is active, moveto commands simply
172    /// accumulate vectors on the stack which will be used
173    /// to emit curves when the flex is finalized
174    is_flexing: bool,
175    /// True if we've seen a command that might read width
176    seen_width_command: bool,
177    /// True if we've actually read a width
178    have_read_width: bool,
179    stem_count: usize,
180    x: Fixed,
181    y: Fixed,
182    /// X side-bearing
183    sbx: Fixed,
184    /// X width
185    wx: Fixed,
186    stack: Stack,
187    stack_ix: usize,
188    in_seac: bool,
189}
190
191impl<'a, S> Evaluator<'a, S>
192where
193    S: CommandSink,
194{
195    fn new(
196        context: &'a dyn CharstringContext,
197        blend_state: Option<BlendState<'a>>,
198        sink: &'a mut S,
199    ) -> Self {
200        let is_type1 = context.kind() == CharstringKind::Type1;
201        Self {
202            context,
203            is_type1,
204            blend_state,
205            sink,
206            is_open: false,
207            is_flexing: false,
208            seen_width_command: false,
209            have_read_width: false,
210            stem_count: 0,
211            stack: Stack::new(),
212            x: Fixed::ZERO,
213            y: Fixed::ZERO,
214            sbx: Fixed::ZERO,
215            wx: Fixed::ZERO,
216            stack_ix: 0,
217            in_seac: false,
218        }
219    }
220
221    fn evaluate(&mut self, charstring_data: &[u8]) -> Result<(), Error> {
222        let seen_endchar = self.evaluate_impl(charstring_data, 0)?;
223        if !self.is_type1 && !seen_endchar {
224            // FreeType simulates an endchar operator for CFF and CFF2
225            // <https://gitlab.freedesktop.org/freetype/freetype/-/blob/80a507a6b8e3d2906ad2c8ba69329bd2fb2a85ef/src/psaux/psintrp.c#L632>
226            self.evaluate_operator(
227                Operator::EndChar,
228                &mut crate::FontData::default().cursor(),
229                0,
230            )?;
231        }
232        if self.is_open {
233            self.sink.close();
234        }
235        Ok(())
236    }
237
238    fn evaluate_impl(&mut self, charstring_data: &[u8], nesting_depth: u32) -> Result<bool, Error> {
239        if nesting_depth > NESTING_DEPTH_LIMIT {
240            return Err(Error::CharstringNestingDepthLimitExceeded);
241        }
242        let mut cursor = crate::FontData::new(charstring_data).cursor();
243        let mut seen_endchar = false;
244        while cursor.remaining_bytes() != 0 {
245            let b0 = cursor.read::<u8>()?;
246            match b0 {
247                // See "3.2 Charstring Number Encoding" <https://adobe-type-tools.github.io/font-tech-notes/pdfs/5177.Type2.pdf#page=12>
248                //
249                // Push an integer to the stack
250                28 | 32..=254 => {
251                    self.stack.push(num::parse_int(&mut cursor, b0)?)?;
252                }
253                // Push a fixed point value to the stack
254                255 => {
255                    let val = cursor.read::<i32>()?;
256                    if self.is_type1 {
257                        // Type1 interprets this as an integer
258                        self.stack.push(val)?;
259                    } else {
260                        // Type2 interprets this as a raw 16.16 fixed point
261                        // value
262                        self.stack.push(Fixed::from_bits(val))?;
263                    }
264                }
265                _ => {
266                    // FreeType ignores reserved (unknown) operators.
267                    // See <https://gitlab.freedesktop.org/freetype/freetype/-/blob/80a507a6b8e3d2906ad2c8ba69329bd2fb2a85ef/src/psaux/psintrp.c#L703>
268                    // and fontations issue <https://github.com/googlefonts/fontations/issues/1680>
269                    if let Ok(operator) = Operator::read(&mut cursor, b0) {
270                        seen_endchar |= operator == Operator::EndChar;
271                        if !self.evaluate_operator(operator, &mut cursor, nesting_depth)? {
272                            break;
273                        }
274                    } else {
275                        // Clear the stack for unknown operators
276                        self.reset_stack();
277                    }
278                }
279            }
280        }
281        Ok(seen_endchar)
282    }
283
284    /// Evaluates a single charstring operator.
285    ///
286    /// Returns `Ok(true)` if evaluation should continue.
287    fn evaluate_operator(
288        &mut self,
289        operator: Operator,
290        cursor: &mut Cursor,
291        nesting_depth: u32,
292    ) -> Result<bool, Error> {
293        use Operator::*;
294        use PointMode::*;
295        match operator {
296            // The following "flex" operators are intended to emit
297            // either two curves or a straight line depending on
298            // a "flex depth" parameter and the distance from the
299            // joining point to the chord connecting the two
300            // end points. In practice, we just emit the two curves,
301            // following FreeType:
302            // <https://gitlab.freedesktop.org/freetype/freetype/-/blob/80a507a6b8e3d2906ad2c8ba69329bd2fb2a85ef/src/psaux/psintrp.c#L335>
303            //
304            // Spec: <https://adobe-type-tools.github.io/font-tech-notes/pdfs/5177.Type2.pdf#page=18>
305            Flex => {
306                self.emit_curves([DxDy; 6])?;
307                self.reset_stack();
308            }
309            // Spec: <https://adobe-type-tools.github.io/font-tech-notes/pdfs/5177.Type2.pdf#page=19>
310            HFlex => {
311                self.emit_curves([DxY, DxDy, DxY, DxY, DxInitialY, DxY])?;
312                self.reset_stack();
313            }
314            // Spec: <https://adobe-type-tools.github.io/font-tech-notes/pdfs/5177.Type2.pdf#page=19>
315            HFlex1 => {
316                self.emit_curves([DxDy, DxDy, DxY, DxY, DxDy, DxInitialY])?;
317                self.reset_stack();
318            }
319            // Spec: <https://adobe-type-tools.github.io/font-tech-notes/pdfs/5177.Type2.pdf#page=20>
320            Flex1 => {
321                self.emit_curves([DxDy, DxDy, DxDy, DxDy, DxDy, DLargerCoordDist])?;
322                self.reset_stack();
323            }
324            // Set the variation store index
325            // <https://learn.microsoft.com/en-us/typography/opentype/spec/cff2charstr#syntax-for-font-variations-support-operators>
326            VariationStoreIndex => {
327                if !self.is_type1 {
328                    let blend_state = self.blend_state.as_mut().ok_or(Error::MissingBlendState)?;
329                    let store_index = self.stack.pop_i32()? as u16;
330                    blend_state.set_store_index(store_index)?;
331                }
332            }
333            // Apply blending to the current operand stack
334            // <https://learn.microsoft.com/en-us/typography/opentype/spec/cff2charstr#syntax-for-font-variations-support-operators>
335            Blend => {
336                if !self.is_type1 {
337                    let blend_state = self.blend_state.as_ref().ok_or(Error::MissingBlendState)?;
338                    self.stack.apply_blend(blend_state)?;
339                }
340            }
341            // Return from the current subroutine
342            // Spec: <https://adobe-type-tools.github.io/font-tech-notes/pdfs/5177.Type2.pdf#page=29>
343            Return => {
344                return Ok(false);
345            }
346            // End the current charstring
347            // Spec: <https://adobe-type-tools.github.io/font-tech-notes/pdfs/5177.Type2.pdf#page=21>
348            // FT: <https://gitlab.freedesktop.org/freetype/freetype/-/blob/80a507a6b8e3d2906ad2c8ba69329bd2fb2a85ef/src/psaux/psintrp.c#L2463>
349            EndChar => {
350                let stack_len = self.stack.len();
351                if (stack_len == 1 || stack_len == 5) && !self.seen_width_command {
352                    self.read_width()?;
353                }
354                self.seen_width_command = true;
355                if stack_len > 1 {
356                    self.handle_seac(SeacMode::Implicit, nesting_depth)?;
357                }
358                return Ok(false);
359            }
360            // Emits a sequence of stem hints
361            // Spec: <https://adobe-type-tools.github.io/font-tech-notes/pdfs/5177.Type2.pdf#page=21>
362            // FT: <https://gitlab.freedesktop.org/freetype/freetype/-/blob/80a507a6b8e3d2906ad2c8ba69329bd2fb2a85ef/src/psaux/psintrp.c#L777>
363            HStem | VStem | HStemHm | VStemHm => {
364                let mut i = 0;
365                let len = if self.stack.len_is_odd() && !self.seen_width_command {
366                    self.read_width()?;
367                    i = 1;
368                    self.stack.len() - 1
369                } else {
370                    self.stack.len()
371                };
372                self.seen_width_command = true;
373                let is_horizontal = matches!(operator, HStem | HStemHm);
374                let mut u = Fixed::ZERO;
375                while i < self.stack.len() {
376                    let args = self.stack.fixed_array::<2>(i)?;
377                    u += args[0];
378                    let w = args[1];
379                    let v = u.wrapping_add(w);
380                    if is_horizontal {
381                        self.sink.hstem(u, v);
382                    } else {
383                        self.sink.vstem(u, v);
384                    }
385                    u = v;
386                    i += 2;
387                }
388                self.stem_count += len / 2;
389                self.reset_stack();
390            }
391            // Applies a hint or counter mask.
392            // If there are arguments on the stack, this is also an
393            // implied series of VSTEMHM operators.
394            // Hint and counter masks are bitstrings that determine
395            // the currently active set of hints.
396            // Spec: <https://adobe-type-tools.github.io/font-tech-notes/pdfs/5177.Type2.pdf#page=24>
397            // FT: <https://gitlab.freedesktop.org/freetype/freetype/-/blob/80a507a6b8e3d2906ad2c8ba69329bd2fb2a85ef/src/psaux/psintrp.c#L2580>
398            HintMask | CntrMask => {
399                let mut i = 0;
400                let len = if self.stack.len_is_odd() && !self.seen_width_command {
401                    self.read_width()?;
402                    i = 1;
403                    self.stack.len() - 1
404                } else {
405                    self.stack.len()
406                };
407                self.seen_width_command = true;
408                let mut u = Fixed::ZERO;
409                while i < self.stack.len() {
410                    let args = self.stack.fixed_array::<2>(i)?;
411                    u += args[0];
412                    let w = args[1];
413                    let v = u + w;
414                    self.sink.vstem(u, v);
415                    u = v;
416                    i += 2;
417                }
418                self.stem_count += len / 2;
419                let count = self.stem_count.div_ceil(8);
420                let mask = cursor.read_array::<u8>(count)?;
421                if operator == HintMask {
422                    self.sink.hint_mask(mask);
423                } else {
424                    self.sink.counter_mask(mask);
425                }
426                self.reset_stack();
427            }
428            // Starts a new subpath
429            // Spec: <https://adobe-type-tools.github.io/font-tech-notes/pdfs/5177.Type2.pdf#page=16>
430            // FT: <https://gitlab.freedesktop.org/freetype/freetype/-/blob/80a507a6b8e3d2906ad2c8ba69329bd2fb2a85ef/src/psaux/psintrp.c#L2653>
431            RMoveTo => {
432                if self.stack.len() > 2 && !self.seen_width_command {
433                    self.read_width()?;
434                }
435                self.seen_width_command = true;
436                if !self.is_flexing {
437                    let dy = self.stack.pop_fixed()?;
438                    let dx = self.stack.pop_fixed()?;
439                    self.x += dx;
440                    self.y += dy;
441                    if !self.is_open {
442                        self.is_open = true;
443                    } else {
444                        self.sink.close();
445                    }
446                    self.sink.move_to(self.x, self.y);
447                    self.reset_stack();
448                }
449            }
450            // Starts a new subpath by moving the current point in the
451            // horizontal or vertical direction
452            // Spec: <https://adobe-type-tools.github.io/font-tech-notes/pdfs/5177.Type2.pdf#page=16>
453            // FT: <https://gitlab.freedesktop.org/freetype/freetype/-/blob/80a507a6b8e3d2906ad2c8ba69329bd2fb2a85ef/src/psaux/psintrp.c#L839>
454            HMoveTo | VMoveTo => {
455                if self.stack.len() > 1 && !self.seen_width_command {
456                    self.read_width()?;
457                }
458                self.seen_width_command = true;
459                if self.is_flexing {
460                    // We need to add the other coordinate to the stack so we
461                    // have a full flex vector
462                    self.stack.push(0)?;
463                    if operator == VMoveTo {
464                        // For vertical move, the coordinates are in the wrong
465                        // order so swap them
466                        self.stack.exch()?;
467                    }
468                } else {
469                    let delta = self.stack.pop_fixed()?;
470                    if operator == HMoveTo {
471                        self.x += delta;
472                    } else {
473                        self.y += delta;
474                    }
475                    if !self.is_open {
476                        self.is_open = true;
477                    } else {
478                        self.sink.close();
479                    }
480                    self.sink.move_to(self.x, self.y);
481                    self.reset_stack();
482                }
483            }
484            // Emits a sequence of lines
485            // Spec: <https://adobe-type-tools.github.io/font-tech-notes/pdfs/5177.Type2.pdf#page=16>
486            // FT: <https://gitlab.freedesktop.org/freetype/freetype/-/blob/80a507a6b8e3d2906ad2c8ba69329bd2fb2a85ef/src/psaux/psintrp.c#L863>
487            RLineTo => {
488                let mut i = 0;
489                while i < self.stack.len() {
490                    let [dx, dy] = self.stack.fixed_array::<2>(i)?;
491                    self.x += dx;
492                    self.y += dy;
493                    self.emit_line(self.x, self.y);
494                    i += 2;
495                }
496                self.reset_stack();
497            }
498            // Emits a sequence of alternating horizontal and vertical
499            // lines
500            // Spec: <https://adobe-type-tools.github.io/font-tech-notes/pdfs/5177.Type2.pdf#page=16>
501            // FT: <https://gitlab.freedesktop.org/freetype/freetype/-/blob/80a507a6b8e3d2906ad2c8ba69329bd2fb2a85ef/src/psaux/psintrp.c#L885>
502            HLineTo | VLineTo => {
503                let mut is_x = operator == HLineTo;
504                for i in 0..self.stack.len() {
505                    let delta = self.stack.get_fixed(i)?;
506                    if is_x {
507                        self.x += delta;
508                    } else {
509                        self.y += delta;
510                    }
511                    is_x = !is_x;
512                    self.emit_line(self.x, self.y);
513                }
514                self.reset_stack();
515            }
516            // Emits curves that start and end horizontal, unless
517            // the stack count is odd, in which case the first
518            // curve may start with a vertical tangent
519            // Spec: <https://adobe-type-tools.github.io/font-tech-notes/pdfs/5177.Type2.pdf#page=17>
520            // FT: <https://gitlab.freedesktop.org/freetype/freetype/-/blob/80a507a6b8e3d2906ad2c8ba69329bd2fb2a85ef/src/psaux/psintrp.c#L2789>
521            HhCurveTo => {
522                let count1 = self.stack.len();
523                let count = count1 & !2;
524                self.stack_ix = count1 - count;
525                while self.stack_ix < count {
526                    if (count - self.stack_ix) & 1 != 0 {
527                        self.y += self.stack.get_fixed(self.stack_ix)?;
528                        self.stack_ix += 1;
529                    }
530                    self.emit_curves([DxY, DxDy, DxY])?;
531                }
532                self.reset_stack();
533            }
534            // Alternates between curves with horizontal and vertical
535            // tangents
536            // Spec: <https://adobe-type-tools.github.io/font-tech-notes/pdfs/5177.Type2.pdf#page=17>
537            // FT: <https://gitlab.freedesktop.org/freetype/freetype/-/blob/80a507a6b8e3d2906ad2c8ba69329bd2fb2a85ef/src/psaux/psintrp.c#L2834>
538            HvCurveTo | VhCurveTo => {
539                let count1 = self.stack.len();
540                let count = count1 & !2;
541                let mut is_horizontal = operator == HvCurveTo;
542                self.stack_ix = count1 - count;
543                while self.stack_ix < count {
544                    let do_last_delta = count - self.stack_ix == 5;
545                    if is_horizontal {
546                        self.emit_curves([DxY, DxDy, MaybeDxDy(do_last_delta)])?;
547                    } else {
548                        self.emit_curves([XDy, DxDy, DxMaybeDy(do_last_delta)])?;
549                    }
550                    is_horizontal = !is_horizontal;
551                }
552                self.reset_stack();
553            }
554            // Emits a sequence of curves possibly followed by a line
555            // Spec: <https://adobe-type-tools.github.io/font-tech-notes/pdfs/5177.Type2.pdf#page=17>
556            // FT: <https://gitlab.freedesktop.org/freetype/freetype/-/blob/80a507a6b8e3d2906ad2c8ba69329bd2fb2a85ef/src/psaux/psintrp.c#L915>
557            RrCurveTo | RCurveLine => {
558                while self.coords_remaining() >= 6 {
559                    self.emit_curves([DxDy; 3])?;
560                }
561                if operator == RCurveLine {
562                    let [dx, dy] = self.stack.fixed_array::<2>(self.stack_ix)?;
563                    self.x += dx;
564                    self.y += dy;
565                    self.emit_line(self.x, self.y);
566                }
567                self.reset_stack();
568            }
569            // Emits a sequence of lines followed by a curve
570            // Spec: <https://adobe-type-tools.github.io/font-tech-notes/pdfs/5177.Type2.pdf#page=18>
571            // FT: <https://gitlab.freedesktop.org/freetype/freetype/-/blob/80a507a6b8e3d2906ad2c8ba69329bd2fb2a85ef/src/psaux/psintrp.c#L2702>
572            RLineCurve => {
573                while self.coords_remaining() > 6 {
574                    let [dx, dy] = self.stack.fixed_array::<2>(self.stack_ix)?;
575                    self.x += dx;
576                    self.y += dy;
577                    self.emit_line(self.x, self.y);
578                    self.stack_ix += 2;
579                }
580                while self.coords_remaining() >= 6 {
581                    self.emit_curves([DxDy; 3])?;
582                }
583                self.reset_stack();
584            }
585            // Emits curves that start and end vertical, unless
586            // the stack count is odd, in which case the first
587            // curve may start with a horizontal tangent
588            // Spec: <https://adobe-type-tools.github.io/font-tech-notes/pdfs/5177.Type2.pdf#page=18>
589            // FT: <https://gitlab.freedesktop.org/freetype/freetype/-/blob/80a507a6b8e3d2906ad2c8ba69329bd2fb2a85ef/src/psaux/psintrp.c#L2744>
590            VvCurveTo => {
591                let count1 = self.stack.len();
592                let count = count1 & !2;
593                self.stack_ix = count1 - count;
594                while self.stack_ix < count {
595                    if (count - self.stack_ix) & 1 != 0 {
596                        self.x += self.stack.get_fixed(self.stack_ix)?;
597                        self.stack_ix += 1;
598                    }
599                    self.emit_curves([XDy, DxDy, XDy])?;
600                }
601                self.reset_stack();
602            }
603            // Call local or global subroutine
604            // Spec: <https://adobe-type-tools.github.io/font-tech-notes/pdfs/5177.Type2.pdf#page=29>
605            // FT: <https://gitlab.freedesktop.org/freetype/freetype/-/blob/80a507a6b8e3d2906ad2c8ba69329bd2fb2a85ef/src/psaux/psintrp.c#L972>
606            CallSubr | CallGsubr => {
607                let index = self.stack.pop_i32()?;
608                let subr_charstring = if operator == CallSubr {
609                    self.context.subr(index)?
610                } else {
611                    self.context.global_subr(index)?
612                };
613                self.evaluate_impl(subr_charstring, nesting_depth + 1)?;
614            }
615            // Sets the left sidebearing point to (sbx, 0) and the character
616            // width vector to (wx, 0) in character space. Also sets current
617            // point to (sbx, 0).
618            // Spec: <https://adobe-type-tools.github.io/font-tech-notes/pdfs/T1_SPEC.pdf#page=56>
619            // FT: <https://gitlab.freedesktop.org/freetype/freetype/-/blob/80a507a6b8e3d2906ad2c8ba69329bd2fb2a85ef/src/psaux/psintrp.c#L2429>
620            Hsbw => {
621                if self.is_type1 {
622                    let [sbx, wx] = self.stack.fixed_array(0)?;
623                    self.sbx += sbx;
624                    self.x += sbx;
625                    self.wx = wx;
626                    self.seen_width_command = true;
627                    self.have_read_width = true;
628                    self.reset_stack();
629                }
630            }
631            // Standard Encoding Accented Character.
632            // Makes an accented character from two other characters in the
633            // font program.
634            // Spec: <https://adobe-type-tools.github.io/font-tech-notes/pdfs/T1_SPEC.pdf#page=56>
635            // FT: <https://gitlab.freedesktop.org/freetype/freetype/-/blob/80a507a6b8e3d2906ad2c8ba69329bd2fb2a85ef/src/psaux/psintrp.c#L1294>
636            Seac => {
637                self.handle_seac(SeacMode::Explicit, nesting_depth)?;
638            }
639            // Sets the left sidebearing point to (sbx, sby) and the character
640            // width vector to (wx, wy) in character space. Also sets current
641            // point to (sbx, sby).
642            // Spec: <https://adobe-type-tools.github.io/font-tech-notes/pdfs/T1_SPEC.pdf#page=57>
643            // FT: <https://gitlab.freedesktop.org/freetype/freetype/-/blob/80a507a6b8e3d2906ad2c8ba69329bd2fb2a85ef/src/psaux/psintrp.c#L1496>
644            Sbw => {
645                if self.is_type1 {
646                    let [x, y, wx, _wy] = self.stack.fixed_array(0)?;
647                    self.x += x;
648                    self.y += y;
649                    self.sbx += x;
650                    self.wx = wx;
651                    self.seen_width_command = true;
652                    self.have_read_width = true;
653                    self.reset_stack();
654                }
655            }
656            // Brackets an outline section for dots in letters such as 'i',
657            // 'j' and '!'. Purely metadata that a hinter can use.
658            // Spec: <https://adobe-type-tools.github.io/font-tech-notes/pdfs/T1_SPEC.pdf#page=58>
659            DotSection => {
660                // Nothing to do.
661            }
662            // Declares ranges for three horizontal or vertical stem zones.
663            // Spec: <https://adobe-type-tools.github.io/font-tech-notes/pdfs/T1_SPEC.pdf#page=59>
664            // FT: <https://gitlab.freedesktop.org/freetype/freetype/-/blob/80a507a6b8e3d2906ad2c8ba69329bd2fb2a85ef/src/psaux/psintrp.c#L1199>
665            HStem3 | VStem3 => {
666                // Currently unimplemented.
667                self.reset_stack();
668            }
669            // Division operator.
670            // Spec: <https://adobe-type-tools.github.io/font-tech-notes/pdfs/T1_SPEC.pdf#page=60>
671            // FT: <https://gitlab.freedesktop.org/freetype/freetype/-/blob/80a507a6b8e3d2906ad2c8ba69329bd2fb2a85ef/src/psaux/psintrp.c#L1586>
672            Div => {
673                self.stack.div(self.is_type1)?;
674            }
675            // Mechanism for making calls into the PostScript interpreter.
676            // Spec: <https://adobe-type-tools.github.io/font-tech-notes/pdfs/T1_SPEC.pdf#page=61>
677            // FT: <https://gitlab.freedesktop.org/freetype/freetype/-/blob/80a507a6b8e3d2906ad2c8ba69329bd2fb2a85ef/src/psaux/psintrp.c#L1644>
678            CallOtherSubr => {
679                let subr_idx = self.stack.pop_i32()?;
680                let num_args = self.stack.pop_i32()? as usize;
681                let weight_vector = self.context.weight_vector();
682                match (subr_idx, num_args) {
683                    // End flex. Emit curves from accumulated vectors on the
684                    // stack.
685                    (0, 3) => {
686                        self.is_flexing = false;
687                        self.ensure_open();
688                        self.handle_flex()?;
689                    }
690                    // Begin flex. Accumulate vectors from moveto operators.
691                    (1, 0) => {
692                        self.is_flexing = true;
693                    }
694                    // Counter control hints.
695                    // <https://gitlab.freedesktop.org/freetype/freetype/-/blob/80a507a6b8e3d2906ad2c8ba69329bd2fb2a85ef/src/psaux/psintrp.c#L1817>
696                    (12 | 13, _) => {
697                        self.reset_stack();
698                    }
699                    // Handle blends for multiple masters.
700                    // <https://gitlab.freedesktop.org/freetype/freetype/-/blob/80a507a6b8e3d2906ad2c8ba69329bd2fb2a85ef/src/psaux/psintrp.c#L1823>
701                    (14..=18, _) if weight_vector.len() > 1 => {
702                        self.handle_mm_blend(subr_idx, num_args)?;
703                    }
704                    _ => {
705                        // Unknown othersubr, so simply drop the arguments
706                        // from the stack and hopefully we can keep going
707                        self.stack.drop(num_args);
708                    }
709                }
710            }
711            // Removes a number from the PostScript interpreter stack and
712            // pushes that number to the BuildChar stack. Only used to
713            // retrieve results from OtherSubrs procedures and those are
714            // handled explicitly so this is a nop.
715            // Spec: <https://adobe-type-tools.github.io/font-tech-notes/pdfs/T1_SPEC.pdf#page=61>
716            Pop => {
717                // Nothing to do.
718            }
719            // Sets the current point without performing a move command.
720            // Spec: <https://adobe-type-tools.github.io/font-tech-notes/pdfs/T1_SPEC.pdf#page=62>
721            // FT: <https://gitlab.freedesktop.org/freetype/freetype/-/blob/80a507a6b8e3d2906ad2c8ba69329bd2fb2a85ef/src/psaux/psintrp.c#L2379>
722            SetCurrentPoint => {
723                if self.is_type1 {
724                    let [x, y] = self.stack.fixed_array(0)?;
725                    self.x = x;
726                    self.y = y;
727                    self.reset_stack();
728                }
729            }
730        }
731        Ok(true)
732    }
733
734    fn read_width(&mut self) -> Result<(), Error> {
735        self.wx = self.stack.get_fixed(0)?;
736        self.seen_width_command = true;
737        self.have_read_width = true;
738        Ok(())
739    }
740
741    /// See `endchar` in Appendix C at <https://adobe-type-tools.github.io/font-tech-notes/pdfs/5177.Type2.pdf#page=35>
742    fn handle_seac(&mut self, mode: SeacMode, nesting_depth: u32) -> Result<(), Error> {
743        // handle seac operator
744        if self.in_seac {
745            return Err(Error::CharstringNestingDepthLimitExceeded);
746        }
747        self.in_seac = true;
748        let accent_code = self.stack.pop_i32()?;
749        let base_code = self.stack.pop_i32()?;
750        let [base_charstring, accent_charstring] =
751            self.context.seac_components(base_code, accent_code)?;
752        let dy = self.stack.pop_fixed()?;
753        let dx = self.stack.pop_fixed()?;
754        let sb = if self.is_type1 {
755            // Type1 has an additional side bearing argument
756            self.stack.pop_fixed()?
757        } else if !self.stack.is_empty() && !self.seen_width_command {
758            self.wx = self.stack.pop_fixed()?;
759            self.seen_width_command = true;
760            Fixed::ZERO
761        } else {
762            Fixed::ZERO
763        };
764        // Save metrics to potentially restore later.
765        let mut sbx = self.sbx;
766        let mut wx = self.wx;
767        let seen_width = self.seen_width_command;
768        let read_width = self.have_read_width;
769        struct Component<'a> {
770            charstring: &'a [u8],
771            x: Fixed,
772            y: Fixed,
773            /// True if we want to use metrics from this component
774            /// if the original charstring does not provide any
775            maybe_use_metrics: bool,
776        }
777        let x = self.x;
778        let y = self.y;
779        // Base components for explicit seac are always 0 in FreeType
780        let [bx, by] = if mode == SeacMode::Explicit {
781            [Fixed::ZERO; 2]
782        } else {
783            [x, y]
784        };
785        let mut components = [
786            Component {
787                charstring: base_charstring,
788                x: bx,
789                y: by,
790                // In explicit seac mode, use the metrics of the base component
791                // if the original charstring didn't provide any
792                maybe_use_metrics: mode == SeacMode::Explicit,
793            },
794            Component {
795                charstring: accent_charstring,
796                // Adjustments only for type1 but these will be 0 for type2
797                // anyway
798                x: dx + self.sbx - sb,
799                y: dy,
800                maybe_use_metrics: false,
801            },
802        ];
803        // FreeType evaluates accent first for implicit seac but base first
804        // for explicit so swap if necessary.
805        if mode == SeacMode::Implicit {
806            components.swap(0, 1);
807        }
808        // FreeType calls cf2_interpT2CharString for each component
809        // which uses a fresh set of stem hints. Since our hinter is in
810        // a separate crate, we signal this through the sink. Also
811        // reset our own stem count so we read the correct number of
812        // bytes for each hint mask instruction.
813        // See <https://gitlab.freedesktop.org/freetype/freetype/-/blob/80a507a6b8e3d2906ad2c8ba69329bd2fb2a85ef/src/psaux/psintrp.c#L1443>
814        // and <https://gitlab.freedesktop.org/freetype/freetype/-/blob/80a507a6b8e3d2906ad2c8ba69329bd2fb2a85ef/src/psaux/psintrp.c#L540>
815        for component in components {
816            self.reset_stack();
817            self.seen_width_command = false;
818            self.sink.clear_hints();
819            self.stem_count = 0;
820            self.x = component.x;
821            self.y = component.y;
822            self.evaluate_impl(component.charstring, nesting_depth + 1)?;
823            if component.maybe_use_metrics && !seen_width {
824                sbx = self.sbx;
825                wx = self.wx;
826            }
827        }
828        self.seen_width_command = seen_width;
829        self.have_read_width = read_width;
830        self.sbx = sbx;
831        self.wx = wx;
832        self.in_seac = false;
833        Ok(())
834    }
835
836    /// Emit two curves for the accumulated flex vectors.
837    fn handle_flex(&mut self) -> Result<(), Error> {
838        // FreeType does weird accounting for flex vectors
839        // that we don't wish to copy so do the equivalent
840        // thing from fonttools instead:
841        // <https://github.com/fonttools/fonttools/blob/9cec77d49bdb1a1ca346ac5fefdc5e7c30929026/Lib/fontTools/misc/psCharStrings.py#L1066>
842        let final_y = self.stack.pop_fixed()?;
843        let final_x = self.stack.pop_fixed()?;
844        // Flex height is unused
845        let _ = self.stack.pop_fixed()?;
846        let p3y = self.stack.pop_fixed()?;
847        let p3x = self.stack.pop_fixed()?;
848        let bcp4y = self.stack.pop_fixed()?;
849        let bcp4x = self.stack.pop_fixed()?;
850        let bcp3y = self.stack.pop_fixed()?;
851        let bcp3x = self.stack.pop_fixed()?;
852        let p2y = self.stack.pop_fixed()?;
853        let p2x = self.stack.pop_fixed()?;
854        let bcp2y = self.stack.pop_fixed()?;
855        let bcp2x = self.stack.pop_fixed()?;
856        let bcp1y = self.stack.pop_fixed()?;
857        let bcp1x = self.stack.pop_fixed()?;
858        let rpy = self.stack.pop_fixed()?;
859        let rpx = self.stack.pop_fixed()?;
860        self.reset_stack();
861        self.stack.push(bcp1x + rpx)?;
862        self.stack.push(bcp1y + rpy)?;
863        self.stack.push(bcp2x)?;
864        self.stack.push(bcp2y)?;
865        self.stack.push(p2x)?;
866        self.stack.push(p2y)?;
867        self.emit_curves([PointMode::DxDy; 3])?;
868        self.reset_stack();
869        self.stack.push(bcp3x)?;
870        self.stack.push(bcp3y)?;
871        self.stack.push(bcp4x)?;
872        self.stack.push(bcp4y)?;
873        self.stack.push(p3x)?;
874        self.stack.push(p3y)?;
875        self.emit_curves([PointMode::DxDy; 3])?;
876        self.reset_stack();
877        // Push final position back on the stack
878        self.stack.push(final_x)?;
879        self.stack.push(final_y)?;
880        Ok(())
881    }
882
883    /// Handle point blending for multiple master fonts.
884    ///
885    /// See <https://gitlab.freedesktop.org/freetype/freetype/-/blob/80a507a6b8e3d2906ad2c8ba69329bd2fb2a85ef/src/psaux/psintrp.c#L1823>
886    fn handle_mm_blend(&mut self, subr_idx: i32, num_args: usize) -> Result<(), Error> {
887        let weight_vector = self.context.weight_vector();
888        let num_points = (subr_idx - 13) as usize + (subr_idx == 18) as usize;
889        if num_args != num_points * weight_vector.len() {
890            return Err(Error::Read(crate::ReadError::MalformedData(
891                "incorrect number of multiple masters arguments",
892            )));
893        }
894        // The stack is setup to contain `num_points` values followed
895        // by `num_points * (num_weights - 1)` deltas for each point.
896        //
897        // The blend algorithm is p[0] + d[0]*w[1] + d[1]*w[2]...
898        // where p = points, d = deltas and w = weights
899        //
900        // The first weight is always ignored per FT:
901        // <https://gitlab.freedesktop.org/freetype/freetype/-/blob/80a507a6b8e3d2906ad2c8ba69329bd2fb2a85ef/src/psaux/psintrp.c#L1880>
902        let stack_base = self
903            .stack
904            .len()
905            .checked_sub(num_args)
906            .ok_or(Error::StackUnderflow)?;
907        let mut delta_idx = stack_base + num_points;
908        for i in 0..num_points {
909            let mut val = self.stack.get_fixed(stack_base + i)?;
910            for &weight in &weight_vector[1..] {
911                val += self.stack.get_fixed(delta_idx)? * weight;
912                delta_idx += 1;
913            }
914            self.stack.set(stack_base + i, val)?;
915        }
916        self.stack.drop(num_args.saturating_sub(num_points));
917        Ok(())
918    }
919
920    fn coords_remaining(&self) -> usize {
921        // This is overly defensive to avoid overflow but in the case of
922        // broken fonts, just return 0 when stack_ix > stack_len to prevent
923        // potential runaway while loops in the evaluator if this wraps
924        self.stack.len().saturating_sub(self.stack_ix)
925    }
926
927    fn ensure_open(&mut self) {
928        if !self.is_open {
929            self.sink.move_to(Fixed::ZERO, Fixed::ZERO);
930            self.is_open = true;
931        }
932    }
933
934    fn emit_line(&mut self, x: Fixed, y: Fixed) {
935        self.ensure_open();
936        self.sink.line_to(x, y);
937    }
938
939    fn emit_curves<const N: usize>(&mut self, modes: [PointMode; N]) -> Result<(), Error> {
940        use PointMode::*;
941        let initial_x = self.x;
942        let initial_y = self.y;
943        let mut count = 0;
944        let mut points = [Point::default(); 2];
945        self.ensure_open();
946        for mode in modes {
947            let stack_used = match mode {
948                DxDy => {
949                    self.x += self.stack.get_fixed(self.stack_ix)?;
950                    self.y += self.stack.get_fixed(self.stack_ix + 1)?;
951                    2
952                }
953                XDy => {
954                    self.y += self.stack.get_fixed(self.stack_ix)?;
955                    1
956                }
957                DxY => {
958                    self.x += self.stack.get_fixed(self.stack_ix)?;
959                    1
960                }
961                DxInitialY => {
962                    self.x += self.stack.get_fixed(self.stack_ix)?;
963                    self.y = initial_y;
964                    1
965                }
966                // Emits a delta for the coordinate with the larger distance
967                // from the original value. Sets the other coordinate to the
968                // original value.
969                DLargerCoordDist => {
970                    let delta = self.stack.get_fixed(self.stack_ix)?;
971                    if (self.x - initial_x).abs() > (self.y - initial_y).abs() {
972                        self.x += delta;
973                        self.y = initial_y;
974                    } else {
975                        self.y += delta;
976                        self.x = initial_x;
977                    }
978                    1
979                }
980                // Apply delta to y if `do_dy` is true.
981                DxMaybeDy(do_dy) => {
982                    self.x += self.stack.get_fixed(self.stack_ix)?;
983                    if do_dy {
984                        self.y += self.stack.get_fixed(self.stack_ix + 1)?;
985                        2
986                    } else {
987                        1
988                    }
989                }
990                // Apply delta to x if `do_dx` is true.
991                MaybeDxDy(do_dx) => {
992                    self.y += self.stack.get_fixed(self.stack_ix)?;
993                    if do_dx {
994                        self.x += self.stack.get_fixed(self.stack_ix + 1)?;
995                        2
996                    } else {
997                        1
998                    }
999                }
1000            };
1001            self.stack_ix += stack_used;
1002            if count == 2 {
1003                self.sink.curve_to(
1004                    points[0].x,
1005                    points[0].y,
1006                    points[1].x,
1007                    points[1].y,
1008                    self.x,
1009                    self.y,
1010                );
1011                count = 0;
1012            } else {
1013                points[count] = Point::new(self.x, self.y);
1014                count += 1;
1015            }
1016        }
1017        Ok(())
1018    }
1019
1020    fn reset_stack(&mut self) {
1021        self.stack.clear();
1022        self.stack_ix = 0;
1023    }
1024}
1025
1026/// Specifies how point coordinates for a curve are computed.
1027#[derive(Copy, Clone)]
1028enum PointMode {
1029    DxDy,
1030    XDy,
1031    DxY,
1032    DxInitialY,
1033    DLargerCoordDist,
1034    DxMaybeDy(bool),
1035    MaybeDxDy(bool),
1036}
1037
1038/// PostScript charstring operator.
1039///
1040/// See <https://learn.microsoft.com/en-us/typography/opentype/spec/cff2charstr#appendix-a-cff2-charstring-command-codes>
1041// TODO: This is currently missing legacy math and logical operators.
1042// fonttools doesn't even implement these: <https://github.com/fonttools/fonttools/blob/65598197c8afd415781f6667a7fb647c2c987fff/Lib/fontTools/misc/psCharStrings.py#L409>
1043#[derive(Copy, Clone, PartialEq, Eq, Debug)]
1044enum Operator {
1045    HStem,
1046    VStem,
1047    VMoveTo,
1048    RLineTo,
1049    HLineTo,
1050    VLineTo,
1051    RrCurveTo,
1052    CallSubr,
1053    Return,
1054    Hsbw,
1055    EndChar,
1056    VariationStoreIndex,
1057    Blend,
1058    HStemHm,
1059    HintMask,
1060    CntrMask,
1061    RMoveTo,
1062    HMoveTo,
1063    VStemHm,
1064    RCurveLine,
1065    RLineCurve,
1066    VvCurveTo,
1067    HhCurveTo,
1068    CallGsubr,
1069    VhCurveTo,
1070    HvCurveTo,
1071    DotSection,
1072    VStem3,
1073    HStem3,
1074    Seac,
1075    Sbw,
1076    Div,
1077    CallOtherSubr,
1078    Pop,
1079    SetCurrentPoint,
1080    HFlex,
1081    Flex,
1082    HFlex1,
1083    Flex1,
1084}
1085
1086impl Operator {
1087    fn read(cursor: &mut Cursor, b0: u8) -> Result<Self, Error> {
1088        // Escape opcode for accessing two byte operators
1089        const ESCAPE: u8 = 12;
1090        let (opcode, operator) = if b0 == ESCAPE {
1091            let b1 = cursor.read::<u8>()?;
1092            (b1, Self::from_two_byte_opcode(b1))
1093        } else {
1094            (b0, Self::from_opcode(b0))
1095        };
1096        operator.ok_or(Error::InvalidCharstringOperator(opcode))
1097    }
1098
1099    /// Creates an operator from the given opcode.
1100    fn from_opcode(opcode: u8) -> Option<Self> {
1101        use Operator::*;
1102        Some(match opcode {
1103            1 => HStem,
1104            3 => VStem,
1105            4 => VMoveTo,
1106            5 => RLineTo,
1107            6 => HLineTo,
1108            7 => VLineTo,
1109            8 => RrCurveTo,
1110            10 => CallSubr,
1111            11 => Return,
1112            13 => Hsbw,
1113            14 => EndChar,
1114            15 => VariationStoreIndex,
1115            16 => Blend,
1116            18 => HStemHm,
1117            19 => HintMask,
1118            20 => CntrMask,
1119            21 => RMoveTo,
1120            22 => HMoveTo,
1121            23 => VStemHm,
1122            24 => RCurveLine,
1123            25 => RLineCurve,
1124            26 => VvCurveTo,
1125            27 => HhCurveTo,
1126            29 => CallGsubr,
1127            30 => VhCurveTo,
1128            31 => HvCurveTo,
1129            _ => return None,
1130        })
1131    }
1132
1133    /// Creates an operator from the given extended opcode.
1134    ///
1135    /// These are preceded by a byte containing the escape value of 12.
1136    pub fn from_two_byte_opcode(opcode: u8) -> Option<Self> {
1137        use Operator::*;
1138        Some(match opcode {
1139            0 => DotSection,
1140            1 => VStem3,
1141            2 => HStem3,
1142            6 => Seac,
1143            7 => Sbw,
1144            12 => Div,
1145            16 => CallOtherSubr,
1146            17 => Pop,
1147            33 => SetCurrentPoint,
1148            34 => HFlex,
1149            35 => Flex,
1150            36 => HFlex1,
1151            37 => Flex1,
1152            _ => return None,
1153        })
1154    }
1155}
1156
1157// Used for scaling sink below
1158const ONE_OVER_64: Fixed = Fixed::from_bits(0x400);
1159
1160/// Command sink adapter that applies a matrix and optional scale.
1161pub struct TransformSink<'a, S> {
1162    inner: &'a mut S,
1163    matrix: Option<FontMatrix>,
1164    scale: Option<Fixed>,
1165}
1166
1167impl<'a, S> TransformSink<'a, S> {
1168    /// Creates a new sink for the given transform.
1169    pub fn new(sink: &'a mut S, transform: Transform) -> Self {
1170        Self::from_matrix_scale(sink, transform.matrix, transform.scale)
1171    }
1172
1173    /// Creates a new sink for the given matrix and optional scale.
1174    pub fn from_matrix_scale(sink: &'a mut S, matrix: FontMatrix, scale: Option<Fixed>) -> Self {
1175        Self {
1176            inner: sink,
1177            matrix: (matrix != FontMatrix::IDENTITY).then_some(matrix),
1178            scale,
1179        }
1180    }
1181
1182    fn transform(&self, x: Fixed, y: Fixed) -> (Fixed, Fixed) {
1183        // The following dance is necessary to exactly match FreeType's
1184        // application of scaling factors. This seems to be the result
1185        // of merging the contributed Adobe code while not breaking the
1186        // FreeType public API.
1187        //
1188        // The first two steps apply to both scaled and unscaled outlines:
1189        //
1190        // 1. Multiply by 1/64
1191        // <https://gitlab.freedesktop.org/freetype/freetype/-/blob/80a507a6b8e3d2906ad2c8ba69329bd2fb2a85ef/src/psaux/psft.c#L284>
1192        let ax = x * ONE_OVER_64;
1193        let ay = y * ONE_OVER_64;
1194        // 2. Truncate the bottom 10 bits. Combined with the division by 64,
1195        // converts to font units.
1196        // <https://gitlab.freedesktop.org/freetype/freetype/-/blob/80a507a6b8e3d2906ad2c8ba69329bd2fb2a85ef/src/psaux/psobjs.c#L2219>
1197        let bx = Fixed::from_bits(ax.to_bits() >> 10);
1198        let by = Fixed::from_bits(ay.to_bits() >> 10);
1199        // 3. Apply the transform. It must be done here to match FreeType.
1200        let (cx, cy) = self
1201            .matrix
1202            .as_ref()
1203            .map(|mat| mat.transform(bx, by))
1204            .unwrap_or((bx, by));
1205        if let Some(scale) = self.scale {
1206            // Scaled case:
1207            // 4. Multiply by the original scale factor (to 26.6)
1208            // <https://gitlab.freedesktop.org/freetype/freetype/-/blob/80a507a6b8e3d2906ad2c8ba69329bd2fb2a85ef/src/cff/cffgload.c#L721>
1209            let dx = cx * scale;
1210            let dy = cy * scale;
1211            // 5. Convert from 26.6 to 16.16
1212            (
1213                Fixed::from_bits(dx.to_bits() << 10),
1214                Fixed::from_bits(dy.to_bits() << 10),
1215            )
1216        } else {
1217            // Unscaled case:
1218            // 4. Convert from integer to 16.16
1219            (
1220                Fixed::from_bits(cx.to_bits() << 16),
1221                Fixed::from_bits(cy.to_bits() << 16),
1222            )
1223        }
1224    }
1225}
1226
1227impl<S: CommandSink> CommandSink for TransformSink<'_, S> {
1228    fn hstem(&mut self, y: Fixed, dy: Fixed) {
1229        self.inner.hstem(y, dy);
1230    }
1231
1232    fn vstem(&mut self, x: Fixed, dx: Fixed) {
1233        self.inner.vstem(x, dx);
1234    }
1235
1236    fn hint_mask(&mut self, mask: &[u8]) {
1237        self.inner.hint_mask(mask);
1238    }
1239
1240    fn counter_mask(&mut self, mask: &[u8]) {
1241        self.inner.counter_mask(mask);
1242    }
1243
1244    fn clear_hints(&mut self) {
1245        self.inner.clear_hints();
1246    }
1247
1248    fn move_to(&mut self, x: Fixed, y: Fixed) {
1249        let (x, y) = self.transform(x, y);
1250        self.inner.move_to(x, y);
1251    }
1252
1253    fn line_to(&mut self, x: Fixed, y: Fixed) {
1254        let (x, y) = self.transform(x, y);
1255        self.inner.line_to(x, y);
1256    }
1257
1258    fn curve_to(&mut self, cx1: Fixed, cy1: Fixed, cx2: Fixed, cy2: Fixed, x: Fixed, y: Fixed) {
1259        let (cx1, cy1) = self.transform(cx1, cy1);
1260        let (cx2, cy2) = self.transform(cx2, cy2);
1261        let (x, y) = self.transform(x, y);
1262        self.inner.curve_to(cx1, cy1, cx2, cy2, x, y);
1263    }
1264
1265    fn close(&mut self) {
1266        self.inner.close();
1267    }
1268
1269    fn finish(&mut self) {
1270        self.inner.finish();
1271    }
1272}
1273
1274#[derive(Copy, Clone)]
1275enum PendingElement {
1276    Move([Fixed; 2]),
1277    Line([Fixed; 2]),
1278    Curve([Fixed; 6]),
1279}
1280
1281impl PendingElement {
1282    fn target_point(&self) -> [Fixed; 2] {
1283        match self {
1284            Self::Move(xy) | Self::Line(xy) => *xy,
1285            Self::Curve([.., x, y]) => [*x, *y],
1286        }
1287    }
1288}
1289
1290/// Command sink adapter that suppresses degenerate move and line commands.
1291///
1292/// FreeType avoids emitting empty contours and zero length lines to prevent
1293/// artifacts when stem darkening is enabled. We don't support stem darkening
1294/// because it's not enabled by any of our clients but we remove the degenerate
1295/// elements regardless to match the output.
1296///
1297/// See <https://gitlab.freedesktop.org/freetype/freetype/-/blob/80a507a6b8e3d2906ad2c8ba69329bd2fb2a85ef/src/psaux/pshints.c#L1786>
1298pub struct NopFilterSink<'a, S> {
1299    is_open: bool,
1300    start: Option<(Fixed, Fixed)>,
1301    pending_element: Option<PendingElement>,
1302    inner: &'a mut S,
1303}
1304
1305impl<'a, S> NopFilterSink<'a, S>
1306where
1307    S: CommandSink,
1308{
1309    /// Creates a new sink that suppresses degenerate move and line commands
1310    /// before forwarding the result to the given inner sink.
1311    pub fn new(inner: &'a mut S) -> Self {
1312        Self {
1313            is_open: false,
1314            start: None,
1315            pending_element: None,
1316            inner,
1317        }
1318    }
1319
1320    fn flush_pending(&mut self, for_close: bool) {
1321        if let Some(pending) = self.pending_element.take() {
1322            match pending {
1323                PendingElement::Move([x, y]) => {
1324                    if !for_close {
1325                        self.is_open = true;
1326                        self.inner.move_to(x, y);
1327                        self.start = Some((x, y));
1328                    }
1329                }
1330                PendingElement::Line([x, y]) => {
1331                    if !for_close || self.start != Some((x, y)) {
1332                        self.inner.line_to(x, y);
1333                    }
1334                }
1335                PendingElement::Curve([cx0, cy0, cx1, cy1, x, y]) => {
1336                    self.inner.curve_to(cx0, cy0, cx1, cy1, x, y);
1337                }
1338            }
1339        }
1340    }
1341}
1342
1343impl<S> CommandSink for NopFilterSink<'_, S>
1344where
1345    S: CommandSink,
1346{
1347    fn hstem(&mut self, y: Fixed, dy: Fixed) {
1348        self.inner.hstem(y, dy);
1349    }
1350
1351    fn vstem(&mut self, x: Fixed, dx: Fixed) {
1352        self.inner.vstem(x, dx);
1353    }
1354
1355    fn hint_mask(&mut self, mask: &[u8]) {
1356        self.inner.hint_mask(mask);
1357    }
1358
1359    fn counter_mask(&mut self, mask: &[u8]) {
1360        self.inner.counter_mask(mask);
1361    }
1362
1363    fn clear_hints(&mut self) {
1364        self.inner.clear_hints();
1365    }
1366
1367    fn move_to(&mut self, x: Fixed, y: Fixed) {
1368        self.pending_element = Some(PendingElement::Move([x, y]));
1369    }
1370
1371    fn line_to(&mut self, x: Fixed, y: Fixed) {
1372        // Omit the line if we're already at the given position
1373        if self
1374            .pending_element
1375            .map(|element| element.target_point() == [x, y])
1376            .unwrap_or_default()
1377        {
1378            return;
1379        }
1380        self.flush_pending(false);
1381        self.pending_element = Some(PendingElement::Line([x, y]));
1382    }
1383
1384    fn curve_to(&mut self, cx1: Fixed, cy1: Fixed, cx2: Fixed, cy2: Fixed, x: Fixed, y: Fixed) {
1385        self.flush_pending(false);
1386        self.pending_element = Some(PendingElement::Curve([cx1, cy1, cx2, cy2, x, y]));
1387    }
1388
1389    fn close(&mut self) {
1390        self.flush_pending(true);
1391        if self.is_open {
1392            self.inner.close();
1393            self.is_open = false;
1394        }
1395    }
1396
1397    fn finish(&mut self) {
1398        self.close();
1399        self.inner.finish();
1400    }
1401}
1402
1403impl<P: OutlinePen> CommandSink for P {
1404    fn move_to(&mut self, x: Fixed, y: Fixed) {
1405        self.move_to(x.to_f32(), y.to_f32());
1406    }
1407
1408    fn line_to(&mut self, x: Fixed, y: Fixed) {
1409        self.line_to(x.to_f32(), y.to_f32());
1410    }
1411
1412    fn curve_to(&mut self, cx0: Fixed, cy0: Fixed, cx1: Fixed, cy1: Fixed, x: Fixed, y: Fixed) {
1413        self.curve_to(
1414            cx0.to_f32(),
1415            cy0.to_f32(),
1416            cx1.to_f32(),
1417            cy1.to_f32(),
1418            x.to_f32(),
1419            y.to_f32(),
1420        );
1421    }
1422
1423    fn close(&mut self) {
1424        self.close()
1425    }
1426}
1427
1428#[cfg(test)]
1429pub(crate) mod test_helpers {
1430    use super::{CommandSink, Fixed};
1431
1432    #[derive(Copy, Clone, PartialEq, Debug)]
1433    #[allow(clippy::enum_variant_names)]
1434    pub enum Command {
1435        MoveTo(Fixed, Fixed),
1436        LineTo(Fixed, Fixed),
1437        CurveTo(Fixed, Fixed, Fixed, Fixed, Fixed, Fixed),
1438    }
1439
1440    #[derive(PartialEq, Default, Debug)]
1441    pub struct CaptureCommandSink(pub Vec<Command>);
1442
1443    impl CommandSink for CaptureCommandSink {
1444        fn move_to(&mut self, x: Fixed, y: Fixed) {
1445            self.0.push(Command::MoveTo(x, y))
1446        }
1447
1448        fn line_to(&mut self, x: Fixed, y: Fixed) {
1449            self.0.push(Command::LineTo(x, y))
1450        }
1451
1452        fn curve_to(&mut self, cx0: Fixed, cy0: Fixed, cx1: Fixed, cy1: Fixed, x: Fixed, y: Fixed) {
1453            self.0.push(Command::CurveTo(cx0, cy0, cx1, cy1, x, y))
1454        }
1455
1456        fn close(&mut self) {
1457            // For testing purposes, replace the close command
1458            // with a line to the most recent move or (0, 0)
1459            // if none exists
1460            let mut last_move = [Fixed::ZERO; 2];
1461            for command in self.0.iter().rev() {
1462                if let Command::MoveTo(x, y) = command {
1463                    last_move = [*x, *y];
1464                    break;
1465                }
1466            }
1467            self.0.push(Command::LineTo(last_move[0], last_move[1]));
1468        }
1469    }
1470
1471    impl CaptureCommandSink {
1472        pub fn to_svg(&self) -> String {
1473            use core::fmt::Write;
1474            let mut buf = String::default();
1475            for cmd in &self.0 {
1476                if !buf.is_empty() {
1477                    buf.push(' ');
1478                }
1479                match cmd {
1480                    Command::MoveTo(x, y) => write!(buf, "M{},{}", x.to_f32(), y.to_f32()).unwrap(),
1481                    Command::LineTo(x, y) => write!(buf, "L{},{}", x.to_f32(), y.to_f32()).unwrap(),
1482                    Command::CurveTo(x0, y0, x1, y1, x, y) => write!(
1483                        buf,
1484                        "C{},{} {},{} {},{}",
1485                        x0.to_f32(),
1486                        y0.to_f32(),
1487                        x1.to_f32(),
1488                        y1.to_f32(),
1489                        x.to_f32(),
1490                        y.to_f32()
1491                    )
1492                    .unwrap(),
1493                }
1494            }
1495            buf
1496        }
1497    }
1498
1499    #[derive(Default)]
1500    pub struct CharstringCommandCounter(pub usize);
1501
1502    impl CommandSink for CharstringCommandCounter {
1503        fn move_to(&mut self, _x: Fixed, _y: Fixed) {
1504            self.0 += 1;
1505        }
1506
1507        fn line_to(&mut self, _x: Fixed, _y: Fixed) {
1508            self.0 += 1;
1509        }
1510
1511        fn curve_to(
1512            &mut self,
1513            _cx0: Fixed,
1514            _cy0: Fixed,
1515            _cx1: Fixed,
1516            _cy1: Fixed,
1517            _x: Fixed,
1518            _y: Fixed,
1519        ) {
1520            self.0 += 1;
1521        }
1522
1523        fn close(&mut self) {
1524            self.0 += 1;
1525        }
1526    }
1527}
1528
1529#[cfg(test)]
1530mod tests {
1531    use super::{test_helpers::*, *};
1532    use crate::{tables::variations::ItemVariationStore, types::F2Dot14, FontData, FontRead};
1533
1534    #[test]
1535    fn cff2_example_subr() {
1536        use Command::*;
1537        let charstring = &font_test_data::cff2::EXAMPLE[0xc8..=0xe1];
1538        let store =
1539            ItemVariationStore::read(FontData::new(&font_test_data::cff2::EXAMPLE[18..])).unwrap();
1540        let coords = &[F2Dot14::from_f32(0.0)];
1541        let blend_state = BlendState::new(store, coords, 0).unwrap();
1542        let mut commands = CaptureCommandSink::default();
1543        evaluate(
1544            &NullContext(CharstringKind::Type2),
1545            Some(blend_state),
1546            charstring,
1547            &mut commands,
1548        )
1549        .unwrap();
1550        // 50 50 100 1 blend 0 rmoveto
1551        // 500 -100 -200 1 blend hlineto
1552        // 500 vlineto
1553        // -500 100 200 1 blend hlineto
1554        //
1555        // applying blends at default location results in:
1556        // 50 0 rmoveto
1557        // 500 hlineto
1558        // 500 vlineto
1559        // -500 hlineto
1560        //
1561        // applying relative operators:
1562        // 50 0 moveto
1563        // 550 0 lineto
1564        // 550 500 lineto
1565        // 50 500 lineto
1566        let expected = &[
1567            MoveTo(Fixed::from_f64(50.0), Fixed::ZERO),
1568            LineTo(Fixed::from_f64(550.0), Fixed::ZERO),
1569            LineTo(Fixed::from_f64(550.0), Fixed::from_f64(500.0)),
1570            LineTo(Fixed::from_f64(50.0), Fixed::from_f64(500.0)),
1571            LineTo(Fixed::from_f64(50.0), Fixed::ZERO),
1572        ];
1573        assert_eq!(&commands.0, expected);
1574    }
1575
1576    #[test]
1577    fn all_path_ops() {
1578        // This charstring was manually constructed in
1579        // font-test-data/test_data/ttx/charstring_path_ops.ttx
1580        //
1581        // The encoded version was extracted from the font and inlined below
1582        // for simplicity.
1583        //
1584        // The geometry is arbitrary but includes the full set of path
1585        // construction operators:
1586        // --------------------------------------------------------------------
1587        // -137 -632 rmoveto
1588        // 34 -5 20 -6 rlineto
1589        // 1 2 3 hlineto
1590        // -179 -10 3 vlineto
1591        // -30 15 22 8 -50 26 -14 -42 -41 19 -15 25 rrcurveto
1592        // -30 15 22 8 hhcurveto
1593        // 8 -30 15 22 8 hhcurveto
1594        // 24 20 15 41 42 -20 14 -24 -25 -19 -14 -42 -41 19 -15 25 hvcurveto
1595        // 20 vmoveto
1596        // -20 14 -24 -25 -19 -14 4 5 rcurveline
1597        // -20 14 -24 -25 -19 -14 4 5 rlinecurve
1598        // -55 -23 -22 -59 vhcurveto
1599        // -30 15 22 8 vvcurveto
1600        // 8 -30 15 22 8 vvcurveto
1601        // 24 20 15 41 42 -20 14 -24 -25 -19 -14 -42 23 flex
1602        // 24 20 15 41 42 -20 14 hflex
1603        // 13 hmoveto
1604        // 41 42 -20 14 -24 -25 -19 -14 -42 hflex1
1605        // 15 41 42 -20 14 -24 -25 -19 -14 -42 8 flex1
1606        // endchar
1607        let charstring = &[
1608            251, 29, 253, 12, 21, 173, 134, 159, 133, 5, 140, 141, 142, 6, 251, 71, 129, 142, 7,
1609            109, 154, 161, 147, 89, 165, 125, 97, 98, 158, 124, 164, 8, 109, 154, 161, 147, 27,
1610            147, 109, 154, 161, 147, 27, 163, 159, 154, 180, 181, 119, 153, 115, 114, 120, 125, 97,
1611            98, 158, 124, 164, 31, 159, 4, 119, 153, 115, 114, 120, 125, 143, 144, 24, 119, 153,
1612            115, 114, 120, 125, 143, 144, 25, 84, 116, 117, 80, 30, 109, 154, 161, 147, 26, 147,
1613            109, 154, 161, 147, 26, 163, 159, 154, 180, 181, 119, 153, 115, 114, 120, 125, 97, 162,
1614            12, 35, 163, 159, 154, 180, 181, 119, 153, 12, 34, 152, 22, 180, 181, 119, 153, 115,
1615            114, 120, 125, 97, 12, 36, 154, 180, 181, 119, 153, 115, 114, 120, 125, 97, 147, 12,
1616            37, 14,
1617        ];
1618        use Command::*;
1619        let mut commands = CaptureCommandSink::default();
1620        evaluate(
1621            &NullContext(CharstringKind::Type2),
1622            None,
1623            charstring,
1624            &mut commands,
1625        )
1626        .unwrap();
1627        // Expected results from extracted glyph data in
1628        // font-test-data/test_data/extracted/charstring_path_ops-glyphs.txt
1629        // --------------------------------------------------------------------
1630        // m  -137,-632
1631        // l  -103,-637
1632        // l  -83,-643
1633        // l  -82,-643
1634        // l  -82,-641
1635        // l  -79,-641
1636        // l  -79,-820
1637        // l  -89,-820
1638        // l  -89,-817
1639        // c  -119,-802 -97,-794 -147,-768
1640        // c  -161,-810 -202,-791 -217,-766
1641        // c  -247,-766 -232,-744 -224,-744
1642        // c  -254,-736 -239,-714 -231,-714
1643        // c  -207,-714 -187,-699 -187,-658
1644        // c  -187,-616 -207,-602 -231,-602
1645        // c  -256,-602 -275,-616 -275,-658
1646        // c  -275,-699 -256,-714 -231,-714
1647        // l  -137,-632
1648        // m  -231,-694
1649        // c  -251,-680 -275,-705 -294,-719
1650        // l  -290,-714
1651        // l  -310,-700
1652        // c  -334,-725 -353,-739 -349,-734
1653        // c  -349,-789 -372,-811 -431,-811
1654        // c  -431,-841 -416,-819 -416,-811
1655        // c  -408,-841 -393,-819 -393,-811
1656        // c  -369,-791 -354,-750 -312,-770
1657        // c  -298,-794 -323,-813 -337,-855
1658        // c  -313,-855 -293,-840 -252,-840
1659        // c  -210,-840 -230,-855 -216,-855
1660        // l  -231,-694
1661        // m  -203,-855
1662        // c  -162,-813 -182,-799 -206,-799
1663        // c  -231,-799 -250,-813 -292,-855
1664        // c  -277,-814 -235,-834 -221,-858
1665        // c  -246,-877 -260,-919 -292,-911
1666        // l  -203,-855
1667        let expected = &[
1668            MoveTo(Fixed::from_i32(-137), Fixed::from_i32(-632)),
1669            LineTo(Fixed::from_i32(-103), Fixed::from_i32(-637)),
1670            LineTo(Fixed::from_i32(-83), Fixed::from_i32(-643)),
1671            LineTo(Fixed::from_i32(-82), Fixed::from_i32(-643)),
1672            LineTo(Fixed::from_i32(-82), Fixed::from_i32(-641)),
1673            LineTo(Fixed::from_i32(-79), Fixed::from_i32(-641)),
1674            LineTo(Fixed::from_i32(-79), Fixed::from_i32(-820)),
1675            LineTo(Fixed::from_i32(-89), Fixed::from_i32(-820)),
1676            LineTo(Fixed::from_i32(-89), Fixed::from_i32(-817)),
1677            CurveTo(
1678                Fixed::from_i32(-119),
1679                Fixed::from_i32(-802),
1680                Fixed::from_i32(-97),
1681                Fixed::from_i32(-794),
1682                Fixed::from_i32(-147),
1683                Fixed::from_i32(-768),
1684            ),
1685            CurveTo(
1686                Fixed::from_i32(-161),
1687                Fixed::from_i32(-810),
1688                Fixed::from_i32(-202),
1689                Fixed::from_i32(-791),
1690                Fixed::from_i32(-217),
1691                Fixed::from_i32(-766),
1692            ),
1693            CurveTo(
1694                Fixed::from_i32(-247),
1695                Fixed::from_i32(-766),
1696                Fixed::from_i32(-232),
1697                Fixed::from_i32(-744),
1698                Fixed::from_i32(-224),
1699                Fixed::from_i32(-744),
1700            ),
1701            CurveTo(
1702                Fixed::from_i32(-254),
1703                Fixed::from_i32(-736),
1704                Fixed::from_i32(-239),
1705                Fixed::from_i32(-714),
1706                Fixed::from_i32(-231),
1707                Fixed::from_i32(-714),
1708            ),
1709            CurveTo(
1710                Fixed::from_i32(-207),
1711                Fixed::from_i32(-714),
1712                Fixed::from_i32(-187),
1713                Fixed::from_i32(-699),
1714                Fixed::from_i32(-187),
1715                Fixed::from_i32(-658),
1716            ),
1717            CurveTo(
1718                Fixed::from_i32(-187),
1719                Fixed::from_i32(-616),
1720                Fixed::from_i32(-207),
1721                Fixed::from_i32(-602),
1722                Fixed::from_i32(-231),
1723                Fixed::from_i32(-602),
1724            ),
1725            CurveTo(
1726                Fixed::from_i32(-256),
1727                Fixed::from_i32(-602),
1728                Fixed::from_i32(-275),
1729                Fixed::from_i32(-616),
1730                Fixed::from_i32(-275),
1731                Fixed::from_i32(-658),
1732            ),
1733            CurveTo(
1734                Fixed::from_i32(-275),
1735                Fixed::from_i32(-699),
1736                Fixed::from_i32(-256),
1737                Fixed::from_i32(-714),
1738                Fixed::from_i32(-231),
1739                Fixed::from_i32(-714),
1740            ),
1741            LineTo(Fixed::from_i32(-137), Fixed::from_i32(-632)),
1742            MoveTo(Fixed::from_i32(-231), Fixed::from_i32(-694)),
1743            CurveTo(
1744                Fixed::from_i32(-251),
1745                Fixed::from_i32(-680),
1746                Fixed::from_i32(-275),
1747                Fixed::from_i32(-705),
1748                Fixed::from_i32(-294),
1749                Fixed::from_i32(-719),
1750            ),
1751            LineTo(Fixed::from_i32(-290), Fixed::from_i32(-714)),
1752            LineTo(Fixed::from_i32(-310), Fixed::from_i32(-700)),
1753            CurveTo(
1754                Fixed::from_i32(-334),
1755                Fixed::from_i32(-725),
1756                Fixed::from_i32(-353),
1757                Fixed::from_i32(-739),
1758                Fixed::from_i32(-349),
1759                Fixed::from_i32(-734),
1760            ),
1761            CurveTo(
1762                Fixed::from_i32(-349),
1763                Fixed::from_i32(-789),
1764                Fixed::from_i32(-372),
1765                Fixed::from_i32(-811),
1766                Fixed::from_i32(-431),
1767                Fixed::from_i32(-811),
1768            ),
1769            CurveTo(
1770                Fixed::from_i32(-431),
1771                Fixed::from_i32(-841),
1772                Fixed::from_i32(-416),
1773                Fixed::from_i32(-819),
1774                Fixed::from_i32(-416),
1775                Fixed::from_i32(-811),
1776            ),
1777            CurveTo(
1778                Fixed::from_i32(-408),
1779                Fixed::from_i32(-841),
1780                Fixed::from_i32(-393),
1781                Fixed::from_i32(-819),
1782                Fixed::from_i32(-393),
1783                Fixed::from_i32(-811),
1784            ),
1785            CurveTo(
1786                Fixed::from_i32(-369),
1787                Fixed::from_i32(-791),
1788                Fixed::from_i32(-354),
1789                Fixed::from_i32(-750),
1790                Fixed::from_i32(-312),
1791                Fixed::from_i32(-770),
1792            ),
1793            CurveTo(
1794                Fixed::from_i32(-298),
1795                Fixed::from_i32(-794),
1796                Fixed::from_i32(-323),
1797                Fixed::from_i32(-813),
1798                Fixed::from_i32(-337),
1799                Fixed::from_i32(-855),
1800            ),
1801            CurveTo(
1802                Fixed::from_i32(-313),
1803                Fixed::from_i32(-855),
1804                Fixed::from_i32(-293),
1805                Fixed::from_i32(-840),
1806                Fixed::from_i32(-252),
1807                Fixed::from_i32(-840),
1808            ),
1809            CurveTo(
1810                Fixed::from_i32(-210),
1811                Fixed::from_i32(-840),
1812                Fixed::from_i32(-230),
1813                Fixed::from_i32(-855),
1814                Fixed::from_i32(-216),
1815                Fixed::from_i32(-855),
1816            ),
1817            LineTo(Fixed::from_i32(-231), Fixed::from_i32(-694)),
1818            MoveTo(Fixed::from_i32(-203), Fixed::from_i32(-855)),
1819            CurveTo(
1820                Fixed::from_i32(-162),
1821                Fixed::from_i32(-813),
1822                Fixed::from_i32(-182),
1823                Fixed::from_i32(-799),
1824                Fixed::from_i32(-206),
1825                Fixed::from_i32(-799),
1826            ),
1827            CurveTo(
1828                Fixed::from_i32(-231),
1829                Fixed::from_i32(-799),
1830                Fixed::from_i32(-250),
1831                Fixed::from_i32(-813),
1832                Fixed::from_i32(-292),
1833                Fixed::from_i32(-855),
1834            ),
1835            CurveTo(
1836                Fixed::from_i32(-277),
1837                Fixed::from_i32(-814),
1838                Fixed::from_i32(-235),
1839                Fixed::from_i32(-834),
1840                Fixed::from_i32(-221),
1841                Fixed::from_i32(-858),
1842            ),
1843            CurveTo(
1844                Fixed::from_i32(-246),
1845                Fixed::from_i32(-877),
1846                Fixed::from_i32(-260),
1847                Fixed::from_i32(-919),
1848                Fixed::from_i32(-292),
1849                Fixed::from_i32(-911),
1850            ),
1851            LineTo(Fixed::from_i32(-203), Fixed::from_i32(-855)),
1852        ];
1853        assert_eq!(&commands.0, expected);
1854    }
1855
1856    /// Fuzzer caught subtract with overflow
1857    /// <https://g-issues.oss-fuzz.com/issues/383609770>
1858    #[test]
1859    fn coords_remaining_avoid_overflow() {
1860        // Test case:
1861        // Evaluate HHCURVETO operator with 2 elements on the stack
1862        let mut commands = CaptureCommandSink::default();
1863        let mut evaluator =
1864            Evaluator::new(&NullContext(CharstringKind::Type2), None, &mut commands);
1865        evaluator.stack.push(0).unwrap();
1866        evaluator.stack.push(0).unwrap();
1867        let mut cursor = FontData::new(&[]).cursor();
1868        // Just don't panic
1869        let _ = evaluator.evaluate_operator(Operator::HhCurveTo, &mut cursor, 0);
1870    }
1871
1872    #[test]
1873    fn ignore_reserved_operators() {
1874        let charstring = &[
1875            0u8, // reserved
1876            32,  // push -107
1877            22,  // hmoveto
1878            2,   // reserved
1879        ];
1880        let mut commands = CaptureCommandSink::default();
1881        evaluate(
1882            &NullContext(CharstringKind::Type2),
1883            None,
1884            charstring,
1885            &mut commands,
1886        )
1887        .unwrap();
1888        let x = Fixed::from_i32(-107);
1889        assert_eq!(
1890            commands.0,
1891            [
1892                Command::MoveTo(x, Fixed::ZERO),
1893                Command::LineTo(x, Fixed::ZERO)
1894            ]
1895        );
1896    }
1897
1898    #[test]
1899    fn op_div() {
1900        let mut commands = CaptureCommandSink::default();
1901        let mut eval = Evaluator::new(&NullContext(CharstringKind::Type2), None, &mut commands);
1902        let mut cursor = FontData::new(&[]).cursor();
1903        eval.stack.push(Fixed::from_f64(512.5)).unwrap();
1904        eval.stack.push(2).unwrap();
1905        eval.evaluate_operator(Operator::Div, &mut cursor, 0)
1906            .unwrap();
1907        assert_eq!(
1908            eval.stack.pop_fixed().unwrap(),
1909            Fixed::from_f64(512.5 / 2.0)
1910        );
1911    }
1912
1913    #[test]
1914    fn op_div_type1_large_int() {
1915        let mut commands = CaptureCommandSink::default();
1916        let mut eval = Evaluator::new(&NullContext(CharstringKind::Type1), None, &mut commands);
1917        let mut cursor = FontData::new(&[]).cursor();
1918        // Greater than 32,000 which triggers "large int div" behavior for type1.
1919        eval.stack.push(32001).unwrap();
1920        eval.stack.push(2).unwrap();
1921        eval.evaluate_operator(Operator::Div, &mut cursor, 0)
1922            .unwrap();
1923        assert_eq!(
1924            eval.stack.pop_fixed().unwrap(),
1925            Fixed::from_f64(32001.0 / 2.0)
1926        );
1927    }
1928
1929    /// Shared code for the (h)sbw tests.
1930    ///
1931    /// Returns [sbx, wx]
1932    fn eval_h_sbw(operator: Operator, kind: CharstringKind) -> [Fixed; 2] {
1933        let mut commands = CaptureCommandSink::default();
1934        let ctx = &NullContext(kind);
1935        let mut eval = Evaluator::new(ctx, None, &mut commands);
1936        let mut cursor = FontData::new(&[]).cursor();
1937        eval.stack.push(Fixed::from_f64(42.5)).unwrap();
1938        if operator == Operator::Sbw {
1939            // sbw includes y coords
1940            eval.stack.push(0).unwrap();
1941        }
1942        eval.stack.push(501).unwrap();
1943        eval.stack.push(1000).unwrap();
1944        eval.evaluate_operator(operator, &mut cursor, 0).unwrap();
1945        [eval.sbx, eval.wx]
1946    }
1947
1948    #[test]
1949    fn op_sbw_type1() {
1950        let [sbx, wx] = eval_h_sbw(Operator::Sbw, CharstringKind::Type1);
1951        assert_eq!(sbx, Fixed::from_f64(42.5));
1952        assert_eq!(wx, Fixed::from_f64(501.0));
1953    }
1954
1955    #[test]
1956    fn op_hsbw_type1() {
1957        let [sbx, wx] = eval_h_sbw(Operator::Hsbw, CharstringKind::Type1);
1958        assert_eq!(sbx, Fixed::from_f64(42.5));
1959        assert_eq!(wx, Fixed::from_f64(501.0));
1960    }
1961
1962    /// sbw is ignored in type 2
1963    #[test]
1964    fn op_sbw_type2_no_effect() {
1965        let [sbx, wx] = eval_h_sbw(Operator::Sbw, CharstringKind::Type2);
1966        assert_eq!(sbx, Fixed::ZERO);
1967        assert_eq!(wx, Fixed::ZERO);
1968    }
1969
1970    /// hsbw is ignored in type 2
1971    #[test]
1972    fn op_hsbw_type2_no_effect() {
1973        let [sbx, wx] = eval_h_sbw(Operator::Hsbw, CharstringKind::Type2);
1974        assert_eq!(sbx, Fixed::ZERO);
1975        assert_eq!(wx, Fixed::ZERO);
1976    }
1977
1978    #[test]
1979    fn op_callothersubr_flex() {
1980        let mut commands = CaptureCommandSink::default();
1981        let mut eval = Evaluator::new(&NullContext(CharstringKind::Type1), None, &mut commands);
1982        let mut cursor = FontData::new(&[]).cursor();
1983        // push some numbers and optionally evaluate an operator
1984        macro_rules! op {
1985            ($nums:expr) => {
1986                for n in $nums {
1987                    eval.stack.push(n).unwrap();
1988                }
1989            };
1990            ($nums:expr, $op:ident) => {
1991                op!($nums);
1992                eval.evaluate_operator(Operator::$op, &mut cursor, 0)
1993                    .unwrap();
1994            };
1995        }
1996        // Emulate a flex vector call
1997        // begin flex
1998        op!([0, 1], CallOtherSubr);
1999        // emit flex vectors with a series of moves
2000        for vec in [[1, 2]; 7] {
2001            op!(vec, RMoveTo);
2002        }
2003        // flex_height, final_x, final_y
2004        op!([0, 100, 200]);
2005        // end flex
2006        op!([3, 0], CallOtherSubr);
2007        // flex usually ends with a subr call to setcurrentpoint
2008        // which makes use of the final coords pushed to the stack
2009        let none: [i32; 0] = [];
2010        op!(none, SetCurrentPoint);
2011        let expected = [
2012            Command::MoveTo(Fixed::ZERO, Fixed::ZERO),
2013            Command::CurveTo(
2014                Fixed::from_i32(2),
2015                Fixed::from_i32(4),
2016                Fixed::from_i32(3),
2017                Fixed::from_i32(6),
2018                Fixed::from_i32(4),
2019                Fixed::from_i32(8),
2020            ),
2021            Command::CurveTo(
2022                Fixed::from_i32(5),
2023                Fixed::from_i32(10),
2024                Fixed::from_i32(6),
2025                Fixed::from_i32(12),
2026                Fixed::from_i32(7),
2027                Fixed::from_i32(14),
2028            ),
2029        ];
2030        assert_eq!(eval.x, Fixed::from_i32(100));
2031        assert_eq!(eval.y, Fixed::from_i32(200));
2032        assert_eq!(commands.0, expected);
2033    }
2034
2035    struct NullContext(CharstringKind);
2036
2037    impl CharstringContext for NullContext {
2038        fn kind(&self) -> CharstringKind {
2039            self.0
2040        }
2041
2042        fn seac_components(&self, base_code: i32, _accent_code: i32) -> Result<[&[u8]; 2], Error> {
2043            Err(Error::InvalidSeacCode(base_code))
2044        }
2045
2046        fn global_subr(&self, _index: i32) -> Result<&[u8], Error> {
2047            Err(Error::MissingSubroutines)
2048        }
2049
2050        fn subr(&self, _index: i32) -> Result<&[u8], Error> {
2051            Err(Error::MissingSubroutines)
2052        }
2053    }
2054
2055    #[test]
2056    fn nop_filter_sink() {
2057        let mut commands = CaptureCommandSink::default();
2058        let mut nop_filter = NopFilterSink::new(&mut commands);
2059        let (sx, sy) = (Fixed::from_f64(10.2), Fixed::from_f64(20.4));
2060        // filtered
2061        nop_filter.move_to(Fixed::from_f64(0.0), Fixed::from_f64(0.0));
2062        nop_filter.move_to(sx, sy);
2063        // filtered
2064        nop_filter.line_to(sx, sy);
2065        nop_filter.curve_to(
2066            Fixed::from_f64(5.0),
2067            Fixed::from_f64(-5.0),
2068            Fixed::from_f64(1.5),
2069            Fixed::from_f64(2.0),
2070            Fixed::from_f64(4.5),
2071            Fixed::from_f64(-10.0),
2072        );
2073        // filtered
2074        nop_filter.line_to(Fixed::from_f64(4.5), Fixed::from_f64(-10.0));
2075        // filtered due to next close
2076        nop_filter.line_to(sx, sy);
2077        nop_filter.close();
2078        assert_eq!(
2079            commands.0,
2080            [
2081                Command::MoveTo(sx, sy),
2082                Command::CurveTo(
2083                    Fixed::from_f64(5.0),
2084                    Fixed::from_f64(-5.0),
2085                    Fixed::from_f64(1.5),
2086                    Fixed::from_f64(2.0),
2087                    Fixed::from_f64(4.5),
2088                    Fixed::from_f64(-10.0),
2089                ),
2090                Command::LineTo(sx, sy),
2091            ]
2092        )
2093    }
2094
2095    #[test]
2096    fn scaled_matrix_transform_sink() {
2097        // A few points taken from the test font in <https://github.com/googlefonts/fontations/issues/1581>
2098        // Inputs and expected values extracted from FreeType
2099        let input = [(150i32, 46i32), (176, 8), (217, -13), (267, -13)]
2100            .map(|(x, y)| (Fixed::from_bits(x << 16), Fixed::from_bits(y << 16)));
2101        let expected = [(404, 118i32), (453, 20), (550, -33), (678, -33)]
2102            .map(|(x, y)| (Fixed::from_bits(x << 10), Fixed::from_bits(y << 10)));
2103        let mut dummy = ();
2104        let sink =
2105            TransformSink::from_matrix_scale(&mut dummy, TRANSFORM, Some(Fixed::from_bits(167772)));
2106        let transformed = input.map(|(x, y)| sink.transform(x, y));
2107        assert_eq!(transformed, expected);
2108    }
2109
2110    #[test]
2111    fn unscaled_matrix_transform_sink() {
2112        // A few points taken from the test font in <https://github.com/googlefonts/fontations/issues/1581>
2113        // Inputs and expected values extracted from FreeType
2114        let input = [(150i32, 46i32), (176, 8), (217, -13), (267, -13)]
2115            .map(|(x, y)| (Fixed::from_bits(x << 16), Fixed::from_bits(y << 16)));
2116        let expected = [(158, 46i32), (177, 8), (215, -13), (265, -13)]
2117            .map(|(x, y)| (Fixed::from_bits(x << 16), Fixed::from_bits(y << 16)));
2118        let mut dummy = ();
2119        let sink = TransformSink::from_matrix_scale(&mut dummy, TRANSFORM, None);
2120        let transformed = input.map(|(x, y)| sink.transform(x, y));
2121        assert_eq!(transformed, expected);
2122    }
2123
2124    const TRANSFORM: FontMatrix = FontMatrix::from_elements([
2125        Fixed::ONE,
2126        Fixed::ZERO,
2127        // 0.167007446289062
2128        Fixed::from_bits(10945),
2129        Fixed::ONE,
2130        Fixed::ZERO,
2131        Fixed::ZERO,
2132    ]);
2133
2134    #[test]
2135    fn unscaled_transform_sink_produces_integers() {
2136        let nothing = &mut ();
2137        let sink = TransformSink::new(nothing, Transform::default());
2138        for coord in [50.0, 50.1, 50.125, 50.5, 50.9] {
2139            assert_eq!(
2140                sink.transform(Fixed::from_f64(coord), Fixed::ZERO)
2141                    .0
2142                    .to_f32(),
2143                50.0
2144            );
2145        }
2146    }
2147
2148    #[test]
2149    fn scaled_transform_sink() {
2150        let ppem = 20.0;
2151        let upem = 1000.0;
2152        // match FreeType scaling with intermediate conversion to 26.6
2153        let scale = Fixed::from_bits((ppem * 64.) as i32) / Fixed::from_bits(upem as i32);
2154        let nothing = &mut ();
2155        let sink = TransformSink::from_matrix_scale(nothing, FontMatrix::IDENTITY, Some(scale));
2156        let inputs = [
2157            // input coord, expected scaled output
2158            (0.0, 0.0),
2159            (8.0, 0.15625),
2160            (16.0, 0.3125),
2161            (32.0, 0.640625),
2162            (72.0, 1.4375),
2163            (128.0, 2.5625),
2164        ];
2165        for (coord, expected) in inputs {
2166            assert_eq!(
2167                sink.transform(Fixed::from_f64(coord), Fixed::ZERO)
2168                    .0
2169                    .to_f32(),
2170                expected,
2171                "scaling coord {coord}"
2172            );
2173        }
2174    }
2175
2176    #[test]
2177    fn mm_blend() {
2178        let mut commands = CaptureCommandSink::default();
2179        let ctx = MmContext([0.0, -0.25, 1.0].map(Fixed::from_f64));
2180        let mut eval = Evaluator::new(&ctx, None, &mut commands);
2181        let mut cursor = FontData::new(&[]).cursor();
2182        // First two values are base coords. Next four are deltas
2183        // for those coords (two each). Last two values are arg
2184        // count and othersubr number.
2185        for i in [[1, 0], [2, 3], [4, -8], [6, 15]].into_iter().flatten() {
2186            eval.stack.push(i).unwrap();
2187        }
2188        eval.evaluate_operator(Operator::CallOtherSubr, &mut cursor, 0)
2189            .unwrap();
2190        let [a, b] = eval.stack.fixed_array(0).unwrap();
2191        // a = 1 + -0.25*2 + 1*3 = 3.5
2192        assert_eq!(a.to_f32(), 3.5);
2193        // b = 0 + -0.25*4 + 1*-8 = -9.0
2194        assert_eq!(b.to_f32(), -9.0);
2195    }
2196
2197    struct MmContext([Fixed; 3]);
2198
2199    impl CharstringContext for MmContext {
2200        fn kind(&self) -> CharstringKind {
2201            CharstringKind::Type1
2202        }
2203
2204        fn seac_components(&self, base_code: i32, _accent_code: i32) -> Result<[&[u8]; 2], Error> {
2205            Err(Error::InvalidSeacCode(base_code))
2206        }
2207
2208        fn global_subr(&self, _index: i32) -> Result<&[u8], Error> {
2209            Err(Error::MissingSubroutines)
2210        }
2211
2212        fn subr(&self, _index: i32) -> Result<&[u8], Error> {
2213            Err(Error::MissingSubroutines)
2214        }
2215
2216        fn weight_vector(&self) -> &[Fixed] {
2217            &self.0
2218        }
2219    }
2220}