@weasel-js/stroke 1.10.1 → 1.11.1
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.
- package/dist/index.d.ts +67 -12
- package/dist/index.js +404 -280
- package/dist/index.js.map +1 -1
- package/package.json +4 -4
package/dist/index.js
CHANGED
|
@@ -1,160 +1,185 @@
|
|
|
1
1
|
import { getMarker } from './chunk-FQB7N6YD.js';
|
|
2
2
|
export { BUILTIN_MARKERS, getMarker, listMarkers, markerGeneration, markerRegistry, registerMarker } from './chunk-FQB7N6YD.js';
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
3
|
+
import { resolveCornersWithSources, remapPerAnchor, pointInPath, invert, transformPath, resolveCorners, offsetCornerTraces, restoreCorners, simplifyPath, anchorsToPath, pathToAnchors, DEFAULT_FLATTEN_TOLERANCE, boundsOfPath } from '@weasel-js/geom';
|
|
4
|
+
import { widestHalfWidth, resolveScreenLength, alignedStrokeRect, scaleVertexWidth, vertexHalfWidths } from '@weasel-js/paint';
|
|
5
|
+
import { anchorParamsBetween, trimPolyline, extractPolylines } from '@weasel-js/geom/tessellate';
|
|
6
6
|
import { trianglesToPath, pathIntersect, pathSubtract, pathUnion } from '@weasel-js/geom/booleans';
|
|
7
7
|
|
|
8
|
-
|
|
8
|
+
function runFrom(p, widths, offsets) {
|
|
9
|
+
return { pts: [p.x, p.y], a: [p.a], b: [p.b], t: [p.t], w: widths ? [p.w] : null, o: offsets ? [p.o] : null };
|
|
10
|
+
}
|
|
11
|
+
function extend(run, p) {
|
|
12
|
+
run.pts.push(p.x, p.y);
|
|
13
|
+
run.a.push(p.a);
|
|
14
|
+
run.b.push(p.b);
|
|
15
|
+
run.t.push(p.t);
|
|
16
|
+
run.w?.push(p.w);
|
|
17
|
+
run.o?.push(p.o);
|
|
18
|
+
}
|
|
19
|
+
function toPolyline(run, closed = false) {
|
|
20
|
+
const pl = {
|
|
21
|
+
points: run.pts,
|
|
22
|
+
closed,
|
|
23
|
+
anchorA: new Uint32Array(run.a),
|
|
24
|
+
anchorB: new Uint32Array(run.b),
|
|
25
|
+
anchorT: new Float32Array(run.t)
|
|
26
|
+
};
|
|
27
|
+
if (run.w) pl.widths = new Float32Array(run.w);
|
|
28
|
+
if (run.o) pl.offsets = new Float32Array(run.o);
|
|
29
|
+
return pl;
|
|
30
|
+
}
|
|
9
31
|
function splitForDash(pl, dash) {
|
|
10
32
|
const out = [];
|
|
11
|
-
const
|
|
12
|
-
const
|
|
13
|
-
const
|
|
33
|
+
const ptCount = pl.points.length / 2;
|
|
34
|
+
const plA = pl.anchorA ?? new Uint32Array(ptCount);
|
|
35
|
+
const plB = pl.anchorB ?? new Uint32Array(ptCount);
|
|
36
|
+
const plT = pl.anchorT ?? new Float32Array(ptCount);
|
|
37
|
+
const W = pl.widths;
|
|
38
|
+
const O = pl.offsets;
|
|
39
|
+
const pointAt = (i) => ({
|
|
40
|
+
x: pl.points[i * 2],
|
|
41
|
+
y: pl.points[i * 2 + 1],
|
|
42
|
+
a: plA[i],
|
|
43
|
+
b: plB[i],
|
|
44
|
+
t: plT[i],
|
|
45
|
+
w: W ? W[i] : 0,
|
|
46
|
+
o: O ? O[i] : 0
|
|
47
|
+
});
|
|
14
48
|
let dashIdx = 0;
|
|
15
49
|
let dashRemaining = dash[0];
|
|
16
50
|
let onPhase = true;
|
|
17
|
-
let
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
let curT = onPhase ? [plT[0]] : null;
|
|
21
|
-
const flushAndAdvance = () => {
|
|
22
|
-
if (curPts && curPts.length >= 4) {
|
|
23
|
-
out.push({
|
|
24
|
-
points: curPts,
|
|
25
|
-
closed: false,
|
|
26
|
-
anchorA: new Uint32Array(curA),
|
|
27
|
-
anchorB: new Uint32Array(curB),
|
|
28
|
-
anchorT: new Float32Array(curT)
|
|
29
|
-
});
|
|
30
|
-
}
|
|
31
|
-
curPts = null;
|
|
32
|
-
curA = null;
|
|
33
|
-
curB = null;
|
|
34
|
-
curT = null;
|
|
51
|
+
let cur = runFrom(pointAt(0), !!W, !!O);
|
|
52
|
+
const flushAndAdvance = (at) => {
|
|
53
|
+
if (cur && cur.pts.length >= 4) out.push(toPolyline(cur));
|
|
35
54
|
dashIdx = (dashIdx + 1) % dash.length;
|
|
36
55
|
dashRemaining = dash[dashIdx];
|
|
37
56
|
onPhase = !onPhase;
|
|
57
|
+
cur = onPhase ? runFrom(at, !!W, !!O) : null;
|
|
38
58
|
};
|
|
39
59
|
let prevX = pl.points[0], prevY = pl.points[1];
|
|
40
|
-
const ptCount = pl.points.length / 2;
|
|
41
60
|
const segCount = pl.closed ? ptCount : ptCount - 1;
|
|
42
61
|
for (let i = 0; i < segCount; i++) {
|
|
43
|
-
const
|
|
44
|
-
const
|
|
45
|
-
const
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
62
|
+
const start = pointAt(i);
|
|
63
|
+
const end = pointAt((i + 1) % ptCount);
|
|
64
|
+
const segFullLen = Math.hypot(end.x - prevX, end.y - prevY);
|
|
65
|
+
if (segFullLen <= 1e-9) {
|
|
66
|
+
if (onPhase && cur) extend(cur, end);
|
|
67
|
+
continue;
|
|
68
|
+
}
|
|
50
69
|
let segLen = segFullLen;
|
|
51
70
|
let traveled = 0;
|
|
52
71
|
while (segLen > 1e-9) {
|
|
53
72
|
if (segLen <= dashRemaining) {
|
|
54
|
-
if (onPhase &&
|
|
55
|
-
curPts.push(cx, cy);
|
|
56
|
-
curA.push(endA);
|
|
57
|
-
curB.push(endB);
|
|
58
|
-
curT.push(endT);
|
|
59
|
-
}
|
|
73
|
+
if (onPhase && cur) extend(cur, end);
|
|
60
74
|
dashRemaining -= segLen;
|
|
61
|
-
prevX =
|
|
62
|
-
prevY =
|
|
63
|
-
traveled = segFullLen;
|
|
75
|
+
prevX = end.x;
|
|
76
|
+
prevY = end.y;
|
|
64
77
|
segLen = 0;
|
|
65
|
-
if (dashRemaining <= 1e-9)
|
|
66
|
-
flushAndAdvance();
|
|
67
|
-
if (onPhase) {
|
|
68
|
-
curPts = [prevX, prevY];
|
|
69
|
-
curA = [endA];
|
|
70
|
-
curB = [endB];
|
|
71
|
-
curT = [endT];
|
|
72
|
-
}
|
|
73
|
-
}
|
|
78
|
+
if (dashRemaining <= 1e-9) flushAndAdvance(end);
|
|
74
79
|
} else {
|
|
75
|
-
const tConsume = dashRemaining / segLen;
|
|
76
|
-
const ix = prevX + segDx * tConsume;
|
|
77
|
-
const iy = prevY + segDy * tConsume;
|
|
78
80
|
traveled += dashRemaining;
|
|
79
81
|
const frac = traveled / segFullLen;
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
}
|
|
96
|
-
if (onPhase && curPts) {
|
|
97
|
-
curPts.push(ix, iy);
|
|
98
|
-
curA.push(mA);
|
|
99
|
-
curB.push(mB);
|
|
100
|
-
curT.push(mT);
|
|
101
|
-
}
|
|
102
|
-
prevX = ix;
|
|
103
|
-
prevY = iy;
|
|
104
|
-
segDx = cx - prevX;
|
|
105
|
-
segDy = cy - prevY;
|
|
106
|
-
segLen = Math.hypot(segDx, segDy);
|
|
107
|
-
flushAndAdvance();
|
|
108
|
-
if (onPhase) {
|
|
109
|
-
curPts = [prevX, prevY];
|
|
110
|
-
curA = [mA];
|
|
111
|
-
curB = [mB];
|
|
112
|
-
curT = [mT];
|
|
113
|
-
}
|
|
82
|
+
const [a, b, t] = anchorParamsBetween(start.a, start.b, start.t, end.a, end.b, end.t, frac);
|
|
83
|
+
const mid = {
|
|
84
|
+
x: start.x + (end.x - start.x) * frac,
|
|
85
|
+
y: start.y + (end.y - start.y) * frac,
|
|
86
|
+
a,
|
|
87
|
+
b,
|
|
88
|
+
t,
|
|
89
|
+
w: start.w + (end.w - start.w) * frac,
|
|
90
|
+
o: start.o + (end.o - start.o) * frac
|
|
91
|
+
};
|
|
92
|
+
if (onPhase && cur) extend(cur, mid);
|
|
93
|
+
prevX = mid.x;
|
|
94
|
+
prevY = mid.y;
|
|
95
|
+
segLen = Math.hypot(end.x - prevX, end.y - prevY);
|
|
96
|
+
flushAndAdvance(mid);
|
|
114
97
|
}
|
|
115
98
|
}
|
|
116
99
|
}
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
points: curPts,
|
|
120
|
-
closed: false,
|
|
121
|
-
anchorA: new Uint32Array(curA),
|
|
122
|
-
anchorB: new Uint32Array(curB),
|
|
123
|
-
anchorT: new Float32Array(curT)
|
|
124
|
-
};
|
|
125
|
-
const tx = curPts[curPts.length - 2], ty = curPts[curPts.length - 1];
|
|
126
|
-
const meets = (x, y) => Math.hypot(tx - x, ty - y) <= 1e-9;
|
|
127
|
-
if (pl.closed && out.length > 0 && meets(out[0].points[0], out[0].points[1])) {
|
|
128
|
-
out[0] = joinAtSeam(tail, out[0]);
|
|
129
|
-
} else if (pl.closed && out.length === 0 && meets(curPts[0], curPts[1])) {
|
|
130
|
-
out.push({
|
|
131
|
-
points: curPts.slice(0, -2),
|
|
132
|
-
closed: true,
|
|
133
|
-
anchorA: tail.anchorA.slice(0, -1),
|
|
134
|
-
anchorB: tail.anchorB.slice(0, -1),
|
|
135
|
-
anchorT: tail.anchorT.slice(0, -1)
|
|
136
|
-
});
|
|
137
|
-
} else {
|
|
138
|
-
out.push(tail);
|
|
139
|
-
}
|
|
140
|
-
}
|
|
100
|
+
const tail = cur;
|
|
101
|
+
if (tail && tail.pts.length >= 4) closeOut(pl, tail, out);
|
|
141
102
|
return out;
|
|
142
103
|
}
|
|
104
|
+
function closeOut(pl, tail, out) {
|
|
105
|
+
const tx = tail.pts[tail.pts.length - 2], ty = tail.pts[tail.pts.length - 1];
|
|
106
|
+
const meets = (x, y) => Math.hypot(tx - x, ty - y) <= 1e-9;
|
|
107
|
+
if (pl.closed && out.length > 0 && meets(out[0].points[0], out[0].points[1])) {
|
|
108
|
+
out[0] = joinAtSeam(toPolyline(tail), out[0]);
|
|
109
|
+
} else if (pl.closed && out.length === 0 && meets(tail.pts[0], tail.pts[1])) {
|
|
110
|
+
out.push(toPolyline({
|
|
111
|
+
pts: tail.pts.slice(0, -2),
|
|
112
|
+
a: tail.a.slice(0, -1),
|
|
113
|
+
b: tail.b.slice(0, -1),
|
|
114
|
+
t: tail.t.slice(0, -1),
|
|
115
|
+
w: tail.w ? tail.w.slice(0, -1) : null,
|
|
116
|
+
o: tail.o ? tail.o.slice(0, -1) : null
|
|
117
|
+
}, true));
|
|
118
|
+
} else {
|
|
119
|
+
out.push(toPolyline(tail));
|
|
120
|
+
}
|
|
121
|
+
}
|
|
143
122
|
function joinAtSeam(tail, head) {
|
|
144
|
-
|
|
123
|
+
const pl = {
|
|
145
124
|
points: [...tail.points, ...head.points.slice(2)],
|
|
146
125
|
closed: false,
|
|
147
126
|
anchorA: Uint32Array.from([...tail.anchorA, ...head.anchorA.subarray(1)]),
|
|
148
127
|
anchorB: Uint32Array.from([...tail.anchorB, ...head.anchorB.subarray(1)]),
|
|
149
128
|
anchorT: Float32Array.from([...tail.anchorT, ...head.anchorT.subarray(1)])
|
|
150
129
|
};
|
|
130
|
+
if (tail.widths && head.widths) pl.widths = Float32Array.from([...tail.widths, ...head.widths.subarray(1)]);
|
|
131
|
+
if (tail.offsets && head.offsets) pl.offsets = Float32Array.from([...tail.offsets, ...head.offsets.subarray(1)]);
|
|
132
|
+
return pl;
|
|
151
133
|
}
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
return
|
|
134
|
+
|
|
135
|
+
// src/tessellate/nib.ts
|
|
136
|
+
function nibWidthScale(nib) {
|
|
137
|
+
const ex = Math.cos(nib.angle), ey = -Math.sin(nib.angle);
|
|
138
|
+
const r = Math.min(1, Math.max(0, nib.ratio));
|
|
139
|
+
return (tx, ty) => {
|
|
140
|
+
const across = ex * ty - ey * tx;
|
|
141
|
+
const along = ex * tx + ey * ty;
|
|
142
|
+
return Math.sqrt(across * across + r * r * along * along);
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
function applyNib(pl, nib, base) {
|
|
146
|
+
const n = pl.points.length / 2;
|
|
147
|
+
if (n < 2) return;
|
|
148
|
+
const P = pl.points;
|
|
149
|
+
const scale = nibWidthScale(nib);
|
|
150
|
+
const segCount = pl.closed ? n : n - 1;
|
|
151
|
+
const factors = [];
|
|
152
|
+
for (let i = 0; i < segCount; i++) {
|
|
153
|
+
const j = (i + 1) % n;
|
|
154
|
+
const dx = P[j * 2] - P[i * 2], dy = P[j * 2 + 1] - P[i * 2 + 1];
|
|
155
|
+
const len = Math.hypot(dx, dy);
|
|
156
|
+
factors.push(len > 0 ? scale(dx / len, dy / len) : NaN);
|
|
157
|
+
}
|
|
158
|
+
for (let i = 0; i < segCount; i++) {
|
|
159
|
+
if (Number.isNaN(factors[i])) factors[i] = i > 0 ? factors[i - 1] : factors.find((f) => !Number.isNaN(f)) ?? 1;
|
|
160
|
+
}
|
|
161
|
+
const A = pl.anchorA, B = pl.anchorB, T = pl.anchorT, W = pl.widths, O = pl.offsets;
|
|
162
|
+
const pts = [], aA = [], aB = [], aT = [], ws = [], os = [];
|
|
163
|
+
const push = (i, factor) => {
|
|
164
|
+
pts.push(P[i * 2], P[i * 2 + 1]);
|
|
165
|
+
aA.push(A ? A[i] : 0);
|
|
166
|
+
aB.push(B ? B[i] : 0);
|
|
167
|
+
aT.push(T ? T[i] : 0);
|
|
168
|
+
ws.push((W ? W[i] : base) * factor);
|
|
169
|
+
if (O) os.push(O[i] * factor);
|
|
170
|
+
};
|
|
171
|
+
push(0, factors[0]);
|
|
172
|
+
for (let i = 1; i < n; i++) {
|
|
173
|
+
push(i, factors[i - 1]);
|
|
174
|
+
if (i < segCount) push(i, factors[i]);
|
|
175
|
+
}
|
|
176
|
+
if (pl.closed) push(0, factors[segCount - 1]);
|
|
177
|
+
pl.points = pts;
|
|
178
|
+
pl.anchorA = Uint32Array.from(aA);
|
|
179
|
+
pl.anchorB = Uint32Array.from(aB);
|
|
180
|
+
pl.anchorT = Float32Array.from(aT);
|
|
181
|
+
pl.widths = Float32Array.from(ws);
|
|
182
|
+
if (O) pl.offsets = Float32Array.from(os);
|
|
158
183
|
}
|
|
159
184
|
|
|
160
185
|
// src/tessellate/vertexWidths.ts
|
|
@@ -165,23 +190,36 @@ function populatePolylineWidths(pl, vertexWidths, fallbackWidth, scale) {
|
|
|
165
190
|
const aB = pl.anchorB ?? new Uint32Array(ptCount);
|
|
166
191
|
const aT = pl.anchorT ?? new Float32Array(ptCount);
|
|
167
192
|
const widths = new Float32Array(ptCount);
|
|
193
|
+
const offsets = new Float32Array(ptCount);
|
|
194
|
+
let sided = false;
|
|
195
|
+
const fallback = fallbackWidth / 2;
|
|
168
196
|
let k = 1;
|
|
169
|
-
const
|
|
170
|
-
const
|
|
171
|
-
return
|
|
197
|
+
const sides = (i) => {
|
|
198
|
+
const h = vertexHalfWidths(vertexWidths[i], NaN);
|
|
199
|
+
return [Number.isNaN(h.left) ? fallback : h.left * k, Number.isNaN(h.right) ? fallback : h.right * k];
|
|
172
200
|
};
|
|
173
201
|
for (let i = 0; i < ptCount; i++) {
|
|
174
202
|
if (scale) k = scaleAt(pl, i, scale);
|
|
175
203
|
const a = aA[i], b = aB[i], t = aT[i];
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
widths[i] = wa + (wb - wa) * t;
|
|
204
|
+
let [left, right] = sides(a);
|
|
205
|
+
if (a !== b) {
|
|
206
|
+
const [lb, rb] = sides(b);
|
|
207
|
+
left += (lb - left) * t;
|
|
208
|
+
right += (rb - right) * t;
|
|
182
209
|
}
|
|
210
|
+
widths[i] = left + right;
|
|
211
|
+
offsets[i] = (right - left) / 2;
|
|
212
|
+
if (offsets[i] !== 0) sided = true;
|
|
183
213
|
}
|
|
184
214
|
pl.widths = widths;
|
|
215
|
+
if (sided) pl.offsets = offsets;
|
|
216
|
+
}
|
|
217
|
+
function widestStrokeWidth(width, vertexWidths) {
|
|
218
|
+
return 2 * widestHalfWidth(width, vertexWidths);
|
|
219
|
+
}
|
|
220
|
+
function populateWidths(pl, vertexWidths, nib, width, scale) {
|
|
221
|
+
if (vertexWidths) populatePolylineWidths(pl, vertexWidths, width, scale);
|
|
222
|
+
if (nib) applyNib(pl, nib, width);
|
|
185
223
|
}
|
|
186
224
|
function scaleAt(pl, i, scale) {
|
|
187
225
|
const pts = pl.points;
|
|
@@ -203,6 +241,14 @@ function scaleAt(pl, i, scale) {
|
|
|
203
241
|
return len > 0 ? scale(tx / len, ty / len) : 1;
|
|
204
242
|
}
|
|
205
243
|
|
|
244
|
+
// src/tessellate/flattening.ts
|
|
245
|
+
var STROKE_MAX_TURN = Math.PI / 18;
|
|
246
|
+
function strokeFlattening(flattenTolerance, width, vertexWidths) {
|
|
247
|
+
const tolerance = flattenTolerance ?? DEFAULT_FLATTEN_TOLERANCE;
|
|
248
|
+
const widest = widestStrokeWidth(width, vertexWidths);
|
|
249
|
+
return widest / 2 > tolerance ? { flattenTolerance, maxTurn: STROKE_MAX_TURN } : { flattenTolerance };
|
|
250
|
+
}
|
|
251
|
+
|
|
206
252
|
// src/tessellate/metric.ts
|
|
207
253
|
function screenSpaceKey(space) {
|
|
208
254
|
return space === void 0 ? "" : `${metricKey(space.metric)}@${space.pxPerUnit}`;
|
|
@@ -297,143 +343,16 @@ function trimInSpace(pl, from, ribbon, start, end) {
|
|
|
297
343
|
return cur;
|
|
298
344
|
}
|
|
299
345
|
|
|
300
|
-
// src/tessellate/
|
|
301
|
-
var EMPTY_MESH = {
|
|
302
|
-
vertices: new Float32Array(0),
|
|
303
|
-
indices: new Uint32Array(0),
|
|
304
|
-
anchorA: new Uint32Array(0),
|
|
305
|
-
anchorB: new Uint32Array(0),
|
|
306
|
-
anchorT: new Float32Array(0)
|
|
307
|
-
};
|
|
308
|
-
function resolveStrokeWidth(width, scale) {
|
|
309
|
-
return resolveScreenLength(width, scale);
|
|
310
|
-
}
|
|
311
|
-
function tessellateStroke(path, stroke, opts = {}) {
|
|
312
|
-
const width = resolveStrokeWidth(stroke.width ?? 1, 1);
|
|
313
|
-
if (width <= 0) return EMPTY_MESH;
|
|
314
|
-
const join = stroke.join ?? "miter";
|
|
315
|
-
const cap = stroke.cap ?? "butt";
|
|
316
|
-
const align = stroke.align ?? "center";
|
|
317
|
-
const miterLimit = stroke.miterLimit ?? DEFAULT_MITER_LIMIT;
|
|
318
|
-
const varyingThreshold = stroke.varyingWidthJoinThreshold ?? DEFAULT_VARYING_WIDTH_JOIN_THRESHOLD;
|
|
319
|
-
const metric = opts.metric;
|
|
320
|
-
const rectInMetric = metric && path.kind === "rect" ? rectIntoMetric(path, metric) : null;
|
|
321
|
-
let workingPath = rectInMetric ?? path;
|
|
322
|
-
if (workingPath.kind === "rect" && align !== "center") {
|
|
323
|
-
workingPath = { kind: "rect", ...alignedStrokeRect(workingPath, align, width) };
|
|
324
|
-
}
|
|
325
|
-
const vertexWidths = stroke.vertexWidths && stroke.vertexWidths.length > 0 ? stroke.vertexWidths : null;
|
|
326
|
-
const polylines = extractPolylines(workingPath, strokeFlattening(opts.flattenTolerance, width, vertexWidths ?? void 0));
|
|
327
|
-
const scale = !metric ? void 0 : rectInMetric ? (tx, ty) => worldWidthInMetric(metric, tx, ty) : (ux, uy) => worldWidthInMetricAlongWorld(metric, ux, uy);
|
|
328
|
-
const dash = stroke.dash ?? [];
|
|
329
|
-
const verts = [];
|
|
330
|
-
const idx = [];
|
|
331
|
-
const anchorA = [];
|
|
332
|
-
const anchorB = [];
|
|
333
|
-
const anchorT = [];
|
|
334
|
-
const ribbonSpace = metric ?? null;
|
|
335
|
-
for (const pl of polylines) {
|
|
336
|
-
if (vertexWidths) populatePolylineWidths(pl, vertexWidths, width, scale);
|
|
337
|
-
const trimmed = trimInSpace(pl, rectInMetric ? ribbonSpace : null, ribbonSpace, opts.startInset, opts.endInset);
|
|
338
|
-
if (trimmed === null) continue;
|
|
339
|
-
const subs = dash.length > 0 ? splitForDash(trimmed, dash) : [trimmed];
|
|
340
|
-
for (const sub of subs) {
|
|
341
|
-
expandPolyline(sub, width, join, cap, miterLimit, varyingThreshold, verts, idx, anchorA, anchorB, anchorT);
|
|
342
|
-
}
|
|
343
|
-
}
|
|
344
|
-
const vertices = new Float32Array(verts);
|
|
345
|
-
if (metric) coordsOutOfMetric(vertices, metric);
|
|
346
|
-
return {
|
|
347
|
-
vertices,
|
|
348
|
-
indices: new Uint32Array(idx),
|
|
349
|
-
anchorA: new Uint32Array(anchorA),
|
|
350
|
-
anchorB: new Uint32Array(anchorB),
|
|
351
|
-
anchorT: new Float32Array(anchorT)
|
|
352
|
-
};
|
|
353
|
-
}
|
|
354
|
-
function expandPolyline(pl, width, join, cap, miterLimit, varyingThreshold, verts, idx, anchorA, anchorB, anchorT) {
|
|
355
|
-
const half = width / 2;
|
|
356
|
-
const pts = pl.points;
|
|
357
|
-
const ptCount = pts.length / 2;
|
|
358
|
-
const segCount = ptCount - 1;
|
|
359
|
-
if (segCount < 1) return;
|
|
360
|
-
const plA = pl.anchorA ?? new Uint32Array(ptCount);
|
|
361
|
-
const plB = pl.anchorB ?? new Uint32Array(ptCount);
|
|
362
|
-
const plT = pl.anchorT ?? new Float32Array(ptCount);
|
|
363
|
-
const plHalf = pl.widths ? pl.widths.map((w) => w / 2) : null;
|
|
364
|
-
const segs = [];
|
|
365
|
-
const segSrcIdx = [];
|
|
366
|
-
for (let s = 0; s < segCount; s++) {
|
|
367
|
-
const ha = plHalf ? plHalf[s] : half;
|
|
368
|
-
const hb = plHalf ? plHalf[s + 1] : half;
|
|
369
|
-
const seg = makeSeg(pts[s * 2], pts[s * 2 + 1], pts[(s + 1) * 2], pts[(s + 1) * 2 + 1], ha, hb);
|
|
370
|
-
if (seg) {
|
|
371
|
-
segs.push(seg);
|
|
372
|
-
segSrcIdx.push(s);
|
|
373
|
-
}
|
|
374
|
-
}
|
|
375
|
-
let closerSrcIdx = -1;
|
|
376
|
-
if (pl.closed && segs.length >= 1) {
|
|
377
|
-
const last = segs[segs.length - 1];
|
|
378
|
-
const first = segs[0];
|
|
379
|
-
const haCloser = plHalf ? plHalf[ptCount - 1] : half;
|
|
380
|
-
const hbCloser = plHalf ? plHalf[0] : half;
|
|
381
|
-
const closer = makeSeg(last.bx, last.by, first.ax, first.ay, haCloser, hbCloser);
|
|
382
|
-
if (closer) {
|
|
383
|
-
segs.push(closer);
|
|
384
|
-
closerSrcIdx = ptCount - 1;
|
|
385
|
-
segSrcIdx.push(closerSrcIdx);
|
|
386
|
-
}
|
|
387
|
-
}
|
|
388
|
-
if (segs.length === 0) return;
|
|
389
|
-
const segBaseIdx = [];
|
|
390
|
-
for (let s = 0; s < segs.length; s++) {
|
|
391
|
-
const seg = segs[s];
|
|
392
|
-
const startSrc = segSrcIdx[s];
|
|
393
|
-
const endSrc = s === segs.length - 1 && pl.closed && closerSrcIdx >= 0 ? 0 : startSrc + 1;
|
|
394
|
-
const base = verts.length / 2;
|
|
395
|
-
segBaseIdx.push(base);
|
|
396
|
-
const nxA = seg.nxUnit * seg.halfA, nyA = seg.nyUnit * seg.halfA;
|
|
397
|
-
const nxB = seg.nxUnit * seg.halfB, nyB = seg.nyUnit * seg.halfB;
|
|
398
|
-
verts.push(seg.ax + nxA, seg.ay + nyA);
|
|
399
|
-
anchorA.push(plA[startSrc]);
|
|
400
|
-
anchorB.push(plB[startSrc]);
|
|
401
|
-
anchorT.push(plT[startSrc]);
|
|
402
|
-
verts.push(seg.ax - nxA, seg.ay - nyA);
|
|
403
|
-
anchorA.push(plA[startSrc]);
|
|
404
|
-
anchorB.push(plB[startSrc]);
|
|
405
|
-
anchorT.push(plT[startSrc]);
|
|
406
|
-
verts.push(seg.bx + nxB, seg.by + nyB);
|
|
407
|
-
anchorA.push(plA[endSrc]);
|
|
408
|
-
anchorB.push(plB[endSrc]);
|
|
409
|
-
anchorT.push(plT[endSrc]);
|
|
410
|
-
verts.push(seg.bx - nxB, seg.by - nyB);
|
|
411
|
-
anchorA.push(plA[endSrc]);
|
|
412
|
-
anchorB.push(plB[endSrc]);
|
|
413
|
-
anchorT.push(plT[endSrc]);
|
|
414
|
-
idx.push(base, base + 1, base + 2, base + 1, base + 3, base + 2);
|
|
415
|
-
}
|
|
416
|
-
const joinCount = pl.closed ? segs.length : segs.length - 1;
|
|
417
|
-
for (let j = 0; j < joinCount; j++) {
|
|
418
|
-
emitJoin(segs, segBaseIdx, j, join, miterLimit, varyingThreshold, verts, idx, anchorA, anchorB, anchorT, segSrcIdx, plA, plB, plT);
|
|
419
|
-
}
|
|
420
|
-
if (!pl.closed && cap !== "butt") {
|
|
421
|
-
const first = segs[0];
|
|
422
|
-
const firstBase = segBaseIdx[0];
|
|
423
|
-
emitCap(first, firstBase + 0, firstBase + 1, false, first.halfA, cap, verts, idx, anchorA, anchorB, anchorT, plA[0], plB[0], plT[0]);
|
|
424
|
-
const last = segs[segs.length - 1];
|
|
425
|
-
const lastBase = segBaseIdx[segs.length - 1];
|
|
426
|
-
const lastSrc = segSrcIdx[segs.length - 1] + 1;
|
|
427
|
-
emitCap(last, lastBase + 2, lastBase + 3, true, last.halfB, cap, verts, idx, anchorA, anchorB, anchorT, plA[lastSrc], plB[lastSrc], plT[lastSrc]);
|
|
428
|
-
}
|
|
429
|
-
}
|
|
346
|
+
// src/tessellate/ribbon.ts
|
|
430
347
|
var ROUND_CAP_STEP_RAD = 10 * Math.PI / 180;
|
|
431
|
-
function emitCap(seg, leftIdx, rightIdx, atEnd,
|
|
348
|
+
function emitCap(seg, leftIdx, rightIdx, atEnd, pos, neg, cap, verts, idx, anchorA, anchorB, anchorT, endA, endB, endT) {
|
|
432
349
|
const sign = atEnd ? 1 : -1;
|
|
433
350
|
const dx = (seg.bx - seg.ax) / seg.len * sign;
|
|
434
351
|
const dy = (seg.by - seg.ay) / seg.len * sign;
|
|
435
|
-
const
|
|
436
|
-
const
|
|
352
|
+
const half = (pos + neg) / 2;
|
|
353
|
+
const shift = (pos - neg) / 2;
|
|
354
|
+
const cx = (atEnd ? seg.bx : seg.ax) + seg.nxUnit * shift;
|
|
355
|
+
const cy = (atEnd ? seg.by : seg.ay) + seg.nyUnit * shift;
|
|
437
356
|
const nx = seg.nxUnit * half;
|
|
438
357
|
const ny = seg.nyUnit * half;
|
|
439
358
|
if (cap === "square") {
|
|
@@ -484,7 +403,7 @@ function emitCap(seg, leftIdx, rightIdx, atEnd, half, cap, verts, idx, anchorA,
|
|
|
484
403
|
else idx.push(prevIdx, pivotIdx, rightIdx);
|
|
485
404
|
}
|
|
486
405
|
}
|
|
487
|
-
function makeSeg(ax, ay, bx, by,
|
|
406
|
+
function makeSeg(ax, ay, bx, by, posA, negA, posB, negB) {
|
|
488
407
|
const dx = bx - ax, dy = by - ay;
|
|
489
408
|
const len = Math.hypot(dx, dy);
|
|
490
409
|
if (len === 0) return null;
|
|
@@ -495,13 +414,13 @@ function makeSeg(ax, ay, bx, by, halfA, halfB) {
|
|
|
495
414
|
by,
|
|
496
415
|
nxUnit: -dy / len,
|
|
497
416
|
nyUnit: dx / len,
|
|
498
|
-
|
|
499
|
-
|
|
417
|
+
posA,
|
|
418
|
+
negA,
|
|
419
|
+
posB,
|
|
420
|
+
negB,
|
|
500
421
|
len
|
|
501
422
|
};
|
|
502
423
|
}
|
|
503
|
-
var DEFAULT_MITER_LIMIT = 4;
|
|
504
|
-
var DEFAULT_VARYING_WIDTH_JOIN_THRESHOLD = 1.5;
|
|
505
424
|
function emitJoin(segs, segBaseIdx, j, join, miterLimit, varyingThreshold, verts, idx, anchorA, anchorB, anchorT, segSrcIdx, plA, plB, plT) {
|
|
506
425
|
const a = segs[j];
|
|
507
426
|
const b = segs[(j + 1) % segs.length];
|
|
@@ -519,9 +438,11 @@ function emitJoin(segs, segBaseIdx, j, join, miterLimit, varyingThreshold, verts
|
|
|
519
438
|
const cornerA = plA[cornerSrc];
|
|
520
439
|
const cornerB = plB[cornerSrc];
|
|
521
440
|
const cornerT = plT[cornerSrc];
|
|
522
|
-
const
|
|
523
|
-
const
|
|
524
|
-
const
|
|
441
|
+
const outerA = onPositive ? [a.negA, a.negB] : [a.posA, a.posB];
|
|
442
|
+
const outerB = onPositive ? [b.negA, b.negB] : [b.posA, b.posB];
|
|
443
|
+
const halfAtCorner = outerA[1];
|
|
444
|
+
const taperA = Math.max(...outerA) / Math.max(1e-9, Math.min(...outerA));
|
|
445
|
+
const taperB = Math.max(...outerB) / Math.max(1e-9, Math.min(...outerB));
|
|
525
446
|
const forceBevelForVarying = taperA > varyingThreshold || taperB > varyingThreshold;
|
|
526
447
|
if (join === "bevel" || forceBevelForVarying) {
|
|
527
448
|
emitBevel(a, aOuterEnd, bOuterStart, onPositive, verts, idx, anchorA, anchorB, anchorT, cornerA, cornerB, cornerT);
|
|
@@ -576,7 +497,7 @@ function emitRoundJoin(a, aOuterEnd, bOuterStart, onPositive, verts, idx, anchor
|
|
|
576
497
|
const startY = verts[aOuterEnd * 2 + 1] - cy;
|
|
577
498
|
const endX = verts[bOuterStart * 2] - cx;
|
|
578
499
|
const endY = verts[bOuterStart * 2 + 1] - cy;
|
|
579
|
-
const
|
|
500
|
+
const r0 = Math.hypot(startX, startY), r1 = Math.hypot(endX, endY);
|
|
580
501
|
const startAngle = Math.atan2(startY, startX);
|
|
581
502
|
const endAngle = Math.atan2(endY, endX);
|
|
582
503
|
let sweep = endAngle - startAngle;
|
|
@@ -587,10 +508,9 @@ function emitRoundJoin(a, aOuterEnd, bOuterStart, onPositive, verts, idx, anchor
|
|
|
587
508
|
let prevIdx = aOuterEnd;
|
|
588
509
|
for (let i = 1; i < steps; i++) {
|
|
589
510
|
const ang = startAngle + i * stepAngle;
|
|
590
|
-
const
|
|
591
|
-
|
|
592
|
-
const newIdx = verts.length / 2;
|
|
593
|
-
verts.push(fx, fy);
|
|
511
|
+
const r = r0 + (r1 - r0) * i / steps;
|
|
512
|
+
verts.push(cx + Math.cos(ang) * r, cy + Math.sin(ang) * r);
|
|
513
|
+
const newIdx = verts.length / 2 - 1;
|
|
594
514
|
anchorA.push(cornerA);
|
|
595
515
|
anchorB.push(cornerB);
|
|
596
516
|
anchorT.push(cornerT);
|
|
@@ -616,6 +536,139 @@ function lineLineIntersect(apx, apy, adx, ady, bpx, bpy, bdx, bdy) {
|
|
|
616
536
|
const t = ((bpx - apx) * bdy - (bpy - apy) * bdx) / denom;
|
|
617
537
|
return [apx + t * adx, apy + t * ady];
|
|
618
538
|
}
|
|
539
|
+
|
|
540
|
+
// src/tessellate/stroke.ts
|
|
541
|
+
var EMPTY_MESH = {
|
|
542
|
+
vertices: new Float32Array(0),
|
|
543
|
+
indices: new Uint32Array(0),
|
|
544
|
+
anchorA: new Uint32Array(0),
|
|
545
|
+
anchorB: new Uint32Array(0),
|
|
546
|
+
anchorT: new Float32Array(0)
|
|
547
|
+
};
|
|
548
|
+
function resolveStrokeWidth(width, scale) {
|
|
549
|
+
return resolveScreenLength(width, scale);
|
|
550
|
+
}
|
|
551
|
+
function tessellateStroke(path, stroke, opts = {}) {
|
|
552
|
+
const width = resolveStrokeWidth(stroke.width ?? 1, 1);
|
|
553
|
+
if (width <= 0) return EMPTY_MESH;
|
|
554
|
+
const join = stroke.join ?? "miter";
|
|
555
|
+
const cap = stroke.cap ?? "butt";
|
|
556
|
+
const align = stroke.align ?? "center";
|
|
557
|
+
const miterLimit = stroke.miterLimit ?? DEFAULT_MITER_LIMIT;
|
|
558
|
+
const varyingThreshold = stroke.varyingWidthJoinThreshold ?? DEFAULT_VARYING_WIDTH_JOIN_THRESHOLD;
|
|
559
|
+
const metric = opts.metric;
|
|
560
|
+
const rectInMetric = metric && path.kind === "rect" ? rectIntoMetric(path, metric) : null;
|
|
561
|
+
let workingPath = rectInMetric ?? path;
|
|
562
|
+
if (workingPath.kind === "rect" && align !== "center") {
|
|
563
|
+
workingPath = { kind: "rect", ...alignedStrokeRect(workingPath, align, width) };
|
|
564
|
+
}
|
|
565
|
+
const resolved = workingPath.kind === "polygon" ? resolveCornersWithSources(workingPath) : null;
|
|
566
|
+
if (resolved) workingPath = resolved.path;
|
|
567
|
+
const given = stroke.vertexWidths && stroke.vertexWidths.length > 0 ? stroke.vertexWidths : null;
|
|
568
|
+
const vertexWidths = given && resolved?.anchorSources ? remapPerAnchor(given, resolved.anchorSources, 1) : given;
|
|
569
|
+
const polylines = extractPolylines(workingPath, strokeFlattening(opts.flattenTolerance, width, vertexWidths ?? void 0));
|
|
570
|
+
const scale = !metric ? void 0 : rectInMetric ? (tx, ty) => worldWidthInMetric(metric, tx, ty) : (ux, uy) => worldWidthInMetricAlongWorld(metric, ux, uy);
|
|
571
|
+
const dash = stroke.dash ?? [];
|
|
572
|
+
const verts = [];
|
|
573
|
+
const idx = [];
|
|
574
|
+
const anchorA = [];
|
|
575
|
+
const anchorB = [];
|
|
576
|
+
const anchorT = [];
|
|
577
|
+
const ribbonSpace = metric ?? null;
|
|
578
|
+
for (const pl of polylines) {
|
|
579
|
+
populateWidths(pl, vertexWidths, stroke.nib, width, scale);
|
|
580
|
+
const trimmed = trimInSpace(pl, rectInMetric ? ribbonSpace : null, ribbonSpace, opts.startInset, opts.endInset);
|
|
581
|
+
if (trimmed === null) continue;
|
|
582
|
+
const subs = dash.length > 0 ? splitForDash(trimmed, dash) : [trimmed];
|
|
583
|
+
for (const sub of subs) {
|
|
584
|
+
expandPolyline(sub, width, join, cap, miterLimit, varyingThreshold, verts, idx, anchorA, anchorB, anchorT);
|
|
585
|
+
}
|
|
586
|
+
}
|
|
587
|
+
const vertices = new Float32Array(verts);
|
|
588
|
+
if (metric) coordsOutOfMetric(vertices, metric);
|
|
589
|
+
return {
|
|
590
|
+
vertices,
|
|
591
|
+
indices: new Uint32Array(idx),
|
|
592
|
+
anchorA: new Uint32Array(anchorA),
|
|
593
|
+
anchorB: new Uint32Array(anchorB),
|
|
594
|
+
anchorT: new Float32Array(anchorT)
|
|
595
|
+
};
|
|
596
|
+
}
|
|
597
|
+
function expandPolyline(pl, width, join, cap, miterLimit, varyingThreshold, verts, idx, anchorA, anchorB, anchorT) {
|
|
598
|
+
const half = width / 2;
|
|
599
|
+
const pts = pl.points;
|
|
600
|
+
const ptCount = pts.length / 2;
|
|
601
|
+
const segCount = ptCount - 1;
|
|
602
|
+
if (segCount < 1) return;
|
|
603
|
+
const plA = pl.anchorA ?? new Uint32Array(ptCount);
|
|
604
|
+
const plB = pl.anchorB ?? new Uint32Array(ptCount);
|
|
605
|
+
const plT = pl.anchorT ?? new Float32Array(ptCount);
|
|
606
|
+
const W = pl.widths, O = pl.offsets;
|
|
607
|
+
const pos = (i) => (W ? W[i] / 2 : half) + (O ? O[i] : 0);
|
|
608
|
+
const neg = (i) => (W ? W[i] / 2 : half) - (O ? O[i] : 0);
|
|
609
|
+
const segs = [];
|
|
610
|
+
const segSrcIdx = [];
|
|
611
|
+
for (let s = 0; s < segCount; s++) {
|
|
612
|
+
const seg = makeSeg(pts[s * 2], pts[s * 2 + 1], pts[(s + 1) * 2], pts[(s + 1) * 2 + 1], pos(s), neg(s), pos(s + 1), neg(s + 1));
|
|
613
|
+
if (seg) {
|
|
614
|
+
segs.push(seg);
|
|
615
|
+
segSrcIdx.push(s);
|
|
616
|
+
}
|
|
617
|
+
}
|
|
618
|
+
let closerSrcIdx = -1;
|
|
619
|
+
if (pl.closed && segs.length >= 1) {
|
|
620
|
+
const last = segs[segs.length - 1];
|
|
621
|
+
const first = segs[0];
|
|
622
|
+
const closer = makeSeg(last.bx, last.by, first.ax, first.ay, pos(ptCount - 1), neg(ptCount - 1), pos(0), neg(0));
|
|
623
|
+
if (closer) {
|
|
624
|
+
segs.push(closer);
|
|
625
|
+
closerSrcIdx = ptCount - 1;
|
|
626
|
+
segSrcIdx.push(closerSrcIdx);
|
|
627
|
+
}
|
|
628
|
+
}
|
|
629
|
+
if (segs.length === 0) return;
|
|
630
|
+
const segBaseIdx = [];
|
|
631
|
+
for (let s = 0; s < segs.length; s++) {
|
|
632
|
+
const seg = segs[s];
|
|
633
|
+
const startSrc = segSrcIdx[s];
|
|
634
|
+
const endSrc = s === segs.length - 1 && pl.closed && closerSrcIdx >= 0 ? 0 : startSrc + 1;
|
|
635
|
+
const base = verts.length / 2;
|
|
636
|
+
segBaseIdx.push(base);
|
|
637
|
+
const { nxUnit: nx, nyUnit: ny } = seg;
|
|
638
|
+
verts.push(seg.ax + nx * seg.posA, seg.ay + ny * seg.posA);
|
|
639
|
+
anchorA.push(plA[startSrc]);
|
|
640
|
+
anchorB.push(plB[startSrc]);
|
|
641
|
+
anchorT.push(plT[startSrc]);
|
|
642
|
+
verts.push(seg.ax - nx * seg.negA, seg.ay - ny * seg.negA);
|
|
643
|
+
anchorA.push(plA[startSrc]);
|
|
644
|
+
anchorB.push(plB[startSrc]);
|
|
645
|
+
anchorT.push(plT[startSrc]);
|
|
646
|
+
verts.push(seg.bx + nx * seg.posB, seg.by + ny * seg.posB);
|
|
647
|
+
anchorA.push(plA[endSrc]);
|
|
648
|
+
anchorB.push(plB[endSrc]);
|
|
649
|
+
anchorT.push(plT[endSrc]);
|
|
650
|
+
verts.push(seg.bx - nx * seg.negB, seg.by - ny * seg.negB);
|
|
651
|
+
anchorA.push(plA[endSrc]);
|
|
652
|
+
anchorB.push(plB[endSrc]);
|
|
653
|
+
anchorT.push(plT[endSrc]);
|
|
654
|
+
idx.push(base, base + 1, base + 2, base + 1, base + 3, base + 2);
|
|
655
|
+
}
|
|
656
|
+
const joinCount = pl.closed ? segs.length : segs.length - 1;
|
|
657
|
+
for (let j = 0; j < joinCount; j++) {
|
|
658
|
+
emitJoin(segs, segBaseIdx, j, join, miterLimit, varyingThreshold, verts, idx, anchorA, anchorB, anchorT, segSrcIdx, plA, plB, plT);
|
|
659
|
+
}
|
|
660
|
+
if (!pl.closed && cap !== "butt") {
|
|
661
|
+
const first = segs[0];
|
|
662
|
+
const firstBase = segBaseIdx[0];
|
|
663
|
+
emitCap(first, firstBase + 0, firstBase + 1, false, first.posA, first.negA, cap, verts, idx, anchorA, anchorB, anchorT, plA[0], plB[0], plT[0]);
|
|
664
|
+
const last = segs[segs.length - 1];
|
|
665
|
+
const lastBase = segBaseIdx[segs.length - 1];
|
|
666
|
+
const lastSrc = segSrcIdx[segs.length - 1] + 1;
|
|
667
|
+
emitCap(last, lastBase + 2, lastBase + 3, true, last.posB, last.negB, cap, verts, idx, anchorA, anchorB, anchorT, plA[lastSrc], plB[lastSrc], plT[lastSrc]);
|
|
668
|
+
}
|
|
669
|
+
}
|
|
670
|
+
var DEFAULT_MITER_LIMIT = 4;
|
|
671
|
+
var DEFAULT_VARYING_WIDTH_JOIN_THRESHOLD = 1.5;
|
|
619
672
|
function resolveMarkerSize(ref, strokeWidth, scale = 1) {
|
|
620
673
|
if (typeof ref === "string" || ref.size === void 0) return strokeWidth;
|
|
621
674
|
return resolveScreenLength(ref.size, scale);
|
|
@@ -782,6 +835,74 @@ function markerReach(stroke) {
|
|
|
782
835
|
}
|
|
783
836
|
return reach;
|
|
784
837
|
}
|
|
838
|
+
function strokeEdgeNear(path, stroke, x, y, opts = {}) {
|
|
839
|
+
const width = resolveStrokeWidth(stroke.width ?? 1, opts.scale ?? 1);
|
|
840
|
+
const vw = stroke.vertexWidths && stroke.vertexWidths.length > 0 ? stroke.vertexWidths : null;
|
|
841
|
+
const nibScale = stroke.nib ? nibWidthScale(stroke.nib) : null;
|
|
842
|
+
const align = stroke.align ?? "center";
|
|
843
|
+
let best = null;
|
|
844
|
+
for (const pl of extractPolylines(path, strokeFlattening(opts.flattenTolerance, width, vw ?? void 0))) {
|
|
845
|
+
populateWidths(pl, vw, stroke.nib, width);
|
|
846
|
+
const P = pl.points, W = pl.widths, O = pl.offsets;
|
|
847
|
+
const n = P.length / 2;
|
|
848
|
+
const segCount = pl.closed ? n : n - 1;
|
|
849
|
+
const inked = align === "center" ? null : inkedSide(path, P, segCount, align === "inner");
|
|
850
|
+
for (let i = 0; i < segCount; i++) {
|
|
851
|
+
const j = (i + 1) % n;
|
|
852
|
+
const ax = P[i * 2], ay = P[i * 2 + 1];
|
|
853
|
+
const dx = P[j * 2] - ax, dy = P[j * 2 + 1] - ay;
|
|
854
|
+
const len2 = dx * dx + dy * dy;
|
|
855
|
+
if (len2 === 0) continue;
|
|
856
|
+
const len = Math.sqrt(len2);
|
|
857
|
+
const f = Math.min(1, Math.max(0, ((x - ax) * dx + (y - ay) * dy) / len2));
|
|
858
|
+
const fx = ax + dx * f, fy = ay + dy * f;
|
|
859
|
+
const rx = -dy / len, ry = dx / len;
|
|
860
|
+
const d = (x - fx) * rx + (y - fy) * ry;
|
|
861
|
+
const w = W ? W[i] + (W[j] - W[i]) * f : width;
|
|
862
|
+
const o = O ? O[i] + (O[j] - O[i]) * f : 0;
|
|
863
|
+
const side = inked ?? (d >= 0 ? "right" : "left");
|
|
864
|
+
const scale = inked ? 2 : 1;
|
|
865
|
+
const left = inked === "right" ? 0 : scale * (w / 2 - o);
|
|
866
|
+
const right = inked === "left" ? 0 : scale * (w / 2 + o);
|
|
867
|
+
const edge = side === "right" ? right : left;
|
|
868
|
+
const off = Math.hypot(x - fx, y - fy);
|
|
869
|
+
const gap = inked && d >= 0 !== (inked === "right") ? off + edge : off - edge;
|
|
870
|
+
if (best !== null && Math.abs(gap) >= Math.abs(best.gap)) continue;
|
|
871
|
+
const sign = side === "right" ? 1 : -1;
|
|
872
|
+
best = {
|
|
873
|
+
x: fx,
|
|
874
|
+
y: fy,
|
|
875
|
+
nx: rx * sign,
|
|
876
|
+
ny: ry * sign,
|
|
877
|
+
side,
|
|
878
|
+
left,
|
|
879
|
+
right,
|
|
880
|
+
gap,
|
|
881
|
+
factor: scale * (nibScale ? nibScale(dx / len, dy / len) : 1)
|
|
882
|
+
};
|
|
883
|
+
}
|
|
884
|
+
}
|
|
885
|
+
return best;
|
|
886
|
+
}
|
|
887
|
+
function inkedSide(path, P, segCount, inner) {
|
|
888
|
+
const n = P.length / 2;
|
|
889
|
+
let at = -1, longest = 0;
|
|
890
|
+
for (let i = 0; i < segCount; i++) {
|
|
891
|
+
const j2 = (i + 1) % n;
|
|
892
|
+
const l = Math.hypot(P[j2 * 2] - P[i * 2], P[j2 * 2 + 1] - P[i * 2 + 1]);
|
|
893
|
+
if (l > longest) {
|
|
894
|
+
longest = l;
|
|
895
|
+
at = i;
|
|
896
|
+
}
|
|
897
|
+
}
|
|
898
|
+
if (at < 0) return inner ? "right" : "left";
|
|
899
|
+
const j = (at + 1) % n;
|
|
900
|
+
const dx = P[j * 2] - P[at * 2], dy = P[j * 2 + 1] - P[at * 2 + 1];
|
|
901
|
+
const probe = Math.min(longest, 1) * 1e-3;
|
|
902
|
+
const mx = (P[at * 2] + P[j * 2]) / 2 - dy / longest * probe;
|
|
903
|
+
const my = (P[at * 2 + 1] + P[j * 2 + 1]) / 2 + dx / longest * probe;
|
|
904
|
+
return pointInPath(path, mx, my) === inner ? "right" : "left";
|
|
905
|
+
}
|
|
785
906
|
var EMPTY = {
|
|
786
907
|
kind: "polygon",
|
|
787
908
|
commands: new Uint8Array(0),
|
|
@@ -803,7 +924,7 @@ function outlineOf(path, stroke, opts) {
|
|
|
803
924
|
const insets = strokeInsets(sized, width);
|
|
804
925
|
const align = stroke.align ?? "center";
|
|
805
926
|
const cut = align !== "center" && path.kind !== "rect";
|
|
806
|
-
const ribbonStroke = cut ? { ...sized, width: width * 2, align: "center", vertexWidths: stroke.vertexWidths?.map((w) => w
|
|
927
|
+
const ribbonStroke = cut ? { ...sized, width: width * 2, align: "center", vertexWidths: stroke.vertexWidths?.map((w) => scaleVertexWidth(w, 2)) } : sized;
|
|
807
928
|
const mesh = tessellateStroke(path, ribbonStroke, {
|
|
808
929
|
flattenTolerance: tolerance,
|
|
809
930
|
startInset: insets.start,
|
|
@@ -821,16 +942,19 @@ function outlineOf(path, stroke, opts) {
|
|
|
821
942
|
return heads.length === 0 ? ribbon : pathUnion(ribbon, ...heads);
|
|
822
943
|
}
|
|
823
944
|
function offsetPath(path, distance, opts = {}) {
|
|
824
|
-
|
|
825
|
-
|
|
945
|
+
if (distance === 0) return closedRegion(path);
|
|
946
|
+
const region = closedRegion(resolveCorners(path));
|
|
826
947
|
const tolerance = opts.tolerance ?? 0.1;
|
|
827
948
|
const band = strokeToPath(region, {
|
|
828
949
|
width: Math.abs(distance) * 2,
|
|
829
950
|
join: opts.join ?? "miter",
|
|
830
951
|
miterLimit: opts.miterLimit ?? 4
|
|
831
952
|
}, { flattenTolerance: tolerance });
|
|
832
|
-
const
|
|
833
|
-
|
|
953
|
+
const cut = distance > 0 ? pathUnion(region, band) : pathSubtract(region, band);
|
|
954
|
+
const corners = offsetCornerTraces(path, distance);
|
|
955
|
+
const grown = restoreCorners(cut, corners, tolerance);
|
|
956
|
+
if (opts.refit === false || grown.commands.length === 0) return grown;
|
|
957
|
+
return restoreCorners(simplifyPath(grown, tolerance), corners, tolerance);
|
|
834
958
|
}
|
|
835
959
|
function closedRegion(path) {
|
|
836
960
|
if (path.kind === "rect") {
|
|
@@ -842,6 +966,6 @@ function closedRegion(path) {
|
|
|
842
966
|
return anchorsToPath(anchors, closed.map(() => true), path);
|
|
843
967
|
}
|
|
844
968
|
|
|
845
|
-
export { coordsOutOfMetric, markerHeads, markerInset, markerKeyOf, markerReach, markerReadsKey, markerSites, metricKeepsRects, metricKey, offsetPath, polylineIntoMetric, polylineOutOfMetric, populatePolylineWidths, rectIntoMetric, resolveMarkerSize, resolveStrokeWidth, sameMetric, screenSpaceKey, splitForDash, strokeInsets, strokeSpaceOf, strokeToPath, tessellateStroke, trimInSpace, worldWidthInMetric, worldWidthInMetricAlongWorld };
|
|
969
|
+
export { coordsOutOfMetric, markerHeads, markerInset, markerKeyOf, markerReach, markerReadsKey, markerSites, metricKeepsRects, metricKey, nibWidthScale, offsetPath, polylineIntoMetric, polylineOutOfMetric, populatePolylineWidths, rectIntoMetric, resolveMarkerSize, resolveStrokeWidth, sameMetric, screenSpaceKey, splitForDash, strokeEdgeNear, strokeInsets, strokeSpaceOf, strokeToPath, tessellateStroke, trimInSpace, widestStrokeWidth, worldWidthInMetric, worldWidthInMetricAlongWorld };
|
|
846
970
|
//# sourceMappingURL=index.js.map
|
|
847
971
|
//# sourceMappingURL=index.js.map
|