diff --git a/crates/engine/src/cmd/fileio/load.rs b/crates/engine/src/cmd/fileio/load.rs index 891a4307..da4cb9ad 100644 --- a/crates/engine/src/cmd/fileio/load.rs +++ b/crates/engine/src/cmd/fileio/load.rs @@ -273,12 +273,17 @@ pub fn load_with(name: &str, bytes: &[u8], opts: &LoadOptions) -> Result /// own): rasters stay linked, SVG files become art, missing ones placeholders (see /// [`vectorcraft_svg::ImportOptions`]). pub(crate) fn import_svg(text: &str, folder: Option<&str>) -> Result<(Document, Vec)> { + // The importer reads on a thread of its own (for its stack): what may be read is what may be + // read here. + let scope = crate::file_access::ScopeHandle::capture(); let read = |path: &str| { - // Only files (not folders or devices) are read. - file_stamp(path)?; - let bytes = read_file(path).ok()?; - let link = crate::cmd::links::link_info(path, &bytes); - Some((bytes, link)) + scope.clone().run(|| { + // Only files (not folders or devices) are read. + file_stamp(path)?; + let bytes = read_file(path).ok()?; + let link = crate::cmd::links::link_info(path, &bytes); + Some((bytes, link)) + }) }; vectorcraft_svg::import_with(text, &vectorcraft_svg::ImportOptions { folder, read: Some(&read) }).map_err(err) } diff --git a/crates/engine/src/tests_linkfiles.rs b/crates/engine/src/tests_linkfiles.rs index a0e3a430..1c4cc530 100644 --- a/crates/engine/src/tests_linkfiles.rs +++ b/crates/engine/src/tests_linkfiles.rs @@ -314,6 +314,32 @@ fn a_copy_stays_inside_the_write_root() { let _ = std::fs::remove_dir_all(&root); } +/// An SVG's linked files are read on the importer's own thread (it needs a deep stack): the roots +/// in force on the thread that opens the SVG hold there too. +#[test] +fn an_svg_reads_its_links_within_the_read_root() { + let root = folder("svg-roots"); + for dir in ["in", "out"] { + write(&root.join(dir).join("dot.png"), &png(4, 4, RED)); + } + // What came of an SVG linking to the picture in `dir`: the pictures read, and the warnings. + let import = |dir: &str| { + let href = root.join(dir).join("dot.png").to_string_lossy().replace('\\', "/"); + let svg = + format!(""); + let (doc, warnings) = crate::cmd::fileio::import_svg(&svg, None).unwrap(); + (doc.images.len(), warnings) + }; + let (inside, outside) = (import("in"), import("out")); + assert_eq!(inside, outside, "both are read with no roots in force"); + let roots = crate::file_access::AutomationRoots::new(Some(&root.join("in")), None).unwrap(); + crate::file_access::confine(roots.as_ref(), || { + assert_eq!(import("in"), inside, "inside the read root: read as before"); + assert_ne!(import("out"), outside, "outside the read root: not read"); + }); + let _ = std::fs::remove_dir_all(&root); +} + #[test] fn junk_search_params_never_fail_internally() { let root = folder("junk");