@pixerate/editor 0.38.0 → 0.39.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/THIRD_PARTY_NOTICES.md +2 -2
- package/dist/canvas/index.cjs +31 -25
- package/dist/canvas/index.cjs.map +1 -1
- package/dist/canvas/index.d.cts +16 -3
- package/dist/canvas/index.d.ts +16 -3
- package/dist/canvas/index.js +7 -1
- package/dist/canvas/index.js.map +1 -1
- package/dist/{chunk-ZCCU3VNI.js → chunk-3LHWTGHW.js} +158 -9
- package/dist/chunk-3LHWTGHW.js.map +1 -0
- package/dist/{chunk-7EDLVBOO.cjs → chunk-VNYRI265.cjs} +159 -10
- package/dist/chunk-VNYRI265.cjs.map +1 -0
- package/dist/{pathfinder-fLmmFOJw.d.ts → pathfinder-D98S2lV5.d.ts} +20 -2
- package/dist/{pathfinder-Bd87_lsQ.d.cts → pathfinder-DKRcXiLS.d.cts} +20 -2
- package/dist/vector/index.cjs +281 -220
- package/dist/vector/index.cjs.map +1 -1
- package/dist/vector/index.d.cts +55 -12
- package/dist/vector/index.d.ts +55 -12
- package/dist/vector/index.js +101 -40
- package/dist/vector/index.js.map +1 -1
- package/package.json +1 -1
- package/dist/chunk-7EDLVBOO.cjs.map +0 -1
- package/dist/chunk-ZCCU3VNI.js.map +0 -1
package/dist/vector/index.js
CHANGED
|
@@ -8,12 +8,14 @@ import {
|
|
|
8
8
|
closeContour,
|
|
9
9
|
contourSignedArea,
|
|
10
10
|
contoursToPath,
|
|
11
|
+
cutOutPiece,
|
|
11
12
|
distanceToBezierPath,
|
|
12
13
|
fitCubicWithTangents,
|
|
13
14
|
fitCubics,
|
|
14
15
|
flattenChain,
|
|
15
16
|
formatNumber,
|
|
16
17
|
gradientToSvgDef,
|
|
18
|
+
hatchPath,
|
|
17
19
|
isStraightCubic,
|
|
18
20
|
normalizeBezierPath,
|
|
19
21
|
outlineStroke,
|
|
@@ -36,7 +38,7 @@ import {
|
|
|
36
38
|
transformBezierPath,
|
|
37
39
|
trimSegments,
|
|
38
40
|
windingNumber
|
|
39
|
-
} from "../chunk-
|
|
41
|
+
} from "../chunk-3LHWTGHW.js";
|
|
40
42
|
|
|
41
43
|
// src/vector/types.ts
|
|
42
44
|
var VECTOR_CORNER_KINDS = ["round", "inverted", "chamfer"];
|
|
@@ -3815,29 +3817,48 @@ function vectorShapeBuilderMap(entries) {
|
|
|
3815
3817
|
});
|
|
3816
3818
|
const map = PlanarMap.build(inputs);
|
|
3817
3819
|
if (!map) return null;
|
|
3818
|
-
|
|
3819
|
-
|
|
3820
|
-
|
|
3820
|
+
let chains = null;
|
|
3821
|
+
const runs = (line2) => (chains ??= map.chains()).filter((c) => lines[c.source] === line2 && !(line2 && c.closed)).map((c) => {
|
|
3822
|
+
const path = contoursToPath([{ segments: c.segments, closed: c.closed }]);
|
|
3823
|
+
return { path, segments: c.segments, closed: c.closed, source: c.source, bounds: bezierPathBounds(path) };
|
|
3821
3824
|
});
|
|
3822
|
-
|
|
3825
|
+
const pieces = lines.some(Boolean) ? runs(true) : [];
|
|
3826
|
+
let edges = null;
|
|
3827
|
+
let size = 0;
|
|
3828
|
+
for (const input of inputs) {
|
|
3829
|
+
const b = bezierPathBounds(input.path);
|
|
3830
|
+
if (b) size = Math.max(size, b.maxX - b.minX, b.maxY - b.minY);
|
|
3831
|
+
}
|
|
3832
|
+
return {
|
|
3833
|
+
entries: [...entries],
|
|
3834
|
+
map,
|
|
3835
|
+
lines,
|
|
3836
|
+
pieces,
|
|
3837
|
+
get edges() {
|
|
3838
|
+
return edges ??= runs(false);
|
|
3839
|
+
},
|
|
3840
|
+
tolerance: 0.2 * Math.max(size / 2e3, 0.25)
|
|
3841
|
+
};
|
|
3823
3842
|
}
|
|
3824
|
-
function
|
|
3843
|
+
function pieceNear(pieces, p, tol) {
|
|
3825
3844
|
const point = [p.x, p.y];
|
|
3845
|
+
let best = null;
|
|
3846
|
+
pieces.forEach((piece, index) => {
|
|
3847
|
+
const b = piece.bounds;
|
|
3848
|
+
if (p.x < b.minX - tol || p.x > b.maxX + tol || p.y < b.minY - tol || p.y > b.maxY + tol) return;
|
|
3849
|
+
const d = distanceToBezierPath(piece.path, point);
|
|
3850
|
+
if (d <= tol && (!best || d < best.d)) best = { index, d };
|
|
3851
|
+
});
|
|
3852
|
+
return best;
|
|
3853
|
+
}
|
|
3854
|
+
function vectorShapeBuilderHit(m, p, lineTolerance = null) {
|
|
3826
3855
|
if (lineTolerance !== null && lineTolerance > 0) {
|
|
3827
|
-
|
|
3828
|
-
|
|
3829
|
-
|
|
3830
|
-
|
|
3831
|
-
if (p.x < b.minX - bestD || p.x > b.maxX + bestD || p.y < b.minY - bestD || p.y > b.maxY + bestD) return;
|
|
3832
|
-
const d = distanceToBezierPath(piece.path, point);
|
|
3833
|
-
if (d <= bestD) {
|
|
3834
|
-
best = i;
|
|
3835
|
-
bestD = d;
|
|
3836
|
-
}
|
|
3837
|
-
});
|
|
3838
|
-
if (best >= 0) return { line: best };
|
|
3856
|
+
const line2 = pieceNear(m.pieces, p, lineTolerance);
|
|
3857
|
+
const edge = pieceNear(m.edges, p, lineTolerance);
|
|
3858
|
+
if (edge && (!line2 || edge.d < line2.d)) return { edge: edge.index };
|
|
3859
|
+
if (line2) return { line: line2.index };
|
|
3839
3860
|
}
|
|
3840
|
-
const face = m.map.faceAt(
|
|
3861
|
+
const face = m.map.faceAt([p.x, p.y]);
|
|
3841
3862
|
return face >= 0 ? { face } : null;
|
|
3842
3863
|
}
|
|
3843
3864
|
function samplePolyline(points, step) {
|
|
@@ -3855,7 +3876,7 @@ function samplePolyline(points, step) {
|
|
|
3855
3876
|
}
|
|
3856
3877
|
function addBuilderHit(touched, hit) {
|
|
3857
3878
|
if (!hit) return false;
|
|
3858
|
-
const [list, i] = "face" in hit ? [touched.faces, hit.face] : [touched.lines, hit.line];
|
|
3879
|
+
const [list, i] = "face" in hit ? [touched.faces, hit.face] : "line" in hit ? [touched.lines, hit.line] : [touched.edges, hit.edge];
|
|
3859
3880
|
if (list.includes(i)) return false;
|
|
3860
3881
|
list.push(i);
|
|
3861
3882
|
return true;
|
|
@@ -3868,12 +3889,18 @@ function sides(m, piece) {
|
|
|
3868
3889
|
const off = Math.max(1e-6, Math.min(0.05, 0.05 * chord));
|
|
3869
3890
|
return [m.map.faceAt([mid[0] - t[1] * off, mid[1] + t[0] * off]), m.map.faceAt([mid[0] + t[1] * off, mid[1] - t[0] * off])];
|
|
3870
3891
|
}
|
|
3892
|
+
function vectorBuilderErasedEdges(m, touched) {
|
|
3893
|
+
if (!touched.edges.length) return [];
|
|
3894
|
+
const faces = new Set(touched.faces);
|
|
3895
|
+
return touched.edges.filter((k) => m.edges[k] && !sides(m, m.edges[k]).some((f) => f >= 0 && faces.has(f)));
|
|
3896
|
+
}
|
|
3871
3897
|
function planShapeBuilder(m, touched, erase) {
|
|
3872
3898
|
const replace = /* @__PURE__ */ new Map();
|
|
3873
3899
|
const picked = new Set(touched.faces);
|
|
3874
3900
|
const faces = m.map.faces;
|
|
3875
3901
|
const sourcesOf = faces.map((_, i) => m.map.faceSources(i));
|
|
3876
|
-
m.
|
|
3902
|
+
const cuts = erase ? vectorBuilderErasedEdges(m, touched).map((k) => m.edges[k]) : [];
|
|
3903
|
+
m.entries.forEach((entry, i) => {
|
|
3877
3904
|
if (m.lines[i]) {
|
|
3878
3905
|
const own = m.pieces.flatMap((piece, k) => piece.source === i ? [k] : []);
|
|
3879
3906
|
const keep = own.filter((k) => !touched.lines.includes(k) && !sides(m, m.pieces[k]).every((f) => f >= 0 && picked.has(f)));
|
|
@@ -3881,10 +3908,24 @@ function planShapeBuilder(m, touched, erase) {
|
|
|
3881
3908
|
replace.set(i, keep.length ? { kind: "pieces", paths: keep.map((k) => m.pieces[k].path) } : null);
|
|
3882
3909
|
return;
|
|
3883
3910
|
}
|
|
3884
|
-
|
|
3885
|
-
|
|
3886
|
-
|
|
3887
|
-
|
|
3911
|
+
const inUnion = touched.faces.some((f) => sourcesOf[f].includes(i));
|
|
3912
|
+
if (!inUnion && !cuts.length) return;
|
|
3913
|
+
let rest;
|
|
3914
|
+
if (inUnion) {
|
|
3915
|
+
const kept = faces.flatMap((_, f) => sourcesOf[f].includes(i) && !picked.has(f) ? [f] : []);
|
|
3916
|
+
rest = kept.length ? m.map.merge(kept) : [];
|
|
3917
|
+
} else rest = worldOf(entry, entry.node.subpaths.filter(usable));
|
|
3918
|
+
const fillCloses = hasPaint(entry.node.fill);
|
|
3919
|
+
let cut = false;
|
|
3920
|
+
for (const piece of cuts) {
|
|
3921
|
+
const r = rest.length ? cutOutPiece(rest, piece, fillCloses, m.tolerance) : null;
|
|
3922
|
+
if (r) {
|
|
3923
|
+
rest = r;
|
|
3924
|
+
cut = true;
|
|
3925
|
+
}
|
|
3926
|
+
}
|
|
3927
|
+
if (!inUnion && !cut) return;
|
|
3928
|
+
replace.set(i, rest.length ? { kind: inUnion ? "fill" : "outline", path: rest } : null);
|
|
3888
3929
|
});
|
|
3889
3930
|
let merged = null;
|
|
3890
3931
|
if (!erase && touched.faces.length) {
|
|
@@ -5426,20 +5467,23 @@ var VectorEditorController = class {
|
|
|
5426
5467
|
* Shape Builder gesture along `points` (document units; one point is a click) over the faces
|
|
5427
5468
|
* the selected (or given) paths make. Merges the faces touched into one shape styled like the
|
|
5428
5469
|
* top-most shape over the first face (the current fill for a face only outlines enclose);
|
|
5429
|
-
* with `erase` deletes them,
|
|
5430
|
-
*
|
|
5470
|
+
* with `erase` deletes them, the pieces of unfilled open paths and the edges of other outlines
|
|
5471
|
+
* (cut where paths meet) within `tolerance`: a deleted edge opens a closed subpath there,
|
|
5472
|
+
* splits an open one or takes a subpath it runs all the way round, and edges bounding a face
|
|
5473
|
+
* deleted in the same gesture go with the face. Every shape covering a touched face loses
|
|
5474
|
+
* that area; paths keep their own transforms. One undo step.
|
|
5431
5475
|
*/
|
|
5432
5476
|
shapeBuilder(points, options = {}) {
|
|
5433
|
-
const none = (message) => ({ ok: false, message, merged: null, faces: 0, lines: 0 });
|
|
5477
|
+
const none = (message) => ({ ok: false, message, merged: null, faces: 0, lines: 0, edges: 0 });
|
|
5434
5478
|
if (!points.length || !points.every((p) => Number.isFinite(p.x) && Number.isFinite(p.y))) return none("Give the points of the gesture.");
|
|
5435
5479
|
const m = this.shapeBuilderMap(options.ids);
|
|
5436
5480
|
if (!m) return none("Select paths that make faces.");
|
|
5437
5481
|
const erase = !!options.erase;
|
|
5438
5482
|
const tol = erase ? options.tolerance ?? this.builderLineTolerance() : null;
|
|
5439
|
-
const touched = { faces: [], lines: [] };
|
|
5483
|
+
const touched = { faces: [], lines: [], edges: [] };
|
|
5440
5484
|
const size = m.map.faces.reduce((s, f) => Math.max(s, f.bounds.maxX - f.bounds.minX, f.bounds.maxY - f.bounds.minY), 1);
|
|
5441
5485
|
for (const p of samplePolyline(points, Math.max(size / 2e3, 0.25 / this.viewport.zoom))) addBuilderHit(touched, vectorShapeBuilderHit(m, p, tol));
|
|
5442
|
-
if (!touched.faces.length && !touched.lines.length) return none("There is no face under the pointer.");
|
|
5486
|
+
if (!touched.faces.length && !touched.lines.length && !touched.edges.length) return none("There is no face under the pointer.");
|
|
5443
5487
|
return this.applyShapeBuilder(m, touched, erase);
|
|
5444
5488
|
}
|
|
5445
5489
|
applyShapeBuilder(m, touched, erase) {
|
|
@@ -5455,6 +5499,7 @@ var VectorEditorController = class {
|
|
|
5455
5499
|
const r = plan.replace.get(i);
|
|
5456
5500
|
if (r === void 0) list.push(node);
|
|
5457
5501
|
else if (r?.kind === "fill") list.push({ ...node, fillRule: "nonzero", subpaths: bezierToSubpaths(toLocal(r.path)) });
|
|
5502
|
+
else if (r?.kind === "outline") list.push({ ...node, subpaths: bezierToSubpaths(toLocal(r.path)) });
|
|
5458
5503
|
else if (r?.kind === "pieces") {
|
|
5459
5504
|
r.paths.forEach(
|
|
5460
5505
|
(path, k) => list.push({ ...node, id: k === 0 ? node.id : this.newId(), subpaths: bezierToSubpaths(toLocal(path)).map((sp) => ({ ...sp, closed: false })) })
|
|
@@ -5485,7 +5530,7 @@ var VectorEditorController = class {
|
|
|
5485
5530
|
this.editingId = null;
|
|
5486
5531
|
});
|
|
5487
5532
|
this.builderCache = null;
|
|
5488
|
-
return { ok: true, merged: mergedId, faces: touched.faces.length, lines: touched.lines.length };
|
|
5533
|
+
return { ok: true, merged: mergedId, faces: touched.faces.length, lines: touched.lines.length, edges: erase ? vectorBuilderErasedEdges(m, touched).length : 0 };
|
|
5489
5534
|
}
|
|
5490
5535
|
/** The paint bucket's map of the visible outlines, cached per document and gap. */
|
|
5491
5536
|
fillMap(gap) {
|
|
@@ -5542,7 +5587,9 @@ var VectorEditorController = class {
|
|
|
5542
5587
|
const key = hit ? `${alt ? "erase" : "merge"}:${JSON.stringify(hit)}` : null;
|
|
5543
5588
|
if (key === this.builderHoverKey) return false;
|
|
5544
5589
|
this.builderHoverKey = key;
|
|
5545
|
-
|
|
5590
|
+
const touched = { faces: [], lines: [], edges: [] };
|
|
5591
|
+
addBuilderHit(touched, hit);
|
|
5592
|
+
this.builderPreview = hit && m ? this.builderPreviewOf(m, touched, alt, null) : null;
|
|
5546
5593
|
return true;
|
|
5547
5594
|
}
|
|
5548
5595
|
builderPreviewOf(m, touched, erase, trail) {
|
|
@@ -5550,7 +5597,8 @@ var VectorEditorController = class {
|
|
|
5550
5597
|
return {
|
|
5551
5598
|
mode: erase ? "erase" : "merge",
|
|
5552
5599
|
faces: touched.faces.map((f) => faces[f].path),
|
|
5553
|
-
|
|
5600
|
+
// Edges bounding a face touched too go with the face, so they are not shown apart.
|
|
5601
|
+
lines: [...touched.lines.map((k) => m.pieces[k].path), ...erase ? vectorBuilderErasedEdges(m, touched).map((k) => m.edges[k].path) : []],
|
|
5554
5602
|
trail
|
|
5555
5603
|
};
|
|
5556
5604
|
}
|
|
@@ -5566,7 +5614,7 @@ var VectorEditorController = class {
|
|
|
5566
5614
|
const m = this.selectedRoots().length ? this.shapeBuilderMap() : null;
|
|
5567
5615
|
if (!m) return;
|
|
5568
5616
|
const erase = !!input.alt;
|
|
5569
|
-
const touched = { faces: [], lines: [] };
|
|
5617
|
+
const touched = { faces: [], lines: [], edges: [] };
|
|
5570
5618
|
addBuilderHit(touched, vectorShapeBuilderHit(m, p, erase ? this.builderLineTolerance() : null));
|
|
5571
5619
|
this.drag = { kind: "builder", erase, points: [p], touched, map: m };
|
|
5572
5620
|
this.interaction = { type: "builder", mode: erase ? "erase" : "merge", points: [p] };
|
|
@@ -5586,7 +5634,7 @@ var VectorEditorController = class {
|
|
|
5586
5634
|
upBuilder(drag) {
|
|
5587
5635
|
this.builderPreview = null;
|
|
5588
5636
|
this.builderHoverKey = null;
|
|
5589
|
-
if (!drag.touched.faces.length && !drag.touched.lines.length) {
|
|
5637
|
+
if (!drag.touched.faces.length && !drag.touched.lines.length && !drag.touched.edges.length) {
|
|
5590
5638
|
this.notify();
|
|
5591
5639
|
return;
|
|
5592
5640
|
}
|
|
@@ -7323,6 +7371,7 @@ function pencilPressureProfile(points, pressures) {
|
|
|
7323
7371
|
// src/vector/render.ts
|
|
7324
7372
|
var fmt = (n) => Math.round(n * 100) / 100;
|
|
7325
7373
|
var fmtMatrix = (m) => `matrix(${m.map((n) => Math.round(n * 1e6) / 1e6).join(" ")})`;
|
|
7374
|
+
var VECTOR_BUILDER_HATCH_GAP = 6;
|
|
7326
7375
|
function renderNode(node, defs, idPrefix) {
|
|
7327
7376
|
if (node.visible === false) return null;
|
|
7328
7377
|
if (node.type === "group") {
|
|
@@ -7501,9 +7550,17 @@ function getVectorOverlayRenderData(state) {
|
|
|
7501
7550
|
if (state.builder) {
|
|
7502
7551
|
const v2 = state.viewport;
|
|
7503
7552
|
const view = [v2.zoom, 0, 0, v2.zoom, v2.x, v2.y];
|
|
7504
|
-
const
|
|
7553
|
+
const svg = (path) => bezierPathToSvg(path, { precision: 2 });
|
|
7554
|
+
const screen = state.builder.faces.map((f) => transformBezierPath(f, view));
|
|
7555
|
+
const hatch = state.builder.mode !== "fill";
|
|
7505
7556
|
const trail = state.builder.trail && state.builder.trail.length > 1 ? state.builder.trail.map((q, i) => `${i ? "L" : "M"}${sc(q).x} ${sc(q).y}`).join("") : null;
|
|
7506
|
-
builder = {
|
|
7557
|
+
builder = {
|
|
7558
|
+
mode: state.builder.mode,
|
|
7559
|
+
faces: screen.map(svg),
|
|
7560
|
+
hatches: screen.map((f) => hatch ? svg(hatchPath(f, { gap: VECTOR_BUILDER_HATCH_GAP })) : ""),
|
|
7561
|
+
lines: state.builder.lines.map((l) => svg(transformBezierPath(l, view))),
|
|
7562
|
+
trail
|
|
7563
|
+
};
|
|
7507
7564
|
if (state.builder.mode !== "fill" && it?.type === "builder") {
|
|
7508
7565
|
const n = state.builder.faces.length;
|
|
7509
7566
|
status = state.builder.mode === "erase" ? `Delete ${n} ${n === 1 ? "face" : "faces"}` : `Merge ${n} ${n === 1 ? "face" : "faces"}`;
|
|
@@ -7530,7 +7587,9 @@ var VECTOR_OVERLAY_THEME = {
|
|
|
7530
7587
|
builderFill: "rgba(14, 165, 233, 0.22)",
|
|
7531
7588
|
/** Shape Builder faces and line pieces an Alt gesture deletes. */
|
|
7532
7589
|
builderErase: "#ef4444",
|
|
7533
|
-
builderEraseFill: "rgba(239, 68, 68, 0.22)"
|
|
7590
|
+
builderEraseFill: "rgba(239, 68, 68, 0.22)",
|
|
7591
|
+
/** Opacity of the hatch lines over Shape Builder faces (drawn in the face's outline colour). */
|
|
7592
|
+
builderHatchOpacity: 0.6
|
|
7534
7593
|
};
|
|
7535
7594
|
function vectorDiamondPath(x, y, r = VECTOR_OVERLAY_THEME.widthPointSize) {
|
|
7536
7595
|
return `M${fmt(x)} ${fmt(y - r)}L${fmt(x + r)} ${fmt(y)}L${fmt(x)} ${fmt(y + r)}L${fmt(x - r)} ${fmt(y)}Z`;
|
|
@@ -8006,7 +8065,7 @@ function createVectorCommands(controller, options = {}) {
|
|
|
8006
8065
|
id: `${prefix}shapeBuilder`,
|
|
8007
8066
|
label: "Shape Builder",
|
|
8008
8067
|
keywords: ["merge faces", "build shape", "combine regions", "delete region"],
|
|
8009
|
-
description: "Shape Builder gesture over the faces of the given paths: `points` is the pointer path in document units (one point is a click; [[x, y], \u2026]; use the points from shapeBuilderFaces). Merges the faces it touches into one shape, or with `erase: true` deletes them
|
|
8068
|
+
description: "Shape Builder gesture over the faces of the given paths: `points` is the pointer path in document units (one point is a click; [[x, y], \u2026]; use the points from shapeBuilderFaces). Merges the faces it touches into one shape, or with `erase: true` deletes them, the pieces of unfilled open paths and the edges of other outlines (cut where paths meet) within `tolerance`; a deleted edge opens its path there, and edges bounding a deleted face go with it. Shapes covering those faces lose that area. Returns the counts of faces, line pieces and edges.",
|
|
8010
8069
|
params: {
|
|
8011
8070
|
type: "object",
|
|
8012
8071
|
properties: { points: POINTS, erase: { type: "boolean" }, tolerance: { type: "number", minimum: 0 }, ids: IDS },
|
|
@@ -8016,7 +8075,7 @@ function createVectorCommands(controller, options = {}) {
|
|
|
8016
8075
|
run: (_ctx, params) => {
|
|
8017
8076
|
const result = controller.shapeBuilder(params.points.map(([x, y]) => ({ x, y })), { ids: ids(params), erase: params.erase, tolerance: params.tolerance });
|
|
8018
8077
|
check(result);
|
|
8019
|
-
return { merged: result.merged, faces: result.faces, lines: result.lines };
|
|
8078
|
+
return { merged: result.merged, faces: result.faces, lines: result.lines, edges: result.edges };
|
|
8020
8079
|
}
|
|
8021
8080
|
},
|
|
8022
8081
|
{
|
|
@@ -8331,6 +8390,7 @@ export {
|
|
|
8331
8390
|
LazyMouse,
|
|
8332
8391
|
VECTOR_ARROWHEADS,
|
|
8333
8392
|
VECTOR_ARROWHEAD_LABELS,
|
|
8393
|
+
VECTOR_BUILDER_HATCH_GAP,
|
|
8334
8394
|
VECTOR_CLIPBOARD_TYPE,
|
|
8335
8395
|
VECTOR_CONSTRUCTION_ANGLES,
|
|
8336
8396
|
VECTOR_CORNER_KINDS,
|
|
@@ -8481,6 +8541,7 @@ export {
|
|
|
8481
8541
|
unionBounds,
|
|
8482
8542
|
variableStrokeOutline,
|
|
8483
8543
|
variableStrokeRings,
|
|
8544
|
+
vectorBuilderErasedEdges,
|
|
8484
8545
|
vectorCornerAt,
|
|
8485
8546
|
vectorCorners,
|
|
8486
8547
|
vectorCornersAttribute,
|