mirror of
https://github.com/storytold/deckcraft.git
synced 2026-10-24 10:37:03 -06:00
Slide: a new slide's body goes into the layout's content placeholder
slide.new put the body into the first non-title placeholder in shape order, so a layout with a small body placeholder ahead of its content placeholder (as Keynote exports "Title & Bullets") got the body in the small box and left the content placeholder empty. The body now goes to the main content placeholder: idx 1 first, then a full-size placeholder over a half/quarter one, then a text placeholder over a picture/chart/table one, keeping shape order on ties. Fixes #725
This commit is contained in:
@@ -106,7 +106,7 @@ fn new(s: &mut Session, p: &Value) -> Result<Value> {
|
||||
sh.text = Some(TextBody::from_text(t));
|
||||
}
|
||||
if let Some(b) = str_param(p, "body")
|
||||
&& let Some(sh) = slide.shapes.iter_mut().find(|x| x.ph_type().is_some_and(|k| !k.is_title()))
|
||||
&& let Some(sh) = body_placeholder(&mut slide.shapes)
|
||||
{
|
||||
sh.text = Some(TextBody::from_text(b));
|
||||
}
|
||||
@@ -137,6 +137,25 @@ fn new(s: &mut Session, p: &Value) -> Result<Value> {
|
||||
})
|
||||
}
|
||||
|
||||
/// The placeholder a new slide's `body` goes into: the layout's main content placeholder.
|
||||
/// Prefers `idx="1"` (the content slot PowerPoint fills), then a full-size text placeholder over
|
||||
/// a `half`/`quarter` one, then a text placeholder over a picture/chart/table one; ties keep
|
||||
/// shape order, so a layout with a single body placeholder behaves as before.
|
||||
fn body_placeholder(shapes: &mut [deckcraft_model::Shape]) -> Option<&mut deckcraft_model::Shape> {
|
||||
use deckcraft_model::PhType;
|
||||
let rank = |sh: &deckcraft_model::Shape| {
|
||||
let ph = sh.ph.as_ref()?;
|
||||
if ph.kind.is_title() {
|
||||
return None;
|
||||
}
|
||||
let text = matches!(ph.kind, PhType::Body | PhType::Obj | PhType::SubTitle);
|
||||
let small = matches!(ph.size.as_deref(), Some("half" | "quarter"));
|
||||
Some((ph.idx != 1, small, !text))
|
||||
};
|
||||
let best = shapes.iter().enumerate().filter_map(|(i, sh)| rank(sh).map(|r| (r, i))).min()?.1;
|
||||
shapes.get_mut(best)
|
||||
}
|
||||
|
||||
fn index_of(s: &Session, p: &Value) -> Result<usize> {
|
||||
let st = s.doc()?;
|
||||
let i = usize_param(p, "index").unwrap_or(st.selection.slide);
|
||||
|
||||
@@ -835,3 +835,38 @@ fn slide_ranges_parse_like_powerpoints_print_box() {
|
||||
let opts = cmd::file::pdf_options(&json!({"slides": [0, 7, 1]}), 2).unwrap();
|
||||
assert_eq!(opts.slides, Some(vec![0, 1]), "indices past the end are dropped");
|
||||
}
|
||||
|
||||
/// #725: a layout with a small body placeholder ahead of its content placeholder (as Keynote
|
||||
/// exports "Title & Bullets") still gets the body in the content placeholder (`idx="1"`).
|
||||
#[test]
|
||||
fn new_slide_body_goes_to_the_content_placeholder() {
|
||||
for before in [true, false] {
|
||||
let mut s = session();
|
||||
let doc = std::sync::Arc::make_mut(&mut s.doc_mut().unwrap().doc);
|
||||
let id = deckcraft_model::ShapeId(doc.alloc_id());
|
||||
let lay = std::sync::Arc::make_mut(&mut doc.masters[0])
|
||||
.layouts
|
||||
.iter_mut()
|
||||
.find(|l| l.kind == deckcraft_model::LayoutType::TitleAndContent)
|
||||
.unwrap();
|
||||
let at = lay.shapes.iter().position(|x| x.ph.as_ref().is_some_and(|p| p.idx == 1)).unwrap();
|
||||
let mut small = lay.shapes[at].clone();
|
||||
small.id = id;
|
||||
small.name = "Subtitle 21".into();
|
||||
let ph = small.ph.as_mut().unwrap();
|
||||
ph.kind = deckcraft_model::PhType::Body;
|
||||
ph.idx = 21;
|
||||
ph.size = Some("quarter".into());
|
||||
lay.shapes.insert(if before { at } else { at + 1 }, small);
|
||||
|
||||
s.execute("slide.new", &json!({"layout": "titleAndContent", "title": "T", "body": "Line one\nLine two"})).unwrap();
|
||||
let doc = &s.doc().unwrap().doc;
|
||||
let slide = &doc.slides[doc.slides.len() - 1];
|
||||
let text = |idx: u32| {
|
||||
let sh = slide.shapes.iter().find(|x| x.ph.as_ref().is_some_and(|p| p.idx == idx)).unwrap();
|
||||
sh.text.as_ref().map(|t| t.text()).unwrap_or_default()
|
||||
};
|
||||
assert_eq!(text(1), "Line one\nLine two", "before = {before}");
|
||||
assert_eq!(text(21), "", "before = {before}");
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user