mirror of
https://github.com/storytold/vectorcraft.git
synced 2026-10-30 12:47:13 -06:00
The reference app switches the units of measurement with Ctrl+Alt+Shift+U on Windows and Option+Shift+Command+U on macOS. VectorCraft could set the document's units with document.setUnits or a right-click on a ruler, but no command stepped through them and no key did it. document.switchUnits (Switch Units, default Cmd+Alt+Shift+U) sets the document's units to the next one in the order a right-click on a ruler lists them: Points, Picas, Inches, Millimeters, Centimeters, Pixels, Feet & Inches, Meters, Yards, Feet, and from Feet back to Points. Each press is one undo step, and the reply names the new units. Keyboard Shortcuts lists it under Document. The precise cursors shortcut-editor test used Cmd+Alt+Shift+U as a free key; it now uses Cmd+Alt+Shift+Q. Docs: docs/mcp.md, docs/ui-parity.md, docs/target-app-parity.md.
428 lines
22 KiB
Rust
428 lines
22 KiB
Rust
//! File → Document Setup (`document.setup`): validation, one undo step, round-trips, and the
|
||
//! options other commands read (typographer's quotes, superscript/subscript, SVG text export).
|
||
|
||
use serde_json::{Value, json};
|
||
use vectorcraft_doc::{CharPosition, DocSetup, ExportText, GridSize, NodeId, NodeKind, ScriptMetrics, TextObject, TextStyleDef, Unit};
|
||
use vectorcraft_tools::{PointerEvent, PointerKind, ToolKey};
|
||
|
||
use crate::*;
|
||
|
||
fn session() -> Session {
|
||
let mut s = Session::new();
|
||
s.execute("file.new", &json!({"width": 400, "height": 300})).unwrap();
|
||
s
|
||
}
|
||
|
||
fn setup(s: &Session) -> &DocSetup {
|
||
&s.doc().unwrap().doc.setup
|
||
}
|
||
|
||
fn text(s: &Session, id: NodeId) -> TextObject {
|
||
match &s.doc().unwrap().doc.node(id).unwrap().kind {
|
||
NodeKind::Text(t) => (**t).clone(),
|
||
_ => panic!("not text"),
|
||
}
|
||
}
|
||
|
||
fn create_text(s: &mut Session, t: &str) -> NodeId {
|
||
NodeId(s.execute("text.create", &json!({"x": 10, "y": 50, "text": t, "size": 20})).unwrap()["id"].as_u64().unwrap())
|
||
}
|
||
|
||
/// Click with the Type tool at (x, y) and type `keys` one at a time; returns the new text object.
|
||
fn type_at(s: &mut Session, x: f64, y: f64, keys: &[&str]) -> NodeId {
|
||
let v = ViewInfo::default();
|
||
s.select_tool("type", v).unwrap();
|
||
for k in [PointerKind::Down, PointerKind::Up] {
|
||
s.pointer(&PointerEvent::new(k, x, y), v).unwrap();
|
||
}
|
||
let id = s.doc().unwrap().selection.objects[0];
|
||
for k in keys {
|
||
s.tool_text(k, v).unwrap();
|
||
}
|
||
s.tool_key(ToolKey::Escape, Default::default(), v).unwrap();
|
||
id
|
||
}
|
||
|
||
#[test]
|
||
fn query_reports_the_defaults_and_accepts_them_back() {
|
||
let mut s = session();
|
||
let q = s.execute("document.setup", &json!({})).unwrap();
|
||
assert_eq!(q["units"], "Points");
|
||
assert_eq!(q["bleed"], json!([0.0, 0.0, 0.0, 0.0]));
|
||
assert_eq!(q["gridColors"], json!(["#ffffff", "#cccccc"]));
|
||
assert_eq!(q["gridColorsName"], "Light");
|
||
assert_eq!(q["flattenerPreset"], "Medium Resolution");
|
||
assert_eq!(q["backgroundContents"], "transparent");
|
||
assert_eq!(q["quotes"], json!({"double": "“”", "single": "‘’"}));
|
||
assert_eq!(q["typographersQuotes"], true);
|
||
assert_eq!(q["superscript"], json!({"size": 58.3, "position": 33.3}));
|
||
// Feeding the report back changes nothing (and records no undo step).
|
||
assert_eq!(s.execute("document.setup", &q).unwrap(), q);
|
||
assert!(s.doc().unwrap().history.undo.is_empty());
|
||
assert_eq!(*setup(&s), DocSetup::default());
|
||
}
|
||
|
||
#[test]
|
||
fn many_settings_are_one_undo_step() {
|
||
let mut s = session();
|
||
let before = s.doc().unwrap().doc.clone();
|
||
let r = s
|
||
.execute(
|
||
"document.setup",
|
||
&json!({
|
||
"units": "Millimeters", "bleed": {"top": 9, "right": 4.5}, "gridSize": "large", "gridColors": "Blue",
|
||
"simulatePaper": true, "flattenerPreset": "high", "discardWhiteOverprint": false,
|
||
"outlineImages": true, "highlightSubstitutedGlyphs": true, "exportText": "appearance",
|
||
"superscript": {"size": 60}, "subscript": {"position": 20}, "smallCapsSize": 75,
|
||
}),
|
||
)
|
||
.unwrap();
|
||
assert_eq!(r["gridColorsName"], "Blue");
|
||
let d = &s.doc().unwrap().doc;
|
||
assert_eq!(d.units, Unit::Millimeters);
|
||
let st = &d.setup;
|
||
assert_eq!(st.bleed, [9.0, 0.0, 0.0, 4.5]);
|
||
assert_eq!((st.grid_size, st.simulate_paper, st.outline_images, st.highlight_substituted_glyphs), (GridSize::Large, true, true, true));
|
||
assert_eq!((st.flattener(), st.discard_white_overprint, st.export_text), ("High Resolution", false, ExportText::Appearance));
|
||
assert_eq!((st.superscript, st.subscript), (ScriptMetrics { size: 60.0, position: 33.3 }, ScriptMetrics { size: 58.3, position: 20.0 }));
|
||
assert_eq!(st.small_caps_size, 75.0);
|
||
let undo: Vec<&str> = s.doc().unwrap().history.undo.iter().map(|e| e.label.as_str()).collect();
|
||
assert_eq!(undo, ["Document Setup"]);
|
||
s.execute("edit.undo", &json!({})).unwrap();
|
||
assert_eq!(*s.doc().unwrap().doc, *before);
|
||
}
|
||
|
||
#[test]
|
||
fn bad_values_are_rejected_and_change_nothing() {
|
||
let mut s = session();
|
||
for p in [
|
||
json!({"bleed": 73}),
|
||
json!({"bleed": [1, 2, 3]}),
|
||
json!({"bleed": {"middle": 2}}),
|
||
json!({"bleed": -1}),
|
||
json!({"gridSize": "huge"}),
|
||
json!({"gridColors": ["#ffffff"]}),
|
||
json!({"gridColors": "Plaid"}),
|
||
json!({"language": "Klingon"}),
|
||
json!({"quotes": {"double": "“"}}),
|
||
json!({"quotes": {"triple": "“”"}}),
|
||
json!({"superscript": {"size": 0}}),
|
||
json!({"exportText": "pdf"}),
|
||
json!({"flattenerPreset": "Ultra"}),
|
||
json!({"backgroundContents": "grey"}),
|
||
json!({"simulatePaper": "yes"}),
|
||
json!({"units": "furlongs"}),
|
||
json!({"bleed": 9, "nonsense": true}),
|
||
json!([1, 2]),
|
||
] {
|
||
assert!(s.execute("document.setup", &p).is_err(), "{p}");
|
||
}
|
||
assert_eq!(*setup(&s), DocSetup::default());
|
||
assert!(s.doc().unwrap().history.undo.is_empty());
|
||
}
|
||
|
||
#[test]
|
||
fn set_units_is_an_alias() {
|
||
let mut s = session();
|
||
s.execute("document.setUnits", &json!({"units": "inches"})).unwrap();
|
||
assert_eq!(s.doc().unwrap().doc.units, Unit::Inches);
|
||
assert_eq!(s.execute("document.setup", &json!({})).unwrap()["units"], "Inches");
|
||
assert!(s.execute("document.setUnits", &json!({"units": "parsecs"})).is_err());
|
||
}
|
||
|
||
/// Switch Units (Cmd+Alt+Shift+U) steps the document's units through every unit in the order a
|
||
/// right-click on a ruler lists them, Feet going back to Points, one undo step each.
|
||
#[test]
|
||
fn switch_units_steps_through_the_units_and_wraps() {
|
||
let spec = command_specs().iter().find(|c| c.id == "document.switchUnits").expect("registered");
|
||
assert_eq!((spec.label, spec.shortcut), ("Switch Units", Some("Cmd+Alt+Shift+U")));
|
||
assert!(Session::new().execute("document.switchUnits", &json!({})).is_err(), "no document");
|
||
let mut s = session();
|
||
assert_eq!(s.doc().unwrap().doc.units, Unit::Points);
|
||
let order = ["Picas", "Inches", "Millimeters", "Centimeters", "Pixels", "Feet & Inches", "Meters", "Yards", "Feet", "Points"];
|
||
for (i, label) in order.iter().enumerate() {
|
||
assert_eq!(s.execute("document.switchUnits", &json!({})).unwrap(), json!({"units": label}));
|
||
assert_eq!(s.doc().unwrap().doc.units.label(), *label);
|
||
assert_eq!(s.doc().unwrap().history.undo.len(), i + 1, "one undo step each");
|
||
}
|
||
s.execute("document.setUnits", &json!({"units": "Feet"})).unwrap();
|
||
s.execute("document.switchUnits", &json!({})).unwrap();
|
||
assert_eq!(s.doc().unwrap().doc.units, Unit::Points, "after Feet comes Points");
|
||
s.execute("edit.undo", &json!({})).unwrap();
|
||
assert_eq!(s.doc().unwrap().doc.units, Unit::Feet);
|
||
}
|
||
|
||
#[test]
|
||
fn language_picks_its_quotes_unless_quotes_are_given() {
|
||
let mut s = session();
|
||
s.execute("document.setup", &json!({"language": "german"})).unwrap();
|
||
assert_eq!(setup(&s).language, "German");
|
||
assert_eq!(setup(&s).quotes.double, ['„', '“']);
|
||
s.execute("document.setup", &json!({"language": "French", "quotes": {"double": ["“", "”"]}})).unwrap();
|
||
assert_eq!((setup(&s).quotes.double, setup(&s).quotes.single), (['“', '”'], ['‹', '›']));
|
||
}
|
||
|
||
#[test]
|
||
fn typographers_quotes_setting_changes_typed_quotes() {
|
||
let mut s = session();
|
||
let id = type_at(&mut s, 50.0, 50.0, &["\"", "H", "i", "\"", " ", "i", "t", "'", "s"]);
|
||
assert_eq!(text(&s, id).plain_text(), "“Hi” it’s");
|
||
// The document's quote style.
|
||
s.execute("document.setup", &json!({"language": "German"})).unwrap();
|
||
let id = type_at(&mut s, 50.0, 150.0, &["\"", "a", "\""]);
|
||
assert_eq!(text(&s, id).plain_text(), "„a“");
|
||
// Straight quotes with the option off.
|
||
s.execute("document.setup", &json!({"typographersQuotes": false})).unwrap();
|
||
let id = type_at(&mut s, 50.0, 250.0, &["\"", "b", "'"]);
|
||
assert_eq!(text(&s, id).plain_text(), "\"b'");
|
||
}
|
||
|
||
#[test]
|
||
fn smart_punctuation_uses_the_document_quotes() {
|
||
let mut s = session();
|
||
s.execute("document.setup", &json!({"language": "French"})).unwrap();
|
||
let id = create_text(&mut s, "\"oui\" 'non'");
|
||
s.execute("type.smartPunctuation", &json!({"scope": "document"})).unwrap();
|
||
assert_eq!(text(&s, id).plain_text(), "«oui» ‹non›");
|
||
}
|
||
|
||
#[test]
|
||
fn superscript_and_small_caps_use_and_follow_the_setup_sizes() {
|
||
let mut s = session();
|
||
let id = create_text(&mut s, "E=mc2");
|
||
s.execute("document.setup", &json!({"superscript": {"size": 50, "position": 40}})).unwrap();
|
||
s.execute("text.setRangeStyle", &json!({"id": id.0, "start": 4, "end": 5, "position": "superscript"})).unwrap();
|
||
s.execute("text.setFormat", &json!({"ids": [id.0], "smallCaps": true})).unwrap();
|
||
let t = text(&s, id);
|
||
assert_eq!(t.runs.last().unwrap().style.position, CharPosition::Superscript(ScriptMetrics { size: 50.0, position: 40.0 }));
|
||
assert!(t.runs.iter().all(|r| r.style.small_caps == Some(70.0)));
|
||
let narrow = t.cached_bounds.unwrap();
|
||
// A Document Setup change restyles the text in the same undo step.
|
||
let undo = s.doc().unwrap().history.undo.len();
|
||
s.execute("document.setup", &json!({"superscript": {"size": 100}, "smallCapsSize": 100})).unwrap();
|
||
assert_eq!(s.doc().unwrap().history.undo.len(), undo + 1);
|
||
let t = text(&s, id);
|
||
assert_eq!(t.runs.last().unwrap().style.position, CharPosition::Superscript(ScriptMetrics { size: 100.0, position: 40.0 }));
|
||
assert!(t.runs.iter().all(|r| r.style.small_caps == Some(100.0)));
|
||
assert!(t.cached_bounds.unwrap().width() > narrow.width(), "bounds follow the bigger glyphs");
|
||
s.execute("text.setFormat", &json!({"ids": [id.0], "position": "normal", "smallCaps": false})).unwrap();
|
||
assert!(text(&s, id).runs.iter().all(|r| r.style.position == CharPosition::Normal && r.style.small_caps.is_none()));
|
||
assert!(s.execute("text.setFormat", &json!({"ids": [id.0], "position": "sideways"})).is_err());
|
||
}
|
||
|
||
#[test]
|
||
fn character_styles_follow_the_setup_sizes() {
|
||
let mut s = session();
|
||
let attrs = json!({"position": {"kind": "subscript", "size": 58.3, "position": 33.3}, "small_caps": 70.0});
|
||
Arc::make_mut(&mut s.doc_mut().unwrap().doc).char_styles.push(TextStyleDef { name: "Note".into(), attrs: attrs.as_object().unwrap().clone() });
|
||
s.execute("document.setup", &json!({"subscript": {"size": 40}, "smallCapsSize": 80})).unwrap();
|
||
let def = &s.doc().unwrap().doc.char_styles[0];
|
||
assert_eq!(def.attrs["position"], json!({"kind": "subscript", "size": 40.0, "position": 33.3}));
|
||
assert_eq!(def.attrs["small_caps"], json!(80.0));
|
||
}
|
||
|
||
/// A file whose type, symbols, graph label styles, character styles or pattern definition saved in
|
||
/// pattern editing mode hold superscript, subscript or small caps proportions outside the ranges
|
||
/// `document.setup` takes opens with each at the nearer end of its range; the other character
|
||
/// attributes open as saved.
|
||
#[test]
|
||
fn a_file_with_script_proportions_out_of_range_opens_with_them_in_range() {
|
||
let mut s = session();
|
||
let id = create_text(&mut s, "E=mc2");
|
||
s.execute("text.setRangeStyle", &json!({"id": id.0, "start": 4, "end": 5, "position": "superscript"})).unwrap();
|
||
s.execute("text.setFormat", &json!({"ids": [id.0], "smallCaps": true})).unwrap();
|
||
let water = create_text(&mut s, "H2O");
|
||
s.execute("text.setRangeStyle", &json!({"id": water.0, "start": 1, "end": 2, "position": "subscript"})).unwrap();
|
||
let csv = ",2024\nQ1,3\nQ2,5";
|
||
let graph = s.execute("graph.create", &json!({"type": "column", "x": 100, "y": 150, "width": 200, "height": 100, "csv": csv})).unwrap();
|
||
let graph = NodeId(graph["id"].as_u64().unwrap());
|
||
let exponent = create_text(&mut s, "x2");
|
||
s.execute("text.setRangeStyle", &json!({"id": exponent.0, "start": 1, "end": 2, "position": "superscript"})).unwrap();
|
||
s.execute("select.set", &json!({"ids": [exponent.0]})).unwrap();
|
||
s.execute("object.pattern.make", &json!({"name": "Exponent", "width": 20, "height": 20})).unwrap();
|
||
s.execute("object.pattern.done", &json!({})).unwrap();
|
||
// Saved in pattern editing mode: Cancel puts back the definition from before the edit.
|
||
s.execute("object.pattern.edit", &json!({"name": "Exponent"})).unwrap();
|
||
let mut d = (*s.doc().unwrap().doc).clone();
|
||
if let Some(NodeKind::Text(t)) = d.node_mut(id).map(|n| &mut n.kind) {
|
||
t.runs[0].style.small_caps = Some(-5.0);
|
||
let two = &mut t.runs[1].style;
|
||
two.position = CharPosition::Superscript(ScriptMetrics { size: 0.0, position: f64::MAX });
|
||
two.small_caps = Some(f64::MAX);
|
||
}
|
||
if let Some(NodeKind::Text(t)) = d.node_mut(water).map(|n| &mut n.kind) {
|
||
t.runs[1].style.position = CharPosition::Subscript(ScriptMetrics { size: f64::MAX, position: -1000.0 });
|
||
t.runs[1].style.tracking = -5000.0;
|
||
}
|
||
let mut art = d.node(id).unwrap().clone();
|
||
art.id = NodeId(9000);
|
||
d.symbols.push(vectorcraft_doc::Symbol { name: "Formula".into(), art: Arc::new(art) });
|
||
if let Some(g) = d.node_mut(graph).and_then(|n| n.extras_if_any()).and_then(|e| e.graph.as_deref_mut()) {
|
||
let position = CharPosition::Subscript(ScriptMetrics { size: 0.0, position: 1000.0 });
|
||
g.label_styles.legend = Some(vectorcraft_doc::CharStyle { position, small_caps: Some(0.0), ..vectorcraft_doc::CharStyle::default() });
|
||
}
|
||
let original = d.pattern_edit.as_mut().and_then(|pe| pe.original.as_mut()).and_then(|p| p.art.first_mut());
|
||
if let Some(NodeKind::Text(t)) = original.map(|a| &mut Arc::make_mut(a).kind) {
|
||
t.runs[1].style.position = CharPosition::Superscript(ScriptMetrics { size: 0.0, position: f64::MAX });
|
||
}
|
||
let attrs = json!({"position": {"kind": "superscript", "size": 1000.0, "position": -1000.0}, "small_caps": 0.0, "tracking": -5000.0});
|
||
d.char_styles.push(TextStyleDef { name: "Note".into(), attrs: attrs.as_object().unwrap().clone() });
|
||
let data = vectorcraft_format::base64_encode(&vectorcraft_format::save_file(&d));
|
||
s.execute("document.open", &json!({"name": "scripts.vectorcraft", "dataBase64": data})).unwrap();
|
||
let raised = CharPosition::Superscript(ScriptMetrics { size: 1.0, position: 500.0 });
|
||
let t = text(&s, id);
|
||
assert_eq!((t.runs[0].style.small_caps, t.runs[1].style.small_caps), (Some(1.0), Some(200.0)));
|
||
assert_eq!(t.runs[1].style.position, raised);
|
||
let t = text(&s, water);
|
||
assert_eq!(t.runs[1].style.position, CharPosition::Subscript(ScriptMetrics { size: 500.0, position: -500.0 }));
|
||
assert_eq!(t.runs[1].style.tracking, -5000.0, "other attributes open as saved");
|
||
let doc = &s.doc().unwrap().doc;
|
||
match &doc.symbols.iter().find(|s| s.name == "Formula").unwrap().art.kind {
|
||
NodeKind::Text(t) => assert_eq!(t.runs[1].style.position, raised),
|
||
_ => panic!("the symbol's art is not text"),
|
||
}
|
||
let legend = doc.node(graph).unwrap().extras().graph.as_ref().unwrap().label_styles.legend.clone().unwrap();
|
||
assert_eq!((legend.position, legend.small_caps), (CharPosition::Subscript(ScriptMetrics { size: 1.0, position: 500.0 }), Some(1.0)));
|
||
let def = doc.char_styles.iter().find(|d| d.name == "Note").unwrap();
|
||
assert_eq!(def.attrs["position"], json!({"kind": "superscript", "size": 500.0, "position": -500.0}));
|
||
assert_eq!((&def.attrs["small_caps"], &def.attrs["tracking"]), (&json!(1.0), &json!(-5000.0)));
|
||
s.execute("object.pattern.cancel", &json!({})).unwrap();
|
||
match &s.doc().unwrap().doc.pattern("Exponent").unwrap().art[0].kind {
|
||
NodeKind::Text(t) => assert_eq!(t.runs[1].style.position, raised),
|
||
_ => panic!("the pattern's art is not text"),
|
||
}
|
||
// With the file's proportions kept as saved, the move failed with "result would exceed the canvas".
|
||
s.execute("select.set", &json!({"ids": [id.0]})).unwrap();
|
||
s.execute("object.move", &json!({"dx": 5, "dy": 0})).unwrap();
|
||
}
|
||
|
||
#[test]
|
||
fn setup_survives_save_and_load() {
|
||
let mut s = session();
|
||
s.execute(
|
||
"document.setup",
|
||
&json!({"bleed": [9, 9, 18, 18], "gridColors": ["#ff0000", "#00ff00"], "language": "Japanese", "exportText": "appearance"}),
|
||
)
|
||
.unwrap();
|
||
let d = &s.doc().unwrap().doc;
|
||
let back = vectorcraft_format::load(&vectorcraft_format::save_file(d)).unwrap();
|
||
assert_eq!(back.setup, d.setup);
|
||
assert_eq!(back.setup.grid_colors_name(), "Custom");
|
||
}
|
||
|
||
/// Highlight Substituted Fonts is a preference (Preferences › Type): `document.setup` does not
|
||
/// report it, and ignores it with a warning that names `prefs.set`, so an older build's reply
|
||
/// passed back still applies its other keys. The document's stored value and the preference stay.
|
||
#[test]
|
||
fn highlight_substituted_fonts_is_ignored_with_a_pointer_to_the_preference() {
|
||
let mut s = session();
|
||
let q = s.execute("document.setup", &json!({})).unwrap();
|
||
assert!(q.get("highlightSubstitutedFonts").is_none() && q.get("warnings").is_none(), "{q}");
|
||
let r = s.execute("document.setup", &json!({"highlightSubstitutedFonts": false, "bleed": 9})).unwrap();
|
||
let w = r["warnings"][0].as_str().unwrap_or_default();
|
||
assert!(w.contains("prefs.set") && w.contains("highlightSubstitutedFonts"), "{r}");
|
||
assert_eq!(setup(&s).bleed, [9.0; 4]);
|
||
assert!(setup(&s).highlight_substituted_fonts, "the stored value is not changed");
|
||
assert!(s.prefs.highlight_substituted_fonts, "nor is the preference");
|
||
assert_eq!(s.doc().unwrap().history.undo.len(), 1);
|
||
// The reply, warning included, can be passed back; alone, the key changes nothing.
|
||
let mut plain = r.clone();
|
||
plain.as_object_mut().unwrap().remove("warnings");
|
||
assert_eq!(s.execute("document.setup", &r).unwrap(), plain);
|
||
assert_eq!(s.execute("document.setup", &json!({"highlightSubstitutedFonts": true})).unwrap()["warnings"], r["warnings"]);
|
||
assert_eq!(s.doc().unwrap().history.undo.len(), 1);
|
||
let e = s.execute("document.setup", &json!({"highlightSubstitutedFonts": 1})).unwrap_err().to_string();
|
||
assert!(e.contains("true or false"), "{e}");
|
||
}
|
||
|
||
/// A file saved with Document Setup's old Highlight Substituted Fonts value opens, keeps that
|
||
/// value when saved again, and leaves the preference as it was.
|
||
#[test]
|
||
fn a_file_with_the_old_highlight_substituted_fonts_value_opens_and_keeps_it() {
|
||
let mut s = session();
|
||
let mut d = (*s.doc().unwrap().doc).clone();
|
||
d.setup.highlight_substituted_fonts = false;
|
||
let data = vectorcraft_format::base64_encode(&vectorcraft_format::save_file(&d));
|
||
s.execute("document.open", &json!({"name": "old.vectorcraft", "dataBase64": data})).unwrap();
|
||
assert!(!setup(&s).highlight_substituted_fonts);
|
||
assert!(s.prefs.highlight_substituted_fonts, "the preference decides the screen");
|
||
let again = vectorcraft_format::load(&vectorcraft_format::save_file(&s.doc().unwrap().doc)).unwrap();
|
||
assert!(!again.setup.highlight_substituted_fonts);
|
||
let q = s.execute("document.setup", &json!({})).unwrap();
|
||
assert_eq!(s.execute("document.setup", &q).unwrap(), q);
|
||
}
|
||
|
||
/// A file whose bleed, superscript, subscript or small caps size lies outside the range
|
||
/// `document.setup` takes opens with it at the nearer end of that range, and the setup it reports
|
||
/// can be fed back.
|
||
#[test]
|
||
fn a_file_with_setup_values_out_of_range_opens_with_them_in_range() {
|
||
let mut s = session();
|
||
let mut d = (*s.doc().unwrap().doc).clone();
|
||
d.setup.bleed = [-5.0, 100.0, f64::MAX, 9.0];
|
||
d.setup.superscript = ScriptMetrics { size: 0.0, position: f64::MAX };
|
||
d.setup.subscript = ScriptMetrics { size: f64::MAX, position: -1000.0 };
|
||
d.setup.small_caps_size = -5.0;
|
||
let data = vectorcraft_format::base64_encode(&vectorcraft_format::save_file(&d));
|
||
s.execute("document.open", &json!({"name": "setup.vectorcraft", "dataBase64": data})).unwrap();
|
||
let st = setup(&s);
|
||
assert_eq!(st.bleed, [0.0, 72.0, 72.0, 9.0]);
|
||
assert_eq!(st.superscript, ScriptMetrics { size: 1.0, position: 500.0 });
|
||
assert_eq!(st.subscript, ScriptMetrics { size: 500.0, position: -500.0 });
|
||
assert_eq!(st.small_caps_size, 1.0);
|
||
let q = s.execute("document.setup", &json!({})).unwrap();
|
||
assert_eq!(s.execute("document.setup", &q).unwrap(), q);
|
||
}
|
||
|
||
#[test]
|
||
fn svg_text_export_follows_the_export_text_setting() {
|
||
let mut s = session();
|
||
create_text(&mut s, "Hello");
|
||
let svg = |s: &mut Session, p: Value| {
|
||
let mut p = p;
|
||
p["format"] = json!("svg");
|
||
let r = s.execute("document.export", &p).unwrap();
|
||
String::from_utf8(vectorcraft_format::base64_decode(r["dataBase64"].as_str().unwrap()).unwrap()).unwrap()
|
||
};
|
||
assert!(svg(&mut s, json!({})).contains("<text"));
|
||
s.execute("document.setup", &json!({"exportText": "appearance"})).unwrap();
|
||
assert!(!svg(&mut s, json!({})).contains("<text"));
|
||
// An explicit option still wins.
|
||
assert!(svg(&mut s, json!({"outlineText": false})).contains("<text"));
|
||
}
|
||
|
||
#[test]
|
||
fn discard_white_overprint_keeps_white_overprints_in_overprint_preview() {
|
||
let mut s = session();
|
||
let rect = |s: &mut Session, color: &str| {
|
||
let id = s.execute("shape.rectangle", &json!({"x": 0, "y": 0, "width": 40, "height": 40})).unwrap()["id"].as_u64().unwrap();
|
||
s.execute("paint.setFill", &json!({"ids": [id], "color": color})).unwrap();
|
||
s.execute("paint.setStroke", &json!({"ids": [id], "none": true})).unwrap();
|
||
id
|
||
};
|
||
rect(&mut s, "#ff0000");
|
||
let white = rect(&mut s, "#ffffff");
|
||
s.execute("object.setOverprint", &json!({"fill": true, "ids": [white]})).unwrap();
|
||
let centre = |s: &Session| {
|
||
let opts = vectorcraft_render::RenderOptions { background: Some([255; 4]), overprint_preview: true, ..Default::default() };
|
||
vectorcraft_render::Renderer::new().render(&s.doc().unwrap().doc, 40, 40, vectorcraft_geom::Affine::IDENTITY, &opts).pixel(20, 20)
|
||
};
|
||
// On by default: the white knocks out as it will print.
|
||
assert_eq!(centre(&s), [255, 255, 255, 255]);
|
||
s.execute("document.setup", &json!({"discardWhiteOverprint": false})).unwrap();
|
||
let [r, g, b, _] = centre(&s);
|
||
assert!(r > 200 && g < 60 && b < 60, "a white overprint vanishes: {:?}", [r, g, b]);
|
||
}
|
||
|
||
#[test]
|
||
fn saved_flattener_presets_and_background_contents() {
|
||
let mut s = session();
|
||
s.execute("flattener.presets.save", &json!({"name": "Proofs", "preset": "high"})).unwrap();
|
||
assert_eq!(s.execute("document.setup", &json!({"flattenerPreset": "proofs"})).unwrap()["flattenerPreset"], "Proofs");
|
||
assert_eq!(s.execute("document.setup", &json!({"flattenerPreset": "Medium Resolution"})).unwrap()["flattenerPreset"], "Medium Resolution");
|
||
assert_eq!(setup(&s).flattener_preset, None, "the default is not stored");
|
||
let r = s.execute("document.setup", &json!({"backgroundContents": "white"})).unwrap();
|
||
assert_eq!(r["backgroundContents"], "white");
|
||
assert_eq!(setup(&s).background, vectorcraft_doc::Background::White);
|
||
}
|