@weasel-js/stroke 1.10.0 → 1.11.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.
- package/README.md +1 -1
- package/dist/index.d.ts +92 -14
- package/dist/index.js +423 -278
- package/dist/index.js.map +1 -1
- package/package.json +4 -4
package/dist/index.js
CHANGED
|
@@ -1,153 +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;
|
|
133
|
+
}
|
|
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);
|
|
151
183
|
}
|
|
152
184
|
|
|
153
185
|
// src/tessellate/vertexWidths.ts
|
|
@@ -158,23 +190,36 @@ function populatePolylineWidths(pl, vertexWidths, fallbackWidth, scale) {
|
|
|
158
190
|
const aB = pl.anchorB ?? new Uint32Array(ptCount);
|
|
159
191
|
const aT = pl.anchorT ?? new Float32Array(ptCount);
|
|
160
192
|
const widths = new Float32Array(ptCount);
|
|
193
|
+
const offsets = new Float32Array(ptCount);
|
|
194
|
+
let sided = false;
|
|
195
|
+
const fallback = fallbackWidth / 2;
|
|
161
196
|
let k = 1;
|
|
162
|
-
const
|
|
163
|
-
const
|
|
164
|
-
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];
|
|
165
200
|
};
|
|
166
201
|
for (let i = 0; i < ptCount; i++) {
|
|
167
202
|
if (scale) k = scaleAt(pl, i, scale);
|
|
168
203
|
const a = aA[i], b = aB[i], t = aT[i];
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
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;
|
|
175
209
|
}
|
|
210
|
+
widths[i] = left + right;
|
|
211
|
+
offsets[i] = (right - left) / 2;
|
|
212
|
+
if (offsets[i] !== 0) sided = true;
|
|
176
213
|
}
|
|
177
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);
|
|
178
223
|
}
|
|
179
224
|
function scaleAt(pl, i, scale) {
|
|
180
225
|
const pts = pl.points;
|
|
@@ -196,6 +241,14 @@ function scaleAt(pl, i, scale) {
|
|
|
196
241
|
return len > 0 ? scale(tx / len, ty / len) : 1;
|
|
197
242
|
}
|
|
198
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
|
+
|
|
199
252
|
// src/tessellate/metric.ts
|
|
200
253
|
function screenSpaceKey(space) {
|
|
201
254
|
return space === void 0 ? "" : `${metricKey(space.metric)}@${space.pxPerUnit}`;
|
|
@@ -290,150 +343,16 @@ function trimInSpace(pl, from, ribbon, start, end) {
|
|
|
290
343
|
return cur;
|
|
291
344
|
}
|
|
292
345
|
|
|
293
|
-
// src/tessellate/
|
|
294
|
-
var EMPTY_MESH = {
|
|
295
|
-
vertices: new Float32Array(0),
|
|
296
|
-
indices: new Uint32Array(0),
|
|
297
|
-
anchorA: new Uint32Array(0),
|
|
298
|
-
anchorB: new Uint32Array(0),
|
|
299
|
-
anchorT: new Float32Array(0)
|
|
300
|
-
};
|
|
301
|
-
function resolveStrokeWidth(width, scale) {
|
|
302
|
-
return resolveScreenLength(width, scale);
|
|
303
|
-
}
|
|
304
|
-
function tessellateStroke(path, stroke, opts = {}) {
|
|
305
|
-
const width = resolveStrokeWidth(stroke.width ?? 1, 1);
|
|
306
|
-
if (width <= 0) return EMPTY_MESH;
|
|
307
|
-
const join = stroke.join ?? "miter";
|
|
308
|
-
const cap = stroke.cap ?? "butt";
|
|
309
|
-
const align = stroke.align ?? "center";
|
|
310
|
-
const miterLimit = stroke.miterLimit ?? DEFAULT_MITER_LIMIT;
|
|
311
|
-
const varyingThreshold = stroke.varyingWidthJoinThreshold ?? DEFAULT_VARYING_WIDTH_JOIN_THRESHOLD;
|
|
312
|
-
const metric = opts.metric;
|
|
313
|
-
const rectInMetric = metric && path.kind === "rect" ? rectIntoMetric(path, metric) : null;
|
|
314
|
-
let workingPath = rectInMetric ?? path;
|
|
315
|
-
if (workingPath.kind === "rect" && align !== "center") {
|
|
316
|
-
const aligned = alignedStrokeRect(workingPath, align, width);
|
|
317
|
-
workingPath = {
|
|
318
|
-
kind: "rect",
|
|
319
|
-
x: aligned.x,
|
|
320
|
-
y: aligned.y,
|
|
321
|
-
width: aligned.width,
|
|
322
|
-
height: aligned.height
|
|
323
|
-
};
|
|
324
|
-
}
|
|
325
|
-
const polylines = extractPolylines(workingPath, opts);
|
|
326
|
-
const vertexWidths = stroke.vertexWidths && stroke.vertexWidths.length > 0 ? stroke.vertexWidths : null;
|
|
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);
|
|
@@ -718,7 +771,7 @@ function markerHeads(path, stroke, strokeWidth, flattenTolerance, spaces = {}) {
|
|
|
718
771
|
want[role] = true;
|
|
719
772
|
}
|
|
720
773
|
const out = [];
|
|
721
|
-
for (const pl of extractPolylines(path,
|
|
774
|
+
for (const pl of extractPolylines(path, strokeFlattening(flattenTolerance, strokeWidth, stroke.vertexWidths))) {
|
|
722
775
|
for (const [space, want] of groups) {
|
|
723
776
|
const inSpace = space ? { ...pl, points: pl.points.slice() } : pl;
|
|
724
777
|
if (space) polylineIntoMetric(inSpace, space.metric);
|
|
@@ -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,
|
|
@@ -820,7 +941,31 @@ function outlineOf(path, stroke, opts) {
|
|
|
820
941
|
}
|
|
821
942
|
return heads.length === 0 ? ribbon : pathUnion(ribbon, ...heads);
|
|
822
943
|
}
|
|
944
|
+
function offsetPath(path, distance, opts = {}) {
|
|
945
|
+
if (distance === 0) return closedRegion(path);
|
|
946
|
+
const region = closedRegion(resolveCorners(path));
|
|
947
|
+
const tolerance = opts.tolerance ?? 0.1;
|
|
948
|
+
const band = strokeToPath(region, {
|
|
949
|
+
width: Math.abs(distance) * 2,
|
|
950
|
+
join: opts.join ?? "miter",
|
|
951
|
+
miterLimit: opts.miterLimit ?? 4
|
|
952
|
+
}, { flattenTolerance: tolerance });
|
|
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);
|
|
958
|
+
}
|
|
959
|
+
function closedRegion(path) {
|
|
960
|
+
if (path.kind === "rect") {
|
|
961
|
+
const { x, y, width: w, height: h } = path;
|
|
962
|
+
return anchorsToPath([[{ x, y }, { x: x + w, y }, { x: x + w, y: y + h }, { x, y: y + h }]], [true]);
|
|
963
|
+
}
|
|
964
|
+
const { anchors, closed } = pathToAnchors(path);
|
|
965
|
+
if (closed.every(Boolean)) return path;
|
|
966
|
+
return anchorsToPath(anchors, closed.map(() => true), path);
|
|
967
|
+
}
|
|
823
968
|
|
|
824
|
-
export { coordsOutOfMetric, markerHeads, markerInset, markerKeyOf, markerReach, markerReadsKey, markerSites, metricKeepsRects, metricKey, 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 };
|
|
825
970
|
//# sourceMappingURL=index.js.map
|
|
826
971
|
//# sourceMappingURL=index.js.map
|