modern-path2d 1.4.0 → 1.4.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -1008,271 +1008,800 @@ function svgPathDataToCommands(data) {
1008
1008
  return commands;
1009
1009
  }
1010
1010
 
1011
- function catmullRom(t, p0, p1, p2, p3) {
1012
- const v0 = (p2 - p0) * 0.5;
1013
- const v1 = (p3 - p1) * 0.5;
1014
- const t2 = t * t;
1015
- const t3 = t * t2;
1016
- return (2 * p1 - 2 * p2 + v0 + v1) * t3 + (-3 * p1 + 3 * p2 - 2 * v0 - v1) * t2 + v0 * t + p1;
1017
- }
1018
-
1019
- function cubicBezierP0(t, p) {
1020
- const k = 1 - t;
1021
- return k * k * k * p;
1022
- }
1023
- function cubicBezierP1(t, p) {
1024
- const k = 1 - t;
1025
- return 3 * k * k * t * p;
1026
- }
1027
- function cubicBezierP2(t, p) {
1028
- return 3 * (1 - t) * t * t * p;
1029
- }
1030
- function cubicBezierP3(t, p) {
1031
- return t * t * t * p;
1032
- }
1033
- function cubicBezier(t, p0, p1, p2, p3) {
1034
- return cubicBezierP0(t, p0) + cubicBezierP1(t, p1) + cubicBezierP2(t, p2) + cubicBezierP3(t, p3);
1035
- }
1036
-
1037
- function fillTriangulate(pointArray, options = {}) {
1038
- let {
1039
- vertices = [],
1040
- indices = [],
1041
- holes = [],
1042
- verticesStride = 2,
1043
- verticesOffset = vertices.length / verticesStride,
1044
- indicesOffset = indices.length
1045
- } = options;
1046
- const triangles = earcut__default(pointArray, holes, 2);
1047
- if (triangles.length) {
1048
- for (let i = 0; i < triangles.length; i += 3) {
1049
- indices[indicesOffset++] = triangles[i] + verticesOffset;
1050
- indices[indicesOffset++] = triangles[i + 1] + verticesOffset;
1051
- indices[indicesOffset++] = triangles[i + 2] + verticesOffset;
1011
+ const dataUri = "data:image/svg+xml;";
1012
+ const base64DataUri = `${dataUri}base64,`;
1013
+ const utf8DataUri = `${dataUri}charset=utf8,`;
1014
+ function svgToDom(svg) {
1015
+ if (typeof svg === "string") {
1016
+ let xml;
1017
+ if (svg.startsWith(base64DataUri)) {
1018
+ svg = svg.substring(base64DataUri.length, svg.length);
1019
+ xml = atob(svg);
1020
+ } else if (svg.startsWith(utf8DataUri)) {
1021
+ svg = svg.substring(utf8DataUri.length, svg.length);
1022
+ xml = decodeURIComponent(svg);
1023
+ } else {
1024
+ xml = svg;
1052
1025
  }
1053
- let index = verticesOffset * verticesStride;
1054
- for (let i = 0; i < pointArray.length; i += 2) {
1055
- vertices[index] = pointArray[i];
1056
- vertices[index + 1] = pointArray[i + 1];
1057
- index += verticesStride;
1026
+ const doc = new DOMParser().parseFromString(xml, "text/xml");
1027
+ const error = doc.querySelector("parsererror");
1028
+ if (error) {
1029
+ throw new Error(`${error.textContent ?? "parser error"}
1030
+ ${xml}`);
1058
1031
  }
1032
+ return doc.documentElement;
1033
+ } else {
1034
+ return svg;
1059
1035
  }
1060
- return {
1061
- vertices,
1062
- indices
1063
- };
1064
1036
  }
1065
1037
 
1066
- const RECURSION_LIMIT$1 = 8;
1067
- const FLT_EPSILON$1 = 11920929e-14;
1068
- const PATH_DISTANCE_EPSILON$1 = 1;
1069
- function getAdaptiveCubicBezierCurvePoints(sX, sY, x1, y1, x2, y2, x, y, smoothness = 0.5, points = []) {
1070
- const scale = 1;
1071
- const smoothing = Math.min(
1072
- 0.99,
1073
- // a value of 1.0 actually inverts smoothing, so we cap it at 0.99
1074
- Math.max(0, smoothness)
1075
- );
1076
- let distanceTolerance = (PATH_DISTANCE_EPSILON$1 - smoothing) / scale;
1077
- distanceTolerance *= distanceTolerance;
1078
- recursive$1(sX, sY, x1, y1, x2, y2, x, y, points, distanceTolerance, 0);
1079
- points.push(x, y);
1080
- return points;
1081
- }
1082
- function recursive$1(x1, y1, x2, y2, x3, y3, x4, y4, points, distanceTolerance, level) {
1083
- if (level > RECURSION_LIMIT$1)
1084
- return;
1085
- const x12 = (x1 + x2) / 2;
1086
- const y12 = (y1 + y2) / 2;
1087
- const x23 = (x2 + x3) / 2;
1088
- const y23 = (y2 + y3) / 2;
1089
- const x34 = (x3 + x4) / 2;
1090
- const y34 = (y3 + y4) / 2;
1091
- const x123 = (x12 + x23) / 2;
1092
- const y123 = (y12 + y23) / 2;
1093
- const x234 = (x23 + x34) / 2;
1094
- const y234 = (y23 + y34) / 2;
1095
- const x1234 = (x123 + x234) / 2;
1096
- const y1234 = (y123 + y234) / 2;
1097
- if (level > 0) {
1098
- let dx = x4 - x1;
1099
- let dy = y4 - y1;
1100
- const d2 = Math.abs((x2 - x4) * dy - (y2 - y4) * dx);
1101
- const d3 = Math.abs((x3 - x4) * dy - (y3 - y4) * dx);
1102
- if (d2 > FLT_EPSILON$1 && d3 > FLT_EPSILON$1) {
1103
- if ((d2 + d3) * (d2 + d3) <= distanceTolerance * (dx * dx + dy * dy)) {
1104
- {
1105
- points.push(x1234, y1234);
1106
- return;
1107
- }
1108
- }
1109
- } else if (d2 > FLT_EPSILON$1) {
1110
- if (d2 * d2 <= distanceTolerance * (dx * dx + dy * dy)) {
1111
- {
1112
- points.push(x1234, y1234);
1113
- return;
1114
- }
1115
- }
1116
- } else if (d3 > FLT_EPSILON$1) {
1117
- if (d3 * d3 <= distanceTolerance * (dx * dx + dy * dy)) {
1118
- {
1119
- points.push(x1234, y1234);
1120
- return;
1121
- }
1122
- }
1123
- } else {
1124
- dx = x1234 - (x1 + x4) / 2;
1125
- dy = y1234 - (y1 + y4) / 2;
1126
- if (dx * dx + dy * dy <= distanceTolerance) {
1127
- points.push(x1234, y1234);
1128
- return;
1129
- }
1130
- }
1038
+ const defaultUnit = "px";
1039
+ const defaultDPI = 90;
1040
+ const units = ["mm", "cm", "in", "pt", "pc", "px"];
1041
+ const unitConversion = {
1042
+ mm: {
1043
+ mm: 1,
1044
+ cm: 0.1,
1045
+ in: 1 / 25.4,
1046
+ pt: 72 / 25.4,
1047
+ pc: 6 / 25.4,
1048
+ px: -1
1049
+ },
1050
+ cm: {
1051
+ mm: 10,
1052
+ cm: 1,
1053
+ in: 1 / 2.54,
1054
+ pt: 72 / 2.54,
1055
+ pc: 6 / 2.54,
1056
+ px: -1
1057
+ },
1058
+ in: {
1059
+ mm: 25.4,
1060
+ cm: 2.54,
1061
+ in: 1,
1062
+ pt: 72,
1063
+ pc: 6,
1064
+ px: -1
1065
+ },
1066
+ pt: {
1067
+ mm: 25.4 / 72,
1068
+ cm: 2.54 / 72,
1069
+ in: 1 / 72,
1070
+ pt: 1,
1071
+ pc: 6 / 72,
1072
+ px: -1
1073
+ },
1074
+ pc: {
1075
+ mm: 25.4 / 6,
1076
+ cm: 2.54 / 6,
1077
+ in: 1 / 6,
1078
+ pt: 72 / 6,
1079
+ pc: 1,
1080
+ px: -1
1081
+ },
1082
+ px: {
1083
+ px: 1
1131
1084
  }
1132
- recursive$1(x1, y1, x12, y12, x123, y123, x1234, y1234, points, distanceTolerance, level + 1);
1133
- recursive$1(x1234, y1234, x234, y234, x34, y34, x4, y4, points, distanceTolerance, level + 1);
1134
- }
1135
-
1136
- const RECURSION_LIMIT = 8;
1137
- const FLT_EPSILON = 11920929e-14;
1138
- const PATH_DISTANCE_EPSILON = 1;
1139
- function getAdaptiveQuadraticBezierCurvePoints(sX, sY, x1, y1, x, y, smoothness = 0.5, points = []) {
1140
- const scale = 1;
1141
- const smoothing = Math.min(
1142
- 0.99,
1143
- // a value of 1.0 actually inverts smoothing, so we cap it at 0.99
1144
- Math.max(0, smoothness)
1145
- );
1146
- let distanceTolerance = (PATH_DISTANCE_EPSILON - smoothing) / scale;
1147
- distanceTolerance *= distanceTolerance;
1148
- recursive(points, sX, sY, x1, y1, x, y, distanceTolerance, 0);
1149
- points.push(x, y);
1150
- return points;
1151
- }
1152
- function recursive(points, x1, y1, x2, y2, x3, y3, distanceTolerance, level) {
1153
- if (level > RECURSION_LIMIT)
1154
- return;
1155
- const x12 = (x1 + x2) / 2;
1156
- const y12 = (y1 + y2) / 2;
1157
- const x23 = (x2 + x3) / 2;
1158
- const y23 = (y2 + y3) / 2;
1159
- const x123 = (x12 + x23) / 2;
1160
- const y123 = (y12 + y23) / 2;
1161
- let dx = x3 - x1;
1162
- let dy = y3 - y1;
1163
- const d = Math.abs((x2 - x3) * dy - (y2 - y3) * dx);
1164
- if (d > FLT_EPSILON) {
1165
- if (d * d <= distanceTolerance * (dx * dx + dy * dy)) {
1166
- {
1167
- points.push(x123, y123);
1168
- return;
1085
+ };
1086
+ function parseFloatWithUnits(string) {
1087
+ let theUnit = "px";
1088
+ if (typeof string === "string") {
1089
+ for (let i = 0, n = units.length; i < n; i++) {
1090
+ const u = units[i];
1091
+ if (string.endsWith(u)) {
1092
+ theUnit = u;
1093
+ string = string.substring(0, string.length - u.length);
1094
+ break;
1169
1095
  }
1170
1096
  }
1171
- } else {
1172
- dx = x123 - (x1 + x3) / 2;
1173
- dy = y123 - (y1 + y3) / 2;
1174
- if (dx * dx + dy * dy <= distanceTolerance) {
1175
- points.push(x123, y123);
1176
- return;
1097
+ }
1098
+ let scale;
1099
+ {
1100
+ scale = unitConversion[theUnit][defaultUnit];
1101
+ if (scale < 0) {
1102
+ scale = unitConversion[theUnit].in * defaultDPI;
1177
1103
  }
1178
1104
  }
1179
- recursive(points, x1, y1, x12, y12, x123, y123, distanceTolerance, level + 1);
1180
- recursive(points, x123, y123, x23, y23, x3, y3, distanceTolerance, level + 1);
1105
+ return scale * Number.parseFloat(string);
1181
1106
  }
1182
1107
 
1183
- function getDirectedArea(vertices) {
1184
- let area = 0;
1185
- const n = vertices.length;
1186
- for (let i = 0; i < n; i += 2) {
1187
- const x0 = vertices[i];
1188
- const y0 = vertices[i + 1];
1189
- const x1 = vertices[(i + 2) % (n - 1)];
1190
- const y1 = vertices[(i + 3) % n];
1191
- area += x0 * y1 - x1 * y0;
1192
- }
1193
- return area / 2;
1194
- }
1195
-
1196
- function pointInPolygon(point, polygon) {
1197
- let inside = false;
1198
- const [x, y] = point;
1199
- const len = polygon.length / 2;
1200
- for (let i = 0, j = len - 1; i < len; j = i++) {
1201
- const xi = polygon[i * 2];
1202
- const yi = polygon[i * 2 + 1];
1203
- const xj = polygon[j * 2];
1204
- const yj = polygon[j * 2 + 1];
1205
- if (yi > y !== yj > y && x < (xj - xi) * (y - yi) / (yj - yi) + xi) {
1206
- inside = !inside;
1108
+ const tempTransform0$1 = new Matrix3();
1109
+ const tempTransform1$1 = new Matrix3();
1110
+ const tempTransform2$1 = new Matrix3();
1111
+ const tempTransform3 = new Matrix3();
1112
+ function getNodeTransform(node, currentTransform, transformStack) {
1113
+ if (!(node.hasAttribute("transform") || node.nodeName === "use" && (node.hasAttribute("x") || node.hasAttribute("y")))) {
1114
+ return null;
1115
+ }
1116
+ const transform = parseNodeTransform(node);
1117
+ if (transformStack.length > 0) {
1118
+ transform.premultiply(transformStack[transformStack.length - 1]);
1119
+ }
1120
+ currentTransform.copy(transform);
1121
+ transformStack.push(transform);
1122
+ return transform;
1123
+ }
1124
+ function parseNodeTransform(node) {
1125
+ const transform = new Matrix3();
1126
+ const currentTransform = tempTransform0$1;
1127
+ if (node.nodeName === "use" && (node.hasAttribute("x") || node.hasAttribute("y"))) {
1128
+ transform.translate(
1129
+ parseFloatWithUnits(node.getAttribute("x")),
1130
+ parseFloatWithUnits(node.getAttribute("y"))
1131
+ );
1132
+ }
1133
+ if (node.hasAttribute("transform")) {
1134
+ const transformsTexts = node.getAttribute("transform").split(")");
1135
+ for (let tIndex = transformsTexts.length - 1; tIndex >= 0; tIndex--) {
1136
+ const transformText = transformsTexts[tIndex].trim();
1137
+ if (transformText === "")
1138
+ continue;
1139
+ const openParPos = transformText.indexOf("(");
1140
+ const closeParPos = transformText.length;
1141
+ if (openParPos > 0 && openParPos < closeParPos) {
1142
+ const transformType = transformText.slice(0, openParPos);
1143
+ const array = parsePathDataArgs(transformText.slice(openParPos + 1));
1144
+ currentTransform.identity();
1145
+ switch (transformType) {
1146
+ case "translate":
1147
+ if (array.length >= 1) {
1148
+ const tx = array[0];
1149
+ let ty = 0;
1150
+ if (array.length >= 2) {
1151
+ ty = array[1];
1152
+ }
1153
+ currentTransform.translate(tx, ty);
1154
+ }
1155
+ break;
1156
+ case "rotate":
1157
+ if (array.length >= 1) {
1158
+ let angle = 0;
1159
+ let cx = 0;
1160
+ let cy = 0;
1161
+ angle = array[0] * Math.PI / 180;
1162
+ if (array.length >= 3) {
1163
+ cx = array[1];
1164
+ cy = array[2];
1165
+ }
1166
+ tempTransform1$1.makeTranslation(-cx, -cy);
1167
+ tempTransform2$1.makeRotation(angle);
1168
+ tempTransform3.multiplyMatrices(tempTransform2$1, tempTransform1$1);
1169
+ tempTransform1$1.makeTranslation(cx, cy);
1170
+ currentTransform.multiplyMatrices(tempTransform1$1, tempTransform3);
1171
+ }
1172
+ break;
1173
+ case "scale":
1174
+ if (array.length >= 1) {
1175
+ currentTransform.scale(
1176
+ array[0],
1177
+ array[1] ?? array[0]
1178
+ );
1179
+ }
1180
+ break;
1181
+ case "skewX":
1182
+ if (array.length === 1) {
1183
+ currentTransform.set(
1184
+ 1,
1185
+ Math.tan(array[0] * Math.PI / 180),
1186
+ 0,
1187
+ 0,
1188
+ 1,
1189
+ 0,
1190
+ 0,
1191
+ 0,
1192
+ 1
1193
+ );
1194
+ }
1195
+ break;
1196
+ case "skewY":
1197
+ if (array.length === 1) {
1198
+ currentTransform.set(
1199
+ 1,
1200
+ 0,
1201
+ 0,
1202
+ Math.tan(array[0] * Math.PI / 180),
1203
+ 1,
1204
+ 0,
1205
+ 0,
1206
+ 0,
1207
+ 1
1208
+ );
1209
+ }
1210
+ break;
1211
+ case "matrix":
1212
+ if (array.length === 6) {
1213
+ currentTransform.set(
1214
+ array[0],
1215
+ array[2],
1216
+ array[4],
1217
+ array[1],
1218
+ array[3],
1219
+ array[5],
1220
+ 0,
1221
+ 0,
1222
+ 1
1223
+ );
1224
+ }
1225
+ break;
1226
+ }
1227
+ }
1228
+ transform.premultiply(currentTransform);
1207
1229
  }
1208
1230
  }
1209
- return inside;
1231
+ return transform;
1210
1232
  }
1211
1233
 
1212
- function quadraticBezierP0(t, p) {
1213
- const k = 1 - t;
1214
- return k * k * p;
1234
+ function parseCircleNode(node) {
1235
+ return new Path2D().arc(
1236
+ parseFloatWithUnits(node.getAttribute("cx") || 0),
1237
+ parseFloatWithUnits(node.getAttribute("cy") || 0),
1238
+ parseFloatWithUnits(node.getAttribute("r") || 0),
1239
+ 0,
1240
+ Math.PI * 2
1241
+ );
1215
1242
  }
1216
- function quadraticBezierP1(t, p) {
1217
- return 2 * (1 - t) * t * p;
1243
+
1244
+ function parseCSSStylesheet(node, stylesheets) {
1245
+ if (!node.sheet || !node.sheet.cssRules || !node.sheet.cssRules.length)
1246
+ return;
1247
+ for (let i = 0; i < node.sheet.cssRules.length; i++) {
1248
+ const stylesheet = node.sheet.cssRules[i];
1249
+ if (stylesheet.type !== 1)
1250
+ continue;
1251
+ const selectorList = stylesheet.selectorText.split(/,/g).filter(Boolean).map((i2) => i2.trim());
1252
+ const definitions = {};
1253
+ for (let len = stylesheet.style.length, i2 = 0; i2 < len; i2++) {
1254
+ const name = stylesheet.style.item(i2);
1255
+ definitions[name] = stylesheet.style.getPropertyValue(name);
1256
+ }
1257
+ for (let j = 0; j < selectorList.length; j++) {
1258
+ stylesheets[selectorList[j]] = Object.assign(
1259
+ stylesheets[selectorList[j]] || {},
1260
+ { ...definitions }
1261
+ );
1262
+ }
1263
+ }
1218
1264
  }
1219
- function quadraticBezierP2(t, p) {
1220
- return t * t * p;
1265
+
1266
+ function parseEllipseNode(node) {
1267
+ return new Path2D().ellipse(
1268
+ parseFloatWithUnits(node.getAttribute("cx") || 0),
1269
+ parseFloatWithUnits(node.getAttribute("cy") || 0),
1270
+ parseFloatWithUnits(node.getAttribute("rx") || 0),
1271
+ parseFloatWithUnits(node.getAttribute("ry") || 0),
1272
+ 0,
1273
+ 0,
1274
+ Math.PI * 2
1275
+ );
1221
1276
  }
1222
- function quadraticBezier(t, p0, p1, p2) {
1223
- return quadraticBezierP0(t, p0) + quadraticBezierP1(t, p1) + quadraticBezierP2(t, p2);
1277
+
1278
+ function parseLineNode(node) {
1279
+ return new Path2D().moveTo(
1280
+ parseFloatWithUnits(node.getAttribute("x1") || 0),
1281
+ parseFloatWithUnits(node.getAttribute("y1") || 0)
1282
+ ).lineTo(
1283
+ parseFloatWithUnits(node.getAttribute("x2") || 0),
1284
+ parseFloatWithUnits(node.getAttribute("y2") || 0)
1285
+ );
1224
1286
  }
1225
1287
 
1226
- const closePointEps = 1e-4;
1227
- const curveEps = 1e-4;
1228
- function strokeTriangulate(points, options = {}) {
1229
- const {
1230
- vertices = [],
1231
- indices = [],
1232
- lineStyle = {
1233
- alignment: 0.5,
1234
- cap: "butt",
1235
- join: "miter",
1236
- width: 1,
1237
- miterLimit: 10
1238
- },
1239
- flipAlignment = false,
1240
- closed = true
1241
- } = options;
1242
- const eps = closePointEps;
1243
- if (points.length === 0) {
1244
- return { vertices, indices };
1245
- }
1246
- const style = lineStyle;
1247
- let alignment = style.alignment;
1248
- if (lineStyle.alignment !== 0.5) {
1249
- let orientation = getOrientationOfPoints(points);
1250
- if (flipAlignment)
1251
- orientation *= -1;
1252
- alignment = (alignment - 0.5) * orientation + 0.5;
1253
- }
1254
- const firstPoint = { x: points[0], y: points[1] };
1255
- const lastPoint = { x: points[points.length - 2], y: points[points.length - 1] };
1256
- const closedShape = closed;
1257
- const closedPath = Math.abs(firstPoint.x - lastPoint.x) < eps && Math.abs(firstPoint.y - lastPoint.y) < eps;
1258
- if (closedShape) {
1259
- points = points.slice();
1260
- if (closedPath) {
1261
- points.pop();
1262
- points.pop();
1263
- lastPoint.x = points[points.length - 2];
1264
- lastPoint.y = points[points.length - 1];
1288
+ function parsePathNode(node) {
1289
+ const path = new Path2D();
1290
+ const d = node.getAttribute("d");
1291
+ if (!d || d === "none")
1292
+ return null;
1293
+ path.addData(d);
1294
+ return path;
1295
+ }
1296
+
1297
+ const RE$1 = /([+-]?(?:\d+(?:\.\d+)?|\.\d+)(?:e[+-]?\d+)?)(?:,|\s)([+-]?\d*\.?\d+(?:e[+-]?\d+)?)/g;
1298
+ function parsePolygonNode(node) {
1299
+ const path = new Path2D();
1300
+ let index = 0;
1301
+ node.getAttribute("points")?.replace(RE$1, (match, a, b) => {
1302
+ const x = parseFloatWithUnits(a);
1303
+ const y = parseFloatWithUnits(b);
1304
+ if (index === 0) {
1305
+ path.moveTo(x, y);
1306
+ } else {
1307
+ path.lineTo(x, y);
1265
1308
  }
1266
- const midPointX = (firstPoint.x + lastPoint.x) * 0.5;
1267
- const midPointY = (lastPoint.y + firstPoint.y) * 0.5;
1268
- points.unshift(midPointX, midPointY);
1269
- points.push(midPointX, midPointY);
1270
- }
1271
- const verts = vertices;
1272
- const length = points.length / 2;
1273
- let indexCount = points.length;
1274
- const indexStart = verts.length / 2;
1275
- const width = style.width / 2;
1309
+ index++;
1310
+ return match;
1311
+ });
1312
+ path.currentCurve.autoClose = true;
1313
+ return path;
1314
+ }
1315
+
1316
+ const RE = /([+-]?(?:\d+(?:\.\d+)?|\.\d+)(?:e[+-]?\d+)?)(?:,|\s)([+-]?\d*\.?\d+(?:e[+-]?\d+)?)/g;
1317
+ function parsePolylineNode(node) {
1318
+ const path = new Path2D();
1319
+ let index = 0;
1320
+ node.getAttribute("points")?.replace(RE, (match, a, b) => {
1321
+ const x = parseFloatWithUnits(a);
1322
+ const y = parseFloatWithUnits(b);
1323
+ if (index === 0) {
1324
+ path.moveTo(x, y);
1325
+ } else {
1326
+ path.lineTo(x, y);
1327
+ }
1328
+ index++;
1329
+ return match;
1330
+ });
1331
+ path.currentCurve.autoClose = false;
1332
+ return path;
1333
+ }
1334
+
1335
+ function parseRectNode(node) {
1336
+ const x = parseFloatWithUnits(node.getAttribute("x") || 0);
1337
+ const y = parseFloatWithUnits(node.getAttribute("y") || 0);
1338
+ const rx = parseFloatWithUnits(node.getAttribute("rx") || node.getAttribute("ry") || 0);
1339
+ const ry = parseFloatWithUnits(node.getAttribute("ry") || node.getAttribute("rx") || 0);
1340
+ const w = parseFloatWithUnits(node.getAttribute("width"));
1341
+ const h = parseFloatWithUnits(node.getAttribute("height"));
1342
+ const bci = 1 - 0.551915024494;
1343
+ const path = new Path2D();
1344
+ path.moveTo(x + rx, y);
1345
+ path.lineTo(x + w - rx, y);
1346
+ if (rx !== 0 || ry !== 0) {
1347
+ path.bezierCurveTo(
1348
+ x + w - rx * bci,
1349
+ y,
1350
+ x + w,
1351
+ y + ry * bci,
1352
+ x + w,
1353
+ y + ry
1354
+ );
1355
+ }
1356
+ path.lineTo(x + w, y + h - ry);
1357
+ if (rx !== 0 || ry !== 0) {
1358
+ path.bezierCurveTo(
1359
+ x + w,
1360
+ y + h - ry * bci,
1361
+ x + w - rx * bci,
1362
+ y + h,
1363
+ x + w - rx,
1364
+ y + h
1365
+ );
1366
+ }
1367
+ path.lineTo(x + rx, y + h);
1368
+ if (rx !== 0 || ry !== 0) {
1369
+ path.bezierCurveTo(
1370
+ x + rx * bci,
1371
+ y + h,
1372
+ x,
1373
+ y + h - ry * bci,
1374
+ x,
1375
+ y + h - ry
1376
+ );
1377
+ }
1378
+ path.lineTo(x, y + ry);
1379
+ if (rx !== 0 || ry !== 0) {
1380
+ path.bezierCurveTo(x, y + ry * bci, x + rx * bci, y, x + rx, y);
1381
+ }
1382
+ return path;
1383
+ }
1384
+
1385
+ function parseStyle(node, style, stylesheets) {
1386
+ style = Object.assign({}, style);
1387
+ let stylesheetStyles = {};
1388
+ if (node.hasAttribute("class")) {
1389
+ const classSelectors = node.getAttribute("class").split(/\s/).filter(Boolean).map((i) => i.trim());
1390
+ for (let i = 0; i < classSelectors.length; i++) {
1391
+ stylesheetStyles = Object.assign(stylesheetStyles, stylesheets[`.${classSelectors[i]}`]);
1392
+ }
1393
+ }
1394
+ if (node.hasAttribute("id")) {
1395
+ stylesheetStyles = Object.assign(stylesheetStyles, stylesheets[`#${node.getAttribute("id")}`]);
1396
+ }
1397
+ for (let len = node.style.length, i = 0; i < len; i++) {
1398
+ const name = node.style.item(i);
1399
+ const value = node.style.getPropertyValue(name);
1400
+ style[name] = value;
1401
+ stylesheetStyles[name] = value;
1402
+ }
1403
+ function addStyle(svgName, jsName, adjustFunction = copy) {
1404
+ if (node.hasAttribute(svgName))
1405
+ style[jsName] = adjustFunction(node.getAttribute(svgName));
1406
+ if (stylesheetStyles[svgName])
1407
+ style[jsName] = adjustFunction(stylesheetStyles[svgName]);
1408
+ }
1409
+ function copy(v) {
1410
+ if (v.startsWith("url"))
1411
+ console.warn("url access in attributes is not implemented.");
1412
+ return v;
1413
+ }
1414
+ function clamp(v) {
1415
+ return Math.max(0, Math.min(1, parseFloatWithUnits(v)));
1416
+ }
1417
+ function positive(v) {
1418
+ return Math.max(0, parseFloatWithUnits(v));
1419
+ }
1420
+ function array(v) {
1421
+ return v.split(" ").filter((v2) => v2 !== "").map((v2) => parseFloatWithUnits(v2));
1422
+ }
1423
+ addStyle("fill", "fill");
1424
+ addStyle("fill-opacity", "fillOpacity", clamp);
1425
+ addStyle("fill-rule", "fillRule");
1426
+ addStyle("opacity", "opacity", clamp);
1427
+ addStyle("stroke", "stroke");
1428
+ addStyle("stroke-opacity", "strokeOpacity", clamp);
1429
+ addStyle("stroke-width", "strokeWidth", positive);
1430
+ addStyle("stroke-linecap", "strokeLinecap");
1431
+ addStyle("stroke-linejoin", "strokeLinejoin");
1432
+ addStyle("stroke-miterlimit", "strokeMiterlimit", positive);
1433
+ addStyle("stroke-dasharray", "strokeDasharray", array);
1434
+ addStyle("stroke-dashoffset", "strokeDashoffset", parseFloatWithUnits);
1435
+ addStyle("visibility", "visibility");
1436
+ return style;
1437
+ }
1438
+
1439
+ function parseNode(node, style, paths = [], stylesheets = {}) {
1440
+ if (node.nodeType !== 1)
1441
+ return paths;
1442
+ let isDefsNode = false;
1443
+ let path = null;
1444
+ let _style = { ...style };
1445
+ switch (node.nodeName) {
1446
+ case "svg":
1447
+ _style = parseStyle(node, _style, stylesheets);
1448
+ break;
1449
+ case "style":
1450
+ parseCSSStylesheet(node, stylesheets);
1451
+ break;
1452
+ case "g":
1453
+ _style = parseStyle(node, _style, stylesheets);
1454
+ break;
1455
+ case "path":
1456
+ _style = parseStyle(node, _style, stylesheets);
1457
+ if (node.hasAttribute("d"))
1458
+ path = parsePathNode(node);
1459
+ break;
1460
+ case "rect":
1461
+ _style = parseStyle(node, _style, stylesheets);
1462
+ path = parseRectNode(node);
1463
+ break;
1464
+ case "polygon":
1465
+ _style = parseStyle(node, _style, stylesheets);
1466
+ path = parsePolygonNode(node);
1467
+ break;
1468
+ case "polyline":
1469
+ _style = parseStyle(node, _style, stylesheets);
1470
+ path = parsePolylineNode(node);
1471
+ break;
1472
+ case "circle":
1473
+ _style = parseStyle(node, _style, stylesheets);
1474
+ path = parseCircleNode(node);
1475
+ break;
1476
+ case "ellipse":
1477
+ _style = parseStyle(node, _style, stylesheets);
1478
+ path = parseEllipseNode(node);
1479
+ break;
1480
+ case "line":
1481
+ _style = parseStyle(node, _style, stylesheets);
1482
+ path = parseLineNode(node);
1483
+ break;
1484
+ case "defs":
1485
+ isDefsNode = true;
1486
+ break;
1487
+ case "use": {
1488
+ _style = parseStyle(node, _style, stylesheets);
1489
+ const href = node.getAttributeNS("http://www.w3.org/1999/xlink", "href") || node.getAttribute("href") || "";
1490
+ const usedNodeId = href.substring(1);
1491
+ const usedNode = node.viewportElement?.getElementById(usedNodeId);
1492
+ if (usedNode) {
1493
+ parseNode(usedNode, _style, paths, stylesheets);
1494
+ } else {
1495
+ console.warn(`'use node' references non-existent node id: ${usedNodeId}`);
1496
+ }
1497
+ break;
1498
+ }
1499
+ default:
1500
+ console.warn(node);
1501
+ break;
1502
+ }
1503
+ if (_style.display === "none") {
1504
+ return paths;
1505
+ }
1506
+ const currentTransform = new Matrix3();
1507
+ const transformStack = [];
1508
+ const transform = getNodeTransform(node, currentTransform, transformStack);
1509
+ if (path) {
1510
+ path.applyTransform(currentTransform);
1511
+ paths.push(path);
1512
+ path.style = { ..._style };
1513
+ }
1514
+ const childNodes = node.childNodes;
1515
+ for (let i = 0, len = childNodes.length; i < len; i++) {
1516
+ const node2 = childNodes[i];
1517
+ if (isDefsNode && node2.nodeName !== "style" && node2.nodeName !== "defs")
1518
+ continue;
1519
+ parseNode(node2, _style, paths, stylesheets);
1520
+ }
1521
+ if (transform) {
1522
+ transformStack.pop();
1523
+ if (transformStack.length > 0) {
1524
+ currentTransform.copy(transformStack[transformStack.length - 1]);
1525
+ } else {
1526
+ currentTransform.identity();
1527
+ }
1528
+ }
1529
+ return paths;
1530
+ }
1531
+
1532
+ function svgToPath2DSet(svg) {
1533
+ const dom = svgToDom(svg);
1534
+ return new Path2DSet(
1535
+ parseNode(dom, {}),
1536
+ dom.getAttribute("viewBox")?.trim().split(" ").map((v) => Number(v))
1537
+ );
1538
+ }
1539
+
1540
+ function catmullRom(t, p0, p1, p2, p3) {
1541
+ const v0 = (p2 - p0) * 0.5;
1542
+ const v1 = (p3 - p1) * 0.5;
1543
+ const t2 = t * t;
1544
+ const t3 = t * t2;
1545
+ return (2 * p1 - 2 * p2 + v0 + v1) * t3 + (-3 * p1 + 3 * p2 - 2 * v0 - v1) * t2 + v0 * t + p1;
1546
+ }
1547
+
1548
+ function cubicBezierP0(t, p) {
1549
+ const k = 1 - t;
1550
+ return k * k * k * p;
1551
+ }
1552
+ function cubicBezierP1(t, p) {
1553
+ const k = 1 - t;
1554
+ return 3 * k * k * t * p;
1555
+ }
1556
+ function cubicBezierP2(t, p) {
1557
+ return 3 * (1 - t) * t * t * p;
1558
+ }
1559
+ function cubicBezierP3(t, p) {
1560
+ return t * t * t * p;
1561
+ }
1562
+ function cubicBezier(t, p0, p1, p2, p3) {
1563
+ return cubicBezierP0(t, p0) + cubicBezierP1(t, p1) + cubicBezierP2(t, p2) + cubicBezierP3(t, p3);
1564
+ }
1565
+
1566
+ function fillTriangulate(pointArray, options = {}) {
1567
+ let {
1568
+ vertices = [],
1569
+ indices = [],
1570
+ holes = [],
1571
+ verticesStride = 2,
1572
+ verticesOffset = vertices.length / verticesStride,
1573
+ indicesOffset = indices.length
1574
+ } = options;
1575
+ const triangles = earcut__default(pointArray, holes, 2);
1576
+ if (triangles.length) {
1577
+ for (let i = 0; i < triangles.length; i += 3) {
1578
+ indices[indicesOffset++] = triangles[i] + verticesOffset;
1579
+ indices[indicesOffset++] = triangles[i + 1] + verticesOffset;
1580
+ indices[indicesOffset++] = triangles[i + 2] + verticesOffset;
1581
+ }
1582
+ let index = verticesOffset * verticesStride;
1583
+ for (let i = 0; i < pointArray.length; i += 2) {
1584
+ vertices[index] = pointArray[i];
1585
+ vertices[index + 1] = pointArray[i + 1];
1586
+ index += verticesStride;
1587
+ }
1588
+ }
1589
+ return {
1590
+ vertices,
1591
+ indices
1592
+ };
1593
+ }
1594
+
1595
+ const RECURSION_LIMIT$1 = 8;
1596
+ const FLT_EPSILON$1 = 11920929e-14;
1597
+ const PATH_DISTANCE_EPSILON$1 = 1;
1598
+ function getAdaptiveCubicBezierCurvePoints(sX, sY, x1, y1, x2, y2, x, y, smoothness = 0.5, points = []) {
1599
+ const scale = 1;
1600
+ const smoothing = Math.min(
1601
+ 0.99,
1602
+ // a value of 1.0 actually inverts smoothing, so we cap it at 0.99
1603
+ Math.max(0, smoothness)
1604
+ );
1605
+ let distanceTolerance = (PATH_DISTANCE_EPSILON$1 - smoothing) / scale;
1606
+ distanceTolerance *= distanceTolerance;
1607
+ recursive$1(sX, sY, x1, y1, x2, y2, x, y, points, distanceTolerance, 0);
1608
+ points.push(x, y);
1609
+ return points;
1610
+ }
1611
+ function recursive$1(x1, y1, x2, y2, x3, y3, x4, y4, points, distanceTolerance, level) {
1612
+ if (level > RECURSION_LIMIT$1)
1613
+ return;
1614
+ const x12 = (x1 + x2) / 2;
1615
+ const y12 = (y1 + y2) / 2;
1616
+ const x23 = (x2 + x3) / 2;
1617
+ const y23 = (y2 + y3) / 2;
1618
+ const x34 = (x3 + x4) / 2;
1619
+ const y34 = (y3 + y4) / 2;
1620
+ const x123 = (x12 + x23) / 2;
1621
+ const y123 = (y12 + y23) / 2;
1622
+ const x234 = (x23 + x34) / 2;
1623
+ const y234 = (y23 + y34) / 2;
1624
+ const x1234 = (x123 + x234) / 2;
1625
+ const y1234 = (y123 + y234) / 2;
1626
+ if (level > 0) {
1627
+ let dx = x4 - x1;
1628
+ let dy = y4 - y1;
1629
+ const d2 = Math.abs((x2 - x4) * dy - (y2 - y4) * dx);
1630
+ const d3 = Math.abs((x3 - x4) * dy - (y3 - y4) * dx);
1631
+ if (d2 > FLT_EPSILON$1 && d3 > FLT_EPSILON$1) {
1632
+ if ((d2 + d3) * (d2 + d3) <= distanceTolerance * (dx * dx + dy * dy)) {
1633
+ {
1634
+ points.push(x1234, y1234);
1635
+ return;
1636
+ }
1637
+ }
1638
+ } else if (d2 > FLT_EPSILON$1) {
1639
+ if (d2 * d2 <= distanceTolerance * (dx * dx + dy * dy)) {
1640
+ {
1641
+ points.push(x1234, y1234);
1642
+ return;
1643
+ }
1644
+ }
1645
+ } else if (d3 > FLT_EPSILON$1) {
1646
+ if (d3 * d3 <= distanceTolerance * (dx * dx + dy * dy)) {
1647
+ {
1648
+ points.push(x1234, y1234);
1649
+ return;
1650
+ }
1651
+ }
1652
+ } else {
1653
+ dx = x1234 - (x1 + x4) / 2;
1654
+ dy = y1234 - (y1 + y4) / 2;
1655
+ if (dx * dx + dy * dy <= distanceTolerance) {
1656
+ points.push(x1234, y1234);
1657
+ return;
1658
+ }
1659
+ }
1660
+ }
1661
+ recursive$1(x1, y1, x12, y12, x123, y123, x1234, y1234, points, distanceTolerance, level + 1);
1662
+ recursive$1(x1234, y1234, x234, y234, x34, y34, x4, y4, points, distanceTolerance, level + 1);
1663
+ }
1664
+
1665
+ const RECURSION_LIMIT = 8;
1666
+ const FLT_EPSILON = 11920929e-14;
1667
+ const PATH_DISTANCE_EPSILON = 1;
1668
+ function getAdaptiveQuadraticBezierCurvePoints(sX, sY, x1, y1, x, y, smoothness = 0.5, points = []) {
1669
+ const scale = 1;
1670
+ const smoothing = Math.min(
1671
+ 0.99,
1672
+ // a value of 1.0 actually inverts smoothing, so we cap it at 0.99
1673
+ Math.max(0, smoothness)
1674
+ );
1675
+ let distanceTolerance = (PATH_DISTANCE_EPSILON - smoothing) / scale;
1676
+ distanceTolerance *= distanceTolerance;
1677
+ recursive(points, sX, sY, x1, y1, x, y, distanceTolerance, 0);
1678
+ points.push(x, y);
1679
+ return points;
1680
+ }
1681
+ function recursive(points, x1, y1, x2, y2, x3, y3, distanceTolerance, level) {
1682
+ if (level > RECURSION_LIMIT)
1683
+ return;
1684
+ const x12 = (x1 + x2) / 2;
1685
+ const y12 = (y1 + y2) / 2;
1686
+ const x23 = (x2 + x3) / 2;
1687
+ const y23 = (y2 + y3) / 2;
1688
+ const x123 = (x12 + x23) / 2;
1689
+ const y123 = (y12 + y23) / 2;
1690
+ let dx = x3 - x1;
1691
+ let dy = y3 - y1;
1692
+ const d = Math.abs((x2 - x3) * dy - (y2 - y3) * dx);
1693
+ if (d > FLT_EPSILON) {
1694
+ if (d * d <= distanceTolerance * (dx * dx + dy * dy)) {
1695
+ {
1696
+ points.push(x123, y123);
1697
+ return;
1698
+ }
1699
+ }
1700
+ } else {
1701
+ dx = x123 - (x1 + x3) / 2;
1702
+ dy = y123 - (y1 + y3) / 2;
1703
+ if (dx * dx + dy * dy <= distanceTolerance) {
1704
+ points.push(x123, y123);
1705
+ return;
1706
+ }
1707
+ }
1708
+ recursive(points, x1, y1, x12, y12, x123, y123, distanceTolerance, level + 1);
1709
+ recursive(points, x123, y123, x23, y23, x3, y3, distanceTolerance, level + 1);
1710
+ }
1711
+
1712
+ function getDirectedArea(vertices) {
1713
+ let area = 0;
1714
+ const n = vertices.length;
1715
+ for (let i = 0; i < n; i += 2) {
1716
+ const x0 = vertices[i];
1717
+ const y0 = vertices[i + 1];
1718
+ const x1 = vertices[(i + 2) % (n - 1)];
1719
+ const y1 = vertices[(i + 3) % n];
1720
+ area += x0 * y1 - x1 * y0;
1721
+ }
1722
+ return area / 2;
1723
+ }
1724
+
1725
+ function pointInPolygon(point, polygon) {
1726
+ let inside = false;
1727
+ const [x, y] = point;
1728
+ const len = polygon.length / 2;
1729
+ for (let i = 0, j = len - 1; i < len; j = i++) {
1730
+ const xi = polygon[i * 2];
1731
+ const yi = polygon[i * 2 + 1];
1732
+ const xj = polygon[j * 2];
1733
+ const yj = polygon[j * 2 + 1];
1734
+ if (yi > y !== yj > y && x < (xj - xi) * (y - yi) / (yj - yi) + xi) {
1735
+ inside = !inside;
1736
+ }
1737
+ }
1738
+ return inside;
1739
+ }
1740
+
1741
+ function quadraticBezierP0(t, p) {
1742
+ const k = 1 - t;
1743
+ return k * k * p;
1744
+ }
1745
+ function quadraticBezierP1(t, p) {
1746
+ return 2 * (1 - t) * t * p;
1747
+ }
1748
+ function quadraticBezierP2(t, p) {
1749
+ return t * t * p;
1750
+ }
1751
+ function quadraticBezier(t, p0, p1, p2) {
1752
+ return quadraticBezierP0(t, p0) + quadraticBezierP1(t, p1) + quadraticBezierP2(t, p2);
1753
+ }
1754
+
1755
+ const closePointEps = 1e-4;
1756
+ const curveEps = 1e-4;
1757
+ function strokeTriangulate(points, options = {}) {
1758
+ const {
1759
+ vertices = [],
1760
+ indices = [],
1761
+ lineStyle = {
1762
+ alignment: 0.5,
1763
+ cap: "butt",
1764
+ join: "miter",
1765
+ width: 1,
1766
+ miterLimit: 10
1767
+ },
1768
+ flipAlignment = false,
1769
+ closed = true
1770
+ } = options;
1771
+ const eps = closePointEps;
1772
+ if (points.length === 0) {
1773
+ return { vertices, indices };
1774
+ }
1775
+ const style = lineStyle;
1776
+ let alignment = style.alignment;
1777
+ if (lineStyle.alignment !== 0.5) {
1778
+ let orientation = getOrientationOfPoints(points);
1779
+ if (flipAlignment)
1780
+ orientation *= -1;
1781
+ alignment = (alignment - 0.5) * orientation + 0.5;
1782
+ }
1783
+ const firstPoint = { x: points[0], y: points[1] };
1784
+ const lastPoint = { x: points[points.length - 2], y: points[points.length - 1] };
1785
+ const closedShape = closed;
1786
+ const closedPath = Math.abs(firstPoint.x - lastPoint.x) < eps && Math.abs(firstPoint.y - lastPoint.y) < eps;
1787
+ if (closedShape) {
1788
+ points = points.slice();
1789
+ if (closedPath) {
1790
+ points.pop();
1791
+ points.pop();
1792
+ lastPoint.x = points[points.length - 2];
1793
+ lastPoint.y = points[points.length - 1];
1794
+ }
1795
+ const midPointX = (firstPoint.x + lastPoint.x) * 0.5;
1796
+ const midPointY = (lastPoint.y + firstPoint.y) * 0.5;
1797
+ points.unshift(midPointX, midPointY);
1798
+ points.push(midPointX, midPointY);
1799
+ }
1800
+ const verts = vertices;
1801
+ const length = points.length / 2;
1802
+ let indexCount = points.length;
1803
+ const indexStart = verts.length / 2;
1804
+ const width = style.width / 2;
1276
1805
  const widthSquared = width * width;
1277
1806
  const miterLimitSquared = style.miterLimit * style.miterLimit;
1278
1807
  let x0 = points[0];
@@ -1844,9 +2373,9 @@ class Curve {
1844
2373
  }
1845
2374
  }
1846
2375
 
1847
- const tempTransform0$1 = new Matrix3();
1848
- const tempTransform1$1 = new Matrix3();
1849
- const tempTransform2$1 = new Matrix3();
2376
+ const tempTransform0 = new Matrix3();
2377
+ const tempTransform1 = new Matrix3();
2378
+ const tempTransform2 = new Matrix3();
1850
2379
  const tempV2 = new Vector2();
1851
2380
  class RoundCurve extends Curve {
1852
2381
  constructor(_center = new Vector2(), _radius = new Vector2(), _diff = new Vector2(), rotate = 0, startAngle = 0, endAngle = Math.PI * 2, clockwise = false) {
@@ -2124,7 +2653,7 @@ function transfEllipseGeneric(curve, m) {
2124
2653
  const v2 = new Vector2(-b * sinTheta, b * cosTheta);
2125
2654
  const f1 = v1.applyMatrix3(m);
2126
2655
  const f2 = v2.applyMatrix3(m);
2127
- const mF = tempTransform0$1.set(
2656
+ const mF = tempTransform0.set(
2128
2657
  f1.x,
2129
2658
  f2.x,
2130
2659
  0,
@@ -2135,8 +2664,8 @@ function transfEllipseGeneric(curve, m) {
2135
2664
  0,
2136
2665
  1
2137
2666
  );
2138
- const mFInv = tempTransform1$1.copy(mF).invert();
2139
- const mFInvT = tempTransform2$1.copy(mFInv).transpose();
2667
+ const mFInv = tempTransform1.copy(mF).invert();
2668
+ const mFInvT = tempTransform2.copy(mFInv).transpose();
2140
2669
  const mQ = mFInvT.multiply(mFInv);
2141
2670
  const mQe = mQ.elements;
2142
2671
  const ed = eigenDecomposition(mQe[0], mQe[1], mQe[4]);
@@ -2147,7 +2676,7 @@ function transfEllipseGeneric(curve, m) {
2147
2676
  curve.rotate = Math.atan2(ed.sn, ed.cs);
2148
2677
  const isFullEllipse = (curve.endAngle - curve.startAngle) % (2 * Math.PI) < Number.EPSILON;
2149
2678
  if (!isFullEllipse) {
2150
- const mDsqrt = tempTransform1$1.set(
2679
+ const mDsqrt = tempTransform1.set(
2151
2680
  rt1sqrt,
2152
2681
  0,
2153
2682
  0,
@@ -2158,7 +2687,7 @@ function transfEllipseGeneric(curve, m) {
2158
2687
  0,
2159
2688
  1
2160
2689
  );
2161
- const mRT = tempTransform2$1.set(
2690
+ const mRT = tempTransform2.set(
2162
2691
  ed.cs,
2163
2692
  ed.sn,
2164
2693
  0,
@@ -2628,1561 +3157,1032 @@ class CubicBezierCurve extends Curve {
2628
3157
  this.p2.copy(source.p2);
2629
3158
  return this;
2630
3159
  }
2631
- }
2632
-
2633
- class EllipseCurve extends RoundCurve {
2634
- constructor(cx = 0, cy = 0, rx = 1, ry = 1, rotate = 0, startAngle = 0, endAngle = Math.PI * 2, clockwise = false) {
2635
- super(
2636
- new Vector2(cx, cy),
2637
- new Vector2(rx, ry),
2638
- new Vector2(),
2639
- rotate,
2640
- startAngle,
2641
- endAngle,
2642
- clockwise
2643
- );
2644
- }
2645
- drawTo(ctx) {
2646
- ctx.ellipse(
2647
- this.cx,
2648
- this.cy,
2649
- this.rx,
2650
- this.ry,
2651
- this.rotate,
2652
- this.startAngle,
2653
- this.endAngle,
2654
- !this.clockwise
2655
- );
2656
- return this;
2657
- }
2658
- }
2659
-
2660
- class PloygonCurve extends CompositeCurve {
2661
- //
2662
- }
2663
-
2664
- class EquilateralPloygonCurve extends PloygonCurve {
2665
- constructor(cx = 0, cy = 0, radius = 1, sideCount = 3) {
2666
- super();
2667
- this.cx = cx;
2668
- this.cy = cy;
2669
- this.radius = radius;
2670
- this.sideCount = sideCount;
2671
- this.update();
2672
- }
2673
- update() {
2674
- const { cx, cy, radius, sideCount } = this;
2675
- const points = [];
2676
- for (let i = 0; i < sideCount; i++) {
2677
- const radian = i * 2 * Math.PI / sideCount - 0.5 * Math.PI;
2678
- points.push(
2679
- new Vector2(
2680
- radius * Math.cos(radian),
2681
- radius * Math.sin(radian)
2682
- ).add({ x: cx, y: cy })
2683
- );
2684
- }
2685
- const curves = [];
2686
- for (let i = 0; i < sideCount; i++) {
2687
- curves.push(
2688
- new LineCurve(
2689
- points[i],
2690
- points[(i + 1) % sideCount]
2691
- )
2692
- );
2693
- }
2694
- this.curves = curves;
2695
- return this;
2696
- }
2697
- copy(source) {
2698
- super.copy(source);
2699
- this.cx = source.cx;
2700
- this.cy = source.cy;
2701
- this.radius = source.radius;
2702
- this.sideCount = source.sideCount;
2703
- this.update();
2704
- return this;
2705
- }
2706
- }
2707
-
2708
- class QuadraticBezierCurve extends Curve {
2709
- constructor(p1 = new Vector2(), cp = new Vector2(), p2 = new Vector2()) {
2710
- super();
2711
- this.p1 = p1;
2712
- this.cp = cp;
2713
- this.p2 = p2;
2714
- }
2715
- static from(p1x, p1y, cpx, cpy, p2x, p2y) {
2716
- return new QuadraticBezierCurve(
2717
- new Vector2(p1x, p1y),
2718
- new Vector2(cpx, cpy),
2719
- new Vector2(p2x, p2y)
2720
- );
2721
- }
2722
- getPoint(t, output = new Vector2()) {
2723
- const { p1, cp, p2 } = this;
2724
- output.set(
2725
- quadraticBezier(t, p1.x, cp.x, p2.x),
2726
- quadraticBezier(t, p1.y, cp.y, p2.y)
2727
- );
2728
- return output;
2729
- }
2730
- getControlPointRefs() {
2731
- return [this.p1, this.cp, this.p2];
2732
- }
2733
- getAdaptiveVertices(output = []) {
2734
- return getAdaptiveQuadraticBezierCurvePoints(
2735
- this.p1.x,
2736
- this.p1.y,
2737
- this.cp.x,
2738
- this.cp.y,
2739
- this.p2.x,
2740
- this.p2.y,
2741
- 0.5,
2742
- output
2743
- );
2744
- }
2745
- getMinMax(min = Vector2.MAX, max = Vector2.MIN) {
2746
- const { p1, cp, p2 } = this;
2747
- const x1 = 0.5 * (p1.x + cp.x);
2748
- const y1 = 0.5 * (p1.y + cp.y);
2749
- const x2 = 0.5 * (p1.x + p2.x);
2750
- const y2 = 0.5 * (p1.y + p2.y);
2751
- min.x = Math.min(min.x, p1.x, p2.x, x1, x2);
2752
- min.y = Math.min(min.y, p1.y, p2.y, y1, y2);
2753
- max.x = Math.max(max.x, p1.x, p2.x, x1, x2);
2754
- max.y = Math.max(max.y, p1.y, p2.y, y1, y2);
2755
- return { min: min.finite(), max: max.finite() };
2756
- }
2757
- toCommands() {
2758
- const { p1, cp, p2 } = this;
2759
- return [
2760
- { type: "M", x: p1.x, y: p1.y },
2761
- { type: "Q", x1: cp.x, y1: cp.y, x: p2.x, y: p2.y }
2762
- ];
2763
- }
2764
- drawTo(ctx) {
2765
- const { p1, cp, p2 } = this;
2766
- ctx.lineTo(p1.x, p1.y);
2767
- ctx.quadraticCurveTo(cp.x, cp.y, p2.x, p2.y);
2768
- return this;
2769
- }
2770
- copy(source) {
2771
- super.copy(source);
2772
- this.p1.copy(source.p1);
2773
- this.cp.copy(source.cp);
2774
- this.p2.copy(source.p2);
2775
- return this;
2776
- }
2777
- }
2778
-
2779
- class RectangleCurve extends PloygonCurve {
2780
- constructor(x = 0, y = 0, width = 0, height = 0) {
2781
- super();
2782
- this.x = x;
2783
- this.y = y;
2784
- this.width = width;
2785
- this.height = height;
2786
- this.update();
2787
- }
2788
- update() {
2789
- const { x, y, width, height } = this;
2790
- const points = [
2791
- new Vector2(x, y),
2792
- new Vector2(x + width, y),
2793
- new Vector2(x + width, y + height),
2794
- new Vector2(x, y + height)
2795
- ];
2796
- this.curves = [
2797
- new LineCurve(points[0], points[1]),
2798
- new LineCurve(points[1], points[2]),
2799
- new LineCurve(points[2], points[3]),
2800
- new LineCurve(points[3], points[0])
2801
- ];
2802
- return this;
2803
- }
2804
- drawTo(ctx) {
2805
- ctx.rect(this.x, this.y, this.width, this.height);
2806
- return this;
2807
- }
2808
- getFillVertices(_options = {}) {
2809
- const { x, y, width, height } = this;
2810
- return [
2811
- x,
2812
- y,
2813
- x + width,
2814
- y,
2815
- x + width,
2816
- y + height,
2817
- x,
2818
- y + height
2819
- ];
3160
+ }
3161
+
3162
+ class EllipseCurve extends RoundCurve {
3163
+ constructor(cx = 0, cy = 0, rx = 1, ry = 1, rotate = 0, startAngle = 0, endAngle = Math.PI * 2, clockwise = false) {
3164
+ super(
3165
+ new Vector2(cx, cy),
3166
+ new Vector2(rx, ry),
3167
+ new Vector2(),
3168
+ rotate,
3169
+ startAngle,
3170
+ endAngle,
3171
+ clockwise
3172
+ );
2820
3173
  }
2821
- copy(source) {
2822
- super.copy(source);
2823
- this.x = source.x;
2824
- this.y = source.y;
2825
- this.width = source.width;
2826
- this.height = source.height;
2827
- this.update();
3174
+ drawTo(ctx) {
3175
+ ctx.ellipse(
3176
+ this.cx,
3177
+ this.cy,
3178
+ this.rx,
3179
+ this.ry,
3180
+ this.rotate,
3181
+ this.startAngle,
3182
+ this.endAngle,
3183
+ !this.clockwise
3184
+ );
2828
3185
  return this;
2829
3186
  }
2830
3187
  }
2831
3188
 
2832
- class RoundRectangleCurve extends RoundCurve {
2833
- constructor(x = 0, y = 0, width = 1, height = 1, radius = 1) {
3189
+ class PloygonCurve extends CompositeCurve {
3190
+ //
3191
+ }
3192
+
3193
+ class EquilateralPloygonCurve extends PloygonCurve {
3194
+ constructor(cx = 0, cy = 0, radius = 1, sideCount = 3) {
2834
3195
  super();
2835
- this.x = x;
2836
- this.y = y;
2837
- this.width = width;
2838
- this.height = height;
3196
+ this.cx = cx;
3197
+ this.cy = cy;
2839
3198
  this.radius = radius;
3199
+ this.sideCount = sideCount;
2840
3200
  this.update();
2841
3201
  }
2842
3202
  update() {
2843
- const { x, y, width, height, radius } = this;
2844
- const halfWidth = width / 2;
2845
- const halfHeight = height / 2;
2846
- const cx = x + halfWidth;
2847
- const cy = y + halfHeight;
2848
- const rx = Math.max(0, Math.min(radius, Math.min(halfWidth, halfHeight)));
2849
- const ry = rx;
2850
- this._center = new Vector2(cx, cy);
2851
- this._radius = new Vector2(rx, ry);
2852
- this._diff = new Vector2(halfWidth - rx, halfHeight - ry);
2853
- return this;
2854
- }
2855
- drawTo(ctx) {
2856
- const { x, y, width, height, radius } = this;
2857
- ctx.roundRect(x, y, width, height, radius);
3203
+ const { cx, cy, radius, sideCount } = this;
3204
+ const points = [];
3205
+ for (let i = 0; i < sideCount; i++) {
3206
+ const radian = i * 2 * Math.PI / sideCount - 0.5 * Math.PI;
3207
+ points.push(
3208
+ new Vector2(
3209
+ radius * Math.cos(radian),
3210
+ radius * Math.sin(radian)
3211
+ ).add({ x: cx, y: cy })
3212
+ );
3213
+ }
3214
+ const curves = [];
3215
+ for (let i = 0; i < sideCount; i++) {
3216
+ curves.push(
3217
+ new LineCurve(
3218
+ points[i],
3219
+ points[(i + 1) % sideCount]
3220
+ )
3221
+ );
3222
+ }
3223
+ this.curves = curves;
2858
3224
  return this;
2859
3225
  }
2860
3226
  copy(source) {
2861
3227
  super.copy(source);
2862
- this.x = source.x;
2863
- this.y = source.y;
2864
- this.width = source.width;
2865
- this.height = source.height;
3228
+ this.cx = source.cx;
3229
+ this.cy = source.cy;
2866
3230
  this.radius = source.radius;
3231
+ this.sideCount = source.sideCount;
2867
3232
  this.update();
2868
3233
  return this;
2869
3234
  }
2870
3235
  }
2871
3236
 
2872
- class SplineCurve extends Curve {
2873
- constructor(points = []) {
3237
+ class QuadraticBezierCurve extends Curve {
3238
+ constructor(p1 = new Vector2(), cp = new Vector2(), p2 = new Vector2()) {
2874
3239
  super();
2875
- this.points = points;
3240
+ this.p1 = p1;
3241
+ this.cp = cp;
3242
+ this.p2 = p2;
3243
+ }
3244
+ static from(p1x, p1y, cpx, cpy, p2x, p2y) {
3245
+ return new QuadraticBezierCurve(
3246
+ new Vector2(p1x, p1y),
3247
+ new Vector2(cpx, cpy),
3248
+ new Vector2(p2x, p2y)
3249
+ );
2876
3250
  }
2877
3251
  getPoint(t, output = new Vector2()) {
2878
- const { points } = this;
2879
- const p = (points.length - 1) * t;
2880
- const _p = Math.floor(p);
2881
- const weight = p - _p;
2882
- const p0 = points[_p === 0 ? _p : _p - 1];
2883
- const p1 = points[_p];
2884
- const p2 = points[_p > points.length - 2 ? points.length - 1 : _p + 1];
2885
- const p3 = points[_p > points.length - 3 ? points.length - 1 : _p + 2];
3252
+ const { p1, cp, p2 } = this;
2886
3253
  output.set(
2887
- catmullRom(weight, p0.x, p1.x, p2.x, p3.x),
2888
- catmullRom(weight, p0.y, p1.y, p2.y, p3.y)
3254
+ quadraticBezier(t, p1.x, cp.x, p2.x),
3255
+ quadraticBezier(t, p1.y, cp.y, p2.y)
2889
3256
  );
2890
3257
  return output;
2891
3258
  }
2892
3259
  getControlPointRefs() {
2893
- return this.points;
2894
- }
2895
- copy(source) {
2896
- super.copy(source);
2897
- this.points = [];
2898
- for (let i = 0, len = source.points.length; i < len; i++) {
2899
- this.points.push(source.points[i].clone());
2900
- }
2901
- return this;
2902
- }
2903
- }
2904
-
2905
- class CurvePath extends CompositeCurve {
2906
- startPoint;
2907
- currentPoint;
2908
- autoClose = false;
2909
- constructor(points) {
2910
- super();
2911
- if (points) {
2912
- this.addPoints(points);
2913
- }
2914
- }
2915
- addPoints(points) {
2916
- this.moveTo(points[0].x, points[0].y);
2917
- for (let i = 1, len = points.length; i < len; i++) {
2918
- const { x, y } = points[i];
2919
- this.lineTo(x, y);
2920
- }
2921
- return this;
2922
- }
2923
- addCommands(commands) {
2924
- svgPathCommandsAddToPath2D(commands, this);
2925
- return this;
2926
- }
2927
- addData(data) {
2928
- this.addCommands(svgPathDataToCommands(data));
2929
- return this;
2930
- }
2931
- _closeVertices(output) {
2932
- if (this.autoClose && output.length >= 4 && (output[0] !== output[output.length - 2] && output[1] !== output[output.length - 1])) {
2933
- output.push(output[0], output[1]);
2934
- }
2935
- return output;
2936
- }
2937
- getUnevenVertices(count = 40, output = []) {
2938
- return this._closeVertices(
2939
- super.getUnevenVertices(count, output)
2940
- );
2941
- }
2942
- getSpacedVertices(count = 40, output = []) {
2943
- return this._closeVertices(
2944
- super.getSpacedVertices(count, output)
2945
- );
3260
+ return [this.p1, this.cp, this.p2];
2946
3261
  }
2947
3262
  getAdaptiveVertices(output = []) {
2948
- return this._closeVertices(
2949
- super.getAdaptiveVertices(output)
2950
- );
2951
- }
2952
- getFillVertices(options) {
2953
- return this._closeVertices(
2954
- super.getFillVertices(options)
3263
+ return getAdaptiveQuadraticBezierCurvePoints(
3264
+ this.p1.x,
3265
+ this.p1.y,
3266
+ this.cp.x,
3267
+ this.cp.y,
3268
+ this.p2.x,
3269
+ this.p2.y,
3270
+ 0.5,
3271
+ output
2955
3272
  );
2956
3273
  }
2957
- _setCurrentPoint(point) {
2958
- this.currentPoint = new Vector2(point.x, point.y);
2959
- if (!this.startPoint) {
2960
- this.startPoint = this.currentPoint.clone();
2961
- }
2962
- return this;
2963
- }
2964
- _connetLineTo(curve) {
2965
- if (this.curves.length > 0) {
2966
- const first = curve.getPoint(0);
2967
- if (!this.currentPoint || !first.equals(this.currentPoint)) {
2968
- this.lineTo(first.x, first.y);
2969
- }
2970
- }
2971
- return this;
2972
- }
2973
- closePath() {
2974
- const start = this.startPoint;
2975
- if (start) {
2976
- const end = this.currentPoint;
2977
- if (end && !start.equals(end)) {
2978
- this.curves.push(new LineCurve(end.clone(), start.clone()));
2979
- end.copy(start);
2980
- }
2981
- this.startPoint = void 0;
2982
- }
2983
- return this;
2984
- }
2985
- moveTo(x, y) {
2986
- this.currentPoint = new Vector2(x, y);
2987
- this.startPoint = this.currentPoint.clone();
2988
- return this;
2989
- }
2990
- lineTo(x, y) {
2991
- const start = this.currentPoint;
2992
- if (!start?.equals({ x, y })) {
2993
- this.curves.push(
2994
- LineCurve.from(
2995
- start?.x ?? 0,
2996
- start?.y ?? 0,
2997
- x,
2998
- y
2999
- )
3000
- );
3001
- }
3002
- this._setCurrentPoint({ x, y });
3003
- return this;
3004
- }
3005
- bezierCurveTo(cp1x, cp1y, cp2x, cp2y, x, y) {
3006
- const start = this.currentPoint;
3007
- if (!start?.equals({ x, y })) {
3008
- this.curves.push(
3009
- CubicBezierCurve.from(
3010
- start?.x ?? 0,
3011
- start?.y ?? 0,
3012
- cp1x,
3013
- cp1y,
3014
- cp2x,
3015
- cp2y,
3016
- x,
3017
- y
3018
- )
3019
- );
3020
- }
3021
- this._setCurrentPoint({ x, y });
3022
- return this;
3274
+ getMinMax(min = Vector2.MAX, max = Vector2.MIN) {
3275
+ const { p1, cp, p2 } = this;
3276
+ const x1 = 0.5 * (p1.x + cp.x);
3277
+ const y1 = 0.5 * (p1.y + cp.y);
3278
+ const x2 = 0.5 * (p1.x + p2.x);
3279
+ const y2 = 0.5 * (p1.y + p2.y);
3280
+ min.x = Math.min(min.x, p1.x, p2.x, x1, x2);
3281
+ min.y = Math.min(min.y, p1.y, p2.y, y1, y2);
3282
+ max.x = Math.max(max.x, p1.x, p2.x, x1, x2);
3283
+ max.y = Math.max(max.y, p1.y, p2.y, y1, y2);
3284
+ return { min: min.finite(), max: max.finite() };
3023
3285
  }
3024
- quadraticCurveTo(cpx, cpy, x, y) {
3025
- const start = this.currentPoint;
3026
- if (!start?.equals({ x, y })) {
3027
- this.curves.push(
3028
- QuadraticBezierCurve.from(
3029
- start?.x ?? 0,
3030
- start?.y ?? 0,
3031
- cpx,
3032
- cpy,
3033
- x,
3034
- y
3035
- )
3036
- );
3037
- }
3038
- this._setCurrentPoint({ x, y });
3286
+ toCommands() {
3287
+ const { p1, cp, p2 } = this;
3288
+ return [
3289
+ { type: "M", x: p1.x, y: p1.y },
3290
+ { type: "Q", x1: cp.x, y1: cp.y, x: p2.x, y: p2.y }
3291
+ ];
3292
+ }
3293
+ drawTo(ctx) {
3294
+ const { p1, cp, p2 } = this;
3295
+ ctx.lineTo(p1.x, p1.y);
3296
+ ctx.quadraticCurveTo(cp.x, cp.y, p2.x, p2.y);
3039
3297
  return this;
3040
3298
  }
3041
- arc(x, y, radius, startAngle, endAngle, counterclockwise) {
3042
- const curve = new ArcCurve(
3043
- x,
3044
- y,
3045
- radius,
3046
- startAngle,
3047
- endAngle,
3048
- !counterclockwise
3049
- );
3050
- this._connetLineTo(curve);
3051
- this.curves.push(curve);
3052
- this._setCurrentPoint(curve.getPoint(1));
3299
+ copy(source) {
3300
+ super.copy(source);
3301
+ this.p1.copy(source.p1);
3302
+ this.cp.copy(source.cp);
3303
+ this.p2.copy(source.p2);
3053
3304
  return this;
3054
3305
  }
3055
- relativeArc(x, y, radius, startAngle, endAngle, counterclockwise) {
3056
- x += this.currentPoint?.x ?? 0;
3057
- y += this.currentPoint?.y ?? 0;
3058
- this.arc(x, y, radius, startAngle, endAngle, counterclockwise);
3306
+ }
3307
+
3308
+ class RectangleCurve extends PloygonCurve {
3309
+ constructor(x = 0, y = 0, width = 0, height = 0) {
3310
+ super();
3311
+ this.x = x;
3312
+ this.y = y;
3313
+ this.width = width;
3314
+ this.height = height;
3315
+ this.update();
3316
+ }
3317
+ update() {
3318
+ const { x, y, width, height } = this;
3319
+ const points = [
3320
+ new Vector2(x, y),
3321
+ new Vector2(x + width, y),
3322
+ new Vector2(x + width, y + height),
3323
+ new Vector2(x, y + height)
3324
+ ];
3325
+ this.curves = [
3326
+ new LineCurve(points[0], points[1]),
3327
+ new LineCurve(points[1], points[2]),
3328
+ new LineCurve(points[2], points[3]),
3329
+ new LineCurve(points[3], points[0])
3330
+ ];
3059
3331
  return this;
3060
3332
  }
3061
- // TODO
3062
- // eslint-disable-next-line unused-imports/no-unused-vars
3063
- arcTo(x1, y1, x2, y2, radius) {
3064
- console.warn("Method arcTo not supported yet");
3333
+ drawTo(ctx) {
3334
+ ctx.rect(this.x, this.y, this.width, this.height);
3065
3335
  return this;
3066
3336
  }
3067
- ellipse(x, y, radiusX, radiusY, rotation, startAngle, endAngle, counterclockwise = true) {
3068
- const curve = new EllipseCurve(
3337
+ getFillVertices(_options = {}) {
3338
+ const { x, y, width, height } = this;
3339
+ return [
3069
3340
  x,
3070
3341
  y,
3071
- radiusX,
3072
- radiusY,
3073
- rotation,
3074
- startAngle,
3075
- endAngle,
3076
- !counterclockwise
3077
- );
3078
- this._connetLineTo(curve);
3079
- this.curves.push(curve);
3080
- this._setCurrentPoint(curve.getPoint(1));
3081
- return this;
3082
- }
3083
- relativeEllipse(x, y, radiusX, radiusY, rotation, startAngle, endAngle, counterclockwise) {
3084
- x += this.currentPoint?.x ?? 0;
3085
- y += this.currentPoint?.y ?? 0;
3086
- this.ellipse(x, y, radiusX, radiusY, rotation, startAngle, endAngle, counterclockwise);
3087
- return this;
3342
+ x + width,
3343
+ y,
3344
+ x + width,
3345
+ y + height,
3346
+ x,
3347
+ y + height
3348
+ ];
3088
3349
  }
3089
- rect(x, y, width, height) {
3090
- const curve = new RectangleCurve(x, y, width, height);
3091
- this._connetLineTo(curve);
3092
- this.curves.push(curve);
3093
- this._setCurrentPoint({ x, y });
3350
+ copy(source) {
3351
+ super.copy(source);
3352
+ this.x = source.x;
3353
+ this.y = source.y;
3354
+ this.width = source.width;
3355
+ this.height = source.height;
3356
+ this.update();
3094
3357
  return this;
3095
3358
  }
3096
- roundRect(x, y, width, height, radii) {
3097
- const curve = new RoundRectangleCurve(x, y, width, height, radii);
3098
- this._connetLineTo(curve);
3099
- this.curves.push(curve);
3100
- this._setCurrentPoint({ x, y });
3101
- return this;
3359
+ }
3360
+
3361
+ class RoundRectangleCurve extends RoundCurve {
3362
+ constructor(x = 0, y = 0, width = 1, height = 1, radius = 1) {
3363
+ super();
3364
+ this.x = x;
3365
+ this.y = y;
3366
+ this.width = width;
3367
+ this.height = height;
3368
+ this.radius = radius;
3369
+ this.update();
3102
3370
  }
3103
- splineThru(points) {
3104
- const currentPoint = this.currentPoint ?? new Vector2();
3105
- this.curves.push(new SplineCurve([currentPoint].concat(points)));
3106
- this._setCurrentPoint(points[points.length - 1]);
3371
+ update() {
3372
+ const { x, y, width, height, radius } = this;
3373
+ const halfWidth = width / 2;
3374
+ const halfHeight = height / 2;
3375
+ const cx = x + halfWidth;
3376
+ const cy = y + halfHeight;
3377
+ const rx = Math.max(0, Math.min(radius, Math.min(halfWidth, halfHeight)));
3378
+ const ry = rx;
3379
+ this._center = new Vector2(cx, cy);
3380
+ this._radius = new Vector2(rx, ry);
3381
+ this._diff = new Vector2(halfWidth - rx, halfHeight - ry);
3107
3382
  return this;
3108
3383
  }
3109
3384
  drawTo(ctx) {
3110
- const point = this.curves[0]?.getPoint(0);
3111
- if (point) {
3112
- ctx.moveTo(point.x, point.y);
3113
- }
3114
- this.curves.forEach((curve) => curve.drawTo(ctx));
3115
- if (this.autoClose) {
3116
- ctx.closePath();
3117
- }
3385
+ const { x, y, width, height, radius } = this;
3386
+ ctx.roundRect(x, y, width, height, radius);
3118
3387
  return this;
3119
3388
  }
3120
3389
  copy(source) {
3121
3390
  super.copy(source);
3122
- this.autoClose = source.autoClose;
3123
- this.currentPoint = source.currentPoint?.clone();
3391
+ this.x = source.x;
3392
+ this.y = source.y;
3393
+ this.width = source.width;
3394
+ this.height = source.height;
3395
+ this.radius = source.radius;
3396
+ this.update();
3124
3397
  return this;
3125
3398
  }
3126
3399
  }
3127
3400
 
3128
- function toKebabCase(str) {
3129
- return str.replace(/[^a-z0-9]/gi, "-").replace(/\B([A-Z])/g, "-$1").toLowerCase();
3130
- }
3131
- function getIntersectionPoint(p1, p2, q1, q2) {
3132
- const r = p2.clone().sub(p1);
3133
- const s = q2.clone().sub(q1);
3134
- const q1p1 = q1.clone().sub(p1);
3135
- const crossRS = r.cross(s);
3136
- if (crossRS === 0) {
3137
- return new Vector2(
3138
- (p1.x + q1.x) / 2,
3139
- (p1.y + q1.y) / 2
3140
- );
3401
+ class SplineCurve extends Curve {
3402
+ constructor(points = []) {
3403
+ super();
3404
+ this.points = points;
3141
3405
  }
3142
- const t = q1p1.cross(s) / crossRS;
3143
- if (Math.abs(t) > 1) {
3144
- return new Vector2(
3145
- (p1.x + q1.x) / 2,
3146
- (p1.y + q1.y) / 2
3406
+ getPoint(t, output = new Vector2()) {
3407
+ const { points } = this;
3408
+ const p = (points.length - 1) * t;
3409
+ const _p = Math.floor(p);
3410
+ const weight = p - _p;
3411
+ const p0 = points[_p === 0 ? _p : _p - 1];
3412
+ const p1 = points[_p];
3413
+ const p2 = points[_p > points.length - 2 ? points.length - 1 : _p + 1];
3414
+ const p3 = points[_p > points.length - 3 ? points.length - 1 : _p + 2];
3415
+ output.set(
3416
+ catmullRom(weight, p0.x, p1.x, p2.x, p3.x),
3417
+ catmullRom(weight, p0.y, p1.y, p2.y, p3.y)
3147
3418
  );
3419
+ return output;
3148
3420
  }
3149
- return new Vector2(
3150
- p1.x + t * r.x,
3151
- p1.y + t * r.y
3152
- );
3153
- }
3154
-
3155
- class Path2D extends CompositeCurve {
3156
- currentCurve = new CurvePath();
3157
- style;
3158
- get startPoint() {
3159
- return this.currentCurve.startPoint;
3160
- }
3161
- get currentPoint() {
3162
- return this.currentCurve.currentPoint;
3163
- }
3164
- get strokeWidth() {
3165
- return this.style.strokeWidth ?? ((this.style.stroke ?? "none") === "none" ? 0 : 1);
3166
- }
3167
- constructor(path, style = {}) {
3168
- super();
3169
- this.curves.push(this.currentCurve);
3170
- this.style = style;
3171
- if (path) {
3172
- if (path instanceof Path2D) {
3173
- this.addPath(path);
3174
- } else if (Array.isArray(path)) {
3175
- this.addCommands(path);
3176
- } else {
3177
- this.addData(path);
3178
- }
3179
- }
3421
+ getControlPointRefs() {
3422
+ return this.points;
3180
3423
  }
3181
- addPath(path) {
3182
- const index = this.curves.findIndex((v) => v === this.currentCurve);
3183
- if (index > -1) {
3184
- this.curves.splice(index, 1);
3185
- }
3186
- if (path instanceof Path2D) {
3187
- this.curves.push(
3188
- ...path.curves.filter((curvePath) => curvePath.curves.length).map((v) => v.clone())
3189
- );
3190
- } else if (path.curves.length) {
3191
- this.curves.push(path);
3424
+ copy(source) {
3425
+ super.copy(source);
3426
+ this.points = [];
3427
+ for (let i = 0, len = source.points.length; i < len; i++) {
3428
+ this.points.push(source.points[i].clone());
3192
3429
  }
3193
- this.curves.push(this.currentCurve);
3194
3430
  return this;
3195
3431
  }
3196
- closePath() {
3197
- const startPoint = this.startPoint;
3198
- if (startPoint) {
3199
- this.currentCurve.closePath();
3200
- if (this.currentCurve.curves.length) {
3201
- this.currentCurve = new CurvePath().moveTo(startPoint.x, startPoint.y);
3202
- this.curves.push(this.currentCurve);
3203
- }
3432
+ }
3433
+
3434
+ class CurvePath extends CompositeCurve {
3435
+ startPoint;
3436
+ currentPoint;
3437
+ autoClose = false;
3438
+ constructor(points) {
3439
+ super();
3440
+ if (points) {
3441
+ this.addPoints(points);
3204
3442
  }
3205
- return this;
3206
3443
  }
3207
- moveTo(x, y) {
3208
- if (!this.currentCurve.currentPoint?.equals({ x, y })) {
3209
- if (this.currentCurve.curves.length) {
3210
- this.currentCurve = new CurvePath();
3211
- this.curves.push(this.currentCurve);
3212
- }
3213
- this.currentCurve.moveTo(x, y);
3444
+ addPoints(points) {
3445
+ this.moveTo(points[0].x, points[0].y);
3446
+ for (let i = 1, len = points.length; i < len; i++) {
3447
+ const { x, y } = points[i];
3448
+ this.lineTo(x, y);
3214
3449
  }
3215
3450
  return this;
3216
3451
  }
3217
- lineTo(x, y) {
3218
- this.currentCurve.lineTo(x, y);
3452
+ addCommands(commands) {
3453
+ svgPathCommandsAddToPath2D(commands, this);
3219
3454
  return this;
3220
3455
  }
3221
- bezierCurveTo(cp1x, cp1y, cp2x, cp2y, x, y) {
3222
- this.currentCurve.bezierCurveTo(cp1x, cp1y, cp2x, cp2y, x, y);
3456
+ addData(data) {
3457
+ this.addCommands(svgPathDataToCommands(data));
3223
3458
  return this;
3224
3459
  }
3225
- quadraticCurveTo(cpx, cpy, x, y) {
3226
- this.currentCurve.quadraticCurveTo(cpx, cpy, x, y);
3227
- return this;
3460
+ _closeVertices(output) {
3461
+ if (this.autoClose && output.length >= 4 && (output[0] !== output[output.length - 2] && output[1] !== output[output.length - 1])) {
3462
+ output.push(output[0], output[1]);
3463
+ }
3464
+ return output;
3228
3465
  }
3229
- arc(x, y, radius, startAngle, endAngle, counterclockwise) {
3230
- this.currentCurve.arc(x, y, radius, startAngle, endAngle, counterclockwise);
3231
- return this;
3466
+ getUnevenVertices(count = 40, output = []) {
3467
+ return this._closeVertices(
3468
+ super.getUnevenVertices(count, output)
3469
+ );
3232
3470
  }
3233
- arcTo(x1, y1, x2, y2, radius) {
3234
- this.currentCurve.arcTo(x1, y1, x2, y2, radius);
3235
- return this;
3471
+ getSpacedVertices(count = 40, output = []) {
3472
+ return this._closeVertices(
3473
+ super.getSpacedVertices(count, output)
3474
+ );
3236
3475
  }
3237
- ellipse(x, y, radiusX, radiusY, rotation, startAngle, endAngle, counterclockwise) {
3238
- this.currentCurve.ellipse(x, y, radiusX, radiusY, rotation, startAngle, endAngle, counterclockwise);
3476
+ getAdaptiveVertices(output = []) {
3477
+ return this._closeVertices(
3478
+ super.getAdaptiveVertices(output)
3479
+ );
3480
+ }
3481
+ getFillVertices(options) {
3482
+ return this._closeVertices(
3483
+ super.getFillVertices(options)
3484
+ );
3485
+ }
3486
+ _setCurrentPoint(point) {
3487
+ this.currentPoint = new Vector2(point.x, point.y);
3488
+ if (!this.startPoint) {
3489
+ this.startPoint = this.currentPoint.clone();
3490
+ }
3239
3491
  return this;
3240
3492
  }
3241
- rect(x, y, width, height) {
3242
- this.currentCurve.rect(x, y, width, height);
3493
+ _connetLineTo(curve) {
3494
+ if (this.curves.length > 0) {
3495
+ const first = curve.getPoint(0);
3496
+ if (!this.currentPoint || !first.equals(this.currentPoint)) {
3497
+ this.lineTo(first.x, first.y);
3498
+ }
3499
+ }
3243
3500
  return this;
3244
3501
  }
3245
- roundRect(x, y, width, height, radii) {
3246
- this.currentCurve.roundRect(x, y, width, height, radii);
3502
+ closePath() {
3503
+ const start = this.startPoint;
3504
+ if (start) {
3505
+ const end = this.currentPoint;
3506
+ if (end && !start.equals(end)) {
3507
+ this.curves.push(new LineCurve(end.clone(), start.clone()));
3508
+ end.copy(start);
3509
+ }
3510
+ this.startPoint = void 0;
3511
+ }
3247
3512
  return this;
3248
3513
  }
3249
- reset() {
3250
- this.currentCurve = new CurvePath();
3251
- this.curves = [this.currentCurve];
3252
- this.style = {};
3514
+ moveTo(x, y) {
3515
+ this.currentPoint = new Vector2(x, y);
3516
+ this.startPoint = this.currentPoint.clone();
3253
3517
  return this;
3254
3518
  }
3255
- addCommands(commands) {
3256
- svgPathCommandsAddToPath2D(commands, this);
3519
+ lineTo(x, y) {
3520
+ const start = this.currentPoint;
3521
+ if (!start?.equals({ x, y })) {
3522
+ this.curves.push(
3523
+ LineCurve.from(
3524
+ start?.x ?? 0,
3525
+ start?.y ?? 0,
3526
+ x,
3527
+ y
3528
+ )
3529
+ );
3530
+ }
3531
+ this._setCurrentPoint({ x, y });
3257
3532
  return this;
3258
3533
  }
3259
- addData(data) {
3260
- this.addCommands(svgPathDataToCommands(data));
3534
+ bezierCurveTo(cp1x, cp1y, cp2x, cp2y, x, y) {
3535
+ const start = this.currentPoint;
3536
+ if (!start?.equals({ x, y })) {
3537
+ this.curves.push(
3538
+ CubicBezierCurve.from(
3539
+ start?.x ?? 0,
3540
+ start?.y ?? 0,
3541
+ cp1x,
3542
+ cp1y,
3543
+ cp2x,
3544
+ cp2y,
3545
+ x,
3546
+ y
3547
+ )
3548
+ );
3549
+ }
3550
+ this._setCurrentPoint({ x, y });
3261
3551
  return this;
3262
3552
  }
3263
- splineThru(points) {
3264
- this.currentCurve.splineThru(points);
3553
+ quadraticCurveTo(cpx, cpy, x, y) {
3554
+ const start = this.currentPoint;
3555
+ if (!start?.equals({ x, y })) {
3556
+ this.curves.push(
3557
+ QuadraticBezierCurve.from(
3558
+ start?.x ?? 0,
3559
+ start?.y ?? 0,
3560
+ cpx,
3561
+ cpy,
3562
+ x,
3563
+ y
3564
+ )
3565
+ );
3566
+ }
3567
+ this._setCurrentPoint({ x, y });
3265
3568
  return this;
3266
3569
  }
3267
- scale(sx, sy = sx, target = { x: 0, y: 0 }) {
3268
- this.getControlPointRefs().forEach((point) => {
3269
- point.scale(sx, sy, target);
3270
- });
3570
+ arc(x, y, radius, startAngle, endAngle, counterclockwise) {
3571
+ const curve = new ArcCurve(
3572
+ x,
3573
+ y,
3574
+ radius,
3575
+ startAngle,
3576
+ endAngle,
3577
+ !counterclockwise
3578
+ );
3579
+ this._connetLineTo(curve);
3580
+ this.curves.push(curve);
3581
+ this._setCurrentPoint(curve.getPoint(1));
3271
3582
  return this;
3272
3583
  }
3273
- skew(ax, ay = 0, target = { x: 0, y: 0 }) {
3274
- this.getControlPointRefs().forEach((point) => {
3275
- point.skew(ax, ay, target);
3276
- });
3584
+ relativeArc(x, y, radius, startAngle, endAngle, counterclockwise) {
3585
+ x += this.currentPoint?.x ?? 0;
3586
+ y += this.currentPoint?.y ?? 0;
3587
+ this.arc(x, y, radius, startAngle, endAngle, counterclockwise);
3277
3588
  return this;
3278
3589
  }
3279
- rotate(a, target = { x: 0, y: 0 }) {
3280
- this.getControlPointRefs().forEach((point) => {
3281
- point.rotate(a, target);
3282
- });
3590
+ // TODO
3591
+ // eslint-disable-next-line unused-imports/no-unused-vars
3592
+ arcTo(x1, y1, x2, y2, radius) {
3593
+ console.warn("Method arcTo not supported yet");
3283
3594
  return this;
3284
3595
  }
3285
- bold(b) {
3286
- if (b === 0) {
3287
- return this;
3288
- }
3289
- const curves = this.getFlatCurves();
3290
- const _list = [];
3291
- const _isClockwise = [];
3292
- const _points = [];
3293
- curves.forEach((curve, index) => {
3294
- const points = curve.getControlPointRefs();
3295
- const isClockwise = curve.isClockwise();
3296
- _points[index] = points;
3297
- _isClockwise[index] = isClockwise;
3298
- const start = points[0];
3299
- const end = points[points.length - 1] ?? start;
3300
- _list.push({
3301
- start: isClockwise ? end : start,
3302
- end: isClockwise ? start : end,
3303
- index
3304
- });
3305
- });
3306
- const list = [];
3307
- _list.forEach((itemA, indexA) => {
3308
- list[indexA] = [];
3309
- _list.forEach((itemB, indexB) => {
3310
- if (itemB.start && itemA.end && indexB !== indexA && itemB.start?.equals(itemA.end)) {
3311
- list[indexA].push(itemB.index);
3312
- }
3313
- });
3314
- });
3315
- curves.forEach((curve, index) => {
3316
- const isClockwise = _isClockwise[index];
3317
- _points[index].forEach((point) => {
3318
- point.add(
3319
- curve.getNormal(
3320
- curve.getTForPoint(point)
3321
- ).scale(isClockwise ? b : -b)
3322
- );
3323
- });
3324
- });
3325
- list.forEach((indexes, indexA) => {
3326
- const pointsA = _points[indexA];
3327
- indexes.forEach((indexB) => {
3328
- const pointsB = _points[indexB];
3329
- const point = getIntersectionPoint(
3330
- pointsA[pointsA.length - 1],
3331
- pointsA[pointsA.length - 2] ?? pointsA[pointsA.length - 1],
3332
- pointsB[0],
3333
- pointsB[1] ?? pointsB[0]
3334
- );
3335
- if (point) {
3336
- pointsA[pointsA.length - 1].copy(point);
3337
- pointsB[0].copy(point);
3338
- }
3339
- });
3340
- });
3596
+ ellipse(x, y, radiusX, radiusY, rotation, startAngle, endAngle, counterclockwise = true) {
3597
+ const curve = new EllipseCurve(
3598
+ x,
3599
+ y,
3600
+ radiusX,
3601
+ radiusY,
3602
+ rotation,
3603
+ startAngle,
3604
+ endAngle,
3605
+ !counterclockwise
3606
+ );
3607
+ this._connetLineTo(curve);
3608
+ this.curves.push(curve);
3609
+ this._setCurrentPoint(curve.getPoint(1));
3341
3610
  return this;
3342
3611
  }
3343
- getMinMax(min = Vector2.MAX, max = Vector2.MIN, withStyle = true) {
3344
- const strokeWidth = this.strokeWidth;
3345
- this.curves.forEach((curve) => {
3346
- curve.getMinMax(min, max);
3347
- if (withStyle) {
3348
- if (strokeWidth > 1) {
3349
- const halfStrokeWidth = strokeWidth / 2;
3350
- const isClockwise = curve.isClockwise();
3351
- const points = [];
3352
- for (let t = 0; t <= 1; t += 1 / curve.arcLengthDivision) {
3353
- const point = curve.getPoint(t);
3354
- const normal = curve.getNormal(t);
3355
- const dist1 = normal.clone().scale(isClockwise ? halfStrokeWidth : -halfStrokeWidth);
3356
- const dist2 = normal.clone().scale(isClockwise ? -halfStrokeWidth : halfStrokeWidth);
3357
- points.push(
3358
- point.clone().add(dist1),
3359
- point.clone().add(dist2),
3360
- point.clone().add({ x: halfStrokeWidth, y: 0 }),
3361
- point.clone().add({ x: -halfStrokeWidth, y: 0 }),
3362
- point.clone().add({ x: 0, y: halfStrokeWidth }),
3363
- point.clone().add({ x: 0, y: -halfStrokeWidth }),
3364
- point.clone().add({ x: halfStrokeWidth, y: halfStrokeWidth }),
3365
- point.clone().add({ x: -halfStrokeWidth, y: -halfStrokeWidth })
3366
- );
3367
- }
3368
- min.min(...points);
3369
- max.max(...points);
3370
- }
3371
- }
3372
- });
3373
- return { min: min.finite(), max: max.finite() };
3612
+ relativeEllipse(x, y, radiusX, radiusY, rotation, startAngle, endAngle, counterclockwise) {
3613
+ x += this.currentPoint?.x ?? 0;
3614
+ y += this.currentPoint?.y ?? 0;
3615
+ this.ellipse(x, y, radiusX, radiusY, rotation, startAngle, endAngle, counterclockwise);
3616
+ return this;
3374
3617
  }
3375
- strokeTriangulate(options) {
3376
- const indices = options?.indices ?? [];
3377
- const vertices = options?.vertices ?? [];
3378
- this.curves.forEach((curve) => {
3379
- curve.strokeTriangulate({
3380
- ...options,
3381
- indices,
3382
- vertices,
3383
- style: { ...this.style }
3384
- });
3385
- });
3386
- return { indices, vertices };
3618
+ rect(x, y, width, height) {
3619
+ const curve = new RectangleCurve(x, y, width, height);
3620
+ this._connetLineTo(curve);
3621
+ this.curves.push(curve);
3622
+ this._setCurrentPoint({ x, y });
3623
+ return this;
3387
3624
  }
3388
- fillTriangulate(options) {
3389
- const _options = {
3390
- ...options,
3391
- style: {
3392
- ...this.style,
3393
- ...options?.style
3394
- }
3395
- };
3396
- const indices = _options.indices ?? [];
3397
- const vertices = _options.vertices ?? [];
3398
- const fillRule = _options.style.fillRule ?? "nonzero";
3399
- if (fillRule === "nonzero") {
3400
- const pointArrays = this.curves.map((curve) => curve.getFillVertices(_options));
3401
- const parentMap = /* @__PURE__ */ new Map();
3402
- const parentd = /* @__PURE__ */ new Set();
3403
- for (let i = 0; i < pointArrays.length; i++) {
3404
- const parents = [];
3405
- for (let j = 0; j < pointArrays.length; j++) {
3406
- if (i === j)
3407
- continue;
3408
- if (pointInPolygon([pointArrays[i][0], pointArrays[i][1]], pointArrays[j])) {
3409
- parents.push(j);
3410
- }
3411
- }
3412
- if (parents.length) {
3413
- parents.forEach((pi) => {
3414
- let set = parentMap.get(pi);
3415
- if (!set) {
3416
- set = /* @__PURE__ */ new Set();
3417
- parentMap.set(pi, set);
3418
- }
3419
- set.add(i);
3420
- });
3421
- parentd.add(i);
3422
- }
3423
- }
3424
- pointArrays.forEach((pointArray, i) => {
3425
- if (parentd.has(i) || !pointArray.length) {
3426
- return;
3427
- }
3428
- const _pointArray = pointArray.slice();
3429
- const holes = [];
3430
- parentMap.get(i)?.forEach((ci) => {
3431
- holes.push(_pointArray.length / 2);
3432
- _pointArray.push(...pointArrays[ci]);
3433
- });
3434
- fillTriangulate(_pointArray, {
3435
- ...options,
3436
- indices,
3437
- vertices,
3438
- holes,
3439
- style: { ...this.style }
3440
- });
3441
- });
3442
- } else {
3443
- this.curves.forEach((curve) => {
3444
- curve.fillTriangulate({
3445
- ...options,
3446
- indices,
3447
- vertices,
3448
- style: { ...this.style }
3449
- });
3450
- });
3451
- }
3452
- return { indices, vertices };
3625
+ roundRect(x, y, width, height, radii) {
3626
+ const curve = new RoundRectangleCurve(x, y, width, height, radii);
3627
+ this._connetLineTo(curve);
3628
+ this.curves.push(curve);
3629
+ this._setCurrentPoint({ x, y });
3630
+ return this;
3453
3631
  }
3454
- getBoundingBox(withStyle = true) {
3455
- const { min, max } = this.getMinMax(void 0, void 0, withStyle);
3456
- return new BoundingBox(min.x, min.y, max.x - min.x, max.y - min.y);
3632
+ splineThru(points) {
3633
+ const currentPoint = this.currentPoint ?? new Vector2();
3634
+ this.curves.push(new SplineCurve([currentPoint].concat(points)));
3635
+ this._setCurrentPoint(points[points.length - 1]);
3636
+ return this;
3457
3637
  }
3458
- drawTo(ctx, style = {}) {
3459
- style = { ...this.style, ...style };
3460
- const { fill = "#000", stroke = "none" } = style;
3461
- ctx.beginPath();
3462
- ctx.save();
3463
- setCanvasContext(ctx, style);
3464
- this.curves.forEach((path) => {
3465
- path.drawTo(ctx);
3466
- });
3467
- if (fill !== "none") {
3468
- ctx.fill();
3638
+ drawTo(ctx) {
3639
+ const point = this.curves[0]?.getPoint(0);
3640
+ if (point) {
3641
+ ctx.moveTo(point.x, point.y);
3469
3642
  }
3470
- if (stroke !== "none") {
3471
- ctx.stroke();
3643
+ this.curves.forEach((curve) => curve.drawTo(ctx));
3644
+ if (this.autoClose) {
3645
+ ctx.closePath();
3472
3646
  }
3473
- ctx.restore();
3474
3647
  return this;
3475
3648
  }
3476
- drawControlPointsTo(ctx, style = {}) {
3477
- style = { ...this.style, ...style };
3478
- const { fill = "#000", stroke = "none" } = style;
3479
- ctx.beginPath();
3480
- ctx.save();
3481
- setCanvasContext(ctx, style);
3482
- this.getControlPointRefs().forEach((point) => {
3483
- drawPoint(ctx, point.x, point.y, { radius: 4 });
3484
- });
3485
- if (fill !== "none") {
3486
- ctx.fill();
3487
- }
3488
- if (stroke !== "none") {
3489
- ctx.stroke();
3490
- }
3491
- ctx.restore();
3649
+ copy(source) {
3650
+ super.copy(source);
3651
+ this.autoClose = source.autoClose;
3652
+ this.currentPoint = source.currentPoint?.clone();
3492
3653
  return this;
3493
3654
  }
3494
- toCommands() {
3495
- return this.curves.flatMap((v) => v.toCommands());
3655
+ }
3656
+
3657
+ function toKebabCase(str) {
3658
+ return str.replace(/[^a-z0-9]/gi, "-").replace(/\B([A-Z])/g, "-$1").toLowerCase();
3659
+ }
3660
+ function getIntersectionPoint(p1, p2, q1, q2) {
3661
+ const r = p2.clone().sub(p1);
3662
+ const s = q2.clone().sub(q1);
3663
+ const q1p1 = q1.clone().sub(p1);
3664
+ const crossRS = r.cross(s);
3665
+ if (crossRS === 0) {
3666
+ return new Vector2(
3667
+ (p1.x + q1.x) / 2,
3668
+ (p1.y + q1.y) / 2
3669
+ );
3496
3670
  }
3497
- toData() {
3498
- return this.curves.filter((v) => v.curves.length).map((v) => v.toData()).join(" ");
3671
+ const t = q1p1.cross(s) / crossRS;
3672
+ if (Math.abs(t) > 1) {
3673
+ return new Vector2(
3674
+ (p1.x + q1.x) / 2,
3675
+ (p1.y + q1.y) / 2
3676
+ );
3499
3677
  }
3500
- toSvgPathString() {
3501
- const style = {
3502
- ...this.style,
3503
- fill: this.style.fill ?? "#000",
3504
- stroke: this.style.stroke ?? "none"
3505
- };
3506
- const cssStyle = {};
3507
- for (const key in style) {
3508
- if (style[key] !== void 0) {
3509
- cssStyle[toKebabCase(key)] = style[key];
3510
- }
3511
- }
3512
- Object.assign(cssStyle, {
3513
- "stroke-width": `${this.strokeWidth}px`
3514
- });
3515
- let cssText = "";
3516
- for (const key in cssStyle) {
3517
- if (cssStyle[key] !== void 0) {
3518
- cssText += `${key}:${cssStyle[key]};`;
3678
+ return new Vector2(
3679
+ p1.x + t * r.x,
3680
+ p1.y + t * r.y
3681
+ );
3682
+ }
3683
+
3684
+ class Path2D extends CompositeCurve {
3685
+ currentCurve = new CurvePath();
3686
+ style;
3687
+ get startPoint() {
3688
+ return this.currentCurve.startPoint;
3689
+ }
3690
+ get currentPoint() {
3691
+ return this.currentCurve.currentPoint;
3692
+ }
3693
+ get strokeWidth() {
3694
+ return this.style.strokeWidth ?? ((this.style.stroke ?? "none") === "none" ? 0 : 1);
3695
+ }
3696
+ constructor(path, style = {}) {
3697
+ super();
3698
+ this.curves.push(this.currentCurve);
3699
+ this.style = style;
3700
+ if (path) {
3701
+ if (path instanceof Path2D) {
3702
+ this.addPath(path);
3703
+ } else if (Array.isArray(path)) {
3704
+ this.addCommands(path);
3705
+ } else {
3706
+ this.addData(path);
3519
3707
  }
3520
3708
  }
3521
- return `<path d="${this.toData()}" style="${cssText}"></path>`;
3522
3709
  }
3523
- copy(source) {
3524
- super.copy(source);
3525
- this.currentCurve = source.currentCurve.clone();
3526
- this.style = { ...source.style };
3710
+ addPath(path) {
3711
+ const index = this.curves.findIndex((v) => v === this.currentCurve);
3712
+ if (index > -1) {
3713
+ this.curves.splice(index, 1);
3714
+ }
3715
+ if (path instanceof Path2D) {
3716
+ this.curves.push(
3717
+ ...path.curves.filter((curvePath) => curvePath.curves.length).map((v) => v.clone())
3718
+ );
3719
+ } else if (path.curves.length) {
3720
+ this.curves.push(path);
3721
+ }
3722
+ this.curves.push(this.currentCurve);
3527
3723
  return this;
3528
3724
  }
3529
- }
3530
-
3531
- class Path2DSet {
3532
- constructor(paths = [], viewBox) {
3533
- this.paths = paths;
3534
- this.viewBox = viewBox;
3535
- }
3536
- getBoundingBox(withStyle = true) {
3537
- if (!this.paths.length) {
3538
- return void 0;
3725
+ closePath() {
3726
+ const startPoint = this.startPoint;
3727
+ if (startPoint) {
3728
+ this.currentCurve.closePath();
3729
+ if (this.currentCurve.curves.length) {
3730
+ this.currentCurve = new CurvePath().moveTo(startPoint.x, startPoint.y);
3731
+ this.curves.push(this.currentCurve);
3732
+ }
3539
3733
  }
3540
- const min = Vector2.MAX;
3541
- const max = Vector2.MIN;
3542
- this.paths.forEach((path) => path.getMinMax(min, max, withStyle));
3543
- return new BoundingBox(min.x, min.y, max.x - min.x, max.y - min.y);
3734
+ return this;
3544
3735
  }
3545
- toTriangulatedSvgString(result = this.paths.map((p) => p.fillTriangulate()), padding = 0) {
3546
- let polygonStr = "";
3547
- const min = { x: -padding, y: -padding };
3548
- const max = { x: padding, y: padding };
3549
- const results = Array.isArray(result) ? result : [result];
3550
- results.forEach(({ vertices, indices }) => {
3551
- const getPoint = (indice) => {
3552
- const x = vertices[indice * 2];
3553
- const y = vertices[indice * 2 + 1];
3554
- min.x = Math.min(min.x, x + padding);
3555
- max.x = Math.max(max.x, x + padding);
3556
- min.y = Math.min(min.y, y + padding);
3557
- max.y = Math.max(max.y, y + padding);
3558
- return [x, y];
3559
- };
3560
- for (let i = 0, len = indices.length; i < len; i += 3) {
3561
- const p1 = getPoint(indices[i]);
3562
- const p2 = getPoint(indices[i + 1]);
3563
- const p3 = getPoint(indices[i + 2]);
3564
- polygonStr += `<polygon points="${p1.join(",")} ${p2.join(",")} ${p3.join(",")}" fill="black" />`;
3736
+ moveTo(x, y) {
3737
+ if (!this.currentCurve.currentPoint?.equals({ x, y })) {
3738
+ if (this.currentCurve.curves.length) {
3739
+ this.currentCurve = new CurvePath();
3740
+ this.curves.push(this.currentCurve);
3565
3741
  }
3566
- });
3567
- const viewBox = [min.x, min.y, max.x - min.x, max.y - min.y];
3568
- return `<svg width="${viewBox[2]}" height="${viewBox[3]}" viewBox="${viewBox.join(" ")}" xmlns="http://www.w3.org/2000/svg">${polygonStr}</svg>`;
3742
+ this.currentCurve.moveTo(x, y);
3743
+ }
3744
+ return this;
3569
3745
  }
3570
- toTriangulatedSvg(result, padding) {
3571
- return new DOMParser().parseFromString(this.toTriangulatedSvgString(result, padding), "image/svg+xml").documentElement;
3746
+ lineTo(x, y) {
3747
+ this.currentCurve.lineTo(x, y);
3748
+ return this;
3572
3749
  }
3573
- toSvgString() {
3574
- const { x, y, width, height } = this.getBoundingBox();
3575
- const content = this.paths.map((path) => path.toSvgPathString()).join("");
3576
- return `<svg viewBox="${x} ${y} ${width} ${height}" width="${width}px" height="${height}px" xmlns="http://www.w3.org/2000/svg">${content}</svg>`;
3750
+ bezierCurveTo(cp1x, cp1y, cp2x, cp2y, x, y) {
3751
+ this.currentCurve.bezierCurveTo(cp1x, cp1y, cp2x, cp2y, x, y);
3752
+ return this;
3577
3753
  }
3578
- toSvgUrl() {
3579
- return `data:image/svg+xml;base64,${btoa(this.toSvgString())}`;
3754
+ quadraticCurveTo(cpx, cpy, x, y) {
3755
+ this.currentCurve.quadraticCurveTo(cpx, cpy, x, y);
3756
+ return this;
3580
3757
  }
3581
- toSvg() {
3582
- return new DOMParser().parseFromString(this.toSvgString(), "image/svg+xml").documentElement;
3758
+ arc(x, y, radius, startAngle, endAngle, counterclockwise) {
3759
+ this.currentCurve.arc(x, y, radius, startAngle, endAngle, counterclockwise);
3760
+ return this;
3583
3761
  }
3584
- toCanvas(options = {}) {
3585
- const { pixelRatio = 2, ...style } = options;
3586
- const { left, top, width, height } = this.getBoundingBox();
3587
- const canvas = document.createElement("canvas");
3588
- canvas.width = width * pixelRatio;
3589
- canvas.height = height * pixelRatio;
3590
- canvas.style.width = `${width}px`;
3591
- canvas.style.height = `${height}px`;
3592
- const ctx = canvas.getContext("2d");
3593
- if (ctx) {
3594
- ctx.scale(pixelRatio, pixelRatio);
3595
- ctx.translate(-left, -top);
3596
- this.paths.forEach((path) => {
3597
- path.drawTo(ctx, style);
3598
- });
3599
- }
3600
- return canvas;
3762
+ arcTo(x1, y1, x2, y2, radius) {
3763
+ this.currentCurve.arcTo(x1, y1, x2, y2, radius);
3764
+ return this;
3601
3765
  }
3602
- }
3603
-
3604
- class FFDControlGrid {
3605
- constructor(rows, cols, width = 1, height = 1) {
3606
- this.rows = rows;
3607
- this.cols = cols;
3608
- this.width = width;
3609
- this.height = height;
3610
- for (let i = 0; i < rows; i++) {
3611
- this.controlPoints[i] = [];
3612
- for (let j = 0; j < cols; j++) {
3613
- this.controlPoints[i][j] = {
3614
- x: j / (cols - 1) * width,
3615
- y: i / (rows - 1) * height
3616
- };
3617
- }
3618
- }
3766
+ ellipse(x, y, radiusX, radiusY, rotation, startAngle, endAngle, counterclockwise) {
3767
+ this.currentCurve.ellipse(x, y, radiusX, radiusY, rotation, startAngle, endAngle, counterclockwise);
3768
+ return this;
3619
3769
  }
3620
- controlPoints = [];
3621
- moveControlPoint(i, j, dx, dy) {
3622
- this.controlPoints[i][j].x += dx;
3623
- this.controlPoints[i][j].y += dy;
3770
+ rect(x, y, width, height) {
3771
+ this.currentCurve.rect(x, y, width, height);
3624
3772
  return this;
3625
3773
  }
3626
- }
3627
- function bsplineBasis(t) {
3628
- const B = [];
3629
- B[0] = (1 - t) ** 3 / 6;
3630
- B[1] = (3 * t ** 3 - 6 * t ** 2 + 4) / 6;
3631
- B[2] = (-3 * t ** 3 + 3 * t ** 2 + 3 * t + 1) / 6;
3632
- B[3] = t ** 3 / 6;
3633
- return B;
3634
- }
3635
- function applyFFD(point, grid, width = grid.width, height = grid.height) {
3636
- const s = point.x / width * (grid.cols - 1);
3637
- const t = point.y / height * (grid.rows - 1);
3638
- const i = Math.floor(s);
3639
- const j = Math.floor(t);
3640
- const u = s - i;
3641
- const v = t - j;
3642
- const bu = bsplineBasis(u);
3643
- const bv = bsplineBasis(v);
3644
- let x = 0;
3645
- let y = 0;
3646
- for (let m = 0; m < 4; m++) {
3647
- for (let n = 0; n < 4; n++) {
3648
- const row = Math.min(Math.max(j - 1 + m, 0), grid.rows - 1);
3649
- const col = Math.min(Math.max(i - 1 + n, 0), grid.cols - 1);
3650
- const cp = grid.controlPoints[row][col];
3651
- const weight = bu[n] * bv[m];
3652
- x += cp.x * weight;
3653
- y += cp.y * weight;
3654
- }
3774
+ roundRect(x, y, width, height, radii) {
3775
+ this.currentCurve.roundRect(x, y, width, height, radii);
3776
+ return this;
3655
3777
  }
3656
- point.set(x, y);
3657
- }
3658
-
3659
- const dataUri = "data:image/svg+xml;";
3660
- const base64DataUri = `${dataUri}base64,`;
3661
- const utf8DataUri = `${dataUri}charset=utf8,`;
3662
- function svgToDOM(svg) {
3663
- if (typeof svg === "string") {
3664
- let xml;
3665
- if (svg.startsWith(base64DataUri)) {
3666
- svg = svg.substring(base64DataUri.length, svg.length);
3667
- xml = atob(svg);
3668
- } else if (svg.startsWith(utf8DataUri)) {
3669
- svg = svg.substring(utf8DataUri.length, svg.length);
3670
- xml = decodeURIComponent(svg);
3671
- } else {
3672
- xml = svg;
3673
- }
3674
- const doc = new DOMParser().parseFromString(xml, "text/xml");
3675
- const error = doc.querySelector("parsererror");
3676
- if (error) {
3677
- throw new Error(`${error.textContent ?? "parser error"}
3678
- ${xml}`);
3679
- }
3680
- return doc.documentElement;
3681
- } else {
3682
- return svg;
3778
+ reset() {
3779
+ this.currentCurve = new CurvePath();
3780
+ this.curves = [this.currentCurve];
3781
+ this.style = {};
3782
+ return this;
3683
3783
  }
3684
- }
3685
-
3686
- const defaultUnit = "px";
3687
- const defaultDPI = 90;
3688
- const units = ["mm", "cm", "in", "pt", "pc", "px"];
3689
- const unitConversion = {
3690
- mm: {
3691
- mm: 1,
3692
- cm: 0.1,
3693
- in: 1 / 25.4,
3694
- pt: 72 / 25.4,
3695
- pc: 6 / 25.4,
3696
- px: -1
3697
- },
3698
- cm: {
3699
- mm: 10,
3700
- cm: 1,
3701
- in: 1 / 2.54,
3702
- pt: 72 / 2.54,
3703
- pc: 6 / 2.54,
3704
- px: -1
3705
- },
3706
- in: {
3707
- mm: 25.4,
3708
- cm: 2.54,
3709
- in: 1,
3710
- pt: 72,
3711
- pc: 6,
3712
- px: -1
3713
- },
3714
- pt: {
3715
- mm: 25.4 / 72,
3716
- cm: 2.54 / 72,
3717
- in: 1 / 72,
3718
- pt: 1,
3719
- pc: 6 / 72,
3720
- px: -1
3721
- },
3722
- pc: {
3723
- mm: 25.4 / 6,
3724
- cm: 2.54 / 6,
3725
- in: 1 / 6,
3726
- pt: 72 / 6,
3727
- pc: 1,
3728
- px: -1
3729
- },
3730
- px: {
3731
- px: 1
3784
+ addCommands(commands) {
3785
+ svgPathCommandsAddToPath2D(commands, this);
3786
+ return this;
3787
+ }
3788
+ addData(data) {
3789
+ this.addCommands(svgPathDataToCommands(data));
3790
+ return this;
3732
3791
  }
3733
- };
3734
- function parseFloatWithUnits(string) {
3735
- let theUnit = "px";
3736
- if (typeof string === "string") {
3737
- for (let i = 0, n = units.length; i < n; i++) {
3738
- const u = units[i];
3739
- if (string.endsWith(u)) {
3740
- theUnit = u;
3741
- string = string.substring(0, string.length - u.length);
3742
- break;
3743
- }
3744
- }
3792
+ splineThru(points) {
3793
+ this.currentCurve.splineThru(points);
3794
+ return this;
3745
3795
  }
3746
- let scale;
3747
- {
3748
- scale = unitConversion[theUnit][defaultUnit];
3749
- if (scale < 0) {
3750
- scale = unitConversion[theUnit].in * defaultDPI;
3751
- }
3796
+ scale(sx, sy = sx, target = { x: 0, y: 0 }) {
3797
+ this.getControlPointRefs().forEach((point) => {
3798
+ point.scale(sx, sy, target);
3799
+ });
3800
+ return this;
3752
3801
  }
3753
- return scale * Number.parseFloat(string);
3754
- }
3755
-
3756
- const tempTransform0 = new Matrix3();
3757
- const tempTransform1 = new Matrix3();
3758
- const tempTransform2 = new Matrix3();
3759
- const tempTransform3 = new Matrix3();
3760
- function getNodeTransform(node, currentTransform, transformStack) {
3761
- if (!(node.hasAttribute("transform") || node.nodeName === "use" && (node.hasAttribute("x") || node.hasAttribute("y")))) {
3762
- return null;
3802
+ skew(ax, ay = 0, target = { x: 0, y: 0 }) {
3803
+ this.getControlPointRefs().forEach((point) => {
3804
+ point.skew(ax, ay, target);
3805
+ });
3806
+ return this;
3763
3807
  }
3764
- const transform = parseNodeTransform(node);
3765
- if (transformStack.length > 0) {
3766
- transform.premultiply(transformStack[transformStack.length - 1]);
3808
+ rotate(a, target = { x: 0, y: 0 }) {
3809
+ this.getControlPointRefs().forEach((point) => {
3810
+ point.rotate(a, target);
3811
+ });
3812
+ return this;
3767
3813
  }
3768
- currentTransform.copy(transform);
3769
- transformStack.push(transform);
3770
- return transform;
3771
- }
3772
- function parseNodeTransform(node) {
3773
- const transform = new Matrix3();
3774
- const currentTransform = tempTransform0;
3775
- if (node.nodeName === "use" && (node.hasAttribute("x") || node.hasAttribute("y"))) {
3776
- transform.translate(
3777
- parseFloatWithUnits(node.getAttribute("x")),
3778
- parseFloatWithUnits(node.getAttribute("y"))
3779
- );
3814
+ bold(b) {
3815
+ if (b === 0) {
3816
+ return this;
3817
+ }
3818
+ const curves = this.getFlatCurves();
3819
+ const _list = [];
3820
+ const _isClockwise = [];
3821
+ const _points = [];
3822
+ curves.forEach((curve, index) => {
3823
+ const points = curve.getControlPointRefs();
3824
+ const isClockwise = curve.isClockwise();
3825
+ _points[index] = points;
3826
+ _isClockwise[index] = isClockwise;
3827
+ const start = points[0];
3828
+ const end = points[points.length - 1] ?? start;
3829
+ _list.push({
3830
+ start: isClockwise ? end : start,
3831
+ end: isClockwise ? start : end,
3832
+ index
3833
+ });
3834
+ });
3835
+ const list = [];
3836
+ _list.forEach((itemA, indexA) => {
3837
+ list[indexA] = [];
3838
+ _list.forEach((itemB, indexB) => {
3839
+ if (itemB.start && itemA.end && indexB !== indexA && itemB.start?.equals(itemA.end)) {
3840
+ list[indexA].push(itemB.index);
3841
+ }
3842
+ });
3843
+ });
3844
+ curves.forEach((curve, index) => {
3845
+ const isClockwise = _isClockwise[index];
3846
+ _points[index].forEach((point) => {
3847
+ point.add(
3848
+ curve.getNormal(
3849
+ curve.getTForPoint(point)
3850
+ ).scale(isClockwise ? b : -b)
3851
+ );
3852
+ });
3853
+ });
3854
+ list.forEach((indexes, indexA) => {
3855
+ const pointsA = _points[indexA];
3856
+ indexes.forEach((indexB) => {
3857
+ const pointsB = _points[indexB];
3858
+ const point = getIntersectionPoint(
3859
+ pointsA[pointsA.length - 1],
3860
+ pointsA[pointsA.length - 2] ?? pointsA[pointsA.length - 1],
3861
+ pointsB[0],
3862
+ pointsB[1] ?? pointsB[0]
3863
+ );
3864
+ if (point) {
3865
+ pointsA[pointsA.length - 1].copy(point);
3866
+ pointsB[0].copy(point);
3867
+ }
3868
+ });
3869
+ });
3870
+ return this;
3780
3871
  }
3781
- if (node.hasAttribute("transform")) {
3782
- const transformsTexts = node.getAttribute("transform").split(")");
3783
- for (let tIndex = transformsTexts.length - 1; tIndex >= 0; tIndex--) {
3784
- const transformText = transformsTexts[tIndex].trim();
3785
- if (transformText === "")
3786
- continue;
3787
- const openParPos = transformText.indexOf("(");
3788
- const closeParPos = transformText.length;
3789
- if (openParPos > 0 && openParPos < closeParPos) {
3790
- const transformType = transformText.slice(0, openParPos);
3791
- const array = parsePathDataArgs(transformText.slice(openParPos + 1));
3792
- currentTransform.identity();
3793
- switch (transformType) {
3794
- case "translate":
3795
- if (array.length >= 1) {
3796
- const tx = array[0];
3797
- let ty = 0;
3798
- if (array.length >= 2) {
3799
- ty = array[1];
3800
- }
3801
- currentTransform.translate(tx, ty);
3802
- }
3803
- break;
3804
- case "rotate":
3805
- if (array.length >= 1) {
3806
- let angle = 0;
3807
- let cx = 0;
3808
- let cy = 0;
3809
- angle = array[0] * Math.PI / 180;
3810
- if (array.length >= 3) {
3811
- cx = array[1];
3812
- cy = array[2];
3813
- }
3814
- tempTransform1.makeTranslation(-cx, -cy);
3815
- tempTransform2.makeRotation(angle);
3816
- tempTransform3.multiplyMatrices(tempTransform2, tempTransform1);
3817
- tempTransform1.makeTranslation(cx, cy);
3818
- currentTransform.multiplyMatrices(tempTransform1, tempTransform3);
3819
- }
3820
- break;
3821
- case "scale":
3822
- if (array.length >= 1) {
3823
- currentTransform.scale(
3824
- array[0],
3825
- array[1] ?? array[0]
3826
- );
3827
- }
3828
- break;
3829
- case "skewX":
3830
- if (array.length === 1) {
3831
- currentTransform.set(
3832
- 1,
3833
- Math.tan(array[0] * Math.PI / 180),
3834
- 0,
3835
- 0,
3836
- 1,
3837
- 0,
3838
- 0,
3839
- 0,
3840
- 1
3841
- );
3842
- }
3843
- break;
3844
- case "skewY":
3845
- if (array.length === 1) {
3846
- currentTransform.set(
3847
- 1,
3848
- 0,
3849
- 0,
3850
- Math.tan(array[0] * Math.PI / 180),
3851
- 1,
3852
- 0,
3853
- 0,
3854
- 0,
3855
- 1
3856
- );
3857
- }
3858
- break;
3859
- case "matrix":
3860
- if (array.length === 6) {
3861
- currentTransform.set(
3862
- array[0],
3863
- array[2],
3864
- array[4],
3865
- array[1],
3866
- array[3],
3867
- array[5],
3868
- 0,
3869
- 0,
3870
- 1
3871
- );
3872
+ getMinMax(min = Vector2.MAX, max = Vector2.MIN, withStyle = true) {
3873
+ const strokeWidth = this.strokeWidth;
3874
+ this.curves.forEach((curve) => {
3875
+ curve.getMinMax(min, max);
3876
+ if (withStyle) {
3877
+ if (strokeWidth > 1) {
3878
+ const halfStrokeWidth = strokeWidth / 2;
3879
+ const isClockwise = curve.isClockwise();
3880
+ const points = [];
3881
+ for (let t = 0; t <= 1; t += 1 / curve.arcLengthDivision) {
3882
+ const point = curve.getPoint(t);
3883
+ const normal = curve.getNormal(t);
3884
+ const dist1 = normal.clone().scale(isClockwise ? halfStrokeWidth : -halfStrokeWidth);
3885
+ const dist2 = normal.clone().scale(isClockwise ? -halfStrokeWidth : halfStrokeWidth);
3886
+ points.push(
3887
+ point.clone().add(dist1),
3888
+ point.clone().add(dist2),
3889
+ point.clone().add({ x: halfStrokeWidth, y: 0 }),
3890
+ point.clone().add({ x: -halfStrokeWidth, y: 0 }),
3891
+ point.clone().add({ x: 0, y: halfStrokeWidth }),
3892
+ point.clone().add({ x: 0, y: -halfStrokeWidth }),
3893
+ point.clone().add({ x: halfStrokeWidth, y: halfStrokeWidth }),
3894
+ point.clone().add({ x: -halfStrokeWidth, y: -halfStrokeWidth })
3895
+ );
3896
+ }
3897
+ min.min(...points);
3898
+ max.max(...points);
3899
+ }
3900
+ }
3901
+ });
3902
+ return { min: min.finite(), max: max.finite() };
3903
+ }
3904
+ strokeTriangulate(options) {
3905
+ const indices = options?.indices ?? [];
3906
+ const vertices = options?.vertices ?? [];
3907
+ this.curves.forEach((curve) => {
3908
+ curve.strokeTriangulate({
3909
+ ...options,
3910
+ indices,
3911
+ vertices,
3912
+ style: { ...this.style }
3913
+ });
3914
+ });
3915
+ return { indices, vertices };
3916
+ }
3917
+ fillTriangulate(options) {
3918
+ const _options = {
3919
+ ...options,
3920
+ style: {
3921
+ ...this.style,
3922
+ ...options?.style
3923
+ }
3924
+ };
3925
+ const indices = _options.indices ?? [];
3926
+ const vertices = _options.vertices ?? [];
3927
+ const fillRule = _options.style.fillRule ?? "nonzero";
3928
+ if (fillRule === "nonzero") {
3929
+ const pointArrays = this.curves.map((curve) => curve.getFillVertices(_options));
3930
+ const parentMap = /* @__PURE__ */ new Map();
3931
+ const parentd = /* @__PURE__ */ new Set();
3932
+ for (let i = 0; i < pointArrays.length; i++) {
3933
+ const parents = [];
3934
+ for (let j = 0; j < pointArrays.length; j++) {
3935
+ if (i === j)
3936
+ continue;
3937
+ if (pointInPolygon([pointArrays[i][0], pointArrays[i][1]], pointArrays[j])) {
3938
+ parents.push(j);
3939
+ }
3940
+ }
3941
+ if (parents.length) {
3942
+ parents.forEach((pi) => {
3943
+ let set = parentMap.get(pi);
3944
+ if (!set) {
3945
+ set = /* @__PURE__ */ new Set();
3946
+ parentMap.set(pi, set);
3872
3947
  }
3873
- break;
3948
+ set.add(i);
3949
+ });
3950
+ parentd.add(i);
3874
3951
  }
3875
3952
  }
3876
- transform.premultiply(currentTransform);
3953
+ pointArrays.forEach((pointArray, i) => {
3954
+ if (parentd.has(i) || !pointArray.length) {
3955
+ return;
3956
+ }
3957
+ const _pointArray = pointArray.slice();
3958
+ const holes = [];
3959
+ parentMap.get(i)?.forEach((ci) => {
3960
+ holes.push(_pointArray.length / 2);
3961
+ _pointArray.push(...pointArrays[ci]);
3962
+ });
3963
+ fillTriangulate(_pointArray, {
3964
+ ...options,
3965
+ indices,
3966
+ vertices,
3967
+ holes,
3968
+ style: { ...this.style }
3969
+ });
3970
+ });
3971
+ } else {
3972
+ this.curves.forEach((curve) => {
3973
+ curve.fillTriangulate({
3974
+ ...options,
3975
+ indices,
3976
+ vertices,
3977
+ style: { ...this.style }
3978
+ });
3979
+ });
3877
3980
  }
3981
+ return { indices, vertices };
3878
3982
  }
3879
- return transform;
3880
- }
3881
-
3882
- function parseCircleNode(node) {
3883
- return new Path2D().arc(
3884
- parseFloatWithUnits(node.getAttribute("cx") || 0),
3885
- parseFloatWithUnits(node.getAttribute("cy") || 0),
3886
- parseFloatWithUnits(node.getAttribute("r") || 0),
3887
- 0,
3888
- Math.PI * 2
3889
- );
3890
- }
3891
-
3892
- function parseCSSStylesheet(node, stylesheets) {
3893
- if (!node.sheet || !node.sheet.cssRules || !node.sheet.cssRules.length)
3894
- return;
3895
- for (let i = 0; i < node.sheet.cssRules.length; i++) {
3896
- const stylesheet = node.sheet.cssRules[i];
3897
- if (stylesheet.type !== 1)
3898
- continue;
3899
- const selectorList = stylesheet.selectorText.split(/,/g).filter(Boolean).map((i2) => i2.trim());
3900
- const definitions = {};
3901
- for (let len = stylesheet.style.length, i2 = 0; i2 < len; i2++) {
3902
- const name = stylesheet.style.item(i2);
3903
- definitions[name] = stylesheet.style.getPropertyValue(name);
3983
+ getBoundingBox(withStyle = true) {
3984
+ const { min, max } = this.getMinMax(void 0, void 0, withStyle);
3985
+ return new BoundingBox(min.x, min.y, max.x - min.x, max.y - min.y);
3986
+ }
3987
+ drawTo(ctx, style = {}) {
3988
+ style = { ...this.style, ...style };
3989
+ const { fill = "#000", stroke = "none" } = style;
3990
+ ctx.beginPath();
3991
+ ctx.save();
3992
+ setCanvasContext(ctx, style);
3993
+ this.curves.forEach((path) => {
3994
+ path.drawTo(ctx);
3995
+ });
3996
+ if (fill !== "none") {
3997
+ ctx.fill();
3904
3998
  }
3905
- for (let j = 0; j < selectorList.length; j++) {
3906
- stylesheets[selectorList[j]] = Object.assign(
3907
- stylesheets[selectorList[j]] || {},
3908
- { ...definitions }
3909
- );
3999
+ if (stroke !== "none") {
4000
+ ctx.stroke();
3910
4001
  }
4002
+ ctx.restore();
4003
+ return this;
3911
4004
  }
3912
- }
3913
-
3914
- function parseEllipseNode(node) {
3915
- return new Path2D().ellipse(
3916
- parseFloatWithUnits(node.getAttribute("cx") || 0),
3917
- parseFloatWithUnits(node.getAttribute("cy") || 0),
3918
- parseFloatWithUnits(node.getAttribute("rx") || 0),
3919
- parseFloatWithUnits(node.getAttribute("ry") || 0),
3920
- 0,
3921
- 0,
3922
- Math.PI * 2
3923
- );
3924
- }
3925
-
3926
- function parseLineNode(node) {
3927
- return new Path2D().moveTo(
3928
- parseFloatWithUnits(node.getAttribute("x1") || 0),
3929
- parseFloatWithUnits(node.getAttribute("y1") || 0)
3930
- ).lineTo(
3931
- parseFloatWithUnits(node.getAttribute("x2") || 0),
3932
- parseFloatWithUnits(node.getAttribute("y2") || 0)
3933
- );
3934
- }
3935
-
3936
- function parsePathNode(node) {
3937
- const path = new Path2D();
3938
- const d = node.getAttribute("d");
3939
- if (!d || d === "none")
3940
- return null;
3941
- path.addData(d);
3942
- return path;
3943
- }
3944
-
3945
- const RE$1 = /([+-]?(?:\d+(?:\.\d+)?|\.\d+)(?:e[+-]?\d+)?)(?:,|\s)([+-]?\d*\.?\d+(?:e[+-]?\d+)?)/g;
3946
- function parsePolygonNode(node) {
3947
- const path = new Path2D();
3948
- let index = 0;
3949
- node.getAttribute("points")?.replace(RE$1, (match, a, b) => {
3950
- const x = parseFloatWithUnits(a);
3951
- const y = parseFloatWithUnits(b);
3952
- if (index === 0) {
3953
- path.moveTo(x, y);
3954
- } else {
3955
- path.lineTo(x, y);
4005
+ drawControlPointsTo(ctx, style = {}) {
4006
+ style = { ...this.style, ...style };
4007
+ const { fill = "#000", stroke = "none" } = style;
4008
+ ctx.beginPath();
4009
+ ctx.save();
4010
+ setCanvasContext(ctx, style);
4011
+ this.getControlPointRefs().forEach((point) => {
4012
+ drawPoint(ctx, point.x, point.y, { radius: 4 });
4013
+ });
4014
+ if (fill !== "none") {
4015
+ ctx.fill();
3956
4016
  }
3957
- index++;
3958
- return match;
3959
- });
3960
- path.currentCurve.autoClose = true;
3961
- return path;
3962
- }
3963
-
3964
- const RE = /([+-]?(?:\d+(?:\.\d+)?|\.\d+)(?:e[+-]?\d+)?)(?:,|\s)([+-]?\d*\.?\d+(?:e[+-]?\d+)?)/g;
3965
- function parsePolylineNode(node) {
3966
- const path = new Path2D();
3967
- let index = 0;
3968
- node.getAttribute("points")?.replace(RE, (match, a, b) => {
3969
- const x = parseFloatWithUnits(a);
3970
- const y = parseFloatWithUnits(b);
3971
- if (index === 0) {
3972
- path.moveTo(x, y);
3973
- } else {
3974
- path.lineTo(x, y);
4017
+ if (stroke !== "none") {
4018
+ ctx.stroke();
3975
4019
  }
3976
- index++;
3977
- return match;
3978
- });
3979
- path.currentCurve.autoClose = false;
3980
- return path;
3981
- }
3982
-
3983
- function parseRectNode(node) {
3984
- const x = parseFloatWithUnits(node.getAttribute("x") || 0);
3985
- const y = parseFloatWithUnits(node.getAttribute("y") || 0);
3986
- const rx = parseFloatWithUnits(node.getAttribute("rx") || node.getAttribute("ry") || 0);
3987
- const ry = parseFloatWithUnits(node.getAttribute("ry") || node.getAttribute("rx") || 0);
3988
- const w = parseFloatWithUnits(node.getAttribute("width"));
3989
- const h = parseFloatWithUnits(node.getAttribute("height"));
3990
- const bci = 1 - 0.551915024494;
3991
- const path = new Path2D();
3992
- path.moveTo(x + rx, y);
3993
- path.lineTo(x + w - rx, y);
3994
- if (rx !== 0 || ry !== 0) {
3995
- path.bezierCurveTo(
3996
- x + w - rx * bci,
3997
- y,
3998
- x + w,
3999
- y + ry * bci,
4000
- x + w,
4001
- y + ry
4002
- );
4020
+ ctx.restore();
4021
+ return this;
4003
4022
  }
4004
- path.lineTo(x + w, y + h - ry);
4005
- if (rx !== 0 || ry !== 0) {
4006
- path.bezierCurveTo(
4007
- x + w,
4008
- y + h - ry * bci,
4009
- x + w - rx * bci,
4010
- y + h,
4011
- x + w - rx,
4012
- y + h
4013
- );
4023
+ toCommands() {
4024
+ return this.curves.flatMap((v) => v.toCommands());
4014
4025
  }
4015
- path.lineTo(x + rx, y + h);
4016
- if (rx !== 0 || ry !== 0) {
4017
- path.bezierCurveTo(
4018
- x + rx * bci,
4019
- y + h,
4020
- x,
4021
- y + h - ry * bci,
4022
- x,
4023
- y + h - ry
4024
- );
4026
+ toData() {
4027
+ return this.curves.filter((v) => v.curves.length).map((v) => v.toData()).join(" ");
4028
+ }
4029
+ toSvgPathString() {
4030
+ const style = {
4031
+ ...this.style,
4032
+ fill: this.style.fill ?? "#000",
4033
+ stroke: this.style.stroke ?? "none"
4034
+ };
4035
+ const cssStyle = {};
4036
+ for (const key in style) {
4037
+ if (style[key] !== void 0) {
4038
+ cssStyle[toKebabCase(key)] = style[key];
4039
+ }
4040
+ }
4041
+ Object.assign(cssStyle, {
4042
+ "stroke-width": `${this.strokeWidth}px`
4043
+ });
4044
+ let cssText = "";
4045
+ for (const key in cssStyle) {
4046
+ if (cssStyle[key] !== void 0) {
4047
+ cssText += `${key}:${cssStyle[key]};`;
4048
+ }
4049
+ }
4050
+ return `<path d="${this.toData()}" style="${cssText}"></path>`;
4025
4051
  }
4026
- path.lineTo(x, y + ry);
4027
- if (rx !== 0 || ry !== 0) {
4028
- path.bezierCurveTo(x, y + ry * bci, x + rx * bci, y, x + rx, y);
4052
+ copy(source) {
4053
+ super.copy(source);
4054
+ this.currentCurve = source.currentCurve.clone();
4055
+ this.style = { ...source.style };
4056
+ return this;
4029
4057
  }
4030
- return path;
4031
4058
  }
4032
4059
 
4033
- function parseStyle(node, style, stylesheets) {
4034
- style = Object.assign({}, style);
4035
- let stylesheetStyles = {};
4036
- if (node.hasAttribute("class")) {
4037
- const classSelectors = node.getAttribute("class").split(/\s/).filter(Boolean).map((i) => i.trim());
4038
- for (let i = 0; i < classSelectors.length; i++) {
4039
- stylesheetStyles = Object.assign(stylesheetStyles, stylesheets[`.${classSelectors[i]}`]);
4040
- }
4060
+ class Path2DSet {
4061
+ constructor(paths = [], viewBox) {
4062
+ this.paths = paths;
4063
+ this.viewBox = viewBox;
4041
4064
  }
4042
- if (node.hasAttribute("id")) {
4043
- stylesheetStyles = Object.assign(stylesheetStyles, stylesheets[`#${node.getAttribute("id")}`]);
4065
+ getBoundingBox(withStyle = true) {
4066
+ if (!this.paths.length) {
4067
+ return void 0;
4068
+ }
4069
+ const min = Vector2.MAX;
4070
+ const max = Vector2.MIN;
4071
+ this.paths.forEach((path) => path.getMinMax(min, max, withStyle));
4072
+ return new BoundingBox(min.x, min.y, max.x - min.x, max.y - min.y);
4044
4073
  }
4045
- for (let len = node.style.length, i = 0; i < len; i++) {
4046
- const name = node.style.item(i);
4047
- const value = node.style.getPropertyValue(name);
4048
- style[name] = value;
4049
- stylesheetStyles[name] = value;
4074
+ toTriangulatedSvgString(result = this.paths.map((p) => p.fillTriangulate()), padding = 0) {
4075
+ let polygonStr = "";
4076
+ const min = { x: -padding, y: -padding };
4077
+ const max = { x: padding, y: padding };
4078
+ const results = Array.isArray(result) ? result : [result];
4079
+ results.forEach(({ vertices, indices }) => {
4080
+ const getPoint = (indice) => {
4081
+ const x = vertices[indice * 2];
4082
+ const y = vertices[indice * 2 + 1];
4083
+ min.x = Math.min(min.x, x + padding);
4084
+ max.x = Math.max(max.x, x + padding);
4085
+ min.y = Math.min(min.y, y + padding);
4086
+ max.y = Math.max(max.y, y + padding);
4087
+ return [x, y];
4088
+ };
4089
+ for (let i = 0, len = indices.length; i < len; i += 3) {
4090
+ const p1 = getPoint(indices[i]);
4091
+ const p2 = getPoint(indices[i + 1]);
4092
+ const p3 = getPoint(indices[i + 2]);
4093
+ polygonStr += `<polygon points="${p1.join(",")} ${p2.join(",")} ${p3.join(",")}" fill="black" />`;
4094
+ }
4095
+ });
4096
+ const viewBox = [min.x, min.y, max.x - min.x, max.y - min.y];
4097
+ return `<svg width="${viewBox[2]}" height="${viewBox[3]}" viewBox="${viewBox.join(" ")}" xmlns="http://www.w3.org/2000/svg">${polygonStr}</svg>`;
4050
4098
  }
4051
- function addStyle(svgName, jsName, adjustFunction = copy) {
4052
- if (node.hasAttribute(svgName))
4053
- style[jsName] = adjustFunction(node.getAttribute(svgName));
4054
- if (stylesheetStyles[svgName])
4055
- style[jsName] = adjustFunction(stylesheetStyles[svgName]);
4099
+ toTriangulatedSvg(result, padding) {
4100
+ return new DOMParser().parseFromString(this.toTriangulatedSvgString(result, padding), "image/svg+xml").documentElement;
4056
4101
  }
4057
- function copy(v) {
4058
- if (v.startsWith("url"))
4059
- console.warn("url access in attributes is not implemented.");
4060
- return v;
4102
+ toSvgString() {
4103
+ const { x, y, width, height } = this.getBoundingBox();
4104
+ const content = this.paths.map((path) => path.toSvgPathString()).join("");
4105
+ return `<svg viewBox="${x} ${y} ${width} ${height}" width="${width}px" height="${height}px" xmlns="http://www.w3.org/2000/svg">${content}</svg>`;
4061
4106
  }
4062
- function clamp(v) {
4063
- return Math.max(0, Math.min(1, parseFloatWithUnits(v)));
4107
+ toSvgUrl() {
4108
+ return `data:image/svg+xml;base64,${btoa(this.toSvgString())}`;
4064
4109
  }
4065
- function positive(v) {
4066
- return Math.max(0, parseFloatWithUnits(v));
4110
+ toSvg() {
4111
+ return new DOMParser().parseFromString(this.toSvgString(), "image/svg+xml").documentElement;
4067
4112
  }
4068
- function array(v) {
4069
- return v.split(" ").filter((v2) => v2 !== "").map((v2) => parseFloatWithUnits(v2));
4113
+ toCanvas(options = {}) {
4114
+ const { pixelRatio = 2, ...style } = options;
4115
+ const { left, top, width, height } = this.getBoundingBox();
4116
+ const canvas = document.createElement("canvas");
4117
+ canvas.width = width * pixelRatio;
4118
+ canvas.height = height * pixelRatio;
4119
+ canvas.style.width = `${width}px`;
4120
+ canvas.style.height = `${height}px`;
4121
+ const ctx = canvas.getContext("2d");
4122
+ if (ctx) {
4123
+ ctx.scale(pixelRatio, pixelRatio);
4124
+ ctx.translate(-left, -top);
4125
+ this.paths.forEach((path) => {
4126
+ path.drawTo(ctx, style);
4127
+ });
4128
+ }
4129
+ return canvas;
4070
4130
  }
4071
- addStyle("fill", "fill");
4072
- addStyle("fill-opacity", "fillOpacity", clamp);
4073
- addStyle("fill-rule", "fillRule");
4074
- addStyle("opacity", "opacity", clamp);
4075
- addStyle("stroke", "stroke");
4076
- addStyle("stroke-opacity", "strokeOpacity", clamp);
4077
- addStyle("stroke-width", "strokeWidth", positive);
4078
- addStyle("stroke-linecap", "strokeLinecap");
4079
- addStyle("stroke-linejoin", "strokeLinejoin");
4080
- addStyle("stroke-miterlimit", "strokeMiterlimit", positive);
4081
- addStyle("stroke-dasharray", "strokeDasharray", array);
4082
- addStyle("stroke-dashoffset", "strokeDashoffset", parseFloatWithUnits);
4083
- addStyle("visibility", "visibility");
4084
- return style;
4085
4131
  }
4086
4132
 
4087
- function parseNode(node, style, paths = [], stylesheets = {}) {
4088
- if (node.nodeType !== 1)
4089
- return paths;
4090
- let isDefsNode = false;
4091
- let path = null;
4092
- let _style = { ...style };
4093
- switch (node.nodeName) {
4094
- case "svg":
4095
- _style = parseStyle(node, _style, stylesheets);
4096
- break;
4097
- case "style":
4098
- parseCSSStylesheet(node, stylesheets);
4099
- break;
4100
- case "g":
4101
- _style = parseStyle(node, _style, stylesheets);
4102
- break;
4103
- case "path":
4104
- _style = parseStyle(node, _style, stylesheets);
4105
- if (node.hasAttribute("d"))
4106
- path = parsePathNode(node);
4107
- break;
4108
- case "rect":
4109
- _style = parseStyle(node, _style, stylesheets);
4110
- path = parseRectNode(node);
4111
- break;
4112
- case "polygon":
4113
- _style = parseStyle(node, _style, stylesheets);
4114
- path = parsePolygonNode(node);
4115
- break;
4116
- case "polyline":
4117
- _style = parseStyle(node, _style, stylesheets);
4118
- path = parsePolylineNode(node);
4119
- break;
4120
- case "circle":
4121
- _style = parseStyle(node, _style, stylesheets);
4122
- path = parseCircleNode(node);
4123
- break;
4124
- case "ellipse":
4125
- _style = parseStyle(node, _style, stylesheets);
4126
- path = parseEllipseNode(node);
4127
- break;
4128
- case "line":
4129
- _style = parseStyle(node, _style, stylesheets);
4130
- path = parseLineNode(node);
4131
- break;
4132
- case "defs":
4133
- isDefsNode = true;
4134
- break;
4135
- case "use": {
4136
- _style = parseStyle(node, _style, stylesheets);
4137
- const href = node.getAttributeNS("http://www.w3.org/1999/xlink", "href") || node.getAttribute("href") || "";
4138
- const usedNodeId = href.substring(1);
4139
- const usedNode = node.viewportElement?.getElementById(usedNodeId);
4140
- if (usedNode) {
4141
- parseNode(usedNode, _style, paths, stylesheets);
4142
- } else {
4143
- console.warn(`'use node' references non-existent node id: ${usedNodeId}`);
4133
+ class FFDControlGrid {
4134
+ constructor(rows, cols, width = 1, height = 1) {
4135
+ this.rows = rows;
4136
+ this.cols = cols;
4137
+ this.width = width;
4138
+ this.height = height;
4139
+ for (let i = 0; i < rows; i++) {
4140
+ this.controlPoints[i] = [];
4141
+ for (let j = 0; j < cols; j++) {
4142
+ this.controlPoints[i][j] = {
4143
+ x: j / (cols - 1) * width,
4144
+ y: i / (rows - 1) * height
4145
+ };
4144
4146
  }
4145
- break;
4146
4147
  }
4147
- default:
4148
- console.warn(node);
4149
- break;
4150
- }
4151
- if (_style.display === "none") {
4152
- return paths;
4153
- }
4154
- const currentTransform = new Matrix3();
4155
- const transformStack = [];
4156
- const transform = getNodeTransform(node, currentTransform, transformStack);
4157
- if (path) {
4158
- path.applyTransform(currentTransform);
4159
- paths.push(path);
4160
- path.style = { ..._style };
4161
4148
  }
4162
- const childNodes = node.childNodes;
4163
- for (let i = 0, len = childNodes.length; i < len; i++) {
4164
- const node2 = childNodes[i];
4165
- if (isDefsNode && node2.nodeName !== "style" && node2.nodeName !== "defs")
4166
- continue;
4167
- parseNode(node2, _style, paths, stylesheets);
4149
+ controlPoints = [];
4150
+ moveControlPoint(i, j, dx, dy) {
4151
+ this.controlPoints[i][j].x += dx;
4152
+ this.controlPoints[i][j].y += dy;
4153
+ return this;
4168
4154
  }
4169
- if (transform) {
4170
- transformStack.pop();
4171
- if (transformStack.length > 0) {
4172
- currentTransform.copy(transformStack[transformStack.length - 1]);
4173
- } else {
4174
- currentTransform.identity();
4155
+ }
4156
+ function bsplineBasis(t) {
4157
+ const B = [];
4158
+ B[0] = (1 - t) ** 3 / 6;
4159
+ B[1] = (3 * t ** 3 - 6 * t ** 2 + 4) / 6;
4160
+ B[2] = (-3 * t ** 3 + 3 * t ** 2 + 3 * t + 1) / 6;
4161
+ B[3] = t ** 3 / 6;
4162
+ return B;
4163
+ }
4164
+ function applyFFD(point, grid, width = grid.width, height = grid.height) {
4165
+ const s = point.x / width * (grid.cols - 1);
4166
+ const t = point.y / height * (grid.rows - 1);
4167
+ const i = Math.floor(s);
4168
+ const j = Math.floor(t);
4169
+ const u = s - i;
4170
+ const v = t - j;
4171
+ const bu = bsplineBasis(u);
4172
+ const bv = bsplineBasis(v);
4173
+ let x = 0;
4174
+ let y = 0;
4175
+ for (let m = 0; m < 4; m++) {
4176
+ for (let n = 0; n < 4; n++) {
4177
+ const row = Math.min(Math.max(j - 1 + m, 0), grid.rows - 1);
4178
+ const col = Math.min(Math.max(i - 1 + n, 0), grid.cols - 1);
4179
+ const cp = grid.controlPoints[row][col];
4180
+ const weight = bu[n] * bv[m];
4181
+ x += cp.x * weight;
4182
+ y += cp.y * weight;
4175
4183
  }
4176
4184
  }
4177
- return paths;
4178
- }
4179
-
4180
- function svgToPath2DSet(svg) {
4181
- const dom = svgToDOM(svg);
4182
- return new Path2DSet(
4183
- parseNode(dom, {}),
4184
- dom.getAttribute("viewBox")?.trim().split(" ").map((v) => Number(v))
4185
- );
4185
+ point.set(x, y);
4186
4186
  }
4187
4187
 
4188
4188
  exports.ArcCurve = ArcCurve;
@@ -4221,5 +4221,5 @@ exports.strokeTriangulate = strokeTriangulate;
4221
4221
  exports.svgPathCommandsAddToPath2D = svgPathCommandsAddToPath2D;
4222
4222
  exports.svgPathCommandsToData = svgPathCommandsToData;
4223
4223
  exports.svgPathDataToCommands = svgPathDataToCommands;
4224
- exports.svgToDOM = svgToDOM;
4224
+ exports.svgToDom = svgToDom;
4225
4225
  exports.svgToPath2DSet = svgToPath2DSet;