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