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