Skip to content

Commit fa679d1

Browse files
committed
Implemented linear and radial gradients for fill and stroke colors.
Fixed radial gradients. Enable transforms for gradients. Radial transforms not fully supported. Not sure how to deal with scale for them. Parse the transforms as floats Update main.js Fix item tag generation and substituteUseRef This should be a return Parse the style attribute on the stop tag. Adobe likes making stop-color and stop-opacity attributes. Looks like Inkscape use stye tags. Typo with an extra parentheses Fix the extra tag close Bump version number So it doesn't load an old version from the browser cache.
1 parent b0c81db commit fa679d1

2 files changed

Lines changed: 157 additions & 16 deletions

File tree

index.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -155,4 +155,4 @@ <h4 class="modal-title">Multiple SVG files (<span id="files-count"></span> files
155155
</script>
156156

157157
</body>
158-
</html>
158+
</html>

js/main.js

Lines changed: 156 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -148,6 +148,7 @@ var lastFileName = "";
148148
var lastFileData;
149149
var warnings = [];
150150
var svgStyles = {};
151+
var gradients = {};
151152
var globalSvg;
152153

153154
var clipPathEnabled = false;
@@ -235,6 +236,10 @@ function recursiveTreeWalk(parent, groupLevel, clipPath) {
235236
}
236237

237238
function preprocessReferences(svg) {
239+
svg.find("defs").children().each(function () {
240+
var current = $(this);
241+
substituteUseRef(svg, current);
242+
});
238243
svg.find("use").each(function () {
239244
var current = $(this);
240245
substituteUseRef(svg, current);
@@ -272,14 +277,20 @@ function substituteUseRef(parent, current) {
272277
//Find definition in svg
273278
var defs = $(parent).find("[id='" + href.substr(1) + "']");
274279
if (defs.length) {
275-
defs = defs.clone();
276-
277-
//Copy overriding attributes into children
278-
$.each(current.prop("attributes"), function () {
279-
defs.attr(this.name, this.value);
280+
//Copy non-overridden attributes
281+
$.each(defs.prop("attributes"), function () {
282+
var attr = current.attr(this.name)
283+
if (typeof attr !== typeof undefined && attr !== false) {
284+
return;
285+
}
286+
current.attr(this.name, this.value);
280287
});
281288

282-
current.replaceWith(defs);
289+
//Copy children
290+
defs.children().each(function () {
291+
var child = $(this);
292+
current.append(child.clone());
293+
});
283294
} else {
284295
console.warn("Found <use> tag but did not found appropriate block in <defs> for id " + href);
285296
}
@@ -289,6 +300,10 @@ function substituteUseRef(parent, current) {
289300

290301
function parseGroup(groupTag) {
291302
var transform = groupTag.attr("transform");
303+
if (typeof transform === "undefined") {
304+
transform = groupTag.attr("gradientTransform");
305+
}
306+
292307
var id = groupTag.attr("id");
293308
var groupTransform = {transformX: 0, transformY: 0, scaleX: 1, scaleY: 1,
294309
rotate:0, rotatePivotX:-1, rotatePivotY:-1, id:"", isSet:false, clipPathId:null};
@@ -520,11 +535,11 @@ function printPath(pathData, stylesArray, groupLevel, clipPath) {
520535
}
521536

522537
//If strokeWidth is needed but omitted, default to 1
523-
var needsStrokeWidth = (typeof styles["stroke"] !== "undefined") ||
524-
(typeof styles["stroke-opacity"] !== "undefined") ||
525-
(typeof styles["stroke-alpha"] !== "undefined") ||
526-
(typeof styles["stroke-linejoin"] !== "undefined") ||
527-
(typeof styles["stroke-miterlimit"] !== "undefined") ||
538+
var needsStrokeWidth = (typeof styles["stroke"] !== "undefined") ||
539+
(typeof styles["stroke-opacity"] !== "undefined") ||
540+
(typeof styles["stroke-alpha"] !== "undefined") ||
541+
(typeof styles["stroke-linejoin"] !== "undefined") ||
542+
(typeof styles["stroke-miterlimit"] !== "undefined") ||
528543
(typeof styles["stroke-linecap"] !== "undefined");
529544
if (needsStrokeWidth && (typeof styles["stroke-width"] === "undefined")) {
530545
styles["stroke-width"] = "1";
@@ -534,16 +549,37 @@ function printPath(pathData, stylesArray, groupLevel, clipPath) {
534549
if (!clipPath) {
535550
generatedOutput += INDENT.repeat(groupLevel + 1) + '<path\n';
536551
if (toBool(localStorage.useIdAsName)) generatedOutput += generateAttr('name', styles["id"], groupLevel, "");
537-
generatedOutput += generateAttr('fillColor', parseColorToHex(styles["fill"]), groupLevel, "none");
552+
var gradientID = null;
553+
var gradientAttr = null;
554+
if ((typeof styles["fill"] !== "undefined") && styles["fill"].startsWith("url(")) {
555+
gradientID = styles["fill"].slice(5, -1);
556+
gradientAttr = "fillColor";
557+
} else {
558+
generatedOutput += generateAttr('fillColor', parseColorToHex(styles["fill"]), groupLevel, "none");
559+
}
538560
generatedOutput += generateAttr('fillAlpha', styles["fill-opacity"], groupLevel, "1");
539561
generatedOutput += generateAttr('fillType', styles["fill-rule"], groupLevel, "nonZero");
540-
generatedOutput += generateAttr('strokeColor', parseColorToHex(styles["stroke"]), groupLevel, "none");
562+
if ((typeof styles["stroke"] !== "undefined") && styles["stroke"].startsWith("url(")) {
563+
gradientID = styles["stroke"].slice(5, -1);
564+
gradientAttr = "strokeColor";
565+
} else {
566+
generatedOutput += generateAttr('strokeColor', parseColorToHex(styles["stroke"]), groupLevel, "none");
567+
}
541568
generatedOutput += generateAttr('strokeAlpha', styles["stroke-opacity"], groupLevel, "1");
542569
generatedOutput += generateAttr('strokeWidth', removeNonNumeric(styles["stroke-width"]), groupLevel, "0");
543570
generatedOutput += generateAttr('strokeLineJoin', styles["stroke-linejoin"], groupLevel, "miter");
544571
generatedOutput += generateAttr('strokeMiterLimit', styles["stroke-miterlimit"], groupLevel, "4");
545572
generatedOutput += generateAttr('strokeLineCap', styles["stroke-linecap"], groupLevel, "butt");
546-
generatedOutput += generateAttr('pathData', pathData, groupLevel, null, true);
573+
if (gradientID !== null) {
574+
generatedOutput += generateAttr('pathData', pathData, groupLevel, null);
575+
generatedOutput += '>\n';
576+
generatedOutput += INDENT.repeat(groupLevel + 2) + '<aapt:attr name="android:' + gradientAttr + '">\n';
577+
generatedOutput += gradients[gradientID] + '\n';
578+
generatedOutput += INDENT.repeat(groupLevel + 2) + '</aapt:attr>\n';
579+
generatedOutput += INDENT.repeat(groupLevel + 1) + '</path>\n';
580+
} else {
581+
generatedOutput += generateAttr('pathData', pathData, groupLevel, null, true);
582+
}
547583
pathsParsedCount++;
548584
} else {
549585
clipPathMerged.push(pathData);
@@ -555,6 +591,104 @@ function printPath(pathData, stylesArray, groupLevel, clipPath) {
555591
}
556592
}
557593

594+
function parseGradients(svg) {
595+
svg.find("defs").children().each(function () {
596+
var current = $(this);
597+
if (current.prop("tagName").endsWith("Gradient")) {
598+
var transform = parseGroup(current);
599+
if (typeof transform.transformX === "string") {
600+
transform.transformX = parseFloat(transform.transformX);
601+
}
602+
if (typeof transform.transformY === "string") {
603+
transform.transformX = parseFloat(transform.transformX);
604+
}
605+
if (typeof transform.scaleX === "string") {
606+
transform.scaleX = parseFloat(transform.scaleX);
607+
}
608+
if (typeof transform.scaleY === "string") {
609+
transform.scaleY = parseFloat(transform.scaleY);
610+
}
611+
612+
var androidXML = '<gradient\n';
613+
androidXML += generateAttr('angle', transform.rotate, 1, 0);
614+
if (current.prop("tagName").startsWith("linear")) {
615+
var x1 = 0.0;
616+
if (typeof current.attr("x1") !== "undefined") {
617+
x1 = parseFloat(current.attr("x1")) + transform.transformX;
618+
}
619+
var y1 = 0.0;
620+
if (typeof current.attr("y1") !== "undefined") {
621+
y1 = parseFloat(current.attr("y1")) + transform.transformY;
622+
}
623+
var x2 = 0.0;
624+
if (typeof current.attr("x2") !== "undefined") {
625+
x2 = parseFloat(current.attr("x2")) + transform.transformX;
626+
}
627+
var y2 = 0.0;
628+
if (typeof current.attr("y2") !== "undefined") {
629+
y2 = parseFloat(current.attr("y2")) + transform.transformY;
630+
}
631+
632+
var width = Math.abs(x2 - x1);
633+
var height = Math.abs(y2 - y1);
634+
var newWidth = width * transform.scaleX;
635+
var newHeight = height * transform.scaleY;
636+
var dX = (width - newWidth) / 2;
637+
var dY = (height - newHeight) / 2;
638+
if (x1 < x2) {
639+
x1 += dX;
640+
x2 -= dX;
641+
} else {
642+
x1 -= dX;
643+
x2 += dX;
644+
}
645+
if (y1 < y2) {
646+
y1 += dY;
647+
y2 -= dY;
648+
} else {
649+
y1 -= dY;
650+
y2 += dY;
651+
}
652+
653+
androidXML += generateAttr('startX', x1, 1, 0.0);
654+
androidXML += generateAttr('startY', y1, 1, 0.0);
655+
androidXML += generateAttr('endX', x2, 1, 0.0);
656+
androidXML += generateAttr('endY', y2, 1, 0.0);
657+
androidXML += INDENT.repeat(1) + 'android:type="linear">';
658+
} else if (current.prop("tagName").startsWith("radial")) {
659+
androidXML += generateAttr('centerX', parseFloat(current.attr("cx")) + transform.transformX, 1, 0.0);
660+
androidXML += generateAttr('centerY', parseFloat(current.attr("cy")) + transform.transformY, 1, 0.0);
661+
androidXML += generateAttr('gradientRadius', current.attr("r"), 1, "0");
662+
androidXML += INDENT.repeat(1) + 'android:type="radial">';
663+
}
664+
current.children().each(function () {
665+
var stopTag = $(this);
666+
var stopStyles = parseStyles(stopTag);
667+
if (typeof stopTag.attr("stop-color") !== "undefined") {
668+
stopStyles["stop-color"] = stopTag.attr("stop-color");
669+
}
670+
if (typeof stopTag.attr("stop-opacity") !== "undefined") {
671+
stopStyles["stop-opacity"] = stopTag.attr("stop-opacity");
672+
}
673+
androidXML += INDENT.repeat(2) + '<item\n';
674+
androidXML += generateAttr('offset', stopTag.attr("offset"), 3, null);
675+
if (typeof stopStyles["stop-opacity"] !== "undefined") {
676+
var alpha = ("0" + Math.round(parseFloat(stopStyles["stop-opacity"]) * 255).toString(16)).slice(-2)
677+
var color = parseColorToHex(stopStyles["stop-color"]).slice(-6)
678+
679+
androidXML += generateAttr('color', "#"+alpha+color, 3, null, true);
680+
} else if (typeof stopStyles["stop-color"] !== "undefined") {
681+
androidXML += generateAttr('color', parseColorToHex(stopStyles["stop-color"]), 3, null, true);
682+
} else {
683+
androidXML += " />";
684+
}
685+
});
686+
androidXML += '</gradient>';
687+
gradients[current.attr("id")] = androidXML;
688+
}
689+
});
690+
}
691+
558692
function generateCode(inputXml) {
559693
var resultData = { error:null, warnings:null, code:null };
560694

@@ -574,6 +708,7 @@ function generateCode(inputXml) {
574708
var svg = xml.find("svg");
575709
globalSvg = svg;
576710
preprocessReferences(svg);
711+
parseGradients(svg);
577712

578713
if (toBool(localStorage.bakeTransforms)) {
579714
try {
@@ -598,6 +733,7 @@ function generateCode(inputXml) {
598733
//XML Vector start
599734
generatedOutput = '<?xml version="1.0" encoding="utf-8"?>\n';
600735
generatedOutput += '<vector xmlns:android="http://schemas.android.com/apk/res/android"';
736+
generatedOutput += '\n' + INDENT + 'xmlns:aapt="http://schemas.android.com/aapt"'
601737

602738
generatedOutput += '\n' + INDENT + 'android:width="{0}dp"\n'.f(width);
603739
generatedOutput += INDENT + 'android:height="{0}dp"\n'.f(height);
@@ -832,6 +968,11 @@ function parseColorToHex(color) {
832968

833969
//Is hex already
834970
if (color.substr(0, 1) === "#") {
971+
// Expand shorthand form (e.g. "03F") to full form (e.g. "0033FF")
972+
var shorthandRegex = /^#?([a-f\d])([a-f\d])([a-f\d])$/i;
973+
color = color.replace(shorthandRegex, function(m, r, g, b) {
974+
return r + r + g + g + b + b;
975+
});
835976
return color;
836977
} else {
837978
if (color.startsWith("rgb(")) {
@@ -890,4 +1031,4 @@ function showLastUpdate(repo) {
8901031
}
8911032
});
8921033
});
893-
}
1034+
}

0 commit comments

Comments
 (0)