@grida/svg-editor 1.0.0-alpha.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.
@@ -0,0 +1,509 @@
1
+ require("./dom-CfP_ZURh.js");
2
+ let svg_pathdata = require("svg-pathdata");
3
+ //#region src/core/intents.ts
4
+ function num(doc, id, name, fallback = 0) {
5
+ const v = doc.get_attr(id, name);
6
+ if (v === null || v === "") return fallback;
7
+ const n = parseFloat(v);
8
+ return Number.isFinite(n) ? n : fallback;
9
+ }
10
+ function capture_translate_baseline(doc, id) {
11
+ const tag = doc.tag_of(id);
12
+ const own_transform = doc.get_attr(id, "transform");
13
+ if (own_transform !== null || tag === "g") return {
14
+ type: "viaTransform",
15
+ transform: own_transform
16
+ };
17
+ switch (tag) {
18
+ case "rect": return {
19
+ type: "rect",
20
+ x: num(doc, id, "x"),
21
+ y: num(doc, id, "y")
22
+ };
23
+ case "circle": return {
24
+ type: "circle",
25
+ cx: num(doc, id, "cx"),
26
+ cy: num(doc, id, "cy")
27
+ };
28
+ case "ellipse": return {
29
+ type: "ellipse",
30
+ cx: num(doc, id, "cx"),
31
+ cy: num(doc, id, "cy")
32
+ };
33
+ case "line": return {
34
+ type: "line",
35
+ x1: num(doc, id, "x1"),
36
+ y1: num(doc, id, "y1"),
37
+ x2: num(doc, id, "x2"),
38
+ y2: num(doc, id, "y2")
39
+ };
40
+ case "polyline": return {
41
+ type: "polyline",
42
+ points: doc.get_attr(id, "points") ?? ""
43
+ };
44
+ case "polygon": return {
45
+ type: "polygon",
46
+ points: doc.get_attr(id, "points") ?? ""
47
+ };
48
+ case "path": return {
49
+ type: "path",
50
+ d: doc.get_attr(id, "d") ?? ""
51
+ };
52
+ case "text": return {
53
+ type: "text",
54
+ x: num(doc, id, "x"),
55
+ y: num(doc, id, "y")
56
+ };
57
+ case "tspan": return {
58
+ type: "tspan",
59
+ x: num(doc, id, "x"),
60
+ y: num(doc, id, "y")
61
+ };
62
+ case "image": return {
63
+ type: "image",
64
+ x: num(doc, id, "x"),
65
+ y: num(doc, id, "y")
66
+ };
67
+ case "use": return {
68
+ type: "use",
69
+ x: num(doc, id, "x"),
70
+ y: num(doc, id, "y")
71
+ };
72
+ default: return { type: "unsupported" };
73
+ }
74
+ }
75
+ function shift_points_string(points, dx, dy) {
76
+ const nums = points.split(/[\s,]+/).filter(Boolean).map(Number);
77
+ const out = [];
78
+ for (let i = 0; i + 1 < nums.length; i += 2) out.push(`${nums[i] + dx},${nums[i + 1] + dy}`);
79
+ return out.join(" ");
80
+ }
81
+ function compose_leading_translate(existing, dx, dy) {
82
+ if (dx === 0 && dy === 0) return existing ? existing : null;
83
+ if (!existing) return `translate(${dx} ${dy})`;
84
+ const N = "[+-]?(?:\\d+\\.?\\d*|\\.\\d+)(?:[eE][+-]?\\d+)?";
85
+ const re = new RegExp(`^\\s*translate\\(\\s*(${N})(?:\\s*,\\s*|\\s+)(${N})\\s*\\)\\s*(.*)$`);
86
+ const m = existing.match(re);
87
+ if (m) {
88
+ const tx = parseFloat(m[1]) + dx;
89
+ const ty = parseFloat(m[2]) + dy;
90
+ const rest = m[3].trim();
91
+ return rest ? `translate(${tx} ${ty}) ${rest}` : `translate(${tx} ${ty})`;
92
+ }
93
+ return `translate(${dx} ${dy}) ${existing}`;
94
+ }
95
+ function shift_path_d(d, dx, dy) {
96
+ if (dx === 0 && dy === 0) return d;
97
+ try {
98
+ return new svg_pathdata.SVGPathData(d).transform(svg_pathdata.SVGPathDataTransformer.TRANSLATE(dx, dy)).encode();
99
+ } catch {
100
+ return d;
101
+ }
102
+ }
103
+ function apply_translate(doc, id, baseline, dx, dy) {
104
+ switch (baseline.type) {
105
+ case "viaTransform":
106
+ doc.set_attr(id, "transform", compose_leading_translate(baseline.transform ?? "", dx, dy));
107
+ return;
108
+ case "rect":
109
+ case "image":
110
+ case "use":
111
+ case "text":
112
+ case "tspan":
113
+ doc.set_attr(id, "x", String(baseline.x + dx));
114
+ doc.set_attr(id, "y", String(baseline.y + dy));
115
+ return;
116
+ case "circle":
117
+ case "ellipse":
118
+ doc.set_attr(id, "cx", String(baseline.cx + dx));
119
+ doc.set_attr(id, "cy", String(baseline.cy + dy));
120
+ return;
121
+ case "line":
122
+ doc.set_attr(id, "x1", String(baseline.x1 + dx));
123
+ doc.set_attr(id, "y1", String(baseline.y1 + dy));
124
+ doc.set_attr(id, "x2", String(baseline.x2 + dx));
125
+ doc.set_attr(id, "y2", String(baseline.y2 + dy));
126
+ return;
127
+ case "polyline":
128
+ case "polygon":
129
+ doc.set_attr(id, "points", shift_points_string(baseline.points, dx, dy));
130
+ return;
131
+ case "path":
132
+ doc.set_attr(id, "d", shift_path_d(baseline.d, dx, dy));
133
+ return;
134
+ case "unsupported": return;
135
+ }
136
+ }
137
+ function is_resizable(tag) {
138
+ switch (tag) {
139
+ case "rect":
140
+ case "image":
141
+ case "use":
142
+ case "circle":
143
+ case "ellipse":
144
+ case "line":
145
+ case "polyline":
146
+ case "polygon":
147
+ case "path":
148
+ case "text": return true;
149
+ default: return false;
150
+ }
151
+ }
152
+ function capture_resize_baseline(doc, id, bbox) {
153
+ const tag = doc.tag_of(id);
154
+ let attrs;
155
+ switch (tag) {
156
+ case "rect":
157
+ attrs = {
158
+ kind: "rect",
159
+ x: num(doc, id, "x"),
160
+ y: num(doc, id, "y"),
161
+ w: num(doc, id, "width", bbox.width),
162
+ h: num(doc, id, "height", bbox.height)
163
+ };
164
+ break;
165
+ case "image":
166
+ attrs = {
167
+ kind: "image",
168
+ x: num(doc, id, "x"),
169
+ y: num(doc, id, "y"),
170
+ w: num(doc, id, "width", bbox.width),
171
+ h: num(doc, id, "height", bbox.height)
172
+ };
173
+ break;
174
+ case "use":
175
+ attrs = {
176
+ kind: "use",
177
+ x: num(doc, id, "x"),
178
+ y: num(doc, id, "y"),
179
+ w: num(doc, id, "width", bbox.width),
180
+ h: num(doc, id, "height", bbox.height)
181
+ };
182
+ break;
183
+ case "circle":
184
+ attrs = {
185
+ kind: "circle",
186
+ cx: num(doc, id, "cx"),
187
+ cy: num(doc, id, "cy"),
188
+ r: num(doc, id, "r")
189
+ };
190
+ break;
191
+ case "ellipse":
192
+ attrs = {
193
+ kind: "ellipse",
194
+ cx: num(doc, id, "cx"),
195
+ cy: num(doc, id, "cy"),
196
+ rx: num(doc, id, "rx"),
197
+ ry: num(doc, id, "ry")
198
+ };
199
+ break;
200
+ case "line":
201
+ attrs = {
202
+ kind: "line",
203
+ x1: num(doc, id, "x1"),
204
+ y1: num(doc, id, "y1"),
205
+ x2: num(doc, id, "x2"),
206
+ y2: num(doc, id, "y2")
207
+ };
208
+ break;
209
+ case "polyline":
210
+ attrs = {
211
+ kind: "polyline",
212
+ points: doc.get_attr(id, "points") ?? ""
213
+ };
214
+ break;
215
+ case "polygon":
216
+ attrs = {
217
+ kind: "polygon",
218
+ points: doc.get_attr(id, "points") ?? ""
219
+ };
220
+ break;
221
+ case "path":
222
+ attrs = {
223
+ kind: "path",
224
+ d: doc.get_attr(id, "d") ?? ""
225
+ };
226
+ break;
227
+ case "text":
228
+ attrs = {
229
+ kind: "text",
230
+ x: num(doc, id, "x"),
231
+ y: num(doc, id, "y"),
232
+ fontSize: parseFloat(doc.get_attr(id, "font-size") ?? "16") || 16
233
+ };
234
+ break;
235
+ default: attrs = { kind: "unsupported" };
236
+ }
237
+ return {
238
+ bbox,
239
+ attrs
240
+ };
241
+ }
242
+ function compute_resize_factors(baseline, dir, dx, dy, shift) {
243
+ const b = baseline.bbox;
244
+ let anchorX = 0;
245
+ let anchorY = 0;
246
+ let baseHX = 0;
247
+ let baseHY = 0;
248
+ let affectsX = true;
249
+ let affectsY = true;
250
+ switch (dir) {
251
+ case "nw":
252
+ anchorX = b.x + b.width;
253
+ anchorY = b.y + b.height;
254
+ baseHX = b.x;
255
+ baseHY = b.y;
256
+ break;
257
+ case "n":
258
+ anchorX = b.x + b.width / 2;
259
+ anchorY = b.y + b.height;
260
+ baseHX = b.x + b.width / 2;
261
+ baseHY = b.y;
262
+ affectsX = false;
263
+ break;
264
+ case "ne":
265
+ anchorX = b.x;
266
+ anchorY = b.y + b.height;
267
+ baseHX = b.x + b.width;
268
+ baseHY = b.y;
269
+ break;
270
+ case "e":
271
+ anchorX = b.x;
272
+ anchorY = b.y + b.height / 2;
273
+ baseHX = b.x + b.width;
274
+ baseHY = b.y + b.height / 2;
275
+ affectsY = false;
276
+ break;
277
+ case "se":
278
+ anchorX = b.x;
279
+ anchorY = b.y;
280
+ baseHX = b.x + b.width;
281
+ baseHY = b.y + b.height;
282
+ break;
283
+ case "s":
284
+ anchorX = b.x + b.width / 2;
285
+ anchorY = b.y;
286
+ baseHX = b.x + b.width / 2;
287
+ baseHY = b.y + b.height;
288
+ affectsX = false;
289
+ break;
290
+ case "sw":
291
+ anchorX = b.x + b.width;
292
+ anchorY = b.y;
293
+ baseHX = b.x;
294
+ baseHY = b.y + b.height;
295
+ break;
296
+ case "w":
297
+ anchorX = b.x + b.width;
298
+ anchorY = b.y + b.height / 2;
299
+ baseHX = b.x;
300
+ baseHY = b.y + b.height / 2;
301
+ affectsY = false;
302
+ break;
303
+ }
304
+ const newHX = baseHX + (affectsX ? dx : 0);
305
+ const newHY = baseHY + (affectsY ? dy : 0);
306
+ const denomX = baseHX - anchorX;
307
+ const denomY = baseHY - anchorY;
308
+ let sx = affectsX && denomX !== 0 ? (newHX - anchorX) / denomX : 1;
309
+ let sy = affectsY && denomY !== 0 ? (newHY - anchorY) / denomY : 1;
310
+ if (shift && affectsX && affectsY) {
311
+ const mag = Math.max(Math.abs(sx), Math.abs(sy));
312
+ sx = sx >= 0 ? mag : -mag;
313
+ sy = sy >= 0 ? mag : -mag;
314
+ }
315
+ sx = Math.max(.001, sx);
316
+ sy = Math.max(.001, sy);
317
+ return {
318
+ sx,
319
+ sy,
320
+ origin: {
321
+ x: anchorX,
322
+ y: anchorY
323
+ }
324
+ };
325
+ }
326
+ function scale_points_string(points, origin, sx, sy) {
327
+ const nums = points.split(/[\s,]+/).filter(Boolean).map(Number);
328
+ const out = [];
329
+ for (let i = 0; i + 1 < nums.length; i += 2) {
330
+ const x = origin.x + (nums[i] - origin.x) * sx;
331
+ const y = origin.y + (nums[i + 1] - origin.y) * sy;
332
+ out.push(`${x},${y}`);
333
+ }
334
+ return out.join(" ");
335
+ }
336
+ function scale_path_d(d, origin, sx, sy) {
337
+ try {
338
+ const e = origin.x * (1 - sx);
339
+ const f = origin.y * (1 - sy);
340
+ return new svg_pathdata.SVGPathData(d).transform(svg_pathdata.SVGPathDataTransformer.MATRIX(sx, 0, 0, sy, e, f)).encode();
341
+ } catch {
342
+ return d;
343
+ }
344
+ }
345
+ function apply_resize(doc, id, baseline, sx, sy, origin) {
346
+ const a = baseline.attrs;
347
+ switch (a.kind) {
348
+ case "rect":
349
+ case "image":
350
+ case "use":
351
+ doc.set_attr(id, "x", String(origin.x + (a.x - origin.x) * sx));
352
+ doc.set_attr(id, "y", String(origin.y + (a.y - origin.y) * sy));
353
+ doc.set_attr(id, "width", String(Math.max(.001, a.w * sx)));
354
+ doc.set_attr(id, "height", String(Math.max(.001, a.h * sy)));
355
+ return;
356
+ case "circle": {
357
+ const s = Math.min(sx, sy);
358
+ doc.set_attr(id, "cx", String(origin.x + (a.cx - origin.x) * s));
359
+ doc.set_attr(id, "cy", String(origin.y + (a.cy - origin.y) * s));
360
+ doc.set_attr(id, "r", String(Math.max(.001, a.r * s)));
361
+ return;
362
+ }
363
+ case "ellipse":
364
+ doc.set_attr(id, "cx", String(origin.x + (a.cx - origin.x) * sx));
365
+ doc.set_attr(id, "cy", String(origin.y + (a.cy - origin.y) * sy));
366
+ doc.set_attr(id, "rx", String(Math.max(.001, a.rx * sx)));
367
+ doc.set_attr(id, "ry", String(Math.max(.001, a.ry * sy)));
368
+ return;
369
+ case "line":
370
+ doc.set_attr(id, "x1", String(origin.x + (a.x1 - origin.x) * sx));
371
+ doc.set_attr(id, "y1", String(origin.y + (a.y1 - origin.y) * sy));
372
+ doc.set_attr(id, "x2", String(origin.x + (a.x2 - origin.x) * sx));
373
+ doc.set_attr(id, "y2", String(origin.y + (a.y2 - origin.y) * sy));
374
+ return;
375
+ case "polyline":
376
+ case "polygon":
377
+ doc.set_attr(id, "points", scale_points_string(a.points, origin, sx, sy));
378
+ return;
379
+ case "path":
380
+ doc.set_attr(id, "d", scale_path_d(a.d, origin, sx, sy));
381
+ return;
382
+ case "text": {
383
+ if (!(sx !== 1 && sy !== 1)) return;
384
+ const s = Math.min(sx, sy);
385
+ doc.set_attr(id, "x", String(origin.x + (a.x - origin.x) * s));
386
+ doc.set_attr(id, "y", String(origin.y + (a.y - origin.y) * s));
387
+ doc.set_attr(id, "font-size", String(Math.max(1, a.fontSize * s)));
388
+ return;
389
+ }
390
+ case "unsupported": return;
391
+ }
392
+ }
393
+ //#endregion
394
+ //#region src/core/paint.ts
395
+ /**
396
+ * Parse a *computed* paint string into the discriminated union. Returns null
397
+ * for `inherit` / `var()` / empty. Returns an invalid-computed-value record
398
+ * for syntactic errors (rare; we're permissive).
399
+ */
400
+ function parse_paint(declared) {
401
+ if (declared === null || declared === "") return null;
402
+ const trimmed = declared.trim();
403
+ if (trimmed === "") return null;
404
+ if (trimmed === "inherit" || trimmed === "initial" || trimmed === "unset" || trimmed === "revert" || trimmed === "revert-layer") return null;
405
+ if (/^var\s*\(/i.test(trimmed)) return {
406
+ error: "invalid_at_computed_value_time",
407
+ reason: "var() substitution requires a cascade engine (not implemented)"
408
+ };
409
+ if (trimmed === "none") return { kind: "none" };
410
+ if (trimmed === "context-fill" || trimmed === "contextFill") return { kind: "context_fill" };
411
+ if (trimmed === "context-stroke" || trimmed === "contextStroke") return { kind: "context_stroke" };
412
+ const url_match = trimmed.match(/^url\(\s*(["']?)#([^)"']+)\1\s*\)\s*(.*)$/i);
413
+ if (url_match) {
414
+ const id = url_match[2];
415
+ const rest = url_match[3].trim();
416
+ let fallback;
417
+ if (rest !== "") {
418
+ const f = parse_paint(rest);
419
+ if (f && f.kind === "none") fallback = { kind: "none" };
420
+ else if (f && f.kind === "color") fallback = {
421
+ kind: "color",
422
+ value: f.value
423
+ };
424
+ }
425
+ return fallback ? {
426
+ kind: "ref",
427
+ id,
428
+ fallback
429
+ } : {
430
+ kind: "ref",
431
+ id
432
+ };
433
+ }
434
+ if (/^currentcolor$/i.test(trimmed)) return {
435
+ kind: "color",
436
+ value: { kind: "current_color" }
437
+ };
438
+ return {
439
+ kind: "color",
440
+ value: {
441
+ kind: "rgb",
442
+ value: trimmed
443
+ }
444
+ };
445
+ }
446
+ /** Serialize a Paint back to an SVG attribute / inline-style value. */
447
+ function serialize_paint(paint) {
448
+ switch (paint.kind) {
449
+ case "none": return "none";
450
+ case "context_fill": return "context-fill";
451
+ case "context_stroke": return "context-stroke";
452
+ case "color": return paint.value.kind === "current_color" ? "currentColor" : paint.value.value;
453
+ case "ref":
454
+ if (paint.fallback) {
455
+ const f = paint.fallback.kind === "none" ? "none" : paint.fallback.value.kind === "current_color" ? "currentColor" : paint.fallback.value.value;
456
+ return `url(#${paint.id}) ${f}`;
457
+ }
458
+ return `url(#${paint.id})`;
459
+ }
460
+ }
461
+ //#endregion
462
+ Object.defineProperty(exports, "apply_resize", {
463
+ enumerable: true,
464
+ get: function() {
465
+ return apply_resize;
466
+ }
467
+ });
468
+ Object.defineProperty(exports, "apply_translate", {
469
+ enumerable: true,
470
+ get: function() {
471
+ return apply_translate;
472
+ }
473
+ });
474
+ Object.defineProperty(exports, "capture_resize_baseline", {
475
+ enumerable: true,
476
+ get: function() {
477
+ return capture_resize_baseline;
478
+ }
479
+ });
480
+ Object.defineProperty(exports, "capture_translate_baseline", {
481
+ enumerable: true,
482
+ get: function() {
483
+ return capture_translate_baseline;
484
+ }
485
+ });
486
+ Object.defineProperty(exports, "compute_resize_factors", {
487
+ enumerable: true,
488
+ get: function() {
489
+ return compute_resize_factors;
490
+ }
491
+ });
492
+ Object.defineProperty(exports, "is_resizable", {
493
+ enumerable: true,
494
+ get: function() {
495
+ return is_resizable;
496
+ }
497
+ });
498
+ Object.defineProperty(exports, "parse_paint", {
499
+ enumerable: true,
500
+ get: function() {
501
+ return parse_paint;
502
+ }
503
+ });
504
+ Object.defineProperty(exports, "serialize_paint", {
505
+ enumerable: true,
506
+ get: function() {
507
+ return serialize_paint;
508
+ }
509
+ });