├── AI2ASS UI.coffee ├── AI2ASS.coffee ├── COPYING ├── README.md ├── applyLines.moon ├── build.ps1 ├── build.sh ├── built └── AI2ASS.jsx └── screenshot.png /AI2ASS UI.coffee: -------------------------------------------------------------------------------- 1 | dlgRes = "Group { orientation:'column', alignChildren: ['fill', 'fill'], \ 2 | output: Panel { orientation:'column', text: 'ASS Output', \ 3 | edit: EditText {text: 'have ass, will typeset', properties: {multiline: true}, alignment: ['fill', 'fill'], preferredSize: [-1, 100] } \ 4 | }, \ 5 | outputFormat: Panel { orientation:'column', text: 'Output Format', \ 6 | clip: Group {orientation: 'row', alignChildren: ['fill', 'fill'], spacing: 5, \ 7 | noclip: RadioButton {text: 'Drawing', value: true}, \ 8 | clip: RadioButton {text: '\\\\clip'}, \ 9 | iclip: RadioButton {text: '\\\\iclip'}, \ 10 | bare: RadioButton {text: 'Bare'}, \ 11 | line: RadioButton {text: 'Line'} \ 12 | }, \ 13 | }, \ 14 | settings: Panel {orientation: 'column', alignChildren: ['left','fill'], text: 'Settings', \ 15 | collectionTarget: DropDownList {title: 'Collection Target:'}, \ 16 | pathCombining: DropDownList {title: 'Path Combining:'} \ 17 | }, \ 18 | export: Button {text: 'Export'} \ 19 | }" 20 | 21 | 22 | win = new Window "palette", "Export ASS" , undefined, {} 23 | dlg = win.add dlgRes 24 | 25 | outputFormats = { 26 | "Drawing:": "noclip" 27 | "\\clip": "clip" 28 | "\\iclip": "iclip" 29 | "Bare": "bare" 30 | "Line": "line" 31 | } 32 | 33 | exportMethods = { 34 | "Active Layer": "collectActiveLayer" 35 | "Non-Empty Layers": "collectAllLayers" 36 | "All Layers": "collectAllLayersIncludeEmpty" 37 | } 38 | dlg.settings.collectionTarget.add "item", k for k, v of exportMethods 39 | dlg.settings.collectionTarget.selection = 0 40 | 41 | pathCombiningStrategies = { 42 | "Disabled": "off" 43 | "Safe (Maintain Order)": "safe" 44 | "Ignore Blending Order": "any" 45 | } 46 | dlg.settings.pathCombining.add "item", k for k, v of pathCombiningStrategies 47 | dlg.settings.pathCombining.selection = 1 48 | 49 | bt = new BridgeTalk 50 | bt.target = "illustrator" 51 | backendScript = ai2assBackend.toString() 52 | 53 | radioString = ( radioGroup ) -> 54 | for child in radioGroup.children 55 | if child.value 56 | return outputFormats[child.text] 57 | 58 | objToString = (obj) -> 59 | fragments = ("#{k}: \"#{v}\"" for k, v of obj).join ", " 60 | return "{#{fragments}}" 61 | 62 | dlg.export.onClick = -> 63 | dlg.output.edit.active = false 64 | options = objToString { 65 | method: exportMethods[dlg.settings.collectionTarget.selection.text] 66 | wrapper: radioString dlg.outputFormat.clip 67 | combineStrategy: pathCombiningStrategies[dlg.settings.pathCombining.selection.text] 68 | } 69 | 70 | bt.body = "(#{backendScript})(#{options});" 71 | 72 | bt.onResult = ( result ) -> 73 | dlg.output.edit.text = result.body.replace( /\\\\/g, "\\" ).replace /\\n/g, "\n" 74 | dlg.output.edit.active = true 75 | 76 | bt.onError = ( err ) -> 77 | alert "#{err.body} (#{a.headers["Error-Code"]})" 78 | 79 | bt.send( ) 80 | 81 | win.show( ) 82 | -------------------------------------------------------------------------------- /AI2ASS.coffee: -------------------------------------------------------------------------------- 1 | `#target illustrator` 2 | `#targetengine main` 3 | 4 | ai2assBackend = ( options ) -> 5 | app.userInteractionLevel = UserInteractionLevel.DISPLAYALERTS 6 | pWin = new Window "palette" 7 | pWin.text = "Progress Occurs" 8 | pWin.pBar = pWin.add "progressbar", undefined, 0, 250 9 | pWin.pBar.preferredSize = [ 250, 10 ] 10 | doc = app.activeDocument 11 | org = doc.rulerOrigin 12 | black = new RGBColor(); 13 | 14 | countPathItems = ( obj ) -> 15 | recurse = ( obj ) -> 16 | unless obj.hidden 17 | switch obj.typename 18 | when "Document" 19 | recurse layer for layer in obj.layers 20 | when "Layer", "GroupItem" 21 | recurse pageItem for pageItem in obj.pageItems 22 | when "CompoundPathItem" 23 | recurse path for path in obj.pathItems 24 | when "PathItem" 25 | count += 1 26 | 27 | count = 0 28 | recurse obj 29 | return count 30 | 31 | run = (root = doc, includeEmptyLayers) -> 32 | output = { 33 | combineStrategy: "safe" 34 | layers: [] 35 | pathCnt: null 36 | processedPathCnt: 0 37 | tempLayer: null 38 | 39 | makeTempLayer: (name = "AI2ASS_tmp") -> 40 | @tempLayer = doc.layers.add() 41 | @tempLayer.name = name 42 | @tempLayer.zOrder(ZOrderMethod.SENDTOBACK) 43 | 44 | makeClip: (clippingPath) -> 45 | clip = { 46 | tempGroup: null 47 | isVisible: false 48 | output: @ 49 | 50 | add: (clippingPath) -> 51 | # prepare a group to apply the pathfinder effect to 52 | @output.makeTempLayer() unless @output.tempLayer? 53 | 54 | unless @tempGroup 55 | @tempGroup = @output.tempLayer.groupItems.add() 56 | 57 | # copy all path into the group and make sure it has a fill 58 | copy = clippingPath.duplicate(@tempGroup, ElementPlacement.PLACEATBEGINNING) 59 | copy.filled = true 60 | copy.stroked = false 61 | copy.clipping = false 62 | copy.fillColor = black 63 | 64 | if @tempGroup.pageItems.length > 1 65 | # select the group, apply the pathfinder and expand 66 | prevSelection = doc.selection 67 | doc.selection = [@tempGroup] 68 | app.executeMenuCommand("Live Pathfinder Intersect") 69 | app.executeMenuCommand("expandStyle") 70 | # expanding created a new group 71 | @tempGroup = doc.selection[0] 72 | 73 | # no intersection between paths means we have an empty clipping area 74 | if @tempGroup.pageItems.length == 1 75 | @isVisible = true 76 | else 77 | @isVisible = false 78 | @tempGroup.pageItems.removeAll() 79 | 80 | # restore previous selection 81 | doc.selection = prevSelection 82 | else @isVisible = true 83 | 84 | copy: -> return makeClip @tempGroup.pageItems[0] 85 | get: -> return @tempGroup.pageItems[0] 86 | getASS: -> 87 | drawing = ASS_createDrawingFromPoints @tempGroup.pageItems[0].pathPoints 88 | return "\\clip(#{drawing.join ' '})" 89 | } 90 | 91 | clip.add clippingPath 92 | return clip 93 | 94 | makeLayer: (emptyPrefix) -> 95 | layer = { 96 | groups: [] 97 | currGroupIdx: -1 98 | currGroup: null 99 | emptyPrefix: null 100 | 101 | makeMergeGroup: () -> 102 | group = { 103 | dirtyRects: [] 104 | lines: {} 105 | layer: @ 106 | 107 | addPath: (path, prefix) -> 108 | unless @isZeroArea path.visibleBounds 109 | @dirtyRects.push path.visibleBounds 110 | drawing = ASS_createDrawingFromPoints path.pathPoints 111 | 112 | if @lines[prefix]? 113 | Array.prototype.push.apply @lines[prefix], drawing 114 | else @lines[prefix] = drawing 115 | 116 | isZeroArea: (bounds) -> 117 | return bounds[2]-bounds[0] == 0 and bounds[3]-bounds[1] == 0 118 | 119 | isMergeable: (path) -> 120 | if path.parent.typename == "CompoundPathItem" 121 | return true 122 | 123 | switch @layer.combineStrategy 124 | when "off" 125 | return false 126 | when "any" 127 | return true 128 | when "safe" 129 | bounds = path.visibleBounds 130 | 131 | if @isZeroArea bounds 132 | return true 133 | 134 | for rect in @dirtyRects 135 | if bounds[2] > rect[0] and bounds[0] < rect[2] and bounds[3] < rect[1] and bounds[1] > rect[3] 136 | return false 137 | 138 | return true 139 | } 140 | 141 | return group 142 | 143 | addGroup: -> 144 | @currGroupIdx += 1 145 | @currGroup = @makeMergeGroup() 146 | @groups[@currGroupIdx] = @currGroup 147 | 148 | addPath: (path, prefix) -> 149 | unless @currGroup.isMergeable path 150 | @addGroup() 151 | @currGroup.addPath path, prefix 152 | } 153 | 154 | layer.emptyPrefix = emptyPrefix 155 | layer.combineStrategy = @combineStrategy 156 | layer.addGroup() 157 | return layer 158 | 159 | process: ( obj, clip, opacity = 100 ) -> 160 | if not @pathCnt? 161 | @pathCnt = countPathItems obj 162 | 163 | if !obj.hidden and (not clip? or clip.isVisible) 164 | opacity = if obj.opacity? then opacity * obj.opacity/100 else 100 165 | 166 | switch obj.typename 167 | when "Document" 168 | for layer in obj.layers by -1 169 | @process layer 170 | 171 | when "Layer" 172 | if obj.pageItems.length == 0 173 | @layers[obj.zOrderPosition] = @makeLayer @emptyPrefix obj.zOrderPosition, obj.name 174 | else 175 | for subPageItem in obj.pageItems by -1 176 | @process subPageItem, null, opacity 177 | 178 | when "CompoundPathItem" 179 | for path in obj.pathItems by -1 180 | @process path, clip, opacity 181 | 182 | when "GroupItem" 183 | if obj.clipped 184 | clipPath = (pI for pI in obj.pageItems when pI.clipping)[0] 185 | if clip? 186 | clip = clip.copy() 187 | clip.add clipPath 188 | else 189 | clip = @makeClip clipPath 190 | @processedPathCnt += 1 191 | 192 | for subPageItem in obj.pageItems by -1 when not subPageItem.clipping 193 | @process subPageItem, clip, opacity 194 | 195 | when "PathItem" 196 | if @processedPathCnt % 10 == 0 197 | pWin.pBar.value = Math.ceil @processedPathCnt*250/@pathCnt 198 | pWin.update( ) 199 | 200 | unless obj.guides or not (obj.stroked or obj.filled or obj.clipping) or not obj.layer.visible 201 | @appendPath obj, clip, opacity 202 | 203 | @processedPathCnt += 1 204 | 205 | appendPath: ( path, clipObj, opacity ) -> 206 | stroke = manageColor path, "strokeColor", 3 207 | fill = manageColor path, "fillColor", 1 208 | layerName = path.layer.name 209 | layerNum = path.layer.zOrderPosition 210 | alpha = manageOpacity opacity 211 | clip = if clipObj? then clipObj.getASS() else "" 212 | 213 | prefix = @prefix stroke, fill, clip, alpha, layerNum, layerName 214 | 215 | layer = @layers[layerNum] 216 | unless layer? 217 | layer = @makeLayer() 218 | @layers[layerNum] = layer 219 | 220 | layer.addPath path, prefix 221 | 222 | prefix: (stroke, fill, clip, alpha) -> 223 | "{\\an7\\pos(0,0)#{stroke}#{fill}#{alpha}#{clip}\\p1}" 224 | 225 | emptyPrefix: -> "" 226 | suffix: -> "{\\p0}" 227 | 228 | get: (includeEmptyLayers) -> 229 | fragments = [] 230 | suffix = @suffix() 231 | 232 | for layer in @layers when layer? 233 | if includeEmptyLayers && layer.emptyPrefix? 234 | fragments.push layer.emptyPrefix 235 | fragments.push "\n" 236 | 237 | for mergeGroup in layer.groups 238 | for prefix, drawing of mergeGroup.lines 239 | fragments.push prefix 240 | fragments.push drawing.join " " 241 | fragments.push suffix 242 | fragments.push "\n" 243 | 244 | fragments.pop() 245 | return fragments.join "" 246 | } 247 | 248 | if options.combineStrategy? 249 | output.combineStrategy = options.combineStrategy 250 | 251 | switch options.wrapper 252 | when "clip" 253 | output.prefix = -> "\\clip(" 254 | output.suffix = -> ")" 255 | when "iclip" 256 | output.prefix = -> "\\iclip(" 257 | output.suffix = -> ")" 258 | when "bare" 259 | output.prefix = -> "" 260 | output.suffix = -> "" 261 | when "line" 262 | output.prefix = (stroke, fill, clip, alpha, layerNum, layerName) -> 263 | "Dialogue: #{layerNum},0:00:00.00,0:00:00.00,AI,#{layerName},0,0,0,,{\\an7\\pos(0,0)#{stroke}#{fill}#{alpha}#{clip}\\p1}" 264 | output.suffix = -> "" 265 | output.emptyPrefix = (layerNum, layerName) -> 266 | "Dialogue: #{layerNum},0:00:00.00,0:00:00.00,AI,#{layerName},0,0,0,," 267 | 268 | alert "Your colorspace needs to be RGB if you want colors." if doc.documentColorSpace == DocumentColorSpace.CMYK 269 | 270 | pWin.show() 271 | output.process root 272 | 273 | output.tempLayer.remove() if output.tempLayer? 274 | pWin.close() 275 | return output.get(includeEmptyLayers) 276 | 277 | drawing = { 278 | commands: [] 279 | new: -> @commands = [] 280 | get: -> return @commands 281 | 282 | CmdTypes: { 283 | None: -1 284 | Move: 0 285 | Linear: 1 286 | Cubic: 2 287 | } 288 | 289 | prevCmdType: -1 290 | 291 | addMove: ( point ) -> 292 | @commands.push "m" 293 | @addCoords point.anchor 294 | @prevCmdType = @CmdTypes.Move 295 | 296 | addLinear: ( point ) -> 297 | if @prevCmdType != @CmdTypes.Linear 298 | @commands.push "l" 299 | @prevCmdType = @CmdTypes.Linear 300 | 301 | @commands.push 302 | @addCoords point.anchor 303 | 304 | addCubic: (currPoint, prevPoint) -> 305 | if @prevCmdType != @CmdTypes.Cubic 306 | @commands.push "b" 307 | @prevCmdType = @CmdTypes.Cubic 308 | 309 | @addCoords prevPoint.rightDirection 310 | @addCoords currPoint.leftDirection 311 | @addCoords currPoint.anchor 312 | 313 | # For ASS, the origin is the top-left corner 314 | addCoords: ( coordArr ) -> 315 | @commands.push Math.round( (coordArr[0] + org[0])*100 )/100 316 | @commands.push Math.round( (doc.height - (org[1] + coordArr[1]))*100 )/100 317 | 318 | } 319 | 320 | 321 | 322 | checkLinear = ( currPoint, prevPoint ) -> 323 | p1 = (prevPoint.anchor[0] == prevPoint.rightDirection[0] && prevPoint.anchor[1] == prevPoint.rightDirection[1]) 324 | p2 = (currPoint.anchor[0] == currPoint.leftDirection[0] && currPoint.anchor[1] == currPoint.leftDirection[1]) 325 | (p1 && p2) 326 | 327 | zeroPad = ( num ) -> 328 | hexStr = num.toString(16).toUpperCase() 329 | return if num < 16 then "0#{hexStr}" else hexStr 330 | 331 | handleGray = ( theColor ) -> 332 | pct = theColor.gray 333 | pct = Math.round (100-pct)*255/100 334 | "&H#{zeroPad pct}#{zeroPad pct}#{zeroPad pct}&" 335 | 336 | handleRGB = ( theColor ) -> 337 | r = Math.round theColor.red # why am I rounding these? 338 | g = Math.round theColor.green 339 | b = Math.round theColor.blue 340 | "&H#{zeroPad b}#{zeroPad g}#{zeroPad r}&" 341 | 342 | manageColor = ( currPath, field, ASSField ) -> 343 | fmt = "" 344 | 345 | switch currPath[field].typename 346 | when "RGBColor" 347 | fmt = handleRGB currPath[field] 348 | when "GrayColor" 349 | fmt = handleGray currPath[field] 350 | when "NoColor" 351 | switch field 352 | when "fillColor" 353 | return "\\#{ASSField}a&HFF&" 354 | when "strokeColor" 355 | return ""#\\bord0" 356 | else 357 | return "" 358 | 359 | "\\#{ASSField}c#{fmt}" 360 | # "GradientColor" 361 | # "LabColor" 362 | # "PatternColor" 363 | # "SpotColor" 364 | 365 | manageOpacity = (opacity) -> 366 | if opacity >= 100 367 | return "" 368 | 369 | return "\\alpha&H#{zeroPad 255 - Math.round(opacity)/100 * 255}&" 370 | 371 | ASS_createDrawingFromPoints = ( pathPoints ) -> 372 | drawing.new() 373 | 374 | if pathPoints.length > 0 375 | drawing.addMove pathPoints[0] 376 | 377 | for j in [1...pathPoints.length] by 1 378 | currPoint = pathPoints[j] 379 | prevPoint = pathPoints[j-1] 380 | 381 | if checkLinear currPoint, prevPoint 382 | drawing.addLinear currPoint 383 | else 384 | drawing.addCubic currPoint, prevPoint 385 | 386 | prevPoint = pathPoints[pathPoints.length-1] 387 | currPoint = pathPoints[0] 388 | 389 | if checkLinear currPoint, prevPoint 390 | drawing.addLinear currPoint 391 | else 392 | drawing.addCubic currPoint, prevPoint 393 | 394 | return drawing.get() 395 | 396 | methods = { 397 | collectActiveLayer: -> 398 | 399 | # PAGEITEMS DOES NOT INCLUDE SUBLAYERS, AND AS FAR AS I CAN TELL, 400 | # THERE'S NO WAY TO POSSIBLY TELL FROM JS WHAT ORDER SUBLAYERS ARE 401 | # IN RELATIVE TO THE PATHS, COMPOUND PATHS, AND GROUPS WITHIN THE 402 | # LAYER, WHICH MEANS IT IS IMPOSSIBLE TO REPRODUCE THE WAY 403 | # SUBLAYERS ARE LAYERED. TL;DR IF YOU STICK A LAYER INSIDE ANOTHER 404 | # LAYER, FUCK YOU FOREVER. 405 | currLayer = doc.activeLayer 406 | 407 | unless currLayer.visible 408 | return "Not doing anything to that invisible layer." 409 | run currLayer 410 | 411 | collectAllLayers: -> run() 412 | 413 | collectAllLayersIncludeEmpty: -> run doc, true 414 | } 415 | 416 | methods[options.method]( ) 417 | -------------------------------------------------------------------------------- /COPYING: -------------------------------------------------------------------------------- 1 | Copyright (c) 2015, AI2ASS contributors 2 | 3 | Permission to use, copy, modify, and/or distribute this software for any 4 | purpose with or without fee is hereby granted, provided that the above 5 | copyright notice and this permission notice appear in all copies. 6 | 7 | THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 8 | WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 9 | MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 10 | ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 11 | WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 12 | ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 13 | OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 14 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## AI2ASS ## 2 | ![sweet screenshot][screenshit] 3 | 4 | This is a script to export drawings in Adobe Illustrator as ASS 5 | vector objects. It was inspired by tophf's similar script for CorelDRAW. 6 | 7 | The exporting process consists of sticking generated 8 | ASS into a window that has copyable text. This is still terrible design, 9 | but at least it's slightly better than it used to be. 10 | 11 | ### Features and Limitations 12 | 13 | ##### It only acts upon the currently focused document in Illustrator. 14 | 15 | ##### It should work with most versions of Illustrator. 16 | Has been used with CS5, CS6 and CC versions of Illustrator. 17 | 18 | ##### Can export the current layer or all layers at once. 19 | 20 | ##### Shapes are separated both by layer and the prefixes of the chosen output format (which may include color, stroke, opacity and clipping paths). 21 | Paths that share all these attributes can be merged into a single line. 22 | The following merge strategies are available: 23 | 24 | + __Disabled__: turns line merging off 25 | + __Safe__: merges lines in a way that doesn't disturb your scene graph order 26 | + __Ignore Blending Order__: merges all paths of a layer sharing a common prefix without respect to their order within the layer. 27 | 28 | ##### It supports RGB and Grayscale colorspaces 29 | CMYK support may be added at some point in the future if I ever find a 30 | conversion algorithm that works properly. 31 | 32 | ##### It can output shapes wrapped in {\p1}, \clip, \iclip, raw shape data, or complete dialogue lines. 33 | All output shape data uses coordinates with two decimal places of 34 | precision. Modern ASS renderers should be able to handle these properly. 35 | If you are worried about people running horribly old and terrible 36 | software, don't. If you're using this to do typesetting, odds are it'll 37 | be too slow to run on their setup anyway. 38 | 39 | ##### Exports clipping paths as \clips of their respective lines 40 | 41 | ##### There is basic transparency support 42 | AI2ASS correctly calculates the opacity for every path and exports it as 43 | `\alpha` override tag. However, output will only be correct when not 44 | using any of the blending modes unsupported in ASS (which is all of them 45 | except the *Normal* mode). 46 | 47 | ### Great, but how do I run it? ### 48 | 49 | Place [`AI2ASS.jsx`][raw] in your Illustrator 50 | scripts folder. 51 | 52 | On OS X, the scripts folder should be something like 53 | `/Applications/Adobe Illustrator CS6/Presets/en_US/Scripts`. 54 | 55 | On Windows, it'll be `C:\Program Files\Adobe\Adobe Illustrator CS6 (64 56 | Bit)\Presets\en_US\Scripts` , assuming you're running the 64-bit version 57 | (thanks, __ar). 58 | 59 | If you launch Illustrator, the script should now appear in the menu as 60 | `File > Scripts > AI2ASS`. Running this will pop up a persistent window 61 | with a button you can click to convert the active layer into an ASS 62 | drawing. You don't need to close this ever. It's neat. 63 | 64 | #### WARNING: SHIT MOVES SLOW WHEN YOU HAVE A LOT OF STUFF GOING ON #### 65 | 66 | But there's a cool progress bar so you can see that it's going slow. 67 | 68 | ### TODO ### 69 | - AINT NOTHIN THIS IS PERFECT 70 | 71 | #### License 72 | 73 | ISC. See COPYING for details. 74 | 75 | [screenshit]: https://raw.github.com/torque/AI2ASS/master/screenshot.png 76 | [raw]: https://raw.github.com/torque/AI2ASS/master/built/AI2ASS.jsx 77 | -------------------------------------------------------------------------------- /applyLines.moon: -------------------------------------------------------------------------------- 1 | -- This script takes one line and turns it into many lines. 2 | 3 | -- Usage: select 1 line (or a bunch, it actually doesn't matter because it only 4 | -- uses the active line). Run the macro. It will copy all of the shape data from 5 | -- AI2ASS on the clipboard into Aegisub, creating as many lines as necessary. 6 | -- Note that due to me being a fucking idiot, it will just kind of paste 7 | -- whatever the hell you have on the clipboard into the current line. I don't 8 | -- care to fix this. I doubt anyone except me will ever use this. If you do and 9 | -- you don't like this behavior, pull requests welcome. 10 | 11 | require "clipboard" 12 | 13 | string.split = ( sep ) => 14 | sep, fields = sep or ":", {} 15 | string.gsub @, "([^#{sep}]+)", (c) -> table.insert fields, c 16 | fields 17 | 18 | -- Creates lines on a glyph by glyph basis because libass eats shit on very long 19 | -- lines. This looks terrible on fades and ends up bloating the script a lot 20 | -- more, but it's easier than actually fixing libass myself. Oh, and all the 21 | -- colors are hardcoded because I don't give half a shit. Fix it yourself if you 22 | -- want to use it. 23 | applyInnerShadow = ( sub, originalLine, lineTable ) -> 24 | len = #lineTable 25 | for x = 2, len-2, 2 26 | error "DONGS" if aegisub.progress.is_cancelled! 27 | originalLine.text = [[{\pos(2,2)\c&H000000&\blur2}]] .. lineTable[x] 28 | originalLine.layer = 2 29 | sub.insert originalLine.n, originalLine 30 | originalLine.text = [[{\pos(0,0)\c&H0000FF&}]] .. lineTable[x-1] 31 | originalLine.layer = 1 32 | sub.insert originalLine.n, originalLine 33 | originalLine.text = [[{\3c&HFFFFFF&\bord8}]] .. originalLine.text 34 | originalLine.layer = 0 35 | sub.insert originalLine.n, originalLine 36 | 37 | originalLine.text = [[{\pos(2,2)\c&H000000&\blur2}]] .. lineTable[len] 38 | originalLine.layer = 2 39 | sub.insert originalLine.n, originalLine 40 | originalLine.text = [[{\pos(0,0)\c&H0000FF&}]] .. lineTable[len-1] 41 | originalLine.layer = 1 42 | sub.insert originalLine.n, originalLine 43 | originalLine.text = [[{\3c&HFFFFFF&\bord8}]] .. originalLine.text 44 | originalLine.layer = 0 45 | sub[originalLine.n-1] = originalLine 46 | 47 | createInnerShadow = ( sub, sel, act ) -> 48 | originalLine = sub[act] 49 | originalLine.n = act + 1 50 | incomingLines = clipboard.get! 51 | lineTable = incomingLines\split "\r\n" 52 | if lineTable[1]\match "{innerShadow}" 53 | table.remove lineTable, 1 54 | if #lineTable % 2 == 0 55 | applyInnerShadow sub, originalLine, lineTable 56 | else 57 | aegisub.log 0, "This shit is malformed and everything you do is wrong." 58 | elseif lineTable[1]\match "{allLayers}" 59 | table.remove lineTable, 1 60 | applyAllLayers sub, originalLine, lineTable 61 | else 62 | originalLine.text = lineTable 63 | sub[act] = originalLine 64 | 65 | aegisub.register_macro "Apply AI Lines", "Handles paste data from AI2ASS", createInnerShadow 66 | -------------------------------------------------------------------------------- /build.ps1: -------------------------------------------------------------------------------- 1 | gc "AI2ASS.coffee" | Out-File AI2ASScomb.coffee -e UTF8 2 | gc "AI2ASS UI.coffee" | Out-File AI2ASScomb.coffee -e UTF8 -a 3 | coffee -bc AI2ASScomb.coffee 4 | mkdir built -f 5 | mv AI2ASScomb.js built/AI2ASS.jsx -force 6 | rm AI2ASScomb.coffee 7 | -------------------------------------------------------------------------------- /build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | cat AI2ASS.coffee "AI2ASS UI.coffee" > AI2ASScomb.coffee 4 | coffee -bc AI2ASScomb.coffee 5 | mv AI2ASScomb.js built/AI2ASS.jsx 6 | rm AI2ASScomb.coffee 7 | -------------------------------------------------------------------------------- /built/AI2ASS.jsx: -------------------------------------------------------------------------------- 1 | #target illustrator; 2 | #targetengine main; 3 | var ai2assBackend, backendScript, bt, dlg, dlgRes, exportMethods, k, objToString, outputFormats, pathCombiningStrategies, radioString, v, win; 4 | 5 | ai2assBackend = function(options) { 6 | var ASS_createDrawingFromPoints, black, checkLinear, countPathItems, doc, drawing, handleGray, handleRGB, manageColor, manageOpacity, methods, org, pWin, run, zeroPad; 7 | app.userInteractionLevel = UserInteractionLevel.DISPLAYALERTS; 8 | pWin = new Window("palette"); 9 | pWin.text = "Progress Occurs"; 10 | pWin.pBar = pWin.add("progressbar", void 0, 0, 250); 11 | pWin.pBar.preferredSize = [250, 10]; 12 | doc = app.activeDocument; 13 | org = doc.rulerOrigin; 14 | black = new RGBColor(); 15 | countPathItems = function(obj) { 16 | var count, recurse; 17 | recurse = function(obj) { 18 | var i, l, layer, len, len1, len2, m, pageItem, path, ref, ref1, ref2, results, results1, results2; 19 | if (!obj.hidden) { 20 | switch (obj.typename) { 21 | case "Document": 22 | ref = obj.layers; 23 | results = []; 24 | for (i = 0, len = ref.length; i < len; i++) { 25 | layer = ref[i]; 26 | results.push(recurse(layer)); 27 | } 28 | return results; 29 | break; 30 | case "Layer": 31 | case "GroupItem": 32 | ref1 = obj.pageItems; 33 | results1 = []; 34 | for (l = 0, len1 = ref1.length; l < len1; l++) { 35 | pageItem = ref1[l]; 36 | results1.push(recurse(pageItem)); 37 | } 38 | return results1; 39 | break; 40 | case "CompoundPathItem": 41 | ref2 = obj.pathItems; 42 | results2 = []; 43 | for (m = 0, len2 = ref2.length; m < len2; m++) { 44 | path = ref2[m]; 45 | results2.push(recurse(path)); 46 | } 47 | return results2; 48 | break; 49 | case "PathItem": 50 | return count += 1; 51 | } 52 | } 53 | }; 54 | count = 0; 55 | recurse(obj); 56 | return count; 57 | }; 58 | run = function(root, includeEmptyLayers) { 59 | var output; 60 | if (root == null) { 61 | root = doc; 62 | } 63 | output = { 64 | combineStrategy: "safe", 65 | layers: [], 66 | pathCnt: null, 67 | processedPathCnt: 0, 68 | tempLayer: null, 69 | makeTempLayer: function(name) { 70 | if (name == null) { 71 | name = "AI2ASS_tmp"; 72 | } 73 | this.tempLayer = doc.layers.add(); 74 | this.tempLayer.name = name; 75 | return this.tempLayer.zOrder(ZOrderMethod.SENDTOBACK); 76 | }, 77 | makeClip: function(clippingPath) { 78 | var clip; 79 | clip = { 80 | tempGroup: null, 81 | isVisible: false, 82 | output: this, 83 | add: function(clippingPath) { 84 | var copy, prevSelection; 85 | if (this.output.tempLayer == null) { 86 | this.output.makeTempLayer(); 87 | } 88 | if (!this.tempGroup) { 89 | this.tempGroup = this.output.tempLayer.groupItems.add(); 90 | } 91 | copy = clippingPath.duplicate(this.tempGroup, ElementPlacement.PLACEATBEGINNING); 92 | copy.filled = true; 93 | copy.stroked = false; 94 | copy.clipping = false; 95 | copy.fillColor = black; 96 | if (this.tempGroup.pageItems.length > 1) { 97 | prevSelection = doc.selection; 98 | doc.selection = [this.tempGroup]; 99 | app.executeMenuCommand("Live Pathfinder Intersect"); 100 | app.executeMenuCommand("expandStyle"); 101 | this.tempGroup = doc.selection[0]; 102 | if (this.tempGroup.pageItems.length === 1) { 103 | this.isVisible = true; 104 | } else { 105 | this.isVisible = false; 106 | this.tempGroup.pageItems.removeAll(); 107 | } 108 | return doc.selection = prevSelection; 109 | } else { 110 | return this.isVisible = true; 111 | } 112 | }, 113 | copy: function() { 114 | return makeClip(this.tempGroup.pageItems[0]); 115 | }, 116 | get: function() { 117 | return this.tempGroup.pageItems[0]; 118 | }, 119 | getASS: function() { 120 | var drawing; 121 | drawing = ASS_createDrawingFromPoints(this.tempGroup.pageItems[0].pathPoints); 122 | return "\\clip(" + (drawing.join(' ')) + ")"; 123 | } 124 | }; 125 | clip.add(clippingPath); 126 | return clip; 127 | }, 128 | makeLayer: function(emptyPrefix) { 129 | var layer; 130 | layer = { 131 | groups: [], 132 | currGroupIdx: -1, 133 | currGroup: null, 134 | emptyPrefix: null, 135 | makeMergeGroup: function() { 136 | var group; 137 | group = { 138 | dirtyRects: [], 139 | lines: {}, 140 | layer: this, 141 | addPath: function(path, prefix) { 142 | var drawing; 143 | if (!this.isZeroArea(path.visibleBounds)) { 144 | this.dirtyRects.push(path.visibleBounds); 145 | drawing = ASS_createDrawingFromPoints(path.pathPoints); 146 | if (this.lines[prefix] != null) { 147 | return Array.prototype.push.apply(this.lines[prefix], drawing); 148 | } else { 149 | return this.lines[prefix] = drawing; 150 | } 151 | } 152 | }, 153 | isZeroArea: function(bounds) { 154 | return bounds[2] - bounds[0] === 0 && bounds[3] - bounds[1] === 0; 155 | }, 156 | isMergeable: function(path) { 157 | var bounds, i, len, rect, ref; 158 | if (path.parent.typename === "CompoundPathItem") { 159 | return true; 160 | } 161 | switch (this.layer.combineStrategy) { 162 | case "off": 163 | return false; 164 | case "any": 165 | return true; 166 | case "safe": 167 | bounds = path.visibleBounds; 168 | if (this.isZeroArea(bounds)) { 169 | return true; 170 | } 171 | ref = this.dirtyRects; 172 | for (i = 0, len = ref.length; i < len; i++) { 173 | rect = ref[i]; 174 | if (bounds[2] > rect[0] && bounds[0] < rect[2] && bounds[3] < rect[1] && bounds[1] > rect[3]) { 175 | return false; 176 | } 177 | } 178 | return true; 179 | } 180 | } 181 | }; 182 | return group; 183 | }, 184 | addGroup: function() { 185 | this.currGroupIdx += 1; 186 | this.currGroup = this.makeMergeGroup(); 187 | return this.groups[this.currGroupIdx] = this.currGroup; 188 | }, 189 | addPath: function(path, prefix) { 190 | if (!this.currGroup.isMergeable(path)) { 191 | this.addGroup(); 192 | } 193 | return this.currGroup.addPath(path, prefix); 194 | } 195 | }; 196 | layer.emptyPrefix = emptyPrefix; 197 | layer.combineStrategy = this.combineStrategy; 198 | layer.addGroup(); 199 | return layer; 200 | }, 201 | process: function(obj, clip, opacity) { 202 | var clipPath, i, l, layer, m, n, pI, path, ref, ref1, ref2, ref3, results, results1, results2, results3, subPageItem; 203 | if (opacity == null) { 204 | opacity = 100; 205 | } 206 | if (this.pathCnt == null) { 207 | this.pathCnt = countPathItems(obj); 208 | } 209 | if (!obj.hidden && ((clip == null) || clip.isVisible)) { 210 | opacity = obj.opacity != null ? opacity * obj.opacity / 100 : 100; 211 | switch (obj.typename) { 212 | case "Document": 213 | ref = obj.layers; 214 | results = []; 215 | for (i = ref.length - 1; i >= 0; i += -1) { 216 | layer = ref[i]; 217 | results.push(this.process(layer)); 218 | } 219 | return results; 220 | break; 221 | case "Layer": 222 | if (obj.pageItems.length === 0) { 223 | return this.layers[obj.zOrderPosition] = this.makeLayer(this.emptyPrefix(obj.zOrderPosition, obj.name)); 224 | } else { 225 | ref1 = obj.pageItems; 226 | results1 = []; 227 | for (l = ref1.length - 1; l >= 0; l += -1) { 228 | subPageItem = ref1[l]; 229 | results1.push(this.process(subPageItem, null, opacity)); 230 | } 231 | return results1; 232 | } 233 | break; 234 | case "CompoundPathItem": 235 | ref2 = obj.pathItems; 236 | results2 = []; 237 | for (m = ref2.length - 1; m >= 0; m += -1) { 238 | path = ref2[m]; 239 | results2.push(this.process(path, clip, opacity)); 240 | } 241 | return results2; 242 | break; 243 | case "GroupItem": 244 | if (obj.clipped) { 245 | clipPath = ((function() { 246 | var len, n, ref3, results3; 247 | ref3 = obj.pageItems; 248 | results3 = []; 249 | for (n = 0, len = ref3.length; n < len; n++) { 250 | pI = ref3[n]; 251 | if (pI.clipping) { 252 | results3.push(pI); 253 | } 254 | } 255 | return results3; 256 | })())[0]; 257 | if (clip != null) { 258 | clip = clip.copy(); 259 | clip.add(clipPath); 260 | } else { 261 | clip = this.makeClip(clipPath); 262 | } 263 | this.processedPathCnt += 1; 264 | } 265 | ref3 = obj.pageItems; 266 | results3 = []; 267 | for (n = ref3.length - 1; n >= 0; n += -1) { 268 | subPageItem = ref3[n]; 269 | if (!subPageItem.clipping) { 270 | results3.push(this.process(subPageItem, clip, opacity)); 271 | } 272 | } 273 | return results3; 274 | break; 275 | case "PathItem": 276 | if (this.processedPathCnt % 10 === 0) { 277 | pWin.pBar.value = Math.ceil(this.processedPathCnt * 250 / this.pathCnt); 278 | pWin.update(); 279 | } 280 | if (!(obj.guides || !(obj.stroked || obj.filled || obj.clipping) || !obj.layer.visible)) { 281 | this.appendPath(obj, clip, opacity); 282 | } 283 | return this.processedPathCnt += 1; 284 | } 285 | } 286 | }, 287 | appendPath: function(path, clipObj, opacity) { 288 | var alpha, clip, fill, layer, layerName, layerNum, prefix, stroke; 289 | stroke = manageColor(path, "strokeColor", 3); 290 | fill = manageColor(path, "fillColor", 1); 291 | layerName = path.layer.name; 292 | layerNum = path.layer.zOrderPosition; 293 | alpha = manageOpacity(opacity); 294 | clip = clipObj != null ? clipObj.getASS() : ""; 295 | prefix = this.prefix(stroke, fill, clip, alpha, layerNum, layerName); 296 | layer = this.layers[layerNum]; 297 | if (layer == null) { 298 | layer = this.makeLayer(); 299 | this.layers[layerNum] = layer; 300 | } 301 | return layer.addPath(path, prefix); 302 | }, 303 | prefix: function(stroke, fill, clip, alpha) { 304 | return "{\\an7\\pos(0,0)" + stroke + fill + alpha + clip + "\\p1}"; 305 | }, 306 | emptyPrefix: function() { 307 | return ""; 308 | }, 309 | suffix: function() { 310 | return "{\\p0}"; 311 | }, 312 | get: function(includeEmptyLayers) { 313 | var drawing, fragments, i, l, layer, len, len1, mergeGroup, prefix, ref, ref1, ref2, suffix; 314 | fragments = []; 315 | suffix = this.suffix(); 316 | ref = this.layers; 317 | for (i = 0, len = ref.length; i < len; i++) { 318 | layer = ref[i]; 319 | if (!(layer != null)) { 320 | continue; 321 | } 322 | if (includeEmptyLayers && (layer.emptyPrefix != null)) { 323 | fragments.push(layer.emptyPrefix); 324 | fragments.push("\n"); 325 | } 326 | ref1 = layer.groups; 327 | for (l = 0, len1 = ref1.length; l < len1; l++) { 328 | mergeGroup = ref1[l]; 329 | ref2 = mergeGroup.lines; 330 | for (prefix in ref2) { 331 | drawing = ref2[prefix]; 332 | fragments.push(prefix); 333 | fragments.push(drawing.join(" ")); 334 | fragments.push(suffix); 335 | fragments.push("\n"); 336 | } 337 | } 338 | } 339 | fragments.pop(); 340 | return fragments.join(""); 341 | } 342 | }; 343 | if (options.combineStrategy != null) { 344 | output.combineStrategy = options.combineStrategy; 345 | } 346 | switch (options.wrapper) { 347 | case "clip": 348 | output.prefix = function() { 349 | return "\\clip("; 350 | }; 351 | output.suffix = function() { 352 | return ")"; 353 | }; 354 | break; 355 | case "iclip": 356 | output.prefix = function() { 357 | return "\\iclip("; 358 | }; 359 | output.suffix = function() { 360 | return ")"; 361 | }; 362 | break; 363 | case "bare": 364 | output.prefix = function() { 365 | return ""; 366 | }; 367 | output.suffix = function() { 368 | return ""; 369 | }; 370 | break; 371 | case "line": 372 | output.prefix = function(stroke, fill, clip, alpha, layerNum, layerName) { 373 | return "Dialogue: " + layerNum + ",0:00:00.00,0:00:00.00,AI," + layerName + ",0,0,0,,{\\an7\\pos(0,0)" + stroke + fill + alpha + clip + "\\p1}"; 374 | }; 375 | output.suffix = function() { 376 | return ""; 377 | }; 378 | output.emptyPrefix = function(layerNum, layerName) { 379 | return "Dialogue: " + layerNum + ",0:00:00.00,0:00:00.00,AI," + layerName + ",0,0,0,,"; 380 | }; 381 | } 382 | if (doc.documentColorSpace === DocumentColorSpace.CMYK) { 383 | alert("Your colorspace needs to be RGB if you want colors."); 384 | } 385 | pWin.show(); 386 | output.process(root); 387 | if (output.tempLayer != null) { 388 | output.tempLayer.remove(); 389 | } 390 | pWin.close(); 391 | return output.get(includeEmptyLayers); 392 | }; 393 | drawing = { 394 | commands: [], 395 | "new": function() { 396 | return this.commands = []; 397 | }, 398 | get: function() { 399 | return this.commands; 400 | }, 401 | CmdTypes: { 402 | None: -1, 403 | Move: 0, 404 | Linear: 1, 405 | Cubic: 2 406 | }, 407 | prevCmdType: -1, 408 | addMove: function(point) { 409 | this.commands.push("m"); 410 | this.addCoords(point.anchor); 411 | return this.prevCmdType = this.CmdTypes.Move; 412 | }, 413 | addLinear: function(point) { 414 | if (this.prevCmdType !== this.CmdTypes.Linear) { 415 | this.commands.push("l"); 416 | this.prevCmdType = this.CmdTypes.Linear; 417 | } 418 | this.commands.push; 419 | return this.addCoords(point.anchor); 420 | }, 421 | addCubic: function(currPoint, prevPoint) { 422 | if (this.prevCmdType !== this.CmdTypes.Cubic) { 423 | this.commands.push("b"); 424 | this.prevCmdType = this.CmdTypes.Cubic; 425 | } 426 | this.addCoords(prevPoint.rightDirection); 427 | this.addCoords(currPoint.leftDirection); 428 | return this.addCoords(currPoint.anchor); 429 | }, 430 | addCoords: function(coordArr) { 431 | this.commands.push(Math.round((coordArr[0] + org[0]) * 100) / 100); 432 | return this.commands.push(Math.round((doc.height - (org[1] + coordArr[1])) * 100) / 100); 433 | } 434 | }; 435 | checkLinear = function(currPoint, prevPoint) { 436 | var p1, p2; 437 | p1 = prevPoint.anchor[0] === prevPoint.rightDirection[0] && prevPoint.anchor[1] === prevPoint.rightDirection[1]; 438 | p2 = currPoint.anchor[0] === currPoint.leftDirection[0] && currPoint.anchor[1] === currPoint.leftDirection[1]; 439 | return p1 && p2; 440 | }; 441 | zeroPad = function(num) { 442 | var hexStr; 443 | hexStr = num.toString(16).toUpperCase(); 444 | if (num < 16) { 445 | return "0" + hexStr; 446 | } else { 447 | return hexStr; 448 | } 449 | }; 450 | handleGray = function(theColor) { 451 | var pct; 452 | pct = theColor.gray; 453 | pct = Math.round((100 - pct) * 255 / 100); 454 | return "&H" + (zeroPad(pct)) + (zeroPad(pct)) + (zeroPad(pct)) + "&"; 455 | }; 456 | handleRGB = function(theColor) { 457 | var b, g, r; 458 | r = Math.round(theColor.red); 459 | g = Math.round(theColor.green); 460 | b = Math.round(theColor.blue); 461 | return "&H" + (zeroPad(b)) + (zeroPad(g)) + (zeroPad(r)) + "&"; 462 | }; 463 | manageColor = function(currPath, field, ASSField) { 464 | var fmt; 465 | fmt = ""; 466 | switch (currPath[field].typename) { 467 | case "RGBColor": 468 | fmt = handleRGB(currPath[field]); 469 | break; 470 | case "GrayColor": 471 | fmt = handleGray(currPath[field]); 472 | break; 473 | case "NoColor": 474 | switch (field) { 475 | case "fillColor": 476 | return "\\" + ASSField + "a&HFF&"; 477 | case "strokeColor": 478 | return ""; 479 | } 480 | break; 481 | default: 482 | return ""; 483 | } 484 | return "\\" + ASSField + "c" + fmt; 485 | }; 486 | manageOpacity = function(opacity) { 487 | if (opacity >= 100) { 488 | return ""; 489 | } 490 | return "\\alpha&H" + (zeroPad(255 - Math.round(opacity) / 100 * 255)) + "&"; 491 | }; 492 | ASS_createDrawingFromPoints = function(pathPoints) { 493 | var currPoint, i, j, prevPoint, ref; 494 | drawing["new"](); 495 | if (pathPoints.length > 0) { 496 | drawing.addMove(pathPoints[0]); 497 | for (j = i = 1, ref = pathPoints.length; i < ref; j = i += 1) { 498 | currPoint = pathPoints[j]; 499 | prevPoint = pathPoints[j - 1]; 500 | if (checkLinear(currPoint, prevPoint)) { 501 | drawing.addLinear(currPoint); 502 | } else { 503 | drawing.addCubic(currPoint, prevPoint); 504 | } 505 | } 506 | prevPoint = pathPoints[pathPoints.length - 1]; 507 | currPoint = pathPoints[0]; 508 | if (checkLinear(currPoint, prevPoint)) { 509 | drawing.addLinear(currPoint); 510 | } else { 511 | drawing.addCubic(currPoint, prevPoint); 512 | } 513 | return drawing.get(); 514 | } 515 | }; 516 | methods = { 517 | collectActiveLayer: function() { 518 | var currLayer; 519 | currLayer = doc.activeLayer; 520 | if (!currLayer.visible) { 521 | return "Not doing anything to that invisible layer."; 522 | } 523 | return run(currLayer); 524 | }, 525 | collectAllLayers: function() { 526 | return run(); 527 | }, 528 | collectAllLayersIncludeEmpty: function() { 529 | return run(doc, true); 530 | } 531 | }; 532 | return methods[options.method](); 533 | }; 534 | 535 | dlgRes = "Group { orientation:'column', alignChildren: ['fill', 'fill'], output: Panel { orientation:'column', text: 'ASS Output', edit: EditText {text: 'have ass, will typeset', properties: {multiline: true}, alignment: ['fill', 'fill'], preferredSize: [-1, 100] } }, outputFormat: Panel { orientation:'column', text: 'Output Format', clip: Group {orientation: 'row', alignChildren: ['fill', 'fill'], spacing: 5, noclip: RadioButton {text: 'Drawing', value: true}, clip: RadioButton {text: '\\\\clip'}, iclip: RadioButton {text: '\\\\iclip'}, bare: RadioButton {text: 'Bare'}, line: RadioButton {text: 'Line'} }, }, settings: Panel {orientation: 'column', alignChildren: ['left','fill'], text: 'Settings', collectionTarget: DropDownList {title: 'Collection Target:'}, pathCombining: DropDownList {title: 'Path Combining:'} }, export: Button {text: 'Export'} }"; 536 | 537 | win = new Window("palette", "Export ASS", void 0, {}); 538 | 539 | dlg = win.add(dlgRes); 540 | 541 | outputFormats = { 542 | "Drawing:": "noclip", 543 | "\\clip": "clip", 544 | "\\iclip": "iclip", 545 | "Bare": "bare", 546 | "Line": "line" 547 | }; 548 | 549 | exportMethods = { 550 | "Active Layer": "collectActiveLayer", 551 | "Non-Empty Layers": "collectAllLayers", 552 | "All Layers": "collectAllLayersIncludeEmpty" 553 | }; 554 | 555 | for (k in exportMethods) { 556 | v = exportMethods[k]; 557 | dlg.settings.collectionTarget.add("item", k); 558 | } 559 | 560 | dlg.settings.collectionTarget.selection = 0; 561 | 562 | pathCombiningStrategies = { 563 | "Disabled": "off", 564 | "Safe (Maintain Order)": "safe", 565 | "Ignore Blending Order": "any" 566 | }; 567 | 568 | for (k in pathCombiningStrategies) { 569 | v = pathCombiningStrategies[k]; 570 | dlg.settings.pathCombining.add("item", k); 571 | } 572 | 573 | dlg.settings.pathCombining.selection = 1; 574 | 575 | bt = new BridgeTalk; 576 | 577 | bt.target = "illustrator"; 578 | 579 | backendScript = ai2assBackend.toString(); 580 | 581 | radioString = function(radioGroup) { 582 | var child, i, len, ref; 583 | ref = radioGroup.children; 584 | for (i = 0, len = ref.length; i < len; i++) { 585 | child = ref[i]; 586 | if (child.value) { 587 | return outputFormats[child.text]; 588 | } 589 | } 590 | }; 591 | 592 | objToString = function(obj) { 593 | var fragments; 594 | fragments = ((function() { 595 | var results; 596 | results = []; 597 | for (k in obj) { 598 | v = obj[k]; 599 | results.push(k + ": \"" + v + "\""); 600 | } 601 | return results; 602 | })()).join(", "); 603 | return "{" + fragments + "}"; 604 | }; 605 | 606 | dlg["export"].onClick = function() { 607 | var options; 608 | dlg.output.edit.active = false; 609 | options = objToString({ 610 | method: exportMethods[dlg.settings.collectionTarget.selection.text], 611 | wrapper: radioString(dlg.outputFormat.clip), 612 | combineStrategy: pathCombiningStrategies[dlg.settings.pathCombining.selection.text] 613 | }); 614 | bt.body = "(" + backendScript + ")(" + options + ");"; 615 | bt.onResult = function(result) { 616 | dlg.output.edit.text = result.body.replace(/\\\\/g, "\\").replace(/\\n/g, "\n"); 617 | return dlg.output.edit.active = true; 618 | }; 619 | bt.onError = function(err) { 620 | return alert(err.body + " (" + a.headers["Error-Code"] + ")"); 621 | }; 622 | return bt.send(); 623 | }; 624 | 625 | win.show(); 626 | -------------------------------------------------------------------------------- /screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TypesettingTools/AI2ASS/b5bd9706cefce87ae70779f437ca70a25b2e7e6b/screenshot.png --------------------------------------------------------------------------------