@emasoft/svg-matrix 1.2.1 → 1.3.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.
@@ -196,6 +196,97 @@ export function matricesEqual(m1, m2, tolerance = VERIFICATION_TOLERANCE) {
196
196
  return matrixMaxDifference(m1, m2).lessThan(D(tolerance));
197
197
  }
198
198
 
199
+ /**
200
+ * Compute the combined matrix from a list of transform objects.
201
+ * Used internally for verification that optimizations preserve the transform result.
202
+ *
203
+ * @param {Array<Object>} transforms - Array of transform objects with type and params
204
+ * @returns {Matrix} Combined 3x3 transformation matrix
205
+ *
206
+ * @example
207
+ * const transforms = [
208
+ * { type: 'translate', params: { tx: 10, ty: 20 } },
209
+ * { type: 'rotate', params: { angle: 0.5 } }
210
+ * ];
211
+ * const combined = computeCombinedMatrix(transforms);
212
+ */
213
+ export function computeCombinedMatrix(transforms) {
214
+ let combined = identityMatrix();
215
+
216
+ for (const t of transforms) {
217
+ // Validate transform object structure
218
+ if (
219
+ !t ||
220
+ typeof t !== "object" ||
221
+ !t.type ||
222
+ !t.params ||
223
+ typeof t.params !== "object"
224
+ ) {
225
+ continue; // Skip malformed transforms
226
+ }
227
+
228
+ let m = null;
229
+ switch (t.type) {
230
+ case "translate":
231
+ if (
232
+ t.params.tx === null ||
233
+ t.params.tx === undefined ||
234
+ t.params.ty === null ||
235
+ t.params.ty === undefined
236
+ ) {
237
+ continue; // Skip transforms with missing params
238
+ }
239
+ m = translationMatrix(t.params.tx, t.params.ty);
240
+ break;
241
+ case "rotate":
242
+ if (t.params.angle === null || t.params.angle === undefined) {
243
+ continue; // Skip transforms with missing angle
244
+ }
245
+ if (
246
+ t.params.cx !== undefined &&
247
+ t.params.cx !== null &&
248
+ t.params.cy !== undefined &&
249
+ t.params.cy !== null
250
+ ) {
251
+ m = rotationMatrixAroundPoint(
252
+ t.params.angle,
253
+ t.params.cx,
254
+ t.params.cy,
255
+ );
256
+ } else {
257
+ m = rotationMatrix(t.params.angle);
258
+ }
259
+ break;
260
+ case "scale":
261
+ if (
262
+ t.params.sx === null ||
263
+ t.params.sx === undefined ||
264
+ t.params.sy === null ||
265
+ t.params.sy === undefined
266
+ ) {
267
+ continue; // Skip transforms with missing params
268
+ }
269
+ m = scaleMatrix(t.params.sx, t.params.sy);
270
+ break;
271
+ case "matrix":
272
+ if (!t.params.matrix) {
273
+ continue; // Skip transforms with missing matrix
274
+ }
275
+ m = t.params.matrix;
276
+ break;
277
+ default:
278
+ // Skip unknown transform types
279
+ continue;
280
+ }
281
+
282
+ if (m !== null) {
283
+ combined = combined.mul(m);
284
+ }
285
+ }
286
+
287
+ return combined;
288
+ }
289
+
199
290
  // ============================================================================
200
291
  // Transform Merging Functions
201
292
  // ============================================================================
@@ -920,77 +1011,7 @@ export function optimizeTransformList(transforms) {
920
1011
  }
921
1012
 
922
1013
  // Calculate original combined matrix for verification
923
- let originalMatrix = identityMatrix();
924
- for (const t of transforms) {
925
- // Validate transform object structure
926
- if (
927
- !t ||
928
- typeof t !== "object" ||
929
- !t.type ||
930
- !t.params ||
931
- typeof t.params !== "object"
932
- ) {
933
- continue; // Skip malformed transforms
934
- }
935
-
936
- let m = null; // Initialize m to null to catch missing assignments
937
- switch (t.type) {
938
- case "translate":
939
- if (
940
- t.params.tx === null ||
941
- t.params.tx === undefined ||
942
- t.params.ty === null ||
943
- t.params.ty === undefined
944
- ) {
945
- continue; // Skip transforms with missing params
946
- }
947
- m = translationMatrix(t.params.tx, t.params.ty);
948
- break;
949
- case "rotate":
950
- if (t.params.angle === null || t.params.angle === undefined) {
951
- continue; // Skip transforms with missing angle
952
- }
953
- if (
954
- t.params.cx !== undefined &&
955
- t.params.cx !== null &&
956
- t.params.cy !== undefined &&
957
- t.params.cy !== null
958
- ) {
959
- m = rotationMatrixAroundPoint(
960
- t.params.angle,
961
- t.params.cx,
962
- t.params.cy,
963
- );
964
- } else {
965
- m = rotationMatrix(t.params.angle);
966
- }
967
- break;
968
- case "scale":
969
- if (
970
- t.params.sx === null ||
971
- t.params.sx === undefined ||
972
- t.params.sy === null ||
973
- t.params.sy === undefined
974
- ) {
975
- continue; // Skip transforms with missing params
976
- }
977
- m = scaleMatrix(t.params.sx, t.params.sy);
978
- break;
979
- case "matrix":
980
- if (!t.params.matrix) {
981
- continue; // Skip transforms with missing matrix
982
- }
983
- m = t.params.matrix;
984
- break;
985
- default:
986
- // Skip unknown transform types, but don't try to multiply null matrix
987
- continue;
988
- }
989
- // Only multiply if m was successfully assigned (prevents undefined matrix multiplication)
990
- if (m !== null) {
991
- originalMatrix = originalMatrix.mul(m);
992
- }
993
- }
1014
+ const originalMatrix = computeCombinedMatrix(transforms);
994
1015
 
995
1016
  // Step 1: Remove identity transforms
996
1017
  const { transforms: step1, removedCount: _removedCount } =
@@ -1210,77 +1231,7 @@ export function optimizeTransformList(transforms) {
1210
1231
  const { transforms: final } = removeIdentityTransforms(optimized);
1211
1232
 
1212
1233
  // Calculate optimized combined matrix for verification
1213
- let optimizedMatrix = identityMatrix();
1214
- for (const t of final) {
1215
- // Validate transform object structure
1216
- if (
1217
- !t ||
1218
- typeof t !== "object" ||
1219
- !t.type ||
1220
- !t.params ||
1221
- typeof t.params !== "object"
1222
- ) {
1223
- continue; // Skip malformed transforms
1224
- }
1225
-
1226
- let m = null; // Initialize m to null to catch missing assignments
1227
- switch (t.type) {
1228
- case "translate":
1229
- if (
1230
- t.params.tx === null ||
1231
- t.params.tx === undefined ||
1232
- t.params.ty === null ||
1233
- t.params.ty === undefined
1234
- ) {
1235
- continue; // Skip transforms with missing params
1236
- }
1237
- m = translationMatrix(t.params.tx, t.params.ty);
1238
- break;
1239
- case "rotate":
1240
- if (t.params.angle === null || t.params.angle === undefined) {
1241
- continue; // Skip transforms with missing angle
1242
- }
1243
- if (
1244
- t.params.cx !== undefined &&
1245
- t.params.cx !== null &&
1246
- t.params.cy !== undefined &&
1247
- t.params.cy !== null
1248
- ) {
1249
- m = rotationMatrixAroundPoint(
1250
- t.params.angle,
1251
- t.params.cx,
1252
- t.params.cy,
1253
- );
1254
- } else {
1255
- m = rotationMatrix(t.params.angle);
1256
- }
1257
- break;
1258
- case "scale":
1259
- if (
1260
- t.params.sx === null ||
1261
- t.params.sx === undefined ||
1262
- t.params.sy === null ||
1263
- t.params.sy === undefined
1264
- ) {
1265
- continue; // Skip transforms with missing params
1266
- }
1267
- m = scaleMatrix(t.params.sx, t.params.sy);
1268
- break;
1269
- case "matrix":
1270
- if (!t.params.matrix) {
1271
- continue; // Skip transforms with missing matrix
1272
- }
1273
- m = t.params.matrix;
1274
- break;
1275
- default:
1276
- // Skip unknown transform types, but don't try to multiply null matrix
1277
- continue;
1278
- }
1279
- // Only multiply if m was successfully assigned (prevents undefined matrix multiplication)
1280
- if (m !== null) {
1281
- optimizedMatrix = optimizedMatrix.mul(m);
1282
- }
1283
- }
1234
+ const optimizedMatrix = computeCombinedMatrix(final);
1284
1235
 
1285
1236
  // VERIFICATION: Combined matrices must be equal
1286
1237
  const maxError = matrixMaxDifference(originalMatrix, optimizedMatrix);
@@ -15,8 +15,6 @@
15
15
  */
16
16
 
17
17
  import Decimal from "decimal.js";
18
- import { Matrix as _Matrix } from "./matrix.js";
19
- import { Vector as _Vector } from "./vector.js";
20
18
  import * as Transforms2D from "./transforms2d.js";
21
19
 
22
20
  // Use high precision for verifications
@@ -0,0 +1,53 @@
1
+ # SVG Font Replacement Map
2
+ # ========================
3
+ # This file defines font replacements for SVG processing.
4
+ #
5
+ # Format:
6
+ # original_font: replacement_font
7
+ #
8
+ # Examples:
9
+ # "Arial": "Inter" # Replace Arial with Inter
10
+ # "Times New Roman": "Noto Serif"
11
+ #
12
+ # Font sources (in priority order):
13
+ # 1. Local system fonts
14
+ # 2. Google Fonts (default, free)
15
+ # 3. FontGet (npm: fontget)
16
+ # 4. fnt (brew: alexmyczko/fnt/fnt)
17
+ #
18
+ # Options per font:
19
+ # embed: true # Embed as base64 (default: true)
20
+ # subset: true # Only include used glyphs (default: true)
21
+ # source: "google" # Force specific source
22
+ # weight: "400,700" # Specific weights to include
23
+ # style: "normal,italic" # Specific styles
24
+ #
25
+ # Advanced format:
26
+ # "Arial":
27
+ # replacement: "Inter"
28
+ # embed: true
29
+ # subset: true
30
+ # source: "google"
31
+ # weights: ["400", "500", "700"]
32
+
33
+ replacements:
34
+ # Common font replacements
35
+ # "Arial": "Inter"
36
+ # "Helvetica": "Inter"
37
+ # "Times New Roman": "Noto Serif"
38
+ # "Times": "Noto Serif"
39
+ # "Courier New": "Fira Code"
40
+ # "Courier": "Fira Code"
41
+ # "Georgia": "Merriweather"
42
+ # "Verdana": "Open Sans"
43
+ # "Comic Sans MS": "Comic Neue"
44
+
45
+ # Icon fonts
46
+ # "Font Awesome": "Font Awesome 6 Free"
47
+ # "Material Icons": "Material Symbols Outlined"
48
+
49
+ options:
50
+ default_embed: true
51
+ default_subset: true
52
+ fallback_source: "google"
53
+ auto_download: true