Browse / github.com/storytold/deckcraft / patch
PPTX: a picture in a picture placeholder takes its layout's shape
PPTX: a picture in a picture placeholder takes its layout's shape A photo in a picture placeholder whose layout draws it as a circle (or any shape other than a rectangle) was drawn as a plain rectangle, but still with the layout's border. PowerPoint cuts the photo into the layout's shape. Seen in a real deck: a portrait on a slide whose layout has a round picture placeholder with a white border (`<a:prstGeom prst="ellipse"/>` and `<a:ln w="95250">` in the layout) got a white-bordered rectangle in DeckCraft and a white-bordered circle in PowerPoint. The slide's `p:pic` names no geometry of its own, only its frame. Cause: #301 made a placeholder with no
- Status
- open
- Score
- 0.00
- Adopters
- 0 other operators
- Author
- leobasilio (imported from pull request #1384, open)
- Base
aaf6c1c38aa6c59beda8110bd014cc6c96c6ec74- Size
- +54 −4 in 3 files
- Review
- pending
- Shared
- 2026-10-28
- Id
636eebb4ef5dcff502afb1b1
How it went
No other operator has reported using it yet.
Diff
raw · ROADMAP.md crates/pptx/src/read/shapes.rs crates/pptx/tests/it/placeholders.rs
@@ -2,9 +2,9 @@ **Stage: alpha** · next: beta, ~20 points and ~120–210 h away -> **Last reviewed:** 2026-10-14 · **Last updated:** 2026-10-24 · **Change:** trivial (progress log: Edit Points handle drags with Shift, Ctrl/Cmd or Alt/Option set the point type; previous: trivial, progress log: scroll bars on the zoomed slide pane, #1157; previous: minor, native chart payloads stored in bounded ZIP members; previous: major, community PR wave 2026-10-14: ~60 landed items in the progress log, among them the Font, Paragraph, Photo Album, theme colour and font and Date and Time dialogs, presenter and show controls, the SmartArt Text Pane and PPTX corpus slices; breadth 84% after `cargo xtask parity`, ready stays ~55%, stage stays alpha; duplicated rows and headers folded; previous: minor, hours to ~95% per audience added) · **Target:** Microsoft PowerPoint for Mac 16.113 (Microsoft 365)-> **Last reviewed:** 2026-10-14 · **Last updated:** 2026-10-24 · **Change:** trivial (progress log: Ctrl+wheel zoom during the slide show, #1286; previous: trivial, scroll bars on the zoomed slide pane, #1157; previous: minor, native chart payloads stored in bounded ZIP members; previous: major, community PR wave 2026-10-14: ~60 landed items in the progress log, among them the Font, Paragraph, Photo Album, theme colour and font and Date and Time dialogs, presenter and show controls, the SmartArt Text Pane and PPTX corpus slices; breadth 84% after `cargo xtask parity`, ready stays ~55%, stage stays alpha; duplicated rows and headers folded; previous: minor, hours to ~95% per audience added) · **Target:** Microsoft PowerPoint for Mac 16.113 (Microsoft 365)-> **Last reviewed:** 2026-10-14 · **Last updated:** 2026-10-24 · **Change:** trivial (progress log: picture and embedded object fills; previous: trivial, scroll bars on the zoomed slide pane, #1157; previous: minor, native chart payloads stored in bounded ZIP members; previous: major, community PR wave 2026-10-14: ~60 landed items in the progress log, among them the Font, Paragraph, Photo Album, theme colour and font and Date and Time dialogs, presenter and show controls, the SmartArt Text Pane and PPTX corpus slices; breadth 84% after `cargo xtask parity`, ready stays ~55%, stage stays alpha; duplicated rows and headers folded; previous: minor, hours to ~95% per audience added) · **Target:** Microsoft PowerPoint for Mac 16.113 (Microsoft 365)+> **Last reviewed:** 2026-10-14 · **Last updated:** 2026-10-25 · **Change:** trivial (progress log: a picture in a picture placeholder is cut into the layout's shape; previous: trivial, progress log: Edit Points handle drags with Shift, Ctrl/Cmd or Alt/Option set the point type; previous: trivial, progress log: scroll bars on the zoomed slide pane, #1157; previous: minor, native chart payloads stored in bounded ZIP members; previous: major, community PR wave 2026-10-14: ~60 landed items in the progress log, among them the Font, Paragraph, Photo Album, theme colour and font and Date and Time dialogs, presenter and show controls, the SmartArt Text Pane and PPTX corpus slices; breadth 84% after `cargo xtask parity`, ready stays ~55%, stage stays alpha; duplicated rows and headers folded; previous: minor, hours to ~95% per audience added) · **Target:** Microsoft PowerPoint for Mac 16.113 (Microsoft 365)+> **Last reviewed:** 2026-10-14 · **Last updated:** 2026-10-25 · **Change:** trivial (progress log: a picture in a picture placeholder is cut into the layout's shape; previous: trivial, progress log: Ctrl+wheel zoom during the slide show, #1286; previous: trivial, scroll bars on the zoomed slide pane, #1157; previous: minor, native chart payloads stored in bounded ZIP members; previous: major, community PR wave 2026-10-14: ~60 landed items in the progress log, among them the Font, Paragraph, Photo Album, theme colour and font and Date and Time dialogs, presenter and show controls, the SmartArt Text Pane and PPTX corpus slices; breadth 84% after `cargo xtask parity`, ready stays ~55%, stage stays alpha; duplicated rows and headers folded; previous: minor, hours to ~95% per audience added) · **Target:** Microsoft PowerPoint for Mac 16.113 (Microsoft 365)+> **Last reviewed:** 2026-10-14 · **Last updated:** 2026-10-25 · **Change:** trivial (progress log: a picture in a picture placeholder is cut into the layout's shape; previous: trivial, progress log: picture and embedded object fills; previous: trivial, scroll bars on the zoomed slide pane, #1157; previous: minor, native chart payloads stored in bounded ZIP members; previous: major, community PR wave 2026-10-14: ~60 landed items in the progress log, among them the Font, Paragraph, Photo Album, theme colour and font and Date and Time dialogs, presenter and show controls, the SmartArt Text Pane and PPTX corpus slices; breadth 84% after `cargo xtask parity`, ready stays ~55%, stage stays alpha; duplicated rows and headers folded; previous: minor, hours to ~95% per audience added) · **Target:** Microsoft PowerPoint for Mac 16.113 (Microsoft 365) > **Last reviewed:** 2026-10-14 · **Last updated:** 2026-10-22 · **Change:** trivial (fill and outline edits reach group members, #1079; stage and scores unchanged) · **Target:** Microsoft PowerPoint for Mac 16.113 (Microsoft 365) > **Last reviewed:** 2026-10-14 · **Last updated:** 2026-10-20 · **Change:** trivial (empty chart number-format sections hide values; previous: minor, native chart payloads stored in bounded ZIP members; previous: major, community PR wave 2026-10-14: ~60 landed items in the progress log, among them the Font, Paragraph, Photo Album, theme colour and font and Date and Time dialogs, presenter and show controls, the SmartArt Text Pane and PPTX corpus slices; breadth 84% after `cargo xtask parity`, ready stays ~55%, stage stays alpha; duplicated rows and headers folded; previous: minor, hours to ~95% per audience added) · **Target:** Microsoft PowerPoint for Mac 16.113 (Microsoft 365) > **Last reviewed:** 2026-10-14 · **Last updated:** 2026-10-18 · **Change:** minor (Symbol-font characters (U+F0xx) remap to the installed Symbol face's real Unicode on macOS, so they draw from Symbol instead of a look-alike; previous: minor, native chart payloads stored in bounded ZIP members; previous: major, community PR wave 2026-10-14: ~60 landed items in the progress log, among them the Font, Paragraph, Photo Album, theme colour and font and Date and Time dialogs, presenter and show controls, the SmartArt Text Pane and PPTX corpus slices; breadth 84% after `cargo xtask parity`, ready stays ~55%, stage stays alpha; duplicated rows and headers folded; previous: minor, hours to ~95% per audience added) · **Target:** Microsoft PowerPoint for Mac 16.113 (Microsoft 365)@@ -137,6 +137,7 @@ app's JSON control channel (`deckcraft --control PORT`). | Date | What landed | |---|---|+| 2026-10-25 | A photo in a picture placeholder is cut into the shape its layout gives the placeholder, as PowerPoint draws it: a layout's round picture placeholder keeps a slide's photo in a circle with the layout's white border, where it came out as a rectangle (the reader only passed `p:sp` placeholders to `inherit_placeholder_geometry`, not `p:pic`) | | 2026-10-25 | Windows installer: the Start menu shortcut takes the icon embedded in `deckcraft.exe` instead of an MSI `<Icon>`, so a taskbar pin keeps its icon after an update (#1114); a pin that already lost it has to be unpinned and pinned again once | | 2026-10-24 | AutoFormat as you type: a paragraph that starts with `*`, `-`, `--`, `>`, `<>`, `1.`, `1)`, `I.`, `i.`, `A.`, `a.` or `a)` and a space or tab becomes a bulleted or numbered list in that style; Backspace right after puts the typed text back, and Tools ▸ AutoCorrect and `text.autocorrect {lists}` turn it off | | 2026-10-24 | Tables: while the caret is in a cell, dragging into another cell selects that range of cells (shaded, merged cells taken in whole), and Shift+arrow grows or shrinks it; formatting applies to the selected cells only, and Delete clears their text and keeps the table |@@ -294,6 +295,7 @@ app's JSON control channel (`deckcraft --control PORT`). | Date | Change | Summary | |---|---|---|+| 2026-10-25 | trivial | Progress log: a picture in a picture placeholder takes the layout's shape. Scores and milestone names unchanged | | 2026-10-25 | trivial | Progress log: Windows shortcut icon survives updates (#1114). Scores and milestone names unchanged | | 2026-10-24 | trivial | Progress log: selecting a range of table cells by dragging. Scores and milestone names unchanged | | 2026-10-24 | trivial | Progress log: measurement units in centimetres (#1326). Scores unchanged |@@ -338,6 +338,8 @@ fn pic(imp: &mut Imp, part: &Part, ctx: &mut IdCtx, e: &El) -> Shape { if let Some(pr) = e.child("spPr") { sp_pr(imp, part, &mut s, pr); }+ // A picture in a picture placeholder is cut into the layout's shape (a circle, say).+ mark_inherited_geometry(&mut s, e.child("spPr")); s.style = e.child("style").map(style); let pf = e.child("blipFill").and_then(|b| dml::blip_fill(imp, part, b)); let nvpr = e.path(&["nvPicPr", "nvPr"]);@@ -2,7 +2,9 @@ use std::io::{Cursor, Read, Write}; -use deckcraft_model::{Geom, defaults};+use deckcraft_geom::Xfrm;+use deckcraft_model::style::PictureFill;+use deckcraft_model::{Geom, PhType, Placeholder, Shape, ShapeId, ShapeKind, defaults}; /// `bytes` with `edit` applied to every slide part. fn edit_slides(bytes: &[u8], edit: impl Fn(String) -> String) -> Vec<u8> {@@ -62,3 +64,47 @@ fn a_placeholder_without_geometry_takes_its_layouts() { let q = deckcraft_pptx::import(&deckcraft_pptx::export(&p).expect("export")).expect("import"); assert!(q.slides[0].shapes.iter().filter(|s| s.ph.is_some()).all(|s| matches!(&s.geom, Geom::Preset { name, .. } if name == "triangle"))); }++#[test]+fn a_picture_placeholder_without_geometry_takes_its_layouts() {+ // The slide's picture sits in a layout's picture placeholder drawn as an ellipse; PowerPoint+ // writes its spPr with a frame only and shows the picture cut into the circle.+ let mut p = defaults::new_presentation(None);+ let img = {+ let mut out = Cursor::new(vec![]);+ let px = image::RgbImage::from_pixel(4, 4, image::Rgb([10, 200, 30]));+ image::DynamicImage::ImageRgb8(px).write_to(&mut out, image::ImageFormat::Png).expect("png");+ p.add_media("image1.png", "image/png", out.into_inner()).expect("media")+ };+ let ph = Placeholder { kind: PhType::Picture, idx: 15, ..Default::default() };+ let layout = p.slides[0].layout;+ let slot = Shape {+ id: ShapeId(p.alloc_id().expect("ids")),+ name: "Picture Placeholder".into(),+ xfrm: Some(Xfrm::new(100.0, 100.0, 200.0, 200.0)),+ geom: Geom::Preset { name: "ellipse".into(), adj: vec![] },+ ph: Some(ph.clone()),+ ..Default::default()+ };+ std::sync::Arc::make_mut(&mut p.masters[0]).layouts.iter_mut().find(|l| l.id == layout).expect("layout").shapes.push(slot);+ let pic = Shape {+ id: ShapeId(p.alloc_id().expect("ids")),+ name: "Photo".into(),+ xfrm: Some(Xfrm::new(100.0, 100.0, 200.0, 200.0)),+ kind: ShapeKind::Picture { fill: PictureFill { media: img, ..Default::default() } },+ ph: Some(ph),+ ..Default::default()+ };+ std::sync::Arc::make_mut(&mut p.slides[0]).shapes = vec![pic];+ let bytes = deckcraft_pptx::export(&p).expect("export");+ let bare = edit_slides(&bytes, |mut s| {+ while let Some(i) = s.find("<a:prstGeom") {+ let Some(j) = s[i..].find("</a:prstGeom>").map(|j| j + i + "</a:prstGeom>".len()) else { break };+ s.replace_range(i..j, "");+ }+ s+ });+ let q = deckcraft_pptx::import(&bare).expect("import");+ let photo = q.slides[0].shapes.iter().find(|s| matches!(s.kind, ShapeKind::Picture { .. })).expect("picture");+ assert!(matches!(&photo.geom, Geom::Preset { name, .. } if name == "ellipse"), "{:?}", photo.geom);+}