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