136 lines
5.3 KiB
Rust
136 lines
5.3 KiB
Rust
//! Native ink remains editable after XLSX saves, including a save after an unrelated cell edit.
|
|
|
|
use gridcraft_engine::Session;
|
|
use gridcraft_engine::cmd::draw::{ink_points, ink_width};
|
|
use gridcraft_engine::core::{CellRef, Value};
|
|
use gridcraft_engine::model::{Shape, ShapeKind};
|
|
use serde_json::json;
|
|
|
|
fn workbook() -> Session {
|
|
let mut session = Session::new();
|
|
session.new_workbook();
|
|
session
|
|
}
|
|
|
|
fn reopen(session: &mut Session) -> Session {
|
|
let saved = session.execute("file.saveBytes", json!({"format": "xlsx"})).unwrap();
|
|
let mut reopened = Session::new();
|
|
reopened.execute("file.open", json!({"name": "ink.xlsx", "base64": saved["base64"]})).unwrap();
|
|
reopened
|
|
}
|
|
|
|
fn shapes(session: &Session) -> &[Shape] {
|
|
&session.doc().unwrap().wb.active().unwrap().shapes
|
|
}
|
|
|
|
fn assert_anchor(actual: &Shape, expected: &Shape) {
|
|
assert_eq!(actual.anchor.cell, expected.anchor.cell);
|
|
assert_eq!(actual.anchor.mode, expected.anchor.mode);
|
|
for (name, actual, expected) in [
|
|
("dx", actual.anchor.dx, expected.anchor.dx),
|
|
("dy", actual.anchor.dy, expected.anchor.dy),
|
|
("width", actual.anchor.width, expected.anchor.width),
|
|
("height", actual.anchor.height, expected.anchor.height),
|
|
] {
|
|
assert!((actual - expected).abs() < 0.001, "anchor {name}: {actual} != {expected}");
|
|
}
|
|
}
|
|
|
|
fn assert_ink(actual: &Shape, expected: &Shape) {
|
|
assert_eq!(actual.kind, ShapeKind::Ink);
|
|
let actual_points = ink_points(actual);
|
|
let expected_points = ink_points(expected);
|
|
assert_eq!(actual_points.len(), expected_points.len());
|
|
for (actual, expected) in actual_points.iter().zip(&expected_points) {
|
|
for axis in 0..2 {
|
|
// DrawingML quantizes coordinates to integer EMUs (9,525 per model unit).
|
|
assert!((actual[axis] - expected[axis]).abs() < 0.001, "point {actual:?} != {expected:?}");
|
|
}
|
|
}
|
|
assert_eq!(actual.line, expected.line);
|
|
assert_eq!(ink_width(actual), ink_width(expected));
|
|
assert_anchor(actual, expected);
|
|
}
|
|
|
|
#[test]
|
|
fn ink_geometry_and_style_survive_two_saves_and_a_cell_edit() {
|
|
let mut session = workbook();
|
|
session
|
|
.execute("draw.stroke", json!({"points": [[130.5, 45.25], [172.5, 86.25], [151.5, 112.25], [215.5, 59.25]], "color": "#A12BCD", "width": 3}))
|
|
.unwrap();
|
|
session
|
|
.execute("draw.stroke", json!({"points": [[300, 180], [360, 180], [360, 230], [300, 230], [300, 180]], "color": "#16843B", "width": 7}))
|
|
.unwrap();
|
|
let expected = shapes(&session).to_vec();
|
|
assert_eq!(expected.len(), 2);
|
|
|
|
for pass in 0..2 {
|
|
session = reopen(&mut session);
|
|
assert_eq!(shapes(&session).len(), expected.len());
|
|
for (actual, expected) in shapes(&session).iter().zip(&expected) {
|
|
assert_ink(actual, expected);
|
|
}
|
|
if pass == 0 {
|
|
session.execute("cell.set", json!({"cell": "Z30", "input": "42"})).unwrap();
|
|
}
|
|
}
|
|
assert_eq!(session.doc().unwrap().wb.active().unwrap().value(CellRef::parse("Z30").unwrap()), Value::Number(42.0));
|
|
}
|
|
|
|
#[test]
|
|
fn reopened_ink_can_still_be_converted_to_a_shape() {
|
|
for (points, expected_kind, expected_name) in [
|
|
(vec![[100, 100], [200, 100], [200, 180], [100, 180], [100, 100]], ShapeKind::Rectangle, "Rectangle"),
|
|
(vec![[100, 100], [140, 110], [220, 130]], ShapeKind::Line, "Line"),
|
|
] {
|
|
let mut session = workbook();
|
|
session.execute("draw.stroke", json!({"points": points, "color": "#A12BCD", "width": 5})).unwrap();
|
|
let original = shapes(&session)[0].clone();
|
|
session = reopen(&mut session);
|
|
let id = shapes(&session)[0].id;
|
|
let converted = session.execute("draw.inkToShape", json!({"id": id})).unwrap();
|
|
assert_eq!(converted["kind"], expected_name);
|
|
let shape = &shapes(&session)[0];
|
|
assert_eq!(shape.kind, expected_kind);
|
|
assert_eq!(shape.line, original.line);
|
|
assert!(shape.text.is_empty());
|
|
assert_anchor(shape, &original);
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn ordinary_shape_text_is_not_mistaken_for_ink_points() {
|
|
let mut session = workbook();
|
|
session
|
|
.execute(
|
|
"insert.shape",
|
|
json!({"kind": "rectangle", "at": "D6", "width": 120, "height": 60, "fill": "#112233", "line": "#A12BCD", "text": "0.0,0.0;50.0,20.0"}),
|
|
)
|
|
.unwrap();
|
|
let original = shapes(&session)[0].clone();
|
|
for _ in 0..2 {
|
|
session = reopen(&mut session);
|
|
assert_eq!(shapes(&session).len(), 1);
|
|
let shape = &shapes(&session)[0];
|
|
assert_eq!(shape.kind, ShapeKind::Rectangle);
|
|
assert_eq!(shape.text, original.text);
|
|
assert_eq!(shape.fill, original.fill);
|
|
assert_eq!(shape.line, original.line);
|
|
assert_anchor(shape, &original);
|
|
assert!(session.execute("draw.inkToShape", json!({})).is_err());
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn horizontal_vertical_and_signed_highlighter_strokes_survive_reopening() {
|
|
for points in [vec![[25.1, 50.2], [140.3, 50.2]], vec![[40.2, 10.1], [40.2, 100.3]], vec![[-12.3, -4.2], [20.1, 10.2], [80.3, -1.4]]] {
|
|
let mut session = workbook();
|
|
session.execute("draw.stroke", json!({"points": points, "color": "#16843B", "width": 12})).unwrap();
|
|
let expected = shapes(&session)[0].clone();
|
|
for _ in 0..2 {
|
|
session = reopen(&mut session);
|
|
assert_ink(&shapes(&session)[0], &expected);
|
|
}
|
|
}
|
|
}
|