├── README.md └── pathtosvg.jsx /README.md: -------------------------------------------------------------------------------- 1 | # Converting Photoshop paths to SVG 2 | 3 | Grab the file here: 4 | 5 | pathtosvg.jsx 6 | 7 | This is a photoshop "jsx" script that turns all paths in the active document 8 | into a single SVG file for use in something else. Simply download the script 9 | to "somewhere", then run it through *file* → *scripts* → *browse*, find the 10 | script and open it. 11 | 12 | ![screenshot 197](https://cloud.githubusercontent.com/assets/177243/13687396/db3b5532-e6cf-11e5-9a50-b151e13b8761.png) 13 | 14 | This will immediately run the conversion and prompt you with a save dialog 15 | 16 | ![screenshot 201](https://user-images.githubusercontent.com/177243/42773813-44a7bca6-88e3-11e8-8e80-ed00cc72455d.png) 17 | 18 | Type a filename - if you already have a file name with that name, you'll be asked whether you want to overwrite that file: 19 | 20 | ![screenshot 202](https://user-images.githubusercontent.com/177243/42773815-47a53154-88e3-11e8-904c-bafcfe97d6a1.png) 21 | 22 | Once you accept, Photoshop will save the paths to SVG and inform you it finished: 23 | 24 | ![screenshot 203](https://cloud.githubusercontent.com/assets/177243/13692103/3ec50b84-e6f3-11e5-9662-b0060118419d.png) 25 | 26 | We can then open the SVG in whatever target application we need. For instance, Inkscape: 27 | 28 | ![screenshot 200](https://cloud.githubusercontent.com/assets/177243/13687518/8dcc9968-e6d0-11e5-84c1-ebe3c45c6615.png) 29 | -------------------------------------------------------------------------------- /pathtosvg.jsx: -------------------------------------------------------------------------------- 1 | /** 2 | * Ah, JavaScript from 1998... when JS was still a pretty shitty language, 3 | * syntactically, and things that have simple utility APIs today still 4 | * required you to write out all the code yourself. 5 | * 6 | * Seriously: wtf, Adobe? 7 | **/ 8 | 9 | // First: functions that do what modern JS already does. 10 | 11 | Array.prototype.map = function(f) { 12 | var retArr = []; 13 | for (var i = 0, e = this.length; i < e; i++) { 14 | retArr[i] = f(this[i], i); 15 | } 16 | return retArr; 17 | }; 18 | 19 | Array.prototype.forEach = function(f) { 20 | this.map(f); 21 | }; 22 | 23 | Array.from = function(iterable) { 24 | var retArr = []; 25 | for (var i = 0, e = iterable.length; i < e; i++) { 26 | retArr.push(iterable[i]); 27 | } 28 | return retArr; 29 | } 30 | 31 | // 32 | // Also we'll need some custom objects. 33 | // 34 | 35 | var Point = function (kind, x, y) { 36 | this.kind = kind; 37 | this.x = x; 38 | this.y = y; 39 | } 40 | 41 | Point.prototype = { 42 | toString: function () { 43 | var base = [this.kind]; 44 | if (this.in) { 45 | base = base.concat(['(', this.in.x, this.in.y, ')']); 46 | } 47 | base = base.concat([this.x, this.y]); 48 | if (this.out) { 49 | base = base.concat(['(', this.out.x, this.out.y, ')']); 50 | } 51 | return base.join(' '); 52 | } 53 | } 54 | 55 | var BBox = function () { 56 | this.mx = 9999999; 57 | this.my = 9999999; 58 | this.MX = -9999999; 59 | this.MY = -9999999; 60 | }; 61 | 62 | BBox.prototype = { 63 | grow: function (p) { 64 | if (!p) return; 65 | if (p.x < this.mx) { this.mx = p.x; } 66 | if (p.y < this.my) { this.my = p.y; } 67 | if (p.x > this.MX) { this.MX = p.x; } 68 | if (p.y > this.MY) { this.MY = p.y; } 69 | this.grow(p.in); 70 | this.grow(p.out); 71 | } 72 | } 73 | 74 | // 75 | // And with that out of the way, the actual script: 76 | // 77 | 78 | var pointTypes = { 79 | 'PointKind.CORNERPOINT': 'P', 80 | 'PointKind.SMOOTHPOINT': 'C' 81 | }; 82 | 83 | // filewrite function 84 | function writeToFile(data) { 85 | var path = '/'; 86 | // can we get a file location from the current document? 87 | try { path = app.activeDocument.path; } catch (e) { } 88 | var dir = Folder(path); 89 | var file = dir.saveDlg('', '.svg', true); 90 | if (!file) return false; 91 | 92 | var mode = 'w'; 93 | file.open(mode); 94 | file.write(data); 95 | file.close(mode); 96 | return file.toString(); 97 | } 98 | 99 | // convert a PathPoint to a real object. 100 | function improvePoint(point) { 101 | var kind = pointTypes[point.kind]; 102 | var coord = point.anchor; 103 | var x = Math.round(coord[0]); 104 | var y = Math.round(coord[1]); 105 | var obj = new Point(kind, x, y); 106 | 107 | if (kind === 'C') { 108 | var d; 109 | if (point.leftDirection) { 110 | d = point.leftDirection.map(Math.round); 111 | obj.out = { x: d[0], y: d[1] }; 112 | } 113 | if (point.rightDirection) { 114 | d = point.rightDirection.map(Math.round); 115 | obj.in = { x: d[0], y: d[1] }; 116 | } 117 | } 118 | 119 | return obj; 120 | } 121 | 122 | // convert all points in a subpath to easier to parse form 123 | function handleSubPath(subpath) { 124 | var pathPoints = Array.from(subpath.pathPoints); 125 | return pathPoints.map(improvePoint); 126 | }; 127 | 128 | // convert all subpaths in a path to easier to walk form 129 | function handlePath(path) { 130 | var subPaths = Array.from(path.subPathItems); 131 | return subPaths.map(handleSubPath); 132 | }; 133 | 134 | // convert all paths in a document to easier to walk form. 135 | function convertPaths(pathItems) { 136 | var paths = Array.from(pathItems); 137 | return paths.map(handlePath); 138 | } 139 | 140 | // turn a subpath of improved points into an SVG path 141 | function formSVGpath(subpath, bbox) { 142 | var p0 = subpath[0]; 143 | var path = ['M', p0.x, p0.y]; 144 | // we want to close this path: 145 | subpath.push(p0); 146 | subpath.forEach(function (p, i) { 147 | if (i === 0) return; 148 | bbox.grow(p); 149 | 150 | if (p0.kind === 'P' && p.kind === 'P') { 151 | path = path.concat(['L', p.x, p.y]); 152 | } 153 | else if (p0.kind === 'P' && p.kind === 'C') { 154 | path = path.concat(['C', p0.x, p0.y, p.in.x, p.in.y, p.x, p.y]); 155 | } 156 | else if (p0.kind === 'C' && p.kind === 'P') { 157 | path = path.concat(['C', p0.out.x, p0.out.y, p.x, p.y, p.x, p.y]); 158 | } 159 | else if (p0.kind === 'C' && p.kind === 'C') { 160 | path = path.concat(['C', p0.out.x, p0.out.y, p.in.x, p.in.y, p.x, p.y]); 161 | } 162 | p0 = p; 163 | }); 164 | path.push('z'); 165 | return path.join(' '); 166 | } 167 | 168 | // Convert an improved path into an SVG string 169 | function formPathCollectionSVG(pathCollection) { 170 | var svg = []; 171 | var bbox = new BBox(); 172 | pathCollection.forEach(function (path) { 173 | var d = ''; 174 | path.forEach(function (subpath) { 175 | d += formSVGpath(subpath, bbox); 176 | }); 177 | svg.push(''); 178 | }); 179 | svg.push(''); 180 | var w = bbox.MX - bbox.mx; 181 | var h = bbox.MY - bbox.my; 182 | var header = ''; 183 | svg = [header].concat(svg); 184 | return svg.join('\n');; 185 | } 186 | 187 | // =========================================== 188 | // 189 | // JS equivalent of main() 190 | // 191 | // =========================================== 192 | 193 | #target photoshop 194 | 195 | // switch to using pixels as unit, irrespective of what the document is set to 196 | var activeDoc = app.activeDocument; 197 | var origUnits = app.preferences.rulerUnits; 198 | app.preferences.rulerUnits = Units.PIXELS; 199 | 200 | // convert all paths and write them to file 201 | var improved = convertPaths(activeDoc.pathItems); 202 | var svg = formPathCollectionSVG(improved); 203 | var filepath = writeToFile(svg); 204 | if (!!filepath) alert("svg saved to " + filepath); 205 | 206 | // switch back to the original document's units once we're done. 207 | app.preferences.rulerUnits = origUnits; 208 | --------------------------------------------------------------------------------