InDesign Scripting Forum Roundup #10
December 14, 2016 | Snippets | en
InDesign scripts may involve unexpectedly complicate solutions for addressing tasks that seemed initially quite easy, as well as unexpectedly simple codes for solving crooked problems! The mood of the 10th episode of our ISFR series reflects this ambivalence quite well. As usual I try to focus on scripts, snippets, and methods, that open up powerful tracks to informed developers…
1/ Text and Typography
Fit Text to Frame by Adjusting Font Size
Deciphering Special Characters in Text Entities
How FontChangeLocking May Impact GREP Change Method
2/ Graphics and Geometry
Does My Container Extend Beyond Its Inner Image Frame?
Reframing Pages: a Dangerous Sport
3/ Miscellaneous
Basic Prototype for Testing Array Equality
Repairing toSource() Method for Enumerations
Making a Group Responsive Using an IdleTask
1/ Text and Typography
Fit Text to Frame by Adjusting Font Size
We already discussed in this series some text-frame-fitting scripts (e.g. “Fit Text Frame to Content – Supporting Multicolumn”, or “Apply Fit-Frame-to-Content to All Overset Items”), the most flexible being “Fit Horizontal Text Scale to Frame” as it sketches a general dichotomic algorithm for dealing with various cases.
As an illustration the code template can be reused for fitting text to frame by adjusting font size rather than width or horizontal scale. Only two changes must be done to update the original code: (a) Replacing horizontalScale
by pointSize
as input parameter (and adjusting min-max values accordingly), (b) removing condition on lines.length
in the wrong()
function if not needed (this depends on whether you have to handle single-line or multiline frames.)
The final snippet then looks like:
// Fit text in the selected frames by adjusting font size. // --- (function(/*Item[]*/a,/*str*/k,/*uint*/MIN,/*uint*/MAX,/*num*/PRECISION) { var t, s, v, w, m = [], wrong = function(){ return +t.overflows }; while( t=a.pop() ) { if( 'TextFrame'!=t.__class__ ) continue; // Init // --- v = (s=t.parentStory)[k]; m[0] = MIN; m[1] = MAX; // Try extremum and quit if status doesn't change // --- s[k] = m[1-(w=wrong())]; if( w==wrong() ){ s[k]=v; continue; } // Binary search // --- while( m[1]-m[0] > PRECISION ) { m[w=wrong()] = s[k] = (m[0]+m[1])/2; } w && (s[k] = m[0]); } })(app.properties.selection||[], 'pointSize', 6, 36, .1);
Note. — As observed by oleh.melnyk this is not a line-by-line script, the process targets each frame as a whole multiline box and uniformly adjusts the font size. However, a very similar technique can be used to achieve other tasks at various Text
levels. In itself, the dichotomic pattern doesn't change, and will remain highly efficient.
• Original discussion: forums.adobe.com/message/8847812#8847812
• See also: “On Dichotomic Divide-and-Conquer Algorithms”
Deciphering Special Characters in Text Entities
Quick reminder on those recurring questions that relate to SpecialCharacters
in InDesign text flows.
1. How do I check whether myCharacter.contents is a SpecialCharacters entity?
• In CS4, use the test ( 'number' == typeof myCharacter.contents )
.
• In CS5 and later, use ( 'object' == typeof myCharacter.contents )
instead.
Indeed, in any other case typeof
will return 'string'
(assuming myCharacter is a valid Character
instance.) From there, we can even offer a universal test that works in any InDesign version:
// Is myCaracter a 'Special Character'? // --- alert( 'string' != typeof myCharacter.contents );
2. How can I safely convert a single Character (or any Text) into a clean JS string?
Whatever the status of a Character
object (special vs. regular), the following expression always returns a pure JavaScript string:
// Character (or Text) to-String conversion // --- var str = myCharacter.texts[0].contents;
This trick prevents the contents
property from returning a SpecialCharacters
enum even if myCharacter is a special character.
Important note. — The texts[0]
selector operates as desired if it is immediately followed by .contents
. The whole thing consists in invoking the contents
property from a specifier that ends with ...texts[0]
. So if texts[0]
is used upstream in the path you'll need it again at the end. For example, the expression
myChars = story.texts[0].characters.everyItem().getElements()
returns an array of Character
instances that may still embed special entities (despite texts[0]
.) And then myChars[i].contents
is still unsafe, while myChars[i].texts[0].contents
does the job again (that is, the Enumerator
issue vanishes.)
Additional note. — In ExtendScript the expression "blabla" + myEnumerator
returns a string based on myEnumerator.valueOf()
(i.e. the underlying Number
) instead of using myEnumerator.toString()
(i.e. the underlying String.) This is a generic bug of the Enumerator
object, which hasn't been implemented with respect to ECMA's specification regarding type conversion. Normally, given an Object
myObj and a literal string "blabla"
, the string concatenation "blabla" + myObj
should implicitly invoke myObj.toString()
in order to achieve the type conversion before concatenation. Unfortunately, the Enumerator
type does not follow this rule. I suspect that the +
operator hasn't been fully and properly implemented. Using the unary syntax (+myEnumeror
) we get, as expected, a number—as +myObj
is a shorcut of myObj.valueOf()
. Now, regarding the expression anything + myObj
, the JavaScript interpreter must select either an addition if anything is a Number
(and then myObj.valueOf()
is the right guy), or a concatenation if anything is a String
, and then myObj.toString()
should be invoked… which is not the case!
In short, ExtendScript wrongly interprets myString + myEnumerator
as myString + myEnumerator.valueOf()
, which leads to the string concatenation myString + (myEnumerator.valueOf()).toString()
instead of myString + myEnumerator.toString()
.
The following and surprising results illustrate that point:
var specialChar = SpecialCharacters.BULLET_CHARACTER; alert( specialChar.__class__ ); // => Enumerator alert( specialChar ); // => BULLET_CHARACTER alert( '' + specialChar ); // => 1396862068
Added on 01/17/2017. — In case the bug above becomes a critical issue in your project, you can fix it using operator overloading, as follows:
// Globally allows to use the syntax myString + myEnum. // --- NothingEnum.NOTHING.constructor.prototype['+'] = function(x) { return this['string' == typeof x ? 'toString' : 'valueOf']() + x; };
• Original discussion: forums.adobe.com/message/8900483#8900483
How FontChangeLocking May Impact GREP Change Method
In a recent project I had to temporarily use a special character (U+E100
) as a marker for applying some specific paragraph style to the active document:
The fact that this temporary character has no corresponding glyph in the current font didn't sound like a serious problem, since my script was then using changeGrep()
to both remove this marker and change the paragraph style at the corresponding location. It looked like this:
var myDoc = app.properties.activeDocument, myBoldStyle = myDoc.paragraphStyles.itemByName('MyBoldStyle'); // Init. app.findGrepPreferences = app.changeGrepPreferences = null; // Find the marker U+E100 // + the next char (captured in $1) // --- app.findGrepPreferences.findWhat = "\uE100(.)"; // Preset changeGrep so that it both removes the // marker and change the paragraph style. // --- app.changeGrepPreferences.properties = { changeTo: '$1', appliedParagraphStyle: myBoldStyle, }; // Go! myDoc.changeGrep();
Note that findWhat
looks for two characters: the marker ("\uE100"
) and the next character captured into $1
thanks to the parentheses in the "\uE100(.)"
regular expression. Then changeGrepPreferences
is preset so that the whole capture is changed into $1
(which removes the marker) while assigning the desired paragraph style (myBoldStyle).
However, using exactly the same document and script, I got two different results in two different environments:
The unexpected behavior (platform #2) shows that, although the paragraph style is applied everywhere else, the very first character (i.e., the $1
variable) retains its old attributes. In other words, myBoldStyle does not seem to override the formatting, while changeText
/changeGrep
commands having an appliedParagraphStyle
property should definitely reset the style.
The surprising fact is, anyway, that results differ depending on the environment. In such circumstance the most likely hypothesis is that an unidentified preference parameter disrupts the homogeneity of the process.
Thanks to Trevor and Uwe Laubender—and after some trial and error—we finally found the invisible cause of that weird issue: app.fontLockingPreferences
. Here we have a boolean fontChangeLocking
property defined as follows, “If true, turns on missing glyph protection during font change.” And, as you can now guess, this option impacts how changeGREP()
deals with missing glyphs. Keep this in mind when you tweak odd characters and missing glyphs through find/change…
• Original discussion: forums.adobe.com/message/8503226#8503226
2/ Graphics and Geometry
Does My Container Extend Beyond Its Inner Image Frame?
How to check whether a frame does not extend beyond the image it contains? Simple question. Consider the figure below. There is no doubt that the left container is OK (it doesn’t jut out from the image area) while the right container is appreciably larger (its magenta background shines out.)
So, at first sight, the question is all about comparing the PARENT (=container) bounds against the CHILD (=image) bounds, making sure that the parent area is entirely contained within the child area. So the OK condition could be expressed as follows: PARENT ⊆ CHILD
.
Using visibleBounds
(or geometricBounds
) coordinates might seem the way to go. Unfortunately this approach fails as soon as rotations or complicate transformations come into play, because xxxBounds
properties return rectangular coordinates relative to the ruler space. So, in the figure below, the image bounds seen from the rulers (gray rectangle) wrongly lead to assert PARENT ⊆ CHILD
while the magenta warning signal is visible again.
So we likely need to take the actual (inner) bounding boxes into account, and their underlying transformation spaces. The question could first be addressed as follows. To ensure that the container does not extend beyond the image area, it is sufficient to check that the parent inner box is contained within the image inner box. In short we just refined the OK condition that way:
PARENT-INNER-BOX ⊆ CHILD-INNER-BOX
And that specific condition could be instantly tested using (u,v)
coordinates of the respective boxes, without any transformation or Pathfinder operations.
Alas and alack, a closer look at the problem reveals that our intuitive equation is only a sufficient condition. The below counterexample proves it is not necessary:
Here we have an OK case (no magenta signal) while the parent inner box is not contained within the child inner box. How is it possible? The parent box does not match the detailed geometry of the polygon it encloses. The polygon itself (the shape) is entirely contained within the child box, while the enclosing parallelogram (parent box) spills over.
So, the definitive OK condition is PARENT-SHAPE ⊆ CHILD-INNER-BOX
rather than PARENT-INNER-BOX ⊆ CHILD-INNER-BOX
(which is only a sufficient condition since PARENT-SHAPE ⊆ PARENT-INNER-BOX
.)
What would be great for solving the problem would be to handle the “in-child” box of the parent, that is, the visible bounding box of the parent framed in the perspective of the child coordinate space. Something like this (purple frame):
Unfortunately, the DOM does not provide (simple) access to that imaginary “in-child” box, as the concept of CoordinateSpaces.CHILD_COORDINATES
is not implemented! One can access the in-parent box of the child, but not the in-child box of the parent.
Note. — You may think that checking the state of the in-parent box of the child relative to the parent inner box could offer an indirect way of solving the original problem. But this is wrong again. The figure below clearly shows that the in-parent box of the child can entirely contain the parent box while the polygonal shape extends beyond the image area:
At this stage of the discussion, our seemingly simple problem became so convoluted that one would be tempted to turn to more frontal methods, based on PathPoint
coordinates, Bézier curve interpolations, and so on.
But… Wait a minute! There is a workaround for testing the parent shape bounding box in the perspective of the image space. Indeed, we have two crucial properties at hand:
1. The original question (“extending beyond the image area?”) is invariant by any transformation. That is, if we transform the container in some way this doesn't change the problem.
2. The whole image area by nature coincides with its inner box, which is always a parallelogram (in the perspective of the pasteboard.)
Therefore we might find a transformation T
that, being applied to the parent, will change the image box into a rectangle in the pasteboard space. And then, we could consider the “in-board” box of the parent, that is, the rectangle that encloses the parent in the perspective of the pasteboard space. Thus, we would have two comparable rectangles PARENT-REC
and CHILD-REC
so that the problem reduces to checking whether PARENT-REC ⊆ CHILD-REC
.
The figure below summarizes my trick:
Note. — In my code I use the pasteboard space (CoordinateSpaces.PASTEBOARD_COORDINATES
) because it obviously supersedes any involved coordinate space, but the spread space would have done as well.
As usual, the implementation requires a “machine epsilon” (see the EPSILON
constant) in order to prevent floating-point approximation errors which frequently occur in InDesign coordinate system. Here is the suggested code:
// SELECT THE INNER IMAGE BEFORE YOU RUN THIS SNIPPET! const CS_BOARD = +CoordinateSpaces.pasteboardCoordinates, BB_VISIBLE = +BoundingBoxLimits.outerStrokeBounds, BB_PATH = +BoundingBoxLimits.geometricPathBounds; const EPSILON = 1e-6; var image = app.selection[0], // target image (selection) parent = image.parent; // Temporarily transform the container so its image box // be a rectangle in the perspective of the pasteboard. // --- var origin = image.resolve([[.5,.5],BB_PATH],CS_BOARD)[0], mx = image.transformValuesOf(CS_BOARD)[0], T = app.transformationMatrices.add(1,1, -mx.clockwiseShearAngle, -mx.counterclockwiseRotationAngle ); parent.transform(CS_BOARD,origin,T); // Retrieve image corners in board coord space. // --- var iTL = image.resolve([[0,0],BB_PATH],CS_BOARD)[0], iBR = image.resolve([[1,1],BB_PATH],CS_BOARD)[0]; // Retrieve parent *inboard box* corners in board coord space. // --- var pTL = parent.resolve([[0,0],BB_VISIBLE,CS_BOARD],CS_BOARD)[0], pBR = parent.resolve([[1,1],BB_VISIBLE,CS_BOARD],CS_BOARD)[0]; // Revert the transformation. // --- parent.transform(CS_BOARD,origin,T.invertMatrix()); // Check whether the rectangle <pTL,pBR> is included in <iTL,iBR> // --- var r = pTL[0] >= iTL[0]-EPSILON && pTL[1] >= iTL[1]-EPSILON && pBR[0] <= iBR[0]+EPSILON && pBR[1] <= iBR[1]+EPSILON; alert( r ? 'OK' : 'KO' );
Here are some tested examples:
Note. — As pointed out by Uwe Laubender in the original discussion you may still encounter clinical cases based on important gaps between outer strokes and inner strokes in sharp-edged polygons using a significant stroke weight. In such context, a possible option is to increase EPSILON
by the stroke weight to mitigate the constraint. Not tested though.
• Original discussion: forums.adobe.com/message/9158846#9158846
• See also: “Coordinate Spaces & Transformations in InDesign”
Reframing Pages: a Dangerous Sport
Brett G. wrote: “I’m having trouble. I am changing the page size to crop then exporting to jpeg. Then I change the page size back; works great but the master page becomes disconnected. I can’t figure out how to apply it again.”
Fact is that changing pages size through reframe()
is really tricky, especially in a facing-page layout. Indeed, since those facing pages are not supposed to undergo any ‘move’ along the X-axis, each time a reframe occurs that involves changing X-coordinates, unexpected things may happen behind the scene. The page geometry is modified, its translation attributes are readjusted in its transformation matrix, the relationship with the master spread may be somehow altered, and bugs or issues with the masterPageTransform
property may occur (weird shifts that seem unrepairable), not to mention side-effects related to layout rules, and so on.
Hence, it’s hard to design a script that works in all environments, all versions, and all configurations. There is always an obscure preference that someone has activated, or disabled, which interacts with the whole system and ruins our effort.
Anyway, there is one law that we learned from experience: DO NEVER TRUST page.bounds
property. (See Coordinate Spaces & Transformations in InDesign for advanced details.) If you attempt to reframe pages, assuming that no better solution has been found, try to work with properly resolved coordinates—myPage.resolve(...)
—and stick to the tools that coordinate spaces and transformation provide. Here is an attempt to fix Brett's problem on this basis.
// YOUR SETTINGS // ===================================== // Values required IN POINTS (negative numbers allowed => increase the area.) // --- const CROP_SIDE = 12.225/*pt*/, CROP_TOP = 12.225/*pt*/, CROP_BOTTOM = 100/*pt*/; // Export options, etc. // --- const DOC_NAME_MAX_SIZE = 12, OUT_PATH = "/your/output/path/"; const JPG_EXPORT_PREFS = { jpegQuality: +JPEGOptionsQuality.high, exportResolution: 170, jpegExportRange: +ExportRangeOrAllPages.exportRange, }; (function exportPageAreas() // ===================================== // Should support the tricky 'facing-page' case, including when // pages have different sizes, and whatever the rulerOrigin mode. { // Boring constants. // --- const CS_INNER = +CoordinateSpaces.INNER_COORDINATES, CS_SPREAD = +CoordinateSpaces.SPREAD_COORDINATES, TL = +AnchorPoint.TOP_LEFT_ANCHOR, BR = +AnchorPoint.BOTTOM_RIGHT_ANCHOR; const LEFT_HAND = +PageSideOptions.LEFT_HAND; // Context validation. // --- var doc = app.properties.activeDocument; if( !doc ){ alert( "No document!" ); return; } // Export prefs. // --- const EXP_JPG = +ExportFormat.JPG, JEP = app.jpegExportPreferences, filePrefix = OUT_PATH + doc.properties.name.substr(-DOC_NAME_MAX_SIZE); JEP.properties = JPG_EXPORT_PREFS; // Last variables. // --- var pages = doc.pages.everyItem().getElements(), pg, pgName, dxLeft, dxRight, xyTL, xyBR; // Loop. // --- while( pg=pages.pop() ) { // Page corners coords in the parent space. // (Note: Do Never Trust page.bounds!) // --- xyTL = pg.resolve(TL, CS_SPREAD)[0]; xyBR = pg.resolve(BR, CS_SPREAD)[0]; // Opposite corners => reframe. // --- dxLeft = LEFT_HAND == +pg.side ? CROP_SIDE : 0; dxRight = dxLeft ? 0 : CROP_SIDE; pg.reframe( CS_SPREAD, [ [ xyTL[0]+dxLeft, xyTL[1]+CROP_TOP ], [ xyBR[0]-dxRight, xyBR[1]-CROP_BOTTOM ] ]); // Export. // --- JEP.pageString = pgName = pg.name; doc.exportFile( EXP_JPG, new File(filePrefix+('00'+pgName).substr(-3)+'.jpg') ); // Restore page. Here we can't safely re-use [xyTL,xyBR] as it is, // because spread space origin *may* have moved during reframe. // Therefore we need to formally reverse the calculation. // --- xyTL = pg.resolve(TL, CS_SPREAD)[0]; xyBR = pg.resolve(BR, CS_SPREAD)[0]; pg.reframe( CS_SPREAD, [ [ xyTL[0]-dxLeft, xyTL[1]-CROP_TOP ], [ xyBR[0]+dxRight, xyBR[1]+CROP_BOTTOM ] ]); } alert( 'Page areas have been successfully exported.' ); })();
• Original discussion: forums.adobe.com/message/8887166#8887166
3/ Miscellaneous
Basic Prototype for Testing Array Equality
You can't compare arrays using code like [1,2,3,4]==[1,2,3,4]
(result is false
.)
The reason is, when two objects are involved in a soft equality (==
) comparison, JavaScript tests them for strict equality (===
). And, in case of Object
instances, which includes Array
instances, strict equality just means testing operands by reference. Therefore distinct instances in terms of distinct new
calls will always fail in both ==
and ===
tests. Here you must keep in mind that litteral objects {...}
or arrays [...]
still result from an implicit invocation of the new
operator.
Thus, the operator ==
is not only useless for comparing arrays, it also is redundant with ===
. Good news is, in ExtendScript, you can override ==
in order to compare the elements of distinct instances :-) The trick is to redefine Array.prototype['==']
.
To my knowledge the shortest way to do so is the following code:
// Make myArray1==myArray2 relevant // --- Array.prototype['=='] = function(a) { return this.toSource() == a.toSource() };
Just include that block at the beginning of your scripts and a==b
will magically make sense with Array
operands:
Array.prototype['=='] = function(a) { return this.toSource() == a.toSource() }; var a = [1,5,9,12,16,18,22,34]; var b = [1,5,9,12,96,98,22,34]; var c = [1,5,9,12,16,18,22,34]; alert( a==b ); // false alert( a==c ); // true // Works with heterogeneous items as well. // --- var d = ["aaa","bbb",999,true]; var e = ["aaa","bbb",999,false]; var f = ["aaa","bbb",999,true]; alert( d==e ); // false alert( d==f ); // true
• Original discussion (incidental note): forums.adobe.com/message/8991900#8991900
Repairing toSource() Method for Enumerations
A recurring problem with ExtendScript enumerators is, they don't provide human-readable data once unevaluated through the native toSource()
method. Take for example DiacriticPositionOptions.OPENTYPE_POSITION.toSource()
. This will return either the string "({})"
(CS5 and later), or at best "(new Number(1685090164))"
(previous InDesign versions.) Either way that's not very interesting! We usually invoke .toSource()
on DOM object properties in the hope of fully revealing their inner state. At best we can coerce a punctual enumerator into a Number
using XXX.valueOf()
, which always works but doesn't speak for itself at all. And even with objectified enumerators (CS5+) that supports .toString()
we only retrieve the formal key of the Enumerator, e.g. "OPENTYPE_POSITION"
, but the parent Enumeration (DiacriticPositionOptions
) is missing so we cannot easily reconstruct a clean syntax that would evaluate to the enum value.
Fortunately the great Dirk Becker from Ixta.com published a brilliant workaround a few years ago. Its enumToSource.jsx script simply “repairs toSource() for enumerations” in CS5 and later, based on scanning the $.dictionary
structure and overriding Enumerator
's prototype.
Note. — Enumerator
, as a constructor, is something of a hidden object in ExtendScript. In fact, those strange things are called live objects and we need to wake them up before any explicit access. That's why Dirk uses the syntax NothingEnum.NOTHING.constructor
to handle the Enumerator
function (and then its prototype.)
As Dirk's code relies on parsing the $.dictionary
database, it always properly reflects the current DOM in use. It fixes Enumerator's prototype in a way that makes toSource()
both effective and verbose, including when you invoke it from any DOM object properties
that may contain enumerators. Also, since the code only instantiates NothingEnum
in the global scope, one can consider it very 'non-polluting' when included in a larger project or framework.
On my side, I wrote a (slightly) different implementation which just (slightly) increases the verbosity of the result. Numbers are now formatted in 0xHEXA
form and the Adobe 4-char tag is shown too. Here it is, improved from Dirk's comments:
// Repair toSource() for enumerations [alternative version] // for InDesign CS5 and later -- v.2.0 // ------------------------------------- // Based on Dirk Becker's 2014 original code at // http://ixta.com/scripts/utilities/enumToSource.html // ------------------------------------- // Output either the format // // <Enumeration>.<Enumerator> /* <Hexa> [<Tag>] */ // if <Enumeration> is the unique parent for that value, // e.g: AnchorPosition.INLINE_POSITION /* 414F5069 [AOPi] */ // // or // // 0x<Hexa> /* <Enumerator> [<Tag>] */ // if Enumerator's value belongs to multiple parents, // e.g: 0x74787466 /* TEXT_FRAME [txtf] */ // ------------------------------------- // TIP: You can access the whole 'database' (cache) // browsing the following object // NothingEnum.NOTHING.__proto__.toSource.Q // ------------------------------------- (function(/*obj&*/Q, n,i,a,s,x,t,k,v) { const DIC = $.dictionary; const CHR = String.fromCharCode; const FMT = $.global.localize; const REGULAR_PTN = "%1.%2 /*\xA0%3\xA0[%4]\xA0*/"; const SPECIAL_PTN = "0x%3 /*\xA0%2\xA0[%4]\xA0*/"; for( a=DIC.getClasses(), n=a.length, i=-1 ; ++i < n ; ) { x = DIC.getClass(s=a[i]).toXML(); if( 'true' != x.@enumeration ) continue; x = x.elements.property; for each( t in x ) { k = String(t.@name); v = Number(t..value); Q[k] = FMT( Q.hasOwnProperty(k)?SPECIAL_PTN:REGULAR_PTN, // %1 :: Enumeration class name s, // %2 :: Enumerator key k, // %3 :: Hexa representation v.toString(16).toUpperCase(), // %4 :: Adobe 4-char tag CHR( 0xFF&(v>>>24), 0xFF&(v>>>16), 0xFF&(v>>>8), 0xFF&(v>>>0) ) ); } } })( (NothingEnum.NOTHING.constructor.prototype.toSource= function F(){ return F.Q[this] || "({})" }).Q={} );
• Original discussion (scroll down): forums.adobe.com/message/4152441#4152441
• See also: ixta.com/scripts/utilities/enumToSource.html
Making a Group Responsive Using an IdleTask
Ypsillon wrote: “Every day I’m working with grouped elements which consist of one rectangle and several text trames—usually three. I need to change dimension of the rectangle every time but not to ungroup the objects. So I’m clicking twice on the rectangle, move it and resize. In this case all the text frames in group remain sometimes far ... far from the rectangle but there is a need to bring them together again. I’m wondering if there is a possibility to write a script which can concentrate the elements of group by moving text frames to the bounds (or origin) of the rectangle.”
This request sounded like a quite relevant opportunity for implementing an IdleTask
. During Idle events we can make InDesign check whether the currently selected Rectangle
, if any, has been resized by the user. Then we can check whether this object belongs to a group where other elements need to be adjusted accordingly. Here is the magic (based on Ypsillon's conditions):
#targetengine magnetGroupRectangle function follow() { const T=0, L=1, B=2, R=3; if( !app.properties.activeDocument ) return; if( !app.properties.selection ) return; if( 1 != app.selection.length ) return; var a, i, every, b, i, dx, dy, r = app.selection[0], g = r.parent; // Adjust the following to your own conditions. // --- if( !( r instanceof Rectangle ) ) return; if( !( g instanceof Group ) ) return; if( 3 != g.pageItems.count() ) return; a = (every=g.textFrames.everyItem()).getElements(); if( 2 != a.length ) return; b = every.visibleBounds; i = +(b[0][T] > b[1][T]); r = r.visibleBounds; // Right magnet dx = r[R]-b[i][L]; b[i][T]=r[T]; b[i][B]=r[B]; b[i][L]+=dx; b[i][R]+=dx; // Bottom magnet i = 1 - i; dy = r[B]-b[i][T]; b[i][L]=r[L]; b[i][R]=r[R]; b[i][T]+=dy; b[i][B]+=dy; a[0].visibleBounds = b[0]; a[1].visibleBounds = b[1]; } (function(tasks,name,rate,callback) { var t = tasks.itemByName(name); if( t.isValid ) { t.eventListeners.everyItem().remove(); t.remove(); } tasks.add({name:name, sleep:rate}) .addEventListener(IdleEvent.ON_IDLE, callback, false); })(app.idleTasks,'magnetGroupRectangle',15,follow);
Note. — What makes the script session-persistent is the #targetengine
directive. What makes the script responsive is the IdleTask
instance (and the underlying event listener.)
YouTube demo:
• Original discussion: forums.adobe.com/message/8515468#8515468
• See also: ISFR #9 — “Using an IdleTask to Create Responsive PageItems”
GitHub page: github.com/indiscripts
Twitter stream: twitter.com/indiscripts
YouTube: youtube.com/user/IndiscriptsTV
Comments
Many thanks for this round-up, Marc.
Peter
Hi Marc:
I'm just now trying out IndexMatic2 with InDesign 2018. It gives me an error message error #21, Undefined is not an object.
Is IM2 compatible with InD 2018 yet? If not, do you know when it will be?
Thanks,
Susan
Hi Susan,
Thanks for your report.
IndexMatic² is supposed to work in CC 2018, but it seems you found a bug.
Please, contact me at:
support {at} indiscripts [dot] com
Regards,
Marc