diff --git a/src/index.html b/src/index.html
index 8ccce58..e42823d 100644
--- a/src/index.html
+++ b/src/index.html
@@ -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';