Merge main into feat/live-rasterize-effect

This commit is contained in:
dotCooCoo
2026-10-17 01:25:27 -07:00
48 changed files with 17257 additions and 125 deletions
+5
View File
@@ -377,6 +377,11 @@ fn open_loaded(s: &mut Session, loaded: Loaded, links: Resolved, path: Option<St
doc.template = false;
doc.title = s.next_untitled();
}
// A .ai (or .ait) another app wrote carries no open Layers rows we read: all of them start
// closed, its top-level layers as its groups (#1475). Ours carry their own.
if matches!(format.id, "ai" | "ait") && !restored && doc.layers_open.is_none() {
doc.layers_open = Some(vec![]);
}
// Save writes the file back only in a format it writes, only a whole PDF (see
// [`LoadOptions::is_partial`]), and a .ai file only when it carried the native document (Save
// writes .ai that way).
@@ -374,3 +374,43 @@ fn bled_file_opens_on_its_artboard(name: &str, key: &str) {
open(&mut s, name, &file, json!({})).unwrap();
assert_eq!(names(&s.doc().unwrap().doc.layers), ["Art"], "{name}: the editing data's layers are read");
}
/// The rows that open in the Layers panel (layers, sublayers and groups), and which of them are
/// open.
fn open_rows(s: &Session) -> (Vec<String>, Vec<String>) {
let st = s.doc().unwrap();
let (mut rows, mut open) = (vec![], vec![]);
st.doc.walk(|n| {
if n.children().is_some() {
rows.push(n.name.clone().unwrap_or_default());
if st.layers_open.contains(n.id) {
open.push(n.name.clone().unwrap_or_default());
}
}
});
(rows, open)
}
/// #1475: a `.ai` another app wrote keeps no open rows VectorCraft reads, so it opens with every
/// row closed, its top-level layers as its groups; a `.ai` VectorCraft wrote, and other formats,
/// open as before (with their top-level layers open unless the file says otherwise).
#[test]
fn an_illustrator_ai_file_opens_with_its_layers_closed() {
let mut s = Session::new();
for file in [ai::ai(&compressed(&ai::sample_data()), ai::page_pdf()), ai::eps(&ai::sample_data(), ai::page_ps())] {
let r = open(&mut s, "art.ai", &file, json!({})).unwrap();
assert_eq!((&r["format"], &r["restored"]), (&json!("ai"), &json!(false)), "{r}");
let (rows, open) = open_rows(&s);
assert!(rows.contains(&"Art".into()) && rows.contains(&"Pair".into()) && rows.contains(&"Sub".into()), "{rows:?}");
assert_eq!(open, Vec::<String>::new(), "{r}");
}
// The same art as an EPS: its top-level layers open.
open(&mut s, "art.eps", &ai::eps(&ai::sample_data(), ai::page_ps()), json!({})).unwrap();
assert_eq!(open_rows(&s).1, ["Art", "Hidden"]);
// Saved as .ai by VectorCraft, it opens with the rows it was saved with.
let saved = s.execute("document.save", &json!({"format": "ai"})).unwrap();
let bytes = vectorcraft_format::base64_decode(saved["dataBase64"].as_str().unwrap()).unwrap();
let r = open(&mut s, "again.ai", &bytes, json!({})).unwrap();
assert_eq!(r["restored"], json!(true), "{r}");
assert_eq!(open_rows(&s).1, ["Art", "Hidden"]);
}
+2 -2
View File
@@ -345,7 +345,7 @@ static NEXT_DOC_UID: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64
/// The rows open in the Layers panel: the layers, sublayers and groups that show what they hold.
/// A document opens with the ones it was saved with ([`Document::layers_open`]), else with only
/// its top-level layers open.
/// its top-level layers open (a `.ai` another app wrote opens with none open).
#[derive(Clone, Debug, Default)]
pub struct OpenRows {
ids: std::collections::HashSet<NodeId>,
@@ -1007,7 +1007,7 @@ impl Default for Prefs {
font_preview_size: s("medium"),
recent_fonts_count: 10,
missing_glyph_protection: true,
highlight_alternate_glyphs: true,
highlight_alternate_glyphs: false,
placeholder_text: true,
units_general: s("points"),
units_stroke: s("points"),
+27 -13
View File
@@ -391,13 +391,22 @@ fn use_precise_cursors_swaps_the_tools_own_pointers_for_a_crosshair() {
("pen", Cursor::Pen),
("addAnchor", Cursor::PenAdd),
("deleteAnchor", Cursor::PenDelete),
("anchorPoint", Cursor::Pen),
("curvature", Cursor::Pen),
("mesh", Cursor::PenAdd),
("anchorPoint", Cursor::AnchorPoint),
("curvature", Cursor::Curvature),
("mesh", Cursor::MeshAdd),
("eyedropper", Cursor::Eyedropper),
("slice", Cursor::Slice),
("blend", Cursor::Blend),
("shapeBuilder", Cursor::ShapeBuilder),
("paintbrush", Cursor::Paintbrush),
("blobBrush", Cursor::BlobBrush),
("shaper", Cursor::Shaper),
("pencil", Cursor::Pencil),
("smooth", Cursor::Smooth),
("pathEraser", Cursor::PathEraser),
("join", Cursor::Join),
("eraser", Cursor::Eraser),
("knife", Cursor::Knife),
];
for (tool, c) in own {
assert_eq!(cursor(&mut s, tool), c, "{tool} off");
@@ -415,15 +424,6 @@ fn use_precise_cursors_swaps_the_tools_own_pointers_for_a_crosshair() {
"polygon",
"star",
"flare",
"paintbrush",
"blobBrush",
"shaper",
"pencil",
"smooth",
"pathEraser",
"join",
"eraser",
"knife",
"mirrorCut",
"lineCut",
"rectCut",
@@ -440,7 +440,7 @@ fn use_precise_cursors_swaps_the_tools_own_pointers_for_a_crosshair() {
assert_eq!(cursor(&mut s, tool), Cursor::Crosshair, "{tool} on");
}
assert_eq!(cursor(&mut s, "type"), Cursor::Text);
assert_eq!(cursor(&mut s, "scissors"), Cursor::NotAllowed);
assert_eq!(cursor(&mut s, "scissors"), Cursor::ScissorsNotAllowed);
s.execute("prefs.set", &json!({"key": "usePreciseCursors", "value": false})).unwrap();
for (tool, c) in own {
assert_eq!(cursor(&mut s, tool), c, "{tool} off again");
@@ -467,6 +467,8 @@ fn precise_cursors_replace_every_pen_state() {
Cursor::BlendAnchor,
Cursor::ShapeBuilder,
Cursor::ShapeBuilderErase,
Cursor::AnchorPoint,
Cursor::Curvature,
] {
assert_eq!(c.precise(), Cursor::Crosshair, "{c:?}");
}
@@ -1284,6 +1286,18 @@ fn show_font_names_in_english_preference_is_stored() {
assert!(s.prefs.font_names_in_english);
}
/// Preferences › Type › Highlight Alternate Glyphs (#394): off by default so OpenType
/// substitutions are not pink on every canvas until the user turns the preference on.
#[test]
fn highlight_alternate_glyphs_preference_is_off_by_default() {
let mut s = Session::new();
assert!(!s.prefs.highlight_alternate_glyphs, "off by default");
set_pref(&mut s, "highlightAlternateGlyphs", json!(true));
assert!(s.prefs.highlight_alternate_glyphs);
set_pref(&mut s, "highlightAlternateGlyphs", json!(false));
assert!(!s.prefs.highlight_alternate_glyphs);
}
/// Preferences › Hyphenation › Exceptions (#394): `prefs.set` pushes the list into the hyphenator
/// and bumps open documents so type reflows. Parsing of the list is covered in `vectorcraft-text`.
#[test]