mirror of
https://github.com/storytold/vectorcraft.git
synced 2026-10-24 09:46:59 -06:00
Document Raster Effects Settings: a file's resolution outside 1 to 2400 ppi opens in range
document.rasterEffectsSettings accepts a resolution from 1 to 2,400 ppi, but a file's raster_effects_ppi opened as it was saved. With 0, -5 or 5000 in the file, object.rasterize without a ppi param failed with "ppi must be between 1 and 2400", and document.rasterEffectsSettings and document.info reported the stored value. With 0 or -5, EPS and PDF export wrote a freeform gradient as a 1 x 1 px image, and EMF export wrote every gradient as one. A saved New Document preset in the preferences kept such a value too. A file's or a saved preset's resolution outside 1 to 2,400 ppi is now read as the nearer end of that range. Documents with a resolution in range open as before. Docs: docs/mcp.md (resolution, rasterEffectsPpi).
This commit is contained in:
@@ -103,6 +103,11 @@ impl PreviewMode {
|
||||
}
|
||||
}
|
||||
|
||||
/// A saved preset's raster effects resolution, within 1 to 2400 ppi (72 when it isn't finite).
|
||||
fn de_ppi<'de, D: serde::Deserializer<'de>>(d: D) -> std::result::Result<f64, D::Error> {
|
||||
Ok(Some(f64::deserialize(d)?).filter(|v| v.is_finite()).unwrap_or(72.0).clamp(1.0, 2400.0))
|
||||
}
|
||||
|
||||
/// Everything New Document sets: a preset (built-in, saved or recent) or `file.new`'s params.
|
||||
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
|
||||
#[serde(default, rename_all = "camelCase")]
|
||||
@@ -125,6 +130,8 @@ pub struct DocSettings {
|
||||
pub bleed: [f64; 4],
|
||||
pub background: Background,
|
||||
pub color_mode: ColorMode,
|
||||
/// 1 to 2400 ppi (a saved preset's value outside that range is read as the nearer end).
|
||||
#[serde(deserialize_with = "de_ppi")]
|
||||
pub raster_effects_ppi: f64,
|
||||
pub preview_mode: PreviewMode,
|
||||
}
|
||||
|
||||
@@ -225,6 +225,17 @@ fn saved_presets_live_in_the_preferences() {
|
||||
assert!(s.execute("file.newPresets.save", &json!({"name": " "})).is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_saved_preset_with_the_resolution_out_of_range_reads_in_range() {
|
||||
let mut s = Session::new();
|
||||
let saved = json!({"newDocPresets": [{"name": "Zero", "rasterEffectsPpi": 0}, {"name": "Huge", "rasterEffectsPpi": 5000}]});
|
||||
s.apply_prefs(serde_json::from_value(saved).unwrap());
|
||||
let ppi: Vec<Option<f64>> = presets(&mut s, "Saved").iter().map(|p| p["rasterEffectsPpi"].as_f64()).collect();
|
||||
assert_eq!(ppi, [Some(1.0), Some(2400.0)]);
|
||||
s.execute("file.new", &json!({"preset": "Zero"})).unwrap();
|
||||
assert_eq!(s.doc().unwrap().doc.raster_effects_ppi, 1.0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn rearrange_keeps_its_grid() {
|
||||
let mut s = new_doc(json!({"width": 100, "height": 50, "artboards": 3}));
|
||||
|
||||
@@ -213,3 +213,48 @@ fn raster_settings_round_trip_and_old_files_load() {
|
||||
let back = vectorcraft_format::load(&bytes).unwrap();
|
||||
assert_eq!((back.raster_effects_ppi, back.raster_effects), (72.0, RasterEffectsSettings::default()));
|
||||
}
|
||||
|
||||
/// Save the active document with the raster effects resolution `ppi` and open the file.
|
||||
fn reopen_with_ppi(s: &mut Session, ppi: f64) {
|
||||
let mut d = (*s.doc().unwrap().doc).clone();
|
||||
d.raster_effects_ppi = ppi;
|
||||
let data = vectorcraft_format::base64_encode(&vectorcraft_format::save_file(&d));
|
||||
s.execute("document.open", &json!({"name": "ppi.vectorcraft", "dataBase64": data})).unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_file_with_the_resolution_out_of_range_opens_with_it_in_range() {
|
||||
for (saved, opened, pixels) in [(0.0, 1.0, 1), (-5.0, 1.0, 1), (5000.0, 2400.0, 334), (f64::MAX, 2400.0, 334)] {
|
||||
let mut s = session();
|
||||
let r = id(&s.execute("shape.rectangle", &json!({"x": 50, "y": 50, "width": 10, "height": 10})).unwrap());
|
||||
s.execute("paint.setStroke", &json!({"none": true, "ids": [r.0]})).unwrap();
|
||||
reopen_with_ppi(&mut s, saved);
|
||||
assert_eq!(s.doc().unwrap().doc.raster_effects_ppi, opened, "saved as {saved}");
|
||||
assert_eq!(s.execute(C, &json!({})).unwrap()["resolution"], opened);
|
||||
assert_eq!(s.execute("document.info", &json!({})).unwrap()["document"]["rasterEffectsPpi"], opened);
|
||||
// Rasterize without a ppi param renders the 10 pt square at it.
|
||||
s.execute("select.set", &json!({"ids": [r.0]})).unwrap();
|
||||
let out = s.execute("object.rasterize", &json!({})).unwrap();
|
||||
assert_eq!((out["width"].as_u64(), out["height"].as_u64()), (Some(pixels), Some(pixels)), "saved as {saved}");
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_gradient_exports_at_the_resolution_the_file_opens_with() {
|
||||
for saved in [0.0, -5.0] {
|
||||
let mut s = session();
|
||||
let r = id(&s.execute("shape.rectangle", &json!({"x": 0, "y": 0, "width": 720, "height": 720})).unwrap());
|
||||
s.execute("paint.setFill", &json!({"gradient": {"kind": "linear"}, "ids": [r.0]})).unwrap();
|
||||
reopen_with_ppi(&mut s, saved);
|
||||
// EMF writes the gradient as an image at the raster effects resolution: 720 pt at 1 ppi.
|
||||
let region = vectorcraft_geom::Rect::new(0.0, 0.0, 720.0, 720.0);
|
||||
let out = vectorcraft_metafile::export(&s.doc().unwrap().doc, region, vectorcraft_metafile::Kind::Emf).unwrap();
|
||||
let mut sizes = vec![];
|
||||
vectorcraft_metafile::import(&out.bytes).unwrap().document.walk(|n| {
|
||||
if let NodeKind::Image(im) = &n.kind {
|
||||
sizes.push((im.width, im.height));
|
||||
}
|
||||
});
|
||||
assert_eq!(sizes, [(10, 10)], "saved as {saved}");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -555,6 +555,8 @@ pub fn load_file(bytes: &[u8]) -> Result<NativeFile, FormatError> {
|
||||
doc.raster_effects.add_around = doc.raster_effects.add_around.clamp(0.0, vectorcraft_doc::rastersettings::MAX_ADD_AROUND);
|
||||
let mut depth_notes = vec![];
|
||||
doc.limit_depth_noting(&mut depth_notes);
|
||||
// Untrusted: the raster effects resolution, within 1 to 2400 ppi (72 when it isn't finite).
|
||||
doc.raster_effects_ppi = Some(doc.raster_effects_ppi).filter(|v| v.is_finite()).unwrap_or(72.0).clamp(1.0, 2400.0);
|
||||
let profiles = f.profiles.into_iter().filter_map(|(name, icc)| Some((name, bytes_of(&icc)?))).collect();
|
||||
Ok(NativeFile { doc, depth_notes, info: FileInfo { version: f.version, legacy: f.format == LEGACY_EXTENSION }, profiles })
|
||||
}
|
||||
|
||||
@@ -225,7 +225,7 @@ fn approx_equal_documents_compare_equal() {
|
||||
fn bug_f64_bit_exact_roundtrip() {
|
||||
check_native_roundtrip_exact(&rich_doc()).unwrap();
|
||||
let mut d = Document::new(126.86291501015239, 214.69463130731182);
|
||||
d.raster_effects_ppi = 0.1 + 0.2;
|
||||
d.raster_effects_ppi = (0.1 + 0.2) * 1000.0;
|
||||
check_native_roundtrip_exact(&d).unwrap();
|
||||
}
|
||||
|
||||
|
||||
+4
-2
@@ -2220,7 +2220,8 @@ art). Pass any of these keys to change them in one undo step; `document.setUnits
|
||||
|
||||
`file.new` takes everything New Document sets: `preset` (a name from `file.newPresets`), `name`, `width`/`height`
|
||||
(points, or lengths such as `"210 mm"`), `units`, `orientation`, `artboards` with `artboardLayout {layout, columns,
|
||||
spacing, rightToLeft}`, `bleed`, `backgroundContents`, `colorMode`, `rasterEffectsPpi` and `previewMode`.
|
||||
spacing, rightToLeft}`, `bleed`, `backgroundContents`, `colorMode`, `rasterEffectsPpi` (1 to 2,400; a saved preset's
|
||||
value outside that range is read as the nearer end) and `previewMode`.
|
||||
`file.newPresets {category?}` lists the categories and presets (Recent: the last sizes used; Saved: the user's presets,
|
||||
kept in the preferences by `file.newPresets.save` and removed by `file.newPresets.delete`). A listed preset can be
|
||||
passed straight back to `file.new`. Print presets and sizes without `units` start in `unitsGeneral`; screen presets
|
||||
@@ -2342,7 +2343,8 @@ written unless the whole batch succeeds puts the saves and exports last, or runs
|
||||
## Document Raster Effects Settings
|
||||
|
||||
`document.rasterEffectsSettings` holds how raster effects (shadows, glows, blurs, feathers) become images when PDF
|
||||
export or Expand Appearance renders them: `resolution` (ppi, also New Document's `rasterEffectsPpi`), `colorModel`
|
||||
export or Expand Appearance renders them: `resolution` (1 to 2,400 ppi, also New Document's `rasterEffectsPpi`; a
|
||||
file's value outside that range opens as the nearer end), `colorModel`
|
||||
(the document's `rgb`/`cmyk`, `grayscale` or `bitmap`), `background` (`white` makes the images opaque), `antiAlias`
|
||||
(off: hard edges), `clippingMask` (the white stays under the art only), `addAround` (0 to 1,000 points of room
|
||||
around the art; a file's value outside that range opens as the nearer end) and `preserveSpotColors` (stored). They are
|
||||
|
||||
Reference in New Issue
Block a user