-
-
Save brandondurham/b04a2cb6baff6931a5463239ca3e9c83 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#target illustrator | |
// Base vars | |
var doc = app.activeDocument; | |
// Ensure the document is using the expected coordinates | |
with (doc) { | |
pageOrigin = [0, doc.height]; | |
rulerOrigin = [0, doc.height]; | |
} | |
/** | |
* Settings | |
* @type {Object} | |
*/ | |
var HCo = { | |
// Action-wide settings | |
settings: { | |
// Mutable by user | |
renameBackgrounds: false, | |
// Immutable by user | |
tileCoordinates: [], | |
backgroundLayerName: 'background', | |
baseLayer: null, | |
baseLayerName: 'SOURCE_LAYER', | |
firstArtboardName: null, | |
baseArtboard: null, | |
baseArtboardName: 'SOURCE_ARTBOARD', | |
docRect: null, | |
totalTiles: null, | |
processSteps: 5, | |
tilesProcessed: 0, | |
// Form fields that get translated to settings | |
fields: { | |
cancelButton: null, | |
options: null, | |
progressBar: null, | |
progressLabel: null, | |
renameBackgrounds: { | |
no: { | |
text: 'I created backgrounds for each tile named “background”', | |
value: null | |
}, | |
yes: { | |
text: 'Attempt to locate and rename existing objects as backgrounds', | |
value: true | |
} | |
}, | |
startButton: null | |
} | |
}, | |
/** | |
* Failure message | |
* @method failure | |
* @param {String} head Header for the alert | |
* @param {String} body Body message for the alert | |
* @return [Boolean] `false` by default | |
*/ | |
failure: function(head, body) { | |
alert('🚑 ' + head + ' 🚑\n' + body); | |
return false; | |
}, | |
/** | |
* Check for just one artboard and layer | |
* @method artboardAndLayerCheck | |
* @return {Boolean} | |
*/ | |
artboardAndLayerCheck: function() { | |
// Ensure there are no extra layers or artboards | |
if (doc.artboards.length > 1 || doc.layers.length > 1) { | |
alert('Failed initial check.\nThere should be just 1 artboard and 1 layer.'); | |
return false; | |
} | |
return this; | |
}, | |
/** | |
* Check each group for a background path. If it doesn’t exist and the user has | |
* requested it, try to find one to repurpose. | |
* @method checkBackgrounds | |
*/ | |
checkBackgrounds: function() { | |
var layer = doc.activeLayer; | |
var groups = layer.groupItems; | |
var backgrounds = []; | |
var failedGroups = []; | |
var passedGroups = []; | |
var whichProcess = 'Checking for tile backgrounds…'; | |
this.settings.totalTiles = groups.length; | |
// Loop through all groups in the layer | |
for (var i = 0; i < groups.length; i++) { | |
var group = groups[i]; | |
var items = group.pathItems; | |
// Does the group contain pathItems? | |
if (items.length) { | |
// Get the last pathItem in the stack | |
var lastItem = items[items.length - 1]; | |
// If the user chose to rename existing pathItems to “background” | |
if (HCo.settings.renameBackgrounds === true) { | |
lastItem.name = this.settings.backgroundLayerName; | |
backgrounds.push(lastItem); | |
passedGroups.push(group); | |
HCo.updateProgress(whichProcess, 1); | |
// If the user chose to use existing layers named “background” | |
} else if (HCo.settings.renameBackgrounds === false) { | |
// … but the last pathItem ISN’T named “background” | |
if (lastItem.name !== this.settings.backgroundLayerName) { | |
failedGroups.push(group); | |
} else { | |
backgrounds.push(lastItem); | |
passedGroups.push(group); | |
HCo.updateProgress(whichProcess, 1); | |
} | |
} | |
} | |
else failedGroups.push(group); | |
} | |
// Check lengths for both background and groups collections. | |
// If they don’t match, fail and show a message | |
if (groups.length !== backgrounds.length || failedGroups.length) { | |
for (var i = 0; i < passedGroups.length; i++) { | |
passedGroups[i].hidden = true; | |
passedGroups[i].locked = true; | |
} | |
return HCo.failure('Missing background layer.', 'Every group should have a bottommost layer named “background”.\n\nI’m locking and hiding all passing groups. Please fix those remaining.'); | |
} | |
return this; | |
}, | |
/** | |
* Format the name using the file name | |
* @method nameFromFilename | |
* @return {String} Formatted name | |
*/ | |
nameFromFilename: function(index) { | |
return doc.name.replace('.ai', '').replace(' ', '-').toLowerCase() + '_' + index; | |
}, | |
/** | |
* Select everything on the artboard and outline the text | |
* @method outlineText | |
* @return {null} | |
*/ | |
outlineText: function() { | |
// Select only artboard | |
doc.artboards.setActiveArtboardIndex(0); | |
// Select everything on the artboard | |
doc.selectObjectsOnActiveArtboard(); | |
// Find all textFrames and outline the text | |
var textFrames = doc.textFrames; | |
var textFramesCount = textFrames.length - 1; | |
for (var t = textFramesCount; t >= 0; t--) { | |
var text = textFrames[t]; | |
text.createOutline(); | |
} | |
HCo.updateProgress('Outlining all text…', this.settings.totalTiles); | |
return this; | |
}, | |
/** | |
* Convert groups to layers | |
* @method convertGroupsToLayers | |
* @return {null} | |
*/ | |
convertGroupsToLayers: function() { | |
var groupsCount = this.settings.baseLayer.groupItems.length; | |
var whichProcess = 'Converting groups to layers…'; | |
// Cycle through all groups in the layer, create a new | |
// layer, and move the group to the new layer | |
for (var i = groupsCount - 1; i >= 0; i--) { | |
var group = this.settings.baseLayer.groupItems[i]; | |
var layer = doc.layers.add(); | |
var layerName = HCo.nameFromFilename(i); | |
if (i === 0) this.settings.firstArtboardName = layerName; | |
layer.name = layerName; | |
group.move(layer, ElementPlacement.PLACEATEND); | |
layer.visible = false; | |
HCo.updateProgress(whichProcess, 1); | |
} | |
// Remove the original, now-empty layer | |
this.settings.baseLayer.remove(); | |
return this; | |
}, | |
/** | |
* Get the “visible” bounds of the group | |
* @method getBounds | |
* @return {Array} Array containing the top, left, height, and width values | |
*/ | |
getBounds: function(group) { | |
var bounds = []; | |
for (var i = group.pathItems.length - 1; i >= 0; i--) { | |
if (group.pathItems[i].typename === "PathItem" && group.pathItems[i].clipping) { | |
bounds = group.pathItems[i].visibleBounds; | |
} | |
} | |
// controlBounds is for groups without a clipping ask and | |
// visibleBounds is for groups with a clipping mask. | |
return bounds.length === 0 ? group.controlBounds : bounds; | |
}, | |
/** | |
* Gets the position for all four group points and writes | |
* them to a textBox in the layer | |
* @method addPositionDataToGroup | |
* @return {Boolean} | |
*/ | |
addPositionDataToGroup: function(layer) { | |
// Must be working in a layer with only one group | |
var group = layer.groupItems[0]; | |
// Get the dimensions of the active group and push to the tileCoordinates | |
// array for later use with generating a background layer | |
var currentGroupRect = HCo.getBounds(group); | |
this.settings.tileCoordinates.push(currentGroupRect); | |
// JSON-formatted string to write | |
var top = Math.round(currentGroupRect[1]); | |
var left = Math.round(currentGroupRect[0]); | |
var width = Math.round(currentGroupRect[2] - currentGroupRect[0]); | |
var height = Math.round(currentGroupRect[1] - currentGroupRect[3]); | |
// Create and add the caption to the layer | |
var pathRef = layer.pathItems.rectangle(top - 10, left + 10, width - 20, height - 20); | |
var caption = layer.textFrames.areaText(pathRef); | |
var tileData = 'name:' + layer.name + '-top:' + Math.abs(top) + '-left:' + left + '-width:' + width + '-height:' + height; | |
caption.name = 'data'; | |
caption.contents = tileData; | |
caption.textRange.hyphenation = false; | |
caption.textRange.size = 15; | |
caption.textRange.leading = 18; | |
caption.textRange.characterAttributes.textFont = app.textFonts.getByName("OperatorMonoSSm-Book"); | |
caption.move(layer, ElementPlacement.PLACEATBEGINNING); | |
return true; | |
}, | |
/** | |
* Find an artboard using its name | |
* @method setActiveArtboardBy | |
* @param {null} | |
*/ | |
setActiveArtboardBy: function(name) { | |
var ab = doc.artboards.getByName(name); | |
for (i = 0; i < doc.artboards.length; i++) { | |
if (doc.artboards[i] === ab) { | |
doc.artboards.setActiveArtboardIndex(i); | |
break; | |
} | |
} | |
}, | |
/** | |
* Convert all layers to and artboard while retaining position | |
* @method layersToArtboard | |
* @return {null} | |
*/ | |
layersToArtboard: function() { | |
if (app.documents.length === 0) { return; } | |
var layerCount = doc.layers.length; | |
var whichProcess = 'Converting layers to artboards and adding position data…'; | |
for (var i = 0; i < layerCount; i++) { | |
var layer = doc.layers[i]; | |
var layerIsBackground = layer.name === this.settings.backgroundLayerName; | |
// Show the layer if necessary | |
layer.visible = true; | |
// Select everything in the layer | |
layer.hasSelectedArtwork = true; | |
if (doc.visibleBounds[2] === 0) { | |
// ignore empty layers ** Tricks by David Deraedt | |
continue; | |
} | |
// Add the textBox with position data | |
if (!layerIsBackground) HCo.addPositionDataToGroup(layer); | |
// Create and resize the artboard | |
var newArtboard = doc.artboards.add([0, 0, 100, -100]); | |
var indexAB = doc.artboards.getActiveArtboardIndex(); | |
newArtboard.name = layerIsBackground ? this.settings.backgroundLayerName : HCo.nameFromFilename(i); | |
doc.fitArtboardToSelectedArt(indexAB); | |
layer.hasSelectedArtwork = false; | |
HCo.updateProgress(whichProcess, 1); | |
// Run some cleanup once the loop has completed | |
if (i === layerCount - 1) { | |
$.sleep(100); | |
// Remove the original now-empty artboard | |
this.settings.baseArtboard.remove(); | |
HCo.setActiveArtboardBy(this.settings.firstArtboardName); | |
var firstAB = doc.artboards.getActiveArtboardIndex(); | |
doc.layers[0].hasSelectedArtwork = true; | |
doc.fitArtboardToSelectedArt(firstAB); | |
layer.hasSelectedArtwork = false; | |
return this; | |
} | |
}; | |
return false; | |
}, | |
/** | |
* Creates a new artboard matching the size of the original and fills it with | |
* boxes that match the size and position of the original tiles. | |
* @method makeBackground | |
*/ | |
makeBackground: function() { | |
// Current artboard’s dimensions | |
var ax1 = this.settings.docRect[0]; | |
var ay1 = this.settings.docRect[1]; | |
var ax2 = this.settings.docRect[2]; | |
var ay2 = this.settings.docRect[3]; | |
// Settings for artboard positioning | |
var artboardGutter = 48; | |
var positionDifference = (ax2 - ax1) + artboardGutter; | |
// New artboard’s positioning | |
var bx1 = ax1 - positionDifference; | |
var by1 = ay1; | |
var bx2 = ax2 - positionDifference; | |
var by2 = ay2; | |
var coordinates = [ bx1 , by1 , bx2 , by2 ]; | |
// Add new artboard using coordinates from above | |
var bgArtboard = doc.artboards.add(coordinates); | |
bgArtboard.name = this.settings.backgroundLayerName; | |
// And a new layer to place the tiles on… | |
var bgLayer = doc.layers.add(); | |
bgLayer.name = this.settings.backgroundLayerName; | |
bgLayer.move(doc, ElementPlacement.PLACEATEND); | |
// Loop through `tileCoordinates` and add tiles for each coordinate | |
var tiles = this.settings.tileCoordinates; | |
var totalTiles = tiles.length; | |
for (var i = 0; i < totalTiles; i++) { | |
// Shrinking the tile background by one pixel on all sides in order to | |
// ensure that there is no background peeking out | |
var top = Math.round(tiles[i][1] - 1); | |
var left = Math.round(tiles[i][0] - positionDifference + 1); | |
var width = Math.round(tiles[i][2] - tiles[i][0] - 2); | |
var height = Math.round(tiles[i][1] - tiles[i][3] - 2); | |
// Add the tile using the above coordinates | |
var tile = bgLayer.pathItems.rectangle(top, left, width, height); | |
var tileColor = new RGBColor; | |
tile.fillColor = tileColor; | |
} | |
return this; | |
}, | |
/** | |
* Get all children of any type | |
* @method getChildAll | |
* @param {Node} obj The parent node to start with | |
* @return [Array] | |
*/ | |
getChildAll: function(obj) { | |
var childsArr = new Array(); | |
if (obj.pageItems && obj.pageItems.length) { | |
for (var i = 0; i < obj.pageItems.length; i++) childsArr.push(obj.pageItems[i]); | |
return childsArr; | |
} | |
return null; | |
}, | |
/** | |
* Upgroup everything below the focused node | |
* @method ungroup | |
* @param {Node} obj The node to start with | |
*/ | |
ungroup: function(obj) { | |
var elements = HCo.getChildAll(obj); | |
if (elements.length < 1) { | |
obj.remove(); | |
return; | |
} else { | |
for (var i = 0; i < elements.length; i++) { | |
try { | |
if (elements[i].parent.typename != "Layer") elements[i].moveBefore(obj); | |
if (elements[i].typename == "GroupItem") HCo.ungroup(elements[i]); | |
} catch(e) {} | |
} | |
} | |
}, | |
/** | |
* Toggle on or off all layers’ visibility | |
* @method toggleAllLayerVisibility | |
* @param {Boolean} isVisible | |
*/ | |
toggleAllLayerVisibility: function(isVisible) { | |
var layers = doc.layers; | |
var layerCount = layers.length; | |
for (var i = 0; i < layerCount; i++) layers[i].visible = isVisible; | |
}, | |
/** | |
* Find the closes parent `Layer` | |
* @method getParentLayer | |
* @param {Node} node The node to start with | |
* @return [Node] | |
*/ | |
getParentLayer: function(node) { | |
var escapeHatch = 10; | |
while (node.parent && escapeHatch > 0) { | |
node = node.parent; | |
if (node.typename === 'Layer') | |
return node; | |
} | |
return null; | |
}, | |
/** | |
* Crop all tiles with clipping masks | |
* @method cropTiles | |
*/ | |
cropTiles: function() { | |
var whichProcess = 'Cropping the tiles…'; | |
// Load the action file relative to the location of this script | |
var thisFile = new File($.fileName); | |
var basePath = thisFile.path; | |
app.loadAction(new File(basePath + '/actions/H&Co.aia')); | |
HCo.toggleAllLayerVisibility(true); | |
doc.selection = null; | |
app.executeMenuCommand("Clipping Masks menu item"); | |
var clippingPath; | |
var escapeHatch = 100; | |
while (doc.selection.length != 0 && escapeHatch > 0) { | |
escapeHatch--; | |
// Reference the clipping mask | |
clippingPath = doc.selection[0]; | |
doc.selection = null; | |
if (!clippingPath.clipping === true) continue; | |
clippingPath.name = 'clipping'; | |
// Release the clipping mask | |
clippingPath.clipping = false; | |
// Parent layer | |
var layer = HCo.getParentLayer(clippingPath); | |
layer.selected = true; | |
// Ungroup all groups and subgroups | |
HCo.ungroup(layer); | |
// Create group to move all art to and place it in order above the background | |
var artGroup = layer.groupItems.add(); | |
artGroup.name = 'art'; | |
artGroup.moveAfter(clippingPath); | |
// Loop through all layer contents and move art into the artGroup | |
var items = HCo.getChildAll(layer); | |
var itemsLength = items.length; | |
for (var i = 0; i < itemsLength; i++) { | |
try { | |
var item = items[i]; | |
if (item.name !== this.settings.backgroundLayerName && item.name !== 'clipping' && item.name !== 'data') { | |
item.move(artGroup, ElementPlacement.PLACEATBEGINNING); | |
} | |
} catch(e) {} | |
} | |
// Select the artGroup and clippingGroup | |
artGroup.selected = true; | |
clippingPath.selected = true; | |
app.doScript('Crop gallery tile', 'H&Co'); | |
// Rename cropped group | |
doc.selection[0].name = 'art'; | |
// Ensure everything is deselected for the next command | |
doc.selection = null; | |
// Ungroup all groups and subgroups | |
HCo.ungroup(layer); | |
// Now just make sure we prepped all clipping masks | |
app.executeMenuCommand("Clipping Masks menu item"); | |
HCo.updateProgress(whichProcess, 1); | |
if (doc.selection.length === 0) { | |
app.unloadAction('H&Co',''); | |
return this; | |
} | |
} | |
return false; | |
}, | |
updateProgress: function(whichProcess, tilesProcessed) { | |
this.settings.fields.progressLabel.text = whichProcess; | |
this.settings.tilesProcessed = this.settings.tilesProcessed + tilesProcessed; | |
this.settings.fields.progressBar.value = this.settings.tilesProcessed / (this.settings.totalTiles * this.settings.processSteps) * 100; | |
this.dialog.update(); | |
}, | |
setup: function() { | |
this.settings.baseLayer = doc.layers[0]; | |
this.settings.firstArtboardName = null; | |
this.settings.baseArtboard = doc.artboards[0]; | |
this.settings.docRect = this.settings.baseArtboard.artboardRect; | |
// Set the current artboard and layer name so we can identify later | |
this.settings.baseArtboard.name = this.settings.baseArtboardName; | |
this.settings.baseLayer.name = this.settings.baseLayerName; | |
return this; | |
}, | |
/** | |
* Save the options set by the user in `settingsWindow` | |
* @method saveSettings | |
*/ | |
saveSettings: function() { | |
this.settings.renameBackgrounds = !this.settings.fields.renameBackgrounds.no.value; | |
}, | |
/** | |
* Settings window presetned for user input | |
* @method settingsWindow | |
*/ | |
settingsWindow: function() { | |
this.dialog = new Window('dialog', 'H&Co Gallery Prep'); | |
var rowHeight = 30; | |
var labelWidth = 85; | |
var fieldWidth = 300; | |
var progressBarWidth = 400; | |
// Rename backgrounds | |
this.settings.fields.options = this.dialog.add('group', undefined, ''); | |
this.settings.fields.options.margins = [12, 12, 12, 12]; | |
this.settings.fields.options.orientation = 'column'; | |
this.settings.fields.options.alignChildren = ['left', 'top']; | |
// Tile backgrounds label | |
var backgroundLabel = this.settings.fields.options.add('statictext', undefined, 'Tile backgrounds:'); | |
backgroundLabel.size = [progressBarWidth, rowHeight]; | |
// Tile backgrounds radio group | |
var radios = this.settings.fields.options.add('group', undefined, ''); | |
radios.alignChildren = 'left'; | |
radios.orientation = 'column'; | |
// Tile backgrounds options | |
this.settings.fields.renameBackgrounds.yes = radios.add('radiobutton', undefined, this.settings.fields.renameBackgrounds.yes.text); | |
this.settings.fields.renameBackgrounds.yes.value = true; | |
this.settings.fields.renameBackgrounds.no = radios.add('radiobutton', undefined, this.settings.fields.renameBackgrounds.no.text); | |
// ————————————————————————————————————————————————————————————————————— | |
// PROGRESS BAR | |
// ————————————————————————————————————————————————————————————————————— | |
this.settings.fields.progressView = this.dialog.add('group', undefined, ''); | |
this.settings.fields.progressView.orientation = 'column'; | |
this.settings.fields.progressView.alignChildren = 'left'; | |
this.settings.fields.progressView.maximumSize.height = 0; | |
// Text | |
this.settings.fields.progressLabel = this.settings.fields.progressView.add('statictext', undefined, ''); | |
this.settings.fields.progressLabel.size = [progressBarWidth, rowHeight]; | |
// Progress bar | |
this.settings.fields.progressBar = this.settings.fields.progressView.add('progressbar', undefined, 0, 100); | |
this.settings.fields.progressBar.size = [progressBarWidth, 12]; | |
// ————————————————————————————————————————————————————————————————————— | |
// BUTTONS | |
// ————————————————————————————————————————————————————————————————————— | |
var buttons = HCo.dialog.add('group', undefined, ''); | |
buttons.orientation = 'row'; | |
// Cancel button | |
this.settings.fields.cancelButton = buttons.add('button', undefined, 'Cancel', { name: 'cancel' }); | |
this.settings.fields.cancelButton.onClick = function() { HCo.dialog.close(); }; | |
// Start button | |
this.settings.fields.startButton = buttons.add('button', undefined, 'Start', { name: 'ok' }); | |
this.settings.fields.startButton.active = true; | |
this.settings.fields.startButton.onClick = function() { | |
HCo.saveSettings(); | |
try { | |
HCo.run(); | |
} catch(e) { | |
alert(e); | |
} | |
}; | |
HCo.dialog.show(); | |
}, | |
/** | |
* Get the file preparation process running… | |
* @method run | |
*/ | |
run: function () { | |
this.settings.fields.options.maximumSize.height = 0; | |
this.settings.fields.progressView.maximumSize.height = 100; | |
this.settings.fields.startButton.enabled = false; | |
this.dialog.layout.layout(true); | |
HCo | |
.artboardAndLayerCheck() | |
.checkBackgrounds() | |
.setup() | |
.outlineText() | |
.convertGroupsToLayers() | |
.layersToArtboard() | |
.cropTiles() | |
.makeBackground(); | |
HCo.dialog.close(); | |
}, | |
init: function() { | |
this.settingsWindow(); | |
} | |
} | |
HCo.init(); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment