Created
February 9, 2022 13:51
-
-
Save sttk3/1727713c6c182c40df172112279fd2ba 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
/** | |
* @file trim strokes | |
* https://community.adobe.com/t5/illustrator-discussions/creating-a-sliced-path-dashed-dotted-path/m-p/12736845 | |
* @version 0.1.0 | |
* @author sttk3.com | |
* @copyright © 2022 sttk3.com | |
*/ | |
//@target 'illustrator' | |
//@targetengine 'com.sttk3.trimStroke' | |
$.localize = true ; | |
(function() { | |
if(app.documents.length <= 0) {return ;} | |
var doc = app.documents[0] ; | |
var sel = doc.selection ; | |
if(sel.length < 2) {return ;} | |
// reject when selected item is not a path, not a dashed stroke, or has a fill | |
var patternPathItem = /^PathItem$/ ; | |
var targetItems = filterItems(sel, function(aItem) { | |
return ( | |
patternPathItem.test(aItem.constructor.name) | |
&& !aItem.filled | |
) ; | |
}) ; | |
if(targetItems.length != 2) { | |
var message = { | |
en: 'There was no target. \nPlease select two strokes with no fill and try again.', | |
ja: '対象がありませんでした。\n塗りのない線を2つ選択してから実行してください。', | |
} | |
alert(message) ; | |
return ; | |
} | |
// generate a unique color | |
var tempSpot = doc.spots.add() ; | |
var tempColor = new SpotColor() ; | |
tempColor.spot = tempSpot ; | |
tempColor.tint = 100 ; | |
// subtract target strokes | |
var newSelection = [] ; | |
try { | |
var newItem1 = subtractStroke(doc, targetItems[0], targetItems[1], tempColor) ; | |
if(newItem1) {newSelection.push(newItem1) ;} | |
var newItem2 = expandDashedStroke(doc, targetItems[0], tempColor) ; | |
if(newItem2) {newSelection.push(newItem2) ;} | |
newItem2.move(newItem1, ElementPlacement.PLACEBEFORE) ; | |
} catch(e) { | |
alert(e) ; | |
return ; | |
} finally { | |
tempSpot.remove() ; | |
} | |
if(newSelection.length > 0) { | |
doc.selection = newSelection ; | |
app.executeMenuCommand('group') ; | |
} | |
})() ; | |
/** | |
* something like Array.filter | |
* @param {Array} targetItems Array or collection of targets, anything with length and index | |
* @param {Function} callback test function | |
* @return {Array} | |
*/ | |
function filterItems(targetItems, callback) { | |
var res = [] ; | |
for(var i = 0, len = targetItems.length ; i < len ; i++) { | |
if(i in targetItems) { | |
var val = targetItems[i] ; | |
if(callback.call(targetItems, val, i)) {res.push(val) ;} | |
} | |
} | |
return res ; | |
} | |
/** | |
* subtract mainItem from subItem | |
* @param {Document} doc target document | |
* @param {PathItem} mainItem dashed stroke to deduct | |
* @param {PathItem} subItem stroke to be subtracted | |
* @param {SpotColor} tempColor unique color. a marker to identify what is unnecessary | |
* @return {GroupItem} | |
*/ | |
function subtractStroke(doc, mainItem, subItem, tempColor) { | |
var tempSpot = tempColor.spot ; | |
// object to fill dash | |
var dashPath = mainItem.duplicate(mainItem, ElementPlacement.PLACEBEFORE) ; | |
dashPath.strokeColor = tempColor ; | |
dashPath.strokeCap = StrokeCap.BUTTENDCAP ; // make the dash length unchangeable | |
var backupPath ; | |
var oldUserInteractionLevel = app.userInteractionLevel ; | |
try { | |
app.userInteractionLevel = UserInteractionLevel.DONTDISPLAYALERTS ; | |
backupPath = subItem.duplicate(subItem, ElementPlacement.PLACEAFTER) ; | |
doc.selection = [dashPath] ; | |
app.executeMenuCommand('OffsetPath v22') ; // Outline Stroke | |
subItem.selected = true ; | |
app.executeMenuCommand('Make Planet X') ; // Live Paint Make | |
app.executeMenuCommand('Expand Planet X') ; // Live Paint Expand | |
app.redraw() ; | |
// reject unexpected structures | |
var tempGroups = doc.selection[0].groupItems ; | |
if(tempGroups.length <= 0) { | |
doc.selection[0].remove() ; | |
var message = { | |
en: 'Paths with multiple strokes are not supported.', | |
ja: '複数の線を持つパスには対応していません。' | |
} | |
// mark it as a special error | |
var newError = new Error(message) ; | |
newError.number = -128 ; | |
throw newError ; | |
} | |
// remove dashPath | |
var itemColor ; | |
for(var i = tempGroups.length - 1 ; i >= 0 ; i--) { | |
itemColor = tempGroups[i].pathItems[0].fillColor ; | |
if((itemColor.constructor.name == 'SpotColor') && (itemColor.spot == tempSpot)) { | |
tempGroups[i].remove() ; | |
break ; | |
} | |
} | |
backupPath.remove() ; | |
res = doc.selection[0] ; | |
} catch(e) { | |
alert(e) ; | |
if(e.number == -128) {res = backupPath ;} | |
return res ; | |
} finally { | |
app.userInteractionLevel = oldUserInteractionLevel ; | |
} | |
return res ; | |
} | |
/** | |
* expand a dashed stroke | |
* @param {Document} doc target document | |
* @param {PathItem} targetItem target dashed stroke | |
* @param {SpotColor} tempColor unique color. a marker to identify what is unnecessary | |
* @return {GroupItem} | |
*/ | |
function expandDashedStroke(doc, targetItem, tempColor) { | |
var originalPath = targetItem ; | |
var tempSpot = tempColor.spot ; | |
var pathfinderGroup = originalPath.layer.groupItems.add() ; | |
pathfinderGroup.move(originalPath, ElementPlacement.PLACEBEFORE) ; | |
var maskWidth = originalPath.strokeWidth + 1 ; | |
// object to fill dash | |
var dashPath = originalPath.duplicate(pathfinderGroup, ElementPlacement.PLACEATEND) ; | |
dashPath.strokeWidth = maskWidth ; | |
dashPath.strokeCap = StrokeCap.BUTTENDCAP ; // make the dash length unchangeable | |
// object to fill gap | |
var gapPath = originalPath.duplicate(pathfinderGroup, ElementPlacement.PLACEATEND) ; | |
gapPath.strokeDashes = [] ; | |
gapPath.strokeColor = tempColor ; | |
var backupPath ; | |
var oldUserInteractionLevel = app.userInteractionLevel ; | |
try { | |
app.userInteractionLevel = UserInteractionLevel.DONTDISPLAYALERTS ; | |
backupPath = originalPath.duplicate(originalPath, ElementPlacement.PLACEAFTER) ; | |
// leave fill only at gaps | |
doc.selection = [pathfinderGroup] ; | |
app.executeMenuCommand('OffsetPath v22') ; // Outline Stroke | |
app.executeMenuCommand('Live Pathfinder Subtract') ; | |
app.executeMenuCommand('expandStyle') ; // Expand Appearance | |
app.executeMenuCommand('compoundPath') ; // Compound Path Make | |
// remove gaps of originalPath by live paint | |
originalPath.strokeDashes = [] ; | |
originalPath.selected = true ; | |
app.executeMenuCommand('Make Planet X') ; // Live Paint Make | |
app.executeMenuCommand('Expand Planet X') ; // Live Paint Expand | |
app.redraw() ; | |
// reject unexpected structures | |
var tempGroups = doc.selection[0].groupItems ; | |
if(tempGroups.length <= 0) { | |
doc.selection[0].remove() ; | |
var message = { | |
en: 'Paths with multiple strokes are not supported.', | |
ja: '複数の線を持つパスには対応していません。' | |
} | |
// mark it as a special error | |
var newError = new Error(message) ; | |
newError.number = -128 ; | |
throw newError ; | |
} | |
// remove gapPath | |
var itemColor ; | |
for(var i = tempGroups.length - 1 ; i >= 0 ; i--) { | |
itemColor = tempGroups[i].pathItems[0].fillColor ; | |
if((itemColor.constructor.name == 'SpotColor') && (itemColor.spot == tempSpot)) { | |
tempGroups[i].remove() ; | |
break ; | |
} | |
} | |
backupPath.remove() ; | |
res = doc.selection[0] ; | |
} catch(e) { | |
alert(e) ; | |
if(e.number == -128) {res = backupPath ;} | |
return res ; | |
} finally { | |
app.userInteractionLevel = oldUserInteractionLevel ; | |
} | |
return res ; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment