Files
vectorcraft/crates/engine/src/tests/docsetup.rs
T

487 lines
26 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.
//! 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 on the keyboard; returns the
/// new text object.
fn type_at(s: &mut Session, x: f64, y: f64, keys: &[&str]) -> NodeId {
enter_at(s, x, y, keys, true)
}
/// Click with the Type tool at (x, y) and enter `texts` one at a time, typed on the keyboard
/// ([`Session::tool_typed_text`]) or inserted as the Glyphs panel and pastes do
/// ([`Session::tool_text`]); returns the new text object.
fn enter_at(s: &mut Session, x: f64, y: f64, texts: &[&str], keyboard: bool) -> 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 t in texts {
if keyboard {
s.tool_typed_text(t, v).unwrap();
} else {
s.tool_text(t, 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'");
}
/// A lone typed `”` is set like a straight quote where a quote opens: the macOS Serbian Cyrillic
/// layout has no straight quote and types `”` on Shift+2 (#5247), so Serbian text still gets
/// „…”. Where a quote closes it stays `”`. `“ „` and the single quotes stay as typed, so `’90s`
/// keeps its apostrophe. Pasted text and the Glyphs panel keep their quotes, and with the option
/// off `”` stays as typed.
#[test]
fn a_typed_closing_double_quote_opens_where_a_quote_opens() {
let mut s = session();
s.execute("document.setup", &json!({"language": "Serbian: Cyrillic"})).unwrap();
let id = type_at(&mut s, 50.0, 50.0, &["”", "П", "р", "о", "б", "а", "”"]);
assert_eq!(text(&s, id).plain_text(), "„Проба”");
// The other curly quotes stay as typed.
let id = type_at(&mut s, 50.0, 100.0, &["„", "a", " ", "’", "b", "’", "“"]);
assert_eq!(text(&s, id).plain_text(), "„a ’b’“");
// In English a `”` that opens is `“`; the apostrophes stay.
s.execute("document.setup", &json!({"language": "English: USA"})).unwrap();
let id = type_at(&mut s, 50.0, 150.0, &["”", "i", "t", "’", "s", "”"]);
assert_eq!(text(&s, id).plain_text(), "“it’s”");
// An apostrophe after a space stays an apostrophe, and a typed `‘` stays `‘`.
let id = type_at(&mut s, 50.0, 200.0, &["t", "h", "e", " ", "’", "9", "0", "s", " ", "‘", "a"]);
assert_eq!(text(&s, id).plain_text(), "the ’90s ‘a");
// Where a quote closes, `”` stays `”` even if the document's closing quote is another.
s.execute("document.setup", &json!({"language": "German"})).unwrap();
let id = type_at(&mut s, 50.0, 250.0, &["”", "a", "”"]);
assert_eq!(text(&s, id).plain_text(), "„a”");
// More than one character is pasted text, not a typed quote.
let id = type_at(&mut s, 50.0, 300.0, &["”a”"]);
assert_eq!(text(&s, id).plain_text(), "”a”");
// An IME commit of a lone `”` that will open is not verbatim (it is rewritten); from the Glyphs
// panel it would be inserted as it is.
let v = ViewInfo::default();
s.select_tool("type", v).unwrap();
for k in [PointerKind::Down, PointerKind::Up] {
s.pointer(&PointerEvent::new(k, 50.0, 312.0), v).unwrap();
}
assert!(!s.tool_typed_text_is_verbatim("”", v));
assert!(s.tool_text_is_verbatim("”", v));
s.tool_key(ToolKey::Escape, Default::default(), v).unwrap();
// A `”` inserted from the Glyphs panel or pasted alone stays as inserted.
let id = enter_at(&mut s, 50.0, 325.0, &["”", "a"], false);
assert_eq!(text(&s, id).plain_text(), "”a");
// With the option off a typed `”` stays as typed.
s.execute("document.setup", &json!({"typographersQuotes": false})).unwrap();
let id = type_at(&mut s, 50.0, 350.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 { dynamic: false, 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);
}