mirror of
https://github.com/storytold/designcraft.git
synced 2026-11-14 14:22:39 +00:00
Merge pull request #2589 from dotCooCoo/fix/nested-anchored-frame-placement
Anchored text frames: place a frame anchored inside another anchored frame
This commit is contained in:
2 files changed
+73
-11
No files matched your search
@@ -36,24 +36,42 @@ pub(crate) fn placements(doc: &Document, sid: StoryId, host: &mut dyn FnMut(Stor
|
||||
continue;
|
||||
}
|
||||
let items = anchored.get_or_insert_with(|| doc.anchored_items());
|
||||
// A frame anchored in its own story would need its own composition to place it.
|
||||
let Some(owner) = items.iter().find_map(|(owner, it)| (it.id == fid).then_some(*owner)).filter(|o| *o != sid) else { continue };
|
||||
let composed = hosts.entry(owner).or_insert_with(|| host(owner));
|
||||
if let Some(p) = locate(doc, composed, fid) {
|
||||
if let Some(p) = locate(doc, items, sid, fid, &mut hosts, host, 0) {
|
||||
out.push((fid, p));
|
||||
}
|
||||
}
|
||||
out
|
||||
}
|
||||
|
||||
/// Where `cs` (a host story's composition) places item `fid`: in its frames' anchored objects,
|
||||
/// footnotes and table cells.
|
||||
fn locate(doc: &Document, cs: &ComposedStory, fid: ItemId) -> Option<Placement> {
|
||||
/// Where the host story of `fid`, an anchored frame of story `sid`, places it: in its frames'
|
||||
/// anchored objects, footnotes and table cells. A host frame that is itself anchored is placed
|
||||
/// the same way first, up to [`MAX_DEPTH`] hosts out. `items` is [`Document::anchored_items`].
|
||||
fn locate(
|
||||
doc: &Document,
|
||||
items: &[(StoryId, &Item)],
|
||||
sid: StoryId,
|
||||
fid: ItemId,
|
||||
hosts: &mut HashMap<StoryId, Arc<ComposedStory>>,
|
||||
host: &mut dyn FnMut(StoryId) -> Arc<ComposedStory>,
|
||||
depth: usize,
|
||||
) -> Option<Placement> {
|
||||
if depth > MAX_DEPTH {
|
||||
return None;
|
||||
}
|
||||
// A frame anchored in its own story would need its own composition to place it.
|
||||
let owner = items.iter().find_map(|(owner, it)| (it.id == fid).then_some(*owner)).filter(|o| *o != sid)?;
|
||||
let cs = hosts.entry(owner).or_insert_with(|| host(owner)).clone();
|
||||
cs.frames.iter().find_map(|ft| {
|
||||
let loc = doc.find(ft.frame)?;
|
||||
let frame = doc.item_at(&loc)?;
|
||||
let xf = in_frame(ft, doc.parent_xf(&loc) * doc.text_xf(frame), fid, 0)?;
|
||||
Some(Placement { spread: loc.spread, xf })
|
||||
if let Some(loc) = doc.find(ft.frame) {
|
||||
let frame = doc.item_at(&loc)?;
|
||||
let xf = in_frame(ft, doc.parent_xf(&loc) * doc.text_xf(frame), fid, 0)?;
|
||||
return Some(Placement { spread: loc.spread, xf });
|
||||
}
|
||||
// The host frame is anchored too: find `fid` in it before placing the frame.
|
||||
let local = in_frame(ft, Affine::IDENTITY, fid, 0)?;
|
||||
let frame = items.iter().find_map(|(_, it)| (it.id == ft.frame).then_some(*it))?;
|
||||
let outer = locate(doc, items, owner, ft.frame, hosts, host, depth + 1)?;
|
||||
Some(Placement { spread: outer.spread, xf: outer.xf * doc.text_xf(frame) * local })
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -8827,6 +8827,50 @@ fn anchored_frames_in_one_host_compose_it_once() {
|
||||
assert_eq!(Cache::new().get(&d, nsid, None).frames.len(), compose_story(&d, nsid, &ComposeOptions::default()).frames.len());
|
||||
}
|
||||
|
||||
/// A text frame anchored in a text frame that is itself anchored in a frame on page 1 is placed
|
||||
/// through both hosts: on page 1, at the inner object's spot in the outer anchored frame.
|
||||
#[test]
|
||||
fn a_frame_anchored_in_an_anchored_frame_is_placed_through_both_hosts() {
|
||||
let mut d = Document::new(&NewDocument { pages: 1, ..Default::default() });
|
||||
let lid = d.default_layer();
|
||||
let body = "One.\nTwo.\nThree.";
|
||||
let (bf, sid) = d.add_text_frame(SpreadRef::Doc(0), Rect::new(36.0, 36.0, 400.0, 400.0), lid, body, ParaFormat::default()).unwrap();
|
||||
let frame = |d: &mut Document, text: &str, w: f64, h: f64| {
|
||||
let (fid, fsid) = (ItemId(d.alloc()), StoryId(d.alloc()));
|
||||
let mut story = Story::with_text(fsid, text, ParaFormat::default());
|
||||
story.frames.push(fid);
|
||||
d.stories.insert(fsid, Arc::new(story));
|
||||
let mut it =
|
||||
designcraft_doc::Item::new(fid, lid, designcraft_doc::Shape::Rectangle, designcraft_geom::shapes::rectangle(Rect::new(0.0, 0.0, w, h)));
|
||||
it.content = Content::Text(designcraft_doc::TextFrame { story: fsid, options: Default::default() });
|
||||
(it, fsid)
|
||||
};
|
||||
let (outer, osid) = frame(&mut d, "Outer text", 200.0, 100.0);
|
||||
let (inner, isid) = frame(&mut d, "Inner", 80.0, 30.0);
|
||||
let (outer_id, inner_id) = (outer.id, inner.id);
|
||||
d.story_mut(osid).unwrap().insert_object("Outer ".len(), designcraft_doc::AnchoredObject::new(inner, Default::default()));
|
||||
d.story_mut(sid).unwrap().insert_object(body.find("Two").unwrap(), designcraft_doc::AnchoredObject::new(outer, Default::default()));
|
||||
|
||||
let placed = placed::placements(&d, isid, &mut |h| Arc::new(compose_story(&d, h, &ComposeOptions::default())));
|
||||
assert_eq!(placed.len(), 1, "{placed:?}");
|
||||
let (id, p) = placed[0];
|
||||
assert_eq!((id, p.spread), (inner_id, SpreadRef::Doc(0)));
|
||||
// The body frame's object, then the outer frame's object, each at its composed origin.
|
||||
let host = compose_story(&d, sid, &ComposeOptions::default());
|
||||
let o1 = &host.frame(bf).unwrap().objects[0];
|
||||
let mid = compose_story(&d, osid, &ComposeOptions::default());
|
||||
let o2 = &mid.frame(outer_id).unwrap().objects[0];
|
||||
let want = d.item(bf).unwrap().xf
|
||||
* Affine::translate(o1.origin.to_vec2())
|
||||
* d.anchored_item(outer_id).unwrap().xf
|
||||
* Affine::translate(o2.origin.to_vec2());
|
||||
let (got, want) = (p.xf.as_coeffs(), want.as_coeffs());
|
||||
assert!(got.iter().zip(want).all(|(a, b)| (a - b).abs() < 1e-9), "{got:?} != {want:?}");
|
||||
assert_eq!(frame_specs_placed(&d, isid, &placed)[0].page, Some(0));
|
||||
// The cache places it the same way.
|
||||
assert_eq!(Cache::new().get(&d, isid, None).frames.len(), 1);
|
||||
}
|
||||
|
||||
/// A marginal note (an anchored text frame, Only Align First Line to Grid, 9/10) hangs 15 pt
|
||||
/// above its anchor's baseline on a right-hand page. It composes against the document grid where
|
||||
/// it is placed: its first line moves down onto the grid line, the second follows 10 pt below,
|
||||
|
||||
Reference in new issue
Block a user