pen tool: Alt-click anchor now truly converts curved ↔ pointed

The presence of handles is now the source of truth for whether a
node is pointed or curved, so Alt-click does a full flip in either
direction:

- Anchor with any handle → drops both handles and demotes to a real
  corner. The path renders straight segments through it.
- Anchor with no handles → grows symmetric Bézier handles aimed along
  the chord through this anchor (using neighbouring anchors when they
  exist, falling back to the single available neighbour for endpoints
  of an open path). Handle length is 1/3 of the chord, so the curve
  bends visibly without ballooning out.

Previously the toggle only flipped the kind flag without touching
handles, so a smooth-to-corner click left the curve looking unchanged
and a corner-with-no-handles click did nothing visible at all.
This commit is contained in:
thePR0M3TH3AN committed 2026-06-01 21:42:48 -04:00
1 parent 62f2a7592e
commit d50284ecef
1 file changed
+46 -21
+46 -21
View File
@@ -10514,40 +10514,65 @@
render();
}
// Toggle a node between 'corner' (handles move independently)
// and 'smooth' (handles mirror across the anchor). When
// promoting to smooth, mirror hOut from hIn (or vice versa) so
// the curve actually becomes continuous through this node.
// Flip an anchor between "pointed" and "curved" — Illustrator's
// Convert Anchor Point behavior. The presence of handles is the
// source of truth (kind is just metadata for how a smooth anchor
// mirrors its handles):
// has any handle → drop both handles → real pointed corner
// (path renders straight segments through it)
// no handles → grow symmetric handles aimed along the
// chord through this anchor → real curved
// smooth point. Handle length = 1/3 of the
// chord length to the nearest neighbour.
function togglePenAnchorKind(layer, idx) {
const nodes = layer.shape.pen.nodes;
const n = nodes[idx];
if (!n) return;
pushHistory('toggle pen anchor kind');
if (n.kind === 'smooth') {
const hasHandles = !!(n.hIn || n.hOut);
if (hasHandles) {
n.kind = 'corner';
n.hIn = null;
n.hOut = null;
} else {
n.kind = 'smooth';
if (n.hIn && !n.hOut) {
n.hOut = { x: 2 * n.x - n.hIn.x, y: 2 * n.y - n.hIn.y };
} else if (n.hOut && !n.hIn) {
n.hIn = { x: 2 * n.x - n.hOut.x, y: 2 * n.y - n.hOut.y };
} else if (n.hIn && n.hOut) {
// Average direction and re-mirror so the smoothing is
// visible (handles likely were independent before).
const inLen = Math.hypot(n.hIn.x - n.x, n.hIn.y - n.y);
const outLen = Math.hypot(n.hOut.x - n.x, n.hOut.y - n.y);
const ax = (n.hOut.x - n.x) - (n.hIn.x - n.x);
const ay = (n.hOut.y - n.y) - (n.hIn.y - n.y);
const al = Math.hypot(ax, ay) || 1;
const dirX = ax / al, dirY = ay / al;
n.hOut = { x: n.x + dirX * outLen, y: n.y + dirY * outLen };
n.hIn = { x: n.x - dirX * inLen, y: n.y - dirY * inLen };
}
const grown = _defaultSmoothHandles(layer, idx);
n.hIn = grown.hIn;
n.hOut = grown.hOut;
}
regenPenLayerD(layer);
render();
}
// Compute default symmetric handles for a node that has none.
// Tangent direction = chord (prev → next) so the resulting curve
// bends naturally through this anchor. Endpoints of an open path
// use the single available neighbour. Isolated nodes get nothing.
function _defaultSmoothHandles(layer, idx) {
const nodes = layer.shape.pen.nodes;
const closed = !!layer.shape.pen.closed;
const n = nodes[idx];
const len = nodes.length;
let prev = null, next = null;
if (idx > 0) prev = nodes[idx - 1];
else if (closed && len > 1) prev = nodes[len - 1];
if (idx < len - 1) next = nodes[idx + 1];
else if (closed && len > 1) next = nodes[0];
let dx = 0, dy = 0;
if (prev && next) { dx = next.x - prev.x; dy = next.y - prev.y; }
else if (prev) { dx = n.x - prev.x; dy = n.y - prev.y; }
else if (next) { dx = next.x - n.x; dy = next.y - n.y; }
else return { hIn: null, hOut: null };
const chord = Math.hypot(dx, dy);
if (!chord) return { hIn: null, hOut: null };
const handleLen = chord / 3;
const ux = dx / chord, uy = dy / chord;
return {
hIn: { x: n.x - ux * handleLen, y: n.y - uy * handleLen },
hOut: { x: n.x + ux * handleLen, y: n.y + uy * handleLen },
};
}
function renderPenEditHandles(layer) {
if (!_penOverlay) return;
const ns = 'http://www.w3.org/2000/svg';