PDF export: Add Around is clamped when a file opens, and art too large for f64 gets no effect image

document.rasterEffectsSettings accepts addAround from 0 to 1,000 pt,
but a file's raster_effects.addAround opened as it was saved. With
addAround 1e300, the area of a raster effect image overflowed f64,
the image scale became 0 and the image got the transform
[inf, NaN, NaN, inf, x, y]. PDF export left that image out, so the
drop shadow was missing.

A file's addAround now opens clamped to 0 to 1,000 pt. An effect
image whose area or scale is not finite is not made. Export then
keeps the object with its effects, and Expand Appearance expands it
without an image, as when its PNG can't be encoded.

Docs: docs/mcp.md (addAround).
This commit is contained in:
dotCooCoo
2026-10-17 12:50:36 +07:00
committed by bflatastic
parent 0af1741e24
commit 3d2e8bbe82
4 changed files with 70 additions and 3 deletions
+55
View File
@@ -2,6 +2,7 @@
//! raster effect images they make (PDF export, Expand Appearance) and the Rasterize defaults.
use serde_json::{Value, json};
use vectorcraft_doc::rastersettings::MAX_ADD_AROUND;
use vectorcraft_doc::{Background, Document, NodeId, NodeKind, RasterColorModel, RasterEffectsSettings};
use super::*;
@@ -98,6 +99,60 @@ fn add_around_grows_the_effect_image() {
assert_eq!((after.width() - before.width(), after.height() - before.height()), (72, 72));
}
/// The transforms of the images in `doc` flattened for output.
fn image_transforms(doc: &Document) -> Vec<[f64; 6]> {
let mut xfs = vec![];
crate::flatten_raster_effects(doc).unwrap().walk(|n| {
if let NodeKind::Image(im) = &n.kind {
xfs.push(im.xf.as_coeffs());
}
});
xfs
}
#[test]
fn a_file_with_add_around_out_of_range_opens_with_it_in_range() {
let mut s = session();
shadowed(&mut s);
let mut d = (*s.doc().unwrap().doc).clone();
d.raster_effects.add_around = 1e300;
let back = vectorcraft_format::load(&vectorcraft_format::save_file(&d)).unwrap();
assert_eq!(back.raster_effects.add_around, MAX_ADD_AROUND);
let xfs = image_transforms(&back);
assert!(!xfs.is_empty() && xfs.iter().flatten().all(|c| c.is_finite()), "{xfs:?}");
// 1,000 pt on every side at 72 ppi.
assert_eq!(effect_image(&back).width() - effect_image(&s.doc().unwrap().doc).width(), 2000);
d.raster_effects.add_around = -5.0;
assert_eq!(vectorcraft_format::load(&vectorcraft_format::save_file(&d)).unwrap().raster_effects.add_around, 0.0);
}
#[test]
fn art_too_large_for_f64_gets_no_effect_image() {
let mut s = session();
let r = shadowed(&mut s);
// Settings out of range in a document made without opening a file.
let mut d = (*s.doc().unwrap().doc).clone();
d.raster_effects.add_around = 1e300;
assert_eq!(image_transforms(&d), Vec::<[f64; 6]>::new());
let mut d = (*s.doc().unwrap().doc).clone();
let NodeKind::Path { path, live, .. } = &mut d.node_mut(r).unwrap().kind else { panic!("a path") };
(*path, *live) = (vectorcraft_geom::shapes::rectangle(vectorcraft_geom::Rect::new(-1e300, -1e300, 1e300, 1e300)), None);
assert_eq!(image_transforms(&d), Vec::<[f64; 6]>::new());
// Expand Appearance of it by id, with nothing selected, puts no image into the document (the
// rectangle at its own size gets one).
let normal = (*s.doc().unwrap().doc).clone();
let mut expanded_images = |d: Document| {
let st = s.doc_mut().unwrap();
(st.doc, st.selection) = (std::sync::Arc::new(d), Default::default());
s.execute("effect.expandAppearance", &json!({"ids": [r.0]})).unwrap();
let mut images = 0;
s.doc().unwrap().doc.walk(|n| images += usize::from(matches!(n.kind, NodeKind::Image(_))));
images
};
assert_eq!(expanded_images(normal), 1);
assert_eq!(expanded_images(d), 0);
}
#[test]
fn the_color_model_and_anti_alias_reach_the_pixels() {
let mut s = session();