M4.63: Links panel with status, Relink, Go To, Update, Edit Original, Embed/Unembed, Link Info and Placement Options

The Links panel lists every image (thumbnail, name, page, missing/modified/embedded
badge) with Show filters, sorting and thumbnail sizes; its buttons and flyout relink
(to a file or a folder), go to, update, edit the original, embed and unembed, show in
folder and open Placement Options. Link Info shows format, colour space, location, ppi
and effective ppi, dimensions, scale, rotation, size and dates. Edit > Edit Original
and the Properties panel's image section reach the same commands.

Engine: links.list, links.goTo, links.embed, links.unembed, links.info and
links.placementOptions; links.relink and links.update now place the new file as the
image's Placement Options say (transforms, bounds, file dimensions, fit, fill, the
alignment point, clip to the old bounds). Model: ImageObject.placement.
UI: links.editOriginal, links.reveal, ui.placementOptionsDialog; Services.open_file
and pick_folder on the desktop.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
bflatasticandClaude Opus 5.5 committed 2026-10-05 12:40:05 +07:00
1 parent 171de62fde
commit 2925e7f9b5
50 files changed
+1901 -86

No files matched your search

+1 -1
View File
@@ -451,7 +451,7 @@ mod tests {
d.images.insert("k".into(), ImageBlob::new("image/png", vec![1, 2, 3, 250]));
// Only images in use are saved.
let img = d.alloc_id();
let im = ImageObject { key: "k".into(), width: 1, height: 1, xf: Affine::IDENTITY, link: None };
let im = ImageObject { key: "k".into(), width: 1, height: 1, xf: Affine::IDENTITY, link: None, placement: Default::default() };
d.insert(Some(l), 1, Node::new(img, NodeKind::Image(im))).unwrap();
let bytes = save(&d, true);
assert!(sniff(&bytes));
+1 -1
View File
@@ -12,7 +12,7 @@ use vectorcraft_geom::Affine;
fn image(d: &mut Document, key: &str, link: Option<LinkInfo>) {
let (id, layer) = (d.alloc_id(), d.layers[0].id);
let im = ImageObject { key: key.into(), width: 600, height: 300, xf: Affine::IDENTITY, link };
let im = ImageObject { key: key.into(), width: 600, height: 300, xf: Affine::IDENTITY, link, placement: Default::default() };
d.insert(Some(layer), 0, Node::new(id, NodeKind::Image(im))).unwrap();
}
+4 -1
View File
@@ -33,7 +33,10 @@ fn blob(bytes: &[u8]) -> ImageBlob {
fn image_node(d: &mut Document, key: &str) -> Node {
let id = d.alloc_id();
Node::new(id, NodeKind::Image(ImageObject { key: key.into(), width: 2, height: 2, xf: Affine::IDENTITY, link: None }))
Node::new(
id,
NodeKind::Image(ImageObject { key: key.into(), width: 2, height: 2, xf: Affine::IDENTITY, link: None, placement: Default::default() }),
)
}
fn opts(version: u32, compress: bool) -> SaveOptions {
+1 -1
View File
@@ -75,7 +75,7 @@ proptest! {
d.images.insert(key.clone(), ImageBlob::new("image/png", data.clone()));
let layer = d.layers[0].id;
let id = d.alloc_id();
let im = ImageObject { key: key.clone(), width: 1, height: 1, xf: Affine::IDENTITY, link: None };
let im = ImageObject { key: key.clone(), width: 1, height: 1, xf: Affine::IDENTITY, link: None, placement: Default::default() };
d.insert(Some(layer), 0, Node::new(id, NodeKind::Image(im))).unwrap();
for pretty in [false, true] {
let back = load(&save(&d, pretty)).unwrap();