├── 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 | 
13 |
14 | This will immediately run the conversion and prompt you with a save dialog
15 |
16 | 
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 | 
21 |
22 | Once you accept, Photoshop will save the paths to SVG and inform you it finished:
23 |
24 | 
25 |
26 | We can then open the SVG in whatever target application we need. For instance, Inkscape:
27 |
28 | 
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 = '