/***********************************************************************/ /* */ /* MagicFit 2.1b for InDesign CS / CS2 -- 01/18/06 */ /* */ /* Fits to content the WIDTH of the selected text container(s) */ /* */ /* Features: */ /* - Fits selected TextFrame(s) width to content */ /* - 1st call: "strict" fitting (preserve each line's length) */ /* - 2nd call (within 2 secs) : "fluid" fitting (preserve height) */ /* - (NEW) Alternate fitting of table column(s) if selected */ /* - (NEW) Compute a minimal width by parsing embedded objects */ /* - (NEW) Runs on selected frames, CELLS, groups, insertion pt */ /* */ /* Installation & usage: */ /* */ /* 0) !! CS2 users only !! */ /* Rename this script file with .jsx extension */ /* (activating extend script features) */ /* */ /* 1) Put the present file into the Presets/Scripts/ subdir */ /* */ /* 2) Run InDesign, open a document and select object(s) to fit */ /* (or put insertion point into) */ /* */ /* 3) Run the script via Window > Automation > Scripts */ /* and double-clic on MagicFit.js */ /* Alternate way: assign a keyboard shortcut to the script via */ /* Edit > Keyboard Shortcuts... > Product area:"Scripts" */ /* */ /* Help (FR) : http://marcautret.free.fr/geek/indd/magicfit/ */ /* (sorry, that's a french web page!) */ /* */ /* Feedbacks : marcautret@free.fr */ /* */ /***********************************************************************/ //---------------------------------------------------------- // SETTINGS //---------------------------------------------------------- var LATENCE = 2; // in seconds (default:2) var PRECISION = 0.5; // in pts (default:0.5) var APP_INT_VERSION = parseInt(app.version); //---------------------------------------------------------- // TOOLBOX FUNCTIONS //---------------------------------------------------------- /*void*/ function exitMessage(/*exception*/ ex) //---------------------------------------------------------- { alert("Error:\n" + ex.toString()); exit(); } //---------------------------------------------------------- // DOCUMENT METHODS //---------------------------------------------------------- /*void*/ Document.prototype.setUnitsTo = function(/*units*/ newUnits) //---------------------------------------------------------- // units can be single value (horiz=vert) or array(horizUnits, vertUnits) { var arrUnits = (newUnits.length) ? newUnits : new Array(newUnits,newUnits); this.viewPreferences.horizontalMeasurementUnits = arrUnits[0]; this.viewPreferences.verticalMeasurementUnits = arrUnits[1]; } /*arr2*/ Document.prototype.getUnits = function() //---------------------------------------------------------- { return(Array( this.viewPreferences.horizontalMeasurementUnits, this.viewPreferences.verticalMeasurementUnits)); } /*bool*/ Document.prototype.withinDelay = function() //---------------------------------------------------------- { if (this.label) return( (Date.parse(Date())-this.label) <= LATENCE*1000 ); return(false); } /*void*/ Document.prototype.storeTimeStamp = function() //---------------------------------------------------------- { this.label = Date.parse(Date()).toString(); } //---------------------------------------------------------- // GENERIC METHODS (OBJECT LEVEL) //---------------------------------------------------------- /*arr*/ Object.prototype.asObjsToFit = function() //---------------------------------------------------------- // Returns the "fittable-container" corresponding to THIS // Return array or collection HorizFit-compliant // NULL if failure { switch(this.constructor.name) { case "TextFrame" : // textframe -> singleton this return(Array(this)); case "Cell" : // cells -> parent columns var r = new Array(); // !! [CS1] Cell::parentColumn === Cell !! // !! [CS2] Cell::parentColumn === Column !! // !! [CS2] Cells::lastItem().parentColumn BUG !! var c0 = this.cells.firstItem().name.split(":")[0]; var c1 = this.cells.lastItem().name.split(":")[0]; for (var i=c0 ; i<=c1; i++) r.push(this.parent.columns[i]); return(r); case "Table" /*CS2*/ : // table -> columns return(this.columns); case "Group" : // group -> textFrames return((this.textFrames.length>0) ? this.textFrames : null); case "Text" : // selection is Text or InsertionPoint case "InsertionPoint" : // -> run on container var textContainer = this.getTextContainer(); return((textContainer) ? textContainer.asObjsToFit() : null); default: return(null); } } /*obj*/ Object.prototype.getTextContainer = function() //---------------------------------------------------------- // Returns Text's or InsertionPoint's container : // Type returned: TextFrame or Cell - NULL if failure { try { // try...catch because of CS2 behaviour if (this.parent.constructor.name == "Cell") return(this.parent); if (this.parentTextFrames) // plural in CS2 return(this.parentTextFrames[0]); if (this.parentTextFrame) // single in CS1 return(this.parentTextFrame); return(null); } catch(ex) {return(null);} } /*int*/ Object.prototype.computeIncludedObjectsWidth = function() //---------------------------------------------------------- // Parse embedded "objects": tables, pageitems [including graphics] // and returns the max width // !! All parsed objects have to provide a computeWidth method !! { var objsNames = new Array("pageItems","tables"); // could be extended var objsWidth = 0; var w = 0; for (var j=objsNames.length-1 ; j>=0 ; j--) { for (var i=this[objsNames[j]].length-1 ; i>=0 ; i--) { try {w = this[objsNames[j]][i].computeWidth({VISIBLE:true});} catch(ex) {w=0;} if (w > objsWidth) objsWidth=w; } } return(objsWidth); } /*int*/ Object.prototype.computeWidth = function(/*bool*/ VISIBLE) //---------------------------------------------------------- // Generic computeWidth method for bounded objects // VISIBLE true -> external width // VISIBLE false -> internal width { if (VISIBLE) { if (this.visibleBounds) return(this.visibleBounds[3]-this.visibleBounds[1]); } else { if (this.geometricBounds) return(this.geometricBounds[3]-this.geometricBounds[1]); } return(0); } /*int*/ Table.prototype.computeWidth = function() //---------------------------------------------------------- // Override Object::computeWidth for Table : returns simply the width { return(this.width); } /*arr*/ Object.prototype.createLinesSizesArray = function() //---------------------------------------------------------- // Returns chars count for each LINE of this (-> array) // empty array IF this.lines==NULL OR this.lines.length==0 { r = new Array(); if (this.lines) for (var i=this.lines.length-1; i>=0 ; i--) r.unshift(this.lines[i].characters.length); return(r); } /*bool*/ Object.prototype.isoceleLines = function(/*arr*/ arrSizes) //---------------------------------------------------------- // Compare chars count beetween THIS and arrSizes argument // (generic method just presuming that THIS have lines prop.) // -> TRUE if isoceles, FALSE if not { if (this.lines.length != arrSizes.length) return(false); for (var i=arrSizes.length-1 ; i>=0 ; i--) if (arrSizes[i] != this.lines[i].characters.length) return(false); return(true); } //---------------------------------------------------------- // TEXTFRAME METHODS // intanciate the part of the abstract process for TextFrames //---------------------------------------------------------- /*bool*/ TextFrame.prototype.isEmpty = function() //---------------------------------------------------------- { return(this.characters.length==0); } /*bool*/ TextFrame.prototype.isOverflowed = function() //---------------------------------------------------------- { return(this.overflows); } /*int*/ TextFrame.prototype.getWidth = function() //---------------------------------------------------------- { return(this.computeWidth({VISIBLE:false})); } /*void*/ TextFrame.prototype.resizeWidthBy = function(/*int*/ widthOffset) //---------------------------------------------------------- // Redim the frame in width by widthOffset { this.geometricBounds = Array( this.geometricBounds[0], this.geometricBounds[1], this.geometricBounds[2], this.geometricBounds[3] + widthOffset); } /*int*/ TextFrame.prototype.computeMinWidth = function() //---------------------------------------------------------- // Returns the minWidth of the frame according to embedded content // and inner space { // inner width space var inSpace = this.textFramePreferences.insetSpacing; var inWidth = (inSpace.length) ? inSpace[1] + inSpace[3] : // distinct left & right inspace 2*inSpace; // global inspace return(this.computeIncludedObjectsWidth() + inWidth); } /*int*/ TextFrame.prototype.getCharsCount = function() //---------------------------------------------------------- { return(this.characters.length); } /*int*/ TextFrame.prototype.getLinesCount = function() //---------------------------------------------------------- { return(this.lines.length); } /*arr*/ TextFrame.prototype.getLinesSizes = function() //---------------------------------------------------------- // Return chars count BY LINE (-> array) { return(this.createLinesSizesArray()); } /*int*/ TextFrame.prototype.preserveCharsCount = function(/*int*/ charsCount) //---------------------------------------------------------- // YES -> -1 , NOT -> 1 { return( (this.characters.length != charsCount) ? 1 : -1 ); } /*int*/ TextFrame.prototype.preserveLinesCount = function(/*int*/ linesCount) //---------------------------------------------------------- // Indicates whether: // - chars count equals linesCount // - frame DOES NOT overflow // YES -> -1 , NOT -> 1 { return( ((this.overflows) || (this.lines.length != linesCount)) ? 1 : -1 ); } /*int*/ TextFrame.prototype.preserveLinesSizes = function(/*arr*/ linesSizes) //---------------------------------------------------------- // Indicates whether: // each x line isoceles linesSizes[x] // YES -> -1 , NOT -> 1 { return( (this.isoceleLines(linesSizes)) ? -1 : 1 ); } //---------------------------------------------------------- // COLUMN METHODS // intanciate the part of the abstract process for Columns //---------------------------------------------------------- /*bool*/ Column.prototype.isEmpty = function() //---------------------------------------------------------- { for (var i=this.cells.length-1; i>=0 ; i--) if (this.cells[i].characters.length>0) return(false); return(true); } /*bool*/ Column.prototype.isOverflowed = function() //---------------------------------------------------------- // Indicates whether AT LEAST a cell overflows // !! We can't trust Column::overflows !! { for (var i=this.cells.length-1 ; i>= 0 ; i--) if (this.cells[i].overflows) return(true); return(false); } /*int*/ Column.prototype.getWidth = function() //---------------------------------------------------------- { return(this.width); } /*void*/ Column.prototype.resizeWidthBy = function(/*int*/ widthOffset) //---------------------------------------------------------- // Redim the column width by widthOffset // !! we HAVE TO update the display after resizing !! { this.width += widthOffset; // updates the display if (APP_INT_VERSION > 3) // CS2+ this.recompose(); else { // CS -- thx to Tilo for this hack -- for(var i = this.cells.length - 1 ; i >= 0 ; i-- ) { // Comparing the cell contents against null // seems to internally recompose the cell! if (this.cells[i].contents == null) {} } } } /*int*/ Column.prototype.computeMinWidth = function() //---------------------------------------------------------- // Returns the minWidth of the column according to embedded content // and inner space { var iCell = null; var w = 0; var r = 0; for (var i=this.cells.length-1 ; i>= 0 ; i--) { iCell = this.cells[i]; w = iCell.computeIncludedObjectsWidth() + iCell.leftInset + iCell.rightInset; if (w > r) r = w; } return(r); } /*arr*/ Column.prototype.getCharsCount = function() //---------------------------------------------------------- // Returns SIGNED chars count BY CELL (negatif if overflows) { var r = new Array(); var sgn = 0; for (var i=this.cells.length-1 ; i>= 0 ; i--) { sgn = (this.cells[i].overflows) ? -1 : 1; r.unshift(sgn * this.cells[i].characters.length); } return(r); } /*arr*/ Column.prototype.getLinesCount = function() //---------------------------------------------------------- // Returns lines count BY CELL { var r = new Array(); for (var i=this.cells.length-1 ; i>= 0 ; i--) r.unshift(this.cells[i].lines.length); return(r); } /*bi-arr*/ Column.prototype.getLinesSizes = function() //---------------------------------------------------------- // Matrix: returns the chars count BY LINE / BY CELL { var r = new Array(); for (var i=this.cells.length-1 ; i>= 0 ; i--) r.unshift(this.cells[i].createLinesSizesArray()); return(r); } /*int*/ Column.prototype.preserveCharsCount = function(/*arr*/ charsCount) //---------------------------------------------------------- // Indicates whether: // overflow sign BY CELL x equals sgn(charsCount[x]) // YES -> -1 , NO -> 1 { var sgn = 0; for (var i=this.cells.length-1 ; i>= 0 ; i--) { sgn = (this.cells[i].overflows) ? -1 : 1; if (sgn * charsCount[i] < 0) return(1); } return(-1); } /*int*/ Column.prototype.preserveLinesCount = function(/*arr*/ linesCount) //---------------------------------------------------------- // Indicates whether: // - lines count BY CELL x equals linesCount[x] // - no cell overflows // YES -> -1 , NO -> 1 { for (var i=this.cells.length-1 ; i>= 0 ; i--) { if (this.cells[i].overflows) return(1); if (this.cells[i].lines.length != linesCount[i]) return(1); } return(-1); } /*int*/ Column.prototype.preserveLinesSizes = function(/*bi-arr*/ linesSizes) //---------------------------------------------------------- // Indicates whether: // - in each CELL x, each LIGNE y isoceles linesSizes[x][y] // (if a cell overflows, returns 1) // YES -> -1 , NO -> 1 { for (var i=this.cells.length-1 ; i>= 0 ; i--) { if (this.cells[i].overflows) return(1); if (this.cells[i].isoceleLines(linesSizes[i]) == false) return(1); } return(-1); } //---------------------------------------------------------- // METHODES CENTRALES //---------------------------------------------------------- /*void*/ Object.prototype.manageFit = function(/*bool*/ FLUIDFITTING) //---------------------------------------------------------- { // !! [CS2 only] Prevents a strange crash on wide table columns selection !! // !! Thx to Tilo for this hack -- if (APP_INT_VERSION>=4) { $.gc(); } // NOP if empty object if (this.isEmpty()) return; // min width to preserve var minWidth = this.computeMinWidth(); // let's go! this.processFit(FLUIDFITTING, minWidth); } /*void*/ Object.prototype.processFit = function(/*bool*/ FLUIDFITTING, /*int*/ minWidth) //---------------------------------------------------------- // Fits this object // if FLUIDFITTING -> fluid fitting, else: strict fitting // minWidth sets the threshold { if (FLUIDFITTING) { // FLUID FITTING if (this.isOverflowed()) { // NB : overflowed CELLS are "transparent" var charsCount = this.getCharsCount(); var evalFlag = function(thisObj) {return(thisObj.preserveCharsCount(charsCount));} } else { var linesCount = this.getLinesCount(); evalFlag = function(thisObj) {return(thisObj.preserveLinesCount(linesCount));} } } else { // STRICT FITTING // NB : overflowed columns are "intouchable" if ((this.constructor.name=="Column") && (this.isOverflowed())) return; var linesSizes = this.getLinesSizes(); var evalFlag = function(thisObj) {return(thisObj.preserveLinesSizes(linesSizes));} } // DICHOTOMIC LOOP var sgnFLAG = -1; var w = ( this.getWidth() - minWidth ) / 2; while (w >= PRECISION) { // resize width by +/- w this.resizeWidthBy(sgnFLAG*w); // +1 = increase | -1 = reduce sgnFLAG = evalFlag(this); // divide w = w/2; } // exit with sgnFLAG==+1 -> undo last reduction -> +2w if (sgnFLAG>0) this.resizeWidthBy(2*w); }