mirror of
https://github.com/storytold/vectorcraft.git
synced 2026-10-29 20:27:05 -06:00
Arc tool: a closed arc with Base Along Y draws the X-axis sector mirrored
A closed arc from Arc Segment Tool Options, an Arc tool drag or shape.arc with lengthX and lengthY closed through the box's bottom-left corner with Base Along X, which makes a convex arc a quarter-ellipse sector. With Base Along Y it closed through the corner a convex arc bulges toward, so it drew the thin sliver between the arc and that corner instead. Such a closed arc now closes through the corner across from the one a convex arc bulges toward with either base, so the Y-axis arc, open or closed, convex or concave, is the X-axis arc mirrored top to bottom. Open arcs and arcs with Base Along X are unchanged. The two-point shape.arc form, whose params list no baseAlong, now closes through (x1, y2) whatever baseAlong says. Docs: docs/target-app-parity.md.
This commit is contained in:
@@ -1200,6 +1200,22 @@ fn line_family_tools_draw_and_click_asks_dialog() {
|
||||
assert_eq!(ui, vec![UiRequest::Dialog("polarGrid".into(), json!({"x": 10.0, "y": 10.0}))]);
|
||||
}
|
||||
|
||||
/// A closed `shape.arc` with Base Along Y runs from the box's bottom-left corner to its top-right
|
||||
/// one and closes through the top-left corner, the X-base arc's sector mirrored top to bottom.
|
||||
#[test]
|
||||
fn shape_arc_closed_along_y_closes_through_the_top_left_corner() {
|
||||
let mut s = session();
|
||||
let p = json!({"x": 100, "y": 50, "lengthX": 80, "lengthY": 40, "baseAlong": "y", "slope": 100, "closed": true});
|
||||
let id = NodeId(s.execute("shape.arc", &p).unwrap()["id"].as_u64().unwrap());
|
||||
let path = s.doc().unwrap().doc.node(id).unwrap().path_data().unwrap().clone();
|
||||
let sp = &path.subpaths[0];
|
||||
assert!(sp.closed);
|
||||
let ends: Vec<Point> = sp.anchors.iter().map(|a| a.p).collect();
|
||||
assert_eq!(ends, [Point::new(100.0, 90.0), Point::new(180.0, 50.0), Point::new(100.0, 50.0)]);
|
||||
let area = sp.signed_area().abs() / (80.0 * 40.0);
|
||||
assert!((area - std::f64::consts::FRAC_PI_4).abs() < 0.01, "{area}");
|
||||
}
|
||||
|
||||
/// The children of the grid a `shape.rectangularGrid` / `shape.polarGrid` call made, bottom first.
|
||||
fn grid(s: &mut Session, cmd: &str, p: serde_json::Value) -> Vec<std::sync::Arc<vectorcraft_doc::Node>> {
|
||||
let id = NodeId(s.execute(cmd, &p).unwrap()["id"].as_u64().unwrap());
|
||||
|
||||
@@ -191,9 +191,12 @@ pub fn arc_segment(anchor: Point, reference: usize, length_x: f64, length_y: f64
|
||||
arc(a, b, slope, closed, base_along_x)
|
||||
}
|
||||
|
||||
/// Quarter-ellipse arc from `a` to `b` (Arc tool default: open, x-axis base when `base_along_x`).
|
||||
/// `slope` is −100…100 (concave…convex); 0 is a straight chord.
|
||||
pub fn arc(a: Point, b: Point, slope: f64, closed: bool, base_along_x: bool) -> PathData {
|
||||
/// Quarter-ellipse arc from `a` to `b` (Arc tool default: open). `slope` is −100…100
|
||||
/// (concave…convex); 0 is a straight chord. A convex arc bulges toward the corner `(b.x, a.y)`,
|
||||
/// and a closed arc closes through the corner `(a.x, b.y)`, so a closed convex arc is the
|
||||
/// quarter-ellipse sector. The shape does not depend on `_base_along_x`: for a Y-base arc,
|
||||
/// [`arc_segment`] passes ends on the box mirrored top to bottom.
|
||||
pub fn arc(a: Point, b: Point, slope: f64, closed: bool, _base_along_x: bool) -> PathData {
|
||||
let t = slope.clamp(-100.0, 100.0) / 100.0;
|
||||
let corner = if t >= 0.0 { Point::new(b.x, a.y) } else { Point::new(a.x, b.y) };
|
||||
let k = KAPPA * t.abs();
|
||||
@@ -202,8 +205,7 @@ pub fn arc(a: Point, b: Point, slope: f64, closed: bool, base_along_x: bool) ->
|
||||
let mut anchors =
|
||||
vec![Anchor { p: a, h_in: a, h_out: h1, kind: AnchorKind::Corner }, Anchor { p: b, h_in: h2, h_out: b, kind: AnchorKind::Corner }];
|
||||
if closed {
|
||||
let o = if base_along_x { Point::new(a.x, b.y) } else { Point::new(b.x, a.y) };
|
||||
anchors.push(Anchor::corner(o));
|
||||
anchors.push(Anchor::corner(Point::new(a.x, b.y)));
|
||||
}
|
||||
PathData::single(SubPath::new(anchors, closed))
|
||||
}
|
||||
@@ -545,6 +547,37 @@ mod tests {
|
||||
}
|
||||
}
|
||||
|
||||
/// An arc with Base Along Y, open or closed, convex or concave, is the arc with Base Along X in
|
||||
/// the same box mirrored top to bottom, anchor for anchor and handle for handle.
|
||||
#[test]
|
||||
fn a_y_base_arc_is_the_x_base_arc_mirrored_top_to_bottom() {
|
||||
let mirror = |p: Point| Point::new(p.x, 20.0 + 70.0 - p.y);
|
||||
for closed in [false, true] {
|
||||
for slope in [100.0, 50.0, -50.0, -100.0] {
|
||||
let x = arc_segment(Point::new(10.0, 20.0), 0, 100.0, 50.0, true, slope, closed);
|
||||
let y = arc_segment(Point::new(10.0, 20.0), 0, 100.0, 50.0, false, slope, closed);
|
||||
let (xs, ys) = (&x.subpaths[0], &y.subpaths[0]);
|
||||
assert_eq!(ys.closed, closed);
|
||||
assert_eq!(xs.anchors.len(), ys.anchors.len(), "slope {slope}, closed {closed}");
|
||||
for (i, (p, q)) in xs.anchors.iter().zip(&ys.anchors).enumerate() {
|
||||
for (u, v) in [(p.p, q.p), (p.h_in, q.h_in), (p.h_out, q.h_out)] {
|
||||
assert!(mirror(u).distance(v) < 1e-9, "slope {slope}, closed {closed}, anchor {i}: {u:?} mirrored is not {v:?}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A closed convex arc is the quarter-ellipse sector of its box with either base: its area is
|
||||
/// a quarter of the ellipse's, π/4 of the box's.
|
||||
#[test]
|
||||
fn a_closed_convex_arc_is_a_quarter_ellipse_sector_with_either_base() {
|
||||
for base_along_x in [true, false] {
|
||||
let a = arc_segment(Point::ZERO, 0, 100.0, 50.0, base_along_x, 100.0, true).subpaths[0].signed_area().abs();
|
||||
assert!((a / (100.0 * 50.0) - PI / 4.0).abs() < 0.01, "base along x {base_along_x}: area {a}");
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn arc_open_and_closed() {
|
||||
assert_eq!(arc(Point::ZERO, Point::new(10.0, 10.0), 0.0, false, true).anchor_count(), 2);
|
||||
|
||||
@@ -691,6 +691,7 @@ Moved from the ROADMAP's "Shipped so far" on 2026-10-10. Update it when a featur
|
||||
In the Swatches panel, Alt-dragging a swatch onto another swatch of its kind (not a tint swatch) replaces that swatch's color, gradient or pattern, and the swatch keeps its name and place.
|
||||
- **Tool double-clicks:** double-clicking a tool's button opens its options: Hand fits the artboard in the window, Zoom shows 100 %, Rotate, Scale, Reflect and Shear open their Object › Transform dialogs, the Selection, Direct Selection and Group Selection tools the Move dialog (Enter opens these seven tools' dialogs too; Move opens on the offset of the last drag or Move and with the checkboxes it was last confirmed with, so Enter, Enter repeats a move or a copy at the same spacing, as [an Adobe Community thread](https://community.adobe.com/questions-652/move-tool-771221) describes), Gradient, Eyedropper, Blend and the Liquify tools their panels or options, the Pencil, Paintbrush, Smooth, Blob Brush and Eraser tools their Tool Options (Fidelity, fill, Keep selected, the tolerances, the brush size), and (#812) the Flare tool its Flare Tool Options, the Artboard tool the active artboard's Artboard Options, the graph tools Graph Type for the selected graph, the Symbolism tools the Symbolism Tools Options (Diameter, Intensity, Symbol Set Density) and the Magic Wand tool its panel.
|
||||
Double-clicking the Line Segment, Arc, Rectangular Grid or Polar Grid tool opens its Tool Options, as step 3 of the [Rectangular Grid tool](https://helpx.adobe.com/illustrator/using/tool-techniques/rectangular-grid-tool.html) and [Arc tool](https://helpx.adobe.com/illustrator/using/tool-techniques/arc-tool.html) pages and the [Tools](https://helpx.adobe.com/illustrator/using/tools.html) page describe (#2691): the dialog a click with the tool opens, without a place to draw. Line Segment Tool Options has Length, Angle and Fill Line (a new line takes the current fill, `shape.line` `fill`); Arc Segment Tool Options its lengths, Reference Point, Type, Base Along, Slope and Fill Arc; the grids' Tool Options the Reference Point, Default Size, dividers, skews and switches. OK draws nothing: the next drag draws with the options and a click's dialog opens at them, the Length, Angle and Default Size included; Cancel changes nothing. Tests: `toolbar::tests::double_clicking_the_line_arc_and_grid_tools_opens_their_tool_options`, `line_segment_options_and_fill_line`, `grid_default_size_is_an_option_the_drag_leaves_out`, `fill_line_gives_a_new_line_the_current_fill`.
|
||||
A closed arc from Arc Segment Tool Options or `shape.arc` with `lengthX` and `lengthY` closes through the corner of its box across from the one a convex arc bulges toward, with either Base Along, so a closed convex arc is a quarter-ellipse sector and the Y Axis arc is the X Axis arc mirrored top to bottom. The [Draw arcs](https://web.archive.org/web/20251025042220/https://helpx.adobe.com/illustrator/using/drawing-simple-lines-shapes.html) section's Type option makes "an open path or a closed path"; the corner it closes through is VectorCraft's own rule. Tests: `shapes::tests::a_y_base_arc_is_the_x_base_arc_mirrored_top_to_bottom`, `a_closed_convex_arc_is_a_quarter_ellipse_sector_with_either_base`, `tests::draw2::shape_arc_closed_along_y_closes_through_the_top_left_corner`.
|
||||
Live curve fitting in the Pencil Tool Options is on by default; while it is off, the path shown while a stroke is drawn joins the stroke's points with straight segments, and the release fits the curve, as [Pencil Tool Options](https://web.archive.org/web/20261002104400/https://helpx.adobe.com/illustrator/desktop/draw-shapes-and-paths/draw-shapes/pencil-tool-options.html) describes (`path.freehand` `fit`).
|
||||
In the Pencil and Paintbrush Tool Options, Edit selected paths within 0 px turns editing off, so a stroke that starts exactly on an end of a selected open path draws a new path; Close paths when ends are within 0 px turns closing off, so a stroke that ends exactly where it started stays open.
|
||||
Before a press that draws a new path, the Pencil cursor shows a small star, as [Draw freeform paths with the Pencil tool](https://web.archive.org/web/20260926095638/https://helpx.adobe.com/illustrator/desktop/draw-shapes-and-paths/draw-shapes/draw-freeform-paths-with-the-pencil-tool.html) describes; over a selected path within Edit selected paths' distance, which the press continues from an end or redraws, the star is not shown ([Reshape paths with the Pencil tool](https://web.archive.org/web/20260106004407/https://helpx.adobe.com/illustrator/desktop/draw-shapes-and-paths/draw-shapes/reshape-paths-with-the-pencil-tool.html)).
|
||||
|
||||
Reference in New Issue
Block a user