Merge #819: Keep a missing graphic missing when it is pasted into another document (#720)

# Conflicts:
#	crates/engine/src/cmd/object.rs
This commit is contained in:
aiongg
2026-10-13 19:06:14 +00:00
2 changed files with 63 additions and 9 deletions
+46
View File
@@ -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)]
+17 -9
View File
@@ -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<StoryId, StoryId>) {
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() {