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