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