1use crate::{Font, be_i16, be_u16};
19
20pub const MAX_COMPOSITE_DEPTH: usize = 8;
24
25pub const MAX_OUTLINE_POINTS: usize = 65_536;
30
31pub const MAX_COMPONENTS: usize = 512;
34
35#[derive(Debug, Clone, Copy, PartialEq)]
39pub struct Point {
40 pub x: f64,
42 pub y: f64,
44}
45
46impl Point {
47 fn midpoint(self, other: Point) -> Point {
48 Point {
49 x: (self.x + other.x) / 2.0,
50 y: (self.y + other.y) / 2.0,
51 }
52 }
53}
54
55#[derive(Debug, Clone, Copy, PartialEq)]
58pub enum Segment {
59 Line {
61 to: Point,
63 },
64 Quad {
66 ctrl: Point,
68 to: Point,
70 },
71}
72
73impl Segment {
74 #[must_use]
76 pub fn to(&self) -> Point {
77 match self {
78 Self::Line { to } | Self::Quad { to, .. } => *to,
79 }
80 }
81}
82
83#[derive(Debug, Clone, PartialEq)]
87pub struct Contour {
88 pub start: Point,
91 pub segments: Vec<Segment>,
93}
94
95#[derive(Debug, Clone, PartialEq)]
97pub struct GlyphOutline {
98 pub contours: Vec<Contour>,
100 pub advance: u16,
103 pub lsb: i16,
105 pub rsb: i32,
109 pub bbox: Option<[i16; 4]>,
112}
113
114impl GlyphOutline {
115 #[must_use]
119 pub fn extents(&self) -> Option<[f64; 4]> {
120 let mut ext: Option<[f64; 4]> = None;
121 let mut fold = |p: Point| {
122 ext = Some(match ext {
123 None => [p.x, p.y, p.x, p.y],
124 Some([x0, y0, x1, y1]) => [x0.min(p.x), y0.min(p.y), x1.max(p.x), y1.max(p.y)],
125 });
126 };
127 for c in &self.contours {
128 fold(c.start);
129 for s in &c.segments {
130 if let Segment::Quad { ctrl, .. } = s {
131 fold(*ctrl);
132 }
133 fold(s.to());
134 }
135 }
136 ext
137 }
138}
139
140#[derive(Debug, Clone, Copy, PartialEq, Eq)]
142pub enum OutlineError {
143 NoGlyfOutlines,
145 BadGlyphId,
147 Malformed,
150 BudgetExceeded,
153}
154
155impl core::fmt::Display for OutlineError {
156 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
157 match self {
158 Self::NoGlyfOutlines => write!(f, "font has no TrueType (glyf) outlines"),
159 Self::BadGlyphId => write!(f, "glyph id out of range"),
160 Self::Malformed => write!(f, "glyph outline data is malformed"),
161 Self::BudgetExceeded => write!(f, "glyph outline exceeds decode budgets"),
162 }
163 }
164}
165
166impl std::error::Error for OutlineError {}
167
168#[derive(Debug, Clone, Copy)]
170struct RawPoint {
171 x: f64,
172 y: f64,
173 on_curve: bool,
174}
175
176#[derive(Debug, Default)]
180struct RawGlyph {
181 points: Vec<RawPoint>,
182 contour_ends: Vec<usize>,
183}
184
185struct Budget {
187 points_left: usize,
188 components_left: usize,
189}
190
191const ARG_1_AND_2_ARE_WORDS: u16 = 0x0001;
193const ARGS_ARE_XY_VALUES: u16 = 0x0002;
194const WE_HAVE_A_SCALE: u16 = 0x0008;
195const MORE_COMPONENTS: u16 = 0x0020;
196const X_AND_Y_SCALE: u16 = 0x0040;
197const TWO_BY_TWO: u16 = 0x0080;
198const USE_MY_METRICS: u16 = 0x0200;
199const SCALED_COMPONENT_OFFSET: u16 = 0x0800;
200const UNSCALED_COMPONENT_OFFSET: u16 = 0x1000;
201
202fn f2dot14(v: i16) -> f64 {
203 f64::from(v) / 16384.0
204}
205
206impl Font {
207 pub fn glyph_outline(&self, gid: u16) -> Result<GlyphOutline, OutlineError> {
217 if !self.has_glyf_outlines() {
218 return Err(OutlineError::NoGlyfOutlines);
219 }
220 if gid >= self.num_glyphs {
221 return Err(OutlineError::BadGlyphId);
222 }
223 let mut budget = Budget {
224 points_left: MAX_OUTLINE_POINTS,
225 components_left: MAX_COMPONENTS,
226 };
227 let raw = decode_raw(self, gid, 0, &mut budget)?;
228 let (advance, lsb) = resolve_metrics(self, gid, 0);
229 let bbox = self.glyph_bbox(gid);
230 let rsb = match bbox {
231 Some([x_min, _, x_max, _]) => {
232 i32::from(advance) - i32::from(lsb) - (i32::from(x_max) - i32::from(x_min))
233 }
234 None => i32::from(advance) - i32::from(lsb),
235 };
236 Ok(GlyphOutline {
237 contours: to_contours(&raw),
238 advance,
239 lsb,
240 rsb,
241 bbox,
242 })
243 }
244}
245
246fn resolve_metrics(font: &Font, gid: u16, depth: usize) -> (u16, i16) {
249 if depth < MAX_COMPOSITE_DEPTH
250 && let Some(data) = font.glyph_data(gid)
251 && let Some(metrics_gid) = use_my_metrics_component(data)
252 && metrics_gid != gid
253 {
254 return resolve_metrics(font, metrics_gid, depth + 1);
255 }
256 (font.advance_width(gid), font.left_side_bearing(gid))
257}
258
259fn use_my_metrics_component(data: &[u8]) -> Option<u16> {
261 let num_contours = be_i16(data, 0)?;
262 if num_contours >= 0 {
263 return None;
264 }
265 let mut p = 10usize;
266 for _ in 0..MAX_COMPONENTS {
267 let flags = be_u16(data, p)?;
268 let comp = be_u16(data, p.checked_add(2)?)?;
269 if flags & USE_MY_METRICS != 0 {
270 return Some(comp);
271 }
272 p = p.checked_add(component_record_len(flags))?;
273 if flags & MORE_COMPONENTS == 0 {
274 return None;
275 }
276 }
277 None
278}
279
280fn component_record_len(flags: u16) -> usize {
283 let args = if flags & ARG_1_AND_2_ARE_WORDS != 0 {
284 4
285 } else {
286 2
287 };
288 let xform = if flags & WE_HAVE_A_SCALE != 0 {
289 2
290 } else if flags & X_AND_Y_SCALE != 0 {
291 4
292 } else if flags & TWO_BY_TWO != 0 {
293 8
294 } else {
295 0
296 };
297 4 + args + xform
298}
299
300fn decode_raw(
303 font: &Font,
304 gid: u16,
305 depth: usize,
306 budget: &mut Budget,
307) -> Result<RawGlyph, OutlineError> {
308 if depth > MAX_COMPOSITE_DEPTH {
309 return Err(OutlineError::BudgetExceeded);
310 }
311 let data = font.glyph_data(gid).ok_or(OutlineError::BadGlyphId)?;
312 if data.is_empty() {
313 return Ok(RawGlyph::default()); }
315 let num_contours = be_i16(data, 0).ok_or(OutlineError::Malformed)?;
316 if num_contours >= 0 {
317 decode_simple(data, num_contours as usize, budget)
318 } else {
319 decode_composite(font, data, depth, budget)
320 }
321}
322
323fn decode_simple(
325 data: &[u8],
326 num_contours: usize,
327 budget: &mut Budget,
328) -> Result<RawGlyph, OutlineError> {
329 let mut contour_ends = Vec::with_capacity(num_contours);
331 let mut prev_end: Option<usize> = None;
332 for i in 0..num_contours {
333 let off = 10usize
334 .checked_add(i.checked_mul(2).ok_or(OutlineError::Malformed)?)
335 .ok_or(OutlineError::Malformed)?;
336 let end = be_u16(data, off).ok_or(OutlineError::Malformed)? as usize;
337 if prev_end.is_some_and(|p| end < p) {
340 return Err(OutlineError::Malformed);
341 }
342 prev_end = Some(end);
343 contour_ends.push(end);
344 }
345 let num_points = match contour_ends.last() {
346 Some(&last) => last + 1,
347 None => {
348 return Ok(RawGlyph::default()); }
350 };
351 if num_points > budget.points_left {
352 return Err(OutlineError::BudgetExceeded);
353 }
354 budget.points_left -= num_points;
355
356 let instr_off = 10 + num_contours * 2;
357 let instr_len = be_u16(data, instr_off).ok_or(OutlineError::Malformed)? as usize;
358 let mut p = instr_off
359 .checked_add(2)
360 .and_then(|v| v.checked_add(instr_len))
361 .ok_or(OutlineError::Malformed)?;
362
363 const ON_CURVE: u8 = 0x01;
365 const X_SHORT: u8 = 0x02;
366 const Y_SHORT: u8 = 0x04;
367 const REPEAT: u8 = 0x08;
368 const X_SAME_OR_POS: u8 = 0x10;
369 const Y_SAME_OR_POS: u8 = 0x20;
370 let mut flags = Vec::with_capacity(num_points);
371 while flags.len() < num_points {
372 let f = *data.get(p).ok_or(OutlineError::Malformed)?;
373 p += 1;
374 flags.push(f);
375 if f & REPEAT != 0 {
376 let n = *data.get(p).ok_or(OutlineError::Malformed)? as usize;
377 p += 1;
378 if flags.len() + n > num_points {
379 return Err(OutlineError::Malformed);
380 }
381 for _ in 0..n {
382 flags.push(f);
383 }
384 }
385 }
386
387 let mut points = Vec::with_capacity(num_points);
389 let mut x = 0i32;
390 for &f in &flags {
391 let dx = if f & X_SHORT != 0 {
392 let b = i32::from(*data.get(p).ok_or(OutlineError::Malformed)?);
393 p += 1;
394 if f & X_SAME_OR_POS != 0 { b } else { -b }
395 } else if f & X_SAME_OR_POS != 0 {
396 0
397 } else {
398 let v = i32::from(be_i16(data, p).ok_or(OutlineError::Malformed)?);
399 p += 2;
400 v
401 };
402 x += dx;
403 points.push(RawPoint {
404 x: f64::from(x),
405 y: 0.0,
406 on_curve: f & ON_CURVE != 0,
407 });
408 }
409 let mut y = 0i32;
410 for (i, &f) in flags.iter().enumerate() {
411 let dy = if f & Y_SHORT != 0 {
412 let b = i32::from(*data.get(p).ok_or(OutlineError::Malformed)?);
413 p += 1;
414 if f & Y_SAME_OR_POS != 0 { b } else { -b }
415 } else if f & Y_SAME_OR_POS != 0 {
416 0
417 } else {
418 let v = i32::from(be_i16(data, p).ok_or(OutlineError::Malformed)?);
419 p += 2;
420 v
421 };
422 y += dy;
423 if let Some(pt) = points.get_mut(i) {
424 pt.y = f64::from(y);
425 }
426 }
427
428 Ok(RawGlyph {
429 points,
430 contour_ends,
431 })
432}
433
434fn decode_composite(
438 font: &Font,
439 data: &[u8],
440 depth: usize,
441 budget: &mut Budget,
442) -> Result<RawGlyph, OutlineError> {
443 let mut out = RawGlyph::default();
444 let mut p = 10usize;
445 loop {
446 if budget.components_left == 0 {
447 return Err(OutlineError::BudgetExceeded);
448 }
449 budget.components_left -= 1;
450
451 let flags = be_u16(data, p).ok_or(OutlineError::Malformed)?;
452 let comp_gid = be_u16(data, p.checked_add(2).ok_or(OutlineError::Malformed)?)
453 .ok_or(OutlineError::Malformed)?;
454 let record_end = p
455 .checked_add(component_record_len(flags))
456 .ok_or(OutlineError::Malformed)?;
457 if record_end > data.len() {
458 return Err(OutlineError::Malformed);
459 }
460
461 let arg_base = p + 4;
464 let (arg1, arg2) = if flags & ARG_1_AND_2_ARE_WORDS != 0 {
465 (
466 i32::from(be_i16(data, arg_base).ok_or(OutlineError::Malformed)?),
467 i32::from(be_i16(data, arg_base + 2).ok_or(OutlineError::Malformed)?),
468 )
469 } else {
470 let a = *data.get(arg_base).ok_or(OutlineError::Malformed)?;
471 let b = *data.get(arg_base + 1).ok_or(OutlineError::Malformed)?;
472 if flags & ARGS_ARE_XY_VALUES != 0 {
473 (i32::from(a as i8), i32::from(b as i8))
474 } else {
475 (i32::from(a), i32::from(b))
476 }
477 };
478
479 let xf_base = arg_base
481 + if flags & ARG_1_AND_2_ARE_WORDS != 0 {
482 4
483 } else {
484 2
485 };
486 let (a, b, c, d) = if flags & WE_HAVE_A_SCALE != 0 {
487 let s = f2dot14(be_i16(data, xf_base).ok_or(OutlineError::Malformed)?);
488 (s, 0.0, 0.0, s)
489 } else if flags & X_AND_Y_SCALE != 0 {
490 (
491 f2dot14(be_i16(data, xf_base).ok_or(OutlineError::Malformed)?),
492 0.0,
493 0.0,
494 f2dot14(be_i16(data, xf_base + 2).ok_or(OutlineError::Malformed)?),
495 )
496 } else if flags & TWO_BY_TWO != 0 {
497 (
498 f2dot14(be_i16(data, xf_base).ok_or(OutlineError::Malformed)?),
499 f2dot14(be_i16(data, xf_base + 2).ok_or(OutlineError::Malformed)?),
500 f2dot14(be_i16(data, xf_base + 4).ok_or(OutlineError::Malformed)?),
501 f2dot14(be_i16(data, xf_base + 6).ok_or(OutlineError::Malformed)?),
502 )
503 } else {
504 (1.0, 0.0, 0.0, 1.0)
505 };
506 let apply = |pt: RawPoint, dx: f64, dy: f64| RawPoint {
507 x: a * pt.x + c * pt.y + dx,
508 y: b * pt.x + d * pt.y + dy,
509 on_curve: pt.on_curve,
510 };
511
512 let child = decode_raw(font, comp_gid, depth + 1, budget)?;
513
514 let (dx, dy) = if flags & ARGS_ARE_XY_VALUES != 0 {
515 let (dx, dy) = (f64::from(arg1), f64::from(arg2));
521 if flags & SCALED_COMPONENT_OFFSET != 0 && flags & UNSCALED_COMPONENT_OFFSET == 0 {
522 (a * dx + c * dy, b * dx + d * dy)
523 } else {
524 (dx, dy)
525 }
526 } else {
527 let parent_ix = usize::try_from(arg1).map_err(|_| OutlineError::Malformed)?;
531 let child_ix = usize::try_from(arg2).map_err(|_| OutlineError::Malformed)?;
532 let parent_pt = *out.points.get(parent_ix).ok_or(OutlineError::Malformed)?;
533 let child_pt = *child.points.get(child_ix).ok_or(OutlineError::Malformed)?;
534 let placed = apply(child_pt, 0.0, 0.0);
535 (parent_pt.x - placed.x, parent_pt.y - placed.y)
536 };
537
538 let base = out.points.len();
539 out.points
540 .extend(child.points.iter().map(|&pt| apply(pt, dx, dy)));
541 out.contour_ends
542 .extend(child.contour_ends.iter().map(|&e| base + e));
543
544 if flags & MORE_COMPONENTS == 0 {
545 break;
546 }
547 p = record_end;
548 }
549 Ok(out)
550}
551
552fn to_contours(raw: &RawGlyph) -> Vec<Contour> {
556 let mut contours = Vec::with_capacity(raw.contour_ends.len());
557 let mut start_ix = 0usize;
558 for &end in &raw.contour_ends {
559 let Some(pts) = raw.points.get(start_ix..=end) else {
560 break; };
562 start_ix = end + 1;
563 if let Some(c) = contour_to_quads(pts) {
564 contours.push(c);
565 }
566 }
567 contours
568}
569
570fn contour_to_quads(pts: &[RawPoint]) -> Option<Contour> {
573 if pts.len() < 2 {
574 return None;
575 }
576 let n = pts.len();
577 let first_on = pts.iter().position(|p| p.on_curve);
580 let (start, mut pending_ctrl, seq_start) = match first_on {
581 Some(i) => {
582 let p = pts[i];
583 (Point { x: p.x, y: p.y }, None, i + 1)
584 }
585 None => {
586 let last = pts[n - 1];
587 let first = pts[0];
588 let mid = Point {
589 x: (last.x + first.x) / 2.0,
590 y: (last.y + first.y) / 2.0,
591 };
592 (mid, None, 0)
593 }
594 };
595
596 let mut segments = Vec::with_capacity(n);
597 let mut cursor = start;
598 let count = if first_on.is_some() { n - 1 } else { n };
601 let mut emit = |ctrl: Option<Point>, to: Point, segments: &mut Vec<Segment>| {
602 match ctrl {
603 Some(ctrl) => segments.push(Segment::Quad { ctrl, to }),
604 None => {
605 if to != cursor {
608 segments.push(Segment::Line { to });
609 }
610 }
611 }
612 cursor = to;
613 };
614 for k in 0..count {
615 let p = pts[(seq_start + k) % n];
616 let point = Point { x: p.x, y: p.y };
617 if p.on_curve {
618 emit(pending_ctrl.take(), point, &mut segments);
619 } else if let Some(prev_ctrl) = pending_ctrl.replace(point) {
620 emit(Some(prev_ctrl), prev_ctrl.midpoint(point), &mut segments);
623 pending_ctrl = Some(point);
624 }
625 }
626 match pending_ctrl.take() {
628 Some(ctrl) => segments.push(Segment::Quad { ctrl, to: start }),
629 None => {
630 if cursor != start {
631 segments.push(Segment::Line { to: start });
632 }
633 }
634 }
635 if segments.is_empty() {
636 return None;
637 }
638 Some(Contour { start, segments })
639}
640
641#[cfg(test)]
642#[allow(clippy::unwrap_used, clippy::expect_used, clippy::indexing_slicing)]
643mod hostile_outline_tests {
644 use super::*;
649
650 fn push16(v: &mut Vec<u8>, x: u16) {
651 v.extend_from_slice(&x.to_be_bytes());
652 }
653 fn push_i16(v: &mut Vec<u8>, x: i16) {
654 v.extend_from_slice(&x.to_be_bytes());
655 }
656 fn push32(v: &mut Vec<u8>, x: u32) {
657 v.extend_from_slice(&x.to_be_bytes());
658 }
659
660 fn sfnt(tables: &[(&[u8; 4], Vec<u8>)]) -> Vec<u8> {
661 let mut out = Vec::new();
662 push32(&mut out, 0x0001_0000);
663 push16(&mut out, u16::try_from(tables.len()).unwrap());
664 out.extend_from_slice(&[0u8; 6]);
665 let mut offset = 12 + tables.len() * 16;
666 let mut body = Vec::new();
667 for (tag, bytes) in tables {
668 out.extend_from_slice(&tag[..]);
669 push32(&mut out, 0);
670 push32(&mut out, u32::try_from(offset).unwrap());
671 push32(&mut out, u32::try_from(bytes.len()).unwrap());
672 offset += bytes.len();
673 body.extend_from_slice(bytes);
674 }
675 out.extend_from_slice(&body);
676 out
677 }
678
679 fn font_with_glyphs(glyphs: &[Vec<u8>]) -> Font {
682 let n = u16::try_from(glyphs.len()).unwrap();
683 let mut head = vec![0u8; 54];
684 head[18..20].copy_from_slice(&1000u16.to_be_bytes());
685 head[50..52].copy_from_slice(&1u16.to_be_bytes()); let mut maxp = vec![0u8; 6];
687 maxp[4..6].copy_from_slice(&n.to_be_bytes());
688 let mut hhea = vec![0u8; 36];
689 hhea[4..6].copy_from_slice(&700i16.to_be_bytes());
690 hhea[6..8].copy_from_slice(&(-200i16).to_be_bytes());
691 hhea[34..36].copy_from_slice(&n.to_be_bytes());
692 let mut hmtx = Vec::new();
693 for _ in 0..n {
694 push16(&mut hmtx, 600);
695 push_i16(&mut hmtx, 50);
696 }
697 let mut cmap = Vec::new();
699 push16(&mut cmap, 0);
700 push16(&mut cmap, 1);
701 push16(&mut cmap, 3);
702 push16(&mut cmap, 1);
703 push32(&mut cmap, 12);
704 push16(&mut cmap, 4); push16(&mut cmap, 32); push16(&mut cmap, 0); push16(&mut cmap, 2); push16(&mut cmap, 0);
709 push16(&mut cmap, 0);
710 push16(&mut cmap, 0);
711 push16(&mut cmap, 0xFFFF); push16(&mut cmap, 0); push16(&mut cmap, 0xFFFF); push16(&mut cmap, 1); push16(&mut cmap, 0); let mut loca = Vec::new();
717 let mut glyf = Vec::new();
718 push32(&mut loca, 0);
719 for g in glyphs {
720 glyf.extend_from_slice(g);
721 push32(&mut loca, u32::try_from(glyf.len()).unwrap());
722 }
723 Font::parse(sfnt(&[
724 (b"head", head),
725 (b"maxp", maxp),
726 (b"hhea", hhea),
727 (b"hmtx", hmtx),
728 (b"cmap", cmap),
729 (b"loca", loca),
730 (b"glyf", glyf),
731 ]))
732 .expect("synthetic font parses")
733 }
734
735 fn triangle_glyph() -> Vec<u8> {
737 let mut g = Vec::new();
738 push_i16(&mut g, 1); push_i16(&mut g, 0); push_i16(&mut g, 0); push_i16(&mut g, 500); push_i16(&mut g, 400); push16(&mut g, 2); push16(&mut g, 0); g.extend_from_slice(&[0x01, 0x01, 0x01]); push_i16(&mut g, 0); push_i16(&mut g, 500);
748 push_i16(&mut g, -250);
749 push_i16(&mut g, 0); push_i16(&mut g, 0);
751 push_i16(&mut g, 400);
752 g
753 }
754
755 fn composite_glyph(component_gid: u16, dx: i16, dy: i16, more: bool) -> Vec<u8> {
757 let mut g = Vec::new();
758 push_i16(&mut g, -1);
759 for _ in 0..4 {
760 push_i16(&mut g, 0); }
762 let mut flags = ARG_1_AND_2_ARE_WORDS | ARGS_ARE_XY_VALUES;
763 if more {
764 flags |= MORE_COMPONENTS;
765 }
766 push16(&mut g, flags);
767 push16(&mut g, component_gid);
768 push_i16(&mut g, dx);
769 push_i16(&mut g, dy);
770 g
771 }
772
773 #[test]
774 fn triangle_decodes_closed_with_exact_extents() {
775 let font = font_with_glyphs(&[triangle_glyph()]);
776 let o = font.glyph_outline(0).expect("triangle decodes");
777 assert_eq!(o.contours.len(), 1);
778 let c = &o.contours[0];
779 assert_eq!(c.segments.last().unwrap().to(), c.start, "contour closes");
780 assert_eq!(o.extents(), Some([0.0, 0.0, 500.0, 400.0]));
781 assert_eq!(o.bbox, Some([0, 0, 500, 400]));
782 assert_eq!((o.advance, o.lsb), (600, 50));
783 assert_eq!(o.rsb, i32::from(600u16) - 50 - 500);
784 }
785
786 #[test]
787 fn blank_glyph_decodes_empty() {
788 let font = font_with_glyphs(&[Vec::new()]);
789 let o = font.glyph_outline(0).expect("blank glyph decodes");
790 assert!(o.contours.is_empty());
791 assert_eq!(o.bbox, None);
792 assert_eq!(o.rsb, 600 - 50);
793 }
794
795 #[test]
796 fn out_of_range_gid_is_rejected() {
797 let font = font_with_glyphs(&[triangle_glyph()]);
798 assert_eq!(font.glyph_outline(7), Err(OutlineError::BadGlyphId));
799 }
800
801 #[test]
802 fn composite_offsets_translate_the_component() {
803 let font = font_with_glyphs(&[triangle_glyph(), composite_glyph(0, 100, -50, false)]);
804 let o = font.glyph_outline(1).expect("composite decodes");
805 assert_eq!(o.extents(), Some([100.0, -50.0, 600.0, 350.0]));
806 }
807
808 #[test]
809 fn self_referential_composite_trips_the_depth_budget() {
810 let font = font_with_glyphs(&[composite_glyph(0, 10, 10, false)]);
811 assert_eq!(font.glyph_outline(0), Err(OutlineError::BudgetExceeded));
812 }
813
814 #[test]
815 fn mutually_recursive_composites_trip_the_depth_budget() {
816 let font = font_with_glyphs(&[
817 composite_glyph(1, 0, 0, false),
818 composite_glyph(0, 0, 0, false),
819 ]);
820 assert_eq!(font.glyph_outline(0), Err(OutlineError::BudgetExceeded));
821 assert_eq!(font.glyph_outline(1), Err(OutlineError::BudgetExceeded));
822 }
823
824 #[test]
825 fn anchor_args_out_of_range_are_malformed() {
826 let mut g = Vec::new();
829 push_i16(&mut g, -1);
830 for _ in 0..4 {
831 push_i16(&mut g, 0);
832 }
833 push16(&mut g, ARG_1_AND_2_ARE_WORDS); push16(&mut g, 0); push_i16(&mut g, 999);
836 push_i16(&mut g, 999);
837 let font = font_with_glyphs(&[triangle_glyph(), g]);
838 assert_eq!(font.glyph_outline(1), Err(OutlineError::Malformed));
839 }
840
841 #[test]
842 fn truncated_simple_glyph_is_malformed() {
843 let full = triangle_glyph();
844 for cut in 2..full.len() {
847 let font = font_with_glyphs(&[full[..cut].to_vec()]);
848 let r = font.glyph_outline(0);
849 assert!(r.is_err(), "prefix of {cut} bytes must not decode");
850 }
851 }
852
853 #[test]
854 fn decreasing_end_pts_are_malformed() {
855 let mut g = Vec::new();
856 push_i16(&mut g, 2); for _ in 0..4 {
858 push_i16(&mut g, 0);
859 }
860 push16(&mut g, 5); push16(&mut g, 2); push16(&mut g, 0);
863 let font = font_with_glyphs(&[g]);
864 assert_eq!(font.glyph_outline(0), Err(OutlineError::Malformed));
865 }
866
867 #[test]
868 fn flag_repeat_overflow_is_malformed() {
869 let mut g = Vec::new();
870 push_i16(&mut g, 1);
871 for _ in 0..4 {
872 push_i16(&mut g, 0);
873 }
874 push16(&mut g, 2); push16(&mut g, 0); g.extend_from_slice(&[0x09, 0xFF]); let font = font_with_glyphs(&[g]);
878 assert_eq!(font.glyph_outline(0), Err(OutlineError::Malformed));
879 }
880
881 #[test]
882 fn overlong_component_chain_trips_the_component_budget() {
883 let mut g = Vec::new();
886 push_i16(&mut g, -1);
887 for _ in 0..4 {
888 push_i16(&mut g, 0);
889 }
890 for i in 0..(MAX_COMPONENTS + 8) {
891 let last = i == MAX_COMPONENTS + 7;
892 let mut flags = ARG_1_AND_2_ARE_WORDS | ARGS_ARE_XY_VALUES;
893 if !last {
894 flags |= MORE_COMPONENTS;
895 }
896 push16(&mut g, flags);
897 push16(&mut g, 0);
898 push_i16(&mut g, 0);
899 push_i16(&mut g, 0);
900 }
901 let font = font_with_glyphs(&[triangle_glyph(), g]);
902 assert_eq!(font.glyph_outline(1), Err(OutlineError::BudgetExceeded));
903 }
904
905 #[test]
906 fn all_off_curve_contour_closes_with_quads() {
907 let mut g = Vec::new();
910 push_i16(&mut g, 1);
911 for _ in 0..4 {
912 push_i16(&mut g, 0);
913 }
914 push16(&mut g, 3); push16(&mut g, 0);
916 g.extend_from_slice(&[0x00, 0x00, 0x00, 0x00]); for dx in [0i16, 100, 100, -100] {
920 push_i16(&mut g, dx);
921 }
922 for dy in [100i16, 100, -100, -100] {
923 push_i16(&mut g, dy);
924 }
925 let font = font_with_glyphs(&[g]);
926 let o = font
927 .glyph_outline(0)
928 .expect("all-off-curve contour decodes");
929 assert_eq!(o.contours.len(), 1);
930 let c = &o.contours[0];
931 assert_eq!(c.segments.len(), 4);
932 assert!(
933 c.segments.iter().all(|s| matches!(s, Segment::Quad { .. })),
934 "an all-off-curve contour is pure quads"
935 );
936 assert_eq!(c.segments.last().unwrap().to(), c.start);
937 }
938}