Skip to content

Instantly share code, notes, and snippets.

@TomByrne
Last active March 19, 2024 10:12
Show Gist options
  • Save TomByrne/7816376 to your computer and use it in GitHub Desktop.
Save TomByrne/7816376 to your computer and use it in GitHub Desktop.
An Illustrator script for exporting layers and/or artboards into separate files (PNG8 / PNG24 / EPS / PDF / SVG / JPG / FXG).See http://www.tbyrne.org/export-illustrator-layers-to-svg-files
// MultiExporter.jsx
// Version 0.1
// Version 0.2 Adds PNG and EPS exports
// Version 0.3 Adds support for exporting at different resolutions
// Version 0.4 Adds support for SVG, changed EPS behaviour to minimise output filesize
// Version 0.5 Fixed cropping issues
// Version 0.6 Added inner padding mode to prevent circular bounds clipping
//
// Copyright 2013 Tom Byrne
// Comments or suggestions to tom@tbyrne.org
//
// Copyright 2011 Matthew Ericson
var docRef = app.activeDocument;
// Format specific functionality
getPng8Options = function ( transparency, scaling, embedImage, embedFont, trimEdges ) {
var options = new ExportOptionsPNG8();
options.antiAliasing = true;
options.transparency = transparency;
options.artBoardClipping = true;
options.horizontalScale = scaling;
options.verticalScale = scaling;
return options;
}
getPng24Options = function ( transparency, scaling, embedImage, embedFont, trimEdges ) {
var options = new ExportOptionsPNG24();
options.antiAliasing = true;
options.transparency = transparency;
options.artBoardClipping = true;
options.horizontalScale = scaling;
options.verticalScale = scaling;
return options;
}
getPdfOptions = function ( transparency, scaling, embedImage, embedFont, trimEdges ) {
var options = new PDFSaveOptions();
options.compatibility = PDFCompatibility.ACROBAT5;
options.generateThumbnails = true;
options.preserveEditability = false;
return options;
}
getJpgOptions = function ( transparency, scaling, embedImage, embedFont, trimEdges ) {
var options = new ExportOptionsJPEG();
options.antiAliasing = true;
options.artBoardClipping = true;
options.horizontalScale = scaling;
options.verticalScale = scaling;
return options;
}
getEpsOptions = function ( transparency, scaling, embedImage, embedFont, trimEdges ) {
var options = new EPSSaveOptions();
options.embedLinkedFiles = embedImage;
options.embedAllFonts = embedFont;
options.includeDocumentThumbnails = true;
options.saveMultipleArtboards = false;
return options;
}
getSvgOptions = function ( transparency, scaling, embedImage, embedFont, trimEdges ) {
options = new ExportOptionsSVG();
options.embedRasterImages = embedImage;
return options;
}
getFxg1Options = function ( transparency, scaling, embedImage, embedFont, trimEdges ) {
options = new FXGSaveOptions();
options.version = FXGVersion.VERSION1PT0;
return options;
}
getFxg2Options = function ( transparency, scaling, embedImage, embedFont, trimEdges ) {
options = new FXGSaveOptions();
options.version = FXGVersion.VERSION2PT0;
return options;
}
// Format specific save functions
savePng8 = function ( doc, filePath, options, artboardIndex, artboardName ) {
var destFile = new File( filePath + '.png' );
doc.exportFile(destFile, ExportType.PNG8 , options);
}
savePng24 = function ( doc, filePath, options, artboardIndex, artboardName ) {
var destFile = new File( filePath + '.png' );
doc.exportFile(destFile, ExportType.PNG24 , options);
}
savePdf = function ( doc, filePath, options, artboardIndex, artboardName ) {
var destFile = new File( filePath + '.pdf' );
options.artboardRange = (artboardIndex+1).toString();
doc.saveAs( destFile, options, artboardIndex, artboardName )
}
saveJpg = function ( doc, filePath, options, artboardIndex, artboardName ) {
var destFile = new File( filePath + '.jpg' );
doc.exportFile(destFile, ExportType.JPEG , options);
}
saveEps = function ( doc, filePath, options, artboardIndex, artboardName ) {
var destFile = new File( filePath + '.eps' );
options.artboardRange = (artboardIndex+1).toString();
doc.saveAs( destFile, options, artboardIndex, artboardName )
}
saveSvg = function ( doc, filePath, options, artboardIndex, artboardName ) {
var destFile = new File( filePath + '.svg' );
doc.exportFile(destFile, ExportType.SVG , options);
}
saveFxg = function ( doc, filePath, options, artboardIndex, artboardName ) {
var destFile = new File( filePath + '.fxg' );
options.artboardRange = (artboardIndex+1).toString();
doc.saveAs( destFile, options, artboardIndex, artboardName )
}
var multi_exporter = {
PREFS_LAYER_NAME: "nyt_exporter_info",
multiExporterPrefs: null,
prefix: '',
suffix: '',
base_path: '~/Desktop',
transparency: false,
embedImage: true,
embedFont: true,
trimEdges: true,
innerPadding: false,
ignoreWarnings: false,
format: "PNG 24",
artboards: null,
layers: null,
dlg: null,
prefs_xml: null,
currentFormatInfo: null,
num_to_export: 0,
failed_artboards: null,
failed_layers: null,
export_artboards: null,
export_layers: null,
whole_artboard_mode: false,
formatList:null,
artboardList:null,
progLabel:null,
// these are controls that are format dependant
controlNames:["scalingInput","transCheckBox","embedImageCheckBox","embedFontCheckBox","trimEdgesCheckBox","innerPaddingCheckBox"],
scalingInput:null,
transCheckBox:null,
embedImageCheckBox:null,
embedFontCheckBox:null,
trimEdgesCheckBox:null,
ignoreCheckBox:null,
exportArtboardsCheckBox:null,
// copyBehaviour - for vector outputs the output must be done from a copy of the document (to avoid hidden layers being included in output)
formatInfo: [ {name:"PNG 8", copyBehaviour:false, pixelDocSize:true, getOptions:getPng8Options, saveFile:savePng8, activeControls:["scalingInput","transCheckBox","trimEdgesCheckBox","innerPaddingCheckBox"]},
{name:"PNG 24", copyBehaviour:false, pixelDocSize:true, getOptions:getPng24Options, saveFile:savePng24, activeControls:["scalingInput","transCheckBox","trimEdgesCheckBox","innerPaddingCheckBox"]},
{name:"PDF", copyBehaviour:false, pixelDocSize:false, getOptions:getPdfOptions, saveFile:savePdf, activeControls:["trimEdgesCheckBox"]},
{name:"JPG", copyBehaviour:false, pixelDocSize:true, getOptions:getJpgOptions, saveFile:saveJpg, activeControls:["scalingInput","trimEdgesCheckBox","innerPaddingCheckBox"]},
{name:"EPS", copyBehaviour:true, pixelDocSize:false, getOptions:getEpsOptions, saveFile:saveEps, activeControls:["embedImageCheckBox","embedFontCheckBox","trimEdgesCheckBox"]},
{name:"SVG", copyBehaviour:true, pixelDocSize:false, getOptions:getSvgOptions, saveFile:saveSvg, activeControls:["embedImageCheckBox","trimEdgesCheckBox"]},
{name:"FXG 1.0", copyBehaviour:true, pixelDocSize:false, getOptions:getFxg1Options, saveFile:saveFxg, activeControls:["trimEdgesCheckBox"]},
{name:"FXG 2.0", copyBehaviour:true, pixelDocSize:false, getOptions:getFxg2Options, saveFile:saveFxg, activeControls:["trimEdgesCheckBox"]}],
artboardSelect: [ {code:"all", name:'All Artboards (except those beginning with - )'},
{code:"current", name:'Current Artboard'},
{name:'---'} ],
layerSelect: [ {code:"all", name:'All Layers (except those beginning with - )'},
{code:"none", name:'None (for use with \'Export Artboard Images\')'},
{code:"selected", name:'Selected Items\' Layers'},
{name:'---'} ],
init: function() {
var parse_success = this.load_prefs();
if (parse_success) {
this.show_dialog();
}
},
findExportTypeByCode: function(code){
for(var i=0; i<this.exportTypes.length; ++i){
var type = this.exportTypes[i];
if(type.code==code)return type;
}
},
load_prefs: function() {
var parse_success = false;
// find existing layers or add new one
try {
this.multiExporterPrefs = docRef.layers.getByName( this.PREFS_LAYER_NAME );
} catch ( e ) {
this.multiExporterPrefs = docRef.layers.add();
this.multiExporterPrefs.name = this.PREFS_LAYER_NAME;
var nyt_exporter_info_xml = this.multiExporterPrefs.textFrames.add();
var saved_data = new XML( '<nyt_prefs></nyt_prefs>' );
saved_data.appendChild( new XML('<nyt_prefix></nyt_prefix>') );
saved_data.appendChild( new XML('<nyt_suffix></nyt_suffix>') );
saved_data.appendChild( new XML('<nyt_base_path>~/Desktop</nyt_base_path>') );
saved_data.appendChild( new XML('<nyt_scaling>100%</nyt_scaling>') );
saved_data.appendChild( new XML('<nyt_transparency>true</nyt_transparency>') );
saved_data.appendChild( new XML('<nyt_embedImage>true</nyt_embedImage>') );
saved_data.appendChild( new XML('<nyt_embedFont>true</nyt_embedFont>') );
saved_data.appendChild( new XML('<nyt_trimEdges>true</nyt_trimEdges>') );
saved_data.appendChild( new XML('<nyt_innerPadding>false</nyt_innerPadding>') );
saved_data.appendChild( new XML('<nyt_format>PNG 24</nyt_format>') );
saved_data.appendChild( new XML('<nyt_artboards>all</nyt_artboards>') );
saved_data.appendChild( new XML('<nyt_layers>all</nyt_layers>') );
saved_data.appendChild( new XML('<nyt_exportArtboards>false</nyt_exportArtboards>') );
saved_data.appendChild( new XML('<nyt_ignoreWarnings>false</nyt_ignoreWarnings>') );
nyt_exporter_info_xml.contents = saved_data.toXMLString();
this.multiExporterPrefs.printable = false;
this.multiExporterPrefs.visible = false;
}
// get xml out of the 1 text item on that layer and parse it
if ( this.multiExporterPrefs.textFrames.length != 1 ) {
Window.alert( 'Please delete the '+this.PREFS_LAYER_NAME+' layer and try again.' );
} else {
try {
this.prefs_xml = new XML( this.multiExporterPrefs.textFrames[0].contents );
this.prefix = this.prefs_xml.nyt_prefix;
this.suffix = this.prefs_xml.nyt_suffix;
this.base_path = this.prefs_xml.nyt_base_path;
this.scaling = this.prefs_xml.nyt_scaling;
this.transparency = this.prefs_xml.nyt_transparency == "true" ? true : false;
this.embedImage = this.prefs_xml.nyt_embedImage == "true" ? true : false;
this.ignoreWarnings = this.prefs_xml.nyt_ignoreWarnings == "true" ? true : false;
this.embedFont = this.prefs_xml.nyt_embedFont == "true" ? true : false;
this.trimEdges = this.prefs_xml.nyt_trimEdges == "true" ? true : false;
this.innerPadding = this.prefs_xml.nyt_innerPadding == "true" ? true : false;
this.format = this.prefs_xml.nyt_format;
this.artboards = this.prefs_xml.nyt_artboards.toString();
this.layers = this.prefs_xml.nyt_layers.toString();
this.whole_artboard_mode = this.prefs_xml.nyt_exportArtboards == "true" ? true : false;
if(!this.artboards){
this.artboards = this.artboardSelect[0].code;
}else if(parseInt(this.artboards).toString()==this.artboards){
this.artboards = parseInt(this.artboards);
}
if(!this.layers){
this.layers = this.layerSelect[0].code;
}else if(parseInt(this.layers).toString()==this.layers){
this.layers = parseInt(this.layers);
}
if ( ! this.prefs_xml.nyt_scaling || this.prefs_xml.nyt_scaling == '' ) {
this.scaling = '100%';
}
parse_success = true;
} catch ( e ) {
Window.alert( 'Please delete the this.multiExporterPrefs layer and try again.' );
}
}
return parse_success;
},
// dialog display
show_dialog: function() {
// Export dialog
this.dlg = new Window('dialog', 'Multi Exporter');
var row;
// ARTBOARD TYPE ROW
row = this.dlg.add('group', undefined, '')
row.oreintation = 'row';
row.alignment = [ScriptUI.Alignment.LEFT, ScriptUI.Alignment.TOP]
var typeSt = row.add('statictext', undefined, 'Export artboards:');
typeSt.size = [ 100,20 ];
var artboardNames = [];
for(var i=0; i<this.artboardSelect.length; ++i){
artboardNames.push(this.artboardSelect[i].name)
}
for(var i=0; i<docRef.artboards.length; i++){
var artboard = docRef.artboards[i];
artboardNames.push((i+1)+": "+artboard.name);
}
this.artboardList = row.add('dropdownlist', undefined, artboardNames);
this.artboardList.selection = this.findDataIndex(this.artboards, this.artboardSelect);
this.exportArtboardsCheckBox = row.add('checkbox', undefined, 'Export Artboard Images');
this.exportArtboardsCheckBox.value = this.whole_artboard_mode;
// LAYER TYPE ROW
row = this.dlg.add('group', undefined, '')
row.oreintation = 'row';
row.alignment = [ScriptUI.Alignment.LEFT, ScriptUI.Alignment.TOP]
var typeSt = row.add('statictext', undefined, 'Export layers:');
typeSt.size = [ 100,20 ];
var layerNames = [];
for(var i=0; i<this.layerSelect.length; ++i){
layerNames.push(this.layerSelect[i].name)
}
for(var i=0; i<docRef.layers.length; i++){
var layer = docRef.layers[i];
layerNames.push((i+1)+": "+layer.name);
}
this.layerList = row.add('dropdownlist', undefined, layerNames);
this.layerList.selection = this.findDataIndex(this.layers, this.layerSelect);
// PREFIX GRP
row = this.dlg.add('group', undefined, '')
row.oreintation = 'row';
row.alignment = [ScriptUI.Alignment.LEFT, ScriptUI.Alignment.TOP]
var prefixSt = row.add('statictext', undefined, 'File prefix:');
prefixSt.size = [100,20]
this.prefixEt = row.add('edittext', undefined, this.prefix);
this.prefixEt.size = [ 300,20 ];
// suffix row
row = this.dlg.add('group', undefined, '')
row.oreintation = 'row';
row.alignment = [ScriptUI.Alignment.LEFT, ScriptUI.Alignment.TOP]
row = this.dlg.add('group', undefined, '')
row.oreintation = 'row';
row.alignment = [ScriptUI.Alignment.LEFT, ScriptUI.Alignment.TOP]
var suffixSt = row.add('statictext', undefined, 'File suffix:');
suffixSt.size = [100,20]
this.suffixEt = row.add('edittext', undefined, this.suffix);
this.suffixEt.size = [ 300,20 ];
// scaling row
row = this.dlg.add('group', undefined, '')
row.oreintation = 'row';
row.alignment = [ScriptUI.Alignment.LEFT, ScriptUI.Alignment.TOP]
var scalingLabel = row.add('statictext', undefined, 'Scaling:');
scalingLabel.size = [100,20]
this.scalingInput = row.add('edittext', undefined, this.scaling);
this.scalingInput.size = [ 100,20 ];
var scalingTip = row.add('statictext', undefined, '(Normally 100%; Use 200% for Retina display exports)');
scalingTip.size = [300,20]
// DIR GROUP
row = this.dlg.add( 'group', undefined, '')
row.orientation = 'row'
row.alignment = [ScriptUI.Alignment.LEFT, ScriptUI.Alignment.TOP]
var dirSt = row.add('statictext', undefined, 'Output directory:');
dirSt.size = [ 100,20 ];
this.dirEt = row.add('edittext', undefined, this.base_path);
this.dirEt.size = [ 300,20 ];
var chooseBtn = row.add('button', undefined, 'Choose ...' );
chooseBtn.onClick = function() { multi_exporter.dirEt.text = Folder.selectDialog(); }
// FORMAT ROW
row = this.dlg.add('group', undefined, '');
row.orientation = 'row'
row.alignment = [ScriptUI.Alignment.LEFT, ScriptUI.Alignment.TOP]
var formatSt = row.add('statictext', undefined, 'Export format:');
formatSt.size = [ 100,20 ];
var formatNames = [];
for(var i=0; i<this.formatInfo.length; ++i){
formatNames.push(this.formatInfo[i].name)
}
this.formatList = row.add('dropdownlist', undefined, formatNames);
this.formatList.selection = 1;
for ( var i=0; i < this.formatList.items.length; i++ ) {
if ( multi_exporter.format == this.formatList.items[i].text ) {
this.formatList.selection = i;
}
}
this.embedImageCheckBox = row.add('checkbox', undefined, 'Embed Imagery');
this.embedImageCheckBox.value = this.embedImage;
this.embedFontCheckBox = row.add('checkbox', undefined, 'Embed Fonts');
this.embedFontCheckBox.value = this.embedFont;
// TRANSPARENCY AND TRIM ROW
row = this.dlg.add('group', undefined, '');
row.orientation = 'row'
row.alignment = [ScriptUI.Alignment.CENTER, ScriptUI.Alignment.TOP]
this.transCheckBox = row.add('checkbox', undefined, 'Transparency');
this.transCheckBox.value = this.transparency;
this.trimEdgesCheckBox = row.add('checkbox', undefined, 'Trim Edges');
this.trimEdgesCheckBox.value = this.trimEdges;
this.innerPaddingCheckBox = row.add('checkbox', undefined, 'Inner Padding (to prevent curved edge clipping)');
this.innerPaddingCheckBox.value = this.innerPadding;
// progress bar
var progBar = this.dlg.add( 'progressbar', undefined, 0, 100 );
progBar.size = [400,10]
this.progLabel = this.dlg.add('statictext', undefined, '...' );
this.progLabel.size = [ 400,20 ];
// buttons row
row = this.dlg.add('group', undefined, '');
row.orientation = 'row'
var cancelBtn = row.add('button', undefined, 'Cancel', {name:'cancel'});
cancelBtn.onClick = function() { multi_exporter.dlg.close() };
var saveBtn = row.add('button', undefined, 'Save and Close', {name:'saveClose'});
saveBtn.onClick = function() {
multi_exporter.saveOptions();
multi_exporter.dlg.close()
};
// OK button
var okBtn = row.add('button', undefined, 'Export', {name:'ok'});
okBtn.onClick = function() {
multi_exporter.saveOptions(); // save options before export in case of errors
try{
multi_exporter.run_export();
}catch(e){
alert(e);
}
};
this.ignoreCheckBox = row.add('checkbox', undefined, 'Ignore Warnings');
this.ignoreCheckBox.value = this.ignoreWarnings;
// Export type handler
this.artboardList.onChange = function() {
multi_exporter.artboards = multi_exporter.getListData(multi_exporter.artboardList.selection.index, multi_exporter.artboardSelect);
multi_exporter.update_export_desc( );
};
this.layerList.onChange = function() {
multi_exporter.layers = multi_exporter.getListData(multi_exporter.layerList.selection.index, multi_exporter.layerSelect);
multi_exporter.update_export_desc( );
};
this.exportArtboardsCheckBox.onClick = function() {
multi_exporter.whole_artboard_mode = multi_exporter.exportArtboardsCheckBox.value;
multi_exporter.update_export_desc( );
};
// Format change handler
this.formatList.onChange = function() {
multi_exporter.checkFormat();
};
multi_exporter.update_export_desc( );
this.dlg.progBar = progBar;
this.checkFormat();
this.dlg.show();
},
findDataIndex: function(data, selectList){
if(typeof(data)=="string" && parseInt(data).toString()==data){
data = parseInt(data);
}
if(typeof(data)=="number"){
return selectList.length+data;
}else{
for(var i=0; i<selectList.length; ++i){
if(selectList[i].code==data){
return i;
}
}
}
alert("no find: "+data);
},
getListData: function(index, selectList){
if(index>=selectList.length){
return index-selectList.length;
}else{
return selectList[index].code;
}
},
checkFormat: function ( progLabel ) {
var formatInfo = this.formatInfo[this.formatList.selection.index];
this.currentFormatInfo = formatInfo;
for(var i=0; i<this.controlNames.length; i++){
var controlName = this.controlNames[i];
this[controlName].enabled = (this.indexOf(formatInfo.activeControls, controlName)!=-1);
}
},
indexOf: function ( array, element ) {
for(var i=0; i<array.length; i++){
if(array[i]==element)return i;
}
return -1;
},
update_export_desc: function () {
this.export_artboards = [];
this.export_layers = [];
if(this.artboards=="all"){
for(var i=0; i<docRef.artboards.length; ++i){
var artboard = docRef.artboards[i];
if(!artboard.name.match( /^\-/ )){
this.export_artboards.push(i);
}
}
}else if(this.artboards=="current"){
this.export_artboards.push(docRef.artboards.getActiveArtboardIndex());
}else if(typeof(this.artboards)=="number"){
this.export_artboards.push(this.artboards);
}
if(this.layers=="all"){
for(var i=0; i<docRef.layers.length; ++i){
var layer = docRef.layers[i];
if(!this.isAdditionalLayer(layer) && this.isIncludeLayer(layer)){
this.export_layers.push(i);
}
}
}else if(this.layers=="selected"){
for(var i=0; i<docRef.layers.length; ++i){
var layer = docRef.layers[i];
if(!this.isAdditionalLayer(layer) && this.isIncludeLayer(layer) && layer.hasSelectedArtwork){
this.export_layers.push(i);
}
}
this.export_layers.push(docRef.artboards.getActiveArtboardIndex());
}else if(typeof(this.layers)=="number"){
this.export_layers.push(this.layers);
}
this.updateExportCounts();
},
updateExportCounts: function(){
this.num_to_export = this.export_artboards.length*this.export_layers.length;
var artboardExportTxt;
if(this.whole_artboard_mode){
this.num_to_export += this.export_artboards.length;
if(this.export_layers.length){
artboardExportTxt = " (and "+this.export_artboards.length+" artboard images)";
}else{
this.progLabel.text = 'Will export ' + this.export_artboards.length + ' of ' + docRef.artboards.length + ' artboards';
return;
}
}else{
artboardExportTxt = "";
}
if(this.export_artboards.length>1 && this.export_layers.length>1){
this.progLabel.text = 'Will export ' + this.num_to_export + ' files (' + this.export_layers.length + ' layers * ' + this.export_artboards.length + ' artboards)'+artboardExportTxt;
}else if(this.export_layers.length>0 && this.export_artboards.length==1){
var artboard = docRef.artboards[this.export_artboards[0]];
this.progLabel.text = 'Will export ' + this.export_layers.length + ' of ' + docRef.layers.length+ ' layers on artboard "' + artboard.name +'"' +artboardExportTxt;
}else if(this.export_layers.length>0 && this.export_artboards.length>0){
var artboard = docRef.artboards[this.export_artboards[0]];
this.progLabel.text = 'Will export ' + this.export_layers.length+' layers on "' + this.export_artboards.length +'" artboards' +artboardExportTxt;
}else{
this.progLabel.text = 'Please select valid artboards / layers' ;
}
},
saveOptions:function(){
this.prefix = this.prefixEt.text;
this.suffix = this.suffixEt.text;
this.base_path = this.dirEt.text;
this.transparency = this.transCheckBox.value;
this.embedImage = this.embedImageCheckBox.value;
this.embedFont = this.embedFontCheckBox.value;
this.trimEdges = this.trimEdgesCheckBox.value;
this.innerPadding = this.innerPaddingCheckBox.value;
this.ignoreWarnings = this.ignoreCheckBox.value;
this.format = this.formatList.selection.text;
this.scaling = parseFloat( this.scalingInput.text.replace( /\% /, '' ));
this.prefs_xml.nyt_base_path = this.base_path;
this.prefs_xml.nyt_scaling = this.scaling;
this.prefs_xml.nyt_prefix = this.prefix;
this.prefs_xml.nyt_suffix = this.suffix;
this.prefs_xml.nyt_transparency = this.transparency;
this.prefs_xml.nyt_embedImage = this.embedImage;
this.prefs_xml.nyt_ignoreWarnings = this.ignoreWarnings;
this.prefs_xml.nyt_embedFont = this.embedFont;
this.prefs_xml.nyt_trimEdges = this.trimEdges;
this.prefs_xml.nyt_innerPadding = this.innerPadding;
this.prefs_xml.nyt_format = this.format;
this.prefs_xml.nyt_artboards = this.artboards;
this.prefs_xml.nyt_layers = this.layers;
this.prefs_xml.nyt_exportArtboards = this.whole_artboard_mode;
this.multiExporterPrefs.textFrames[0].contents = this.prefs_xml.toXMLString();
},
// run_export function. does the dirty work
run_export: function() {
this.failed_artboards = [];
this.failed_layers = [];
var formatInfo = this.currentFormatInfo;
var copyBehaviour = formatInfo.copyBehaviour || this.trimEdges;
var num_exported = 0;
var options = formatInfo.getOptions(this.transparency, this.scaling, this.embedImage, this.embedFont, this.trimEdges);
if(!copyBehaviour){
var were_shown = this.get_shown_layers();
}
if(!this.export_artboards.length || (!this.export_layers.length && !this.whole_artboard_mode)){
alert('Please select valid artboards / layers');
return;
}
if(!this.base_path){
alert('Please select select a destination');
return;
}
// offsetting must be in relation to the center of the first artboard
var firstRect = docRef.artboards[0].artboardRect;
var firstCentX = (firstRect[2]-firstRect[0])/2;
var firstCentY = (firstRect[1]-firstRect[3])/2;
for (var i = 0; i < this.export_artboards.length; i++ ) {
var artboard = docRef.artboards[this.export_artboards[i]];
var artboardName = artboard.name;
starting_artboard = docRef.artboards.setActiveArtboardIndex(i);
var rect = artboard.artboardRect;
var offsetX = firstRect[0]-rect[0];
var offsetY = firstRect[1]-rect[1];
var artW = rect[2]-rect[0];
var artH = rect[1]-rect[3];
var copyDoc;
// if exporting artboard by artboard, export layers as is
if ( this.whole_artboard_mode) {
try{
var base_filename = this.base_path + "/" + this.prefix + artboardName + this.suffix;
if(copyBehaviour){
var offset = {x:offsetX, y:offsetY};
copyDoc = this.copyDocument(docRef, formatInfo.pixelDocSize, artboard, rect, artW, artH, offset, function(layer){return (layer.name!=multi_exporter.PREFS_LAYER_NAME && layer.visible)});
formatInfo.saveFile(copyDoc, base_filename, options, i, artboardName);
copyDoc.close(SaveOptions.DONOTSAVECHANGES);
copyDoc = null;
}else{
formatInfo.saveFile(docRef, base_filename, options, i, artboardName);
}
}catch(e){
this.failed_artboards.push(i);
}
this.updateProgress(++num_exported);
}
if(this.export_layers.length){
if(copyBehaviour){
if(!this.trimEdges){
var layerDepths = [];
var offset = {x:offsetX, y:offsetY};
copyDoc = this.copyDocument(docRef, formatInfo.pixelDocSize, artboard, rect, docRef.width, docRef.height, offset, this.isAdditionalLayer, layerDepths);
var hasAdditLayers = copyDoc.layers.length>0;
}
}else{
this.hide_all_layers();
}
for ( var j=0; j < this.export_layers.length; j++ ) {
var layer = docRef.layers[this.export_layers[j]];
var lyr_name = layer.name;
try{
var layerRect
// only process layer if it has bounds (i.e. not guide layer) and falls within current artboard bounds
layerRect = this.get_layer_bounds(layer)
if(layerRect==null)continue;
if (layerRect[0]<layerRect[2] && layerRect[1]>layerRect[3]) {
var isVis = this.intersects(rect, layerRect);
if(!hasAdditLayers && !isVis && !this.trimEdges){
// skip layers where nothing is visible
continue;
}
var base_filename;
if ( this.export_artboards.length==1 ) {
base_filename = this.base_path + "/" + this.prefix + lyr_name + this.suffix;
} else{
base_filename = this.base_path + "/" + this.prefix + artboardName + '-' + lyr_name + this.suffix;
}
if(copyBehaviour){
if(this.trimEdges){
if(copyDoc){
copyDoc.close(SaveOptions.DONOTSAVECHANGES);
copyDoc = null;
}
// crop to artboard
if(layerRect[0]<rect[0]){
layerRect[0] = rect[0];
}else{
intendedX = 0;
}
if(layerRect[1]>rect[1]){
layerRect[1] = rect[1];
}
if(layerRect[2]>rect[2]){
layerRect[2] = rect[2];
}
if(layerRect[3]<rect[3]){
layerRect[3] = rect[3];
}else{
intendedY = 0;
}
layerOffsetY = rect[3] - layerRect[3];
layerOffsetX = rect[0] - layerRect[0];
docW = layerRect[2]-layerRect[0];
docH = layerRect[1]-layerRect[3];
offset = {x:offsetX+layerOffsetX, y:offsetY+layerOffsetY};
var layerDepths = [];
var copyDoc = this.copyDocument(docRef, formatInfo.pixelDocSize, artboard, rect, docW, docH, offset, this.isAdditionalLayer, layerDepths);
var hasAdditLayers = copyDoc.layers.length>1; // there will be one empty layer in the new file (which can be ignored)
if(!hasAdditLayers && !isVis){
// skip layers where nothing is visible
continue;
}
}
if(isVis){
// only copy layer if it is visible (if not only visible '+' layers will be output)
var new_layer = this.copy_layer(layer, copyDoc.layers.add(), offset, copyDoc.width, copyDoc.height, this.innerPadding);
new_layer.visible = true;
var depth = layerDepths[this.export_layers[j]];
while(new_layer.zOrderPosition<depth){
new_layer.zOrder(ZOrderMethod.BRINGFORWARD);
}
while(new_layer.zOrderPosition>depth){
new_layer.zOrder(ZOrderMethod.SENDBACKWARD);
}
}
formatInfo.saveFile(copyDoc, base_filename, options, i, artboardName);
if(new_layer && !this.trimEdges){
new_layer.remove();
new_layer = null;
}
}else{
layer.visible = true;
formatInfo.saveFile(docRef, base_filename, options, i, artboardName);
layer.visible = false;
}
}
}catch(e){
this.failed_artboards.push(i);
this.failed_layers.push(j);
if(new_layer && !this.trimEdges){
new_layer.remove();
new_layer = null;
}
}
this.updateProgress(++num_exported);
}
if(copyDoc){
copyDoc.close(SaveOptions.DONOTSAVECHANGES);
copyDoc = null;
}
}
}
if(!copyBehaviour){
this.show_layers(were_shown);
}
if((!this.failed_layers.length && !this.failed_artboards.length) || !this.redoFailed(this.failed_layers, this.failed_artboards)){
this.dlg.close();
}
},
redoFailed: function(failed_layers, failed_artboards) {
var newLayers = [];
for(var i=0; i<failed_layers.length; ++i){
var index = this.export_layers[failed_layers[i]];
if(this.indexOf(newLayers, index)==-1)newLayers.push(index);
}
var newArtboards = [];
for(var i=0; i<failed_artboards.length; ++i){
var index = this.export_artboards[failed_artboards[i]];
if(this.indexOf(newArtboards, index)==-1)newArtboards.push(index);
}
if(newLayers.length){
var layerNames = "";
for(var i=0; i<newLayers.length; ++i){
var index = newLayers[i];
layerNames += "\n - "+docRef.layers[index].name;
}
var msg = newLayers.length+" layers failed across "+newArtboards.length+" artboards:"+layerNames+"\n Retry?";
}else{
var artboardNames = "";
for(var i=0; i<newArtboards.length; ++i){
var index = newArtboards[i];
artboardNames += "\n - "+docRef.artboards[index].name;
}
var msg = newArtboards.length+" artboards failed:"+artboardNames+"\nRetry?";
}
if(confirm(msg)){
this.export_artboards = newArtboards;
this.export_layers = newLayers;
this.updateExportCounts();
this.run_export();
return true;
}
return false;
},
traceRect: function(rect) {
if(!rect)return "no rect";
return "l: "+Math.round(rect[0])+" t: "+Math.round(rect[1])+" r: "+Math.round(rect[2])+" b: "+Math.round(rect[3])+" w: "+(rect[2]-rect[0])+" h: "+(rect[1]-rect[3]);
},
isAdditionalLayer: function(layer) {
return ( layer.name.match( /^\+/ ) && layer.visible);
},
isIncludeLayer: function(layer) {
return ( !layer.name.match( /^\+/ ) && layer.name!=this.PREFS_LAYER_NAME && !layer.name.match( /^\-/) )
},
copyDocument: function(docRef, pixelDocSize, artboard, rect, w, h, offset, layerCheck, layerDepths) {
var docW;
var docH;
if(pixelDocSize){
w = Math.round(w *1000) / 1000;
h = Math.round(h *1000) / 1000;
w = Math.ceil(w);
h = Math.ceil(h);
}
var preset = new DocumentPreset();
preset.width = w;
preset.height = h;
preset.colorMode = docRef.documentColorSpace;
preset.units = docRef.rulerUnits;
var copyDoc = app.documents.addDocument(docRef.documentColorSpace, preset);
copyDoc.pageOrigin = docRef.pageOrigin;
copyDoc.rulerOrigin = docRef.rulerOrigin;
var count = 1; // indices are 1 based!
var n = docRef.layers.length;
for ( var j=docRef.layers.length-1; j >=0; j-- ) {
layer = docRef.layers[j];
if (layerCheck(layer)) {
var layerBounds = this.get_layer_bounds(layer);
if(layerBounds && this.intersects(rect, layerBounds)){
this.copy_layer(layer, copyDoc.layers.add(), offset, w, h, false);
++count;
}
}else if(layerDepths){
layerDepths[j] = count;
}
}
return copyDoc;
},
updateProgress: function(num_exported) {
this.progLabel.text = 'Exported ' + num_exported + ' of ' + this.num_to_export;
this.dlg.progBar.value = num_exported / this.num_to_export * 100;
this.dlg.update();
},
copy_layer: function(from_layer, to_layer, offset, docW, docH, doInnerPadding) {
to_layer.artworkKnockout = from_layer.artworkKnockout;
to_layer.blendingMode = from_layer.blendingMode;
to_layer.color = from_layer.color;
to_layer.dimPlacedImages = from_layer.dimPlacedImages;
to_layer.isIsolated = from_layer.isIsolated;
to_layer.name = from_layer.name;
to_layer.opacity = from_layer.opacity;
to_layer.preview = from_layer.preview;
to_layer.printable = from_layer.printable;
to_layer.sliced = from_layer.sliced;
to_layer.typename = from_layer.typename;
if(!offset.norm){
var oldBounds = this.get_layer_bounds(from_layer);
//for mystery reasons, this only works if done before copying items across
}
var items = from_layer.pageItems;
try{
this.copy_items(items, to_layer);
}catch(e){
alert("copy items failed");
}
// copy backwards for correct z-ordering
for(var i=from_layer.layers.length-1; i>=0; --i){
var child = from_layer.layers[i];
if(child.visible)this.copy_layer(child, to_layer.layers.add(), offset, docW, docH, false)
}
if(!offset.norm){
var newBounds = this.get_layer_bounds(to_layer);
if(this.rect_equal(oldBounds, newBounds)){
//$.sleep(5000); // sleeping doesn't help!!
if(!this.ignoreWarnings)alert("Illustrator visibleBounds issue workaround.\nTry removing groups on layer '"+from_layer.name+"' to avoid this in future.\nPlease press OK");
newBounds = this.get_layer_bounds(to_layer);
// sometimes it takes a moment for bounds to be updated
}
if(oldBounds && newBounds){
offset.x += oldBounds[0]-newBounds[0];
offset.y += oldBounds[3]-newBounds[3];
offset.norm = true;
}
}
if(to_layer.parent.artboards!=null){ // only if top level layer
try{
this.shift_layer(to_layer, offset.x, offset.y);
}catch(e){
alert("shift layer failed");
}
}
if(doInnerPadding)this.innerPadLayer(to_layer, docW, docH);
return to_layer;
},
innerPadLayer: function(layer, docW, docH){
docW = Math.round(docW * 100) / 100;
docH = Math.round(docH * 100) / 100;
for(var i=0; i<layer.pageItems.length; ++i){
var item = layer.pageItems[i];
var bounds = item.visibleBounds;
// round to two decimal points
var l = Math.round(bounds[0] * 100) / 100;
var b = Math.round(bounds[1] * 100) / 100;
var r = Math.round(bounds[2] * 100) / 100;
var t = Math.round(bounds[3] * 100) / 100;
var w = (r - l);
var h = (b - t);
if(w>docW-1 && h>docH-1){
var scaleX = (w-1) / w * 100; // resize takes percentage values
var scaleY = (h-1) / h * 100;
item.resize(scaleX, scaleY, null, null, null, null, null, Transformation.CENTER);
}
}
for(var i=0; i<layer.layers.length; ++i){
innerPadLayer(layer.layers[i], docW, docH);
}
},
rect_equal: function(rect1, rect2) {
return rect1[0]==rect2[0] && rect1[1]==rect2[1] && rect1[2]==rect2[2] && rect1[3]==rect2[3] ;
},
copy_items: function(from_list, to_layer) {
var visWas = to_layer.visible;
to_layer.visible = true;
for(var i=0; i<from_list.length; ++i){
var item = from_list[i].duplicate(to_layer, ElementPlacement.PLACEATEND);
/*if(shiftX!=0 || shiftY!=0){
item.translate(shiftX, shiftY)
}*/
}
to_layer.visible = visWas;
},
shift_layer: function(layer, shiftX, shiftY) {
this.shift_items(layer.pageItems, shiftX, shiftY);
// copy backwards for correct z-ordering
for(var i=layer.layers.length-1; i>=0; --i){
this.shift_layer(layer.layers[i], shiftX, shiftY)
}
},
shift_items: function(items, shiftX, shiftY) {
if(shiftX==undefined)shiftX = 0;
if(shiftY==undefined)shiftY = 0;
for(var i=0; i<items.length; ++i){
items[i].translate(shiftX, shiftY)
}
},
hide_all_layers: function() {
var n = docRef.layers.length;
for(var i=0; i<n; ++i) {
layer = docRef.layers[i];
lyr_name = layer.name;
// any layers that start with + are always turned on
if (this.isAdditionalLayer(layer)) {
layer.visible = true;
} else {
layer.visible = false;
}
}
},
get_shown_layers: function() {
var shown = []
var n = docRef.layers.length;
for(var i=0; i<n; ++i) {
layer = docRef.layers[i];
if(layer.visible){
shown.push(i);
}
}
return shown;
},
get_layer_bounds: function(layer) {
var rect;
var items = layer.pageItems;
for(var i=0; i<items.length; ++i){
var item = items[i];
if(item.guides){
continue;
}
var visBounds = item.visibleBounds;
if(visBounds==null)continue;
if(rect==null){
rect = visBounds;
}else{
if(rect[0]>visBounds[0]){
rect[0] = visBounds[0];
}
if(rect[1]<visBounds[1]){
rect[1] = visBounds[1];
}
if(rect[2]<visBounds[2]){
rect[2] = visBounds[2];
}
if(rect[3]>visBounds[3]){
rect[3] = visBounds[3];
}
}
}
for(var i=0; i<layer.layers.length; ++i){
var childRect = this.get_layer_bounds(layer.layers[i]);
if(childRect==null)continue;
if(rect==null){
rect = childRect;
}else{
if(rect[0]>childRect[0]){
rect[0] = childRect[0];
}
if(rect[1]<childRect[1]){
rect[1] = childRect[1];
}
if(rect[2]<childRect[2]){
rect[2] = childRect[2];
}
if(rect[3]>childRect[3]){
rect[3] = childRect[3];
}
}
}
return rect;
},
intersects: function(rect1, rect2) {
return !( rect2[0] > rect1[2] ||
rect2[1] < rect1[3] ||
rect2[2] < rect1[0] ||
rect2[3] > rect1[1]);
},
show_layers: function(layerIndices) {
var n = layerIndices.length;
for(var i=0; i<n; ++i) {
layer = docRef.layers[layerIndices[i]];
layer.visible = true;
}
}
};
multi_exporter.init();
@grayassociates
Copy link

grayassociates commented Oct 11, 2021

Hi there. I have recently used this script and it works perfectly and is what we are after, but I just have a small question, is there a way of adding in the original document name as the prefix to the exported files?

A client is wanting to have production colour illustrations as single svg files for there website, so we have the original file that is named after the product code e.g AA1234, which will have several colours. When running the script the exported files will be named same as the layers, which are the individual colours (blue.svg) and we would like to have the document name adding to the beginning so it will be AA1234_blue.svg.

There are thousands of files so the more automated the better.

@TomByrne
Copy link
Author

@grayassociates

It doesn't look like this is supported (I haven't looked at this script for a loooong time).

My Smart Export project, which grew out of this, does support this kind of thing though. It's freely available on github, but also isn't supported anymore.

@grayassociates
Copy link

@TomByrne

Thanks for your reply and help much appreciated, I will take a look at your Smart Export project.

@SekaiMiller
Copy link

SekaiMiller commented Nov 20, 2021

@TomByrne
Love the script!!! I just have a little question. Is there a way I can save the images in a higher resolution/quality?
It will be awesome if you could help me with this! Thanks!

@TomByrne
Copy link
Author

@SekaiMiller
I don't really support this any more, but it looks like the scale factor is supported by jpg, png and png24.
If you need more than this, the Smart Export plugin exposes all options in the API.

@SekaiMiller
Copy link

@TomByrne Thank you!!! I will take a look at it!

@Anotherbeardedguy
Copy link

@TomByrne Thanks for the script. is it possible to export each layer with the artboards boundaries?

@KittyDreadful
Copy link

@TomByrne Any chance you're back to supporting this?

@TomByrne
Copy link
Author

TomByrne commented May 5, 2022

@KittyDreadful - I'm afraid not, I doubt very much that I ever will.

@jamshedakber
Copy link

Hi Tom,

I am facing an issue with script when i am using it, there is no option for svg export in the window, please guide on it.

Thanks

@skilltimate
Copy link

@TomByrne This is not working in 2023 version, any help is appreciated.

@ingridada
Copy link

Hi Tom,
Thanks this script is a huge helper for me. Thanks!

@Swapot
Copy link

Swapot commented Nov 29, 2023

@TomByrne Does anyone have this or Tom's IllustratorSmartExport extension working on Illustrator CS6? I copy/pasted the code into (Mac) Script Editor, saved as MultiExporter.jsx and dropped into Scripts folder on Mac. Illustrator recognizes it, but returnes following error when I try to run it:
"This script contains uncompiled changes and cannot be run."

I also tried downloading and installing the latest version of Tom's Illustrator Smart Export. ZXPInstaller only works with CC, so I tried Extension Manager. An error says it does not contain valid signature.

Any workarounds? This would save me MANY hours on a current project in which I need to make .eps, .png, .jpg, and .svg from multiple layers and multiple files.

Any advice welcome!

@sbgraphic
Copy link

Hi, I am not sure about "smart layer export", but I've used this script "MultiExporter.jsx" a few years ago and it was really useful. I've just tried it again and yes, it still work on CS6/High Sierra for me.
Open you .ai file , then go to Illustrator Files menu > Scripts… > Other scripts… then choose the "MultiExporter.jsx" file from your finder location. A script popup menu should appear with serveral options…

ex-1
ex-2

@Swapot
Copy link

Swapot commented Dec 9, 2023

Thanks sbgraphic - I did get it working! I think I was corrupting the script by trying to copy/paste it into AppleScript app to make the javascript file. First time I've ever tried a script from GitHub. Didn't realize I could just right-click and "save-as" directly from GitHub.

Very grateful for this script - many thanks to Matthew Ericson and Tom Byrne. Glad the New York Times needed this and Matthew was willing to share.

I'm not sure what added options Tom built into the IllustratorSmartExport extension (which I assume was built upon this javascript), but I haven't been able to get it installed.

I've, tried the Adobe Extension Manager that came with CS6 Design Suite, but I gets part way through installing (just past accepting the extensions license agreement) and then it returns the following error:

Screen Shot 2023-12-09 at 7 57 16 AM

I've tried other zxp installers, but they all seem made for CC only.

I've tried installing it manually, but I don't know where to put it. I found other instruction online for manual installation of a extension for CS6, but they seem to be only for Photoshop. More than one says that the unzipped extension needs to be placed here:

Mac: /Users/username/Library/Application Support/Adobe/CEP/extensions

However, no such folder exists on my Mac. I created a folder named "CEP", but that didn't work either.

Another site said I would need to run a command ln in Terminal afterward manual installation. I didn't try that because I don't want to do something I don't know how to undo, and I really need Illustrator to be working for a project I'm currently on.

Does anyone who has this Extension working on Illustrator CS6? If so, do you know what folder it resides in? Any other advice on trying to manually install a .zxp file for CS apps welcome. Thanks.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment