From 8e83e8bb81449fbf06f4f00fa5b3c5adfc142ad7 Mon Sep 17 00:00:00 2001 From: Sazid Al Bayazid Date: Fri, 16 Oct 2026 02:35:40 +0600 Subject: [PATCH 1/2] Render: quick styles render on empty placeholder text boxes (#832) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The render gate for an empty placeholder's fill checked only s.fill.is_none(). A quick style that sets a style reference without an explicit fill left s.fill as None, so the gate skipped the fill even though the shape had been formatted — the style only appeared after text was added. The gate now also checks s.line and s.style: if any of the three has a local override, the placeholder is considered formatted and the fill is drawn. A truly unformatted empty placeholder still shows the dashed outline and prompt. --- ROADMAP.md | 1 + crates/render/src/lib.rs | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/ROADMAP.md b/ROADMAP.md index 93bc931e..91cfac6f 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -137,6 +137,7 @@ app's JSON control channel (`deckcraft --control PORT`). | 2026-10-17 | Edit Points: turning a shape into a freeform is one undo step, so Undo brings the original shape back (it used to stay a freeform) | | 2026-10-17 | While a placeholder's text is being edited, a click on a shape in front of it selects that shape instead of moving the caret, as in PowerPoint (#931) | | 2026-10-15 | Native saves keep an imported chart's embedded workbook, drawings and relationships; headless PPTX → edit → native save/reopen → PPTX tests verify the payloads and stale-workbook rejection | +| 2026-10-15 | Render: quick styles render on empty placeholders — the fill gate now checks for style and line overrides too (#832) | | 2026-10-15 | PPTX trigger sequences keep separate On Click steps, including paragraph builds; synthetic import/edit/save/reopen and headless show regressions | | 2026-10-16 | Export: PNG with a transparent background — the slide background is left out, only the slide's content is drawn (Export… dialog, File › Export, web download, `file.export`/`file.render` `transparentBackground`, `deckcraft-cli render --transparent`) (#784) | | 2026-10-16 | Arrange ▸ Align: Align to Slide and Align Selected Objects, as in PowerPoint. One object aligns to the slide and several to each other until the other choice is ticked; the choice holds for that selection and also drives Distribute | diff --git a/crates/render/src/lib.rs b/crates/render/src/lib.rs index cf124a9f..96a063b3 100644 --- a/crates/render/src/lib.rs +++ b/crates/render/src/lib.rs @@ -1106,7 +1106,7 @@ impl Renderer { } _ => { if let Some(fl) = &fill - && !(empty_ph && f.opts.edit && s.fill.is_none()) + && !(empty_ph && f.opts.edit && s.fill.is_none() && s.line.is_none() && s.style.is_none()) { for (i, sp) in geo.paths.iter().enumerate() { if sp.fill == FillMode::None || fade(i).fill <= 0.0 { From 52a9a45f6af49ea09e9159335eea2b41576d4e86 Mon Sep 17 00:00:00 2001 From: Sazid Al Bayazid Date: Tue, 20 Oct 2026 00:44:48 +0600 Subject: [PATCH 2/2] Render: a quick style on an empty placeholder is covered by a test (#832) The fix changed the empty-placeholder fill gate, but nothing locked the behaviour in: a quick style's fill has to appear on an empty placeholder, and a placeholder with no style at all still has to stay empty. The new render test draws an empty body placeholder in the editor with a quick style and checks that the accent fill is painted - every channel of it - and that the same placeholder without a style stays white. With the gate reverted to `s.fill.is_none()` alone the first assertion fails - the placeholder stays white - so the test is red on the unfixed code. --- crates/render/src/tests.rs | 29 ++++++++++++++++++++++++++++- 1 file changed, 28 insertions(+), 1 deletion(-) diff --git a/crates/render/src/tests.rs b/crates/render/src/tests.rs index 4ee1c889..da9c0fab 100644 --- a/crates/render/src/tests.rs +++ b/crates/render/src/tests.rs @@ -2,7 +2,7 @@ use super::*; use deckcraft_model::chart::{ChartType, Trendline, TrendlineType}; use deckcraft_model::style::ColorRef; use deckcraft_model::text::TextBody; -use deckcraft_model::{Chart, Geom, Presentation, Shape, ShapeId, ShapeKind, ShapeStyle, Table, Xfrm}; +use deckcraft_model::{Chart, Geom, PhType, Placeholder, Presentation, Shape, ShapeId, ShapeKind, ShapeStyle, Table, Xfrm}; fn deck_with(shapes: Vec) -> Presentation { let mut p = Presentation::default(); @@ -35,6 +35,33 @@ fn shape_fill_uses_theme_accent() { assert_eq!(img.pixel(50, 50), [255, 255, 255, 255]); } +/// #832: a quick style on an empty placeholder paints its fill. The style reference +/// (`s.style`) carries the fill, but the empty-placeholder gate skipped the fill whenever the +/// shape had no local fill, so the quick style only appeared once text was added. A +/// placeholder with no style at all still shows nothing but its dashed outline. +#[test] +fn a_quick_style_fills_an_empty_placeholder() { + let placeholder = || Shape { + id: ShapeId(910), + xfrm: Some(Xfrm::new(100.0, 100.0, 200.0, 100.0)), + ph: Some(Placeholder { kind: PhType::Body, idx: 1, ..Default::default() }), + ..Default::default() + }; + let p = deck_with(vec![Shape { style: Some(ShapeStyle::accent(deckcraft_color::SchemeSlot::Accent1)), ..placeholder() }]); + let img = render_slide(&p, 0, &RenderOpts { scale: 1.0, edit: true, ..Default::default() }); + let accent = p.masters[0].theme.colors.get(deckcraft_color::SchemeSlot::Accent1); + // Lower right of the shape: the prompt text sits at the top of the text rect. + let px = img.pixel(280, 185); + assert!( + (px[0] as i32 - accent.r as i32).abs() <= 2 && (px[1] as i32 - accent.g as i32).abs() <= 2 && (px[2] as i32 - accent.b as i32).abs() <= 2, + "the quick style's fill is drawn on the empty placeholder: {px:?} vs {accent:?}" + ); + // An unformatted empty placeholder stays empty: the gate is not simply gone. + let p = deck_with(vec![placeholder()]); + let img = render_slide(&p, 0, &RenderOpts { scale: 1.0, edit: true, ..Default::default() }); + assert_eq!(img.pixel(280, 185), [255, 255, 255, 255], "an unformatted placeholder has no fill"); +} + #[test] fn render_shapes_draws_the_selection_on_a_transparent_canvas() { let sh = Shape {