├── README.md └── SVG Exporter.jsx /README.md: -------------------------------------------------------------------------------- 1 | # Illustrator SVG Exporter 2 | 3 | Exporting SVGs from Illustrator is a slow, laborious process—this script fixes that. The script doesn't waste your time with GUI or settings you'll never use. You just run the script, select a location to export and you have your SVGs. We love the concept behind [Generator](http://blogs.adobe.com/photoshopdotcom/2013/09/introducing-adobe-generator-for-photoshop-cc.html) and this script takes a strong cue from it. The script exports any layer, group or path named with the `.svg` extension. We use this script to export all our icons for [Open Iconic](https://github.com/iconic/open-iconic). 4 | 5 | ## Installation 6 | 7 | You don't _have_ to install the script to use it (more on that later), but installing the script is by far the best way to use it. All you need to do is drop the `SVG Exporter.jsx` file in one of the following directories: 8 | 9 | * Windows: `C:\Program Files\Adobe\Adobe lllustratorCC2014\Presets\[language]\Scripts\` 10 | * Mac OS: `/Applications/Adobe lllustrator CC 2014/Presets/[language]/Scripts/` 11 | 12 | Note: Make sure to restart Illustrator if you installed the script while the Application is running. 13 | 14 | ## Running the Script 15 | 16 | Once the script is installed, you'll be able to run it by going to `File > Scripts > SVG Exporter`. As mentioned, you don't need to install the script. If you want to run it as a one-off, select `File > Scripts > Other Script...` and select the `SVG Exporter.jsx` file in the file chooser. 17 | 18 | Once you run the script, you'll be prompted to select a location to save the SVG files. After a location is set, you're done—the script does the rest. 19 | 20 | ## Document Setup 21 | 22 | The script doesn't force any setup or organization on you. You can export layers, groups, compound paths or individual paths. Just name the path/layer/group/compound path what you want the file name to be (e.g., my-cool-vector-drawing.svg) and the script will prep it for export. You can export nested layers (example: export indiviual assets as well all assets in a parent layer). The exported SVGs will be cropped to the bounding box of the path/group/layer. 23 | 24 | You can name artboards with a `.svg` extension to export SVGs to specific dimensions other than the paths' bounding box. All paths within the artboard will be exported, so make sure to clean up any unwanted paths before export. 25 | 26 | If you want to individually name each element in your SVG for CSS styling (ala [Iconic](http://useiconic.com)), just name each path within a layer or group you wish to have exported. The script will santize the name so that it will be converted to a pretty ID by Illustrator's SVG export engine. _**Hint:** We've also made a slick [Grunt tool](https://github.com/iconic/grunt-svg-toolkit) which (among other things) will convert the IDs from the Illustrator-exported SVG to classes._ 27 | 28 | If you previously named an element for export but now don't want to export for some reason, simply lock it to keep it from being exported. 29 | -------------------------------------------------------------------------------- /SVG Exporter.jsx: -------------------------------------------------------------------------------- 1 | /** 2 | * The MIT License (MIT) 3 | * 4 | * Copyright (c) 2015 Waybury 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in 14 | * all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | * THE SOFTWARE. 23 | * 24 | */ 25 | 26 | #target illustrator 27 | 28 | var exportFolder, 29 | sourceDoc, 30 | itemsToExport, 31 | exportDoc, 32 | svgOptions; 33 | 34 | try { 35 | if ( app.documents.length > 0 ) { 36 | svgOptions = new ExportOptionsSVG(); 37 | svgOptions.embedRasterImages = false; 38 | svgOptions.cssProperties = SVGCSSPropertyLocation.PRESENTATIONATTRIBUTES; 39 | svgOptions.fontSubsetting = SVGFontSubsetting.None; 40 | svgOptions.documentEncoding = SVGDocumentEncoding.UTF8; 41 | svgOptions.coordinatePrecision = 4; 42 | 43 | itemsToExport = []; 44 | sourceDoc = app.activeDocument; 45 | exportFolder = Folder.selectDialog('Select Folder to Save Files'); 46 | exportDoc = documents.add(DocumentColorSpace.RGB); 47 | 48 | main(); 49 | 50 | exportDoc.close(SaveOptions.DONOTSAVECHANGES); 51 | } 52 | else{ 53 | throw new Error('There are no documents open. Open a document and try again.'); 54 | } 55 | } 56 | catch(e) { 57 | alert(e.message, "Script Alert", true); 58 | } 59 | 60 | function main() { 61 | var item; 62 | app.activeDocument = sourceDoc; 63 | itemsToExport = getNamedItems(sourceDoc); 64 | 65 | for ( var i = 0, len = itemsToExport.length; i < len; i++ ) { 66 | 67 | 68 | item = itemsToExport[i]; 69 | 70 | if ( item.typename === 'Artboard' ) { 71 | exportArtboard(item); 72 | } else if ( item.typename === 'Layer' ) { 73 | exportLayer(item); 74 | } else { 75 | exportItem(item); 76 | } 77 | 78 | // Empty export document 79 | exportDoc.pageItems.removeAll(); 80 | } 81 | 82 | } 83 | 84 | function exportArtboard(artboard) { 85 | 86 | var item, 87 | name, 88 | prettyName, 89 | doc, 90 | rect, 91 | bbox; 92 | 93 | app.activeDocument = sourceDoc; 94 | rect = artboard.artboardRect; 95 | 96 | bbox = sourceDoc.pathItems.rectangle(rect[1], rect[0], rect[2]-rect[0], rect[1]-rect[3]); 97 | bbox.stroked = false; 98 | bbox.name = '__ILSVGEX__BOUNDING_BOX'; 99 | 100 | name = artboard.name; 101 | prettyName = name.slice(0, -4).replace(/[^\w\s]|_/g, " ").replace(/\s+/g, "-").toLowerCase(); 102 | 103 | app.activeDocument = exportDoc; 104 | 105 | for ( var i = 0, len = sourceDoc.pageItems.length; i < len; i++ ) { 106 | item = sourceDoc.pageItems[i]; 107 | 108 | if( hitTest(item, bbox) && !item.locked && !anyParentLocked(item) ) { 109 | item.duplicate( exportDoc, ElementPlacement.PLACEATEND ); 110 | } 111 | } 112 | 113 | app.activeDocument = exportDoc; 114 | exportDoc.pageItems.getByName('__ILSVGEX__BOUNDING_BOX').remove(); 115 | 116 | // Check if artboard is blank, clean up and exit 117 | if(!exportDoc.pageItems.length) { 118 | sourceDoc.pageItems.getByName('__ILSVGEX__BOUNDING_BOX').remove(); 119 | return; 120 | } 121 | 122 | for ( i = 0, len = exportDoc.pageItems.length; i < len; i++) { 123 | item = exportDoc.pageItems[i]; 124 | 125 | /* 126 | * For the moment, all pageItems are made visible and exported 127 | * unless they are locked. This may not make sense, but it'll 128 | * work for now. 129 | */ 130 | item.hidden = false; 131 | } 132 | 133 | exportDoc.layers[0].name = prettyName; 134 | exportSVG( exportDoc, name, bbox.visibleBounds, svgOptions ); 135 | 136 | sourceDoc.pageItems.getByName('__ILSVGEX__BOUNDING_BOX').remove(); 137 | } 138 | 139 | function exportLayer(layer) { 140 | 141 | var item, 142 | startX, 143 | startY, 144 | endX, 145 | endY, 146 | name, 147 | prettyName, 148 | itemName, 149 | layerItems; 150 | 151 | layerItems = []; 152 | 153 | for ( var i = 0, len = layer.pageItems.length; i < len; i++ ) { 154 | layerItems.push(layer.pageItems[i]); 155 | } 156 | recurseItems(layer.layers, layerItems); 157 | 158 | if ( !layerItems.length ) { 159 | return; 160 | } 161 | 162 | name = layer.name; 163 | prettyName = name.slice(0, -4).replace(/[^\w\s]|_/g, " ").replace(/\s+/g, "-").toLowerCase(); 164 | 165 | for ( i = 0, len = layerItems.length; i < len; i++ ) { 166 | app.activeDocument = sourceDoc; 167 | item = layerItems[i]; 168 | item.duplicate( exportDoc, ElementPlacement.PLACEATEND ); 169 | } 170 | 171 | app.activeDocument = exportDoc; 172 | 173 | for ( i = 0, len = exportDoc.pageItems.length; i < len; i++) { 174 | 175 | item = exportDoc.pageItems[i]; 176 | 177 | /* 178 | * For the moment, all pageItems are made visible and exported 179 | * unless they are locked. This may not make sense, but it'll 180 | * work for now. 181 | */ 182 | item.hidden = false; 183 | 184 | if(item.name) { 185 | itemName = item.name; 186 | if(itemName.split('.').pop() === 'svg') { 187 | itemName = itemName.slice(0, -4); 188 | } 189 | itemName = itemName.replace(/[^\w\s]|_/g, " ").replace(/\s+/g, "-").toLowerCase() 190 | 191 | item.name = prettyName + '-' + itemName; 192 | } 193 | /* 194 | * We want the smallest startX, startY for obvious reasons. 195 | * We also want the smallest endX and endY because Illustrator 196 | * Extendscript treats this coordinate reversed to how the UI 197 | * treats it (e.g., -142 in the UI is 142). 198 | * 199 | */ 200 | startX = ( !startX || startX > item.visibleBounds[0] ) ? item.visibleBounds[0] : startX; 201 | startY = ( !startY || startY < item.visibleBounds[1] ) ? item.visibleBounds[1] : startY; 202 | endX = ( !endX || endX < item.visibleBounds[2] ) ? item.visibleBounds[2] : endX; 203 | endY = ( !endY || endY > item.visibleBounds[3] ) ? item.visibleBounds[3] : endY; 204 | } 205 | 206 | exportDoc.layers[0].name = name.slice(0, -4); 207 | exportSVG( exportDoc, name, [startX, startY, endX, endY], svgOptions ); 208 | } 209 | 210 | function exportItem(item) { 211 | 212 | var name, 213 | newItem; 214 | 215 | name = item.name; 216 | newItem = item.duplicate( exportDoc, ElementPlacement.PLACEATEND ); 217 | newItem.hidden = false; 218 | newItem.name = item.name.slice(0, -4); 219 | app.activeDocument = exportDoc; 220 | 221 | exportDoc.layers[0].name = ' '; 222 | exportSVG( exportDoc, name, item.visibleBounds, svgOptions ); 223 | } 224 | 225 | function exportSVG(doc, name, bounds, exportOptions) { 226 | 227 | doc.artboards[0].artboardRect = bounds; 228 | 229 | var file = new File( exportFolder.fsName + '/' + name ); 230 | doc.exportFile( file, ExportType.SVG, exportOptions ); 231 | } 232 | 233 | function getNamedItems(doc) { 234 | var item, 235 | items, 236 | doclayers, 237 | artboards; 238 | 239 | items = []; 240 | 241 | // Check all artboards for name match 242 | artboards = []; 243 | 244 | for ( var i = 0, len = doc.artboards.length; i < len; i++ ) { 245 | item = doc.artboards[i]; 246 | if ( item.name.split('.').pop() === 'svg' ) { 247 | items.push(item); 248 | } 249 | } 250 | 251 | // Check all layers for name match 252 | doclayers = []; 253 | recurseLayers( doc.layers, doclayers ); 254 | 255 | for ( i = 0, len = doclayers.length; i < len; i++ ) { 256 | item = doclayers[i]; 257 | 258 | if ( item.name.split('.').pop() === 'svg' && !item.locked && !anyParentLocked(item) ) { 259 | items.push(item); 260 | } 261 | } 262 | 263 | // Check all pageItems for name match 264 | for ( i = 0, len = doc.pageItems.length; i < len; i++ ) { 265 | item = doc.pageItems[i]; 266 | 267 | if ( item.name.split('.').pop() === 'svg' && !item.locked && !anyParentLocked(item) ) { 268 | items.push(item); 269 | } 270 | } 271 | 272 | return items; 273 | } 274 | 275 | function recurseLayers(layers, layerArray) { 276 | 277 | var layer; 278 | 279 | for ( var i = 0, len = layers.length; i < len; i++ ) { 280 | layer = layers[i]; 281 | if ( !layer.locked ) { 282 | layerArray.push(layer); 283 | } 284 | if (layer.layers.length > 0) { 285 | recurseLayers( layer.layers, layerArray ); 286 | } 287 | } 288 | } 289 | 290 | function recurseItems(layers, items) { 291 | 292 | var layer; 293 | 294 | for ( var i = 0, len = layers.length; i < len; i++ ) { 295 | layer = layers[i]; 296 | if ( layer.pageItems.length > 0 && !layer.locked ) { 297 | for ( var j = 0, plen = layer.pageItems.length; j < plen; j++ ) { 298 | if ( !layer.pageItems[j].locked ) { 299 | items.push(layer.pageItems[j]); 300 | } 301 | } 302 | } 303 | 304 | if ( layer.layers.length > 0 ) { 305 | recurseItems( layer.layers, items ); 306 | } 307 | } 308 | } 309 | 310 | function anyParentLocked(item) { 311 | while ( item.parent ) { 312 | if ( item.parent.locked ) { 313 | return true; 314 | } 315 | item = item.parent; 316 | } 317 | 318 | return false; 319 | } 320 | 321 | 322 | /* Code derived from John Wundes ( john@wundes.com ) www.wundes.com 323 | * Copyright (c) 2005 wundes.com 324 | * All rights reserved. 325 | * 326 | * This code is derived from software contributed to or originating on wundes.com 327 | */ 328 | 329 | function hitTest(a,b){ 330 | if(!hitTestX(a,b)){ 331 | return false; 332 | } 333 | if(!hitTestY(a,b)){ 334 | return false; 335 | } 336 | return true; 337 | } 338 | 339 | function hitTestX(a,b){ 340 | var p1 = a.visibleBounds[0]; 341 | var p2 = b.visibleBounds[0]; 342 | if( (p2<=p1 && p1<=p2+b.width) || (p1<=p2 && p2<=p1+a.width) ) { 343 | return true; 344 | } 345 | return false; 346 | } 347 | 348 | function hitTestY(a,b){ 349 | var p3 = a.visibleBounds[1]; 350 | var p4 = b.visibleBounds[1]; 351 | if( (p3>=p4 && p4>=(p3-a.height)) || (p4>=p3 && p3>=(p4-b.height)) ) { 352 | return true; 353 | } 354 | return false; 355 | } 356 | --------------------------------------------------------------------------------