-
Notifications
You must be signed in to change notification settings - Fork 16
/
Copy pathAI2ASS.coffee
416 lines (329 loc) · 12.4 KB
/
AI2ASS.coffee
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
`#target illustrator`
`#targetengine main`
ai2assBackend = ( options ) ->
app.userInteractionLevel = UserInteractionLevel.DISPLAYALERTS
pWin = new Window "palette"
pWin.text = "Progress Occurs"
pWin.pBar = pWin.add "progressbar", undefined, 0, 250
pWin.pBar.preferredSize = [ 250, 10 ]
doc = app.activeDocument
org = doc.rulerOrigin
black = new RGBColor();
countPathItems = ( obj ) ->
recurse = ( obj ) ->
unless obj.hidden
switch obj.typename
when "Document"
recurse layer for layer in obj.layers
when "Layer", "GroupItem"
recurse pageItem for pageItem in obj.pageItems
when "CompoundPathItem"
recurse path for path in obj.pathItems
when "PathItem"
count += 1
count = 0
recurse obj
return count
run = (root = doc, includeEmptyLayers) ->
output = {
combineStrategy: "safe"
layers: []
pathCnt: null
processedPathCnt: 0
tempLayer: null
makeTempLayer: (name = "AI2ASS_tmp") ->
@tempLayer = doc.layers.add()
@tempLayer.name = name
@tempLayer.zOrder(ZOrderMethod.SENDTOBACK)
makeClip: (clippingPath) ->
clip = {
tempGroup: null
isVisible: false
output: @
add: (clippingPath) ->
# prepare a group to apply the pathfinder effect to
@output.makeTempLayer() unless @output.tempLayer?
unless @tempGroup
@tempGroup = @output.tempLayer.groupItems.add()
# copy all path into the group and make sure it has a fill
copy = clippingPath.duplicate(@tempGroup, ElementPlacement.PLACEATBEGINNING)
copy.filled = true
copy.stroked = false
copy.clipping = false
copy.fillColor = black
if @tempGroup.pageItems.length > 1
# select the group, apply the pathfinder and expand
prevSelection = doc.selection
doc.selection = [@tempGroup]
app.executeMenuCommand("Live Pathfinder Intersect")
app.executeMenuCommand("expandStyle")
# expanding created a new group
@tempGroup = doc.selection[0]
# no intersection between paths means we have an empty clipping area
if @tempGroup.pageItems.length == 1
@isVisible = true
else
@isVisible = false
@tempGroup.pageItems.removeAll()
# restore previous selection
doc.selection = prevSelection
else @isVisible = true
copy: -> return makeClip @tempGroup.pageItems[0]
get: -> return @tempGroup.pageItems[0]
getASS: ->
drawing = ASS_createDrawingFromPoints @tempGroup.pageItems[0].pathPoints
return "\\clip(#{drawing.join ' '})"
}
clip.add clippingPath
return clip
makeLayer: (emptyPrefix) ->
layer = {
groups: []
currGroupIdx: -1
currGroup: null
emptyPrefix: null
makeMergeGroup: () ->
group = {
dirtyRects: []
lines: {}
layer: @
addPath: (path, prefix) ->
unless @isZeroArea path.visibleBounds
@dirtyRects.push path.visibleBounds
drawing = ASS_createDrawingFromPoints path.pathPoints
if @lines[prefix]?
Array.prototype.push.apply @lines[prefix], drawing
else @lines[prefix] = drawing
isZeroArea: (bounds) ->
return bounds[2]-bounds[0] == 0 and bounds[3]-bounds[1] == 0
isMergeable: (path) ->
if path.parent.typename == "CompoundPathItem"
return true
switch @layer.combineStrategy
when "off"
return false
when "any"
return true
when "safe"
bounds = path.visibleBounds
if @isZeroArea bounds
return true
for rect in @dirtyRects
if bounds[2] > rect[0] and bounds[0] < rect[2] and bounds[3] < rect[1] and bounds[1] > rect[3]
return false
return true
}
return group
addGroup: ->
@currGroupIdx += 1
@currGroup = @makeMergeGroup()
@groups[@currGroupIdx] = @currGroup
addPath: (path, prefix) ->
unless @currGroup.isMergeable path
@addGroup()
@currGroup.addPath path, prefix
}
layer.emptyPrefix = emptyPrefix
layer.combineStrategy = @combineStrategy
layer.addGroup()
return layer
process: ( obj, clip, opacity = 100 ) ->
if not @pathCnt?
@pathCnt = countPathItems obj
if !obj.hidden and (not clip? or clip.isVisible)
opacity = if obj.opacity? then opacity * obj.opacity/100 else 100
switch obj.typename
when "Document"
for layer in obj.layers by -1
@process layer
when "Layer"
if obj.pageItems.length == 0
@layers[obj.zOrderPosition] = @makeLayer @emptyPrefix obj.zOrderPosition, obj.name
else
for subPageItem in obj.pageItems by -1
@process subPageItem, null, opacity
when "CompoundPathItem"
for path in obj.pathItems by -1
@process path, clip, opacity
when "GroupItem"
if obj.clipped
clipPath = (pI for pI in obj.pageItems when pI.clipping)[0]
if clip?
clip = clip.copy()
clip.add clipPath
else
clip = @makeClip clipPath
@processedPathCnt += 1
for subPageItem in obj.pageItems by -1 when not subPageItem.clipping
@process subPageItem, clip, opacity
when "PathItem"
if @processedPathCnt % 10 == 0
pWin.pBar.value = Math.ceil @processedPathCnt*250/@pathCnt
pWin.update( )
unless obj.guides or not (obj.stroked or obj.filled or obj.clipping) or not obj.layer.visible
@appendPath obj, clip, opacity
@processedPathCnt += 1
appendPath: ( path, clipObj, opacity ) ->
stroke = manageColor path, "strokeColor", 3
fill = manageColor path, "fillColor", 1
layerName = path.layer.name
layerNum = path.layer.zOrderPosition
alpha = manageOpacity opacity
clip = if clipObj? then clipObj.getASS() else ""
prefix = @prefix stroke, fill, clip, alpha, layerNum, layerName
layer = @layers[layerNum]
unless layer?
layer = @makeLayer()
@layers[layerNum] = layer
layer.addPath path, prefix
prefix: (stroke, fill, clip, alpha) ->
"{\\an7\\pos(0,0)#{stroke}#{fill}#{alpha}#{clip}\\p1}"
emptyPrefix: -> ""
suffix: -> "{\\p0}"
get: (includeEmptyLayers) ->
fragments = []
suffix = @suffix()
for layer in @layers when layer?
if includeEmptyLayers && layer.emptyPrefix?
fragments.push layer.emptyPrefix
fragments.push "\n"
for mergeGroup in layer.groups
for prefix, drawing of mergeGroup.lines
fragments.push prefix
fragments.push drawing.join " "
fragments.push suffix
fragments.push "\n"
fragments.pop()
return fragments.join ""
}
if options.combineStrategy?
output.combineStrategy = options.combineStrategy
switch options.wrapper
when "clip"
output.prefix = -> "\\clip("
output.suffix = -> ")"
when "iclip"
output.prefix = -> "\\iclip("
output.suffix = -> ")"
when "bare"
output.prefix = -> ""
output.suffix = -> ""
when "line"
output.prefix = (stroke, fill, clip, alpha, layerNum, layerName) ->
"Dialogue: #{layerNum},0:00:00.00,0:00:00.00,AI,#{layerName},0,0,0,,{\\an7\\pos(0,0)#{stroke}#{fill}#{alpha}#{clip}\\p1}"
output.suffix = -> ""
output.emptyPrefix = (layerNum, layerName) ->
"Dialogue: #{layerNum},0:00:00.00,0:00:00.00,AI,#{layerName},0,0,0,,"
alert "Your colorspace needs to be RGB if you want colors." if doc.documentColorSpace == DocumentColorSpace.CMYK
pWin.show()
output.process root
output.tempLayer.remove() if output.tempLayer?
pWin.close()
return output.get(includeEmptyLayers)
drawing = {
commands: []
new: -> @commands = []
get: -> return @commands
CmdTypes: {
None: -1
Move: 0
Linear: 1
Cubic: 2
}
prevCmdType: -1
addMove: ( point ) ->
@commands.push "m"
@addCoords point.anchor
@prevCmdType = @CmdTypes.Move
addLinear: ( point ) ->
if @prevCmdType != @CmdTypes.Linear
@commands.push "l"
@prevCmdType = @CmdTypes.Linear
@commands.push
@addCoords point.anchor
addCubic: (currPoint, prevPoint) ->
if @prevCmdType != @CmdTypes.Cubic
@commands.push "b"
@prevCmdType = @CmdTypes.Cubic
@addCoords prevPoint.rightDirection
@addCoords currPoint.leftDirection
@addCoords currPoint.anchor
# For ASS, the origin is the top-left corner
addCoords: ( coordArr ) ->
@commands.push Math.round( (coordArr[0] + org[0])*100 )/100
@commands.push Math.round( (doc.height - (org[1] + coordArr[1]))*100 )/100
}
checkLinear = ( currPoint, prevPoint ) ->
p1 = (prevPoint.anchor[0] == prevPoint.rightDirection[0] && prevPoint.anchor[1] == prevPoint.rightDirection[1])
p2 = (currPoint.anchor[0] == currPoint.leftDirection[0] && currPoint.anchor[1] == currPoint.leftDirection[1])
(p1 && p2)
zeroPad = ( num ) ->
hexStr = num.toString(16).toUpperCase()
return if num < 16 then "0#{hexStr}" else hexStr
handleGray = ( theColor ) ->
pct = theColor.gray
pct = Math.round (100-pct)*255/100
"&H#{zeroPad pct}#{zeroPad pct}#{zeroPad pct}&"
handleRGB = ( theColor ) ->
r = Math.round theColor.red # why am I rounding these?
g = Math.round theColor.green
b = Math.round theColor.blue
"&H#{zeroPad b}#{zeroPad g}#{zeroPad r}&"
manageColor = ( currPath, field, ASSField ) ->
fmt = ""
switch currPath[field].typename
when "RGBColor"
fmt = handleRGB currPath[field]
when "GrayColor"
fmt = handleGray currPath[field]
when "NoColor"
switch field
when "fillColor"
return "\\#{ASSField}a&HFF&"
when "strokeColor"
return ""#\\bord0"
else
return ""
"\\#{ASSField}c#{fmt}"
# "GradientColor"
# "LabColor"
# "PatternColor"
# "SpotColor"
manageOpacity = (opacity) ->
if opacity >= 100
return ""
return "\\alpha&H#{zeroPad 255 - Math.round(opacity)/100 * 255}&"
ASS_createDrawingFromPoints = ( pathPoints ) ->
drawing.new()
if pathPoints.length > 0
drawing.addMove pathPoints[0]
for j in [1...pathPoints.length] by 1
currPoint = pathPoints[j]
prevPoint = pathPoints[j-1]
if checkLinear currPoint, prevPoint
drawing.addLinear currPoint
else
drawing.addCubic currPoint, prevPoint
prevPoint = pathPoints[pathPoints.length-1]
currPoint = pathPoints[0]
if checkLinear currPoint, prevPoint
drawing.addLinear currPoint
else
drawing.addCubic currPoint, prevPoint
return drawing.get()
methods = {
collectActiveLayer: ->
# PAGEITEMS DOES NOT INCLUDE SUBLAYERS, AND AS FAR AS I CAN TELL,
# THERE'S NO WAY TO POSSIBLY TELL FROM JS WHAT ORDER SUBLAYERS ARE
# IN RELATIVE TO THE PATHS, COMPOUND PATHS, AND GROUPS WITHIN THE
# LAYER, WHICH MEANS IT IS IMPOSSIBLE TO REPRODUCE THE WAY
# SUBLAYERS ARE LAYERED. TL;DR IF YOU STICK A LAYER INSIDE ANOTHER
# LAYER, FUCK YOU FOREVER.
currLayer = doc.activeLayer
unless currLayer.visible
return "Not doing anything to that invisible layer."
run currLayer
collectAllLayers: -> run()
collectAllLayersIncludeEmpty: -> run doc, true
}
methods[options.method]( )