diff --git a/crates/engine/src/cmd/links.rs b/crates/engine/src/cmd/links.rs index b37dae68e..c48b3651f 100644 --- a/crates/engine/src/cmd/links.rs +++ b/crates/engine/src/cmd/links.rs @@ -569,6 +569,52 @@ mod shared_asset_tests { assert_eq!((g.asset, g.crop), (same, [1.0; 4])); assert_eq!(dst.assets.len(), 2); } + + #[test] + fn pasted_missing_graphic_does_not_show_an_unrelated_target_asset() { + use designcraft_doc::build::NewDocument; + use designcraft_doc::{Asset, Graphic, Item, ItemId, Shape, SpreadRef}; + let mut src = Document::new(&NewDocument::default()); + let mut dst = Document::new(&NewDocument::default()); + // The source graphic points at an asset the source does not have; the target uses that id for another image. + let missing = AssetId(src.alloc()); + dst.assets.insert( + missing, + Arc::new(Asset { + id: missing, + name: "p.png".into(), + mime: "image/png".into(), + link: None, + data: Arc::new(png(8, 4)), + pixels: None, + page: 0, + pdf_crop: Default::default(), + pdf_box: None, + }), + ); + let iid = ItemId(src.alloc()); + let mut it = Item::new( + iid, + src.default_layer(), + Shape::Rectangle, + designcraft_geom::shapes::rectangle(designcraft_geom::Rect::new(0.0, 0.0, 9.0, 9.0)), + ); + it.content = Content::Graphic(Graphic { + asset: missing, + size: (4.0, 4.0), + xf: designcraft_geom::Affine::IDENTITY, + clip: None, + auto_fit: Default::default(), + fit_align: 4, + crop: [1.0; 4], + wrap: Default::default(), + }); + src.insert_item(SpreadRef::Doc(0), it, None).unwrap(); + let ids = super::super::object::duplicate_from(&mut dst, &src, &[iid], SpreadRef::Doc(0), designcraft_geom::Vec2::ZERO).unwrap(); + let g = dst.item(ids[0]).and_then(|i| i.graphic()).unwrap(); + assert_ne!(g.asset, missing); + assert!(!dst.assets.contains_key(&g.asset)); + } } #[cfg(test)] diff --git a/crates/engine/src/cmd/object.rs b/crates/engine/src/cmd/object.rs index 41b285300..e5d259804 100644 --- a/crates/engine/src/cmd/object.rs +++ b/crates/engine/src/cmd/object.rs @@ -6,8 +6,8 @@ use std::sync::Arc; use designcraft_color::BlendMode; use designcraft_doc::{ - Content, Document, Effects, Fill, Item, ItemId, ItemLoc, ParaFormat, Selection, Shape, SpreadRef, Story, StoryId, Stroke, TextFrame, - TextFrameOptions, TextSel, + AssetId, Content, Document, Effects, Fill, Item, ItemId, ItemLoc, ParaFormat, Selection, Shape, SpreadRef, Story, StoryId, Stroke, + TextFrame, TextFrameOptions, TextSel, }; use designcraft_geom::{Affine, Point, Rect, Vec2, shapes}; use serde_json::{Value, json}; @@ -948,13 +948,24 @@ pub fn duplicate_from(dst: &mut Document, src: &Document, ids: &[ItemId], to: Sp Ok(out) } +/// The id to reference in `dst` for asset `id` of `src`. When `src` lacks the asset (a missing graphic), an id that `dst` +/// already uses for a different asset is swapped for a fresh unused one so the reference stays missing instead of showing +/// an unrelated image. +fn adopt_or_dangle(dst: &mut Document, src: &Document, id: AssetId) -> AssetId { + match dst.adopt_asset(src, id) { + Some(a) => a, + None if dst.assets.contains_key(&id) => AssetId(dst.alloc()), + None => id, + } +} + fn renumber(dst: &mut Document, src: &Document, it: &mut Item, stories: &mut HashMap) { it.id = ItemId(dst.alloc()); let my_id = it.id; if let Some(m) = it.media.as_mut() && let Some(p) = m.poster { - m.poster = dst.adopt_asset(src, p).or(Some(p)); + m.poster = Some(adopt_or_dangle(dst, src, p)); } match &mut it.content { Content::Text(tf) => { @@ -966,9 +977,8 @@ fn renumber(dst: &mut Document, src: &Document, it: &mut Item, stories: &mut Has st.id = n; let mut assets = HashMap::new(); for a in designcraft_doc::story_asset_refs(&st) { - if let Some(na) = dst.adopt_asset(src, a) - && na != a - { + let na = adopt_or_dangle(dst, src, a); + if na != a { assets.insert(a, na); } } @@ -985,9 +995,7 @@ fn renumber(dst: &mut Document, src: &Document, it: &mut Item, stories: &mut Has } } Content::Graphic(g) => { - if let Some(a) = dst.adopt_asset(src, g.asset) { - g.asset = a; - } + g.asset = adopt_or_dangle(dst, src, g.asset); } Content::Group { items } => { for c in items.iter_mut() {