AF.5: bound hostile Affinity files; fuzz the new import paths

- One budget for the whole read (layers, pixels, characters, table
  cells), shared with embedded documents and charged also when their
  reading fails; the first reading is dropped before the second.
- Tables: at most 65 536 cells; lines and cell formats that don't
  match the grid are left out instead of shifted.
- Paragraph formats matched to runs by binary search; font names
  longer than 256 bytes are dropped.
- Overlays: one work budget per import; encoded originals decoded
  within their layer's size; table cell outlines recoloured too.
- Engine: master page copies capped, each page's master found in
  linear time, line gaps looked up once per font, the working CMYK
  table built once, objects past MAX_COORD left out.
- Embedded content stays with its node; pixel masks under artistic
  text are read whole and include stroke reach; gradient overlays on
  hidden layers are kept.
- Render: contexts and raster effect images stay within
  MAX_RASTER_SIDE per side (vello overflowed near u16::MAX; found by
  the new fuzz target).
- Tests: affinity tests/import_limits.rs, feature samples in
  import_fuzz.rs, shared builders in engine/tests/support/.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
dwetscher
2026-10-22 18:24:57 +07:00
committed by bflatastic
co-authored by Claude Opus 5.5
parent fd50bd6f84
commit f73a3df974
16 changed files with 1330 additions and 524 deletions
+4 -2
View File
@@ -192,8 +192,10 @@ pub(crate) fn effect_image(out: &mut Document, whole: &Node, knockout: Option<&N
if !area.is_finite() {
return None;
}
// Whole pixels, and no larger than 64 Mpx.
let scale = scale.min((64.0e6 / area.max(1.0)).sqrt());
// Whole pixels, no larger than 64 Mpx, and no side longer than the renderer can address
// (a long thin object stays within the area but not within a side).
let side = f64::from(vectorcraft_render::MAX_RASTER_SIDE) - 2.0;
let scale = scale.min((64.0e6 / area.max(1.0)).sqrt()).min(side / b.width().max(b.height()).max(1.0));
if !(scale.is_finite() && scale > 0.0) {
return None;
}