Render: shapes filled with their group's fill (a:grpFill) show it
A shape inside a group whose fill is grpFill drew nothing (the fill was read but the renderer skipped it). PowerPoint fills it with the group's fill laid out over the whole group: a gradient or pattern runs on across the children rather than restarting in each. Groups without a fill of their own pass their parent's on. Wild synthetic set: group fills slide 27.33 -> 7.88. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
8dafbcb8c2
commit
061e2c8ad5
|
|
@ -244,11 +244,14 @@ pub struct Renderer {
|
||||||
/// The transform a top-level shape is drawn under (slide to pixels); a group's children draw
|
/// The transform a top-level shape is drawn under (slide to pixels); a group's children draw
|
||||||
/// under it combined with the group's child-coordinate mapping, which must not scale lines.
|
/// under it combined with the group's child-coordinate mapping, which must not scale lines.
|
||||||
view: Affine,
|
view: Affine,
|
||||||
|
/// The fill of the group being drawn, with the transform and box it is laid out in: children
|
||||||
|
/// filled with the group's fill (`a:grpFill`) show it, as one fill across the whole group.
|
||||||
|
group_fill: Option<(Fill, Affine, Rect)>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for Renderer {
|
impl Default for Renderer {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Renderer { resources: Resources::new(), paths: Vec::new(), bg: None, drawn: None, view: Affine::IDENTITY }
|
Renderer { resources: Resources::new(), paths: Vec::new(), bg: None, drawn: None, view: Affine::IDENTITY, group_fill: None }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -779,9 +782,16 @@ impl Renderer {
|
||||||
ShapeKind::Group { children, child } => {
|
ShapeKind::Group { children, child } => {
|
||||||
let ch = Rect::new(child.x, child.y, child.x + child.w, child.y + child.h);
|
let ch = Rect::new(child.x, child.y, child.x + child.w, child.y + child.h);
|
||||||
let gm = parent * deckcraft_geom::group_child_affine(&x, ch);
|
let gm = parent * deckcraft_geom::group_child_affine(&x, ch);
|
||||||
|
// A group's own fill (a group filled with its parent's passes that on).
|
||||||
|
let own = match &s.fill {
|
||||||
|
Some(Fill::Group) | None => self.group_fill.clone(),
|
||||||
|
Some(fl) => Some((fl.clone(), m, Rect::new(0.0, 0.0, x.w, x.h))),
|
||||||
|
};
|
||||||
|
let outer = std::mem::replace(&mut self.group_fill, own);
|
||||||
for c in children {
|
for c in children {
|
||||||
self.shape(ctx, f, rctx, c, gm, depth + 1);
|
self.shape(ctx, f, rctx, c, gm, depth + 1);
|
||||||
}
|
}
|
||||||
|
self.group_fill = outer;
|
||||||
}
|
}
|
||||||
ShapeKind::Table(t) => {
|
ShapeKind::Table(t) => {
|
||||||
// Rows grow with their text: what the cells cover, borders included.
|
// Rows grow with their text: what the cells cover, borders included.
|
||||||
|
|
@ -925,6 +935,15 @@ impl Renderer {
|
||||||
let path = (view.inverse() * m) * sp.path.clone();
|
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, bg, *ph, &path, slide, *view, sp.fill, fade(i).fill, f.pres);
|
||||||
}
|
}
|
||||||
|
// `grpFill`: the group's fill, laid out over the whole group.
|
||||||
|
(Fill::Group, _) => {
|
||||||
|
if let Some((gf, gm, gbox)) = &self.group_fill
|
||||||
|
&& gm.determinant().abs() > 1e-12
|
||||||
|
{
|
||||||
|
let path = (gm.inverse() * m) * sp.path.clone();
|
||||||
|
paint::fill_path(ctx, rctx, gf, None, &path, *gbox, *gm, 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),
|
_ => 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);
|
ctx.set_fill_rule(peniko::Fill::NonZero);
|
||||||
|
|
|
||||||
|
|
@ -287,3 +287,29 @@ fn a_pictures_border_lies_outside_the_picture() {
|
||||||
let img = render_slide(&picture_deck(Some(line)), 0, &RenderOpts { scale: 1.0, ..Default::default() });
|
let img = render_slide(&picture_deck(Some(line)), 0, &RenderOpts { scale: 1.0, ..Default::default() });
|
||||||
assert!(close(img.pixel(120, 96), RED, 30) && close(img.pixel(140, 96), WHITE, 10), "{:?}", img.pixel(140, 96));
|
assert!(close(img.pixel(120, 96), RED, 30) && close(img.pixel(140, 96), WHITE, 10), "{:?}", img.pixel(140, 96));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn children_filled_with_the_group_fill_show_one_fill_across_the_group() {
|
||||||
|
use deckcraft_model::style::{Gradient, GradientShape, GradientStop};
|
||||||
|
let stops = vec![GradientStop { pos: 0.0, color: ColorRef::rgb(RED) }, GradientStop { pos: 1.0, color: ColorRef::rgb(Rgba::rgb(0, 0, 255)) }];
|
||||||
|
let grad = Fill::Gradient(Gradient { stops, shape: GradientShape::Linear { angle: 0.0, scaled: false }, rotate_with_shape: true });
|
||||||
|
let child = |id: u32, x: f64| Shape {
|
||||||
|
id: ShapeId(id),
|
||||||
|
xfrm: Some(Xfrm::new(x, 0.0, 90.0, 100.0)),
|
||||||
|
fill: Some(Fill::Group),
|
||||||
|
line: no_line(),
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
|
let g = Shape {
|
||||||
|
id: ShapeId(16),
|
||||||
|
xfrm: Some(Xfrm::new(100.0, 100.0, 200.0, 100.0)),
|
||||||
|
fill: Some(grad),
|
||||||
|
kind: ShapeKind::Group { children: vec![child(17, 0.0), child(18, 110.0)], child: Xfrm::new(0.0, 0.0, 200.0, 100.0) },
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
|
let img = render(vec![g]);
|
||||||
|
let (l, r) = (img.pixel(105, 150), img.pixel(295, 150));
|
||||||
|
assert!(l[0] > 200 && l[2] < 60, "left end of the group's gradient {l:?}");
|
||||||
|
assert!(r[2] > 200 && r[0] < 60, "right end {r:?}");
|
||||||
|
assert!(close(img.pixel(200, 150), WHITE, 5), "the gap between the children stays empty");
|
||||||
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue