@metamesh/modeling 0.0.0-stage → 0.6.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/CHANGELOG.md +105 -0
- package/LICENSE +200 -0
- package/README.md +314 -2
- package/THIRD_PARTY_NOTICES.md +153 -0
- package/dist/api/engine.d.ts +54 -0
- package/dist/api/geometry.d.ts +2912 -0
- package/dist/api/index.d.ts +1648 -0
- package/dist/api/text.d.ts +77 -0
- package/dist/engine/construction-operations.js +189 -0
- package/dist/engine/execution.js +227 -0
- package/dist/engine/export-3mf.js +100 -0
- package/dist/engine/extrusion-finishing.js +82 -0
- package/dist/engine/finalization.js +214 -0
- package/dist/engine/index.js +72 -0
- package/dist/engine/inspection-operations.js +52 -0
- package/dist/engine/kernel/LICENSE +201 -0
- package/dist/engine/kernel/manifold.js +2 -0
- package/dist/engine/kernel/manifold.wasm +0 -0
- package/dist/engine/loft-operations.js +156 -0
- package/dist/engine/mechanical-operations.js +51 -0
- package/dist/engine/modeling-context.js +38 -0
- package/dist/engine/native/adapter.js +20 -0
- package/dist/engine/native/context.js +209 -0
- package/dist/engine/native/copied-views.js +46 -0
- package/dist/engine/native/index.js +13 -0
- package/dist/engine/native/operations.js +115 -0
- package/dist/engine/native/ownership.js +98 -0
- package/dist/engine/native/regions.js +138 -0
- package/dist/engine/native/scoped-primitives.js +127 -0
- package/dist/engine/native/scoped-threads.js +34 -0
- package/dist/engine/native/threads.js +52 -0
- package/dist/engine/native/transforms.js +111 -0
- package/dist/engine/operation-environment.js +20 -0
- package/dist/engine/pattern-operations.js +36 -0
- package/dist/engine/pattern-placements.js +176 -0
- package/dist/engine/primitive-anchors.js +70 -0
- package/dist/engine/primitive-correspondence.js +74 -0
- package/dist/engine/primitive-operations.js +151 -0
- package/dist/engine/primitive-profile-types.js +1 -0
- package/dist/engine/primitive-profiles.js +250 -0
- package/dist/engine/profile-mesh.js +279 -0
- package/dist/engine/section-operations.js +93 -0
- package/dist/engine/shape-anchors.js +88 -0
- package/dist/engine/transform-operations.js +225 -0
- package/dist/engine/types.js +1 -0
- package/dist/geometry/anchors.js +226 -0
- package/dist/geometry/curves/evaluation.js +162 -0
- package/dist/geometry/curves/sampling.js +265 -0
- package/dist/geometry/curves/types.js +1 -0
- package/dist/geometry/cylinder.js +117 -0
- package/dist/geometry/edge-finishing.js +160 -0
- package/dist/geometry/errors.js +76 -0
- package/dist/geometry/extrusion.js +150 -0
- package/dist/geometry/heightfield.js +107 -0
- package/dist/geometry/index.js +42 -0
- package/dist/geometry/loft-correspondence.js +204 -0
- package/dist/geometry/loft-geometry.js +184 -0
- package/dist/geometry/loft-options.js +56 -0
- package/dist/geometry/loft-simple-mesh.js +92 -0
- package/dist/geometry/loft-triangulation.js +64 -0
- package/dist/geometry/loft.js +80 -0
- package/dist/geometry/math/vector3.js +41 -0
- package/dist/geometry/mechanical/gear-motion.js +94 -0
- package/dist/geometry/mechanical/gears.js +484 -0
- package/dist/geometry/mechanical/index.js +13 -0
- package/dist/geometry/mechanical/standards.js +67 -0
- package/dist/geometry/mechanical/thread-helices.js +44 -0
- package/dist/geometry/mechanical/thread-mesh-plan.js +358 -0
- package/dist/geometry/mechanical/thread-mesh.js +142 -0
- package/dist/geometry/mechanical/thread-profiles.js +368 -0
- package/dist/geometry/mechanical/thread-solids.js +230 -0
- package/dist/geometry/mechanical/thread-surface.js +433 -0
- package/dist/geometry/mechanical/threads.js +138 -0
- package/dist/geometry/mesh-planar.js +408 -0
- package/dist/geometry/mesh-quality.js +396 -0
- package/dist/geometry/mesh-signature.js +137 -0
- package/dist/geometry/mesh.js +300 -0
- package/dist/geometry/model-limits.js +50 -0
- package/dist/geometry/numeric.js +11 -0
- package/dist/geometry/paths/frames.js +118 -0
- package/dist/geometry/paths/helix.js +49 -0
- package/dist/geometry/paths/path.js +30 -0
- package/dist/geometry/paths/planar.js +135 -0
- package/dist/geometry/paths/resampling.js +124 -0
- package/dist/geometry/polygon-clipping.js +52 -0
- package/dist/geometry/primitive-options.js +1 -0
- package/dist/geometry/primitive-resolution.js +17 -0
- package/dist/geometry/primitive-resolvers.js +235 -0
- package/dist/geometry/profiles/matched.js +184 -0
- package/dist/geometry/profiles/open-profile.js +137 -0
- package/dist/geometry/profiles/profiles.js +70 -0
- package/dist/geometry/profiles/rectangle.js +235 -0
- package/dist/geometry/profiles/shapes.js +34 -0
- package/dist/geometry/profiles/span-sampling.js +28 -0
- package/dist/geometry/profiles/types.js +1 -0
- package/dist/geometry/profiles/validation.js +20 -0
- package/dist/geometry/regions.js +371 -0
- package/dist/geometry/resolution.js +236 -0
- package/dist/geometry/revolve/revolve-mesh.js +123 -0
- package/dist/geometry/sampling-limits.js +29 -0
- package/dist/geometry/stroke-path.js +181 -0
- package/dist/geometry/svg/errors.js +23 -0
- package/dist/geometry/svg/fill.js +172 -0
- package/dist/geometry/svg/index.js +1 -0
- package/dist/geometry/svg/matrix.js +130 -0
- package/dist/geometry/svg/parser.js +536 -0
- package/dist/geometry/svg/path.js +349 -0
- package/dist/geometry/svg/types.js +1 -0
- package/dist/geometry/svg-import.js +662 -0
- package/dist/geometry/sweeps/options.js +28 -0
- package/dist/geometry/sweeps/orientation.js +106 -0
- package/dist/geometry/sweeps/stations.js +161 -0
- package/dist/geometry/sweeps/sweep-closed.js +96 -0
- package/dist/geometry/sweeps/sweep-mesh.js +20 -0
- package/dist/geometry/sweeps/sweep-open.js +76 -0
- package/dist/geometry/sweeps/validation.js +116 -0
- package/dist/geometry/text-contours.js +32 -0
- package/dist/geometry/text-layout.js +1 -0
- package/dist/geometry/tolerance.js +53 -0
- package/dist/geometry/transform-geometry.js +41 -0
- package/dist/geometry/transform3.js +340 -0
- package/dist/geometry/types.js +1 -0
- package/dist/index.js +1 -0
- package/dist/model/execution-binding.js +30 -0
- package/dist/model/operations.js +152 -0
- package/dist/model/options.js +1 -0
- package/dist/model/types.js +1 -0
- package/dist/text/index.js +48 -0
- package/dist/text/vector-text-layout.js +279 -0
- package/package.json +100 -3
|
@@ -0,0 +1,349 @@
|
|
|
1
|
+
import { SVGPathData, SVGPathDataTransformer } from "svg-pathdata";
|
|
2
|
+
import { resolveArcSegments, resolveGeometryResolution, } from "../resolution.js";
|
|
3
|
+
import { assertSamplingCount, resolveGeometrySamplingLimits, } from "../sampling-limits.js";
|
|
4
|
+
import { complexity, invalid, rethrowAsInvalid } from "./errors.js";
|
|
5
|
+
import { transformPoint } from "./matrix.js";
|
|
6
|
+
export const MAX_CURVE_RECURSION = 16;
|
|
7
|
+
export class SvgVertexBudget {
|
|
8
|
+
limits;
|
|
9
|
+
#emittedCount = 0;
|
|
10
|
+
#parsedCount = 0;
|
|
11
|
+
#removedDuplicateCount = 0;
|
|
12
|
+
#removedSeamCount = 0;
|
|
13
|
+
constructor(limits = {}) {
|
|
14
|
+
this.limits = resolveGeometrySamplingLimits(limits);
|
|
15
|
+
}
|
|
16
|
+
get count() {
|
|
17
|
+
return this.#emittedCount;
|
|
18
|
+
}
|
|
19
|
+
get emittedCount() {
|
|
20
|
+
return this.#emittedCount;
|
|
21
|
+
}
|
|
22
|
+
get parsedCount() {
|
|
23
|
+
return this.#parsedCount;
|
|
24
|
+
}
|
|
25
|
+
get removedDuplicateCount() {
|
|
26
|
+
return this.#removedDuplicateCount;
|
|
27
|
+
}
|
|
28
|
+
get removedSeamCount() {
|
|
29
|
+
return this.#removedSeamCount;
|
|
30
|
+
}
|
|
31
|
+
add(operation) {
|
|
32
|
+
this.#emittedCount += 1;
|
|
33
|
+
assertSamplingCount(this.#emittedCount, operation, this.limits.maxProfilePoints);
|
|
34
|
+
}
|
|
35
|
+
addParsed(count, operation) {
|
|
36
|
+
this.#parsedCount += count;
|
|
37
|
+
assertSamplingCount(this.#parsedCount, operation, this.limits.maxProfilePoints, `SVG input requires ${this.#parsedCount} parsed vertices; the configured ceiling allows ${this.limits.maxProfilePoints}.`);
|
|
38
|
+
}
|
|
39
|
+
removeDuplicate() {
|
|
40
|
+
this.#removedDuplicateCount += 1;
|
|
41
|
+
}
|
|
42
|
+
removeSeam() {
|
|
43
|
+
this.#removedSeamCount += 1;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
function squaredDistance(left, right) {
|
|
47
|
+
const dx = left[0] - right[0];
|
|
48
|
+
const dy = left[1] - right[1];
|
|
49
|
+
return dx * dx + dy * dy;
|
|
50
|
+
}
|
|
51
|
+
function pointSegmentDistance(point, start, end) {
|
|
52
|
+
const dx = end[0] - start[0];
|
|
53
|
+
const dy = end[1] - start[1];
|
|
54
|
+
const denominator = dx * dx + dy * dy;
|
|
55
|
+
if (denominator === 0)
|
|
56
|
+
return Math.sqrt(squaredDistance(point, start));
|
|
57
|
+
const projection = Math.max(0, Math.min(1, ((point[0] - start[0]) * dx + (point[1] - start[1]) * dy) / denominator));
|
|
58
|
+
return Math.hypot(point[0] - (start[0] + projection * dx), point[1] - (start[1] + projection * dy));
|
|
59
|
+
}
|
|
60
|
+
function midpoint(left, right) {
|
|
61
|
+
return [(left[0] + right[0]) / 2, (left[1] + right[1]) / 2];
|
|
62
|
+
}
|
|
63
|
+
function pushUnique(output, point, budget, operation) {
|
|
64
|
+
const previous = output.at(-1);
|
|
65
|
+
if (previous !== undefined && squaredDistance(previous, point) <= 1e-24) {
|
|
66
|
+
budget.removeDuplicate();
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
if (!Number.isFinite(point[0]) || !Number.isFinite(point[1])) {
|
|
70
|
+
invalid(operation, "SVG geometry produced a non-finite vertex.");
|
|
71
|
+
}
|
|
72
|
+
budget.add(operation);
|
|
73
|
+
output.push(point);
|
|
74
|
+
}
|
|
75
|
+
function flattenQuadratic(start, control, end, resolution, depth, output, budget, operation) {
|
|
76
|
+
if (pointSegmentDistance(control, start, end) <= resolution.maxChordError &&
|
|
77
|
+
(resolution.maxSegmentLength === undefined ||
|
|
78
|
+
Math.sqrt(squaredDistance(start, end)) <= resolution.maxSegmentLength)) {
|
|
79
|
+
pushUnique(output, end, budget, operation);
|
|
80
|
+
return;
|
|
81
|
+
}
|
|
82
|
+
if (depth >= MAX_CURVE_RECURSION) {
|
|
83
|
+
complexity(operation, `Curve flattening exceeds ${MAX_CURVE_RECURSION} subdivisions.`, {
|
|
84
|
+
maxRecursion: MAX_CURVE_RECURSION,
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
const startControl = midpoint(start, control);
|
|
88
|
+
const controlEnd = midpoint(control, end);
|
|
89
|
+
const split = midpoint(startControl, controlEnd);
|
|
90
|
+
flattenQuadratic(start, startControl, split, resolution, depth + 1, output, budget, operation);
|
|
91
|
+
flattenQuadratic(split, controlEnd, end, resolution, depth + 1, output, budget, operation);
|
|
92
|
+
}
|
|
93
|
+
function flattenCubic(start, first, second, end, resolution, depth, output, budget, operation) {
|
|
94
|
+
if (Math.max(pointSegmentDistance(first, start, end), pointSegmentDistance(second, start, end)) <=
|
|
95
|
+
resolution.maxChordError &&
|
|
96
|
+
(resolution.maxSegmentLength === undefined ||
|
|
97
|
+
Math.sqrt(squaredDistance(start, end)) <= resolution.maxSegmentLength)) {
|
|
98
|
+
pushUnique(output, end, budget, operation);
|
|
99
|
+
return;
|
|
100
|
+
}
|
|
101
|
+
if (depth >= MAX_CURVE_RECURSION) {
|
|
102
|
+
complexity(operation, `Curve flattening exceeds ${MAX_CURVE_RECURSION} subdivisions.`, {
|
|
103
|
+
maxRecursion: MAX_CURVE_RECURSION,
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
const ab = midpoint(start, first);
|
|
107
|
+
const bc = midpoint(first, second);
|
|
108
|
+
const cd = midpoint(second, end);
|
|
109
|
+
const abc = midpoint(ab, bc);
|
|
110
|
+
const bcd = midpoint(bc, cd);
|
|
111
|
+
const split = midpoint(abc, bcd);
|
|
112
|
+
flattenCubic(start, ab, abc, split, resolution, depth + 1, output, budget, operation);
|
|
113
|
+
flattenCubic(split, bcd, cd, end, resolution, depth + 1, output, budget, operation);
|
|
114
|
+
}
|
|
115
|
+
function endpointArcToCenter(start, end, radiusX, radiusY, rotationDegrees, largeArc, sweep) {
|
|
116
|
+
let rx = Math.abs(radiusX);
|
|
117
|
+
let ry = Math.abs(radiusY);
|
|
118
|
+
if (rx === 0 || ry === 0 || squaredDistance(start, end) <= 1e-24)
|
|
119
|
+
return null;
|
|
120
|
+
const phi = ((rotationDegrees % 360) * Math.PI) / 180;
|
|
121
|
+
const cosPhi = Math.cos(phi);
|
|
122
|
+
const sinPhi = Math.sin(phi);
|
|
123
|
+
const halfDx = (start[0] - end[0]) / 2;
|
|
124
|
+
const halfDy = (start[1] - end[1]) / 2;
|
|
125
|
+
const xPrime = cosPhi * halfDx + sinPhi * halfDy;
|
|
126
|
+
const yPrime = -sinPhi * halfDx + cosPhi * halfDy;
|
|
127
|
+
const lambda = (xPrime * xPrime) / (rx * rx) + (yPrime * yPrime) / (ry * ry);
|
|
128
|
+
if (lambda > 1) {
|
|
129
|
+
const scale = Math.sqrt(lambda);
|
|
130
|
+
rx *= scale;
|
|
131
|
+
ry *= scale;
|
|
132
|
+
}
|
|
133
|
+
const rxSquared = rx * rx;
|
|
134
|
+
const rySquared = ry * ry;
|
|
135
|
+
const numerator = Math.max(0, rxSquared * rySquared - rxSquared * yPrime * yPrime - rySquared * xPrime * xPrime);
|
|
136
|
+
const denominator = rxSquared * yPrime * yPrime + rySquared * xPrime * xPrime;
|
|
137
|
+
const sign = largeArc === sweep ? -1 : 1;
|
|
138
|
+
const coefficient = denominator === 0 ? 0 : sign * Math.sqrt(numerator / denominator);
|
|
139
|
+
const centerPrimeX = (coefficient * rx * yPrime) / ry;
|
|
140
|
+
const centerPrimeY = (-coefficient * ry * xPrime) / rx;
|
|
141
|
+
const center = [
|
|
142
|
+
cosPhi * centerPrimeX - sinPhi * centerPrimeY + (start[0] + end[0]) / 2,
|
|
143
|
+
sinPhi * centerPrimeX + cosPhi * centerPrimeY + (start[1] + end[1]) / 2,
|
|
144
|
+
];
|
|
145
|
+
const vectorAngle = (ux, uy, vx, vy) => Math.atan2(ux * vy - uy * vx, ux * vx + uy * vy);
|
|
146
|
+
const ux = (xPrime - centerPrimeX) / rx;
|
|
147
|
+
const uy = (yPrime - centerPrimeY) / ry;
|
|
148
|
+
const vx = (-xPrime - centerPrimeX) / rx;
|
|
149
|
+
const vy = (-yPrime - centerPrimeY) / ry;
|
|
150
|
+
const startAngle = Math.atan2(uy, ux);
|
|
151
|
+
let delta = vectorAngle(ux, uy, vx, vy);
|
|
152
|
+
if (!sweep && delta > 0)
|
|
153
|
+
delta -= Math.PI * 2;
|
|
154
|
+
if (sweep && delta < 0)
|
|
155
|
+
delta += Math.PI * 2;
|
|
156
|
+
return { center, delta, phi, radii: [rx, ry], startAngle };
|
|
157
|
+
}
|
|
158
|
+
function arcPoint(arc, angle) {
|
|
159
|
+
const cosPhi = Math.cos(arc.phi);
|
|
160
|
+
const sinPhi = Math.sin(arc.phi);
|
|
161
|
+
const x = arc.radii[0] * Math.cos(angle);
|
|
162
|
+
const y = arc.radii[1] * Math.sin(angle);
|
|
163
|
+
return [arc.center[0] + cosPhi * x - sinPhi * y, arc.center[1] + sinPhi * x + cosPhi * y];
|
|
164
|
+
}
|
|
165
|
+
function arcSecondDerivativeBound(arc, matrix) {
|
|
166
|
+
const cosPhi = Math.cos(arc.phi);
|
|
167
|
+
const sinPhi = Math.sin(arc.phi);
|
|
168
|
+
const basisX = [cosPhi * arc.radii[0], sinPhi * arc.radii[0]];
|
|
169
|
+
const basisY = [-sinPhi * arc.radii[1], cosPhi * arc.radii[1]];
|
|
170
|
+
const transformVector = (vector) => [
|
|
171
|
+
matrix[0] * vector[0] + matrix[2] * vector[1],
|
|
172
|
+
matrix[1] * vector[0] + matrix[3] * vector[1],
|
|
173
|
+
];
|
|
174
|
+
const transformedX = transformVector(basisX);
|
|
175
|
+
const transformedY = transformVector(basisY);
|
|
176
|
+
return Math.hypot(...transformedX) + Math.hypot(...transformedY);
|
|
177
|
+
}
|
|
178
|
+
function flattenArcRange(arc, matrix, from, to, end, resolution, output, budget, operation) {
|
|
179
|
+
// The transformed second-derivative norm is a conservative effective radius for eccentric
|
|
180
|
+
// ellipses under arbitrary affine transforms. Circular exact counts and both physical criteria
|
|
181
|
+
// therefore apply in final millimetre space.
|
|
182
|
+
const effectiveRadius = arcSecondDerivativeBound(arc, matrix);
|
|
183
|
+
const segments = resolveArcSegments(effectiveRadius, ((to - from) * 180) / Math.PI, resolution, budget.limits);
|
|
184
|
+
assertSamplingCount(segments, operation, budget.limits.maxProfilePoints);
|
|
185
|
+
for (let index = 1; index <= segments; index += 1) {
|
|
186
|
+
const point = index === segments
|
|
187
|
+
? end
|
|
188
|
+
: transformPoint(matrix, arcPoint(arc, from + ((to - from) * index) / segments));
|
|
189
|
+
pushUnique(output, point, budget, operation);
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
function hasNonCollinearVertices(contour) {
|
|
193
|
+
const first = contour[0];
|
|
194
|
+
if (first === undefined)
|
|
195
|
+
return false;
|
|
196
|
+
const second = contour.find((point) => squaredDistance(first, point) > 1e-24);
|
|
197
|
+
if (second === undefined)
|
|
198
|
+
return false;
|
|
199
|
+
const dx = second[0] - first[0];
|
|
200
|
+
const dy = second[1] - first[1];
|
|
201
|
+
return contour.some((point) => Math.abs(dx * (point[1] - first[1]) - dy * (point[0] - first[0])) > 1e-12);
|
|
202
|
+
}
|
|
203
|
+
function finalizeSubpath(contour, output, closed, budget) {
|
|
204
|
+
if (closed && contour.length > 1 && squaredDistance(contour[0], contour.at(-1)) <= 1e-24) {
|
|
205
|
+
contour.pop();
|
|
206
|
+
budget.removeSeam();
|
|
207
|
+
}
|
|
208
|
+
if (contour.length >= 2)
|
|
209
|
+
output.push({ closed, points: contour });
|
|
210
|
+
}
|
|
211
|
+
function commandPoint(command) {
|
|
212
|
+
return [command.x, command.y];
|
|
213
|
+
}
|
|
214
|
+
/** Flattens complete SVG path data into transformed, non-seam-duplicated subpaths. */
|
|
215
|
+
export function flattenSvgSubpaths(pathData, matrix, policy, budget, operation = "svgPath") {
|
|
216
|
+
const resolution = resolveGeometryResolution(policy, budget.limits);
|
|
217
|
+
let commands;
|
|
218
|
+
try {
|
|
219
|
+
const path = new SVGPathData(pathData).toAbs();
|
|
220
|
+
// Count source controls before shorthand expansion adds implicit S/T control points.
|
|
221
|
+
for (const command of path.commands) {
|
|
222
|
+
switch (command.type) {
|
|
223
|
+
case SVGPathData.CURVE_TO:
|
|
224
|
+
budget.addParsed(3, operation);
|
|
225
|
+
break;
|
|
226
|
+
case SVGPathData.SMOOTH_CURVE_TO:
|
|
227
|
+
case SVGPathData.QUAD_TO:
|
|
228
|
+
budget.addParsed(2, operation);
|
|
229
|
+
break;
|
|
230
|
+
case SVGPathData.CLOSE_PATH:
|
|
231
|
+
break;
|
|
232
|
+
default:
|
|
233
|
+
budget.addParsed(1, operation);
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
// Preserve explicit closure and Bézier sampling; only expand shorthand commands.
|
|
237
|
+
commands = path
|
|
238
|
+
.transform(SVGPathDataTransformer.NORMALIZE_HVZ(false, true, true, false))
|
|
239
|
+
.normalizeST().commands;
|
|
240
|
+
}
|
|
241
|
+
catch (error) {
|
|
242
|
+
rethrowAsInvalid(operation, error);
|
|
243
|
+
}
|
|
244
|
+
const subpaths = [];
|
|
245
|
+
let contour = [];
|
|
246
|
+
let current = [0, 0];
|
|
247
|
+
let start = [0, 0];
|
|
248
|
+
const startContour = (point) => {
|
|
249
|
+
finalizeSubpath(contour, subpaths, false, budget);
|
|
250
|
+
contour = [];
|
|
251
|
+
current = point;
|
|
252
|
+
start = point;
|
|
253
|
+
pushUnique(contour, transformPoint(matrix, point), budget, operation);
|
|
254
|
+
};
|
|
255
|
+
const lineTo = (point) => {
|
|
256
|
+
const transformedStart = transformPoint(matrix, current);
|
|
257
|
+
const transformedEnd = transformPoint(matrix, point);
|
|
258
|
+
if (![...transformedStart, ...transformedEnd].every(Number.isFinite))
|
|
259
|
+
invalid(operation, "SVG geometry produced a non-finite vertex.");
|
|
260
|
+
const segments = resolution.maxSegmentLength === undefined
|
|
261
|
+
? 1
|
|
262
|
+
: Math.max(1, Math.ceil(Math.sqrt(squaredDistance(transformedStart, transformedEnd)) /
|
|
263
|
+
resolution.maxSegmentLength));
|
|
264
|
+
assertSamplingCount(segments, operation, budget.limits.maxProfilePoints);
|
|
265
|
+
for (let index = 1; index <= segments; index += 1) {
|
|
266
|
+
pushUnique(contour, index === segments
|
|
267
|
+
? transformedEnd
|
|
268
|
+
: [
|
|
269
|
+
transformedStart[0] + ((transformedEnd[0] - transformedStart[0]) * index) / segments,
|
|
270
|
+
transformedStart[1] + ((transformedEnd[1] - transformedStart[1]) * index) / segments,
|
|
271
|
+
], budget, operation);
|
|
272
|
+
}
|
|
273
|
+
current = point;
|
|
274
|
+
};
|
|
275
|
+
for (const command of commands) {
|
|
276
|
+
if (contour.length === 0 &&
|
|
277
|
+
command.type !== SVGPathData.MOVE_TO &&
|
|
278
|
+
command.type !== SVGPathData.CLOSE_PATH) {
|
|
279
|
+
pushUnique(contour, transformPoint(matrix, current), budget, operation);
|
|
280
|
+
start = current;
|
|
281
|
+
}
|
|
282
|
+
switch (command.type) {
|
|
283
|
+
case SVGPathData.MOVE_TO:
|
|
284
|
+
startContour(commandPoint(command));
|
|
285
|
+
break;
|
|
286
|
+
case SVGPathData.LINE_TO:
|
|
287
|
+
lineTo(commandPoint(command));
|
|
288
|
+
break;
|
|
289
|
+
case SVGPathData.CURVE_TO: {
|
|
290
|
+
const first = [command.x1, command.y1];
|
|
291
|
+
const second = [command.x2, command.y2];
|
|
292
|
+
const end = commandPoint(command);
|
|
293
|
+
flattenCubic(transformPoint(matrix, current), transformPoint(matrix, first), transformPoint(matrix, second), transformPoint(matrix, end), resolution, 0, contour, budget, operation);
|
|
294
|
+
current = end;
|
|
295
|
+
break;
|
|
296
|
+
}
|
|
297
|
+
case SVGPathData.QUAD_TO: {
|
|
298
|
+
const control = [command.x1, command.y1];
|
|
299
|
+
const end = commandPoint(command);
|
|
300
|
+
flattenQuadratic(transformPoint(matrix, current), transformPoint(matrix, control), transformPoint(matrix, end), resolution, 0, contour, budget, operation);
|
|
301
|
+
current = end;
|
|
302
|
+
break;
|
|
303
|
+
}
|
|
304
|
+
case SVGPathData.ARC: {
|
|
305
|
+
const end = commandPoint(command);
|
|
306
|
+
const arc = endpointArcToCenter(current, end, command.rX, command.rY, command.xRot, command.lArcFlag === 1, command.sweepFlag === 1);
|
|
307
|
+
if (arc === null) {
|
|
308
|
+
lineTo(end);
|
|
309
|
+
}
|
|
310
|
+
else {
|
|
311
|
+
flattenArcRange(arc, matrix, arc.startAngle, arc.startAngle + arc.delta, transformPoint(matrix, end), resolution, contour, budget, operation);
|
|
312
|
+
current = end;
|
|
313
|
+
}
|
|
314
|
+
break;
|
|
315
|
+
}
|
|
316
|
+
case SVGPathData.CLOSE_PATH: {
|
|
317
|
+
if (resolution.maxSegmentLength !== undefined && squaredDistance(current, start) > 1e-24) {
|
|
318
|
+
const transformedCurrent = transformPoint(matrix, current);
|
|
319
|
+
const transformedStart = transformPoint(matrix, start);
|
|
320
|
+
const segments = Math.max(1, Math.ceil(Math.sqrt(squaredDistance(transformedCurrent, transformedStart)) /
|
|
321
|
+
resolution.maxSegmentLength));
|
|
322
|
+
assertSamplingCount(segments, operation, budget.limits.maxProfilePoints);
|
|
323
|
+
for (let index = 1; index < segments; index += 1) {
|
|
324
|
+
pushUnique(contour, [
|
|
325
|
+
transformedCurrent[0] +
|
|
326
|
+
((transformedStart[0] - transformedCurrent[0]) * index) / segments,
|
|
327
|
+
transformedCurrent[1] +
|
|
328
|
+
((transformedStart[1] - transformedCurrent[1]) * index) / segments,
|
|
329
|
+
], budget, operation);
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
current = start;
|
|
333
|
+
finalizeSubpath(contour, subpaths, true, budget);
|
|
334
|
+
contour = [];
|
|
335
|
+
break;
|
|
336
|
+
}
|
|
337
|
+
default:
|
|
338
|
+
invalid(operation, "Unsupported normalized SVG path command.");
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
finalizeSubpath(contour, subpaths, false, budget);
|
|
342
|
+
return subpaths;
|
|
343
|
+
}
|
|
344
|
+
/** Flattens a complete SVG path-data grammar into non-seam-duplicated filled contours. */
|
|
345
|
+
export function flattenSvgPath(pathData, matrix, policy, budget, operation = "svgPath") {
|
|
346
|
+
return flattenSvgSubpaths(pathData, matrix, policy, budget, operation)
|
|
347
|
+
.map((subpath) => [...subpath.points])
|
|
348
|
+
.filter((contour) => contour.length >= 3 && hasNonCollinearVertices(contour));
|
|
349
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|