mirror of
https://github.com/storytold/figcraft.git
synced 2026-11-08 19:42:05 +00:00
* M8: a whole ellipse drawn backwards keeps its simple stroke in SVG An ellipse with a sweep of -2π goes all the way round, as the renderer and the .fig export already read it. The SVG export's simple stroke checked `sweep >= 2π` only, so such an ellipse's inside or outside stroke was written as a filled even-odd ring instead of a stroke. All three places now ask `figcraft_geom::full_sweep`, as the geometry and the bounds do. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * M8: a backwards whole ellipse's stroke ring in the renderer and .fig export, tested Review follow-up (#1848): the renderer's stroke ring and the .fig export's stroke geometry read a sweep of -2π as whole too, now through `full_sweep`. A test for each checks a backwards ellipse strokes the same ring as a forward one, and fails if either goes back to checking positive sweeps only. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * M8: the .fig backwards-ellipse test compares the stroke ring itself Review follow-up (#1848): the test checked only that one stroke path was written. It now compares the path's commands and winding rule for the forward and backwards ellipse, and that the ring is even-odd. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
67 lines
2.6 KiB
Rust
67 lines
2.6 KiB
Rust
//! Outside strokes on rectangles honour the stroke join at the outer corners; inside strokes keep
|
||
//! the shape's square corners whatever the join.
|
||
|
||
use figcraft_color::{Color, Paint};
|
||
use figcraft_doc::{Document, NodeKind, StrokeAlign, StrokeJoin};
|
||
use figcraft_geom::kurbo::{BezPath, Point, Shape};
|
||
|
||
fn winding_at(align: StrokeAlign, join: StrokeJoin, at: Point) -> i32 {
|
||
let mut d = Document::new("join");
|
||
let p = d.first_page().unwrap();
|
||
let id = d.create(p, NodeKind::Rectangle, "R", 0.0, 0.0, 60.0, 40.0).unwrap();
|
||
let n = d.get_mut(id).unwrap();
|
||
n.strokes = vec![Paint::solid(Color::BLACK)];
|
||
n.stroke.weight = 10.0;
|
||
n.stroke.align = align;
|
||
n.stroke.join = join;
|
||
let rect: BezPath = figcraft_geom::kurbo::Rect::new(0.0, 0.0, 60.0, 40.0).to_path(0.1);
|
||
let n = d.get(id).unwrap();
|
||
crate::draw::stroke_area(n, &rect, true).winding(at)
|
||
}
|
||
|
||
fn covered(join: StrokeJoin) -> i32 {
|
||
winding_at(StrokeAlign::Outside, join, Point::new(-9.5, -9.5))
|
||
}
|
||
|
||
#[test]
|
||
fn inside_round_and_bevel_joins_keep_square_corner() {
|
||
for join in [StrokeJoin::Round, StrokeJoin::Bevel, StrokeJoin::Miter] {
|
||
assert_ne!(winding_at(StrokeAlign::Inside, join, Point::new(0.5, 0.5)), 0, "{join:?}");
|
||
}
|
||
}
|
||
|
||
#[test]
|
||
fn outside_round_join_rounds_outer_corner() {
|
||
assert_eq!(covered(StrokeJoin::Round), 0);
|
||
}
|
||
|
||
#[test]
|
||
fn outside_bevel_join_cuts_outer_corner() {
|
||
assert_eq!(covered(StrokeJoin::Bevel), 0);
|
||
}
|
||
|
||
#[test]
|
||
fn outside_miter_join_keeps_square_corner() {
|
||
assert_ne!(covered(StrokeJoin::Miter), 0);
|
||
}
|
||
|
||
/// An ellipse drawn the other way round (a sweep of −2π) is whole: its inside stroke is the same
|
||
/// ring of two outlines as a forward one's.
|
||
#[test]
|
||
fn a_whole_ellipse_drawn_backwards_strokes_as_a_ring() {
|
||
let ring = |sweep: f64| {
|
||
let mut d = Document::new("ring");
|
||
let p = d.first_page().unwrap();
|
||
let id = d.create(p, NodeKind::Ellipse { start: 0.0, sweep, inner: 0.0 }, "E", 0.0, 0.0, 60.0, 40.0).unwrap();
|
||
let n = d.get_mut(id).unwrap();
|
||
n.strokes = vec![Paint::solid(Color::BLACK)];
|
||
(n.stroke.weight, n.stroke.align) = (6.0, StrokeAlign::Inside);
|
||
let outline: BezPath = figcraft_geom::kurbo::Ellipse::from_rect(figcraft_geom::kurbo::Rect::new(0.0, 0.0, 60.0, 40.0)).to_path(0.1);
|
||
(*crate::draw::stroke_area(d.get(id).unwrap(), &outline, true)).clone()
|
||
};
|
||
let forwards = ring(std::f64::consts::TAU);
|
||
let moves = forwards.elements().iter().filter(|e| matches!(e, figcraft_geom::kurbo::PathEl::MoveTo(_))).count();
|
||
assert_eq!(moves, 2, "two outlines: {forwards:?}");
|
||
assert_eq!(ring(-std::f64::consts::TAU), forwards);
|
||
}
|