/* TrimMasks.jsx for Adobe Illustrator Description: Automatic trimming of all clipping groups in a document using Pathfinder > Crop Date: March, 2020 Author: Sergey Osokin, email: hi@sergosokin.ru Installation: https://github.com/creold/illustrator-scripts#how-to-run-scripts ************************************************************************************************* * WARNING: Don't put this script in the action slot for a quick run. It will freeze Illustrator * ************************************************************************************************* Release notes: 0.1 Initial version. 0.2 Added "isSaveMask" boolean flag for save the filled mask path, fixed the live text masks. 0.3 Fixed a cropping bug when is even-odd fill-rule Donate (optional): If you find this script helpful, you can buy me a coffee - via Buymeacoffee: https://www.buymeacoffee.com/aiscripts - via Donatty https://donatty.com/sergosokin - via DonatePay https://new.donatepay.ru/en/@osokin - via YooMoney https://yoomoney.ru/to/410011149615582 NOTICE: Tested with Adobe Illustrator CC 2019-2025 (Mac/Win). This script is provided "as is" without warranty of any kind. Free to use, not for sale Released under the MIT license http://opensource.org/licenses/mit-license.php Check my other scripts: https://github.com/creold */ //@target illustrator app.preferences.setBooleanPreference('ShowExternalJSXWarning', false); // Fix drag and drop a .jsx file $.localize = true; // Enabling automatic localization app.userInteractionLevel = UserInteractionLevel.DONTDISPLAYALERTS; function main() { var CFG = { aiVers: parseInt(app.version), actionSet: 'Trim-Mask', actionName: 'Trim-Mask', actionPath: Folder.myDocuments + '/Adobe Scripts/', isSaveMask: true, // true — save the filled mask path when trimming, false - not save over: 10 // When the number of clip groups >, full-screen mode is enabled }, LANG = { errDoc: { en: 'Error\nOpen a document and try again', ru: 'Ошибка\nОткройте документ и запустите скрипт' }, errVers: { en: 'Error\nSorry, script only works in Illustrator CS6 and later', ru: 'Ошибка\nСкрипт работает в Illustrator CS6 и выше' } }, itemAttr = { mOpacity: 100, mBlending: BlendModes.NORMAL }; if (!documents.length) { alert(LANG.errDoc); return; } if (CFG.aiVers < 16) { alert(LANG.errVers); return; } if (selection.typename == 'TextRange') return; if (!Folder(CFG.actionPath).exists) Folder(CFG.actionPath).create(); var doc = app.activeDocument, userView = doc.views[0].screenMode; // Generate action var actionStr = [' /version 3', '/name [' + CFG.actionSet.length + ' ' + ascii2Hex(CFG.actionSet) + ']', '/actionCount 1', '/action-1 {', '/name [' + CFG.actionName.length + ' ' + ascii2Hex(CFG.actionName) + ']', ' /keyIndex 0', ' /colorIndex 0', ' /isOpen 1', ' /eventCount 1', ' /event-1 {', ' /useRulersIn1stQuadrant 0', ' /internalName (ai_plugin_pathfinder)', ' /localizedName [ 10', ' 5061746866696e646572', ' ]', ' /isOpen 0', ' /isOn 1', ' /hasDialog 0', ' /parameterCount 1', ' /parameter-1 {', ' /key 1851878757', ' /showInPalette 4294967295', ' /type (enumerated)', ' /name [ 4', ' 43726f70', ' ]', ' /value 9', ' }', ' }', '}'].join(''); createAction(actionStr, CFG.actionSet, CFG.actionPath); if (selection.length == 0) app.executeMenuCommand('selectall'); var groups = getGroups(selection), clipCounter = countClipGroups(groups); // When the number of clip groups >, full-screen mode is enabled if (clipCounter > CFG.over) doc.views[0].screenMode = ScreenMode.FULLSCREEN; try { processing(groups, itemAttr, CFG.isSaveMask, CFG.actionSet, CFG.actionName); } catch (e) {} app.unloadAction(CFG.actionSet, ''); deselect(); doc.views[0].screenMode = userView; app.userInteractionLevel = UserInteractionLevel.DISPLAYALERTS; } function createAction(str, set, path) { var f = new File('' + path + '/' + set + '.aia'); f.open('w'); f.write(str); f.close(); app.loadAction(f); f.remove(); } function ascii2Hex(hex) { return hex.replace(/./g, function (a) { return a.charCodeAt(0).toString(16) }); } // Get only groups from selection or document function getGroups(items) { var childsArr = [], currItem; for (var i = 0, iLen = items.length; i < iLen; i++) { currItem = items[i]; if (isGroup(currItem)) childsArr.push(currItem); } return childsArr; } // Check the item type function isGroup(item) { return item.typename === 'GroupItem'; } // Count all clipping groups function countClipGroups(items) { var counter = 0; for (var i = 0, iLen = items.length; i < iLen; i++) { if (isGroup(items[i]) && items[i].clipped) counter++; if (isGroup(items[i]) && !items[i].clipped) counter += countClipGroups(items[i].pageItems); } return counter; } function processing(items, attr, isSaveMask, actionSet, actionName) { var currItem; for (var i = 0, iLen = items.length; i < iLen; i++) { deselect(); currItem = items[i]; if (isGroup(currItem) && currItem.clipped) { fixFillRule(currItem); trim(currItem, attr, isSaveMask, actionSet, actionName); } if (isGroup(currItem) && !currItem.clipped) { if (currItem.pageItems.length == 1 && isGroup(currItem.pageItems[0])) { var singleItem = currItem.pageItems[0]; singleItem.moveBefore(currItem); trim(singleItem, attr, isSaveMask, actionSet, actionName); } else { processing(currItem.pageItems, attr, isSaveMask, actionSet, actionName); } } } } function deselect() { selection = null; redraw(); } // If Attributes > Even-Odd is true, then the Pathfinder > Crop has an incorrect result function fixFillRule(item) { for (var i = 0, piLen = item.pageItems.length; i < piLen; i++) { var currItem = item.pageItems[i]; if (isGroup(currItem)) fixFillRule(currItem); if (currItem.typename === 'CompoundPathItem') currItem = currItem.pathItems[0]; currItem.evenodd = false; } } function trim(item, attr, isSaveMask, actionSet, actionName) { // Save opacity & blendingMode properties if (item.opacity < 100) attr.mOpacity = item.opacity; if (item.blendingMode != BlendModes.NORMAL) attr.mBlending = item.blendingMode; // If contains live text outlineText(item); // Trick for Compound path created from groups of paths item.selected = true; compoundPathsNormalize(selection); if (isSaveMask) { duplicateFilledMask(item, attr.mOpacity, attr.mBlending); } item.selected = true; // Because the duplicate mask is select behind if (isSaveMask) selection = selection[0]; app.doScript(actionName, actionSet); if (selection.length > 0) { // Restore opacity to child path if (attr.mOpacity < 100) selection[0].opacity = attr.mOpacity; // Restore blendingMode to child path if (attr.mBlending != BlendModes.NORMAL) selection[0].blendingMode = attr.mBlending; } attr.mOpacity = 100; attr.mBlending = BlendModes.NORMAL; } function outlineText(group) { try { for (var i = 0, piLen = group.pageItems.length; i < piLen; i++) { var currItem = group.pageItems[i], itemType = currItem.typename; if (itemType === 'TextFrame') { var textColor = currItem.textRange.fillColor; currItem.selected = true; app.executeMenuCommand('outline'); for (var j = 0, selLen = selection.length; j < selLen; j++) { if (selection[j].typename === 'PathItem') selection[j].fillColor = textColor; if (selection[j].typename === 'CompoundPathItem') { // Trick for Compound path created from groups of paths if (selection[j].pathItems.length == 0) { var tempPath = selection[j].pathItems.add(); } selection[j].pathItems[0].fillColor = textColor; tempPath.remove(); } } deselect(); } if (isGroup(currItem)) outlineText(currItem); } } catch (e) {} } function ungroup(items) { for (var i = 0, iLen = items.length; i < iLen; i++) { if (isGroup(items[i])) { var j = items[i].pageItems.length; while (j--) { items[i].pageItems[0].locked = items[i].pageItems[0].hidden = false; items[i].pageItems[0].moveBefore(items[i]); } items[i].remove(); } } } // Trick for Compound path created from groups of paths function compoundPathFix(item) { selection = [item]; app.executeMenuCommand('noCompoundPath'); ungroup(selection); app.executeMenuCommand('compoundPath'); deselect(); } // From compoundFix.jsx by Alexander Ladygin https://github.com/alexander-ladygin function compoundPathsNormalize(items) { var i = items.length; while (i--) { if (isGroup(items[i])) { compoundPathsNormalize(items[i].pageItems); } else if (items[i].typename === 'CompoundPathItem') { compoundPathFix(items[i]); } } } function duplicateFilledMask(group, opacity, blending) { try { for (var i = 0, piLen = group.pageItems.length; i < piLen; i++) { var currItem = group.pageItems[i], itemType = currItem.typename, zeroPath = (itemType === 'CompoundPathItem') ? currItem.pathItems[0] : currItem; if ((itemType === 'PathItem' || itemType === 'CompoundPathItem') && zeroPath.clipping && zeroPath.filled) { var maskClone = currItem.duplicate(group, ElementPlacement.PLACEAFTER); // Restore opacity to child path if (opacity < 100) maskClone.opacity = opacity; // Restore blendingMode to child path if (blending != BlendModes.NORMAL) maskClone.blendingMode = blending; } } redraw(); } catch (e) {} } try { main(); } catch (e) {}