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));
+}