svg-vectordrawable 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,1129 @@
1
+ // src/svgo.ts
2
+ import { optimize, _collections } from "svgo";
3
+
4
+ // src/color.ts
5
+ var NAMED = {
6
+ ..._collections.colorsNames ?? {},
7
+ transparent: "rgba(0,0,0,0)"
8
+ };
9
+ var clamp = (n2, min, max) => Math.min(Math.max(n2, min), max);
10
+ var hex2 = (n2) => clamp(Math.round(n2), 0, 255).toString(16).padStart(2, "0");
11
+ function hslToRgb(h, s, l) {
12
+ h = (h % 360 + 360) % 360;
13
+ s = clamp(s, 0, 1);
14
+ l = clamp(l, 0, 1);
15
+ const c = (1 - Math.abs(2 * l - 1)) * s;
16
+ const x = c * (1 - Math.abs(h / 60 % 2 - 1));
17
+ const m = l - c / 2;
18
+ let r = 0;
19
+ let g = 0;
20
+ let b = 0;
21
+ if (h < 60) [r, g, b] = [c, x, 0];
22
+ else if (h < 120) [r, g, b] = [x, c, 0];
23
+ else if (h < 180) [r, g, b] = [0, c, x];
24
+ else if (h < 240) [r, g, b] = [0, x, c];
25
+ else if (h < 300) [r, g, b] = [x, 0, c];
26
+ else [r, g, b] = [c, 0, x];
27
+ return { r: (r + m) * 255, g: (g + m) * 255, b: (b + m) * 255, a: 1 };
28
+ }
29
+ function parseRgba(raw, currentColor, seen = 0) {
30
+ if (seen > 4) return null;
31
+ const value = raw.trim().toLowerCase();
32
+ if (value === "none") return null;
33
+ if (value === "currentcolor") return parseRgba(currentColor, currentColor, seen + 1);
34
+ if (value.startsWith("#")) {
35
+ const h = value.slice(1);
36
+ const expand = (s) => parseInt(s.length === 1 ? s + s : s, 16);
37
+ if (h.length === 3 || h.length === 4) {
38
+ return {
39
+ r: expand(h[0]),
40
+ g: expand(h[1]),
41
+ b: expand(h[2]),
42
+ a: h.length === 4 ? expand(h[3]) / 255 : 1
43
+ };
44
+ }
45
+ if (h.length === 6 || h.length === 8) {
46
+ return {
47
+ r: parseInt(h.slice(0, 2), 16),
48
+ g: parseInt(h.slice(2, 4), 16),
49
+ b: parseInt(h.slice(4, 6), 16),
50
+ a: h.length === 8 ? parseInt(h.slice(6, 8), 16) / 255 : 1
51
+ };
52
+ }
53
+ return null;
54
+ }
55
+ const fn = value.match(/^(rgba?|hsla?)\(([^)]+)\)$/);
56
+ if (fn) {
57
+ const kind = fn[1];
58
+ const parts = fn[2].split(/[\s,/]+/).filter(Boolean);
59
+ const num2 = (s) => parseFloat(s ?? "0");
60
+ const alpha = parts[3] !== void 0 ? parts[3].endsWith("%") ? num2(parts[3]) / 100 : num2(parts[3]) : 1;
61
+ if (kind.startsWith("rgb")) {
62
+ const ch = (s) => s?.endsWith("%") ? num2(s) / 100 * 255 : num2(s);
63
+ return { r: ch(parts[0]), g: ch(parts[1]), b: ch(parts[2]), a: alpha };
64
+ }
65
+ const rgb = hslToRgb(num2(parts[0]), num2(parts[1]) / 100, num2(parts[2]) / 100);
66
+ return { ...rgb, a: alpha };
67
+ }
68
+ if (value in NAMED) return parseRgba(NAMED[value], currentColor, seen + 1);
69
+ return null;
70
+ }
71
+ function toAndroidColor(value, opacity, currentColor) {
72
+ if (value === void 0) return null;
73
+ const rgba = parseRgba(value, currentColor);
74
+ if (!rgba) return null;
75
+ const a = clamp(rgba.a * (opacity ?? 1), 0, 1);
76
+ return `#${hex2(a * 255)}${hex2(rgba.r)}${hex2(rgba.g)}${hex2(rgba.b)}`.toUpperCase();
77
+ }
78
+
79
+ // src/transform.ts
80
+ var IDENTITY = { a: 1, b: 0, c: 0, d: 1, e: 0, f: 0 };
81
+ function multiply(m1, m2) {
82
+ return {
83
+ a: m1.a * m2.a + m1.c * m2.b,
84
+ b: m1.b * m2.a + m1.d * m2.b,
85
+ c: m1.a * m2.c + m1.c * m2.d,
86
+ d: m1.b * m2.c + m1.d * m2.d,
87
+ e: m1.a * m2.e + m1.c * m2.f + m1.e,
88
+ f: m1.b * m2.e + m1.d * m2.f + m1.f
89
+ };
90
+ }
91
+ function applyPoint(m, x, y) {
92
+ return [m.a * x + m.c * y + m.e, m.b * x + m.d * y + m.f];
93
+ }
94
+ function scaleFactor(m) {
95
+ return Math.sqrt(Math.abs(m.a * m.d - m.b * m.c));
96
+ }
97
+ function parseTransform(transform) {
98
+ if (!transform) return null;
99
+ let m = IDENTITY;
100
+ let matched = false;
101
+ const re = /(matrix|translate|scale|rotate|skewX|skewY)\s*\(([^)]*)\)/gi;
102
+ let fn;
103
+ while ((fn = re.exec(transform)) !== null) {
104
+ const name = fn[1].toLowerCase();
105
+ const args = fn[2].split(/[\s,]+/).map(Number).filter((n2) => !Number.isNaN(n2));
106
+ let next = null;
107
+ switch (name) {
108
+ case "matrix":
109
+ if (args.length >= 6)
110
+ next = { a: args[0], b: args[1], c: args[2], d: args[3], e: args[4], f: args[5] };
111
+ break;
112
+ case "translate":
113
+ next = { ...IDENTITY, e: args[0] ?? 0, f: args[1] ?? 0 };
114
+ break;
115
+ case "scale":
116
+ next = { ...IDENTITY, a: args[0] ?? 1, d: args[1] ?? args[0] ?? 1 };
117
+ break;
118
+ case "rotate": {
119
+ const r = (args[0] ?? 0) * Math.PI / 180;
120
+ const cos = Math.cos(r);
121
+ const sin = Math.sin(r);
122
+ const rot = { a: cos, b: sin, c: -sin, d: cos, e: 0, f: 0 };
123
+ if (args.length >= 3) {
124
+ const cx = args[1];
125
+ const cy = args[2];
126
+ next = multiply({ ...IDENTITY, e: cx, f: cy }, multiply(rot, { ...IDENTITY, e: -cx, f: -cy }));
127
+ } else next = rot;
128
+ break;
129
+ }
130
+ case "skewx":
131
+ next = { ...IDENTITY, c: Math.tan((args[0] ?? 0) * Math.PI / 180) };
132
+ break;
133
+ case "skewy":
134
+ next = { ...IDENTITY, b: Math.tan((args[0] ?? 0) * Math.PI / 180) };
135
+ break;
136
+ }
137
+ if (next) {
138
+ m = multiply(m, next);
139
+ matched = true;
140
+ }
141
+ }
142
+ return matched ? m : null;
143
+ }
144
+ function decompose(m) {
145
+ const { a, b, c, d, e, f } = m;
146
+ const scaleX = Math.hypot(a, b);
147
+ const rotation = Math.atan2(b, a);
148
+ const det = a * d - b * c;
149
+ const scaleY = scaleX === 0 ? 0 : det / scaleX;
150
+ const shear = scaleX === 0 ? 0 : (a * c + b * d) / (scaleX * scaleX);
151
+ return {
152
+ translateX: e,
153
+ translateY: f,
154
+ rotation: rotation * 180 / Math.PI,
155
+ scaleX,
156
+ scaleY,
157
+ hasSkew: Math.abs(shear) > 1e-4
158
+ };
159
+ }
160
+
161
+ // src/gradient.ts
162
+ var SPREAD = { pad: "clamp", repeat: "repeated", reflect: "mirror" };
163
+ var round = (v, p) => {
164
+ const f = 10 ** p;
165
+ return Math.round(v * f) / f;
166
+ };
167
+ function coord(value, fallback, ref) {
168
+ if (value === void 0) return fallback;
169
+ const v = value.trim();
170
+ if (v.endsWith("%")) return parseFloat(v) / 100 * ref;
171
+ const n2 = parseFloat(v);
172
+ return Number.isNaN(n2) ? fallback : n2;
173
+ }
174
+ function collectGradients(gradientEls, currentColor, _warn) {
175
+ const byId = /* @__PURE__ */ new Map();
176
+ for (const el of gradientEls) {
177
+ const id = el.attributes?.id;
178
+ if (id) byId.set(id, el);
179
+ }
180
+ const attrsOf = (el, seen = 0) => {
181
+ const href = el.attributes?.href ?? el.attributes?.["xlink:href"];
182
+ const parent = seen < 10 && href?.startsWith("#") ? byId.get(href.slice(1)) : void 0;
183
+ return { ...parent ? attrsOf(parent, seen + 1) : {}, ...el.attributes };
184
+ };
185
+ const stopsOf = (el, seen = 0) => {
186
+ const own = (el.children ?? []).filter((c) => c.name === "stop");
187
+ if (own.length) return own;
188
+ const href = el.attributes?.href ?? el.attributes?.["xlink:href"];
189
+ const parent = seen < 10 && href?.startsWith("#") ? byId.get(href.slice(1)) : void 0;
190
+ return parent ? stopsOf(parent, seen + 1) : [];
191
+ };
192
+ const out = /* @__PURE__ */ new Map();
193
+ for (const el of gradientEls) {
194
+ const id = el.attributes?.id;
195
+ if (!id) continue;
196
+ const a = attrsOf(el);
197
+ const stops = stopsOf(el).map((s) => {
198
+ const off = s.attributes?.offset ?? "0";
199
+ const offset = off.endsWith("%") ? parseFloat(off) / 100 : parseFloat(off);
200
+ const color = toAndroidColor(
201
+ s.attributes?.["stop-color"] ?? "#000",
202
+ s.attributes?.["stop-opacity"] !== void 0 ? parseFloat(s.attributes["stop-opacity"]) : void 0,
203
+ currentColor
204
+ );
205
+ return color ? { offset: Number.isNaN(offset) ? 0 : offset, color } : null;
206
+ }).filter((s) => s !== null);
207
+ if (stops.length === 0) continue;
208
+ const tileMode = a.spreadMethod ? SPREAD[a.spreadMethod] : void 0;
209
+ out.set(id, {
210
+ type: el.name === "radialGradient" ? "radial" : "linear",
211
+ objectBox: (a.gradientUnits ?? "objectBoundingBox") !== "userSpaceOnUse",
212
+ attrs: a,
213
+ matrix: parseTransform(a.gradientTransform),
214
+ stops,
215
+ ...tileMode ? { tileMode } : {}
216
+ });
217
+ }
218
+ return out;
219
+ }
220
+ function resolveGradient(g, bbox, viewport, precision, warn) {
221
+ let base;
222
+ let refW;
223
+ let refH;
224
+ let refD;
225
+ if (g.objectBox) {
226
+ const bb = bbox ?? { x: 0, y: 0, width: viewport.width, height: viewport.height };
227
+ if (!bbox)
228
+ warn({
229
+ code: "gradient-bbox-unavailable",
230
+ message: "objectBoundingBox gradient could not use the path bounding box; approximated to the viewport."
231
+ });
232
+ base = multiply({ a: bb.width, b: 0, c: 0, d: bb.height, e: bb.x, f: bb.y }, g.matrix ?? IDENTITY);
233
+ refW = 1;
234
+ refH = 1;
235
+ refD = Math.SQRT2 / 2;
236
+ } else {
237
+ base = g.matrix ?? IDENTITY;
238
+ refW = viewport.width;
239
+ refH = viewport.height;
240
+ refD = Math.hypot(refW, refH) / Math.SQRT2;
241
+ }
242
+ const a = g.attrs;
243
+ const scale = scaleFactor(base);
244
+ if (g.type === "radial") {
245
+ const cx = coord(a.cx, 0.5 * refW, refW);
246
+ const cy = coord(a.cy, 0.5 * refH, refH);
247
+ const r = coord(a.r, 0.5 * refD, refD);
248
+ const [centerX, centerY] = applyPoint(base, cx, cy);
249
+ return {
250
+ type: "radial",
251
+ coords: {
252
+ centerX: round(centerX, precision),
253
+ centerY: round(centerY, precision),
254
+ gradientRadius: round(r * scale, precision)
255
+ },
256
+ stops: g.stops,
257
+ ...g.tileMode ? { tileMode: g.tileMode } : {}
258
+ };
259
+ }
260
+ const x1 = coord(a.x1, 0, refW);
261
+ const y1 = coord(a.y1, 0, refH);
262
+ const x2 = coord(a.x2, refW, refW);
263
+ const y2 = coord(a.y2, 0, refH);
264
+ const [sx, sy] = applyPoint(base, x1, y1);
265
+ const [ex, ey] = applyPoint(base, x2, y2);
266
+ return {
267
+ type: "linear",
268
+ coords: {
269
+ startX: round(sx, precision),
270
+ startY: round(sy, precision),
271
+ endX: round(ex, precision),
272
+ endY: round(ey, precision)
273
+ },
274
+ stops: g.stops,
275
+ ...g.tileMode ? { tileMode: g.tileMode } : {}
276
+ };
277
+ }
278
+ function renderGradient(g, pad, step) {
279
+ const inner = pad + step + step;
280
+ const coordLines = Object.entries(g.coords).map(([k, v]) => `${inner + step}android:${k}="${v}"`).join("\n");
281
+ const tile = g.tileMode ? `
282
+ ${inner + step}android:tileMode="${g.tileMode}"` : "";
283
+ const items = g.stops.map((s) => `${inner + step}<item android:offset="${s.offset}" android:color="${s.color}" />`).join("\n");
284
+ return `${pad + step}<aapt:attr name="android:fillColor">
285
+ ${inner}<gradient
286
+ ${coordLines}
287
+ ${inner + step}android:type="${g.type}"${tile}>
288
+ ${items}
289
+ ${inner}</gradient>
290
+ ${pad + step}</aapt:attr>`;
291
+ }
292
+
293
+ // src/normalize.ts
294
+ var DEFAULT_CONFIG = {
295
+ plugins: [
296
+ {
297
+ name: "preset-default",
298
+ params: {
299
+ overrides: {
300
+ inlineStyles: { onlyMatchedOnce: false },
301
+ convertShapeToPath: { convertArcs: true },
302
+ // Keep ids: gradients / clip-paths are referenced by url(#id).
303
+ cleanupIds: { remove: false }
304
+ }
305
+ }
306
+ }
307
+ ]
308
+ };
309
+ function parseSvg(svg, optimizeFlag, svgoConfig) {
310
+ let captured = null;
311
+ const capture = {
312
+ name: "svgvd-capture",
313
+ fn: (root) => {
314
+ captured = root;
315
+ return {};
316
+ }
317
+ };
318
+ const base = optimizeFlag ? svgoConfig ?? DEFAULT_CONFIG : { plugins: [] };
319
+ const plugins = [...base.plugins ?? [], capture];
320
+ try {
321
+ optimize(svg, { ...base, plugins });
322
+ } catch (err) {
323
+ throw new Error(`Invalid SVG: ${err.message}`);
324
+ }
325
+ if (!captured) throw new Error("Failed to parse SVG: empty document");
326
+ return captured;
327
+ }
328
+
329
+ // src/pathData.ts
330
+ var ARG_COUNT = {
331
+ M: 2,
332
+ L: 2,
333
+ H: 1,
334
+ V: 1,
335
+ C: 6,
336
+ S: 4,
337
+ Q: 4,
338
+ T: 2,
339
+ A: 7,
340
+ Z: 0
341
+ };
342
+ function parsePath(d) {
343
+ const commands = [];
344
+ const tokenRe = /([MmLlHhVvCcSsQqTtAaZz])|(-?(?:\d*\.\d+|\d+\.?)(?:[eE][+-]?\d+)?)/g;
345
+ const tokens = [];
346
+ let match;
347
+ while ((match = tokenRe.exec(d)) !== null) {
348
+ tokens.push(match[1] ?? match[2]);
349
+ }
350
+ let i = 0;
351
+ let prevCmd = "";
352
+ while (i < tokens.length) {
353
+ const tok = tokens[i];
354
+ let cmd;
355
+ if (/[a-zA-Z]/.test(tok)) {
356
+ cmd = tok;
357
+ i += 1;
358
+ } else {
359
+ if (!prevCmd) break;
360
+ cmd = prevCmd === "M" ? "L" : prevCmd === "m" ? "l" : prevCmd;
361
+ }
362
+ const upper = cmd.toUpperCase();
363
+ const count = ARG_COUNT[upper];
364
+ if (count === void 0) break;
365
+ if (count === 0) {
366
+ commands.push({ cmd, args: [] });
367
+ prevCmd = cmd;
368
+ continue;
369
+ }
370
+ const args = [];
371
+ for (let k = 0; k < count; k += 1) {
372
+ const next = tokens[i];
373
+ if (next === void 0 || /[a-zA-Z]/.test(next)) break;
374
+ args.push(Number(next));
375
+ i += 1;
376
+ }
377
+ if (args.length < count) break;
378
+ commands.push({ cmd, args });
379
+ prevCmd = cmd;
380
+ }
381
+ return commands;
382
+ }
383
+ function pathBBox(d) {
384
+ const commands = parsePath(d);
385
+ let minX = Infinity;
386
+ let minY = Infinity;
387
+ let maxX = -Infinity;
388
+ let maxY = -Infinity;
389
+ let seen = false;
390
+ const include = (x, y) => {
391
+ if (x < minX) minX = x;
392
+ if (y < minY) minY = y;
393
+ if (x > maxX) maxX = x;
394
+ if (y > maxY) maxY = y;
395
+ seen = true;
396
+ };
397
+ const pen = { x: 0, y: 0, startX: 0, startY: 0 };
398
+ for (const { cmd, args } of commands) {
399
+ const abs = cmd === cmd.toUpperCase();
400
+ const upper = cmd.toUpperCase();
401
+ const ox = abs ? 0 : pen.x;
402
+ const oy = abs ? 0 : pen.y;
403
+ switch (upper) {
404
+ case "M": {
405
+ pen.x = ox + args[0];
406
+ pen.y = oy + args[1];
407
+ pen.startX = pen.x;
408
+ pen.startY = pen.y;
409
+ include(pen.x, pen.y);
410
+ break;
411
+ }
412
+ case "L": {
413
+ pen.x = ox + args[0];
414
+ pen.y = oy + args[1];
415
+ include(pen.x, pen.y);
416
+ break;
417
+ }
418
+ case "H": {
419
+ pen.x = ox + args[0];
420
+ include(pen.x, pen.y);
421
+ break;
422
+ }
423
+ case "V": {
424
+ pen.y = oy + args[0];
425
+ include(pen.x, pen.y);
426
+ break;
427
+ }
428
+ case "C": {
429
+ include(ox + args[0], oy + args[1]);
430
+ include(ox + args[2], oy + args[3]);
431
+ pen.x = ox + args[4];
432
+ pen.y = oy + args[5];
433
+ include(pen.x, pen.y);
434
+ break;
435
+ }
436
+ case "S": {
437
+ include(ox + args[0], oy + args[1]);
438
+ pen.x = ox + args[2];
439
+ pen.y = oy + args[3];
440
+ include(pen.x, pen.y);
441
+ break;
442
+ }
443
+ case "Q": {
444
+ include(ox + args[0], oy + args[1]);
445
+ pen.x = ox + args[2];
446
+ pen.y = oy + args[3];
447
+ include(pen.x, pen.y);
448
+ break;
449
+ }
450
+ case "T": {
451
+ pen.x = ox + args[0];
452
+ pen.y = oy + args[1];
453
+ include(pen.x, pen.y);
454
+ break;
455
+ }
456
+ case "A": {
457
+ pen.x = ox + args[5];
458
+ pen.y = oy + args[6];
459
+ include(pen.x, pen.y);
460
+ break;
461
+ }
462
+ case "Z": {
463
+ pen.x = pen.startX;
464
+ pen.y = pen.startY;
465
+ break;
466
+ }
467
+ }
468
+ }
469
+ if (!seen) return null;
470
+ return { x: minX, y: minY, width: maxX - minX, height: maxY - minY };
471
+ }
472
+ function fmt(n2) {
473
+ if (!Number.isFinite(n2)) return "0";
474
+ let r = Math.round(n2 * 1e3) / 1e3;
475
+ if (Object.is(r, -0)) r = 0;
476
+ let s = r.toFixed(3).replace(/\.?0+$/, "");
477
+ if (s === "" || s === "-") s = "0";
478
+ return s;
479
+ }
480
+ function reflect(curr, prevControl) {
481
+ return 2 * curr - prevControl;
482
+ }
483
+ function arcToCubics(x1, y1, rxIn, ryIn, phiDeg, largeArc, sweep, x2, y2) {
484
+ if (rxIn === 0 || ryIn === 0 || x1 === x2 && y1 === y2) {
485
+ return [x1, y1, x2, y2, x2, y2];
486
+ }
487
+ let rx = Math.abs(rxIn);
488
+ let ry = Math.abs(ryIn);
489
+ const phi = phiDeg * Math.PI / 180;
490
+ const cosPhi = Math.cos(phi);
491
+ const sinPhi = Math.sin(phi);
492
+ const dx = (x1 - x2) / 2;
493
+ const dy = (y1 - y2) / 2;
494
+ const x1p = cosPhi * dx + sinPhi * dy;
495
+ const y1p = -sinPhi * dx + cosPhi * dy;
496
+ const lambda = x1p * x1p / (rx * rx) + y1p * y1p / (ry * ry);
497
+ if (lambda > 1) {
498
+ const s = Math.sqrt(lambda);
499
+ rx *= s;
500
+ ry *= s;
501
+ }
502
+ const rxSq = rx * rx;
503
+ const rySq = ry * ry;
504
+ const x1pSq = x1p * x1p;
505
+ const y1pSq = y1p * y1p;
506
+ let num2 = rxSq * rySq - rxSq * y1pSq - rySq * x1pSq;
507
+ if (num2 < 0) num2 = 0;
508
+ const denom = rxSq * y1pSq + rySq * x1pSq;
509
+ let coef = denom === 0 ? 0 : Math.sqrt(num2 / denom);
510
+ if (largeArc === sweep) coef = -coef;
511
+ const cxp = coef * (rx * y1p) / ry;
512
+ const cyp = coef * -(ry * x1p) / rx;
513
+ const cx = cosPhi * cxp - sinPhi * cyp + (x1 + x2) / 2;
514
+ const cy = sinPhi * cxp + cosPhi * cyp + (y1 + y2) / 2;
515
+ const angle = (ux, uy, vx, vy) => {
516
+ const dot = ux * vx + uy * vy;
517
+ const len = Math.sqrt((ux * ux + uy * uy) * (vx * vx + vy * vy));
518
+ let a = Math.acos(Math.min(1, Math.max(-1, len === 0 ? 1 : dot / len)));
519
+ if (ux * vy - uy * vx < 0) a = -a;
520
+ return a;
521
+ };
522
+ const theta1 = angle(1, 0, (x1p - cxp) / rx, (y1p - cyp) / ry);
523
+ let deltaTheta = angle((x1p - cxp) / rx, (y1p - cyp) / ry, (-x1p - cxp) / rx, (-y1p - cyp) / ry);
524
+ if (!sweep && deltaTheta > 0) deltaTheta -= 2 * Math.PI;
525
+ else if (sweep && deltaTheta < 0) deltaTheta += 2 * Math.PI;
526
+ const segCount = Math.max(1, Math.ceil(Math.abs(deltaTheta) / (Math.PI / 2)));
527
+ const delta = deltaTheta / segCount;
528
+ const t = 4 / 3 * Math.tan(delta / 4);
529
+ const out = [];
530
+ let theta = theta1;
531
+ const pointAt = (a) => {
532
+ const cosA = Math.cos(a);
533
+ const sinA = Math.sin(a);
534
+ const ex = cosPhi * rx * cosA - sinPhi * ry * sinA + cx;
535
+ const ey = sinPhi * rx * cosA + cosPhi * ry * sinA + cy;
536
+ const dxA = -cosPhi * rx * sinA - sinPhi * ry * cosA;
537
+ const dyA = -sinPhi * rx * sinA + cosPhi * ry * cosA;
538
+ return { x: ex, y: ey, dxA, dyA };
539
+ };
540
+ for (let s = 0; s < segCount; s += 1) {
541
+ const p1 = pointAt(theta);
542
+ const thetaNext = theta + delta;
543
+ const p2 = pointAt(thetaNext);
544
+ const c1x = p1.x + t * p1.dxA;
545
+ const c1y = p1.y + t * p1.dyA;
546
+ const c2x = p2.x - t * p2.dxA;
547
+ const c2y = p2.y - t * p2.dyA;
548
+ out.push(c1x, c1y, c2x, c2y, p2.x, p2.y);
549
+ theta = thetaNext;
550
+ }
551
+ return out;
552
+ }
553
+ function toAbsolute(commands) {
554
+ const out = [];
555
+ const pen = { x: 0, y: 0, startX: 0, startY: 0 };
556
+ let lastCubicCx = null;
557
+ let lastCubicCy = null;
558
+ let lastQuadCx = null;
559
+ let lastQuadCy = null;
560
+ for (const { cmd, args } of commands) {
561
+ const abs = cmd === cmd.toUpperCase();
562
+ const upper = cmd.toUpperCase();
563
+ const ox = abs ? 0 : pen.x;
564
+ const oy = abs ? 0 : pen.y;
565
+ let producedCubic = false;
566
+ let producedQuad = false;
567
+ switch (upper) {
568
+ case "M": {
569
+ pen.x = ox + args[0];
570
+ pen.y = oy + args[1];
571
+ pen.startX = pen.x;
572
+ pen.startY = pen.y;
573
+ out.push({ cmd: "M", args: [pen.x, pen.y] });
574
+ break;
575
+ }
576
+ case "L": {
577
+ pen.x = ox + args[0];
578
+ pen.y = oy + args[1];
579
+ out.push({ cmd: "L", args: [pen.x, pen.y] });
580
+ break;
581
+ }
582
+ case "H": {
583
+ pen.x = ox + args[0];
584
+ out.push({ cmd: "L", args: [pen.x, pen.y] });
585
+ break;
586
+ }
587
+ case "V": {
588
+ pen.y = oy + args[0];
589
+ out.push({ cmd: "L", args: [pen.x, pen.y] });
590
+ break;
591
+ }
592
+ case "C": {
593
+ const c1x = ox + args[0];
594
+ const c1y = oy + args[1];
595
+ const c2x = ox + args[2];
596
+ const c2y = oy + args[3];
597
+ pen.x = ox + args[4];
598
+ pen.y = oy + args[5];
599
+ out.push({ cmd: "C", args: [c1x, c1y, c2x, c2y, pen.x, pen.y] });
600
+ lastCubicCx = c2x;
601
+ lastCubicCy = c2y;
602
+ producedCubic = true;
603
+ break;
604
+ }
605
+ case "S": {
606
+ const c1x = lastCubicCx !== null ? reflect(pen.x, lastCubicCx) : pen.x;
607
+ const c1y = lastCubicCy !== null ? reflect(pen.y, lastCubicCy) : pen.y;
608
+ const c2x = ox + args[0];
609
+ const c2y = oy + args[1];
610
+ pen.x = ox + args[2];
611
+ pen.y = oy + args[3];
612
+ out.push({ cmd: "C", args: [c1x, c1y, c2x, c2y, pen.x, pen.y] });
613
+ lastCubicCx = c2x;
614
+ lastCubicCy = c2y;
615
+ producedCubic = true;
616
+ break;
617
+ }
618
+ case "Q": {
619
+ const cqx = ox + args[0];
620
+ const cqy = oy + args[1];
621
+ pen.x = ox + args[2];
622
+ pen.y = oy + args[3];
623
+ out.push({ cmd: "Q", args: [cqx, cqy, pen.x, pen.y] });
624
+ lastQuadCx = cqx;
625
+ lastQuadCy = cqy;
626
+ producedQuad = true;
627
+ break;
628
+ }
629
+ case "T": {
630
+ const tcx = lastQuadCx !== null ? reflect(pen.x, lastQuadCx) : pen.x;
631
+ const tcy = lastQuadCy !== null ? reflect(pen.y, lastQuadCy) : pen.y;
632
+ pen.x = ox + args[0];
633
+ pen.y = oy + args[1];
634
+ out.push({ cmd: "Q", args: [tcx, tcy, pen.x, pen.y] });
635
+ lastQuadCx = tcx;
636
+ lastQuadCy = tcy;
637
+ producedQuad = true;
638
+ break;
639
+ }
640
+ case "A": {
641
+ const x2 = ox + args[5];
642
+ const y2 = oy + args[6];
643
+ const cubics = arcToCubics(
644
+ pen.x,
645
+ pen.y,
646
+ args[0],
647
+ args[1],
648
+ args[2],
649
+ args[3] !== 0,
650
+ args[4] !== 0,
651
+ x2,
652
+ y2
653
+ );
654
+ for (let k = 0; k + 5 < cubics.length; k += 6) {
655
+ out.push({
656
+ cmd: "C",
657
+ args: [
658
+ cubics[k],
659
+ cubics[k + 1],
660
+ cubics[k + 2],
661
+ cubics[k + 3],
662
+ cubics[k + 4],
663
+ cubics[k + 5]
664
+ ]
665
+ });
666
+ }
667
+ pen.x = x2;
668
+ pen.y = y2;
669
+ break;
670
+ }
671
+ case "Z": {
672
+ pen.x = pen.startX;
673
+ pen.y = pen.startY;
674
+ out.push({ cmd: "Z", args: [] });
675
+ break;
676
+ }
677
+ }
678
+ if (!producedCubic) {
679
+ lastCubicCx = null;
680
+ lastCubicCy = null;
681
+ }
682
+ if (!producedQuad) {
683
+ lastQuadCx = null;
684
+ lastQuadCy = null;
685
+ }
686
+ }
687
+ return out;
688
+ }
689
+ function transformPathData(d, m) {
690
+ const absCommands = toAbsolute(parsePath(d));
691
+ const parts = [];
692
+ for (const { cmd, args } of absCommands) {
693
+ if (cmd === "Z") {
694
+ parts.push("Z");
695
+ continue;
696
+ }
697
+ const mapped = [];
698
+ for (let k = 0; k + 1 < args.length; k += 2) {
699
+ const [tx, ty] = applyPoint(m, args[k], args[k + 1]);
700
+ mapped.push(fmt(tx), fmt(ty));
701
+ }
702
+ parts.push(cmd + mapped.join(" "));
703
+ }
704
+ return parts.join("");
705
+ }
706
+
707
+ // src/useResolver.ts
708
+ var MAX_DEPTH = 10;
709
+ var USE_GEOMETRY_ATTRS = /* @__PURE__ */ new Set(["href", "xlink:href", "x", "y", "width", "height", "id"]);
710
+ var isElement = (node) => node.type === "element" && node.name !== void 0;
711
+ function targetId(use) {
712
+ const href = use.attributes?.href ?? use.attributes?.["xlink:href"];
713
+ if (href === void 0 || !href.startsWith("#")) return void 0;
714
+ return href.slice(1);
715
+ }
716
+ function indexById(root) {
717
+ const byId = /* @__PURE__ */ new Map();
718
+ const visit = (node) => {
719
+ const id = node.attributes?.id;
720
+ if (id !== void 0 && !byId.has(id)) byId.set(id, node);
721
+ for (const child of node.children ?? []) visit(child);
722
+ };
723
+ visit(root);
724
+ return byId;
725
+ }
726
+ function cloneElement(node) {
727
+ const clone = { type: node.type };
728
+ if (node.name !== void 0) clone.name = node.name;
729
+ if (node.attributes !== void 0) clone.attributes = { ...node.attributes };
730
+ if (node.children !== void 0) clone.children = node.children.map(cloneElement);
731
+ return clone;
732
+ }
733
+ function buildReplacement(use, target) {
734
+ const clone = cloneElement(target);
735
+ if (clone.name === "symbol") clone.name = "g";
736
+ const attributes = {};
737
+ const useAttrs = use.attributes ?? {};
738
+ const x = useAttrs.x;
739
+ const y = useAttrs.y;
740
+ const baseTransform = useAttrs.transform;
741
+ const translate = x !== void 0 || y !== void 0 ? `translate(${x ?? "0"} ${y ?? "0"})` : void 0;
742
+ const transform = [baseTransform, translate].filter((t) => t !== void 0).join(" ");
743
+ if (transform.length > 0) attributes.transform = transform;
744
+ for (const [key, value] of Object.entries(useAttrs)) {
745
+ if (USE_GEOMETRY_ATTRS.has(key) || key === "transform") continue;
746
+ attributes[key] = value;
747
+ }
748
+ return { type: "element", name: "g", attributes, children: [clone] };
749
+ }
750
+ function resolveUses(root) {
751
+ const byId = indexById(root);
752
+ const resolveChildren = (node, depth) => {
753
+ const children = node.children;
754
+ if (children === void 0) return;
755
+ for (let i = 0; i < children.length; i++) {
756
+ const child = children[i];
757
+ if (child === void 0) continue;
758
+ if (isElement(child) && child.name === "use") {
759
+ const id = depth > 0 ? targetId(child) : void 0;
760
+ const target = id !== void 0 ? byId.get(id) : void 0;
761
+ if (target !== void 0) {
762
+ const replacement = buildReplacement(child, target);
763
+ resolveChildren(replacement, depth - 1);
764
+ children[i] = replacement;
765
+ continue;
766
+ }
767
+ }
768
+ resolveChildren(child, depth);
769
+ }
770
+ };
771
+ resolveChildren(root, MAX_DEPTH);
772
+ }
773
+
774
+ // src/shapes.ts
775
+ var n = (attrs, key, fallback = 0) => {
776
+ const v = attrs[key];
777
+ if (v === void 0) return fallback;
778
+ const parsed = parseFloat(v);
779
+ return Number.isNaN(parsed) ? fallback : parsed;
780
+ };
781
+ function shapeToPathData(name, attrs) {
782
+ switch (name) {
783
+ case "rect": {
784
+ const w = n(attrs, "width");
785
+ const h = n(attrs, "height");
786
+ if (w <= 0 || h <= 0) return null;
787
+ const x = n(attrs, "x");
788
+ const y = n(attrs, "y");
789
+ let rx = attrs.rx !== void 0 ? n(attrs, "rx") : n(attrs, "ry");
790
+ let ry = attrs.ry !== void 0 ? n(attrs, "ry") : n(attrs, "rx");
791
+ rx = Math.min(Math.max(rx, 0), w / 2);
792
+ ry = Math.min(Math.max(ry, 0), h / 2);
793
+ if (rx > 0 || ry > 0) {
794
+ return `M${x + rx},${y}h${w - 2 * rx}a${rx},${ry} 0 0 1 ${rx},${ry}v${h - 2 * ry}a${rx},${ry} 0 0 1 ${-rx},${ry}h${-(w - 2 * rx)}a${rx},${ry} 0 0 1 ${-rx},${-ry}v${-(h - 2 * ry)}a${rx},${ry} 0 0 1 ${rx},${-ry}z`;
795
+ }
796
+ return `M${x},${y}h${w}v${h}h${-w}z`;
797
+ }
798
+ case "circle": {
799
+ const r = n(attrs, "r");
800
+ if (r <= 0) return null;
801
+ const cx = n(attrs, "cx");
802
+ const cy = n(attrs, "cy");
803
+ return `M${cx - r},${cy}a${r},${r} 0 1 0 ${2 * r},0a${r},${r} 0 1 0 ${-2 * r},0z`;
804
+ }
805
+ case "ellipse": {
806
+ const rx = n(attrs, "rx");
807
+ const ry = n(attrs, "ry");
808
+ if (rx <= 0 || ry <= 0) return null;
809
+ const cx = n(attrs, "cx");
810
+ const cy = n(attrs, "cy");
811
+ return `M${cx - rx},${cy}a${rx},${ry} 0 1 0 ${2 * rx},0a${rx},${ry} 0 1 0 ${-2 * rx},0z`;
812
+ }
813
+ case "line":
814
+ return `M${n(attrs, "x1")},${n(attrs, "y1")}L${n(attrs, "x2")},${n(attrs, "y2")}`;
815
+ case "polyline":
816
+ case "polygon": {
817
+ const pts = (attrs.points ?? "").trim().split(/[\s,]+/).map(Number).filter((v) => !Number.isNaN(v));
818
+ if (pts.length < 4) return null;
819
+ let d = `M${pts[0]},${pts[1]}`;
820
+ for (let i = 2; i + 1 < pts.length; i += 2) d += `L${pts[i]},${pts[i + 1]}`;
821
+ return name === "polygon" ? d + "z" : d;
822
+ }
823
+ default:
824
+ return null;
825
+ }
826
+ }
827
+ var SHAPE_NAMES = /* @__PURE__ */ new Set(["rect", "circle", "ellipse", "line", "polyline", "polygon"]);
828
+
829
+ // src/convert.ts
830
+ var UNSUPPORTED = /* @__PURE__ */ new Set(["mask", "filter", "pattern", "image", "text", "foreignObject", "marker", "switch"]);
831
+ var SKIP = /* @__PURE__ */ new Set([
832
+ "defs",
833
+ "symbol",
834
+ "metadata",
835
+ "title",
836
+ "desc",
837
+ "style",
838
+ "linearGradient",
839
+ "radialGradient",
840
+ "clipPath"
841
+ ]);
842
+ var escapeXml = (s) => s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
843
+ var INHERITABLE = [
844
+ "fill",
845
+ "fill-opacity",
846
+ "fill-rule",
847
+ "stroke",
848
+ "stroke-opacity",
849
+ "stroke-width",
850
+ "stroke-linecap",
851
+ "stroke-linejoin",
852
+ "stroke-miterlimit",
853
+ "color"
854
+ ];
855
+ function parseStyle(style) {
856
+ if (!style) return {};
857
+ const out = {};
858
+ for (const decl of style.split(";")) {
859
+ const i = decl.indexOf(":");
860
+ if (i > 0) out[decl.slice(0, i).trim().toLowerCase()] = decl.slice(i + 1).trim();
861
+ }
862
+ return out;
863
+ }
864
+ function num(value, precision) {
865
+ const f = 10 ** precision;
866
+ return String(Math.round(parseFloat(value) * f) / f);
867
+ }
868
+ function collectByName(node, names, out) {
869
+ for (const child of node.children ?? []) {
870
+ if (child.type === "element") {
871
+ if (child.name && names.has(child.name)) out.push(child);
872
+ collectByName(child, names, out);
873
+ }
874
+ }
875
+ }
876
+ function convert(svg, options = {}) {
877
+ const {
878
+ optimize: optimize2 = true,
879
+ svgoConfig,
880
+ floatPrecision = 3,
881
+ currentColor = "#000000",
882
+ fillBlackForUnfilled = true,
883
+ strict = false,
884
+ indent = 4,
885
+ xmlTag = false,
886
+ tint,
887
+ onWarn
888
+ } = options;
889
+ const warnings = [];
890
+ const warn = (w) => {
891
+ if (strict) throw new Error(`[${w.code}] ${w.message}`);
892
+ warnings.push(w);
893
+ onWarn?.(w);
894
+ };
895
+ const root = parseSvg(svg, optimize2, svgoConfig);
896
+ resolveUses(root);
897
+ const svgEl = (root.children ?? []).find((c) => c.type === "element" && c.name === "svg");
898
+ if (!svgEl) throw new Error("No <svg> root element found");
899
+ const a = svgEl.attributes ?? {};
900
+ let viewBox = a.viewBox?.trim();
901
+ let vbParts = viewBox ? viewBox.split(/[\s,]+/).map(Number) : [];
902
+ if (vbParts.length !== 4 || vbParts.some(Number.isNaN)) {
903
+ const w = parseFloat(a.width ?? "") || 24;
904
+ const h = parseFloat(a.height ?? "") || 24;
905
+ vbParts = [0, 0, w, h];
906
+ viewBox = void 0;
907
+ }
908
+ const [, , vpW, vpH] = vbParts;
909
+ const width = parseFloat(a.width ?? "") || vpW || 24;
910
+ const height = parseFloat(a.height ?? "") || vpH || 24;
911
+ const gradientEls = [];
912
+ collectByName(svgEl, /* @__PURE__ */ new Set(["linearGradient", "radialGradient"]), gradientEls);
913
+ const gradients = collectGradients(gradientEls, currentColor, warn);
914
+ const clipEls = [];
915
+ collectByName(svgEl, /* @__PURE__ */ new Set(["clipPath"]), clipEls);
916
+ const clipPaths = /* @__PURE__ */ new Map();
917
+ for (const clip of clipEls) {
918
+ const id = clip.attributes?.id;
919
+ if (!id) continue;
920
+ const d = (clip.children ?? []).map(
921
+ (c) => c.name === "path" ? c.attributes?.d : SHAPE_NAMES.has(c.name ?? "") ? shapeToPathData(c.name, c.attributes ?? {}) : null
922
+ ).filter(Boolean).join("");
923
+ if (d) clipPaths.set(id, d);
924
+ }
925
+ const step = " ".repeat(indent);
926
+ let usesGradient = false;
927
+ const resolveStyle = (el, parent) => {
928
+ const attrs = el.attributes ?? {};
929
+ const style = parseStyle(attrs.style);
930
+ const get = (name) => style[name] ?? attrs[name];
931
+ const next = { ...parent };
932
+ for (const prop of INHERITABLE) {
933
+ const v = get(prop);
934
+ if (v === void 0) continue;
935
+ if (prop === "fill") next.fill = v;
936
+ else if (prop === "fill-opacity") next.fillOpacity = parseFloat(v);
937
+ else if (prop === "fill-rule") next.fillRule = v;
938
+ else if (prop === "stroke") next.stroke = v;
939
+ else if (prop === "stroke-opacity") next.strokeOpacity = parseFloat(v);
940
+ else if (prop === "stroke-width") next.strokeWidth = v;
941
+ else if (prop === "stroke-linecap") next.strokeLinecap = v;
942
+ else if (prop === "stroke-linejoin") next.strokeLinejoin = v;
943
+ else if (prop === "stroke-miterlimit") next.strokeMiterlimit = v;
944
+ else if (prop === "color") next.color = v;
945
+ }
946
+ const op = get("opacity");
947
+ next.opacityMul = parent.opacityMul * (op !== void 0 ? parseFloat(op) : 1);
948
+ return next;
949
+ };
950
+ const emitPath = (d, el, style, pad, bake) => {
951
+ const attrs = el.attributes ?? {};
952
+ const pd = bake ? transformPathData(d, bake) : d;
953
+ const lines = [`${pad}<path`, `${pad}${step}android:pathData="${escapeXml(pd)}"`];
954
+ let gradientChild = null;
955
+ const fillRaw = style.fill ?? (fillBlackForUnfilled ? "#000000" : "none");
956
+ const urlMatch = /^url\(#([^)]+)\)/.exec(fillRaw.trim());
957
+ if (urlMatch) {
958
+ const raw = gradients.get(urlMatch[1]);
959
+ if (raw) {
960
+ if (bake)
961
+ warn({
962
+ code: "gradient-under-skew",
963
+ message: "Gradient under a skewed/baked group may be imprecisely placed.",
964
+ node: "path"
965
+ });
966
+ const bbox = raw.objectBox ? pathBBox(pd) : null;
967
+ const resolved = resolveGradient(raw, bbox, { width: vpW, height: vpH }, floatPrecision, warn);
968
+ gradientChild = renderGradient(resolved, pad, step);
969
+ usesGradient = true;
970
+ } else {
971
+ warn({
972
+ code: "missing-gradient",
973
+ message: `Path references unknown gradient "${urlMatch[1]}"; using black.`,
974
+ node: "path"
975
+ });
976
+ lines.push(`${pad}${step}android:fillColor="#FF000000"`);
977
+ }
978
+ } else if (fillRaw.trim().toLowerCase() !== "none") {
979
+ const color = toAndroidColor(fillRaw, void 0, style.color);
980
+ if (color) lines.push(`${pad}${step}android:fillColor="${color}"`);
981
+ }
982
+ const fillAlpha = style.fillOpacity * style.opacityMul;
983
+ if (fillAlpha < 1) lines.push(`${pad}${step}android:fillAlpha="${Math.max(fillAlpha, 0)}"`);
984
+ if (style.fillRule === "evenodd") lines.push(`${pad}${step}android:fillType="evenOdd"`);
985
+ if (style.stroke && style.stroke.trim().toLowerCase() !== "none") {
986
+ const dash = parseStyle(attrs.style)["stroke-dasharray"] ?? attrs["stroke-dasharray"];
987
+ if (dash && dash.trim().toLowerCase() !== "none")
988
+ warn({
989
+ code: "unsupported-stroke-dasharray",
990
+ message: "stroke-dasharray is not representable on a VectorDrawable path; ignored.",
991
+ node: "path"
992
+ });
993
+ if (/^url\(/.test(style.stroke.trim())) {
994
+ warn({
995
+ code: "unsupported-stroke-gradient",
996
+ message: "Gradient strokes are not supported by VectorDrawable; stroke dropped.",
997
+ node: "path"
998
+ });
999
+ } else {
1000
+ const sc = toAndroidColor(style.stroke, void 0, style.color);
1001
+ if (sc) {
1002
+ lines.push(`${pad}${step}android:strokeColor="${sc}"`);
1003
+ lines.push(`${pad}${step}android:strokeWidth="${num(style.strokeWidth ?? "1", floatPrecision)}"`);
1004
+ const sa = style.strokeOpacity * style.opacityMul;
1005
+ if (sa < 1) lines.push(`${pad}${step}android:strokeAlpha="${Math.max(sa, 0)}"`);
1006
+ if (style.strokeLinecap === "round" || style.strokeLinecap === "square")
1007
+ lines.push(`${pad}${step}android:strokeLineCap="${style.strokeLinecap}"`);
1008
+ if (style.strokeLinejoin === "round" || style.strokeLinejoin === "bevel" || style.strokeLinejoin === "miter")
1009
+ lines.push(`${pad}${step}android:strokeLineJoin="${style.strokeLinejoin}"`);
1010
+ if (style.strokeMiterlimit)
1011
+ lines.push(
1012
+ `${pad}${step}android:strokeMiterLimit="${num(style.strokeMiterlimit, floatPrecision)}"`
1013
+ );
1014
+ }
1015
+ }
1016
+ }
1017
+ const clipD = clipRef(attrs);
1018
+ const clip = clipD && bake ? transformPathData(clipD, bake) : clipD;
1019
+ const body2 = gradientChild ? `${lines.join("\n")}>
1020
+ ${gradientChild}
1021
+ ${pad}</path>` : `${lines.join("\n")} />`;
1022
+ return clip ? wrapClip(clip, body2, pad) : body2;
1023
+ };
1024
+ const clipRef = (attrs) => {
1025
+ const m = /^url\(#([^)]+)\)/.exec((attrs["clip-path"] ?? "").trim());
1026
+ if (!m) return null;
1027
+ const d = clipPaths.get(m[1]);
1028
+ if (!d) {
1029
+ warn({ code: "missing-clip-path", message: `Unknown clip-path "${m[1]}"; ignored.`, node: "clip-path" });
1030
+ return null;
1031
+ }
1032
+ return d;
1033
+ };
1034
+ const wrapClip = (d, body2, pad) => `${pad}<group>
1035
+ ${pad}${step}<clip-path android:pathData="${escapeXml(d)}" />
1036
+ ${body2.replace(/^/gm, step)}
1037
+ ${pad}</group>`;
1038
+ const r = (v) => String(Math.round(v * 10 ** floatPrecision) / 10 ** floatPrecision);
1039
+ const walk = (el, parent, pad, bake) => {
1040
+ const out = [];
1041
+ for (const child of el.children ?? []) {
1042
+ if (child.type !== "element" || !child.name) continue;
1043
+ const name = child.name;
1044
+ if (SKIP.has(name)) continue;
1045
+ if (UNSUPPORTED.has(name)) {
1046
+ warn({
1047
+ code: "unsupported-element",
1048
+ message: `<${name}> cannot be represented in a VectorDrawable; skipped.`,
1049
+ node: name
1050
+ });
1051
+ continue;
1052
+ }
1053
+ const style = resolveStyle(child, parent);
1054
+ if (name === "g") {
1055
+ const matrix = parseTransform(child.attributes?.transform);
1056
+ if (matrix) {
1057
+ const dec = decompose(matrix);
1058
+ if (bake || dec.hasSkew) {
1059
+ if (dec.hasSkew && !bake)
1060
+ warn({
1061
+ code: "group-skew",
1062
+ message: "A <g> uses skew/shear; baking the transform into path geometry.",
1063
+ node: "g"
1064
+ });
1065
+ const inner = walk(child, style, pad, multiply(bake ?? IDENTITY, matrix));
1066
+ if (inner.trim()) out.push(inner);
1067
+ } else {
1068
+ const inner = walk(child, style, pad + step);
1069
+ if (inner.trim()) {
1070
+ const attrs = [];
1071
+ if (Math.abs(dec.translateX) > 1e-6)
1072
+ attrs.push(`${pad}${step}android:translateX="${r(dec.translateX)}"`);
1073
+ if (Math.abs(dec.translateY) > 1e-6)
1074
+ attrs.push(`${pad}${step}android:translateY="${r(dec.translateY)}"`);
1075
+ if (Math.abs(dec.rotation) > 1e-6)
1076
+ attrs.push(`${pad}${step}android:rotation="${r(dec.rotation)}"`);
1077
+ if (Math.abs(dec.scaleX - 1) > 1e-6)
1078
+ attrs.push(`${pad}${step}android:scaleX="${r(dec.scaleX)}"`);
1079
+ if (Math.abs(dec.scaleY - 1) > 1e-6)
1080
+ attrs.push(`${pad}${step}android:scaleY="${r(dec.scaleY)}"`);
1081
+ const head = attrs.length ? `${pad}<group
1082
+ ${attrs.join("\n")}>` : `${pad}<group>`;
1083
+ out.push(`${head}
1084
+ ${inner}
1085
+ ${pad}</group>`);
1086
+ }
1087
+ }
1088
+ } else {
1089
+ const inner = walk(child, style, pad, bake);
1090
+ if (inner.trim()) out.push(inner);
1091
+ }
1092
+ continue;
1093
+ }
1094
+ let d = null;
1095
+ if (name === "path") d = child.attributes?.d ?? null;
1096
+ else if (SHAPE_NAMES.has(name)) d = shapeToPathData(name, child.attributes ?? {});
1097
+ if (d) out.push(emitPath(d, child, style, pad, bake));
1098
+ else if (name !== "use")
1099
+ warn({ code: "empty-path", message: `<${name}> produced no drawable geometry; skipped.`, node: name });
1100
+ else
1101
+ warn({
1102
+ code: "unsupported-element",
1103
+ message: "<use> references are not resolved; skipped.",
1104
+ node: "use"
1105
+ });
1106
+ }
1107
+ return out.join("\n");
1108
+ };
1109
+ const rootStyle = { fillOpacity: 1, strokeOpacity: 1, color: currentColor, opacityMul: 1 };
1110
+ const baseStyle = resolveStyle(svgEl, rootStyle);
1111
+ const body = walk(svgEl, baseStyle, step);
1112
+ const aapt = usesGradient ? `
1113
+ ${step}xmlns:aapt="http://schemas.android.com/aapt"` : "";
1114
+ const tintLine = tint ? `
1115
+ ${step}android:tint="${tint.trim()}"` : "";
1116
+ const decl = xmlTag ? '<?xml version="1.0" encoding="utf-8"?>\n' : "";
1117
+ const xml = decl + `<vector xmlns:android="http://schemas.android.com/apk/res/android"${aapt}${tintLine}
1118
+ ${step}android:width="${num(String(width), floatPrecision)}dp"
1119
+ ${step}android:height="${num(String(height), floatPrecision)}dp"
1120
+ ${step}android:viewportWidth="${num(String(vpW), floatPrecision)}"
1121
+ ${step}android:viewportHeight="${num(String(vpH), floatPrecision)}">
1122
+ ${body}
1123
+ </vector>
1124
+ `;
1125
+ return { xml, warnings };
1126
+ }
1127
+ export {
1128
+ convert
1129
+ };