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