diff --git a/crates/render/src/lib.rs b/crates/render/src/lib.rs index 495bd75..e6cc5d1 100644 --- a/crates/render/src/lib.rs +++ b/crates/render/src/lib.rs @@ -875,7 +875,7 @@ impl Renderer { }; let (base, rel) = self.line_space(m, depth); let lw = line.width.unwrap_or(0.75).max(0.0); - let outside = if is_pic { outside_line(&line, lw) } else { None }; + let outside = if border_outside(is_pic, effects.as_ref()) { outside_line(&line, lw) } else { None }; Caster { line: outside.map_or(std::borrow::Cow::Borrowed(&line), std::borrow::Cow::Owned), ph: line_ph, alpha: a, base, rel } }); self.effects_below(ctx, rctx, e, fx_ph, &geo, &fill, fill_alpha, outline, content.as_ref(), m, w, h); @@ -1000,8 +1000,8 @@ impl Renderer { None => std::borrow::Cow::Borrowed(&line), }; // A picture's border lies outside the picture, as PowerPoint draws it: stroked twice - // as wide, outside the outline only. - let outside = is_pic.then(|| outside_line(&line, lw)).flatten(); + // as wide, outside the outline only (see `border_outside`). + let outside = border_outside(is_pic, effects.as_ref()).then(|| outside_line(&line, lw)).flatten(); let clipped = outside.is_some(); let (line, lw) = match outside { Some(o) => { @@ -1242,6 +1242,13 @@ impl Renderer { } } +/// Whether a shape's border is drawn outside its outline: a picture's is, unless the picture is +/// drawn in 3-D (a bevel seen through a 3-D scene), where PowerPoint centres it on the edge as on +/// any shape (observed: an 18 pt border covers 9 pt of the picture then). +fn border_outside(is_pic: bool, effects: Option<&Effects>) -> bool { + is_pic && !effects.is_some_and(|e| e.bevel.is_some() && e.raw3d.as_deref().is_some_and(|r| r.contains("scene3d"))) +} + /// The line that draws `line` (`lw` wide) outside a picture: twice as wide, so the half outside /// the outline is the border, with dashes kept their length. `None` for compound lines, whose /// bands would not survive the doubling (they stay centred on the outline). diff --git a/crates/render/src/tests_wild.rs b/crates/render/src/tests_wild.rs index 064dce0..99ce03c 100644 --- a/crates/render/src/tests_wild.rs +++ b/crates/render/src/tests_wild.rs @@ -371,3 +371,19 @@ fn a_linear_gradient_spans_its_tile_rect() { assert!(img.pixel(102, 150)[0] > 240); assert!(img.pixel(305, 150)[0] < 15 && img.pixel(495, 150)[0] < 15, "{:?}", img.pixel(305, 150)); } + +#[test] +fn a_3d_pictures_border_stays_centred_on_its_edge() { + let mut p = picture_deck(Some(Line::solid(ColorRef::rgb(RED), 8.0))); + let s = std::sync::Arc::make_mut(&mut p.slides[0]); + if let Some(sh) = s.shapes.first_mut() { + sh.effects = Some(deckcraft_model::Effects { + bevel: Some("circle".into()), + raw3d: Some("".into()), + ..Default::default() + }); + } + let img = render_slide(&p, 0, &RenderOpts { scale: 1.0, ..Default::default() }); + assert!(close(img.pixel(102, 150), RED, 30), "covers the picture's edge {:?}", img.pixel(102, 150)); + assert!(close(img.pixel(94, 150), WHITE, 10), "reaches half the width out {:?}", img.pixel(94, 150)); +}