@pixerate/editor 0.212.0 → 0.213.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -10,13 +10,23 @@ import {
10
10
  } from "../chunk-65MDXWA7.js";
11
11
  import {
12
12
  VECTOR_TRACE_MAX_PIXELS,
13
+ bendSegment,
14
+ curvatureMoveAnchor,
15
+ cutSubpathAt,
16
+ joinSubpaths,
13
17
  loadOpenTypeFont,
14
18
  parseOpenTypeFont,
15
19
  quantizeVectorTraceColors,
20
+ removeAnchorKeepingShape,
21
+ removeAnchorsKeepingShape,
22
+ removeRedundantPoints,
23
+ smoothSubpath,
24
+ splitSubpathAt,
25
+ subpathPointAt,
16
26
  traceField,
17
27
  traceVectorImage,
18
28
  vectorTraceContours
19
- } from "../chunk-5OC22IEO.js";
29
+ } from "../chunk-DJBYJOHM.js";
20
30
  import {
21
31
  applyHomography,
22
32
  homographyFromPoints,
@@ -540,7 +550,6 @@ import {
540
550
  transformBezierPath
541
551
  } from "../chunk-WEIK3UYB.js";
542
552
  import {
543
- fitCubicWithTangents,
544
553
  fitCubics,
545
554
  parseSvgPath
546
555
  } from "../chunk-PWBZH3FK.js";
@@ -1032,8 +1041,8 @@ function vectorBlendStepCount(a, b, options, length) {
1032
1041
  if (cd > 1 / 255) return Math.min(VECTOR_BLEND_MAX_SMOOTH_STEPS, Math.max(1, Math.ceil(cd * 255 / 2)));
1033
1042
  const ba = vectorNodeBounds(a);
1034
1043
  const bb = vectorNodeBounds(b);
1035
- const dist4 = ba && bb ? Math.max(Math.abs(ba.x - bb.x), Math.abs(ba.y - bb.y), Math.abs(ba.x + ba.width - bb.x - bb.width), Math.abs(ba.y + ba.height - bb.y - bb.height)) : length;
1036
- return Math.min(VECTOR_BLEND_MAX_SMOOTH_STEPS, Math.max(1, Math.ceil(dist4 / 2)));
1044
+ const dist3 = ba && bb ? Math.max(Math.abs(ba.x - bb.x), Math.abs(ba.y - bb.y), Math.abs(ba.x + ba.width - bb.x - bb.width), Math.abs(ba.y + ba.height - bb.y - bb.height)) : length;
1045
+ return Math.min(VECTOR_BLEND_MAX_SMOOTH_STEPS, Math.max(1, Math.ceil(dist3 / 2)));
1037
1046
  }
1038
1047
  return Math.min(VECTOR_BLEND_MAX_STEPS, Math.max(1, Math.round(Number.isFinite(o.steps) ? o.steps : DEFAULT_VECTOR_BLEND_STEPS)));
1039
1048
  }
@@ -2539,7 +2548,7 @@ function vectorConstraintActions(doc, selection) {
2539
2548
  const act = (kind, refs) => ({ kind, label: VECTOR_CONSTRAINT_LABELS[kind], refs, dimensional: VECTOR_DIMENSIONAL_KINDS.has(kind) });
2540
2549
  const anchors = selection.anchors.filter((r) => located.get(r.nodeId)?.node.subpaths[r.subpath]?.anchors[r.index]);
2541
2550
  if (anchors.length) {
2542
- const neighbours2 = (a, b) => {
2551
+ const neighbours = (a, b) => {
2543
2552
  if (a.nodeId !== b.nodeId || a.subpath !== b.subpath) return null;
2544
2553
  const sp = located.get(a.nodeId).node.subpaths[a.subpath];
2545
2554
  const n2 = sp.anchors.length;
@@ -2551,14 +2560,14 @@ function vectorConstraintActions(doc, selection) {
2551
2560
  const segRef = (r) => ({ ...pointRef(r), segment: true });
2552
2561
  if (anchors.length === 1) return [act("fix", [pointRef(anchors[0])]), act("tangent", [pointRef(anchors[0])]), act("smooth", [pointRef(anchors[0])])];
2553
2562
  if (anchors.length === 2) {
2554
- const start = neighbours2(anchors[0], anchors[1]);
2563
+ const start = neighbours(anchors[0], anchors[1]);
2555
2564
  if (start) return ["horizontal", "vertical", "distance", "fix"].map((k) => act(k, [segRef(start)]));
2556
2565
  return ["coincident", "horizontal", "vertical", "distance"].map((k) => act(k, anchors.map(pointRef)));
2557
2566
  }
2558
2567
  if (anchors.length === 3) {
2559
2568
  for (let i = 0; i < 3; i++) {
2560
2569
  const others = anchors.filter((_, k) => k !== i);
2561
- const start = neighbours2(others[0], others[1]);
2570
+ const start = neighbours(others[0], others[1]);
2562
2571
  if (start) return [act("coincident", [pointRef(anchors[i]), segRef(start)]), act("distance", [pointRef(anchors[i]), segRef(start)])];
2563
2572
  }
2564
2573
  return [];
@@ -2569,8 +2578,8 @@ function vectorConstraintActions(doc, selection) {
2569
2578
  [0, 2, 1, 3],
2570
2579
  [0, 3, 1, 2]
2571
2580
  ]) {
2572
- const s1 = neighbours2(anchors[i], anchors[j]);
2573
- const s2 = neighbours2(anchors[k], anchors[l]);
2581
+ const s1 = neighbours(anchors[i], anchors[j]);
2582
+ const s2 = neighbours(anchors[k], anchors[l]);
2574
2583
  if (s1 && s2) return ["parallel", "perpendicular", "equal", "collinear", "angle"].map((kind) => act(kind, [segRef(s1), segRef(s2)]));
2575
2584
  }
2576
2585
  }
@@ -5847,402 +5856,6 @@ function solveOnSegment(seg, base, cond) {
5847
5856
  return out;
5848
5857
  }
5849
5858
 
5850
- // src/vector/pathEdit.ts
5851
- var EPS = 1e-9;
5852
- var unit2 = (x, y) => {
5853
- const l = Math.hypot(x, y);
5854
- return l > 1e-12 && Number.isFinite(l) ? [x / l, y / l] : null;
5855
- };
5856
- function cubicOf(sp, i) {
5857
- const s = subpathSegment(sp, i);
5858
- if (s.length === 4) return s;
5859
- const [a, b] = s;
5860
- return [a, [a[0] + (b[0] - a[0]) / 3, a[1] + (b[1] - a[1]) / 3], [a[0] + 2 * (b[0] - a[0]) / 3, a[1] + 2 * (b[1] - a[1]) / 3], b];
5861
- }
5862
- var isLine = (sp, i) => subpathSegment(sp, i).length === 2;
5863
- function startTangent(c) {
5864
- for (let k = 1; k < 4; k++) {
5865
- const u = unit2(c[k][0] - c[0][0], c[k][1] - c[0][1]);
5866
- if (u) return u;
5867
- }
5868
- return [1, 0];
5869
- }
5870
- function endTangent(c) {
5871
- for (let k = 2; k >= 0; k--) {
5872
- const u = unit2(c[3][0] - c[k][0], c[3][1] - c[k][1]);
5873
- if (u) return u;
5874
- }
5875
- return [1, 0];
5876
- }
5877
- function sample(c, n2, out, withStart) {
5878
- for (let i = withStart ? 0 : 1; i <= n2; i++) out.push(segmentPoint(c, i / n2));
5879
- }
5880
- var onAnchor = (a, h) => !h || Math.abs(h.x - a.x) < EPS && Math.abs(h.y - a.y) < EPS;
5881
- function tidy(a, keepType = true) {
5882
- const next = cloneAnchor(a);
5883
- if (onAnchor(next, next.in)) delete next.in;
5884
- if (onAnchor(next, next.out)) delete next.out;
5885
- const shown = inferAnchorType(next);
5886
- const type = keepType && a.type ? a.type === "symmetric" && shown !== "symmetric" ? shown === "corner" ? "corner" : "smooth" : a.type === "smooth" && shown === "corner" ? "corner" : a.type : shown;
5887
- if (type === "corner") delete next.type;
5888
- else next.type = type;
5889
- return next;
5890
- }
5891
- function neighbours(sp, i) {
5892
- const n2 = sp.anchors.length;
5893
- const wrap2 = sp.closed && n2 > 1;
5894
- return [i > 0 ? i - 1 : wrap2 ? n2 - 1 : -1, i < n2 - 1 ? i + 1 : wrap2 ? 0 : -1];
5895
- }
5896
- function goldenMin(f, a, b) {
5897
- const R = 0.618033988749895;
5898
- let x1 = b - R * (b - a);
5899
- let x2 = a + R * (b - a);
5900
- let f1 = f(x1);
5901
- let f2 = f(x2);
5902
- for (let i = 0; i < 40; i++) {
5903
- if (f1.error < f2.error || Number.isNaN(f2.error)) {
5904
- b = x2;
5905
- x2 = x1;
5906
- f2 = f1;
5907
- x1 = b - R * (b - a);
5908
- f1 = f(x1);
5909
- } else {
5910
- a = x1;
5911
- x1 = x2;
5912
- f1 = f2;
5913
- x2 = a + R * (b - a);
5914
- f2 = f(x2);
5915
- }
5916
- }
5917
- return f1.error <= f2.error ? f1 : f2;
5918
- }
5919
- function fitAcross(pts, N, t0, t1) {
5920
- const fit = (k) => {
5921
- const u = [];
5922
- for (let i = 0; i <= N; i++) u.push(k * i / N);
5923
- for (let i = 1; i <= N; i++) u.push(k + (1 - k) * i / N);
5924
- return fitCubicWithTangents(pts, u, t0, t1);
5925
- };
5926
- return goldenMin(fit, 0, 1);
5927
- }
5928
- function removeAnchorKeepingShape(sp, index) {
5929
- const anchors = sp.anchors.map(cloneAnchor);
5930
- const n2 = anchors.length;
5931
- if (index < 0 || index >= n2) return { anchors, closed: sp.closed };
5932
- if ((sp.closed || index > 0 && index < n2 - 1) && n2 >= 3) {
5933
- const pi = (index + n2 - 1) % n2;
5934
- const ni = (index + 1) % n2;
5935
- if (!(isLine(sp, pi) && isLine(sp, index))) {
5936
- const left = cubicOf(sp, pi);
5937
- const right = cubicOf(sp, index);
5938
- const N = 16;
5939
- const pts = [];
5940
- sample(left, N, pts, true);
5941
- sample(right, N, pts, false);
5942
- const t0 = startTangent(left);
5943
- const t1 = endTangent(right);
5944
- const { curve } = fitAcross(pts, N, t0, t1);
5945
- if ([curve.p1, curve.p2].every((p) => Number.isFinite(p[0]) && Number.isFinite(p[1]))) {
5946
- anchors[pi] = tidy({ ...anchors[pi], out: { x: curve.p1[0], y: curve.p1[1] } });
5947
- anchors[ni] = tidy({ ...anchors[ni], in: { x: curve.p2[0], y: curve.p2[1] } });
5948
- }
5949
- }
5950
- }
5951
- anchors.splice(index, 1);
5952
- let closed = sp.closed;
5953
- if (!closed && anchors.length) {
5954
- anchors[0] = tidy({ ...anchors[0], in: null });
5955
- const last = anchors.length - 1;
5956
- anchors[last] = tidy({ ...anchors[last], out: null });
5957
- }
5958
- if (anchors.length < 3) closed = closed && anchors.length === 2 && anchors.some((a) => a.in || a.out);
5959
- return { anchors, closed };
5960
- }
5961
- function removeAnchorsKeepingShape(sp, indices) {
5962
- const order = [...new Set(indices)].filter((i) => i >= 0 && i < sp.anchors.length).sort((a, b) => b - a);
5963
- return order.reduce((acc, i) => removeAnchorKeepingShape(acc, i), { anchors: sp.anchors.map(cloneAnchor), closed: sp.closed });
5964
- }
5965
- function bendSegment(sp, segment, t, p) {
5966
- const anchors = sp.anchors.map(cloneAnchor);
5967
- const n2 = anchors.length;
5968
- if (segment < 0 || segment >= subpathSegmentCount(sp) || !Number.isFinite(t) || !Number.isFinite(p.x) || !Number.isFinite(p.y)) return { anchors, closed: sp.closed };
5969
- const tc = Math.min(0.95, Math.max(0.05, t));
5970
- const ia = segment;
5971
- const ib = (segment + 1) % n2;
5972
- const a = anchors[ia];
5973
- const b = anchors[ib];
5974
- const k = 3 * tc * (1 - tc);
5975
- const ox = (p.x - (a.x + (b.x - a.x) * tc)) / k;
5976
- const oy = (p.y - (a.y + (b.y - a.y) * tc)) / k;
5977
- const c1 = { x: a.x + (b.x - a.x) / 3 + ox, y: a.y + (b.y - a.y) / 3 + oy };
5978
- const c2 = { x: b.x + (a.x - b.x) / 3 + ox, y: b.y + (a.y - b.y) / 3 + oy };
5979
- anchors[ia] = moveAnchorHandle(a, "out", c1);
5980
- anchors[ib] = moveAnchorHandle(b, "in", c2);
5981
- return { anchors, closed: sp.closed };
5982
- }
5983
- function subpathPointAt(sp, segment, t) {
5984
- const [x, y] = segmentPoint(subpathSegment(sp, segment), t);
5985
- return { x, y };
5986
- }
5987
- function curvatureMoveAnchor(sp, index, q) {
5988
- const anchors = sp.anchors.map(cloneAnchor);
5989
- const a = sp.anchors[index];
5990
- if (!a) return { anchors, closed: sp.closed };
5991
- const own = [
5992
- a.in && !onAnchor(a, a.in) ? { x: a.in.x - a.x, y: a.in.y - a.y } : null,
5993
- a.out && !onAnchor(a, a.out) ? { x: a.out.x - a.x, y: a.out.y - a.y } : null
5994
- ];
5995
- const ends = [null, null];
5996
- neighbours(sp, index).forEach((j, side) => {
5997
- if (j < 0 || j === index) return;
5998
- const b = anchors[j];
5999
- const old = Math.hypot(a.x - b.x, a.y - b.y);
6000
- const s = old > 1e-9 ? Math.hypot(q.x - b.x, q.y - b.y) / old : 1;
6001
- const key = side === 0 ? "out" : "in";
6002
- const h = b[key];
6003
- if (h) b[key] = { x: b.x + (h.x - b.x) * s, y: b.y + (h.y - b.y) * s };
6004
- const o = own[side];
6005
- if (o) own[side] = { x: o.x * s, y: o.y * s };
6006
- ends[side] = { x: b.x, y: b.y };
6007
- });
6008
- const moved = { x: q.x, y: q.y };
6009
- if (own[0]) moved.in = { x: q.x + own[0].x, y: q.y + own[0].y };
6010
- if (own[1]) moved.out = { x: q.x + own[1].x, y: q.y + own[1].y };
6011
- if (a.type) moved.type = a.type;
6012
- if (a.corner) moved.corner = { ...a.corner };
6013
- if (a.id) moved.id = a.id;
6014
- const [prev, next] = ends;
6015
- if (a.type && a.type !== "corner" && prev && next) {
6016
- const dx = next.x - prev.x;
6017
- const dy = next.y - prev.y;
6018
- const l = Math.hypot(dx, dy);
6019
- if (l > 1e-9) {
6020
- const len2 = (h, to) => h ? Math.hypot(h.x, h.y) : Math.hypot(q.x - to.x, q.y - to.y) / 3;
6021
- let li = len2(own[0], prev);
6022
- let lo = len2(own[1], next);
6023
- if (a.type === "symmetric") li = lo = (li + lo) / 2;
6024
- moved.in = { x: q.x - dx * li / l, y: q.y - dy * li / l };
6025
- moved.out = { x: q.x + dx * lo / l, y: q.y + dy * lo / l };
6026
- }
6027
- }
6028
- anchors[index] = moved;
6029
- return { anchors, closed: sp.closed };
6030
- }
6031
- function cutSubpathAt(sp, at) {
6032
- const n2 = sp.anchors.length;
6033
- const cuts = [...new Set(at)].filter((i) => Number.isInteger(i) && i >= 0 && i < n2 && (sp.closed || i > 0 && i < n2 - 1)).sort((a, b) => a - b);
6034
- if (!cuts.length || n2 < 2) return [{ anchors: sp.anchors.map(cloneAnchor), closed: sp.closed }];
6035
- const first = cuts[0];
6036
- const run = sp.closed ? Array.from({ length: n2 + 1 }, (_, i) => sp.anchors[(first + i) % n2]) : sp.anchors;
6037
- const cutSet = new Set(sp.closed ? [...cuts.map((c) => (c - first + n2) % n2), n2] : cuts);
6038
- const pieces = [];
6039
- let cur = [];
6040
- run.forEach((a, i) => {
6041
- if (!cutSet.has(i)) {
6042
- cur.push(cloneAnchor(a));
6043
- return;
6044
- }
6045
- if (cur.length) {
6046
- cur.push(tidy({ ...a, out: null }, false));
6047
- pieces.push({ anchors: cur, closed: false });
6048
- cur = [];
6049
- }
6050
- if (i < n2) cur.push(tidy({ ...a, in: null }, false));
6051
- });
6052
- if (cur.length > 1) pieces.push({ anchors: cur, closed: false });
6053
- return pieces;
6054
- }
6055
- function splitSubpathAt(sp, segment, t, endTolerance = 1e-6) {
6056
- const n2 = sp.anchors.length;
6057
- if (segment < 0 || segment >= subpathSegmentCount(sp)) return [{ anchors: sp.anchors.map(cloneAnchor), closed: sp.closed }];
6058
- if (t <= endTolerance) return cutSubpathAt(sp, [segment]);
6059
- if (t >= 1 - endTolerance) return cutSubpathAt(sp, [(segment + 1) % n2]);
6060
- const { subpath, index } = insertAnchor(sp, segment, t);
6061
- return cutSubpathAt(subpath, [index]);
6062
- }
6063
- var endOf = (sp, last) => sp.anchors[last ? sp.anchors.length - 1 : 0];
6064
- var dist2 = (a, b) => Math.hypot(a.x - b.x, a.y - b.y);
6065
- function reverse(sp) {
6066
- return {
6067
- closed: sp.closed,
6068
- anchors: sp.anchors.slice().reverse().map((a) => {
6069
- const next = { x: a.x, y: a.y };
6070
- if (a.out) next.in = { ...a.out };
6071
- if (a.in) next.out = { ...a.in };
6072
- if (a.type) next.type = a.type;
6073
- if (a.corner) next.corner = { ...a.corner };
6074
- if (a.id) next.id = a.id;
6075
- return next;
6076
- })
6077
- };
6078
- }
6079
- function joinSubpaths(subpaths, tolerance) {
6080
- const closed = [];
6081
- let open = [];
6082
- for (const sp of subpaths) {
6083
- const copy = { anchors: sp.anchors.map(cloneAnchor), closed: sp.closed };
6084
- if (sp.closed || sp.anchors.length < 2) {
6085
- if (sp.anchors.length) closed.push(copy);
6086
- } else open.push(copy);
6087
- }
6088
- if (open.length === 1) {
6089
- const sp = open[0];
6090
- const first = sp.anchors[0];
6091
- const last = sp.anchors[sp.anchors.length - 1];
6092
- if (sp.anchors.length > 2 && dist2(first, last) <= tolerance) {
6093
- sp.anchors.pop();
6094
- sp.anchors[0] = tidy({ ...first, in: last.in ?? null }, false);
6095
- } else {
6096
- sp.anchors[0] = tidy({ ...first, in: null }, false);
6097
- sp.anchors[sp.anchors.length - 1] = tidy({ ...sp.anchors[sp.anchors.length - 1], out: null }, false);
6098
- }
6099
- sp.closed = true;
6100
- return [...closed, sp];
6101
- }
6102
- while (open.length > 1) {
6103
- let best = { d: Infinity, i: 0, j: 1, ei: true, ej: false };
6104
- for (let i = 0; i < open.length; i++) {
6105
- for (let j = i + 1; j < open.length; j++) {
6106
- for (const ei of [false, true]) {
6107
- for (const ej of [false, true]) {
6108
- const d = dist2(endOf(open[i], ei), endOf(open[j], ej));
6109
- if (d < best.d) best = { d, i, j, ei, ej };
6110
- }
6111
- }
6112
- }
6113
- }
6114
- let a = open[best.i];
6115
- let b = open[best.j];
6116
- open = open.filter((_, k) => k !== best.j);
6117
- if (!best.ei) a = reverse(a);
6118
- if (best.ej) b = reverse(b);
6119
- const anchors = a.anchors;
6120
- if (best.d <= tolerance) {
6121
- const [head, ...rest] = b.anchors;
6122
- const l = anchors[anchors.length - 1];
6123
- anchors[anchors.length - 1] = tidy({ ...l, out: head.out ?? null }, false);
6124
- anchors.push(...rest);
6125
- } else {
6126
- const l = anchors[anchors.length - 1];
6127
- anchors[anchors.length - 1] = tidy({ ...l, out: null }, false);
6128
- anchors.push(tidy({ ...b.anchors[0], in: null }, false), ...b.anchors.slice(1));
6129
- }
6130
- open[best.i > best.j ? best.i - 1 : best.i] = { anchors, closed: false };
6131
- }
6132
- return [...closed, ...open];
6133
- }
6134
- function smoothSubpath(sp, amount = 1, only) {
6135
- const t = Math.min(1, Math.max(0, amount));
6136
- const orig = sp.anchors;
6137
- const n2 = orig.length;
6138
- const anchors = orig.map(cloneAnchor);
6139
- if (n2 < 3 || t === 0) return { anchors, closed: sp.closed };
6140
- for (let i = 0; i < n2; i++) {
6141
- if (!sp.closed && (i === 0 || i === n2 - 1)) continue;
6142
- if (only && !only.has(i)) continue;
6143
- const prev = orig[(i + n2 - 1) % n2];
6144
- const next = orig[(i + 1) % n2];
6145
- const p = orig[i];
6146
- const dir = unit2(next.x - prev.x, next.y - prev.y);
6147
- if (!dir) continue;
6148
- const li = dist2(p, prev) / 3;
6149
- const lo = dist2(p, next) / 3;
6150
- const hin = p.in ?? p;
6151
- const hout = p.out ?? p;
6152
- const target = (h, x, y) => ({ x: h.x + (x - h.x) * t, y: h.y + (y - h.y) * t });
6153
- anchors[i] = tidy(
6154
- {
6155
- x: p.x,
6156
- y: p.y,
6157
- in: target(hin, p.x - dir[0] * li, p.y - dir[1] * li),
6158
- out: target(hout, p.x + dir[0] * lo, p.y + dir[1] * lo)
6159
- },
6160
- false
6161
- );
6162
- }
6163
- return { anchors, closed: sp.closed };
6164
- }
6165
- function segsOf(sp) {
6166
- const out = [];
6167
- for (let i = 0; i < subpathSegmentCount(sp); i++) {
6168
- const s = subpathSegment(sp, i);
6169
- out.push(s.length === 2 ? { c: s, line: true } : { c: s, line: false });
6170
- }
6171
- return out;
6172
- }
6173
- function segsToSubpath(segs, closed, types) {
6174
- if (!segs.length) return null;
6175
- const anchors = [];
6176
- const at = (p) => ({ x: p[0], y: p[1] });
6177
- anchors.push(at(segs[0].c[0]));
6178
- for (const s of segs) {
6179
- const end = s.c[s.c.length - 1];
6180
- if (!s.line) anchors[anchors.length - 1].out = { x: s.c[1][0], y: s.c[1][1] };
6181
- const next = at(end);
6182
- if (!s.line) next.in = { x: s.c[2][0], y: s.c[2][1] };
6183
- anchors.push(next);
6184
- }
6185
- if (closed) {
6186
- const last = anchors.pop();
6187
- if (last.in) anchors[0].in = last.in;
6188
- }
6189
- const typed2 = anchors.map((a) => {
6190
- const type = types.get(`${a.x},${a.y}`);
6191
- return tidy(type ? { ...a, type } : a, !!type);
6192
- });
6193
- return { anchors: typed2, closed };
6194
- }
6195
- var isZeroSeg = (s) => s.c.every((p) => Math.abs(p[0] - s.c[0][0]) < 1e-9 && Math.abs(p[1] - s.c[0][1]) < 1e-9);
6196
- function mergePair(a, b, tol) {
6197
- const a0 = a.c[0];
6198
- const a3 = a.c[a.c.length - 1];
6199
- const b3 = b.c[b.c.length - 1];
6200
- if (a.line && b.line) {
6201
- const cx = b3[0] - a0[0];
6202
- const cy = b3[1] - a0[1];
6203
- const len2 = Math.hypot(cx, cy);
6204
- if (len2 < 1e-12) return null;
6205
- const mx = a3[0] - a0[0];
6206
- const my = a3[1] - a0[1];
6207
- const along = (mx * cx + my * cy) / len2;
6208
- return Math.abs((mx * cy - my * cx) / len2) <= tol && along >= 0 && along <= len2 ? { c: [a0, b3], line: true } : null;
6209
- }
6210
- const ca = a.line ? [a0, a0, a3, a3] : a.c;
6211
- const cb = b.line ? [a3, a3, b3, b3] : b.c;
6212
- const ta = endTangent(ca);
6213
- const tb = startTangent(cb);
6214
- if (ta[0] * tb[0] + ta[1] * tb[1] < 0.99) return null;
6215
- const pts = [];
6216
- sample(ca, 16, pts, true);
6217
- sample(cb, 16, pts, false);
6218
- const { curve, error } = fitAcross(pts, 16, startTangent(ca), endTangent(cb));
6219
- return error <= tol ? { c: [curve.p0, curve.p1, curve.p2, curve.p3], line: false } : null;
6220
- }
6221
- function removeRedundantPoints(sp, tolerance) {
6222
- const tol = Math.max(tolerance, 1e-9);
6223
- const types = new Map(sp.anchors.filter((a) => a.type).map((a) => [`${a.x},${a.y}`, a.type]));
6224
- const segs = segsOf(sp).filter((s) => !isZeroSeg(s));
6225
- if (!segs.length) return { anchors: sp.anchors.map(cloneAnchor), closed: sp.closed };
6226
- const minSegs = sp.closed ? 2 : 1;
6227
- let changed = true;
6228
- while (changed && segs.length > minSegs) {
6229
- changed = false;
6230
- const n2 = segs.length;
6231
- const joints = sp.closed ? n2 : n2 - 1;
6232
- for (let j = 0; j < joints; j++) {
6233
- const [i0, i1] = sp.closed ? [(j + n2 - 1) % n2, j] : [j, j + 1];
6234
- const m = mergePair(segs[i0], segs[i1], tol);
6235
- if (!m) continue;
6236
- segs[i0] = m;
6237
- segs.splice(i1, 1);
6238
- if (sp.closed && i1 === 0) segs.unshift(segs.pop());
6239
- changed = true;
6240
- break;
6241
- }
6242
- }
6243
- return segsToSubpath(segs, sp.closed, types) ?? { anchors: sp.anchors.map(cloneAnchor), closed: sp.closed };
6244
- }
6245
-
6246
5859
  // src/vector/ink.ts
6247
5860
  var LazyMouse = class {
6248
5861
  /** `length` is the string length in the units of the points (0 turns smoothing off). */
@@ -6297,7 +5910,7 @@ function vectorInkShapeName(shape) {
6297
5910
  return shape.points.length === 3 ? "triangle" : "polygon";
6298
5911
  }
6299
5912
  }
6300
- var dist3 = (a, b) => Math.hypot(b[0] - a[0], b[1] - a[1]);
5913
+ var dist2 = (a, b) => Math.hypot(b[0] - a[0], b[1] - a[1]);
6301
5914
  function bbox(pts) {
6302
5915
  if (!pts.length) return [0, 0, 0, 0];
6303
5916
  let [x0, y0] = pts[0];
@@ -6315,7 +5928,7 @@ function segDistance(a, b, p) {
6315
5928
  const dy = b[1] - a[1];
6316
5929
  const len2 = dx * dx + dy * dy;
6317
5930
  const t = len2 > 0 ? Math.min(1, Math.max(0, ((p[0] - a[0]) * dx + (p[1] - a[1]) * dy) / len2)) : 0;
6318
- return dist3([a[0] + t * dx, a[1] + t * dy], p);
5931
+ return dist2([a[0] + t * dx, a[1] + t * dy], p);
6319
5932
  }
6320
5933
  function polygonDistance(c, p) {
6321
5934
  let best = Infinity;
@@ -6377,7 +5990,7 @@ function corners(p, diag) {
6377
5990
  let far = 0;
6378
5991
  let farD = 0;
6379
5992
  p.forEach((q, i) => {
6380
- const d = dist3(start, q);
5993
+ const d = dist2(start, q);
6381
5994
  if (d > farD) {
6382
5995
  farD = d;
6383
5996
  far = i;
@@ -6404,7 +6017,7 @@ function corners(p, diag) {
6404
6017
  }
6405
6018
  let short = -1;
6406
6019
  for (let j = 0; j < n2; j++) {
6407
- if (dist3(ring[j], ring[(j + 1) % n2]) < 0.1 * diag) {
6020
+ if (dist2(ring[j], ring[(j + 1) % n2]) < 0.1 * diag) {
6408
6021
  short = j;
6409
6022
  break;
6410
6023
  }
@@ -6444,8 +6057,8 @@ function recognizeInkShape(points, options = {}) {
6444
6057
  const diag = Math.hypot(w, h);
6445
6058
  if (size < minSize) return null;
6446
6059
  let length = 0;
6447
- for (let i = 1; i < p.length; i++) length += dist3(p[i - 1], p[i]);
6448
- const gap = dist3(first, last);
6060
+ for (let i = 1; i < p.length; i++) length += dist2(p[i - 1], p[i]);
6061
+ const gap = dist2(first, last);
6449
6062
  if (gap > 0.85 * length && gap >= minSize) {
6450
6063
  const off = Math.max(...p.map((q) => segDistance(first, last, q)));
6451
6064
  if (off <= Math.max(0.06 * gap, 1.5 * minSize / 6)) return { kind: "line", a: first, b: last };
@@ -10687,7 +10300,7 @@ var VectorEditorController = class _VectorEditorController {
10687
10300
  if (input.radius !== void 0 && !(Number.isFinite(input.radius) && input.radius >= 0)) return { ok: false, message: "radius must be zero or more." };
10688
10301
  if (input.gap !== void 0 && !(Number.isFinite(input.gap) && input.gap >= 0)) return { ok: false, message: "gap must be zero or more." };
10689
10302
  const point = (e) => glued(e) ? { x: 0, y: 0 } : { x: e.x, y: e.y };
10690
- const endOf2 = (e) => glued(e) ? { nodeId: e.nodeId, ...e.side && e.side !== "auto" ? { side: e.side } : {} } : null;
10303
+ const endOf = (e) => glued(e) ? { nodeId: e.nodeId, ...e.side && e.side !== "auto" ? { side: e.side } : {} } : null;
10691
10304
  const id = this.newId();
10692
10305
  const style = this.styleFor(true);
10693
10306
  const node = {
@@ -10700,8 +10313,8 @@ var VectorEditorController = class _VectorEditorController {
10700
10313
  ...input.startArrow !== void 0 ? { startArrow: input.startArrow } : {},
10701
10314
  endArrow: input.endArrow !== void 0 ? input.endArrow : style.endArrow ?? "triangle",
10702
10315
  connector: {
10703
- start: endOf2(input.start),
10704
- end: endOf2(input.end),
10316
+ start: endOf(input.start),
10317
+ end: endOf(input.end),
10705
10318
  ...route !== "straight" ? { route } : {},
10706
10319
  ...input.radius ? { radius: input.radius } : {},
10707
10320
  ...input.gap !== void 0 ? { gap: input.gap } : {}
@@ -10787,14 +10400,14 @@ var VectorEditorController = class _VectorEditorController {
10787
10400
  }
10788
10401
  }
10789
10402
  const set = new Set(ids);
10790
- const endOf2 = (e) => e ? { nodeId: e.nodeId, ...e.side && e.side !== "auto" ? { side: e.side } : {} } : null;
10403
+ const endOf = (e) => e ? { nodeId: e.nodeId, ...e.side && e.side !== "auto" ? { side: e.side } : {} } : null;
10791
10404
  this.change("Connector", () => {
10792
10405
  this.setDoc(
10793
10406
  mapTree(this.doc.nodes, (n2) => {
10794
10407
  if (!set.has(n2.id) || n2.type !== "path") return n2;
10795
10408
  const c = { ...n2.connector ?? {} };
10796
- if (patch.start !== void 0) c.start = endOf2(patch.start);
10797
- if (patch.end !== void 0) c.end = endOf2(patch.end);
10409
+ if (patch.start !== void 0) c.start = endOf(patch.start);
10410
+ if (patch.end !== void 0) c.end = endOf(patch.end);
10798
10411
  if (patch.route !== void 0) {
10799
10412
  if (patch.route === "straight") delete c.route;
10800
10413
  else c.route = patch.route;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pixerate/editor",
3
- "version": "0.212.0",
3
+ "version": "0.213.0",
4
4
  "description": "UI-agnostic rich text and media editing core library",
5
5
  "author": "Pixerate",
6
6
  "license": "MIT",
@@ -103,6 +103,8 @@ Live per-layer effects (shadows, glows, bevel, overlays, strokes): see `referenc
103
103
  1. **Keep outlines as paths**: `make_work_path` turns the selection into the Work Path (the next
104
104
  one replaces it; `rename_path` saves it), `add_path` adds one from geometry.
105
105
  2. **Use them**: `path_to_selection` / `add_vector_mask` with `pathId`, `fill_path`, `stroke_path`.
106
+ 3. **Edit anchors** as the Pen and Direct Selection tools do: `get_path` → change anchors and handles
107
+ → `set_path_geometry` (one undo step); shapes `set_shape_component`, vector masks `set_vector_mask_path`.
106
108
 
107
109
  ### Painting
108
110