InDesign Scripting Forum: 25 ‘sticky’ posts
March 07, 2011 | Extras | en
The InDesign Scripting Forum is an ideal place to post scripting recipes and to address technical issues. I learn a lot from its famous contributors —Dave Saunders, Harbs, Jongware, Peter Kahrel, Kasyan Servetsky, Marijan Tompa… At odd times I post my own brainchilds. Here is a small selection of snippets and topics that I think are worthwhile.
Dealing with Page Items
Adding items in a group
A complicated solution to a simple problem:
Group.prototype.addItems = function(/*PageItem[]*/ newItems) //---------------------------- // Emulates Group.groups.add(items) // [ Also supports Group.addItems(existing_item) ] // <newItems> == Array of 2+ PageItem // -> create a <newItems> subgroup in <this> // <newItems> == single PageItem // -> add the PageItem in <this> // Returns the added subgroup or item // (the whole group hierarchy is preserved) // Note - this method fails if <this> is // anchored/embedded in a story { var nodes=[], gs=[], g=this, node; var makeNode = function(id,g) { var elems = g.pageItems.everyItem().getElements(); if (id === null) // placeholder for newItems return {index: elems.length, items:elems.concat(0)}; for(var i=elems.length-1 ; i>=0 ; i--) // need to find the actual index in the group if (elems[i].id == id) break; return {index: i, items:elems}; }; var id=null; for( ; g.constructor == Group ; id=g.id, g=g.parent ) { gs.push(g.getElements()[0]); nodes.push(makeNode(id,g)); } var add = (function() { var host = g; return function(items) { return host.groups.add(items); }; })(); var r = ( 'array' == typeof newItems ) ? add(newItems) : newItems; while( g=gs.pop() ) g.ungroup(); for( g=r ; node=nodes.shift() ; ) { node.items[node.index] = g; g = add(node.items); } return r; }
• Original post: http://forums.adobe.com/message/2259499#2259499
Retrieving “Exclusive” Spread Items
This snippet returns the page items of Spreads —excluding those which belong to Pages:
var pItems = app.activeDocument. pageItems.everyItem().getElements(); var exclusiveSpreadItems = []; for( var p, i = pItems.length-1 ; i>=0 ; i-- ) { p = pItems[i].parent.constructor; if ( p == Spread || p == MasterSpread ) exclusiveSpreadItems.push(pItems[i]); } alert( exclusiveSpreadItems );
• Original post: http://forums.adobe.com/message/2218151#2218151
Page and PageItems
An improved version of getPage().
Page.prototype.intersects = function(/*bounds*/pib) //---------------------------- // Return TRUE if this page intersects the bounds // <pib> == [top,left,bottom,right] { var b = this.bounds; return b[0]<=pib[2] && pib[0]<=b[2] && b[1]<=pib[3] && pib[1]<=b[3]; } TextFrame.prototype.getPage = function() //---------------------------- // Returns the containing Page of this TextFrame // [ Works also with grouped/embedded frame ] { var p = this.parent, b = this.visibleBounds, pc, pgs, i; while( pc=p.constructor ) { if( pc == Page ) { if( p.intersects(b) ) return p; pgs = p.parent.pages; // spread pages for( i=pgs.length-1 ; i>=0 && p=pgs[i] ; i-- ) { if( p.intersects(b) ) return p; } pc = Spread; } if( pc == Spread || pc == MasterSpread ) throw Error("The textframe is placed on a spread!"); if( 'parentTextFrames' in p && !(p=p.parentTextFrames[0]) ) throw Error("The textframe's container overflows!"); p=p.parent; } } // Sample code (assumed a text frame is selected) //---------------------------- var tf = app.selection[0], pg; try { pg = tf.getPage(); alert( "Page: " + pg.name + " Side: " + pg.side); } catch(e) { alert( e ); }
• Original post: http://forums.adobe.com/message/2525480#2525480
Drawing a rounded corner
The below code illustrates a method to change the entirePath of an Oval in order to create a rounded corner.
var pg = app.activeWindow.activePage, pb = pg.bounds, w = pb[3]-pb[1], h = pb[2]-pb[0], diag = Math.sqrt(w*w+h*h), radius = diag/12.0, spacing = radius/3.0; var t = spacing + 2*radius, c, ep; // Create and position the circle at [top,right] // --- (c=pg.ovals.add()).geometricBounds = [ pb[0]+spacing, pb[3]-t, pb[0]+t, pb[3]-spacing ]; // Reduce the path points to a top-right corner // --- ep = c.paths[0].entirePath; ep[0] = [ep[1][1][0], ep[0][1][1]]; ep[1][0] = ep[1][1].concat(); ep[3] = [ep[3][1][0], ep[2][1][1]]; ep[2][2] = ep[2][1].concat(); c.paths[0].properties = { entirePath: ep, pathType: PathType.OPEN_PATH };

• Original post: http://forums.adobe.com/message/3500409#3500409
Text, Contents, Font
Font Replacement Routine
A simple changeMissingFontsBy method (tested in InDesign CS4):
Document.prototype. changeMissingFontsBy = function(/*str|Font*/fontOrFontName) //---------------------------- { var asFont = function(/*var*/f) { if( !f ) return null; if( 'string' == typeof f ) f = app.fonts.item(f); if( f.constructor != Font ) return null; return f; }; var missing = function(/*Font*/f) { return f.status != FontStatus.INSTALLED; }; var substFont = asFont(fontOrFontName); if( (!substFont) || missing(substFont) ) { alert( "["+ fontOrFontName + "] not installed!" ); return; } var changeMissingFont = function(obj) { // <obj> == any object w/ appliedFont prop var f = asFont(obj.appliedFont); if( !f || !missing(f) ) return; try{obj.appliedFont = substFont;} catch(_){} }; var scope = this.allCharacterStyles .concat(this.allParagraphStyles) .concat(this.stories.everyItem(). textStyleRanges.everyItem().getElements()); var s; while( s=scope.shift() ) changeMissingFont(s); } // test app.activeDocument.changeMissingFontsBy("Times New Roman");
• Original post: http://forums.adobe.com/message/2250103#2250103
Fit Text Frame to Content (supports multicolumn)
CS4/CS5 fitHorizontal script —based on an dichotomous algorithm:
// Your Settings: var X_PRECISION = .1; // pts // Some constants var INNER = CoordinateSpaces. INNER_COORDINATES, MULTIPLY = ResizeMethods. MULTIPLYING_CURRENT_DIMENSIONS_BY, ADDTO = ResizeMethods. ADDING_CURRENT_DIMENSIONS_TO, AP_LEFT = AnchorPoint. TOP_LEFT_ANCHOR, AP_RIGHT = AnchorPoint. TOP_RIGHT_ANCHOR; function fitHorizontal(/*TextFrame*/ tf, /*str*/xRef) //---------------------------- // Fits a textframe width to its content // i.e. adjusts the frame width to the optimal value // w/o changing the height // <xRef> (opt.): reference pt // 'left'(def.) | 'right' | 'center' // * If needed, you must perform first a vertical fit // i.e.: tf.fit(FitOptions.FRAME_TO_CONTENT) // * This routine supports rotated text frame // * Also, unlike the InDesign UI, this routine // supports *multicolumn* TF //---------------------------- { // Default width multiplier. This value is only // used if tf overflows in its initial state. // 1.5 is fine, usually. var X_FACTOR = 1.5; var ovf = tf.overflows, dx; xRef = AnchorPoint['TOP_' + (xRef||'left').toUpperCase() + '_ANCHOR']; // If tf originally overflows, we need to // increase the width while( tf.overflows ) tf.resize(INNER,xRef,MULTIPLY,[X_FACTOR,1]); // Now, let's compute the maximal // width variation (dx) dx = tf.resolve(AP_RIGHT, INNER)[0][0]- tf.resolve(AP_LEFT, INNER)[0][0]; if( ovf ) dx *= (1-1/X_FACTOR); // Dichotomy on dx while( dx > X_PRECISION ) { dx*=.5; tf.resize(INNER,xRef,ADDTO, [dx*(tf.overflows?1:-1),0]); } // Last step, if needed if( tf.overflows ) tf.resize(INNER,xRef,ADDTO,[dx,0]); } // Sample code //---------------------------- // Assuming the user has selected a text frame var tf = app.selection[0]; // Vertical fit (if you want it 1st --not needed) // tf.fit(FitOptions.FRAME_TO_CONTENT); // Horizontal fit (from the left edge) fitHorizontal(tf, 'left');

• Original post: http://forums.adobe.com/message/3387054#3387054
Paragraph selection
Selecting paragraph(s), with or without end mark(s):
function selectParagraph(/*Paragraph*/par, /*?bool*/includingEndmark) //---------------------------- // Returns FALSE if the target is invalid, // (otherwise returns TRUE) // * Supports single Paragraph, or Paragraph // range, or everyItem() // * If the target is empty (no character), // move the insertion pt within { var p, ips, c = -1; try { p=resolve(par.toSpecifier()); if( p && p.constructor == Array ) // converts everyItem into a range p=p[0].parent.paragraphs.itemByRange(0,-1); ips = p.insertionPoints; } catch(_){} if( !ips ) return false; // invalid specif. if( !includingEndmark ) c -= ((c=p.characters) && c.length && c[-1].contents=='\r'); ips.itemByRange(0,c).select(); return true; } // Sample tests //---------------------------- var pars = app.activeDocument. stories[0].paragraphs; // Single paragraph //---------------------------- if( selectParagraph(pars[1]) ) alert( "Selection of the 2nd paragraph " + "EXCLUDING end mark" ); else alert( "Unable to select the 2nd paragraph" ); if( selectParagraph(pars[1], true) ) alert( "Selection of the 2nd paragraph " + "INCLUDING end mark" ); else alert( "Unable to select the 2nd paragraph" ); // Paragraph range //---------------------------- if( selectParagraph(pars.itemByRange(1,2), true) ) alert( "Selection of paragraph range [1,2] " + "INCLUDING end mark" ); else alert( "Unable to select the " + "paragraph range [1,2]" ); if( selectParagraph(pars.itemByRange(1,2)) ) alert( "Selection of paragraph range [1,2] " + "EXCLUDING end mark" ); else alert( "Unable to select the " + "paragraph range [1,2]" ); // everyItem() support -- equiv. to itemByRange(0,-1) //---------------------------- if( selectParagraph(pars.everyItem()) ) alert( "Selection of every paragraph " + "EXCLUDING end mark" ); else alert( "Unable to select every paragraph" ); if( selectParagraph(pars.everyItem(), true) ) alert( "Selection of every paragraph " + "INCLUDING end mark" ); else alert( "Unable to select every paragraph" );
• Original post: http://forums.adobe.com/message/2993426#2993426
Useful Algorithms
Removing duplicate items from an Array of Strings
The “Hash Sieving” method is in O(2n):
function unique(/*str[]*/ arr) { var o={}, r=[], n = arr.length, i; for( i=0 ; i<n ; ++i ) o[arr[i]] = null; for( i in o ) r.push(i); o = null; return r; } // TEST: alert( unique(["a","b","c","c","a","d","b","b"]) ); // => a, b, c, d
• Original post: http://forums.adobe.com/message/3288743#3288743
Polygon Collision
Two functions that help you to detect whether two shapes intersect:
Polygon.prototype. collideBounds = function(/*Polygon*/ _poly) //---------------------------- // Ret. TRUE if the bounding boxes intersect, // otherwise return FALSE // Note - Fast method, but TRUE does not mean that // the inner shapes actually intersect { var b = this.visibleBounds; var _b = _poly.visibleBounds; return( !((b[0]>_b[2]) || (b[2]<_b[0]) || (b[1]>_b[3]) || (b[3]<_b[1])) ); } Polygon.prototype. collideShapes = function(/*Polygon*/ _poly) //---------------------------- // Ret. TRUE if the shapes actually intersect // (Slow!) { try { this.intersectPath(_poly); app.activeDocument.undo(); return(true); } catch(_){ return(false); } }
• Original post: http://forums.adobe.com/message/2385700#2385700
Formatting Numbers and Ranges
With the help of Peter Kahrel:
function formatRanges(numbers, separator, joiner, minWidth, tolerance) //---------------------------- // Formats an array of integers into an ordered // sequence of single numbers and/or ranges. // Returns the formatted string. // // <numbers> // Array of Numbers [required] // The integers to format. Supports: empty // array, unsorted array, duplicated elems, // negative values. // <separator> // String [opt] -- Default value: ", " // A string inserted between each result. // Ex.: formatRanges([4,1,3,8,9,6], " | ") // => "1 | 3-4 | 6 | 8-9" // <joiner> // String [opt] -- Default value: "-" // A string used to format a range. // Ex.: formatRanges([4,1,3,8,9,6], ", ", "_") // => "1, 3_4, 6, 8_9" // <minWidth> // Number [opt] -- Default value: 1 // Minimum distance between the 1st and the // last number in a range. // Ex.: formatRanges([1,2,4,5,6,8,9,10,11], '', '', 1) // => "1-2, 4-6, 8-11" // Ex.: formatRanges([1,2,4,5,6,8,9,10,11], '', '', 2) // => "1, 2, 4-6, 8-11" // Ex.: formatRanges([1,2,4,5,6,8,9,10,11], '', '', 3) // => "1, 2, 4, 5, 6, 8-11" // <tolerance> // Number [opt] -- Default value: 0 // Number of allowed missing numbers in a range, // as suggested by Peter Kahrel (http://bit.ly/cABqIP) // Ex.: formatRanges([2,3,5,8,12,17,23], '', '', 1, 0) // => "2-3, 5, 8, 12, 17, 23" // Ex.: formatRanges([2,3,5,8,12,17,23], '', '', 1, 1) // => "2-5, 8, 12, 17, 23" // Ex.: formatRanges([2,3,5,8,12,17,23], '', '', 1, 2) // => "2-8, 12, 17, 23" { // Defaults // --- separator = separator || ", "; joiner = joiner || "-"; if( minWidth !== ~~minWidth || minWidth < 1 ) minWidth = 1; if( tolerance !== ~~tolerance || ++tolerance < 1 ) tolerance = 1; // Init. // --- var a = numbers.concat(). sort(function(x,y){return x-y;}), sz = a.length, n = sz && a[0], d = sz || false, i = 0, w = 0, t = 0, ret = []; // Loop // --- while( d !== false ) { if( 0 === (d=(++i<sz)?a[i]-n:false) ) continue; // skip duplicates if( d && (d<=tolerance) ) { ret.push(n); n += d; ++w; t += (d-1); continue; } if( w >= minWidth ) { ret.length -= w; ret.push((n-w-t)+joiner+n); } else { ret.push(n); } n += d; w = t = 0; } return ret.join(separator); }
• Original code: http://forums.adobe.com/message/3145480#3145480
ScriptUI stuff
Get swatches in dropdown list with color preview
Shows the document's Swatches in ScriptUI:
// PNG-String Generator (=> 13X13 pixels) //---------------------------- var pngSwatch = (function() { // Table of CRCs of 8-bit messages var CRC_256 = [0, 0x77073096, 0xee0e612c, 0x990951ba, 0x76dc419, 0x706af48f, 0xe963a535, 0x9e6495a3, 0xedb8832, 0x79dcb8a4, 0xe0d5e91e, 0x97d2d988, 0x9b64c2b, 0x7eb17cbd, 0xe7b82d07, 0x90bf1d91, 0x1db71064, 0x6ab020f2, 0xf3b97148, 0x84be41de, 0x1adad47d, 0x6ddde4eb, 0xf4d4b551, 0x83d385c7, 0x136c9856, 0x646ba8c0, 0xfd62f97a, 0x8a65c9ec, 0x14015c4f, 0x63066cd9, 0xfa0f3d63, 0x8d080df5, 0x3b6e20c8, 0x4c69105e, 0xd56041e4, 0xa2677172, 0x3c03e4d1, 0x4b04d447, 0xd20d85fd, 0xa50ab56b, 0x35b5a8fa, 0x42b2986c, 0xdbbbc9d6, 0xacbcf940, 0x32d86ce3, 0x45df5c75, 0xdcd60dcf, 0xabd13d59, 0x26d930ac, 0x51de003a, 0xc8d75180, 0xbfd06116, 0x21b4f4b5, 0x56b3c423, 0xcfba9599, 0xb8bda50f, 0x2802b89e, 0x5f058808, 0xc60cd9b2, 0xb10be924, 0x2f6f7c87, 0x58684c11, 0xc1611dab, 0xb6662d3d, 0x76dc4190, 0x1db7106, 0x98d220bc, 0xefd5102a, 0x71b18589, 0x6b6b51f, 0x9fbfe4a5, 0xe8b8d433, 0x7807c9a2, 0xf00f934, 0x9609a88e, 0xe10e9818, 0x7f6a0dbb, 0x86d3d2d, 0x91646c97, 0xe6635c01, 0x6b6b51f4, 0x1c6c6162, 0x856530d8, 0xf262004e, 0x6c0695ed, 0x1b01a57b, 0x8208f4c1, 0xf50fc457, 0x65b0d9c6, 0x12b7e950, 0x8bbeb8ea, 0xfcb9887c, 0x62dd1ddf, 0x15da2d49, 0x8cd37cf3, 0xfbd44c65, 0x4db26158, 0x3ab551ce, 0xa3bc0074, 0xd4bb30e2, 0x4adfa541, 0x3dd895d7, 0xa4d1c46d, 0xd3d6f4fb, 0x4369e96a, 0x346ed9fc, 0xad678846, 0xda60b8d0, 0x44042d73, 0x33031de5, 0xaa0a4c5f, 0xdd0d7cc9, 0x5005713c, 0x270241aa, 0xbe0b1010, 0xc90c2086, 0x5768b525, 0x206f85b3, 0xb966d409, 0xce61e49f, 0x5edef90e, 0x29d9c998, 0xb0d09822, 0xc7d7a8b4, 0x59b33d17, 0x2eb40d81, 0xb7bd5c3b, 0xc0ba6cad, 0xedb88320, 0x9abfb3b6, 0x3b6e20c, 0x74b1d29a, 0xead54739, 0x9dd277af, 0x4db2615, 0x73dc1683, 0xe3630b12, 0x94643b84, 0xd6d6a3e, 0x7a6a5aa8, 0xe40ecf0b, 0x9309ff9d, 0xa00ae27, 0x7d079eb1, 0xf00f9344, 0x8708a3d2, 0x1e01f268, 0x6906c2fe, 0xf762575d, 0x806567cb, 0x196c3671, 0x6e6b06e7, 0xfed41b76, 0x89d32be0, 0x10da7a5a, 0x67dd4acc, 0xf9b9df6f, 0x8ebeeff9, 0x17b7be43, 0x60b08ed5, 0xd6d6a3e8, 0xa1d1937e, 0x38d8c2c4, 0x4fdff252, 0xd1bb67f1, 0xa6bc5767, 0x3fb506dd, 0x48b2364b, 0xd80d2bda, 0xaf0a1b4c, 0x36034af6, 0x41047a60, 0xdf60efc3, 0xa867df55, 0x316e8eef, 0x4669be79, 0xcb61b38c, 0xbc66831a, 0x256fd2a0, 0x5268e236, 0xcc0c7795, 0xbb0b4703, 0x220216b9, 0x5505262f, 0xc5ba3bbe, 0xb2bd0b28, 0x2bb45a92, 0x5cb36a04, 0xc2d7ffa7, 0xb5d0cf31, 0x2cd99e8b, 0x5bdeae1d, 0x9b64c2b0, 0xec63f226, 0x756aa39c, 0x26d930a, 0x9c0906a9, 0xeb0e363f, 0x72076785, 0x5005713, 0x95bf4a82, 0xe2b87a14, 0x7bb12bae, 0xcb61b38, 0x92d28e9b, 0xe5d5be0d, 0x7cdcefb7, 0xbdbdf21, 0x86d3d2d4, 0xf1d4e242, 0x68ddb3f8, 0x1fda836e, 0x81be16cd, 0xf6b9265b, 0x6fb077e1, 0x18b74777, 0x88085ae6, 0xff0f6a70, 0x66063bca, 0x11010b5c, 0x8f659eff, 0xf862ae69, 0x616bffd3, 0x166ccf45, 0xa00ae278, 0xd70dd2ee, 0x4e048354, 0x3903b3c2, 0xa7672661, 0xd06016f7, 0x4969474d, 0x3e6e77db, 0xaed16a4a, 0xd9d65adc, 0x40df0b66, 0x37d83bf0, 0xa9bcae53, 0xdebb9ec5, 0x47b2cf7f, 0x30b5ffe9, 0xbdbdf21c, 0xcabac28a, 0x53b39330, 0x24b4a3a6, 0xbad03605, 0xcdd70693, 0x54de5729, 0x23d967bf, 0xb3667a2e, 0xc4614ab8, 0x5d681b02, 0x2a6f2b94, 0xb40bbe37, 0xc30c8ea1, 0x5a05df1b, 0x2d02ef8d]; // PNG Cyclic Redundancy Code algorithm // http://www.w3.org/TR/PNG/#D-CRCAppendix var crc32 = function(/*uint[]*/buf) { var c = 0xffffffff >>> 0, n = buf.length >>> 0, i; for( i=0 ; i < n ; ++i ) c = CRC_256[( ( c>>>0 ) ^ buf[i]) & 0xff] ^ (c >>> 8); return (c ^ 0xffffffff)>>>0; }; var PNG_PROLOG = "\x89PNG\r\n\x1A\n\x00\x00\x00\rIHDR\x00\x00\x00\r\x00\x00\x00\r\b\x03\x00\x00\x00E5\x14N\x00\x00\x00\x06", PNG_EPILOG = "\x00\x00\x00\x16IDATx\xDAb`D\x06\f\x8C\f\b0\xC8x\xC8\x00 \xC0\x00\x11\xC6\x001{U\x93\xB6\x00\x00\x00\x00IEND\xAEB`\x82"; return function(/*uint[3]*/rgb) { var buf = [0x50,0x4C,0x54,0x45, rgb[0], rgb[1], rgb[2], 0, 0, 0], crc = crc32(buf), i, r; buf = buf.concat([ (crc>>>24)&0xFF, (crc>>>16)&0xFF, (crc>>>8)&0xFF, (crc>>>0)&0xFF ]); i = buf.length; while( i-- ) buf[i] = String.fromCharCode(buf[i]); r = PNG_PROLOG + buf.join('') + PNG_EPILOG; buf.length = 0; buf = null; return r; }; })(); var PNG_NONE = "\x89PNG\r\n\x1A\n\x00\x00\x00\rIHDR\x00\x00\x00\r\x00\x00\x00\r\b\x03\x00\x00\x00E5\x14N\x00\x00\x00\fPLTE\xFF\xFF\xFF\x00\x00\x00\xFF\xBC\xBC\xFF\x00\x00 C\x89[\x00\x00\x002IDATx\xDAL\xC7\xC9\x01\x000\b\x020\xD4\xFDwn\xF1\xC2\xFC\x02\xBB`\x18\x1Eg\x1E\xAE\xFD`\xC7\xEC2\xB3J\xAFS\x9B\xE46\x9C\xC2)\xDC\xF5\x04\x18\x00+\xB9\x00z\xA7\xBDj\x1B\x00\x00\x00\x00IEND\xAEB`\x82"; var localeColor = function(/*str*/name) { return '['+ app.translateKeyString('$ID/'+name)+ ']'; }; // Parse the app|doc Swatches //---------------------------- var RGB = ColorSpace.RGB, SPOT = ColorModel.SPOT, REGISTRATION = ColorModel.REGISTRATION, MIXEDINK = ColorModel.MIXEDINKMODEL; var target = (app.documents.length&& app.activeDocument)||app, swatches = target.swatches. everyItem().getElements(), n = swatches.length, color, rgbValues, colors = [], sp, md, nm, i, t; for( i=n-1 ; i>=0 ; i-- ) { color = swatches[i]; if( !(color instanceof Color) ) continue; color = color.getElements()[0]; sp = color.space; md = color.model; nm = color.name; if( MIXEDINK==md ) continue; // TODO switch( -(REGISTRATION==md||"Black" == nm) || +(RGB==sp) ) { case -1: rgbValues = [0,0,0]; nm = localeColor(nm); break; case 1: rgbValues = color.colorValue; break; default: // backup the color value t = color.colorValue; // convert to rgb color.space = RGB; rgbValues = color.colorValue; // revert to the color space color.space = sp; color.colorValue = t; } if( nm=="Paper" ) nm = localeColor(nm); colors.unshift({ name:nm, png: pngSwatch(rgbValues), id:color.id }); } colors.unshift({ name:localeColor("None"), png: PNG_NONE, id:target.swatches.itemByName('None').id }); // UI //---------------------------- var w = new Window("dialog", "See Your Swatches!"), ddl = w.add("dropdownlist"); n = colors.length; for( i=0 ; i < n ; ++i ) (ddl.add('item', "\xa0"+ colors[i].name)).image = colors[i].png; ddl.selection = 0; w.show();

• Original post: http://forums.adobe.com/message/3426666#3426666
Using the ‘mouseout’ event in a palette
Automatically reactivate the application when the mouse leaves the drawable area of the palette:
#targetengine "mySession" var pal = new Window("palette", "focustest", undefined, {resizeable:true, closeButton:true}); pal.st = pal.add("statictext", undefined, "some static text..."); pal.addEventListener('mouseout', leaveTestPalette); function leaveTestPalette(/*MouseEvent*/mev) { if( mev.target instanceof Window ) app.activate(); } pal.show();
• Original post: http://forums.adobe.com/message/3462710#3462710
Miscellaneous
Open an URL in the default browser
An adaptation of Gerald Singelmann's function:
function openInBrowser(/*str*/ url) //---------------------------- { var isMac = (File.fs == "Macintosh"), fName = 'tmp' + (+new Date()) + (isMac ? '.webloc' : '.url'), fCode = isMac ? ('<?xml version="1.0" encoding="UTF-8"?>\r'+ '<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" '+ '"http://www.apple.com/DTDs/PropertyList-1.0.dtd">\r'+ '<plist version="1.0">\r'+ '<dict>\r'+ '\t<key>URL</key>\r'+ '\t<string>%url%</string>\r'+ '</dict>\r'+ '</plist>') : '[InternetShortcut]\rURL=%url%\r'; var f = new File(Folder.temp.absoluteURI + '/' + fName); if(! f.open('w') ) return false; f.write(fCode.replace('%url%',url)); f.close(); f.execute(); $.sleep(500); // 500 ms timer f.remove(); return true; } // Test: openInBrowser("http://www.indiscripts.com/");
• Original post: http://forums.adobe.com/message/3180866#3180866
Processing XMP data through the XMPScript API
Just a sample code:
TextFrame.prototype.getCaption = function() //---------------------------- // Here you can customize the XMP caption { // E.g.: // return this.contents.replace( /[\n\r]/g , " " ); // Default: return this.contents; } Document.prototype.getScope = function() //---------------------------- // return the array of {txf,img} objects to treat { var scope = [], pages = this.pages; for ( var pg, p = pages.length-1 ; p >= 0 ; p-- ) { pg = pages[p]; if ( pg.textFrames.length != 1 ) continue; if ( pg.rectangles.length != 1 ) continue; if ( pg.rectangles[0].images.length != 1 ) continue; scope.push({ txf: pg.textFrames[0], img: pg.rectangles[0].images[0] }); } return(scope); } Application.prototype.main = function() //---------------------------- { if( this.documents.length<=0 ) { alert("Think to open a document!"); return; } var scope, sel = this.selection, t; switch(sel.length) { case 0 : scope = this.activeDocument.getScope(); break; case 2 : scope = ( function() { t = (sel[0].constructor == TextFrame) ? 0 : ( (sel[1].constructor == TextFrame) ? 1 : false ); if (t===false) return null; if ( sel[1-t].images.length != 1 ) return null; return [{ txf: sel[t], img: sel[1-t].images[0] }]; } )(); if (scope) break; default : alert("One image and one textframe "+ "must be selected."); return; } if( ExternalObject.AdobeXMPScript == undefined ) { try { ExternalObject.AdobeXMPScript = new ExternalObject('lib:AdobeXMPScript'); } catch(_) { alert("Unable to load the AdobeXMPScript library"); return; } }; var txfImg, iLink, iFile, xmpFile, xmp; var err=0, cpt=0; while (txfImg=scope.pop()) { iLink = txfImg.img.itemLink; try {iFile = new File(iLink.filePath);} catch(_) {err++;continue;} xmpFile = new XMPFile(iFile.fsName, XMPConst.UNKNOWN, XMPConst.OPEN_FOR_UPDATE); xmp = xmpFile.getXMP(); xmp.deleteProperty(XMPConst.NS_XMP, "Description"); xmp.setProperty(XMPConst.NS_XMP, "Description", txfImg.txf.getCaption()); if( xmpFile.canPutXMP(xmp) ) { xmpFile.putXMP(xmp); cpt++; } else err++; xmpFile.closeFile(XMPConst.CLOSE_UPDATE_SAFELY); if (iLink.status == LinkStatus.linkOutOfDate) iLink.update(); } alert(''+cpt+" image descriptions updated -- "+ err+" errors."); try{this.activeDocument.save();} catch(ex){} } app.main();
• Original post: http://forums.adobe.com/message/2225492#2225492
Bugs (and workarounds)
Attempt to bypass the ‘FastEntireScript Bug’
How to prevent the fastEntireScript mode to puzzle InDesign undo feature? Not easy! Here I suggest we embed the fastEntireScript block within a safe entireScript block:
var myLauncher = function() { alert('Undo mode:' + app.activeScriptUndoMode); // still in safe zone app.doScript( myFastFunc, undefined, undefined, UndoModes.fastEntireScript, // => dangerous zone "MyFastEntireScript"); alert('Undo mode:' + app.activeScriptUndoMode); // safe zone restored }; var myFastFunc = function() { alert('Undo mode:' + app.activeScriptUndoMode); // dangerous zone // do something here... // but please avoid try...catch! }; app.doScript( myLauncher, ScriptLanguage.javascript, undefined, UndoModes.entireScript, // => safe zone "MyEntireScript");
• Original post: http://forums.adobe.com/message/3464685#3464685
ScriptUI Window.children Memory Leak (CS4)
The below code demoes a memory leak in CS4 (no workaround):
// Use a persistent engine #targetengine 'myTest' // Create a minimal Dialog var w = new Window('dialog'); // Just 'hit' w.children --does nothing! w.children; // Nullify w w = null; // Double garbage collection $.gc(); $.gc(); // Count the residual objects/instances alert( $.summary() );
• Original post: http://forums.adobe.com/message/3465525#3465525
UnitValue Bug
UnitValue reverses the subtraction operands when the first term is a Number!
// tested in ID CS4 and CS5 var r = Number(10) - UnitValue(0,'in'); alert(r); // => -10 in alert(r.__class__); // => UnitValue alert(r.value); // => -10 alert(r.type); // => 'in'
• Original post: http://forums.adobe.com/message/3343724#3343724
CS3 Closure Bug
In CS3, inner variables may not hide outer variables having the same name!
var outerFct = (function() { var v = 'v in outerFct'; var innerFct = (function() { // v hides locally outer-v: var v = 'v in innerFct'; // no conflict with any outer var: var w = 'w in innerFct'; return function() { alert(v + ' - ' + w); } })(); innerFct(); })(); // Results // --- // ID CS4 alerts: // 'v in innerFct - w in innerFct' (as expected) // ID CS3 alerts: // 'v in outerFct - w in innerFct' (!!!)
• Original post: http://forums.adobe.com/message/2311472#2311472
CS4 bug with RegExp Negated Character Class
In CS4 the following regex does not behave as expected:
// THE BUG: // --- alert( /foo[^a-z0-9]/.test("foo") ); // CS4 => true (!) -- CS5 => false (ok) // A WORKAROUND (using negative lookahead): // --- alert( /foo(?![a-z0-9])./.test("foo") ); // CS4 & CS5 => false (ok)
• Original post: http://forums.adobe.com/message/3510078#3510078
Quick tips, good practices, discussions
Open a TextFrame in Text-Edit mode
One line code:
// Text Edit Mode: myTextFrame.parentStory.storyEdit();
• Original post: http://forums.adobe.com/message/3168140#3168140
Setting an XML attribute from a variable
What if you need to set an XML attribute whose name is given in a variable?
var xml = <document><pages><page/></pages></document>; var att_name = "foo"; xml.page['@'+att_name] = 123;
• Original post: http://forums.adobe.com/message/3190611#3190611
Dealing with InDesign's SpecialCharacters
Precisions about SpecialCharacters cast and conversion issues:
• http://forums.adobe.com/message/3381674#3381674
Escaping characters in RegExp
How to use backslashes in literal RegExp and in literal strings supplied to the RegExp() constructor:
// Here's a literal RegExp in JS: var RE1 = /[a\-c]/; alert( RE1.test("-") ); // TRUE alert( RE1.test("b") ); // FALSE // ...RE1 means a|-|c (as expected) // Now, using explicitly the RegExp class, // we need to pass a String : var RE2 = RegExp("[a\-c]"); alert( RE2.test("-") ); // FALSE alert( RE2.test("b") ); // TRUE // ...RE2 actually means [a-c] ! // Why? // Because "\" is a metamarker in JS literal strings. // "\" is supposed to escape a few special chars and // is ignored in other cases: alert( "\a\.\-" == "a.-" ); // TRUE ! // So, to get a "\" in a string, you need // to use "\\" var RE3 = RegExp("[a\\-c]"); alert( RE3.test("-") ); // TRUE alert( RE3.test("b") ); // FALSE // Finally RE3 works like RE1. /* One more example */ var str = "a\\t"; // str contains 3 chars: a\t // How to grab str in a RegExp? // 1) In a literal RegExp, we need to // escape the backslash (because of \t): alert( /a\\t/.test(str) ); // TRUE // 2) Using explicitly the RegExp class, // we need to express the pattern a\\t // in a literal string, so: alert( RegExp("a\\\\t").test(str) ); // TRUE // 4 backslahes to target 1 backslash!
• Original post: http://forums.adobe.com/message/2845333#2845333
Avoiding Global Variables
A few reasons to avoid globals in ExtendScript:
• http://forums.adobe.com/message/3148058#3148058
Preventing Memory Leak in a Closure
Since we cannot delete a declared variable, we have to nullify complex data to free up memory within closures:
• http://forums.adobe.com/message/3274984#3274984
Comments
Most of us learn tricks from you these days, you know! Thanks for the nice roundup.
Peter
Cool! Thanks!