fix: IDML links in a subfolder beside the package load on another machine

A package opened away from its author's machine kept image links like
`/images/logo.png` that no longer exist; only the package folder and its
Links/ folder were searched, so images in `images/` next to the .idml
showed as missing. Also try the link's trailing folders (up to 8) under
the package folder, longest first.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
markus.doebele
2026-10-15 11:35:39 +02:00
co-authored by Claude Opus 5.5
parent a930276fd7
commit 0a836edb7d
2 changed files with 30 additions and 0 deletions
+1
View File
@@ -360,6 +360,7 @@ Newest first.
- **2026-10-14** · The Pathfinder panel is laid out as InDesign's: Paths (Join, Open, Close, Reverse), Pathfinder, Convert Shape and Convert Point, each a row of icon buttons. Join Path closes an open path or joins two at their nearest ends (`path.join`), and Convert Point makes the selected points plain, corner, smooth or symmetrical (`path.convertPoints`). No readiness percentage change.
- **2026-10-12** · Linked text files (#597): placed Word, RTF and text files can stay linked; the Links panel shows them OK / Modified / Missing, and Update imports the file again into the same frames with the saved style map (the Links panel asks before replacing edits made in the layout). No readiness percentage change.
- **2026-10-15** · IDML: the CJK leading model InDesign writes on [No paragraph style] of every document (Aki Below) only applies when the document uses a Japanese composer, as in InDesign; a Western document's paragraphs after a size change no longer sit lower than in InDesign (Montilla Design's Flex Layout file: its captions came out overset). No readiness percentage change.
- **2026-10-15** · IDML: linked images in a subfolder beside the package load when the package is opened on another machine (a link to `/images/logo.png` finds `images/logo.png` next to the .idml); they showed as missing. No readiness percentage change.
- **2026-10-14** · Type › Fill with Placeholder Text fills a frame with paragraphs of a few sentences, as InDesign does, so one paragraph can be styled on its own (it was one long paragraph, and the filler ran sentences together, "interit.Ovid"). No readiness percentage change.
- **2026-10-13** · Object › Fitting has InDesign's shortcuts: Fill Frame Proportionally ⌘⌥⇧C, Fit Content Proportionally ⌘⌥⇧E, Fit Frame to Content ⌘⌥C, Fit Content to Frame ⌘⌥E, Center Content ⌘⇧E. No readiness percentage change.
- **2026-10-13** · Gradients between process colours blend as inks, as InDesign shows them: magenta to cyan passes through C 50 M 50 (a light lilac) on screen instead of the darker RGB mix, and a midpoint between CMYK or grey stops no longer turns the PDF shading RGB. No readiness percentage change.
+29
View File
@@ -80,6 +80,10 @@ pub fn import_report(bytes: &[u8], dir: Option<&std::path::Path>) -> Result<(Doc
#[cfg(not(target_arch = "wasm32"))]
const MAX_LINK_PATH_BYTES: usize = 128 * 1024;
/// How many trailing folders of a link are tried beside the opened package.
#[cfg(not(target_arch = "wasm32"))]
const MAX_LINK_SUFFIX_PARTS: usize = 8;
#[cfg(not(target_arch = "wasm32"))]
fn packaged_link_candidates(link: &str, dir: Option<&std::path::Path>) -> Vec<std::path::PathBuf> {
// Enough for a maximum-length Windows path even with four-byte UTF-8 characters.
@@ -103,6 +107,14 @@ fn packaged_link_candidates(link: &str, dir: Option<&std::path::Path>) -> Vec<st
candidates.push(dir.join("Links").join(suffix.iter().collect::<std::path::PathBuf>()));
}
}
// A package moved off its author's machine keeps its own subfolders (`/images/logo.png`
// beside the .idml as `images/logo.png`): try the link's trailing folders, longest first.
let first = parts.len().saturating_sub(MAX_LINK_SUFFIX_PARTS);
for start in first..parts.len().saturating_sub(1) {
if let Some(suffix) = parts.get(start..).filter(|s| s.iter().all(safe_part)) {
candidates.push(dir.join(suffix.iter().collect::<std::path::PathBuf>()));
}
}
if let Some(name) = parts.last().filter(|part| safe_part(part)) {
candidates.push(dir.join(name));
candidates.push(dir.join("Links").join(name));
@@ -208,6 +220,23 @@ mod tests {
std::fs::remove_dir_all(dir).unwrap();
}
#[test]
fn opens_links_from_a_subfolder_beside_the_package() {
let dir = std::env::temp_dir().join(format!("dc-idml-subfolder-links-{}", std::process::id()));
let path = dir.join("images").join("logo.png");
std::fs::create_dir_all(path.parent().unwrap()).unwrap();
// The author's absolute `/images/…` does not exist here; `images/` beside the .idml does.
let (idml, png) = linked_idml("/images/logo.png", false).unwrap();
std::fs::write(&path, &png).unwrap();
let doc = import(&idml, Some(&dir)).unwrap();
let asset = doc.assets.values().next().unwrap();
assert_eq!(*asset.data, png);
assert_eq!(asset.link.as_deref(), std::path::absolute(&path).unwrap().to_str());
assert_eq!(super::super::links::status(asset), "ok");
std::fs::remove_dir_all(dir).unwrap();
}
#[test]
fn remembers_flat_packaged_link_location() {
let dir = std::env::temp_dir().join(format!("dc-idml-flat-links-{}", std::process::id()));