Files
vectorcraft/crates/engine/src/tests_printpreview.rs
T
dotCooCoo a285efda46 Print: Print, Print Preview and Print Setup take a print preset by name
file.print, print.preview and print.setup took print settings only as
settings, so an agent had to read a preset with print.presets.list and
copy its settings into the call. They now take preset, the name of
[Default] or of a saved print preset in any case. The preset's settings
are used instead of the document's, settings applies over them, and
print.setup saves them with the document as one undo step. An unknown
name is a parameter error that lists the presets. The app's file.print
with params passes preset on to the engine.

Docs: docs/mcp.md, docs/target-app-parity.md.
2026-10-17 13:49:49 +07:00

383 lines
23 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
//! `print.preview`'s pages say where the art lands (what the Print dialog's preview draws): the
//! transform from the document onto the page, the region each page prints and its trim box.
use serde_json::{Value, json};
use super::*;
/// `print.preview {settings}`'s first page.
fn first_sheet(s: &mut Session, settings: Value) -> Value {
s.execute("print.preview", &json!({ "settings": settings })).unwrap()["sheets"][0].clone()
}
/// `[a, b, c, d, e, f]` applied to (x, y).
fn apply(t: &Value, (x, y): (f64, f64)) -> (f64, f64) {
let n = |i: usize| t[i].as_f64().unwrap();
(n(0) * x + n(2) * y + n(4), n(1) * x + n(3) * y + n(5))
}
fn close(a: (f64, f64), b: (f64, f64)) -> bool {
(a.0 - b.0).abs() < 1e-6 && (a.1 - b.1).abs() < 1e-6
}
#[test]
fn pages_place_the_artboard_on_the_trim_box() {
let mut s = Session::new();
s.execute("file.new", &json!({"width": 300, "height": 200})).unwrap();
s.execute("document.setup", &json!({"bleed": 9})).unwrap();
// Centred on Letter, turned landscape for the wide artboard.
let p = first_sheet(&mut s, json!({}));
assert_eq!((p["width"].clone(), p["height"].clone()), (json!(792.0), json!(612.0)));
assert_eq!(p["trim"], json!([246.0, 206.0, 546.0, 406.0]));
assert!(close(apply(&p["transform"], (0.0, 0.0)), (246.0, 206.0)) && close(apply(&p["transform"], (300.0, 200.0)), (546.0, 406.0)));
assert_eq!(p["area"], json!([-9.0, -9.0, 309.0, 209.0]), "the artboard with the document's bleed");
// Flipped, the artboard's top-left corner lands on the trim box's bottom-right one.
let p = first_sheet(
&mut s,
json!({"autoRotate": false, "orientation": "landscapeFlipped", "scaling": "custom", "scale": {"width": 50, "height": 50}}),
);
let t = &p["trim"];
assert!(close(apply(&p["transform"], (0.0, 0.0)), (t[2].as_f64().unwrap(), t[3].as_f64().unwrap())), "{p}");
assert_eq!(p["scale"], json!([50.0, 50.0]));
// A tile prints its part of the artboard.
let v = s.execute("print.preview", &json!({"settings": {"scaling": "tileFull", "scale": {"width": 400, "height": 400}}})).unwrap();
let tiles = v["tiles"][0]["tiles"].as_array().unwrap();
for (sheet, tile) in v["sheets"].as_array().unwrap().iter().zip(tiles) {
let (a, t) = (&sheet["area"], tile);
for i in 0..4 {
let inside =
if i < 2 { a[i].as_f64().unwrap() >= t[i].as_f64().unwrap() - 1e-6 } else { a[i].as_f64().unwrap() <= t[i].as_f64().unwrap() + 1e-6 };
assert!(inside, "{a} within {t}");
}
}
}
#[test]
fn a_print_preset_lays_out_its_settings_with_settings_over_them() {
let mut s = Session::new();
s.execute("file.new", &json!({"width": 300, "height": 200, "artboards": 4})).unwrap();
s.execute("print.setup", &json!({"settings": {"media": "a4"}})).unwrap();
s.execute("print.presets.save", &json!({"name": "Sheet", "settings": {"artboards": "range", "range": "4"}})).unwrap();
let v = s.execute("print.preview", &json!({"preset": "Sheet", "settings": {"artboards": "range", "range": "3"}})).unwrap();
assert_eq!((v["pages"].clone(), v["sheets"][0]["artboard"].clone()), (json!(1), json!(2)), "{v}");
assert_eq!(v["settings"]["media"], "letter", "the preset's paper, not the document's");
let v = s.execute("print.preview", &json!({"preset": "Sheet"})).unwrap();
assert_eq!((v["pages"].clone(), v["sheets"][0]["artboard"].clone()), (json!(1), json!(3)));
assert_eq!(s.doc().unwrap().doc.print_setup.as_ref().unwrap()["media"], "a4", "the preview keeps nothing");
}
/// Point type at (`x`, `y`) in `family` → its id.
fn type_in(s: &mut Session, x: f64, y: f64, family: &str) -> u64 {
s.execute("text.create", &json!({"x": x, "y": y, "text": "Ab", "font": family, "style": "Regular"})).unwrap()["id"].as_u64().unwrap()
}
/// The warnings in `v` about type in the fallback font.
fn fallback_warnings(v: &Value) -> Vec<&str> {
v["warnings"].as_array().unwrap().iter().filter_map(Value::as_str).filter(|w| w.contains("fallback font")).collect()
}
/// `print.preview {settings}`'s warnings about type in the fallback font.
fn preview_fallback(s: &mut Session, settings: Value) -> Vec<String> {
let v = s.execute("print.preview", &json!({ "settings": settings })).unwrap();
fallback_warnings(&v).into_iter().map(str::to_string).collect()
}
#[test]
fn type_that_prints_in_the_fallback_font_is_named() {
let mut s = Session::new();
s.execute("file.new", &json!({"width": 300, "height": 200, "artboards": 2})).unwrap();
type_in(&mut s, 10.0, 50.0, vectorcraft_text::FALLBACK_FAMILY);
assert_eq!(preview_fallback(&mut s, json!({})), Vec::<String>::new(), "an installed family");
// The warning PDF export gives.
let id = type_in(&mut s, 10.0, 100.0, "Nonexistent Sans Probe");
let export = s.execute("document.exportPdf", &json!({})).unwrap();
let warned = preview_fallback(&mut s, json!({}));
assert_eq!(warned, fallback_warnings(&export));
assert!(warned.first().is_some_and(|w| w.contains("Nonexistent Sans Probe")), "{warned:?}");
// A hidden object doesn't print.
s.execute("object.setProps", &json!({"ids": [id], "visible": false})).unwrap();
assert_eq!(preview_fallback(&mut s, json!({})), Vec::<String>::new());
}
#[test]
fn fallback_fonts_count_only_layers_and_artboards_that_print() {
let mut s = Session::new();
s.execute("file.new", &json!({"width": 300, "height": 200, "artboards": 2})).unwrap();
// On the second artboard, the type prints only when that artboard does.
let r = s.doc().unwrap().doc.artboards[1].rect;
type_in(&mut s, r.x0 + 10.0, r.y0 + 50.0, "Nonexistent Sans Probe");
assert_eq!(preview_fallback(&mut s, json!({"artboards": "range", "range": "1"})), Vec::<String>::new());
assert_eq!(preview_fallback(&mut s, json!({"artboards": "range", "range": "2"})).len(), 1);
assert_eq!(preview_fallback(&mut s, json!({})).len(), 1);
// On a hidden layer, the type prints only with Print Layers: All.
let layer = s.execute("layer.new", &json!({})).unwrap()["id"].as_u64().unwrap();
type_in(&mut s, 10.0, 50.0, "Nonexistent Sans Probe");
s.execute("layer.setProps", &json!({"id": layer, "visible": false})).unwrap();
for (layers, warns) in [("visiblePrintable", false), ("visible", false), ("all", true)] {
let warned = preview_fallback(&mut s, json!({"artboards": "range", "range": "1", "printLayers": layers}));
assert_eq!(!warned.is_empty(), warns, "{layers}");
}
// Template layers never print.
s.execute("layer.setProps", &json!({"id": layer, "template": true})).unwrap();
assert_eq!(preview_fallback(&mut s, json!({"artboards": "range", "range": "1", "printLayers": "all"})), Vec::<String>::new());
}
#[test]
fn type_that_does_not_fit_its_frame_counts_only_where_it_shows() {
let mut s = Session::new();
s.execute("file.new", &json!({"width": 300, "height": 200})).unwrap();
let area = json!({"width": 100, "height": 20});
let p = json!({"x": 10, "y": 10, "text": "Ab\nCd\nEf\nGh", "font": vectorcraft_text::FALLBACK_FAMILY, "size": 12, "area": area});
let id = s.execute("text.create", &p).unwrap()["id"].as_u64().unwrap();
s.execute("text.setStyle", &json!({"ids": [id], "font": "Nonexistent Sans Probe", "start": 9, "end": 11})).unwrap();
assert_eq!(preview_fallback(&mut s, json!({})), Vec::<String>::new(), "only the first line shows");
s.execute("text.areaOptions", &json!({"ids": [id], "height": 150})).unwrap();
assert_eq!(preview_fallback(&mut s, json!({})).len(), 1);
// Nor does the pattern of characters that don't show.
let tile = type_in(&mut s, 0.0, 15.0, "Pattern Probe Sans");
s.execute("select.set", &json!({"ids": [tile]})).unwrap();
s.execute("object.pattern.make", &json!({"name": "Words", "width": 40, "height": 40})).unwrap();
s.execute("object.pattern.done", &json!({})).unwrap();
s.execute("select.set", &json!({"ids": [tile]})).unwrap();
s.execute("edit.clear", &json!({})).unwrap();
s.execute("text.setStyle", &json!({"ids": [id], "font": vectorcraft_text::FALLBACK_FAMILY})).unwrap();
s.execute("text.setStyle", &json!({"ids": [id], "tracking": 10, "start": 9, "end": 11})).unwrap();
s.edit("pattern", |d, _| {
if let vectorcraft_doc::NodeKind::Text(t) = &mut d.node_mut(vectorcraft_doc::NodeId(id)).unwrap().kind {
t.runs.last_mut().unwrap().style.fill = vectorcraft_color::Paint::Pattern { pattern: "Words".into(), xf: Default::default() };
}
Ok(())
})
.unwrap();
assert!(preview_fallback(&mut s, json!({}))[0].contains("Pattern Probe Sans"));
s.execute("text.areaOptions", &json!({"ids": [id], "height": 20})).unwrap();
assert_eq!(preview_fallback(&mut s, json!({})), Vec::<String>::new());
}
/// Area type 100 × 20 pt at (`x`, `y`) holding four lines, of which the first shows → its id.
fn overset(s: &mut Session, x: f64, y: f64) -> u64 {
let v = s.execute("text.create", &json!({"x": x, "y": y, "text": "Ab\nCd\nEf\nGh", "size": 12, "area": {"width": 100, "height": 20}})).unwrap();
assert_eq!(v["overflow"], json!([v["id"]]), "{v}");
v["id"].as_u64().unwrap()
}
/// `print.preview {settings}`'s warnings about type that doesn't fit its frame.
fn preview_overflow(s: &mut Session, settings: Value) -> Vec<String> {
let v = s.execute("print.preview", &json!({ "settings": settings })).unwrap();
let over = |w: &&str| w.contains("fit its frame") || w.contains("fit their frames");
v["warnings"].as_array().unwrap().iter().filter_map(Value::as_str).filter(over).map(str::to_string).collect()
}
#[test]
fn type_that_does_not_fit_its_frame_is_named() {
let mut s = Session::new();
s.execute("file.new", &json!({"width": 300, "height": 200})).unwrap();
let id = overset(&mut s, 10.0, 10.0);
let one = format!("1 text object doesn't fit its frame: the text past the end of the frame doesn't print (id {id})");
assert_eq!(preview_overflow(&mut s, json!({})), [one]);
// A frame that holds its text, or a hidden object, leaves nothing out.
s.execute("text.areaOptions", &json!({"ids": [id], "height": 150})).unwrap();
assert_eq!(preview_overflow(&mut s, json!({})), Vec::<String>::new());
s.execute("text.areaOptions", &json!({"ids": [id], "height": 20})).unwrap();
s.execute("object.setProps", &json!({"ids": [id], "visible": false})).unwrap();
assert_eq!(preview_overflow(&mut s, json!({})), Vec::<String>::new());
s.execute("object.setProps", &json!({"ids": [id], "visible": true})).unwrap();
// Type on a path that runs past the path's end.
let path = s.execute("path.create", &json!({"d": "M10 150 L60 150"})).unwrap()["id"].clone();
let on = s.execute("text.createInPath", &json!({"path": path, "mode": "onPath", "text": "Longer than its path"})).unwrap();
assert_eq!(on["overflow"], json!([on["id"]]), "{on}");
let both = format!("2 text objects don't fit their frames: the text past the end of each frame doesn't print (ids {id}, {})", on["id"]);
assert_eq!(preview_overflow(&mut s, json!({})), [both]);
// Past 20, the first 20 ids and how many more.
let mut ids = vec![id, on["id"].as_u64().unwrap()];
ids.extend((0..20).map(|i| overset(&mut s, 150.0, f64::from(i) * 5.0)));
let named: Vec<String> = ids[..20].iter().map(u64::to_string).collect();
let many =
format!("22 text objects don't fit their frames: the text past the end of each frame doesn't print (ids {} and 2 more)", named.join(", "));
assert_eq!(preview_overflow(&mut s, json!({})), [many]);
}
#[test]
fn type_that_does_not_fit_counts_only_layers_and_artboards_that_print() {
let mut s = Session::new();
s.execute("file.new", &json!({"width": 300, "height": 200, "artboards": 2})).unwrap();
let r = s.doc().unwrap().doc.artboards[1].rect;
overset(&mut s, r.x0 + 10.0, r.y0 + 10.0);
assert_eq!(preview_overflow(&mut s, json!({"artboards": "range", "range": "1"})), Vec::<String>::new());
assert_eq!(preview_overflow(&mut s, json!({"artboards": "range", "range": "2"})).len(), 1);
// On a hidden layer, the type prints only with Print Layers: All.
let layer = s.execute("layer.new", &json!({})).unwrap()["id"].as_u64().unwrap();
overset(&mut s, 10.0, 10.0);
s.execute("layer.setProps", &json!({"id": layer, "visible": false})).unwrap();
for (layers, warns) in [("visiblePrintable", false), ("visible", false), ("all", true)] {
let warned = preview_overflow(&mut s, json!({"artboards": "range", "range": "1", "printLayers": layers}));
assert_eq!(!warned.is_empty(), warns, "{layers}");
}
// A layer that doesn't print.
s.execute("layer.setProps", &json!({"id": layer, "visible": true, "printable": false})).unwrap();
assert_eq!(preview_overflow(&mut s, json!({"artboards": "range", "range": "1"})), Vec::<String>::new());
assert_eq!(preview_overflow(&mut s, json!({"artboards": "range", "range": "1", "printLayers": "visible"})).len(), 1);
}
#[test]
fn type_that_does_not_fit_is_named_only_among_the_objects_of_the_document() {
let mut s = Session::new();
s.execute("file.new", &json!({"width": 300, "height": 200})).unwrap();
let id = overset(&mut s, 10.0, 10.0);
assert_eq!(preview_overflow(&mut s, json!({})).len(), 1);
s.execute("symbol.new", &json!({"name": "Label", "ids": [id]})).unwrap();
assert_eq!(preview_overflow(&mut s, json!({})), Vec::<String>::new(), "the art of a symbol");
}
#[test]
fn a_threaded_story_is_named_only_when_its_last_frame_does_not_hold_it() {
let mut s = Session::new();
s.execute("file.new", &json!({"width": 600, "height": 300})).unwrap();
let story = "The quick brown fox jumps over the lazy dog. ".repeat(8);
let a = s.execute("text.create", &json!({"x": 10, "y": 10, "text": story, "area": {"width": 150, "height": 60}})).unwrap();
assert_eq!(a["overflow"], json!([a["id"]]), "{a}");
let b = s.execute("shape.rectangle", &json!({"x": 200, "y": 10, "width": 150, "height": 200})).unwrap()["id"].clone();
s.execute("select.set", &json!({"ids": [a["id"], b]})).unwrap();
s.execute("text.thread.create", &json!({})).unwrap();
assert_eq!(preview_overflow(&mut s, json!({})), Vec::<String>::new(), "the second frame holds the rest");
s.execute("text.editRange", &json!({"id": a["id"], "start": 0, "end": 0, "insert": story.repeat(4)})).unwrap();
let warned = preview_overflow(&mut s, json!({}));
assert!(warned.len() == 1 && warned[0].ends_with(&format!("(id {b})")), "{warned:?}");
}
#[test]
fn type_of_an_artboard_larger_than_the_paper_counts_only_on_the_paper() {
let mut s = Session::new();
s.execute("file.new", &json!({"width": 2000, "height": 200})).unwrap();
type_in(&mut s, 1800.0, 100.0, "Nonexistent Sans Probe");
// At 100% the middle of the artboard prints on Letter paper.
assert_eq!(preview_fallback(&mut s, json!({"scaling": "none"})), Vec::<String>::new());
for scaling in ["fit", "tileFull"] {
assert_eq!(preview_fallback(&mut s, json!({ "scaling": scaling })).len(), 1, "{scaling}");
}
}
#[test]
fn symbol_instances_and_inline_graphics_print_the_type_of_their_symbol() {
let mut s = Session::new();
s.execute("file.new", &json!({"width": 300, "height": 200, "artboards": 2})).unwrap();
let r = s.doc().unwrap().doc.artboards[1].rect;
let id = type_in(&mut s, r.x0 + 10.0, r.y0 + 50.0, "Nonexistent Sans Probe");
s.execute("symbol.new", &json!({"name": "Label", "ids": [id]})).unwrap();
assert_eq!(preview_fallback(&mut s, json!({"artboards": "range", "range": "1"})), Vec::<String>::new());
assert_eq!(preview_fallback(&mut s, json!({"artboards": "range", "range": "2"})).len(), 1);
// An inline graphic in type on the first artboard prints its symbol's art.
let id = type_in(&mut s, 10.0, 50.0, vectorcraft_text::FALLBACK_FAMILY);
s.execute("text.insertInline", &json!({"id": id, "at": 0, "symbol": "Label"})).unwrap();
assert_eq!(preview_fallback(&mut s, json!({"artboards": "range", "range": "1"})).len(), 1);
}
#[test]
fn a_placed_document_prints_its_type() {
let mut src = Session::new();
src.execute("file.new", &json!({"width": 100, "height": 50})).unwrap();
type_in(&mut src, 10.0, 30.0, "Nonexistent Sans Probe");
let path = std::env::temp_dir().join(format!("vc-printfonts-{}.vectorcraft", std::process::id()));
std::fs::write(&path, vectorcraft_format::save_file(&src.doc().unwrap().doc)).unwrap();
let mut s = Session::new();
s.execute("file.new", &json!({"width": 300, "height": 200})).unwrap();
s.execute("file.place", &json!({"path": path.to_string_lossy(), "at": [150, 100]})).unwrap();
let _ = std::fs::remove_file(&path);
assert_eq!(preview_fallback(&mut s, json!({})).len(), 1);
}
#[test]
fn type_prints_where_effects_paint_it() {
let mut s = Session::new();
s.execute("file.new", &json!({"width": 300, "height": 200, "artboards": 2})).unwrap();
let r = s.doc().unwrap().doc.artboards[1].rect;
let id = type_in(&mut s, r.x0 + 10.0, r.y0 + 50.0, "Nonexistent Sans Probe");
// Onto the first artboard.
s.execute("effect.apply", &json!({"effect": "distort.transform", "ids": [id], "params": {"moveH": -r.x0, "moveV": -r.y0}})).unwrap();
assert_eq!(preview_fallback(&mut s, json!({"artboards": "range", "range": "1"})).len(), 1);
assert_eq!(preview_fallback(&mut s, json!({"artboards": "range", "range": "2"})), Vec::<String>::new());
// A group's shadow reaching the artboard prints the type above it.
let id = type_in(&mut s, 10.0, -40.0, "Sans Probe Above");
s.execute("select.set", &json!({"ids": [id]})).unwrap();
let group = s.execute("object.group", &json!({})).unwrap()["id"].clone();
assert!(!preview_fallback(&mut s, json!({"artboards": "range", "range": "1"}))[0].contains("Sans Probe Above"));
s.execute("effect.apply", &json!({"effect": "stylize.dropShadow", "ids": [group], "params": {"x": 0, "y": 60, "blur": 0}})).unwrap();
assert!(preview_fallback(&mut s, json!({"artboards": "range", "range": "1"}))[0].contains("Sans Probe Above"));
// A hidden effect leaves each member of a group printing where it lies.
let shape = s.execute("shape.rectangle", &json!({"x": 10, "y": 120, "width": 20, "height": 20})).unwrap()["id"].clone();
let id = type_in(&mut s, r.x0 + 10.0, r.y0 + 150.0, "Sans Probe Hidden");
s.execute("select.set", &json!({"ids": [shape, id]})).unwrap();
let both = s.execute("object.group", &json!({})).unwrap()["id"].clone();
s.execute("effect.apply", &json!({"effect": "stylize.dropShadow", "ids": [both]})).unwrap();
s.execute("effect.setParams", &json!({"index": 0, "visible": false, "ids": [both], "item": null})).unwrap();
assert!(!preview_fallback(&mut s, json!({"artboards": "range", "range": "1"}))[0].contains("Sans Probe Hidden"));
// A group's own fill, drawn from the glyphs of the type in it and moved onto the artboard, prints it too.
let id = type_in(&mut s, 10.0, -80.0, "Sans Probe Fill");
s.execute("select.set", &json!({"ids": [id]})).unwrap();
let group = s.execute("object.group", &json!({})).unwrap()["id"].clone();
assert!(!preview_fallback(&mut s, json!({"artboards": "range", "range": "1"}))[0].contains("Sans Probe Fill"));
s.execute("appearance.addFill", &json!({})).unwrap();
s.execute("effect.apply", &json!({"effect": "distort.transform", "ids": [group], "item": 0, "params": {"moveV": 100}})).unwrap();
assert!(preview_fallback(&mut s, json!({"artboards": "range", "range": "1"}))[0].contains("Sans Probe Fill"));
}
#[test]
fn a_pattern_prints_the_type_of_its_tile() {
let mut s = Session::new();
s.execute("file.new", &json!({"width": 300, "height": 200})).unwrap();
let id = type_in(&mut s, 0.0, 15.0, "Nonexistent Sans Probe");
s.execute("select.set", &json!({"ids": [id]})).unwrap();
s.execute("object.pattern.make", &json!({"name": "Words", "width": 40, "height": 40})).unwrap();
s.execute("object.pattern.done", &json!({})).unwrap();
s.execute("select.set", &json!({"ids": [id]})).unwrap();
s.execute("edit.clear", &json!({})).unwrap();
assert_eq!(preview_fallback(&mut s, json!({})), Vec::<String>::new(), "a pattern no object uses");
let shape = s.execute("shape.rectangle", &json!({"x": 10, "y": 10, "width": 200, "height": 100})).unwrap()["id"].clone();
s.execute("paint.setFill", &json!({"ids": [shape], "swatch": "Words"})).unwrap();
assert_eq!(preview_fallback(&mut s, json!({})).len(), 1);
// A hidden fill paints nothing.
let n = s.doc().unwrap().doc.node(vectorcraft_doc::NodeId(shape.as_u64().unwrap())).unwrap().clone();
let fill = n.appearance.items.iter().position(|i| i.is_fill()).unwrap();
s.execute("appearance.setItem", &json!({"ids": [shape], "index": fill, "visible": false})).unwrap();
assert_eq!(preview_fallback(&mut s, json!({})), Vec::<String>::new());
}
#[test]
fn a_brushed_stroke_prints_the_type_of_its_brush() {
let mut s = Session::new();
s.execute("file.new", &json!({"width": 300, "height": 200})).unwrap();
let id = type_in(&mut s, 0.0, 15.0, "Nonexistent Sans Probe");
s.execute("brush.new", &json!({"type": "scatter", "name": "Words", "ids": [id]})).unwrap();
s.execute("brush.new", &json!({"type": "art", "name": "Bent", "ids": [id]})).unwrap();
s.execute("select.set", &json!({"ids": [id]})).unwrap();
s.execute("edit.clear", &json!({})).unwrap();
let shape = s.execute("shape.rectangle", &json!({"x": 10, "y": 10, "width": 200, "height": 100})).unwrap()["id"].clone();
assert_eq!(preview_fallback(&mut s, json!({})), Vec::<String>::new());
// An art brush bends paths and leaves type out.
s.execute("brush.apply", &json!({"name": "Bent", "ids": [shape]})).unwrap();
assert_eq!(preview_fallback(&mut s, json!({})), Vec::<String>::new());
s.execute("brush.apply", &json!({"name": "Words", "ids": [shape]})).unwrap();
assert_eq!(preview_fallback(&mut s, json!({})).len(), 1);
}
#[test]
fn type_in_an_opacity_mask_prints_and_type_clipped_away_does_not() {
let mut s = Session::new();
s.execute("file.new", &json!({"width": 300, "height": 200})).unwrap();
let shape = s.execute("shape.rectangle", &json!({"x": 10, "y": 10, "width": 200, "height": 100})).unwrap()["id"].clone();
let mask = type_in(&mut s, 20.0, 60.0, "Nonexistent Sans Probe");
s.execute("transparency.makeOpacityMask", &json!({"ids": [shape, mask]})).unwrap();
assert_eq!(preview_fallback(&mut s, json!({})).len(), 1, "the mask's type");
let mut s = Session::new();
s.execute("file.new", &json!({"width": 300, "height": 200})).unwrap();
let id = type_in(&mut s, 10.0, 150.0, "Nonexistent Sans Probe");
assert_eq!(preview_fallback(&mut s, json!({})).len(), 1);
let clip = s.execute("shape.rectangle", &json!({"x": 0, "y": 0, "width": 100, "height": 50})).unwrap()["id"].clone();
s.execute("select.set", &json!({"ids": [id, clip]})).unwrap();
let group = s.execute("object.clippingMask.make", &json!({})).unwrap()["id"].clone();
assert_eq!(preview_fallback(&mut s, json!({})), Vec::<String>::new(), "outside its clipping path");
// Also in a symbol's art.
s.execute("symbol.new", &json!({"name": "Clipped", "ids": [group]})).unwrap();
assert_eq!(preview_fallback(&mut s, json!({})), Vec::<String>::new(), "outside its clipping path in a symbol");
}