diff --git a/src/index.html b/src/index.html
index 6808551..1fc28c1 100644
--- a/src/index.html
+++ b/src/index.html
@@ -9925,8 +9925,15 @@
}
function regenPenLayerD(layer) {
+ // The renderer applies sx=100/vb.w, sy=vbH/vb.h; we want sx==sy.
+ // So whenever we regenerate the d-string we also update the
+ // stored viewBox.h to match the layer's current aspect, keeping
+ // strokes uniform. Nodes are kept in 0-100 × 0-(100*h/w) space
+ // by the uniform-scale remap in finishPenPath + refit.
+ const vbH = layer.w > 0 ? 100 * layer.h / layer.w : 100;
layer.shape = {
...layer.shape,
+ viewBox: { x: 0, y: 0, w: 100, h: vbH },
d: penNodesToPathD(layer.shape.pen.nodes, layer.shape.pen.closed)
};
}
@@ -10439,11 +10446,28 @@
width: Math.max(0.5, Number(sh.strokeWidth) || 6), dash: 'solid' };
layer.fill = { type: 'none' };
}
+ // primitiveToPenNodes / parsePathDToNodes return nodes in
+ // 0-100 × 0-100. The renderer paints pen paths with sx=100/vb.w,
+ // sy=vbH/vb.h, where vbH = 100*layer.h/layer.w — so for any
+ // non-square layer this would stretch the path. Remap node y
+ // coordinates to 0..vbH so the resulting viewBox is the layer's
+ // actual aspect, giving uniform sx==sy at render time.
+ const lw = Number(layer.w) > 0 ? Number(layer.w) : 1;
+ const lh = Number(layer.h) > 0 ? Number(layer.h) : 1;
+ const vbH = 100 * lh / lw;
+ const yScale = vbH / 100;
+ const scaledNodes = result.nodes.map(n => ({
+ x: n.x,
+ y: n.y * yScale,
+ hIn: n.hIn ? { x: n.hIn.x, y: n.hIn.y * yScale } : null,
+ hOut: n.hOut ? { x: n.hOut.x, y: n.hOut.y * yScale } : null,
+ kind: n.kind || 'corner',
+ }));
layer.shape = {
kind: 'path',
- viewBox: { x: 0, y: 0, w: 100, h: 100 },
- pen: { closed: result.closed, tool: 'pen', nodes: result.nodes },
- d: penNodesToPathD(result.nodes, result.closed)
+ viewBox: { x: 0, y: 0, w: 100, h: vbH },
+ pen: { closed: result.closed, tool: 'pen', nodes: scaledNodes },
+ d: penNodesToPathD(scaledNodes, result.closed)
};
render();
return { ok: true };
@@ -10491,11 +10515,13 @@
}
const bbox = pointsBbox(allPts);
// Uniform scaling: divide both x and y by bbox.w so nodes land
- // in 0-100 × 0-(100*h/w) — matching the renderer's vbH formula
- // (= 100 * layer.h / layer.w). Independent x/y scaling here
- // forces a 0-100×0-100 node range into a non-square viewBox
- // later, stretching curves and varying stroke widths around
- // the path. Uniform scaling keeps strokes a constant width.
+ // in 0-100 × 0-(100*h/w). Then store shape.viewBox.h to match,
+ // so the renderer (buildShapeSvgInner path branch, sx = 100/vb.w,
+ // sy = H/vb.h) ends up with sx == sy = uniform scaling. The
+ // earlier hardcoded viewBox.h = 100 with bbox-rescaled nodes
+ // forced the renderer to stretch the path vertically into a
+ // non-square layer rectangle, varying stroke widths around
+ // curves.
const scale = bbox.w > 0 ? 100 / bbox.w : 1;
const remap = (pt) => ({ x: (pt.x - bbox.x) * scale, y: (pt.y - bbox.y) * scale });
const normNodes = nodes.map(n => ({
@@ -10503,6 +10529,7 @@
hIn: n.hIn ? remap(n.hIn) : null,
hOut: n.hOut ? remap(n.hOut) : null
}));
+ const vbH = bbox.w > 0 ? 100 * bbox.h / bbox.w : 100;
const d = penNodesToPathD(normNodes, closed);
pushHistory('draw pen path');
const fill = (closed && _penToolOpts.penFill)
@@ -10516,7 +10543,7 @@
x: bbox.x, y: bbox.y, w: bbox.w, h: bbox.h,
rotate: 0, opacity: 1, z: nextLayerZ(),
shape: {
- kind: 'path', d, viewBox: { x: 0, y: 0, w: 100, h: 100 },
+ kind: 'path', d, viewBox: { x: 0, y: 0, w: 100, h: vbH },
pen: { closed: !!closed, tool: 'pen', nodes: normNodes }
},
fill,