diff --git a/crates/render/src/lib.rs b/crates/render/src/lib.rs index 3f6bea1..e26f8c6 100644 --- a/crates/render/src/lib.rs +++ b/crates/render/src/lib.rs @@ -222,11 +222,14 @@ pub struct Renderer { resources: Resources, /// Outlines morphing in the frame being drawn by `blend`. paths: Vec, + /// The background of the slide (layout, master) being drawn, its placeholder colour and the + /// slide's view transform: shapes filled with the background (`useBgFill`) show it through. + bg: Option<(Fill, Option, Affine)>, } impl Default for Renderer { fn default() -> Self { - Renderer { resources: Resources::new(), paths: Vec::new() } + Renderer { resources: Resources::new(), paths: Vec::new(), bg: None } } } @@ -590,6 +593,7 @@ impl Renderer { ctx.set_transform(view); let path = r.to_path(0.1); paint::fill_path(ctx, rctx, &fill, ph, &path, r, view, FillMode::Norm, 1.0, f.pres); + self.bg = Some((fill, ph, view)); } fn shape(&mut self, ctx: &mut RenderContext, f: &Frame, rctx: &Ctx, s: &Shape, parent: Affine, depth: usize) { @@ -711,7 +715,16 @@ impl Renderer { } ctx.set_transform(m); ctx.set_fill_rule(if sp.even_odd { peniko::Fill::EvenOdd } else { peniko::Fill::NonZero }); - paint::fill_path(ctx, rctx, fl, fill_ph, &sp.path, Rect::new(0.0, 0.0, w, h), m, sp.fill, fade(i).fill, f.pres); + match (fl, &self.bg) { + // `useBgFill`: the slide's background, laid out on the slide (not the + // shape), shows through the shape. + (Fill::Background, Some((bg, ph, view))) => { + let slide = Rect::new(0.0, 0.0, f.pres.slide_size.width, f.pres.slide_size.height); + let path = (view.inverse() * m) * sp.path.clone(); + paint::fill_path(ctx, rctx, bg, *ph, &path, slide, *view, sp.fill, fade(i).fill, f.pres); + } + _ => paint::fill_path(ctx, rctx, fl, fill_ph, &sp.path, Rect::new(0.0, 0.0, w, h), m, sp.fill, fade(i).fill, f.pres), + } ctx.set_fill_rule(peniko::Fill::NonZero); } } diff --git a/crates/render/src/tests.rs b/crates/render/src/tests.rs index 9b7fa01..7f0ef96 100644 --- a/crates/render/src/tests.rs +++ b/crates/render/src/tests.rs @@ -572,3 +572,41 @@ fn a_gradient_with_a_stop_off_the_end_stays_linear() { assert!((got - want).abs() <= 4, "x {x}: {got} vs {want}"); } } + +#[test] +fn a_shape_filled_with_the_background_shows_the_slide_background() { + // `useBgFill`: PowerPoint paints the slide's background into the shape, laid out on the + // slide. A white panel covers a black-to-grey background; a shape on it with the background + // fill is a window onto that background. + use deckcraft_model::style::{Fill, Gradient, GradientShape, GradientStop}; + let none = || Some(deckcraft_model::style::Line { fill: Some(Fill::None), ..Default::default() }); + let panel = Shape { + id: ShapeId(710), + xfrm: Some(Xfrm::new(0.0, 0.0, 960.0, 540.0)), + fill: Some(Fill::solid(ColorRef::rgb(deckcraft_color::Rgba::rgb(255, 255, 255)))), + line: none(), + ..Default::default() + }; + let window = Shape { + id: ShapeId(711), + xfrm: Some(Xfrm::new(100.0, 100.0, 760.0, 340.0)), + fill: Some(Fill::Background), + line: none(), + ..Default::default() + }; + let mut p = deck_with(vec![panel, window]); + let stop = |pos, v| GradientStop { pos, color: ColorRef::rgb(deckcraft_color::Rgba::rgb(v, v, v)) }; + std::sync::Arc::make_mut(&mut p.slides[0]).background = Some(deckcraft_model::Background::Fill { + fill: Fill::Gradient(Gradient { + stops: vec![stop(0.0, 0), stop(0.5, 0), stop(1.0, 200)], + shape: GradientShape::Linear { angle: 0.0, scaled: false }, + rotate_with_shape: true, + }), + }); + let img = render_slide(&p, 0, &RenderOpts { scale: 1.0, ..Default::default() }); + assert_eq!(img.pixel(50, 50)[0], 255, "the panel stays white outside the window"); + assert!(img.pixel(300, 270)[0] < 8, "the window shows the background's black half: {:?}", img.pixel(300, 270)); + // Laid out on the slide: three quarters across the slide is half way from black to grey. + let v = img.pixel(720, 270)[0] as i32; + assert!((v - 100).abs() <= 6, "{v}"); +}