Merge pull request #1512

Bound EMF+ region paths to declared spans
This commit is contained in:
Brandon Thomas
2026-10-30 15:42:30 -04:00
2 changed files with 47 additions and 6 deletions
+19 -6
View File
@@ -50,6 +50,8 @@ const MAX_CHARS: usize = 32_768;
const MAX_OBJECT_BYTES: usize = 64 * 1024 * 1024;
/// Nesting depth of region nodes.
const MAX_REGION_DEPTH: usize = 32;
/// Region nodes and path points parsed together, including nodes in sibling branches.
const MAX_REGION_WORK: usize = MAX_POINTS;
const TA_RIGHT: u32 = 2;
const TA_CENTER: u32 = 6;
@@ -1106,12 +1108,14 @@ fn path(r: Bytes) -> Option<Vec<Op>> {
/// A region object as a convex area in world units (non-convex parts become their bounding
/// rectangle).
fn region(r: Bytes) -> Option<Obj> {
region_node(r, 8, 0).map(|(a, _)| Obj::Region(a))
let mut work = 0;
region_node(r, 8, 0, &mut work).map(|(a, _)| Obj::Region(a))
}
/// One region node at `off`: its area and its length in bytes.
fn region_node(r: Bytes, off: usize, depth: usize) -> Option<(Area, usize)> {
if depth > MAX_REGION_DEPTH {
fn region_node(r: Bytes, off: usize, depth: usize, work: &mut usize) -> Option<(Area, usize)> {
*work = work.checked_add(1)?;
if depth > MAX_REGION_DEPTH || *work > MAX_REGION_WORK {
return None;
}
match r.u32(off)? {
@@ -1121,14 +1125,23 @@ fn region_node(r: Bytes, off: usize, depth: usize) -> Option<(Area, usize)> {
}
0x1000_0001 => {
let size = r.u32(off + 4)? as usize;
let area = path(r.tail(off + 8)?).map(|ops| area_of_ops(&ops)).unwrap_or_default();
if size < 12 {
return None;
}
let data = Bytes(r.slice(off + 8, size)?);
let points = (data.u32(4)? as usize).min(MAX_POINTS);
*work = work.checked_add(points)?;
if *work > MAX_REGION_WORK {
return None;
}
let area = area_of_ops(&path(data)?);
Some((Some(area), 8usize.checked_add(size)?))
}
0x1000_0002 => Some((Some(Vec::new()), 4)),
0x1000_0003 => Some((None, 4)),
mode @ 0..=5 => {
let (a, la) = region_node(r, off + 4, depth + 1)?;
let (b, lb) = region_node(r, off.checked_add(4 + la)?, depth + 1)?;
let (a, la) = region_node(r, off + 4, depth + 1, work)?;
let (b, lb) = region_node(r, off.checked_add(4 + la)?, depth + 1, work)?;
let out = match mode {
0 => b,
1 => area_intersect(a, b),
+28
View File
@@ -1875,6 +1875,34 @@ fn emfplus_region_clip_intersects_rectangles() {
assert_eq!((p.width, p.height), (20.0, 20.0));
}
#[test]
fn emfplus_region_path_cannot_read_into_its_sibling() {
let rect = |r: [f32; 4]| cat(vec![h32(0x1000_0000), f32s(&r)]);
// The path declares only its 12-byte header, but claims three points. The following
// rectangle must not supply those points or be parsed twice as the next node.
let path = cat(vec![h32(0x1000_0001), h32(12), h32(0), h32(3), h32(0)]);
let region = cat(vec![h32(0xDBC0_1002), h32(2), h32(1), path, rect([10.0, 10.0, 20.0, 20.0])]);
let fill = prec(0x400A, 0x8000, &cat(vec![h32(0xFF00_0000), h32(1), f32s(&[0.0, 0.0, 100.0, 100.0])]));
let p = plus_pic(vec![prec(0x4008, 0x0400 | 6, &region), prec(0x4034, 6, &[]), fill]);
assert_eq!(p.items.len(), 1);
assert_eq!((p.width, p.height), (100.0, 100.0));
}
#[test]
fn emfplus_region_path_keeps_its_shape() {
let mut path_data = cat(vec![h32(0), h32(4), h32(0x4000)]);
for (x, y) in [(10i16, 10i16), (30, 10), (30, 30), (10, 30)] {
path_data.extend(x.to_le_bytes());
path_data.extend(y.to_le_bytes());
}
path_data.extend([0, 1, 1, 0x81]);
let path_node = cat(vec![h32(0x1000_0001), h32(path_data.len() as u32), path_data]);
let region = cat(vec![h32(0xDBC0_1002), h32(1), path_node]);
let fill = prec(0x400A, 0x8000, &cat(vec![h32(0xFF00_0000), h32(1), f32s(&[0.0, 0.0, 100.0, 100.0])]));
let p = plus_pic(vec![prec(0x4008, 0x0400 | 6, &region), prec(0x4034, 6, &[]), fill]);
assert_eq!((p.width, p.height), (20.0, 20.0));
}
#[test]
fn emfplus_pie_and_arc() {
// A quarter pie from 0 to 90 degrees in a 0..100 circle: centre, then (100, 50) round to (50, 100).