android-mcp-toolkit 1.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.
Files changed (3) hide show
  1. package/README.md +125 -0
  2. package/dist/index.js +1489 -0
  3. package/package.json +37 -0
package/dist/index.js ADDED
@@ -0,0 +1,1489 @@
1
+ #!/usr/bin/env node
2
+ var __getOwnPropNames = Object.getOwnPropertyNames;
3
+ var __commonJS = (cb, mod) => function __require() {
4
+ return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;
5
+ };
6
+
7
+ // vendor/svg2vectordrawable/svg-to-vectordrawable.js
8
+ var require_svg_to_vectordrawable = __commonJS({
9
+ "vendor/svg2vectordrawable/svg-to-vectordrawable.js"(exports2, module2) {
10
+ var { parseSvg } = require("svgo/lib/parser");
11
+ var JSAPI = require("svgo/lib/svgo/jsAPI");
12
+ var pathBounds = require("svg-path-bounds");
13
+ var svgpath = require("svgpath");
14
+ var JS2XML = function() {
15
+ this.width = 24;
16
+ this.height = 24;
17
+ this.viewportWidth = 24;
18
+ this.viewportHeight = 24;
19
+ this.indent = 4;
20
+ this.indentLevel = 0;
21
+ this.vectordrawableTags = [
22
+ // Android vs SVG
23
+ "vector",
24
+ // svg
25
+ "group",
26
+ // g
27
+ "path",
28
+ // path, rect, circle, polygon, ellipse, polyline, line
29
+ "clip-path",
30
+ // mask
31
+ "aapt:attr",
32
+ "gradient",
33
+ // linearGradient, radialGradient
34
+ "item"
35
+ // stop
36
+ ];
37
+ this.vectordrawableAttrs = [
38
+ // Android vs SVG
39
+ "android:name",
40
+ // id
41
+ // <Vector>
42
+ "xmlns:aapt",
43
+ "android:width",
44
+ // width
45
+ "android:height",
46
+ // height
47
+ "android:viewportWidth",
48
+ "android:viewportHeight",
49
+ // viewBox
50
+ "android:tint",
51
+ "android:tintMode",
52
+ "android:autoMirrored",
53
+ "android:alpha",
54
+ // <group>
55
+ "android:rotation",
56
+ // transform="rotate(<a> [<x> <y>])"
57
+ "android:pivotX",
58
+ "android:pivotY",
59
+ "android:scaleX",
60
+ // transform="scale(<x> [<y>])"
61
+ "android:scaleY",
62
+ "android:translateX",
63
+ // transform="translate(<x> [<y>])"
64
+ "android:translateY",
65
+ // <path>
66
+ "android:pathData",
67
+ // d
68
+ "android:fillColor",
69
+ // fill
70
+ "android:fillAlpha",
71
+ // fill-opacity 0..1
72
+ "android:strokeColor",
73
+ // stroke
74
+ "android:strokeWidth",
75
+ // stroke-width
76
+ "android:strokeAlpha",
77
+ // stroke-opacity
78
+ "android:trimPathStart",
79
+ "android:trimPathEnd",
80
+ "android:trimPathOffset",
81
+ "android:strokeLineCap",
82
+ // stroke-linecap butt, round, square. Default is butt.
83
+ "android:strokeLineJoin",
84
+ // stroke-linejoin miter, round, bevel. Default is miter.
85
+ "android:strokeMiterLimit",
86
+ // stroke-miterlimit Default is 4.
87
+ "android:fillType",
88
+ // fill-rule For SDK 24+, evenOdd, nonZero. Default is nonZero.
89
+ // aapt
90
+ "name",
91
+ "xmlns:aapt",
92
+ // gradient
93
+ "android:type",
94
+ // linear, radial, sweep
95
+ "android:startX",
96
+ // x1
97
+ "android:startY",
98
+ // y1
99
+ "android:endX",
100
+ // x2
101
+ "android:endY",
102
+ // y2
103
+ "android:centerX",
104
+ // cx
105
+ "android:centerY",
106
+ // cy
107
+ "android:gradientRadius",
108
+ // r
109
+ "android:startColor",
110
+ // not support
111
+ "android:centerColor",
112
+ // not support
113
+ "android:endColor",
114
+ // not support
115
+ "android:tileMode",
116
+ // not support, disabled, clamp, repeat, mirror
117
+ // gradient stop
118
+ "android:color",
119
+ // stop-color
120
+ "android:offset"
121
+ // offset
122
+ ];
123
+ };
124
+ JS2XML.prototype.refactorData = function(data, floatPrecision, fillBlack, tint) {
125
+ let elemUses = data.querySelectorAll("use");
126
+ if (elemUses) {
127
+ elemUses.forEach((elem) => {
128
+ if (elem.hasAttr("xlink:href") || elem.hasAttr("href")) {
129
+ let attr = "";
130
+ if (elem.hasAttr("xlink:href")) {
131
+ attr = "xlink:href";
132
+ }
133
+ if (elem.hasAttr("href")) {
134
+ attr = "href";
135
+ }
136
+ let originalElem = data.querySelector(elem.attr(attr).value);
137
+ let newElem = new JSAPI({
138
+ type: originalElem.type,
139
+ name: originalElem.name
140
+ });
141
+ originalElem.eachAttr((attr2) => {
142
+ if (attr2.name !== attr2 && attr2.name !== "id") {
143
+ newElem.addAttr(attr2);
144
+ }
145
+ });
146
+ elem.eachAttr((attr2) => {
147
+ if (attr2.name !== attr2 && attr2.name !== "id" && attr2.name !== "class") {
148
+ newElem.addAttr(attr2);
149
+ }
150
+ });
151
+ if ((newElem.attr("x") || newElem.attr("y")) && newElem.attr("d")) {
152
+ let x = newElem.attr("x") ? parseFloat(newElem.attr("x").value) : 0;
153
+ let y = newElem.attr("y") ? parseFloat(newElem.attr("y").value) : 0;
154
+ let pathData = newElem.attr("d").value;
155
+ if (x !== 0 || y !== 0) {
156
+ pathData = svgpath(pathData).translate(x, y).rel().round(floatPrecision).toString();
157
+ }
158
+ newElem.removeAttr("d");
159
+ newElem.addAttr({ name: "d", value: pathData, prefix: "", local: "d" });
160
+ }
161
+ elem.parentNode.spliceContent(elem.parentNode.children.indexOf(elem), 0, newElem);
162
+ elem.parentNode.spliceContent(elem.parentNode.children.indexOf(elem), 1, []);
163
+ }
164
+ });
165
+ }
166
+ let elemHaveIds = data.querySelectorAll("path, g, circle, ellipse, line, polygon, polyline, rect");
167
+ if (elemHaveIds) {
168
+ elemHaveIds.forEach((elem) => {
169
+ elem.removeAttr("id");
170
+ });
171
+ }
172
+ let elemRects = data.querySelectorAll("rect");
173
+ if (elemRects) {
174
+ elemRects.forEach((elem) => {
175
+ elem.renameElem("path");
176
+ let x = elem.hasAttr("x") ? parseFloat(elem.attr("x").value) : 0;
177
+ let y = elem.hasAttr("y") ? parseFloat(elem.attr("y").value) : 0;
178
+ let width = elem.hasAttr("width") ? parseFloat(elem.attr("width").value) : 0;
179
+ let height = elem.hasAttr("height") ? parseFloat(elem.attr("height").value) : 0;
180
+ let rx = 0;
181
+ let ry = 0;
182
+ if (elem.hasAttr("rx") && elem.hasAttr("ry")) {
183
+ rx = parseFloat(elem.attr("rx").value);
184
+ ry = parseFloat(elem.attr("ry").value);
185
+ } else if (elem.hasAttr("rx")) {
186
+ rx = parseFloat(elem.attr("rx").value);
187
+ ry = rx;
188
+ } else if (elem.hasAttr("ry")) {
189
+ ry = parseFloat(elem.attr("ry").value);
190
+ rx = ry;
191
+ }
192
+ x = this.round(x, floatPrecision);
193
+ y = this.round(y, floatPrecision);
194
+ width = this.round(width, floatPrecision);
195
+ height = this.round(height, floatPrecision);
196
+ rx = this.round(rx, floatPrecision);
197
+ ry = this.round(ry, floatPrecision);
198
+ let pathData = this.rectToPathData(x, y, width, height, rx, ry);
199
+ if (rx !== 0 || ry !== 0) {
200
+ pathData = svgpath(pathData).unarc().rel().round(floatPrecision).toString();
201
+ }
202
+ if (elem.hasAttr("transform")) {
203
+ let svgTransform = elem.attr("transform").value;
204
+ let number = "((?:-)?\\d+(?:\\.\\d+)?)";
205
+ let separator = "(?:(?:\\s+)|(?:\\s*,\\s*))";
206
+ let translateRegExp = new RegExp(`translate\\(${number}${separator}?${number}?\\)`);
207
+ let scaleRegExp = new RegExp(`scale\\(${number}${separator}?${number}?\\)`);
208
+ let rotateRegExp = new RegExp(`rotate\\(${number}${separator}?${number}?${separator}?${number}?\\)`);
209
+ let translateMatch = translateRegExp.exec(svgTransform);
210
+ let scaleMatch = scaleRegExp.exec(svgTransform);
211
+ let rotateMatch = rotateRegExp.exec(svgTransform);
212
+ if (rotateMatch) {
213
+ let angle = parseFloat(rotateMatch[1]);
214
+ let rx2 = parseFloat(rotateMatch[2]) || 0;
215
+ let ry2 = parseFloat(rotateMatch[3]) || 0;
216
+ pathData = svgpath(pathData).rotate(angle, rx2, ry2).rel().round(floatPrecision).toString();
217
+ }
218
+ if (scaleMatch) {
219
+ let sx = parseFloat(scaleMatch[1]);
220
+ let sy = parseFloat(scaleMatch[2]) || sx;
221
+ pathData = svgpath(pathData).scale(sx, sy).rel().round(floatPrecision).toString();
222
+ }
223
+ if (translateMatch) {
224
+ let x2 = parseFloat(translateMatch[1]);
225
+ let y2 = parseFloat(translateMatch[2]) || 0;
226
+ pathData = svgpath(pathData).translate(x2, y2).rel().round(floatPrecision).toString();
227
+ }
228
+ }
229
+ elem.addAttr({ name: "d", value: pathData, prefix: "", local: "d" });
230
+ elem.removeAttr("x");
231
+ elem.removeAttr("y");
232
+ elem.removeAttr("width");
233
+ elem.removeAttr("height");
234
+ elem.removeAttr("rx");
235
+ elem.removeAttr("ry");
236
+ });
237
+ }
238
+ let elemGroups = data.querySelectorAll("g");
239
+ if (elemGroups) {
240
+ elemGroups.forEach((elem) => {
241
+ let childPaths = elem.querySelectorAll("path");
242
+ if (childPaths) {
243
+ childPaths.forEach((item) => {
244
+ if (elem.hasAttr("fill") && !elem.hasAttr("fill", "none") && !item.hasAttr("fill") && !item.hasAttr("fill", "none")) {
245
+ item.addAttr({ name: "fill", value: elem.attr("fill").value, prefix: "", local: "fill" });
246
+ }
247
+ if (elem.hasAttr("fill-opacity") && !item.hasAttr("fill-opacity")) {
248
+ item.addAttr({ name: "fill-opacity", value: elem.attr("fill-opacity").value, prefix: "", local: "fill-opacity" });
249
+ }
250
+ if (elem.hasAttr("stroke") && !item.hasAttr("stroke")) {
251
+ item.addAttr({ name: "stroke", value: elem.attr("stroke").value, prefix: "", local: "stroke" });
252
+ }
253
+ if (elem.hasAttr("stroke-width") && !item.hasAttr("stroke-width")) {
254
+ item.addAttr({ name: "stroke-width", value: elem.attr("stroke-width").value, prefix: "", local: "stroke-width" });
255
+ }
256
+ if (elem.hasAttr("stroke-opacity") && !item.hasAttr("stroke-opacity")) {
257
+ item.addAttr({ name: "stroke-opacity", value: elem.attr("stroke-opacity").value, prefix: "", local: "stroke-opacity" });
258
+ }
259
+ if (elem.hasAttr("stroke-linecap") && !item.hasAttr("stroke-linecap")) {
260
+ item.addAttr({ name: "stroke-linecap", value: elem.attr("stroke-linecap").value, prefix: "", local: "stroke-linecap" });
261
+ }
262
+ if (elem.hasAttr("stroke-linejoin") && !item.hasAttr("stroke-linejoin")) {
263
+ item.addAttr({ name: "stroke-linejoin", value: elem.attr("stroke-linejoin").value, prefix: "", local: "stroke-linejoin" });
264
+ }
265
+ if (elem.hasAttr("opacity")) {
266
+ let opacity = elem.attr("opacity").value;
267
+ if (item.hasAttr("opacity")) {
268
+ opacity = this.round(elem.attr("opacity").value * item.attr("opacity").value, floatPrecision);
269
+ }
270
+ item.addAttr({ name: "opacity", value: opacity, prefix: "", local: "opacity" });
271
+ }
272
+ if (elem.hasAttr("fill-rule", "evenodd")) {
273
+ if (!item.hasAttr("fill-rule", "nonzero") && item.hasAttr("fill")) {
274
+ item.addAttr({ name: "android:fillType", value: "evenOdd", prefix: "android", local: "fillType" });
275
+ }
276
+ }
277
+ });
278
+ elem.removeAttr("fill");
279
+ elem.removeAttr("fill-opacity");
280
+ elem.removeAttr("stroke");
281
+ elem.removeAttr("stroke-width");
282
+ elem.removeAttr("stroke-opacity");
283
+ elem.removeAttr("stroke-linecap");
284
+ elem.removeAttr("stroke-linejoin");
285
+ elem.removeAttr("opacity");
286
+ elem.removeAttr("fill-rule");
287
+ }
288
+ if (elem.hasAttr("transform")) {
289
+ let svgTransform = elem.attr("transform").value;
290
+ let number = "((?:-)?\\d+(?:\\.\\d+)?)";
291
+ let separator = "(?:(?:\\s+)|(?:\\s*,\\s*))";
292
+ let attrs = { rotation: "", pivotX: "", pivotY: "", scaleX: "", scaleY: "", translateX: "", translateY: "" };
293
+ let translateRegExp = new RegExp(`translate\\(${number}${separator}?${number}?\\)`, "g");
294
+ let translateX = 0;
295
+ let translateY = 0;
296
+ let translateMatch;
297
+ while (translateMatch = translateRegExp.exec(svgTransform)) {
298
+ translateX += Number(translateMatch[1]);
299
+ translateY += Number(translateMatch[2]);
300
+ }
301
+ if (translateX !== 0 && !isNaN(translateX)) {
302
+ attrs.translateX = translateX;
303
+ }
304
+ if (translateY !== 0 && !isNaN(translateY)) {
305
+ attrs.translateY = translateY;
306
+ }
307
+ let scaleRegExp = new RegExp(`scale\\(${number}${separator}?${number}?\\)`, "g");
308
+ let scaleX = 1;
309
+ let scaleY = 1;
310
+ let scaleMatch;
311
+ while (scaleMatch = scaleRegExp.exec(svgTransform)) {
312
+ scaleX *= Number(scaleMatch[1]);
313
+ scaleY *= Number(scaleMatch[2]) || Number(scaleMatch[1]);
314
+ }
315
+ if (scaleX !== 1 && !isNaN(scaleX)) {
316
+ attrs.scaleX = scaleX;
317
+ }
318
+ if (scaleY !== 1 && !isNaN(scaleY)) {
319
+ attrs.scaleY = scaleY;
320
+ }
321
+ let rotateRegExp = new RegExp(`rotate\\(${number}${separator}?${number}?${separator}?${number}?\\)`);
322
+ let rotateMatch = rotateRegExp.exec(svgTransform);
323
+ if (rotateMatch) {
324
+ attrs.rotation = rotateMatch[1];
325
+ attrs.pivotX = rotateMatch[2] || "";
326
+ attrs.pivotY = rotateMatch[3] || "";
327
+ }
328
+ let skewRegExp = new RegExp(`skew([XY])\\(${number}\\)`);
329
+ let matrixRegExp = new RegExp("matrix\\((.*)\\)");
330
+ let skewMatch = skewRegExp.exec(svgTransform);
331
+ let matrixMatch = matrixRegExp.exec(svgTransform);
332
+ if (skewMatch || matrixMatch) {
333
+ let paths = elem.querySelectorAll("path");
334
+ if (paths) {
335
+ paths.forEach((path) => {
336
+ let pathData = path.attr("d").value;
337
+ if (skewMatch) {
338
+ let skewX = skewMatch[1] === "X" ? parseFloat(skewMatch[2]) : 0;
339
+ let skewY = skewMatch[1] === "Y" ? parseFloat(skewMatch[2]) : 0;
340
+ pathData = svgpath(pathData).skewX(skewX).skewY(skewY).rel().round(floatPrecision).toString();
341
+ }
342
+ if (matrixMatch) {
343
+ let matrix = matrixMatch[1].split(" ").map((item) => parseFloat(item));
344
+ pathData = svgpath(pathData).matrix(matrix).rel().round(floatPrecision).toString();
345
+ }
346
+ path.removeAttr("d");
347
+ path.addAttr({ name: "d", value: pathData, prefix: "", local: "d" });
348
+ });
349
+ }
350
+ }
351
+ Object.keys(attrs).forEach((key) => {
352
+ if (attrs[key] !== "" && (attrs[key] !== "0" && (key !== "scaleX" || key !== "scaleY"))) {
353
+ elem.addAttr({ name: `android:${key}`, value: this.round(attrs[key], floatPrecision), prefix: "android", local: key });
354
+ }
355
+ });
356
+ elem.removeAttr("transform");
357
+ }
358
+ });
359
+ }
360
+ elemGroups = data.querySelectorAll("g");
361
+ if (elemGroups) {
362
+ elemGroups.forEach((elem) => {
363
+ if (Object.keys(elem.attrs).length === 0) {
364
+ elem.parentNode.spliceContent(elem.parentNode.children.indexOf(elem), 0, elem.children);
365
+ elem.parentNode.spliceContent(elem.parentNode.children.indexOf(elem), 1, []);
366
+ } else {
367
+ elem.renameElem("group");
368
+ }
369
+ });
370
+ }
371
+ let elemSVG = data.querySelector("svg");
372
+ if (elemSVG) {
373
+ elemSVG.renameElem("vector");
374
+ if (elemSVG.hasAttr("width") && elemSVG.hasAttr("height")) {
375
+ this.width = parseInt(elemSVG.attr("width").value);
376
+ this.height = parseInt(elemSVG.attr("height").value);
377
+ }
378
+ if (elemSVG.hasAttr("viewBox")) {
379
+ let [x, y, w, h] = elemSVG.attr("viewBox").value.split(/\s+/);
380
+ this.viewportWidth = w;
381
+ this.viewportHeight = h;
382
+ if (!elemSVG.hasAttr("width") && !elemSVG.hasAttr("height")) {
383
+ this.width = w;
384
+ this.height = h;
385
+ }
386
+ }
387
+ elemSVG.attrs = {};
388
+ if (data.querySelector("linearGradient, radialGradient, sweepGradient, aapt\\:attr")) {
389
+ elemSVG.addAttr({ name: "xmlns:aapt", value: "http://schemas.android.com/aapt", prefix: "xmlns", local: "aapt" });
390
+ }
391
+ elemSVG.addAttr({ name: "android:width", value: this.width + "dp", prefix: "android", local: "width" });
392
+ elemSVG.addAttr({ name: "android:height", value: this.height + "dp", prefix: "android", local: "height" });
393
+ elemSVG.addAttr({ name: "android:viewportWidth", value: this.viewportWidth, prefix: "android", local: "viewportWidth" });
394
+ elemSVG.addAttr({ name: "android:viewportHeight", value: this.viewportHeight, prefix: "android", local: "viewportHeight" });
395
+ if (tint) {
396
+ if (/^#[A-F0-9]{1,8}$/i.test(tint)) {
397
+ tint = tint.toUpperCase();
398
+ }
399
+ elemSVG.addAttr({ name: "android:tint", value: tint, prefix: "android", local: "tint" });
400
+ }
401
+ }
402
+ let elemGradients = data.querySelectorAll("linearGradient, radialGradient, sweepGradient");
403
+ if (elemGradients) {
404
+ elemGradients.forEach((gradient) => {
405
+ if (gradient.hasAttr("id")) {
406
+ let gradientId = gradient.attr("id").value;
407
+ let gradientPaths = data.querySelectorAll(`path[fill="url(#${gradientId})"], path[stroke="url(#${gradientId})"]`);
408
+ if (gradientPaths) {
409
+ gradientPaths.forEach((path) => {
410
+ this.addGradientToElement(gradient, path, floatPrecision);
411
+ });
412
+ }
413
+ }
414
+ gradient.parentNode.spliceContent(gradient.parentNode.children.indexOf(gradient), 1, []);
415
+ });
416
+ }
417
+ let elemMasks = data.querySelectorAll("mask");
418
+ if (elemMasks) {
419
+ elemMasks.forEach((elem) => {
420
+ if (elem.hasAttr("id")) {
421
+ let maskId = elem.attr("id").value;
422
+ let clipMaskElem = elem.children[0];
423
+ let pathData = svgpath(clipMaskElem.attr("d").value).round(floatPrecision).toString();
424
+ let maskGroup = new JSAPI({
425
+ type: "element",
426
+ name: "group",
427
+ attrs: {},
428
+ children: []
429
+ });
430
+ clipMaskElem.renameElem("clip-path");
431
+ clipMaskElem.attrs = {};
432
+ clipMaskElem.addAttr({ name: "android:pathData", value: pathData, prefix: "android", local: "pathData" });
433
+ maskGroup.children.push(clipMaskElem);
434
+ let maskedElems = data.querySelectorAll(`*[mask="url(#${maskId})"]`);
435
+ if (maskedElems) {
436
+ maskedElems.forEach((item) => {
437
+ item.removeAttr("mask");
438
+ maskGroup.children.push(item);
439
+ });
440
+ elem.parentNode.spliceContent(elem.parentNode.children.indexOf(maskedElems[0]), maskedElems.length, maskGroup);
441
+ } else {
442
+ elem.parentNode.spliceContent(elem.parentNode.children.indexOf(elem), 0, maskGroup);
443
+ }
444
+ }
445
+ elem.parentNode.spliceContent(elem.parentNode.children.indexOf(elem), 1, []);
446
+ });
447
+ }
448
+ let elemPaths = data.querySelectorAll("path");
449
+ if (elemPaths) {
450
+ elemPaths.forEach((elem) => {
451
+ if (elem.hasAttr("fill")) {
452
+ if (elem.attr("fill").value === "none") {
453
+ elem.removeAttr("fill");
454
+ } else if (!/^url\(#.*\)$/.test(elem.attr("fill").value)) {
455
+ let color = this.svgHexToAndroid(elem.attr("fill").value);
456
+ let fillAttr = { name: "android:fillColor", value: color, prefix: "android", local: "fillColor" };
457
+ if (elem.hasAttr("fill-opacity")) {
458
+ fillAttr.value = this.mergeColorAndOpacity(fillAttr.value, elem.attr("fill-opacity").value);
459
+ elem.removeAttr("fill-opacity");
460
+ }
461
+ elem.addAttr(fillAttr);
462
+ elem.removeAttr("fill");
463
+ }
464
+ } else if (fillBlack) {
465
+ let fillAttr = { name: "android:fillColor", value: "#FF000000", prefix: "android", local: "fillColor" };
466
+ elem.addAttr(fillAttr);
467
+ }
468
+ if (elem.hasAttr("opacity")) {
469
+ elem.addAttr({ name: "android:fillAlpha", value: elem.attr("opacity").value, prefix: "android", local: "fillAlpha" });
470
+ }
471
+ if (elem.hasAttr("stroke")) {
472
+ if (!/^url\(#.*\)$/.test(elem.attr("stroke").value) && elem.attr("stroke").value !== "none") {
473
+ let color = this.svgHexToAndroid(elem.attr("stroke").value);
474
+ let strokeAttr = { name: "android:strokeColor", value: color, prefix: "android", local: "strokeColor" };
475
+ if (elem.hasAttr("stroke-opacity")) {
476
+ strokeAttr.value = this.mergeColorAndOpacity(strokeAttr.value, elem.attr("stroke-opacity").value);
477
+ elem.removeAttr("stroke-opacity");
478
+ }
479
+ elem.addAttr(strokeAttr);
480
+ elem.removeAttr("stroke");
481
+ }
482
+ if (elem.hasAttr("opacity")) {
483
+ elem.addAttr({ name: "android:strokeAlpha", value: elem.attr("opacity").value, prefix: "android", local: "strokeAlpha" });
484
+ }
485
+ let strokeWidthAttr = { name: "android:strokeWidth", value: 0, prefix: "android", local: "strokeWidth" };
486
+ if (!elem.hasAttr("stroke-width")) {
487
+ strokeWidthAttr.value = 1;
488
+ } else {
489
+ strokeWidthAttr.value = elem.attr("stroke-width").value;
490
+ elem.removeAttr("stroke-width");
491
+ }
492
+ elem.addAttr(strokeWidthAttr);
493
+ let strokeExtraAttrs = ["stroke-linecap", "stroke-linejoin", "stroke-miterlimit"];
494
+ let strokeExtraAttrsAndroid = ["strokeLineCap", "strokeLineJoin", "strokeMiterLimit"];
495
+ strokeExtraAttrs.forEach((attr, index) => {
496
+ if (elem.hasAttr(attr)) {
497
+ let localName = strokeExtraAttrsAndroid[index];
498
+ elem.addAttr({ name: "android:" + localName, value: elem.attr(attr).value, prefix: "android", local: localName });
499
+ elem.removeAttr("attr");
500
+ }
501
+ });
502
+ }
503
+ if (elem.hasAttr("opacity")) {
504
+ elem.removeAttr("opacity");
505
+ }
506
+ elem.removeAttr("fill-rule", "nonzero");
507
+ if (elem.hasAttr("fill-rule", "evenodd")) {
508
+ elem.addAttr({ name: "android:fillType", value: "evenOdd", prefix: "android", local: "fillType" });
509
+ elem.removeAttr("fill-rule", "evenodd");
510
+ }
511
+ if (elem.hasAttr("d")) {
512
+ let pathData = svgpath(elem.attr("d").value).round(floatPrecision).toString();
513
+ elem.addAttr({ name: "android:pathData", value: pathData, prefix: "android", local: "pathData" });
514
+ elem.removeAttr("d");
515
+ }
516
+ });
517
+ }
518
+ };
519
+ JS2XML.prototype.addGradientToElement = function(gradient, elem, floatPrecision) {
520
+ let vectorDrawableGradient = new JSAPI({
521
+ type: "element",
522
+ name: "gradient",
523
+ children: []
524
+ });
525
+ let vectorDrawableAapt = new JSAPI({
526
+ type: "element",
527
+ name: "aapt:attr",
528
+ children: [vectorDrawableGradient]
529
+ });
530
+ let gradientId = gradient.attr("id").value;
531
+ if (elem.hasAttr("fill", `url(#${gradientId})`)) {
532
+ vectorDrawableAapt.addAttr({ name: "name", value: "android:fillColor", prefix: "", local: "name" });
533
+ elem.removeAttr("fill");
534
+ }
535
+ if (elem.hasAttr("stroke", `url(#${gradientId})`)) {
536
+ vectorDrawableAapt.addAttr({ name: "name", value: "android:strokeColor", prefix: "", local: "name" });
537
+ elem.removeAttr("stroke");
538
+ }
539
+ this.adjustGradientCoordinate(gradient, elem, floatPrecision);
540
+ if (gradient.name === "linearGradient") {
541
+ vectorDrawableGradient.addAttr({ name: "android:type", value: "linear", prefix: "android", local: "type" });
542
+ let startX = gradient.hasAttr("x1") ? gradient.attr("x1").value : "0";
543
+ let startY = gradient.hasAttr("y1") ? gradient.attr("y1").value : "0";
544
+ let endX = gradient.hasAttr("x2") ? gradient.attr("x2").value : this.viewportWidth;
545
+ let endY = gradient.hasAttr("y2") ? gradient.attr("y2").value : "0";
546
+ vectorDrawableGradient.addAttr({ name: "android:startX", value: startX, prefix: "android", local: "startX" });
547
+ vectorDrawableGradient.addAttr({ name: "android:startY", value: startY, prefix: "android", local: "startY" });
548
+ vectorDrawableGradient.addAttr({ name: "android:endX", value: endX, prefix: "android", local: "endX" });
549
+ vectorDrawableGradient.addAttr({ name: "android:endY", value: endY, prefix: "android", local: "endY" });
550
+ }
551
+ if (gradient.name === "radialGradient") {
552
+ vectorDrawableGradient.addAttr({ name: "android:type", value: "radial", prefix: "android", local: "type" });
553
+ let centerX = gradient.hasAttr("cx") ? gradient.attr("cx").value : this.viewportWidth / 2;
554
+ let centerY = gradient.hasAttr("cy") ? gradient.attr("cy").value : this.viewportHeight / 2;
555
+ if (gradient.hasAttr("rx")) centerX = gradient.attr("rx").value;
556
+ if (gradient.hasAttr("ry")) centerY = gradient.attr("ry").value;
557
+ let gradientRadius = gradient.hasAttr("r") ? gradient.attr("r").value : Math.max(this.viewportWidth, this.viewportHeight) / 2;
558
+ vectorDrawableGradient.addAttr({ name: "android:centerX", value: centerX, prefix: "android", local: "centerX" });
559
+ vectorDrawableGradient.addAttr({ name: "android:centerY", value: centerY, prefix: "android", local: "centerY" });
560
+ vectorDrawableGradient.addAttr({ name: "android:gradientRadius", value: gradientRadius, prefix: "android", local: "gradientRadius" });
561
+ }
562
+ if (gradient.name === "sweepGradient") {
563
+ vectorDrawableGradient.addAttr({ name: "android:type", value: "sweep", prefix: "android", local: "type" });
564
+ let centerX = gradient.hasAttr("cx") ? gradient.attr("cx").value : this.viewportWidth / 2;
565
+ let centerY = gradient.hasAttr("cy") ? gradient.attr("cy").value : this.viewportHeight / 2;
566
+ vectorDrawableGradient.addAttr({ name: "android:centerX", value: centerX, prefix: "android", local: "centerX" });
567
+ vectorDrawableGradient.addAttr({ name: "android:centerY", value: centerY, prefix: "android", local: "centerY" });
568
+ }
569
+ gradient.children.forEach((item) => {
570
+ let colorStop = new JSAPI({
571
+ type: "element",
572
+ name: "item"
573
+ });
574
+ const stopColorAttr = item.attr("stop-color");
575
+ let color = this.svgHexToAndroid(stopColorAttr == null ? "#000000FF" : stopColorAttr.value);
576
+ const offsetAttr = item.attr("offset");
577
+ let offset = offsetAttr == null ? 0 : offsetAttr.value;
578
+ if (this.isPercent(offset)) {
579
+ offset = Math.round(parseFloat(offset)) / 100;
580
+ }
581
+ if (item.hasAttr("stop-opacity")) {
582
+ color = this.mergeColorAndOpacity(color, item.attr("stop-opacity").value);
583
+ }
584
+ colorStop.addAttr({ name: "android:color", value: color, prefix: "android", local: "color" });
585
+ colorStop.addAttr({ name: "android:offset", value: offset, prefix: "android", local: "offset" });
586
+ vectorDrawableGradient.children.push(colorStop);
587
+ });
588
+ if (!elem.children) elem.children = [];
589
+ elem.children.push(vectorDrawableAapt);
590
+ };
591
+ JS2XML.prototype.adjustGradientCoordinate = function(gradient, elem, floatPrecision) {
592
+ if (gradient.elem === "linearGradient") {
593
+ if (!gradient.hasAttr("x1")) {
594
+ gradient.addAttr({ name: "x1", value: "0", prefix: "", local: "x1" });
595
+ }
596
+ if (!gradient.hasAttr("y1")) {
597
+ gradient.addAttr({ name: "y1", value: "0", prefix: "", local: "y1" });
598
+ }
599
+ if (!gradient.hasAttr("x2")) {
600
+ gradient.addAttr({ name: "x2", value: "100%", prefix: "", local: "x2" });
601
+ }
602
+ if (!gradient.hasAttr("y2")) {
603
+ gradient.addAttr({ name: "y2", value: "100%", prefix: "", local: "y2" });
604
+ }
605
+ }
606
+ if (gradient.elem === "radialGradient") {
607
+ if (!gradient.hasAttr("cx")) {
608
+ gradient.addAttr({ name: "cx", value: "50%", prefix: "", local: "cx" });
609
+ }
610
+ if (!gradient.hasAttr("cy")) {
611
+ gradient.addAttr({ name: "cy", value: "50%", prefix: "", local: "cy" });
612
+ }
613
+ if (!gradient.hasAttr("r")) {
614
+ gradient.addAttr({ name: "r", value: "50%", prefix: "", local: "r" });
615
+ }
616
+ }
617
+ if (gradient.elem === "sweepGradient") {
618
+ if (!gradient.hasAttr("cx")) {
619
+ gradient.addAttr({ name: "cx", value: "50%", prefix: "", local: "cx" });
620
+ }
621
+ if (!gradient.hasAttr("cy")) {
622
+ gradient.addAttr({ name: "cy", value: "50%", prefix: "", local: "cy" });
623
+ }
624
+ }
625
+ gradient.eachAttr((attr) => {
626
+ let positionAttrs = [
627
+ // SVG linearGradient
628
+ "x1",
629
+ "y1",
630
+ "x2",
631
+ "y2",
632
+ // SVG radialGradient, Android VectorDrawable not support 'fx' and 'fy'.
633
+ "cx",
634
+ "cy",
635
+ "r",
636
+ "fx",
637
+ "fy"
638
+ ];
639
+ if (positionAttrs.indexOf(attr.name) >= 0) {
640
+ if (!gradient.hasAttr("gradientUnits", "userSpaceOnUse")) {
641
+ let [x1, y1, x2, y2] = pathBounds(elem.attr("d").value);
642
+ if (this.isPercent(attr.value)) {
643
+ let valueFloat = parseFloat(attr.value) / 100;
644
+ if (attr.name === "x1" || attr.name === "x2" || attr.name === "cx" || attr.name === "fx") {
645
+ attr.value = x1 + (x2 - x1) * valueFloat;
646
+ }
647
+ if (attr.name === "y1" || attr.name === "y2" || attr.name === "cy" || attr.name === "fy") {
648
+ attr.value = y1 + (y2 - y1) * valueFloat;
649
+ }
650
+ if (attr.name === "r") {
651
+ attr.value = Math.max(x2 - x1, y2 - y1) * valueFloat;
652
+ }
653
+ } else {
654
+ if (attr.name === "x1" || attr.name === "x2" || attr.name === "cx" || attr.name === "fx") {
655
+ attr.value = x1 + attr.value;
656
+ }
657
+ if (attr.name === "y1" || attr.name === "y2" || attr.name === "cy" || attr.name === "fy") {
658
+ attr.value = y1 + attr.value;
659
+ }
660
+ }
661
+ } else {
662
+ if (this.isPercent(attr.value)) {
663
+ let valueFloat = parseFloat(attr.value) / 100;
664
+ if (attr.name === "x1" || attr.name === "x2" || attr.name === "cx" || attr.name === "fx") {
665
+ attr.value = this.viewportWidth * valueFloat;
666
+ }
667
+ if (attr.name === "y1" || attr.name === "y2" || attr.name === "cy" || attr.name === "fy") {
668
+ attr.value = this.viewportHeight * valueFloat;
669
+ }
670
+ if (attr.name === "r") {
671
+ attr.value = Math.max(this.viewportWidth, this.viewportHeight) * valueFloat;
672
+ }
673
+ }
674
+ }
675
+ attr.value = this.round(attr.value, floatPrecision);
676
+ }
677
+ }, this);
678
+ };
679
+ JS2XML.prototype.mergeColorAndOpacity = function(androidColorHex, opacity) {
680
+ let opacityFromAndroidColor = parseInt(androidColorHex.substr(1, 2), 16) / 255;
681
+ let opacityHex = Number(Math.round(opacity * opacityFromAndroidColor * 255)).toString(16).toUpperCase();
682
+ if (opacityHex.length === 1) {
683
+ opacityHex = "0" + opacityHex;
684
+ }
685
+ return "#" + opacityHex + androidColorHex.substr(-6);
686
+ };
687
+ JS2XML.prototype.isPercent = function(value) {
688
+ return /(-)?\d+(\.d+)?%$/.test(String(value));
689
+ };
690
+ JS2XML.prototype.round = function(value, floatPrecision) {
691
+ return Math.round(value * Math.pow(10, floatPrecision)) / Math.pow(10, floatPrecision);
692
+ };
693
+ JS2XML.prototype.convert = function(data, floatPrecision, strict, fillBlack, tint) {
694
+ this.refactorData(data, floatPrecision, fillBlack, tint);
695
+ return this.travelConvert(data, strict);
696
+ };
697
+ JS2XML.prototype.travelConvert = function(data, strict) {
698
+ let xml = "";
699
+ this.indentLevel++;
700
+ if (data.children) {
701
+ data.children.forEach((item) => {
702
+ if (this.vectordrawableTags.indexOf(item.name) >= 0) {
703
+ xml += this.createElement(item, strict);
704
+ } else if (strict) {
705
+ throw new Error("Unsupported element " + item.name);
706
+ }
707
+ }, this);
708
+ }
709
+ this.indentLevel--;
710
+ return xml;
711
+ };
712
+ JS2XML.prototype.createElement = function(data, strict) {
713
+ if (data.isEmpty()) {
714
+ return this.createIndent() + "<" + data.name + this.createAttrs(data, strict) + "/>\n";
715
+ } else {
716
+ let processedData = "";
717
+ processedData += this.travelConvert(data, strict);
718
+ return this.createIndent() + "<" + data.name + this.createAttrs(data, strict) + ">\n" + processedData + this.createIndent() + "</" + data.name + ">\n";
719
+ }
720
+ };
721
+ JS2XML.prototype.createAttrs = function(elem, strict) {
722
+ let attrs = "";
723
+ if (elem.name === "vector") {
724
+ attrs += ' xmlns:android="http://schemas.android.com/apk/res/android"';
725
+ }
726
+ elem.eachAttr(function(attr) {
727
+ if (attr.value !== void 0) {
728
+ if (this.vectordrawableAttrs.indexOf(attr.name) >= 0) {
729
+ if (["fillColor", "strokeColor", "color"].indexOf(attr.local) >= 0) {
730
+ attr.value = this.simplifyAndroidHexCode(attr.value);
731
+ }
732
+ if (elem.name === "aapt:attr" && attr.name === "name") {
733
+ attrs += " " + attr.name + '="' + attr.value + '"';
734
+ } else {
735
+ attrs += "\n" + this.createIndent() + " ".repeat(this.indent) + attr.name + '="' + attr.value + '"';
736
+ }
737
+ } else if (strict) {
738
+ throw new Error("Unsupported attribute " + attr.name);
739
+ }
740
+ }
741
+ }, this);
742
+ return attrs;
743
+ };
744
+ JS2XML.prototype.svgHexToAndroid = function(hexColor) {
745
+ const toByte = (value) => {
746
+ let numeric = value;
747
+ if (typeof numeric === "string" && numeric.trim().endsWith("%")) {
748
+ numeric = Math.round(parseFloat(numeric) * 2.55);
749
+ }
750
+ numeric = Math.min(255, Math.max(0, Math.round(Number(numeric))));
751
+ return numeric;
752
+ };
753
+ const rgbaToHex = (r, g, b, a = 1) => {
754
+ const alpha = Math.min(1, Math.max(0, Number(a)));
755
+ const rHex = toByte(r).toString(16).padStart(2, "0");
756
+ const gHex = toByte(g).toString(16).padStart(2, "0");
757
+ const bHex = toByte(b).toString(16).padStart(2, "0");
758
+ const aHex = Math.round(alpha * 255).toString(16).padStart(2, "0");
759
+ return `#${rHex}${gHex}${bHex}${aHex}`;
760
+ };
761
+ const hslToRgb = (h, s, l) => {
762
+ const hue = (Number(h) % 360 + 360) % 360 / 360;
763
+ const sat = Math.min(1, Math.max(0, s));
764
+ const light = Math.min(1, Math.max(0, l));
765
+ if (sat === 0) {
766
+ const grey = Math.round(light * 255);
767
+ return [grey, grey, grey];
768
+ }
769
+ const q = light < 0.5 ? light * (1 + sat) : light + sat - light * sat;
770
+ const p = 2 * light - q;
771
+ const hueToChannel = (t) => {
772
+ let temp = t;
773
+ if (temp < 0) temp += 1;
774
+ if (temp > 1) temp -= 1;
775
+ if (temp < 1 / 6) return p + (q - p) * 6 * temp;
776
+ if (temp < 1 / 2) return q;
777
+ if (temp < 2 / 3) return p + (q - p) * (2 / 3 - temp) * 6;
778
+ return p;
779
+ };
780
+ return [
781
+ Math.round(hueToChannel(hue + 1 / 3) * 255),
782
+ Math.round(hueToChannel(hue) * 255),
783
+ Math.round(hueToChannel(hue - 1 / 3) * 255)
784
+ ];
785
+ };
786
+ const keywordColors = {
787
+ black: "#000000FF",
788
+ white: "#FFFFFFFF",
789
+ red: "#FF0000FF",
790
+ green: "#008000FF",
791
+ blue: "#0000FFFF",
792
+ yellow: "#FFFF00FF",
793
+ cyan: "#00FFFFFF",
794
+ magenta: "#FF00FFFF",
795
+ gray: "#808080FF",
796
+ grey: "#808080FF",
797
+ orange: "#FFA500FF",
798
+ purple: "#800080FF",
799
+ pink: "#FFC0CBFF",
800
+ brown: "#A52A2AFF",
801
+ transparent: "#00000000"
802
+ };
803
+ const normalized = hexColor.trim();
804
+ if (/^#[0-9a-f]{8}$/i.test(normalized)) {
805
+ hexColor = "#" + normalized.substr(7, 2) + normalized.substr(1, 6);
806
+ } else if (/^#[0-9a-f]{4}$/i.test(normalized)) {
807
+ hexColor = "#" + normalized[4].repeat(2) + normalized[1].repeat(2) + normalized[2].repeat(2) + normalized[3].repeat(2);
808
+ } else if (/^#[0-9a-f]{6}$/i.test(normalized)) {
809
+ hexColor = "#FF" + normalized.substr(1, 6);
810
+ } else if (/^#[0-9a-f]{3}$/i.test(normalized)) {
811
+ hexColor = "#FF" + normalized[1].repeat(2) + normalized[2].repeat(2) + normalized[3].repeat(2);
812
+ } else if (/^rgba?\(/i.test(normalized)) {
813
+ const rgba = normalized.replace(/^rgba?\(/i, "").replace(/\)$/, "").split(",");
814
+ if (rgba.length >= 3) {
815
+ const alpha = rgba.length === 4 ? rgba[3] : 1;
816
+ hexColor = rgbaToHex(rgba[0], rgba[1], rgba[2], alpha);
817
+ } else {
818
+ hexColor = "#FF000000";
819
+ }
820
+ } else if (/^hsla?\(/i.test(normalized)) {
821
+ const hsla = normalized.replace(/^hsla?\(/i, "").replace(/\)$/, "").split(",");
822
+ if (hsla.length >= 3) {
823
+ const hue = parseFloat(hsla[0]);
824
+ const sat = hsla[1].trim().endsWith("%") ? parseFloat(hsla[1]) / 100 : parseFloat(hsla[1]);
825
+ const light = hsla[2].trim().endsWith("%") ? parseFloat(hsla[2]) / 100 : parseFloat(hsla[2]);
826
+ const alpha = hsla.length === 4 ? hsla[3] : 1;
827
+ const [r, g, b] = hslToRgb(hue, sat, light);
828
+ hexColor = rgbaToHex(r, g, b, alpha);
829
+ } else {
830
+ hexColor = "#FF000000";
831
+ }
832
+ } else if (keywordColors[normalized.toLowerCase()]) {
833
+ hexColor = keywordColors[normalized.toLowerCase()];
834
+ } else {
835
+ hexColor = "#FF000000";
836
+ }
837
+ return hexColor.toUpperCase();
838
+ };
839
+ JS2XML.prototype.simplifyAndroidHexCode = function(androidColorHex) {
840
+ if (/#FF[A-F0-9]{6}/.test(androidColorHex)) {
841
+ androidColorHex = "#" + androidColorHex.substr(-6);
842
+ }
843
+ let partOdd = androidColorHex.substr(1).split("").filter((item, index) => {
844
+ return index % 2 === 0;
845
+ }).join("");
846
+ let partEven = androidColorHex.substr(1).split("").filter((item, index) => {
847
+ return index % 2 === 1;
848
+ }).join("");
849
+ if (partOdd === partEven) {
850
+ androidColorHex = "#" + partOdd;
851
+ }
852
+ return androidColorHex;
853
+ };
854
+ JS2XML.prototype.createIndent = function() {
855
+ let indent = " ".repeat(this.indent);
856
+ indent = indent.repeat(this.indentLevel - 1);
857
+ return indent;
858
+ };
859
+ JS2XML.prototype.rectToPathData = function(x, y, width, height, rx, ry) {
860
+ let d = "";
861
+ if (rx === 0 && ry === 0) {
862
+ d = "M" + x + "," + y + "h" + width + "v" + height + "H" + x + "z";
863
+ } else {
864
+ d = "M" + x + "," + (y + ry) + "a" + rx + " -" + ry + " 0 0 1 " + rx + " -" + ry + "h" + (width - rx * 2) + "a" + rx + " " + ry + " 0 0 1 " + rx + " " + ry + "v" + (height - ry * 2) + "a-" + rx + " " + ry + " 0 0 1 -" + rx + " " + ry + "h-" + (width - rx * 2) + "a-" + rx + " -" + ry + " 0 0 1 -" + rx + " -" + ry + "z";
865
+ }
866
+ return d;
867
+ };
868
+ module2.exports = function(svgCode, options) {
869
+ let floatPrecision = 2;
870
+ let strict = false;
871
+ let fillBlack = false;
872
+ let xmlTag = false;
873
+ let tint;
874
+ if (options) {
875
+ if (options.floatPrecision) {
876
+ floatPrecision = options.floatPrecision;
877
+ }
878
+ if (options.strict) {
879
+ strict = options.strict;
880
+ }
881
+ if (options.fillBlack) {
882
+ fillBlack = options.fillBlack;
883
+ }
884
+ if (options.xmlTag) {
885
+ xmlTag = options.xmlTag;
886
+ }
887
+ if (options.tint) {
888
+ tint = options.tint;
889
+ }
890
+ }
891
+ return new Promise((resolve, reject) => {
892
+ let data = parseSvg(svgCode);
893
+ if (data.error) {
894
+ reject(data.error);
895
+ } else {
896
+ let xml = new JS2XML().convert(data, floatPrecision, strict, fillBlack, tint);
897
+ if (xmlTag) {
898
+ xml = '<?xml version="1.0" encoding="utf-8"?>\n' + xml;
899
+ }
900
+ resolve(xml);
901
+ }
902
+ });
903
+ };
904
+ }
905
+ });
906
+
907
+ // vendor/svg2vectordrawable/svgo-config.js
908
+ var require_svgo_config = __commonJS({
909
+ "vendor/svg2vectordrawable/svgo-config.js"(exports2, module2) {
910
+ module2.exports = function(floatPrecision = 2) {
911
+ const svgoConfig = {
912
+ info: {
913
+ input: "string"
914
+ },
915
+ plugins: [
916
+ {
917
+ name: "removeDoctype"
918
+ },
919
+ {
920
+ name: "removeXMLProcInst"
921
+ },
922
+ {
923
+ name: "removeComments"
924
+ },
925
+ {
926
+ name: "removeMetadata"
927
+ },
928
+ {
929
+ name: "removeEditorsNSData"
930
+ },
931
+ {
932
+ name: "cleanupAttrs"
933
+ },
934
+ {
935
+ name: "mergeStyles"
936
+ },
937
+ {
938
+ name: "inlineStyles",
939
+ params: { onlyMatchedOnce: false }
940
+ },
941
+ {
942
+ name: "minifyStyles"
943
+ },
944
+ {
945
+ name: "cleanupIDs",
946
+ active: false
947
+ },
948
+ {
949
+ name: "removeUselessDefs"
950
+ },
951
+ {
952
+ name: "cleanupNumericValues",
953
+ params: { floatPrecision, leadingZero: false }
954
+ },
955
+ {
956
+ name: "convertColors",
957
+ params: { shorthex: false, shortname: false }
958
+ },
959
+ {
960
+ name: "removeUnknownsAndDefaults",
961
+ params: { unknownContent: false, unknownAttrs: false }
962
+ },
963
+ {
964
+ name: "removeNonInheritableGroupAttrs"
965
+ },
966
+ {
967
+ name: "removeUselessStrokeAndFill"
968
+ },
969
+ {
970
+ name: "removeViewBox",
971
+ active: false
972
+ },
973
+ {
974
+ name: "cleanupEnableBackground"
975
+ },
976
+ {
977
+ name: "removeHiddenElems"
978
+ },
979
+ {
980
+ name: "removeEmptyText"
981
+ },
982
+ {
983
+ name: "convertShapeToPath",
984
+ params: { convertArcs: true, floatPrecision }
985
+ },
986
+ {
987
+ name: "convertEllipseToCircle"
988
+ },
989
+ {
990
+ name: "moveElemsAttrsToGroup",
991
+ active: false
992
+ },
993
+ {
994
+ name: "moveGroupAttrsToElems"
995
+ },
996
+ {
997
+ name: "collapseGroups"
998
+ },
999
+ {
1000
+ name: "convertPathData",
1001
+ params: { floatPrecision, transformPrecision: floatPrecision, leadingZero: false, makeArcs: false, noSpaceAfterFlags: false, collapseRepeated: false }
1002
+ },
1003
+ {
1004
+ name: "convertTransform"
1005
+ },
1006
+ {
1007
+ name: "removeEmptyAttrs"
1008
+ },
1009
+ {
1010
+ name: "removeEmptyContainers"
1011
+ },
1012
+ {
1013
+ name: "mergePaths",
1014
+ active: false
1015
+ },
1016
+ {
1017
+ name: "removeUnusedNS"
1018
+ },
1019
+ {
1020
+ name: "sortDefsChildren"
1021
+ },
1022
+ {
1023
+ name: "removeTitle"
1024
+ },
1025
+ {
1026
+ name: "removeDesc"
1027
+ },
1028
+ {
1029
+ name: "removeXMLNS",
1030
+ active: false
1031
+ },
1032
+ {
1033
+ name: "removeRasterImages"
1034
+ },
1035
+ {
1036
+ name: "cleanupListOfValues",
1037
+ params: { floatPrecision, leadingZero: false }
1038
+ },
1039
+ {
1040
+ name: "sortAttrs",
1041
+ active: false
1042
+ },
1043
+ {
1044
+ name: "convertStyleToAttrs",
1045
+ active: false
1046
+ },
1047
+ {
1048
+ name: "prefixIds",
1049
+ active: false
1050
+ },
1051
+ {
1052
+ name: "removeDimensions",
1053
+ active: false
1054
+ },
1055
+ {
1056
+ name: "removeAttrs",
1057
+ active: false
1058
+ },
1059
+ {
1060
+ name: "removeAttributesBySelector",
1061
+ active: false
1062
+ },
1063
+ {
1064
+ name: "removeElementsByAttr",
1065
+ active: false
1066
+ },
1067
+ {
1068
+ name: "addClassesToSVGElement",
1069
+ active: false
1070
+ },
1071
+ {
1072
+ name: "removeStyleElement",
1073
+ active: false
1074
+ },
1075
+ {
1076
+ name: "removeScriptElement",
1077
+ active: false
1078
+ },
1079
+ {
1080
+ name: "addAttributesToSVGElement",
1081
+ active: false
1082
+ },
1083
+ {
1084
+ name: "removeOffCanvasPaths",
1085
+ active: false
1086
+ },
1087
+ {
1088
+ name: "reusePaths",
1089
+ active: false
1090
+ }
1091
+ ]
1092
+ };
1093
+ return svgoConfig;
1094
+ };
1095
+ }
1096
+ });
1097
+
1098
+ // vendor/svg2vectordrawable/main.node.js
1099
+ var require_main_node = __commonJS({
1100
+ "vendor/svg2vectordrawable/main.node.js"(exports2, module2) {
1101
+ var { optimize } = require("svgo");
1102
+ var svg2vectordrawable = require_svg_to_vectordrawable();
1103
+ var svgoConfig = require_svgo_config();
1104
+ module2.exports = function(svgCode, options) {
1105
+ let floatPrecision = options ? options.floatPrecision : 2;
1106
+ const result = optimize(svgCode, svgoConfig(floatPrecision));
1107
+ svgCode = result.data;
1108
+ return svg2vectordrawable(svgCode, options);
1109
+ };
1110
+ }
1111
+ });
1112
+
1113
+ // vendor/svg2vectordrawable/index.js
1114
+ var require_svg2vectordrawable = __commonJS({
1115
+ "vendor/svg2vectordrawable/index.js"(exports2, module2) {
1116
+ module2.exports = require_main_node();
1117
+ }
1118
+ });
1119
+
1120
+ // src/tools/svgTool.js
1121
+ var require_svgTool = __commonJS({
1122
+ "src/tools/svgTool.js"(exports2, module2) {
1123
+ var fs = require("fs/promises");
1124
+ var path = require("path");
1125
+ var { createHash } = require("crypto");
1126
+ var z = require("zod/v4");
1127
+ var svg2vectordrawable = require_svg2vectordrawable();
1128
+ var svgToolInstructions2 = [
1129
+ "Use this server to convert SVG into Android VectorDrawable XML (fast, cached).",
1130
+ "Call tool convert-svg-to-android-drawable for any SVG\u2192VectorDrawable conversion or option tuning.",
1131
+ "Prefer inline SVG via svg; if using svgPath, pass absolute or caller-provided paths only\u2014do not invent paths.",
1132
+ "Set outputPath only if a file should be written; otherwise XML is returned inline.",
1133
+ "Defaults: floatPrecision=2, fillBlack=false, xmlTag=false, cache=true.",
1134
+ "Use fillBlack=true only when the SVG lacks fill and black is desired; set tint only if the caller explicitly requests a tint color.",
1135
+ "Do not alter caller SVG content beyond conversion; keep inputs as provided."
1136
+ ].join("\n");
1137
+ var convertInputSchema = z.object({
1138
+ svg: z.string().min(1).describe("Inline SVG markup to convert").optional(),
1139
+ svgPath: z.string().min(1).describe("Path to an SVG file to read").optional(),
1140
+ outputPath: z.string().min(1).describe("Optional output path for generated VectorDrawable XML").optional(),
1141
+ floatPrecision: z.number().int().min(0).max(6).default(2).describe("Decimal precision when serializing coordinates"),
1142
+ fillBlack: z.boolean().default(false).describe("Force fill color black when missing"),
1143
+ xmlTag: z.boolean().default(false).describe("Include XML declaration"),
1144
+ tint: z.string().min(1).optional().describe("Android tint color (e.g. #FF000000)"),
1145
+ cache: z.boolean().default(true).describe("Reuse cached result for identical inputs within this process")
1146
+ }).refine((data) => data.svg || data.svgPath, { message: "Provide either svg or svgPath" });
1147
+ var conversionCache = /* @__PURE__ */ new Map();
1148
+ var MAX_CACHE_SIZE = 32;
1149
+ function makeCacheKey(svg, options) {
1150
+ const hash = createHash("sha256");
1151
+ hash.update(svg);
1152
+ hash.update(JSON.stringify(options));
1153
+ return hash.digest("hex");
1154
+ }
1155
+ function getCached(key) {
1156
+ const existing = conversionCache.get(key);
1157
+ if (!existing) return null;
1158
+ conversionCache.delete(key);
1159
+ conversionCache.set(key, existing);
1160
+ return existing;
1161
+ }
1162
+ function setCache(key, value) {
1163
+ if (conversionCache.size >= MAX_CACHE_SIZE) {
1164
+ const oldestKey = conversionCache.keys().next().value;
1165
+ if (oldestKey) {
1166
+ conversionCache.delete(oldestKey);
1167
+ }
1168
+ }
1169
+ conversionCache.set(key, value);
1170
+ }
1171
+ async function loadSvg(params) {
1172
+ if (params.svg) return params.svg;
1173
+ const resolvedPath = path.resolve(params.svgPath);
1174
+ return fs.readFile(resolvedPath, "utf8");
1175
+ }
1176
+ async function maybeWriteOutput(outputPath, xml) {
1177
+ if (!outputPath) return null;
1178
+ const resolvedPath = path.resolve(outputPath);
1179
+ await fs.mkdir(path.dirname(resolvedPath), { recursive: true });
1180
+ await fs.writeFile(resolvedPath, xml, "utf8");
1181
+ return resolvedPath;
1182
+ }
1183
+ function registerSvgTool2(server2) {
1184
+ server2.registerTool(
1185
+ "convert-svg-to-android-drawable",
1186
+ {
1187
+ title: "SVG to VectorDrawable",
1188
+ description: "Convert SVG markup or files into Android VectorDrawable XML quickly, optionally writing to disk.",
1189
+ inputSchema: convertInputSchema
1190
+ },
1191
+ async (params, extra) => {
1192
+ const svgCode = await loadSvg(params);
1193
+ const options = {
1194
+ floatPrecision: params.floatPrecision,
1195
+ fillBlack: params.fillBlack,
1196
+ xmlTag: params.xmlTag,
1197
+ tint: params.tint
1198
+ };
1199
+ const cacheKey = makeCacheKey(svgCode, options);
1200
+ const startTime = process.hrtime.bigint();
1201
+ let xml = null;
1202
+ if (params.cache) {
1203
+ xml = getCached(cacheKey);
1204
+ }
1205
+ if (!xml) {
1206
+ xml = await svg2vectordrawable(svgCode, options);
1207
+ if (!xml || typeof xml !== "string") {
1208
+ throw new Error("Conversion did not produce XML");
1209
+ }
1210
+ setCache(cacheKey, xml);
1211
+ }
1212
+ const savedPath = await maybeWriteOutput(params.outputPath, xml);
1213
+ const elapsedMs = Number(process.hrtime.bigint() - startTime) / 1e6;
1214
+ if (extra && typeof extra.sessionId === "string") {
1215
+ server2.sendLoggingMessage(
1216
+ {
1217
+ level: "info",
1218
+ data: `Converted SVG in ${elapsedMs.toFixed(2)}ms` + (savedPath ? ` (saved to ${savedPath})` : "")
1219
+ },
1220
+ extra.sessionId
1221
+ ).catch(() => {
1222
+ });
1223
+ }
1224
+ const content = [];
1225
+ if (savedPath) {
1226
+ content.push({ type: "text", text: `Saved VectorDrawable to ${savedPath}` });
1227
+ }
1228
+ content.push({ type: "text", text: xml });
1229
+ return { content };
1230
+ }
1231
+ );
1232
+ }
1233
+ module2.exports = {
1234
+ registerSvgTool: registerSvgTool2,
1235
+ svgToolInstructions: svgToolInstructions2
1236
+ };
1237
+ }
1238
+ });
1239
+
1240
+ // src/tools/logcatTool.js
1241
+ var require_logcatTool = __commonJS({
1242
+ "src/tools/logcatTool.js"(exports2, module2) {
1243
+ var { execFile } = require("child_process");
1244
+ var { promisify } = require("util");
1245
+ var z = require("zod/v4");
1246
+ var execFileAsync = promisify(execFile);
1247
+ var logcatToolInstructions2 = [
1248
+ "Use read-adb-logcat to tail device logs for a package, pid, or tag; default tail=200 lines.",
1249
+ "Use get-pid-by-package to resolve pid quickly via adb shell pidof -s.",
1250
+ "Use get-current-activity to inspect current focus (Activity/Window) via dumpsys window.",
1251
+ "Use fetch-crash-stacktrace to pull the latest crash buffer (-b crash) optionally filtered by pid.",
1252
+ "Use check-anr-state to inspect ActivityManager ANR logs and /data/anr/traces.txt (best-effort).",
1253
+ "Use clear-logcat-buffer to reset logcat (-c) before running new scenarios."
1254
+ ].join("\n");
1255
+ var logcatInputSchema = z.object({
1256
+ packageName: z.string().min(1).describe("Android package name; resolves pid via adb shell pidof").optional(),
1257
+ pid: z.string().min(1).describe("Explicit process id for logcat --pid").optional(),
1258
+ tag: z.string().min(1).describe("Logcat tag to include (uses -s tag)").optional(),
1259
+ priority: z.enum(["V", "D", "I", "W", "E", "F", "S"]).default("V").describe("Minimum priority when tag is provided (e.g., D for debug)"),
1260
+ maxLines: z.number().int().min(1).max(2e3).default(200).describe("Tail line count via logcat -t"),
1261
+ timeoutMs: z.number().int().min(1e3).max(15e3).default(5e3).describe("Timeout per adb call in milliseconds")
1262
+ }).refine((data) => data.packageName || data.pid || data.tag, {
1263
+ message: "Provide packageName, pid, or tag to avoid unfiltered logs"
1264
+ });
1265
+ var pidInputSchema = z.object({
1266
+ packageName: z.string().min(1).describe("Android package name to resolve pid via adb shell pidof -s"),
1267
+ timeoutMs: z.number().int().min(1e3).max(15e3).default(5e3).describe("Timeout per adb call in milliseconds")
1268
+ });
1269
+ var currentActivityInputSchema = z.object({
1270
+ timeoutMs: z.number().int().min(1e3).max(15e3).default(5e3).describe("Timeout per adb call in milliseconds")
1271
+ });
1272
+ var crashStackInputSchema = z.object({
1273
+ packageName: z.string().min(1).describe("Optional package to resolve pid; filters crash buffer with --pid").optional(),
1274
+ maxLines: z.number().int().min(50).max(2e3).default(400).describe("Tail line count from crash buffer (-b crash -t)"),
1275
+ timeoutMs: z.number().int().min(1e3).max(15e3).default(5e3).describe("Timeout per adb call in milliseconds")
1276
+ });
1277
+ var anrStateInputSchema = z.object({
1278
+ maxLines: z.number().int().min(50).max(2e3).default(400).describe("Tail line count from ActivityManager:E"),
1279
+ timeoutMs: z.number().int().min(1e3).max(15e3).default(5e3).describe("Timeout per adb call in milliseconds")
1280
+ });
1281
+ var clearLogcatInputSchema = z.object({
1282
+ timeoutMs: z.number().int().min(1e3).max(15e3).default(5e3).describe("Timeout per adb call in milliseconds")
1283
+ });
1284
+ async function runAdbCommand(args, timeoutMs) {
1285
+ try {
1286
+ const { stdout } = await execFileAsync("adb", args, {
1287
+ timeout: timeoutMs,
1288
+ maxBuffer: 5 * 1024 * 1024
1289
+ });
1290
+ return stdout.trimEnd();
1291
+ } catch (error) {
1292
+ const stderr = error && typeof error.stderr === "string" ? error.stderr.trim() : "";
1293
+ const message = [`adb ${args.join(" ")} failed`, error.message].filter(Boolean).join(": ");
1294
+ if (stderr) {
1295
+ throw new Error(`${message} | stderr: ${stderr}`);
1296
+ }
1297
+ throw new Error(message);
1298
+ }
1299
+ }
1300
+ async function resolvePid(packageName, timeoutMs) {
1301
+ const output = await runAdbCommand(["shell", "pidof", "-s", packageName], timeoutMs);
1302
+ const pid = output.split(/\s+/).find(Boolean);
1303
+ if (!pid) {
1304
+ throw new Error(`Could not resolve pid for package ${packageName}`);
1305
+ }
1306
+ return pid;
1307
+ }
1308
+ function buildLogcatArgs(params, pid) {
1309
+ const args = ["logcat", "-d", "-t", String(params.maxLines)];
1310
+ if (pid) {
1311
+ args.push(`--pid=${pid}`);
1312
+ }
1313
+ if (params.tag) {
1314
+ const filterSpec = `${params.tag}:${params.priority}`;
1315
+ args.push("-s", filterSpec);
1316
+ }
1317
+ return args;
1318
+ }
1319
+ function registerLogcatTool2(server2) {
1320
+ server2.registerTool(
1321
+ "read-adb-logcat",
1322
+ {
1323
+ title: "Read adb logcat",
1324
+ description: "Dump recent adb logcat output scoped by package, pid, or tag with tail and timeout controls.",
1325
+ inputSchema: logcatInputSchema
1326
+ },
1327
+ async (params, extra) => {
1328
+ const timeoutMs = params.timeoutMs;
1329
+ const pid = params.pid || (params.packageName ? await resolvePid(params.packageName, timeoutMs) : null);
1330
+ const args = buildLogcatArgs(params, pid);
1331
+ const startTime = process.hrtime.bigint();
1332
+ const output = await runAdbCommand(args, timeoutMs);
1333
+ const elapsedMs = Number(process.hrtime.bigint() - startTime) / 1e6;
1334
+ if (extra && typeof extra.sessionId === "string") {
1335
+ server2.sendLoggingMessage(
1336
+ {
1337
+ level: "info",
1338
+ data: `Read logcat (${params.maxLines} lines` + (pid ? `, pid=${pid}` : "") + (params.tag ? `, tag=${params.tag}:${params.priority}` : "") + `) in ${elapsedMs.toFixed(2)}ms`
1339
+ },
1340
+ extra.sessionId
1341
+ ).catch(() => {
1342
+ });
1343
+ }
1344
+ if (!output) {
1345
+ return { content: [{ type: "text", text: "Logcat returned no lines." }] };
1346
+ }
1347
+ return { content: [{ type: "text", text: output }] };
1348
+ }
1349
+ );
1350
+ server2.registerTool(
1351
+ "get-pid-by-package",
1352
+ {
1353
+ title: "Get pid by package",
1354
+ description: "Resolve process id for a package via adb shell pidof -s.",
1355
+ inputSchema: pidInputSchema
1356
+ },
1357
+ async (params) => {
1358
+ const pid = await resolvePid(params.packageName, params.timeoutMs);
1359
+ return { content: [{ type: "text", text: pid }] };
1360
+ }
1361
+ );
1362
+ server2.registerTool(
1363
+ "get-current-activity",
1364
+ {
1365
+ title: "Get current activity/window focus",
1366
+ description: "Inspect current focused app/window via dumpsys window (mCurrentFocus/mFocusedApp). Useful even in single-activity apps to verify top window.",
1367
+ inputSchema: currentActivityInputSchema
1368
+ },
1369
+ async (params) => {
1370
+ const dump = await runAdbCommand(["shell", "dumpsys", "window"], params.timeoutMs);
1371
+ const lines = dump.split("\n").filter((line) => line.includes("mCurrentFocus") || line.includes("mFocusedApp"));
1372
+ const trimmed = lines.slice(0, 8).join("\n").trim();
1373
+ if (!trimmed) {
1374
+ return { content: [{ type: "text", text: "No focus info found in dumpsys window." }] };
1375
+ }
1376
+ return { content: [{ type: "text", text: trimmed }] };
1377
+ }
1378
+ );
1379
+ server2.registerTool(
1380
+ "fetch-crash-stacktrace",
1381
+ {
1382
+ title: "Fetch crash stacktrace (crash buffer)",
1383
+ description: "Pull recent crash buffer (-b crash -d -t) optionally filtered by pid resolved from package.",
1384
+ inputSchema: crashStackInputSchema
1385
+ },
1386
+ async (params) => {
1387
+ const pid = params.packageName ? await resolvePid(params.packageName, params.timeoutMs) : null;
1388
+ const args = ["logcat", "-b", "crash", "-d", "-t", String(params.maxLines)];
1389
+ if (pid) {
1390
+ args.push(`--pid=${pid}`);
1391
+ }
1392
+ const output = await runAdbCommand(args, params.timeoutMs);
1393
+ if (!output) {
1394
+ return { content: [{ type: "text", text: "No crash entries found." }] };
1395
+ }
1396
+ return { content: [{ type: "text", text: output }] };
1397
+ }
1398
+ );
1399
+ server2.registerTool(
1400
+ "check-anr-state",
1401
+ {
1402
+ title: "Check ANR state (ActivityManager + traces)",
1403
+ description: "Check recent ActivityManager ANR logs and tail /data/anr/traces.txt when accessible (best-effort, may require root/debuggable).",
1404
+ inputSchema: anrStateInputSchema
1405
+ },
1406
+ async (params) => {
1407
+ const sections = [];
1408
+ try {
1409
+ const amLogs = await runAdbCommand(
1410
+ ["logcat", "-d", "-t", String(params.maxLines), "ActivityManager:E", "*:S"],
1411
+ params.timeoutMs
1412
+ );
1413
+ if (amLogs) {
1414
+ sections.push("ActivityManager (recent):\n" + amLogs);
1415
+ } else {
1416
+ sections.push("ActivityManager (recent): no entries.");
1417
+ }
1418
+ } catch (error) {
1419
+ sections.push(`ActivityManager: ${error.message}`);
1420
+ }
1421
+ try {
1422
+ const stat = await runAdbCommand(["shell", "ls", "-l", "/data/anr/traces.txt"], params.timeoutMs);
1423
+ sections.push("traces.txt stat:\n" + stat);
1424
+ } catch (error) {
1425
+ sections.push(`traces.txt stat: ${error.message}`);
1426
+ }
1427
+ try {
1428
+ const tail = await runAdbCommand(
1429
+ ["shell", "tail", "-n", "200", "/data/anr/traces.txt"],
1430
+ params.timeoutMs
1431
+ );
1432
+ if (tail) {
1433
+ sections.push("traces.txt tail (200 lines):\n" + tail);
1434
+ } else {
1435
+ sections.push("traces.txt tail: empty.");
1436
+ }
1437
+ } catch (error) {
1438
+ sections.push(`traces.txt tail: ${error.message}`);
1439
+ }
1440
+ return { content: [{ type: "text", text: sections.join("\n\n") }] };
1441
+ }
1442
+ );
1443
+ server2.registerTool(
1444
+ "clear-logcat-buffer",
1445
+ {
1446
+ title: "Clear logcat buffer",
1447
+ description: "Run adb logcat -c to clear buffers before a new scenario.",
1448
+ inputSchema: clearLogcatInputSchema
1449
+ },
1450
+ async (params) => {
1451
+ await runAdbCommand(["logcat", "-c"], params.timeoutMs);
1452
+ return { content: [{ type: "text", text: "Cleared logcat buffers." }] };
1453
+ }
1454
+ );
1455
+ }
1456
+ module2.exports = {
1457
+ registerLogcatTool: registerLogcatTool2,
1458
+ logcatToolInstructions: logcatToolInstructions2
1459
+ };
1460
+ }
1461
+ });
1462
+
1463
+ // src/index.js
1464
+ var { McpServer } = require("@modelcontextprotocol/sdk/server/mcp.js");
1465
+ var { StdioServerTransport } = require("@modelcontextprotocol/sdk/server/stdio.js");
1466
+ var { registerSvgTool, svgToolInstructions } = require_svgTool();
1467
+ var { registerLogcatTool, logcatToolInstructions } = require_logcatTool();
1468
+ var serverInstructions = [svgToolInstructions, logcatToolInstructions].join("\n");
1469
+ var server = new McpServer(
1470
+ {
1471
+ name: "svg-to-android-drawable",
1472
+ version: "1.1.0"
1473
+ },
1474
+ {
1475
+ capabilities: { logging: {} },
1476
+ instructions: serverInstructions
1477
+ }
1478
+ );
1479
+ registerSvgTool(server);
1480
+ registerLogcatTool(server);
1481
+ async function main() {
1482
+ const transport = new StdioServerTransport();
1483
+ await server.connect(transport);
1484
+ process.stdin.resume();
1485
+ }
1486
+ main().catch((error) => {
1487
+ console.error("Failed to start MCP server:", error);
1488
+ process.exit(1);
1489
+ });