Repository: svg/svgo
Pull request #818 (2017-10-27 17:31:18)

Convert circles/ellipses to paths
The ability to convert circles and ellipses to paths has been requested a few times, for example in #652.

This pull request adds the ability to do just that.

LMK what you think!

See #702 for earlier work on this.

GreLI: Issues from the original one are still present here:

- it should be optional
- there're errors in code, it doesn't work
- lack of tests.
alexjlockwood: Fixed the errors, will add some tests later tonight!

@GreLi how do you suggest I make this optional?
GreLI: Just an option for the plugin like `convertEllipses`.
alexjlockwood: Would you prefer two options (one for circles and a second for ellipses)?
GreLI: I think one is enough, may be shorten to like `convertArcs`, but if you see such a point, you're welcome.
alexjlockwood: Cool, sounds good. I'll add some tests tonight when I get back from work.
alexjlockwood: @GreLI I added a simple test case and fixed up the code. LMK what you think!

Patch:
diff --git a/plugins/convertShapeToPath.js b/plugins/convertShapeToPath.js
index 6d32804..9072461 100644
--- a/plugins/convertShapeToPath.js
+++ b/plugins/convertShapeToPath.js
@@ -6,6 +6,10 @@ exports.active = true;
 
 exports.description = 'converts basic shapes to more compact path form';
 
+exports.params = {
+    convertArcs: false
+};
+
 var none = { value: 0 },
     regNumber = /[-+]?(?:\d*\.\d+|\d+\.?)(?:[eE][-+]?\d+)?/g;
 
@@ -22,7 +26,8 @@ var none = { value: 0 },
  *
  * @author Lev Solntsev
  */
-exports.fn = function(item) {
+exports.fn = function(item, params) {
+    var convertArcs = params && params.convertArcs;
 
     if (
         item.isElem('rect') &&
@@ -98,6 +103,47 @@ exports.fn = function(item) {
 
         item.renameElem('path')
             .removeAttr('points');
+    } else if (item.isElem('circle') && convertArcs) {
+
+        var cx = +(item.attr('cx') || none).value;
+        var cy = +(item.attr('cy') || none).value;
+        var r = +(item.attr('r') || none).value;
+        if (isNaN(cx - cy + r)) {
+            return;
+        }
+        var cPathData =
+            'M' + cx  + ' ' + (cy - r) +
+                'A' + r + ' ' + r + ' 0 1 0 ' + cx + ' ' + (cy + r) +
+                'A' + r + ' ' + r + ' 0 1 0 ' + cx + ' ' + (cy - r) +
+                'Z';
+        item.addAttr({
+                name: 'd',
+                value: cPathData,
+                prefix: '',
+                local: 'd',
+        });
+        item.renameElem('path').removeAttr(['cx', 'cy', 'r']);
+
+    } else if (item.isElem('ellipse') && convertArcs) {
+
+        var ecx = +(item.attr('cx') || none).value;
+        var ecy = +(item.attr('cy') || none).value;
+        var rx = +(item.attr('rx') || none).value;
+        var ry = +(item.attr('ry') || none).value;
+        if (isNaN(ecx - ecy + rx - ry)) {
+            return;
+        }
+        var ePathData =
+            'M' + ecx + ' ' + (ecy - ry) +
+                'A' + rx + ' ' + ry + ' 0 1 0 ' + ecx + ' ' + (ecy + ry) +
+                'A' + rx + ' ' + ry + ' 0 1 0 ' + ecx + ' ' + (ecy - ry) +
+                'Z';
+        item.addAttr({
+                name: 'd',
+                value: ePathData,
+                prefix: '',
+                local: 'd',
+        });
+        item.renameElem('path').removeAttr(['cx', 'cy', 'rx', 'ry']);
     }
-
 };


Test patch:
diff --git a/test/plugins/convertShapeToPath.04.svg b/test/plugins/convertShapeToPath.04.svg
new file mode 100644
index 0000000..7589d9b
--- /dev/null
+++ b/test/plugins/convertShapeToPath.04.svg
@@ -0,0 +1,15 @@
+<svg xmlns="http://www.w3.org/2000/svg">
+    <circle cx="10" cy="10" r="5"/>
+    <ellipse cx="10" cy="10" rx="5" ry="5"/>
+</svg>
+
+@@@
+
+<svg xmlns="http://www.w3.org/2000/svg">
+    <path d="M10 5A5 5 0 1 0 10 15A5 5 0 1 0 10 5Z"/>
+    <path d="M10 5A5 5 0 1 0 10 15A5 5 0 1 0 10 5Z"/>
+</svg>
+
+@@@
+
+{ "convertArcs": true }