@weasel-js/stroke 1.15.2 → 1.16.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/dist/index.d.ts +61 -8
- package/dist/index.js +245 -27
- package/dist/index.js.map +1 -1
- package/package.json +4 -4
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { Path, PolygonPath, Affine } from '@weasel-js/geom';
|
|
2
|
-
import { ScreenLength, Stroke, VertexWidth, StrokeNib, MarkerRef } from '@weasel-js/paint';
|
|
1
|
+
import { Path, LengthTable, LengthSpan, CurveSpan, PolygonPath, Affine } from '@weasel-js/geom';
|
|
2
|
+
import { ScreenLength, Stroke, VertexWidth, StrokeNib, StrokeTrim, MarkerRef } from '@weasel-js/paint';
|
|
3
3
|
import { Polyline, Mesh } from '@weasel-js/geom/tessellate';
|
|
4
4
|
import { M as MarkerEntry } from './test-seams-D-zMT-bn.js';
|
|
5
5
|
export { a as MarkerCtx, b as MarkerPaint, g as getMarker, l as listMarkers, m as markerGeneration, c as markerRegistry, r as registerMarker } from './test-seams-D-zMT-bn.js';
|
|
@@ -173,9 +173,10 @@ declare function dashLength(path: Path): number;
|
|
|
173
173
|
* widths and offsets are carried through: a dash boundary landing mid-segment takes
|
|
174
174
|
* {@link anchorParamsBetween}'s params and the width interpolated there.
|
|
175
175
|
* A dash {@link dashPattern} reads as solid along `pl` returns `pl` whole,
|
|
176
|
-
* and a non-finite one returns nothing.
|
|
176
|
+
* and a non-finite one returns nothing. `phase` is how far into the pattern
|
|
177
|
+
* `pl`'s first point sits, so a run cut from a longer line keeps its pattern.
|
|
177
178
|
*/
|
|
178
|
-
declare function splitForDash(pl: Polyline, pattern: readonly number[]): Polyline[];
|
|
179
|
+
declare function splitForDash(pl: Polyline, pattern: readonly number[], phase?: number): Polyline[];
|
|
179
180
|
|
|
180
181
|
/** What one unit of a `vertexWidths` entry measures across the line at a
|
|
181
182
|
* point whose unit tangent, in the polyline's own space, is `(tx, ty)`. */
|
|
@@ -199,6 +200,54 @@ declare function widestStrokeWidth(width: number, vertexWidths: readonly VertexW
|
|
|
199
200
|
*/
|
|
200
201
|
declare function nibWidthScale(nib: StrokeNib): WidthScale;
|
|
201
202
|
|
|
203
|
+
/**
|
|
204
|
+
* The stretch of `t`'s path a trim draws, as pen-order lengths, or null when
|
|
205
|
+
* it draws nothing: a window starting at or past its end, or a position that
|
|
206
|
+
* is NaN. Positions clamp to the path; no trim is `{}`, the whole of it.
|
|
207
|
+
*/
|
|
208
|
+
declare function resolveTrim(t: LengthTable, trim: StrokeTrim | undefined): LengthSpan | null;
|
|
209
|
+
|
|
210
|
+
/**
|
|
211
|
+
* Where `trim` cuts `path`, as curve positions on its corner-resolved
|
|
212
|
+
* outline: resolved once, by exact arc length, so every reader cuts its own
|
|
213
|
+
* flattening at the same points, and a copy carried through a transform
|
|
214
|
+
* command for command takes the positions as they are. Null when it draws
|
|
215
|
+
* nothing; no trim is the whole path.
|
|
216
|
+
*/
|
|
217
|
+
declare function trimSpan(path: Path, trim: StrokeTrim | undefined): CurveSpan | null;
|
|
218
|
+
|
|
219
|
+
/** What a trimmed stroke draws, as a path a writer strokes itself. */
|
|
220
|
+
interface TrimmedPath {
|
|
221
|
+
/** One subpath per visible run, in pen order; curves split, not flattened. */
|
|
222
|
+
path: Path;
|
|
223
|
+
/** Per subpath, how far into its pattern a dash starts: the exact arc length from
|
|
224
|
+
* its subpath's start to the cut, the measure a writer's own dashes run in. */
|
|
225
|
+
dashPhases: number[];
|
|
226
|
+
/** Per subpath, whether it came from a closed one, so it takes no start or end marker. */
|
|
227
|
+
fromClosed: boolean[];
|
|
228
|
+
}
|
|
229
|
+
/**
|
|
230
|
+
* The part of `path` that `trim` strokes, cut on the curve points the ribbon
|
|
231
|
+
* cuts at: on the corner-resolved path, at the positions `trimSpan` resolves.
|
|
232
|
+
* A closed subpath the window covers whole stays closed. `null` when the
|
|
233
|
+
* window draws nothing; the input path itself, with no runs listed, when there
|
|
234
|
+
* is no trim.
|
|
235
|
+
*/
|
|
236
|
+
declare function trimmedPath(path: Path, trim: StrokeTrim | undefined): TrimmedPath | null;
|
|
237
|
+
/** One stroke a writer states a {@link TrimmedPath} in. */
|
|
238
|
+
interface TrimmedRun {
|
|
239
|
+
path: Path;
|
|
240
|
+
dashPhase: number;
|
|
241
|
+
fromClosed: boolean;
|
|
242
|
+
}
|
|
243
|
+
/**
|
|
244
|
+
* `cut` as the strokes a writer states separately: the whole path as one,
|
|
245
|
+
* unless its subpaths differ in dash phase (when `dashed`) or, with
|
|
246
|
+
* `byClosed`, in whether they came from a closed subpath. A writer that
|
|
247
|
+
* restarts a dash on every subpath, or sets markers per element, needs those apart.
|
|
248
|
+
*/
|
|
249
|
+
declare function trimmedRuns(cut: TrimmedPath, dashed: boolean, byClosed: boolean): TrimmedRun[];
|
|
250
|
+
|
|
202
251
|
/**
|
|
203
252
|
* The kit's built-in stroke-marker vocabulary.
|
|
204
253
|
*
|
|
@@ -264,7 +313,8 @@ interface MarkerSite {
|
|
|
264
313
|
angle: number;
|
|
265
314
|
role: 'start' | 'mid' | 'end';
|
|
266
315
|
}
|
|
267
|
-
/** Which marker roles to place. `start` and `end` are ignored on a closed polyline
|
|
316
|
+
/** Which marker roles to place. `start` and `end` are ignored on a closed polyline,
|
|
317
|
+
* and on a run cut from one. */
|
|
268
318
|
interface MarkerSiteRequest {
|
|
269
319
|
start: boolean;
|
|
270
320
|
mid: boolean;
|
|
@@ -274,8 +324,10 @@ interface MarkerSiteRequest {
|
|
|
274
324
|
* Where each requested marker sits on `pl` and the angle it points at.
|
|
275
325
|
* Mid sites fall only on authored vertices, never on flattened curve samples,
|
|
276
326
|
* and need the polyline to carry anchor data; without it no mid sites are returned.
|
|
327
|
+
* `pl`'s first and last points are never mid sites, so a trimmed run's ends are ends.
|
|
328
|
+
* `fromClosed` marks an open run cut from a closed subpath, which takes no start or end site.
|
|
277
329
|
*/
|
|
278
|
-
declare function markerSites(pl: Polyline, want: MarkerSiteRequest): MarkerSite[];
|
|
330
|
+
declare function markerSites(pl: Polyline, want: MarkerSiteRequest, fromClosed?: boolean): MarkerSite[];
|
|
279
331
|
|
|
280
332
|
/**
|
|
281
333
|
* A stroke's marker heads as placed geometry, and how far they reach.
|
|
@@ -294,7 +346,8 @@ interface MarkerHead {
|
|
|
294
346
|
* `strokeWidth` is the already width-resolved stroke width; `flattenTolerance`
|
|
295
347
|
* matches what the ribbon used, so heads land on the same flattened vertices
|
|
296
348
|
* the stroke did. A head `spaces` puts in a screen space is placed and sized
|
|
297
|
-
* there and comes back in world units, so it meets the line it caps.
|
|
349
|
+
* there and comes back in world units, so it meets the line it caps. Under
|
|
350
|
+
* `stroke.trim` heads sit on the visible runs, windowed as the ribbon is.
|
|
298
351
|
*/
|
|
299
352
|
declare function markerHeads(path: Path, stroke: Stroke, strokeWidth: number, flattenTolerance: number | undefined, spaces?: StrokeSpaces): MarkerHead[];
|
|
300
353
|
/**
|
|
@@ -416,4 +469,4 @@ interface OffsetPathOptions {
|
|
|
416
469
|
*/
|
|
417
470
|
declare function offsetPath(path: Path, distance: number, opts?: OffsetPathOptions): PolygonPath;
|
|
418
471
|
|
|
419
|
-
export { BUILTIN_MARKERS, type InsetLength, MarkerEntry, type MarkerHead, type MarkerSite, type MarkerSiteRequest, type MeasuredLength, type OffsetPathOptions, type ScreenSpace, type StrokeEdgeHit, type StrokeEdgeOptions, type StrokeMetric, type StrokeOptions, type StrokeSide, type StrokeSpaces, type StrokeToPathOptions, type WidthScale, coordsOutOfMetric, dashLength, 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 };
|
|
472
|
+
export { BUILTIN_MARKERS, type InsetLength, MarkerEntry, type MarkerHead, type MarkerSite, type MarkerSiteRequest, type MeasuredLength, type OffsetPathOptions, type ScreenSpace, type StrokeEdgeHit, type StrokeEdgeOptions, type StrokeMetric, type StrokeOptions, type StrokeSide, type StrokeSpaces, type StrokeToPathOptions, type TrimmedPath, type TrimmedRun, type WidthScale, coordsOutOfMetric, dashLength, markerHeads, markerInset, markerKeyOf, markerReach, markerReadsKey, markerSites, metricKeepsRects, metricKey, nibWidthScale, offsetPath, polylineIntoMetric, polylineOutOfMetric, populatePolylineWidths, rectIntoMetric, resolveMarkerSize, resolveStrokeWidth, resolveTrim, sameMetric, screenSpaceKey, splitForDash, strokeEdgeNear, strokeInsets, strokeSpaceOf, strokeToPath, tessellateStroke, trimInSpace, trimSpan, trimmedPath, trimmedRuns, widestStrokeWidth, worldWidthInMetric, worldWidthInMetricAlongWorld };
|
package/dist/index.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { getMarker } from './chunk-PNX2RT7L.js';
|
|
2
2
|
export { BUILTIN_MARKERS, getMarker, listMarkers, markerGeneration, markerRegistry, registerMarker } from './chunk-PNX2RT7L.js';
|
|
3
|
-
import { resolveCornersWithSources, remapPerAnchor, segmentT, pointInPath, invert, transformPath, resolveCorners, offsetCornerTraces, restoreCorners, simplifyPath, anchorsToPath, pathToAnchors, DEFAULT_FLATTEN_TOLERANCE, boundsOfPath } from '@weasel-js/geom';
|
|
3
|
+
import { lengthAtSegmentFraction, lengthAtAnchor, lengthAtSubpath, pathLengthTable, curveSpan, resolveCornersWithSources, remapPerAnchor, runSegments, lengthAtCurve, asPolygonPath, splitSubpaths, PATH_L, PATH_Q, PATH_C, PATH_M, PATH_Z, segmentT, pointInPath, invert, transformPath, resolveCorners, offsetCornerTraces, restoreCorners, simplifyPath, anchorsToPath, pathToAnchors, splitQuadraticAt, splitCubicAt, DEFAULT_FLATTEN_TOLERANCE, boundsOfPath, closingAnchor, DEG_TO_RAD } from '@weasel-js/geom';
|
|
4
4
|
import { dashPattern, widestHalfWidth, resolveScreenLength, alignedStrokeRect, scaleVertexWidth, vertexHalfWidths } from '@weasel-js/paint';
|
|
5
|
-
import { extractPolylines, anchorParamsBetween, trimPolyline } from '@weasel-js/geom/tessellate';
|
|
5
|
+
import { extractPolylines, anchorParamsBetween, trimPolyline, polylineAtCurve } 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) {
|
|
@@ -28,16 +28,16 @@ function toPolyline(run, closed = false) {
|
|
|
28
28
|
if (run.o) pl.offsets = new Float32Array(run.o);
|
|
29
29
|
return pl;
|
|
30
30
|
}
|
|
31
|
-
function dashPolylines(polylines, pattern) {
|
|
32
|
-
const dash = dashPattern(pattern,
|
|
31
|
+
function dashPolylines(polylines, pattern, phases, length = polylinesLength(polylines)) {
|
|
32
|
+
const dash = dashPattern(pattern, length);
|
|
33
33
|
if (dash === "solid") return [...polylines];
|
|
34
34
|
if (dash === "non-finite") return [];
|
|
35
|
-
return polylines.flatMap((pl) => splitForDash(pl, dash));
|
|
35
|
+
return polylines.flatMap((pl, i) => splitForDash(pl, dash, phases?.[i] ?? 0));
|
|
36
36
|
}
|
|
37
37
|
function dashLength(path) {
|
|
38
|
-
return
|
|
38
|
+
return polylinesLength(extractPolylines(path));
|
|
39
39
|
}
|
|
40
|
-
function
|
|
40
|
+
function polylinesLength(polylines) {
|
|
41
41
|
return polylines.reduce((sum, pl) => sum + polylineLength(pl), 0);
|
|
42
42
|
}
|
|
43
43
|
function polylineLength(pl) {
|
|
@@ -48,7 +48,7 @@ function polylineLength(pl) {
|
|
|
48
48
|
if (pl.closed && n > 1) len += Math.hypot(p[0] - p[n * 2 - 2], p[1] - p[n * 2 - 1]);
|
|
49
49
|
return len;
|
|
50
50
|
}
|
|
51
|
-
function splitForDash(pl, pattern) {
|
|
51
|
+
function splitForDash(pl, pattern, phase = 0) {
|
|
52
52
|
const dash = dashPattern(pattern, polylineLength(pl));
|
|
53
53
|
if (dash === "solid") return [pl];
|
|
54
54
|
if (dash === "non-finite") return [];
|
|
@@ -71,7 +71,16 @@ function splitForDash(pl, pattern) {
|
|
|
71
71
|
let dashIdx = 0;
|
|
72
72
|
let dashRemaining = dash[0];
|
|
73
73
|
let onPhase = true;
|
|
74
|
-
|
|
74
|
+
if (phase > 0) {
|
|
75
|
+
let into = phase % (dash.reduce((a, b) => a + b, 0) * (dash.length % 2 + 1));
|
|
76
|
+
while (into >= dash[dashIdx]) {
|
|
77
|
+
into -= dash[dashIdx];
|
|
78
|
+
dashIdx = (dashIdx + 1) % dash.length;
|
|
79
|
+
onPhase = !onPhase;
|
|
80
|
+
}
|
|
81
|
+
dashRemaining = dash[dashIdx] - into;
|
|
82
|
+
}
|
|
83
|
+
let cur = onPhase ? runFrom(pointAt(0), !!W, !!O) : null;
|
|
75
84
|
const flushAndAdvance = (at) => {
|
|
76
85
|
if (cur && cur.pts.length >= 4) out.push(toPolyline(cur));
|
|
77
86
|
dashIdx = (dashIdx + 1) % dash.length;
|
|
@@ -364,9 +373,132 @@ function trimInSpace(pl, from, ribbon, start, end) {
|
|
|
364
373
|
moveTo(ribbon);
|
|
365
374
|
return cur;
|
|
366
375
|
}
|
|
376
|
+
function resolveTrim(t, trim) {
|
|
377
|
+
const { start = 0, end = 1, pace = "length" } = trim ?? {};
|
|
378
|
+
const from = lengthOf(t, start, pace);
|
|
379
|
+
const to = lengthOf(t, end, pace);
|
|
380
|
+
if (Number.isNaN(from) || Number.isNaN(to) || from >= to) return null;
|
|
381
|
+
return { from, to };
|
|
382
|
+
}
|
|
383
|
+
function lengthOf(t, at, pace) {
|
|
384
|
+
if (typeof at === "number") {
|
|
385
|
+
return pace === "anchor" ? lengthAtSegmentFraction(t, at) : clampLength(t, at * t.total);
|
|
386
|
+
}
|
|
387
|
+
if ("length" in at) return clampLength(t, at.length);
|
|
388
|
+
if ("anchor" in at) return lengthAtAnchor(t, at.anchor);
|
|
389
|
+
return lengthAtSubpath(t, at.subpath);
|
|
390
|
+
}
|
|
391
|
+
var clampLength = (t, s) => Number.isNaN(s) ? NaN : Math.min(Math.max(s, 0), t.total);
|
|
392
|
+
|
|
393
|
+
// src/trim/trimSpan.ts
|
|
394
|
+
function trimSpan(path, trim) {
|
|
395
|
+
const table = pathLengthTable(path);
|
|
396
|
+
const span = resolveTrim(table, trim);
|
|
397
|
+
return span && curveSpan(table, span);
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
// src/trim/windowPolylines.ts
|
|
401
|
+
var EPS = 1e-9;
|
|
402
|
+
function windowPolylines(polylines, span, runs) {
|
|
403
|
+
const out = [];
|
|
404
|
+
for (let r = Math.max(span.from.run, 0); r <= Math.min(span.to.run, polylines.length - 1); r++) {
|
|
405
|
+
const pl = polylines[r];
|
|
406
|
+
const place = (at) => polylineAtCurve(pl, at.segment, at.t, runs?.[r]);
|
|
407
|
+
const head = r === span.from.run ? place(span.from) : null;
|
|
408
|
+
const tail = r === span.to.run ? place(span.to) : null;
|
|
409
|
+
const total = polylinesLength([pl]);
|
|
410
|
+
const lo = head?.length ?? 0;
|
|
411
|
+
const hi = tail?.length ?? total;
|
|
412
|
+
if (hi - lo <= EPS) continue;
|
|
413
|
+
if (pl.closed && lo <= EPS && total - hi <= EPS) {
|
|
414
|
+
out.push({ polyline: pl, fromClosed: true, dashPhase: 0 });
|
|
415
|
+
continue;
|
|
416
|
+
}
|
|
417
|
+
const base = pl.closed ? opened(pl) : pl;
|
|
418
|
+
const cut = trimPolyline(base, lo, total - hi);
|
|
419
|
+
if (!cut) continue;
|
|
420
|
+
let dashPhase = lo;
|
|
421
|
+
if (cut !== base) {
|
|
422
|
+
const q = cut.points;
|
|
423
|
+
const k = q.length - 2;
|
|
424
|
+
if (head && lo > 0) {
|
|
425
|
+
const next = (x, y) => k >= 2 ? Math.hypot(q[2] - x, q[3] - y) : 0;
|
|
426
|
+
dashPhase = Math.max(0, lo + next(q[0], q[1]) - next(head.x, head.y));
|
|
427
|
+
[q[0], q[1]] = [head.x, head.y];
|
|
428
|
+
}
|
|
429
|
+
if (tail && total - hi > 0) [q[k], q[k + 1]] = [tail.x, tail.y];
|
|
430
|
+
}
|
|
431
|
+
out.push({ polyline: cut, fromClosed: pl.closed, dashPhase });
|
|
432
|
+
}
|
|
433
|
+
return out;
|
|
434
|
+
}
|
|
435
|
+
function windowTrim(polylines, path, trim) {
|
|
436
|
+
const span = trimSpan(path, trim);
|
|
437
|
+
if (!span) return null;
|
|
438
|
+
const { runs } = pathLengthTable(path);
|
|
439
|
+
return { span, runs, windowed: windowPolylines(polylines, span, runs) };
|
|
440
|
+
}
|
|
441
|
+
function opened(pl) {
|
|
442
|
+
const n = pl.points.length / 2;
|
|
443
|
+
const { anchorA: A, anchorB: B, anchorT: T, widths: W, offsets: O } = pl;
|
|
444
|
+
const end = closingAnchor(pl) ?? A?.[0] ?? 0;
|
|
445
|
+
const grow = (a, v) => {
|
|
446
|
+
const g = new a.constructor(n + 1);
|
|
447
|
+
g.set(a);
|
|
448
|
+
g[n] = v;
|
|
449
|
+
return g;
|
|
450
|
+
};
|
|
451
|
+
return {
|
|
452
|
+
points: [...pl.points, pl.points[0], pl.points[1]],
|
|
453
|
+
closed: false,
|
|
454
|
+
anchorA: grow(A ?? new Uint32Array(n), end),
|
|
455
|
+
anchorB: grow(B ?? new Uint32Array(n), end),
|
|
456
|
+
anchorT: grow(T ?? new Float32Array(n), 0),
|
|
457
|
+
...W ? { widths: grow(W, W[0]) } : {},
|
|
458
|
+
...O ? { offsets: grow(O, O[0]) } : {}
|
|
459
|
+
};
|
|
460
|
+
}
|
|
367
461
|
|
|
368
|
-
// src/tessellate/
|
|
369
|
-
var
|
|
462
|
+
// src/tessellate/ribbonWindow.ts
|
|
463
|
+
var withOwnPoints = (pl) => ({ ...pl, points: pl.points.slice() });
|
|
464
|
+
var lengthOf2 = (pl) => pl ? polylinesLength([pl]) : 0;
|
|
465
|
+
var worldInset = (inset) => inset === void 0 || typeof inset === "number" || inset.metric === null;
|
|
466
|
+
var insetLength = (inset) => inset === void 0 ? 0 : typeof inset === "number" ? inset : inset.length;
|
|
467
|
+
function windowRibbon(polylines, cut, ribbon, startInset, endInset, dashing) {
|
|
468
|
+
const windowed = cut?.windowed ?? [];
|
|
469
|
+
const inWorld = ribbon === null && worldInset(startInset) && worldInset(endInset);
|
|
470
|
+
const dashLength2 = !dashing ? void 0 : inWorld ? untrimmedWorldLength(polylines, insetLength(startInset), insetLength(endInset)) : polylinesLength(polylines.flatMap((pl) => trimInSpace(withOwnPoints(pl), null, ribbon, startInset, endInset) ?? []));
|
|
471
|
+
const phases = windowed.map((run) => !dashing || run.dashPhase <= 0 || inWorld ? run.dashPhase : measuredPhase(polylines, cut.runs, run, cut.span.from, ribbon, startInset));
|
|
472
|
+
const out = [];
|
|
473
|
+
const kept = [];
|
|
474
|
+
windowed.forEach(({ polyline, fromClosed }, i) => {
|
|
475
|
+
const t = trimInSpace(polyline, null, ribbon, fromClosed ? void 0 : startInset, fromClosed ? void 0 : endInset);
|
|
476
|
+
if (t !== null) {
|
|
477
|
+
out.push(t);
|
|
478
|
+
kept.push(phases[i]);
|
|
479
|
+
}
|
|
480
|
+
});
|
|
481
|
+
return { runs: out, phases: kept, dashLength: dashLength2 };
|
|
482
|
+
}
|
|
483
|
+
function untrimmedWorldLength(polylines, start, end) {
|
|
484
|
+
const cut = Math.max(0, start) + Math.max(0, end);
|
|
485
|
+
let sum = 0;
|
|
486
|
+
for (const pl of polylines) {
|
|
487
|
+
const len = polylinesLength([pl]);
|
|
488
|
+
sum += pl.closed || cut <= 0 ? len : Math.max(0, len - cut);
|
|
489
|
+
}
|
|
490
|
+
return sum;
|
|
491
|
+
}
|
|
492
|
+
function measuredPhase(polylines, runs, run, at, ribbon, startInset) {
|
|
493
|
+
const pl = polylines[at.run];
|
|
494
|
+
const inset = run.fromClosed ? void 0 : startInset;
|
|
495
|
+
const whole = trimInSpace(withOwnPoints(pl), null, ribbon, inset, void 0);
|
|
496
|
+
const toEnd = { from: { ...at, run: 0 }, to: { run: 0, segment: Infinity, t: 1 } };
|
|
497
|
+
const [rest] = windowPolylines([pl], toEnd, runs && [runs[at.run]]);
|
|
498
|
+
const tail = rest ? trimInSpace(withOwnPoints(rest.polyline), null, ribbon, inset, void 0) : null;
|
|
499
|
+
return Math.max(0, lengthOf2(whole) - lengthOf2(tail));
|
|
500
|
+
}
|
|
501
|
+
var ROUND_CAP_STEP_RAD = 10 * DEG_TO_RAD;
|
|
370
502
|
function emitCap(seg, leftIdx, rightIdx, atEnd, pos, neg, cap, verts, idx, anchorA, anchorB, anchorT, endA, endB, endT) {
|
|
371
503
|
const sign = atEnd ? 1 : -1;
|
|
372
504
|
const dx = (seg.bx - seg.ax) / seg.len * sign;
|
|
@@ -503,7 +635,7 @@ function emitJoin(segs, segBaseIdx, j, join, miterLimit, varyingThreshold, verts
|
|
|
503
635
|
return;
|
|
504
636
|
}
|
|
505
637
|
}
|
|
506
|
-
var ROUND_STEP_RAD = 10 *
|
|
638
|
+
var ROUND_STEP_RAD = 10 * DEG_TO_RAD;
|
|
507
639
|
function emitRoundJoin(a, aOuterEnd, bOuterStart, onPositive, verts, idx, anchorA, anchorB, anchorT, cornerA, cornerB, cornerT, inner) {
|
|
508
640
|
const cx = a.bx, cy = a.by;
|
|
509
641
|
const pivotIdx = inner >= 0 ? inner : cornerVertex(a, verts, anchorA, anchorB, anchorT, cornerA, cornerB, cornerT);
|
|
@@ -649,13 +781,25 @@ function tessellateStroke(path, stroke, opts = {}) {
|
|
|
649
781
|
const anchorB = [];
|
|
650
782
|
const anchorT = [];
|
|
651
783
|
const ribbonSpace = metric ?? null;
|
|
652
|
-
const
|
|
653
|
-
for (const pl of polylines)
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
784
|
+
const from = rectInMetric ? ribbonSpace : null;
|
|
785
|
+
for (const pl of polylines) populateWidths(pl, vertexWidths, stroke.nib, width, scale);
|
|
786
|
+
let runs;
|
|
787
|
+
let phases;
|
|
788
|
+
let dashLength2;
|
|
789
|
+
if (stroke.trim !== void 0) {
|
|
790
|
+
if (from) for (const pl of polylines) polylineOutOfMetric(pl, from);
|
|
791
|
+
({ runs, phases, dashLength: dashLength2 } = windowRibbon(
|
|
792
|
+
polylines,
|
|
793
|
+
windowTrim(polylines, path, stroke.trim),
|
|
794
|
+
ribbonSpace,
|
|
795
|
+
opts.startInset,
|
|
796
|
+
opts.endInset,
|
|
797
|
+
stroke.dash !== void 0
|
|
798
|
+
));
|
|
799
|
+
} else {
|
|
800
|
+
runs = polylines.flatMap((pl) => trimInSpace(pl, from, ribbonSpace, opts.startInset, opts.endInset) ?? []);
|
|
657
801
|
}
|
|
658
|
-
for (const sub of dashPolylines(
|
|
802
|
+
for (const sub of dashPolylines(runs, stroke.dash, phases, dashLength2)) {
|
|
659
803
|
expandPolyline(sub, width, join, cap, miterLimit, varyingThreshold, verts, idx, anchorA, anchorB, anchorT);
|
|
660
804
|
}
|
|
661
805
|
const vertices = new Float32Array(verts);
|
|
@@ -745,6 +889,75 @@ function expandPolyline(pl, width, join, cap, miterLimit, varyingThreshold, vert
|
|
|
745
889
|
}
|
|
746
890
|
var DEFAULT_MITER_LIMIT = 4;
|
|
747
891
|
var DEFAULT_VARYING_WIDTH_JOIN_THRESHOLD = 1.5;
|
|
892
|
+
var EPS2 = 1e-9;
|
|
893
|
+
function trimmedPath(path, trim) {
|
|
894
|
+
if (trim === void 0) return { path, dashPhases: [], fromClosed: [] };
|
|
895
|
+
const span = trimSpan(path, trim);
|
|
896
|
+
if (!span) return null;
|
|
897
|
+
const table = pathLengthTable(path);
|
|
898
|
+
const out = { path, dashPhases: [], fromClosed: [] };
|
|
899
|
+
const commands = [];
|
|
900
|
+
const coords = [];
|
|
901
|
+
for (let r = span.from.run; r <= span.to.run; r++) {
|
|
902
|
+
const segs = runSegments(table, r);
|
|
903
|
+
const lo = r === span.from.run ? span.from : { run: r, segment: 0, t: 0 };
|
|
904
|
+
const hi = r === span.to.run ? span.to : { run: r, segment: segs - 1, t: 1 };
|
|
905
|
+
const at = table.runStart[r];
|
|
906
|
+
const head = lengthAtCurve(table, lo) - at;
|
|
907
|
+
if (segs <= 0 || lengthAtCurve(table, hi) - at - head <= EPS2) continue;
|
|
908
|
+
const { closed } = table.runs[r];
|
|
909
|
+
const whole = closed && head <= EPS2 && table.runStart[r + 1] - lengthAtCurve(table, hi) <= EPS2;
|
|
910
|
+
writeRun(table, r, lo, hi, whole, commands, coords);
|
|
911
|
+
out.dashPhases.push(whole ? 0 : head);
|
|
912
|
+
out.fromClosed.push(closed);
|
|
913
|
+
}
|
|
914
|
+
if (out.dashPhases.length === 0) return null;
|
|
915
|
+
const shape = path.kind === "rect" ? asPolygonPath(path) : path;
|
|
916
|
+
out.path = {
|
|
917
|
+
kind: "polygon",
|
|
918
|
+
commands: Uint8Array.from(commands),
|
|
919
|
+
coords: Float32Array.from(coords),
|
|
920
|
+
fillRule: shape.fillRule,
|
|
921
|
+
...shape.origin ? { origin: shape.origin } : {}
|
|
922
|
+
};
|
|
923
|
+
return out;
|
|
924
|
+
}
|
|
925
|
+
function trimmedRuns(cut, dashed, byClosed) {
|
|
926
|
+
const { dashPhases: phases, fromClosed } = cut;
|
|
927
|
+
const alike = (i) => (!byClosed || fromClosed[i] === fromClosed[0]) && (!dashed || phases[i] === phases[0]);
|
|
928
|
+
if (phases.every((_p, i) => alike(i))) {
|
|
929
|
+
return [{ path: cut.path, dashPhase: phases[0] ?? 0, fromClosed: fromClosed[0] ?? false }];
|
|
930
|
+
}
|
|
931
|
+
return splitSubpaths(cut.path).map((path, i) => ({ path, dashPhase: phases[i], fromClosed: fromClosed[i] }));
|
|
932
|
+
}
|
|
933
|
+
function writeRun(table, r, lo, hi, whole, commands, coords) {
|
|
934
|
+
const { points, controls } = table.runs[r];
|
|
935
|
+
const n = points.length / 2;
|
|
936
|
+
for (let k = lo.segment; k <= hi.segment; k++) {
|
|
937
|
+
const ctl = controls?.[k];
|
|
938
|
+
const cmd = !ctl || ctl.length === 0 ? PATH_L : ctl.length === 2 ? PATH_Q : PATH_C;
|
|
939
|
+
if (whole && cmd === PATH_L && k === n - 1) break;
|
|
940
|
+
const j = (k + 1) % n;
|
|
941
|
+
const seg = [points[k * 2], points[k * 2 + 1], ...ctl ? Array.from(ctl) : [], points[j * 2], points[j * 2 + 1]];
|
|
942
|
+
const piece = subSegment(cmd, seg, k === lo.segment ? lo.t : 0, k === hi.segment ? hi.t : 1);
|
|
943
|
+
if (k === lo.segment) {
|
|
944
|
+
commands.push(PATH_M);
|
|
945
|
+
coords.push(piece[0], piece[1]);
|
|
946
|
+
}
|
|
947
|
+
commands.push(cmd);
|
|
948
|
+
for (let i = 2; i < piece.length; i++) coords.push(piece[i]);
|
|
949
|
+
}
|
|
950
|
+
if (whole) commands.push(PATH_Z);
|
|
951
|
+
}
|
|
952
|
+
function subSegment(cmd, c, t0, t1) {
|
|
953
|
+
if (cmd === PATH_L) {
|
|
954
|
+
const at = (t) => [c[0] + (c[2] - c[0]) * t, c[1] + (c[3] - c[1]) * t];
|
|
955
|
+
return [...at(t0), ...at(t1)];
|
|
956
|
+
}
|
|
957
|
+
const split = (p, t) => cmd === PATH_Q ? splitQuadraticAt(p[0], p[1], p[2], p[3], p[4], p[5], t) : splitCubicAt(p[0], p[1], p[2], p[3], p[4], p[5], p[6], p[7], t);
|
|
958
|
+
const head = t1 >= 1 ? c : split(c, t1)[0];
|
|
959
|
+
return t0 <= 0 ? head : split(head, t1 > 0 ? t0 / t1 : 0)[1];
|
|
960
|
+
}
|
|
748
961
|
function resolveMarkerSize(ref, strokeWidth, scale = 1) {
|
|
749
962
|
if (typeof ref === "string" || ref.size === void 0) return strokeWidth;
|
|
750
963
|
return resolveScreenLength(ref.size, scale);
|
|
@@ -766,12 +979,12 @@ function strokeInsets(stroke, strokeWidth) {
|
|
|
766
979
|
}
|
|
767
980
|
|
|
768
981
|
// src/markerSites.ts
|
|
769
|
-
var
|
|
982
|
+
var EPS3 = 1e-9;
|
|
770
983
|
function dir(pl, i, j) {
|
|
771
984
|
const dx = pl.points[j * 2] - pl.points[i * 2];
|
|
772
985
|
const dy = pl.points[j * 2 + 1] - pl.points[i * 2 + 1];
|
|
773
986
|
const len = Math.hypot(dx, dy);
|
|
774
|
-
if (len <
|
|
987
|
+
if (len < EPS3) return null;
|
|
775
988
|
return { x: dx / len, y: dy / len };
|
|
776
989
|
}
|
|
777
990
|
function nextDistinct(pl, i, n) {
|
|
@@ -782,11 +995,11 @@ function prevDistinct(pl, i) {
|
|
|
782
995
|
for (let k = i - 1; k >= 0; k--) if (dir(pl, k, i)) return k;
|
|
783
996
|
return -1;
|
|
784
997
|
}
|
|
785
|
-
function markerSites(pl, want) {
|
|
998
|
+
function markerSites(pl, want, fromClosed = pl.closed) {
|
|
786
999
|
const out = [];
|
|
787
1000
|
const n = pl.points.length / 2;
|
|
788
1001
|
if (n < 2) return out;
|
|
789
|
-
if (want.start && !
|
|
1002
|
+
if (want.start && !fromClosed) {
|
|
790
1003
|
const j = nextDistinct(pl, 0, n);
|
|
791
1004
|
const d = j >= 0 ? dir(pl, 0, j) : null;
|
|
792
1005
|
if (d) {
|
|
@@ -805,7 +1018,7 @@ function markerSites(pl, want) {
|
|
|
805
1018
|
const outDir = dir(pl, i, q);
|
|
806
1019
|
let bx = inDir.x + outDir.x;
|
|
807
1020
|
let by = inDir.y + outDir.y;
|
|
808
|
-
if (Math.hypot(bx, by) <
|
|
1021
|
+
if (Math.hypot(bx, by) < EPS3) {
|
|
809
1022
|
bx = inDir.x;
|
|
810
1023
|
by = inDir.y;
|
|
811
1024
|
}
|
|
@@ -813,7 +1026,7 @@ function markerSites(pl, want) {
|
|
|
813
1026
|
}
|
|
814
1027
|
}
|
|
815
1028
|
}
|
|
816
|
-
if (want.end && !
|
|
1029
|
+
if (want.end && !fromClosed) {
|
|
817
1030
|
const last = n - 1;
|
|
818
1031
|
const p = prevDistinct(pl, last);
|
|
819
1032
|
const d = p >= 0 ? dir(pl, p, last) : null;
|
|
@@ -848,11 +1061,11 @@ function markerHeads(path, stroke, strokeWidth, flattenTolerance, spaces = {}) {
|
|
|
848
1061
|
}
|
|
849
1062
|
const out = [];
|
|
850
1063
|
const origin = path.kind === "polygon" ? path.origin : void 0;
|
|
851
|
-
for (const pl of
|
|
1064
|
+
for (const { polyline: pl, fromClosed } of headRuns(path, stroke, strokeWidth, flattenTolerance)) {
|
|
852
1065
|
for (const [space, want] of groups) {
|
|
853
1066
|
const inSpace = space ? { ...pl, points: pl.points.slice() } : pl;
|
|
854
1067
|
if (space) polylineIntoMetric(inSpace, space.metric);
|
|
855
|
-
for (const site of markerSites(inSpace, want)) {
|
|
1068
|
+
for (const site of markerSites(inSpace, want, fromClosed)) {
|
|
856
1069
|
const ref = refs[site.role];
|
|
857
1070
|
if (ref === void 0) continue;
|
|
858
1071
|
const entry = getMarker(markerKeyOf(ref));
|
|
@@ -867,6 +1080,11 @@ function markerHeads(path, stroke, strokeWidth, flattenTolerance, spaces = {}) {
|
|
|
867
1080
|
}
|
|
868
1081
|
return out;
|
|
869
1082
|
}
|
|
1083
|
+
function headRuns(path, stroke, strokeWidth, flattenTolerance) {
|
|
1084
|
+
const polylines = extractPolylines(path, strokeFlattening(flattenTolerance, strokeWidth, stroke.vertexWidths));
|
|
1085
|
+
if (stroke.trim === void 0) return polylines.map((polyline) => ({ polyline, fromClosed: polyline.closed }));
|
|
1086
|
+
return windowTrim(polylines, path, stroke.trim)?.windowed ?? [];
|
|
1087
|
+
}
|
|
870
1088
|
function markerReadsKey(entry, stroke) {
|
|
871
1089
|
if (entry.reads === void 0) return null;
|
|
872
1090
|
let key = "";
|
|
@@ -1048,6 +1266,6 @@ function closedRegion(path) {
|
|
|
1048
1266
|
return anchorsToPath(anchors, closed.map(() => true), path);
|
|
1049
1267
|
}
|
|
1050
1268
|
|
|
1051
|
-
export { coordsOutOfMetric, dashLength, 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 };
|
|
1269
|
+
export { coordsOutOfMetric, dashLength, markerHeads, markerInset, markerKeyOf, markerReach, markerReadsKey, markerSites, metricKeepsRects, metricKey, nibWidthScale, offsetPath, polylineIntoMetric, polylineOutOfMetric, populatePolylineWidths, rectIntoMetric, resolveMarkerSize, resolveStrokeWidth, resolveTrim, sameMetric, screenSpaceKey, splitForDash, strokeEdgeNear, strokeInsets, strokeSpaceOf, strokeToPath, tessellateStroke, trimInSpace, trimSpan, trimmedPath, trimmedRuns, widestStrokeWidth, worldWidthInMetric, worldWidthInMetricAlongWorld };
|
|
1052
1270
|
//# sourceMappingURL=index.js.map
|
|
1053
1271
|
//# sourceMappingURL=index.js.map
|