Type tools: hit path outline only for area and path type

## Summary
Adobe help documents Area Type and Type on a Path as starting from a click on the path or shape border, not inside a filled interior. Restore outline-only hit testing for those tools (no fill-interior hits that would steal point-type clicks). Strokeless filled paths still accept clicks on the outline.

## Tests
- area_and_path_tools_hit_path_outline_not_fill_interior

Part of storytold/vectorcraft#3505 (review feedback on PR #3534)

Co-authored-by: nrq6993-a11y <nrq6993-a11y@users.noreply.github.com>
This commit is contained in:
Cursor Agent
2026-10-22 15:01:36 +07:00
committed by bflatastic
co-authored by nrq6993-a11y
parent 6883235bad
commit 8735170a34
2 changed files with 18 additions and 11 deletions
+1 -3
View File
@@ -250,9 +250,7 @@ impl TypeTool {
/// Path under `p` for the Area Type / Type on a Path tools.
fn path_at(cx: &ToolContext, p: Point, closed: bool) -> Option<NodeId> {
let mut opt = cx.hit_options();
opt.tol = cx.tol(4.0);
let h = hit_test(cx.doc, p, opt)?;
let h = hit_test(cx.doc, p, vectorcraft_doc::hit::HitOptions { tol: cx.tol(4.0), outline: true, ..Default::default() })?;
match &cx.doc.node(h.leaf)?.kind {
NodeKind::Path { path, .. } if !closed || path.is_closed() => Some(h.leaf),
_ => None,
+17 -8
View File
@@ -419,23 +419,32 @@ fn area_and_path_tools_convert_clicked_paths() {
assert!(acts.iter().any(|a| matches!(a, Action::Exec(cmd, p) if cmd == "text.createInPath" && p["mode"] == "onPath")), "{acts:?}");
}
/// #3505: a filled path with no stroke is hittable in the interior, not only on its outline.
/// Area Type and Type on a Path follow the reference app: click the path outline, not the fill interior
/// (helpx: add-text-work-with-type-objects, creating-type-path).
#[test]
fn type_on_path_and_area_type_hit_filled_paths_without_a_stroke() {
fn area_and_path_tools_hit_path_outline_not_fill_interior() {
let (mut d, rect) = crate::testutil::doc_with_rect();
if let Some(n) = d.node_mut(rect) {
n.appearance.set_stroke(vectorcraft_color::Paint::None);
}
let (sel, p) = (Selection::default(), paint());
let c = cx(&d, &sel, &p);
let center = (150.0, 150.0);
for (tool_id, mode) in [("areaType", "area"), ("typeOnPath", "onPath")] {
let edge = (100.0, 150.0);
let interior = (150.0, 150.0);
for tool_id in ["areaType", "typeOnPath"] {
let mut tool = TypeTool::new(tool_id);
tool.pointer(&c, &PointerEvent::new(PointerKind::Down, center.0, center.1));
let acts = tool.pointer(&c, &PointerEvent::new(PointerKind::Up, center.0, center.1));
tool.pointer(&c, &PointerEvent::new(PointerKind::Down, edge.0, edge.1));
let acts = tool.pointer(&c, &PointerEvent::new(PointerKind::Up, edge.0, edge.1));
assert!(
acts.iter().any(|a| matches!(a, Action::Exec(cmd, v) if cmd == "text.createInPath" && v["path"] == json!(rect.0) && v["mode"] == mode)),
"{tool_id}: {acts:?}"
acts.iter().any(|a| matches!(a, Action::Exec(cmd, v) if cmd == "text.createInPath" && v["path"] == json!(rect.0))),
"{tool_id} on outline: {acts:?}"
);
let mut tool = TypeTool::new(tool_id);
tool.pointer(&c, &PointerEvent::new(PointerKind::Down, interior.0, interior.1));
let acts = tool.pointer(&c, &PointerEvent::new(PointerKind::Up, interior.0, interior.1));
assert!(
!acts.iter().any(|a| matches!(a, Action::Exec(cmd, _) if cmd == "text.createInPath")),
"{tool_id} in fill interior should not attach to path: {acts:?}"
);
}
}