InDesign Scripting Forum Roundup #7
February 20, 2015 | Snippets | en
Do you want to stretch your InDesign-scripting power? Check out the new episode of the ISFR series and improve your skills in managing text, characters, styles, page items, events… in an automated manner.
1/ Characters, Text & Styles
A basic InDesign-to-HTML Encoder
Breakable Lines and Overset Text
Having Fun with anyItem()
UnBase Styles
2/ PageItems
Anchors and Bounding Boxes
On Changing the ContentType of a PageItem
CS6-CC Bug: Unreachable FormField Properties
3/ Event Management
Understanding stopPropagation()
Dealing with Once-Called Event Installers
ScriptUI: Window.update() and Dynamic Adjustment
4/ On the Javascript Side
Local Variables, Scopes and Closures
Adding Hidden Keys to an Array Object
Those RegExp that Freeze InDesign
5/ BONUS TRACKS
ScanRefs: Keeping Track of Memory References
New Version of ProgressBar.jsx
InGoogle
1/ Characters, Text & Styles
A basic InDesign-to-HTML Encoder
In any script that deals with character encoding we should remember that InDesign characters may
(a) have SpecialCharacters
values
(b) lead to U+FFFD
(REPLACEMENT CHARACTER) when no Unicode value is associated
(c) lead to UTF-16 surrogate pairs based on two bytes in JavaScript strings (for high Unicode values)
Non-exhaustive list, I'm afraid!
Having these rules in mind here is my first step in implementing an HTML encoder:
//======= // HELPERS //======= var findGrep = function(/*Document*/doc, /*str*/argFind, /*str?*/pStyle, /*str?*/cStyle) //------------------------------------------------ { var t; app.findGrepPreferences = NothingEnum.nothing; (t=app.findGrepPreferences).findWhat = argFind; pStyle && t.appliedParagraphStyle = pStyle; cStyle && t.appliedCharacterStyle = cStyle; return doc.findGrep(); }; // --- // Remember that JavaScript uses UTF-16 code units for strings, // so the argument 's' might have 2 chars (surrogate pairs) for the // supplementary multilingual plane. Here you have to take a // decision related to the HTML side (UTF-8 vs. UTF-16 encoding?) // --- // Also, InDesign uses U+FFFD when some replacement is done, that is, // when no Unicode code point is associated to the 'character' // --- var htmlEncode = function F(/*str*/ s) //------------------------------------------------ { // Cache // --- F.PREFIX_SZ || ( (F.PREFIX_SZ = "ZZ".toSource().replace(/ZZ.+$/,'').length), (F.SUFFIX_RE = /["']\)+$/), (F.BSLASHU_RE = /\\u/g) ); // Translate forms like '(new String("\uABCD"))' // into 'ꯍ' -- Multiple chars are supported too // --- return s.toSource().substr(F.PREFIX_SZ). replace(F.BSLASHU_RE,'&#x'). replace(F.SUFFIX_RE,';'); }; //================================================ // MAIN ROUTINE // This will encode any character from U+00A0 //================================================ const TARGET = "[^\x00-\x9F]"; var doc = app.activeDocument, chars = findGrep(doc, TARGET), i = chars.length, c, s; while( i-- ) { s = (c=chars[i].texts[0]).contents; c.contents = htmlEncode(s); }
• Original discussion: forums.adobe.com/thread/1674335
Breakable Lines and Overset Text
azimmon wrote: “I would like to know that when TextFrame.overflows is true, whether it was because there was too much text or because a line could not be broken.”
First above all, there are many possible reasons why TextFrame.overflows
is true in InDesign—not to mention baseline grid constraints, keep options, wrapping, and so many more.
Going back to the original question, it's worth noting that a-line-that-cannot-be-broken is, surprisingly, a meaningless concept, for the line in question does not actually exist! As long as the overset text is not framed in a visual container, you're just talking about a virtual line and therefore you have no clue (such as location, width, height…) for solving the problem.
So one should rephrase the question in more specific terms, or at least with additional conditions: Given a specific text frame that overflows, would it still overflow if…
In the particular case pictured in the topic, the implicit problem was to determine whether the frame width, in itself, explains why the hypothetical line does not appear. Which leads to an indirect question: all things being equal, what if the frame height was increased to make as much room as needed for, say, the whole story? Would it still overflows? If the answer is YES, then one can strongly suspect that the width is involved in the issue, that is, the text “cannot be broken” the way one expects it to.
Practically, we could just check how things evolve if we temporarily multiply the frame height by 2:
const CS_INNER = +CoordinateSpaces.innerCoordinates, AP_TOP_LEFT = +AnchorPoint.topLeftAnchor, RM_MULT = +ResizeMethods.multiplyingCurrentDimensionsBy; var tf = app.selection[0], // assumed a TextFrame is selected msg = "No issue."; // default message if( tf.overflows ) { tf.resize(CS_INNER, AP_TOP_LEFT, RM_MULT, [1,2]); msg = "The selected frame overflows because of:\r" + (tf.overflows ? "Unbreakable line." : "Some other reason."); tf.resize(CS_INNER, AP_TOP_LEFT, RM_MULT, [1,.5]); } alert( msg );
• Original discussion: forums.adobe.com/thread/1563624
Having Fun with anyItem()
Nannini wrote: “Is it possible to have a script that makes a text randomly have caps and lower caps? I would love to create a kind of effect so it would look like manuscript — but it cannot be only the same letter in caps or lower caps — I wOuld NeEd oF SOmeTHing LikE This.”
Detailed answers have been brought forward in this thread, based on either GREP tricks and Math.random()
.
In addition, we suggest a solution which is shorter than the question:
app.selection[0].texts[0].words.everyItem().characters. anyItem().capitalization = Capitalization.ALL_CAPS;
• Original discussion: forums.adobe.com/thread/1557880
UnBase Styles
cchimi wrote: “I have a function that removes the basedOn
style from a paragraph or character style that is passed to it. I thought it was working, but it came to my attention that character styles were losing their formatting when their basedOn
style was removed (actually, set to '[No Character Style]'). Setting the basedOn
style to the '[None]' style has the same effect. (…) My solution has been to create a new character style, copy all of the first style's properties (other than basedOn
and properties) over to it, remove the original style (replacing it with the new style) and then rename the new style to match the old. As far as I can tell that works, but it seems like overkill. Does anyone have a better suggestion or hint?”
Easily checking whether a style property is inherited is an open question to me. But regarding the rewriting-from-scratch routine, here is a simpler approach:
function unBaseStyle(/*ParagraphStyle|CharacterStyle*/sty) { var o = sty.properties; if( !o.hasOwnProperty('basedOn') || 0 <= o.name.indexOf('[') ) return; o.basedOn = "$ID/None"; sty.properties = o; }
• Original discussion: forums.adobe.com/thread/1685566
2/ PageItems
Anchors and Bounding Boxes
Here are some insights on InDesign's bounding boxes.
1. Although bounding boxes are not coordinate spaces in the strict sense, they provide additional coordinate systems (always related to coordinate spaces). A little known fact is that a given page item—say an Oval
instance—has distinct bounding boxes depending on the coordinate space you consider along its hierarchy. Study the figure below. At a minimum, one can distinguish the inner space bounding box of the object (blue rectangle), and its spread-space-related bounding box (magenta).
So for the same object, there are as many distinct bounding boxes as coordinate spaces involved in the hierarchy: inner space, parent space(s)… spread (and page) space, and finally pasteboard space. Each bounding box reflects the orientation—in fact, the transformation state—of the coordinate space we consider. Two bounding boxes do not coincide as soon as the related coordinate spaces are connected through an affine map that contains some non trivial rotation and/or shearing parameter.
2. Another curious fact is, while InDesign's GUI shows the inner space bounding box of the selection (for a single object), the geometricBounds property always returns the coordinates of the pasteboard-related bounding box (which in most cases is equivalent to the spread-related bounding box). In addition, obj.geometricBounds
returns the [top, left, bottom, right] values in a special coordinate system, the Ruler space system, which I will not explore here.
To keep things simple, I just consider the basic situation depicted above—an oval rotated in its parent spread—and will handle coordinates in the pasteboard space.
3. The PageItem.resolve()
method has three parameters: location, inSpace, and consideringRulerUnits (optional). The most important one, location, has almost ten different forms (all poorly documented). Basically, location specify a point anywhere in any space. You can define as well a bounding box related location, a ruler space related location, or a coordinate space location. Here we only consider the first option. The full syntax of a bounding box location is as follows:
[ MyAnchorPoint, MyBoundingBoxLimits, MyCoordinateSpace ]
which means: “considering the bounding box relative to this coordinate space (3rd param), considering whether outer strokes are included or not in that box (2nd param), take this anchor point (1st param).”
Fortunately we have a shortcut: MyAnchorPoint alone is equivalent to
[MyAnchorPoint, BoundingBoxLimits.GEOMETRIC_PATH_BOUNDS, CoordinateSpaces.INNER_COORDINATES]
.
Therefore, in the above figure:
• the location of the blue point is (fully encoded):
[AnchorPoint.TOP_LEFT_ANCHOR, BoundingBoxLimits.GEOMETRIC_PATH_BOUNDS, CoordinateSpaces.INNER_COORDINATES]
• the location of the magenta point is:
[AnchorPoint.TOP_LEFT_ANCHOR, BoundingBoxLimits.GEOMETRIC_PATH_BOUNDS, CoordinateSpaces.SPREAD_COORDINATES]
Finally, given a location, the inSpace parameter tells resolve()
in which coordinate space that location must be expressed.
Here is a simple code to check these rules:
// Assuming a PageItem is selected // --- var obj = app.selection[0]; // Boring constants // --- const AP_TOP_LEFT = +AnchorPoint.TOP_LEFT_ANCHOR, BB_GEOMETRIC = +BoundingBoxLimits.GEOMETRIC_PATH_BOUNDS, CS_SPREAD = +CoordinateSpaces.SPREAD_COORDINATES, CS_INNER = +CoordinateSpaces.INNER_COORDINATES; // Locations in the 'bounds space' format (full syntax) // --- var innerTopLeft = [AP_TOP_LEFT, BB_GEOMETRIC, CS_INNER], spreadTopLeft = [AP_TOP_LEFT, BB_GEOMETRIC, CS_SPREAD]; // Invoke resolve for both locations and // return (x,y) results in spread coordinates // --- var xyInner = obj.resolve(innerTopLeft, CS_SPREAD)[0], xySpread = obj.resolve(spreadTopLeft, CS_SPREAD)[0]; alert([ "INNER SPACE corner location in spread coordinates: "+xyInner, "SPREAD SPACE corner location in spread coordinates: "+xySpread ].join('\r\r') );
• Original discussion: forums.adobe.com/thread/1661859
On Changing the ContentType of a PageItem
When you change the contentType
property of an unassigned page item into ContentType.TEXT_TYPE
, the object implicitly becomes a TextFrame
. This conversion may yield an unresolved specifier, as shown below:
// Assuming spec will point out to some empty shape (Rectangle, etc) var spec = app.documents[0].pages[0].allPageItems[0]; spec.contentType = ContentType.TEXT_TYPE; spec.contents = 'TEST'; // => ERROR! Object doesn't support the property or method 'contents'
Assigning the long path app.documents[0].pages[0].allPageItems[0]
to a variable, spec, is the cause of the problem.
Indeed spec becomes somehow obsolete—i.e. unresolved—as soon as we change the contentType
property, because this command converts the underlying object from a Rectangle
(or equivalent) into a TextFrame
. But once the mutation is done, spec still refers to the rectangle path—e.g. /document[@id=1]//rectangle[@id=342]
—as shown using spec.toSpecifier()
. Thus, the property spec.contents
is not supported. Syntactically, spec is still a Rectangle
.
The issue does not occur if the code reuses the explicit path app.documents[0].pages[0].allPageItems[0]
, which forces the system to resolve the object again:
app.documents[0].pages[0].allPageItems[0] .contentType = ContentType.TEXT_TYPE; app.documents[0].pages[0].allPageItems[0] .contents = 'TEST';
In the first line app.documents[0].pages[0].allPageItems[0]
points out to a Rectangle
(or any equivalent unassigned SplineItem);
In the secund line app.documents[0].pages[0].allPageItems[0]
now points out to a TextFrame
.
However, reusing a complex path is not as elegant as fixing the specifier in a generic way. The usual method to repair unresolved specifiers is getElements()
. In my original code, a clean solution would be:
var spec = app.documents[0].pages[0].allPageItems[0]; spec.contentType = ContentType.TEXT_TYPE; // spec.contents = 'TEST'; // => ERROR! spec.getElements()[0].contents = 'TEST'; // => OK
• Original discussion: forums.adobe.com/thread/1645245
CS6-CC Bug: Unreachable FormField Properties
ID CS6 to CC does not properly provide access to FormField
properties as soon as there is a conflict between the default Buttons and Forms panel settings and what is done through scripting. This serious bug is detailed here by Uwe Laubender.
Note. — See comment #1 below for additional details from Uwe.
As a very first test one can check that using myObject = myTextBox.properties
in a snippet won't generate errors anymore, since all failing properties (fontSize
, multiline
, etc.) are not considered available at all in the TextBox
instance.
Interestingly, all these properties become fully functional (read and write access) when explicitly set at creation time:
var TB = { description: 'foobar', // default: '' fontSize: 16, // default: 0 hiddenUntilTriggered: true, // default: false multiline: true, // default: false name: 'myTextBox', // default: auto password: true, // default: false readOnly: true, // default: false required: true, // default: false scrollable: true, // default: false }; var doc = app.activeDocument, tb = doc.textBoxes.add(TB), k, a = []; for( k in TB ) { a[a.length] = k + ': ' + tb[k]; // no issue } alert( a.join('\r') );
However, if the user changes any of these via the Buttons and Forms panel, there is good chance that the “multiple values” error comes back when the script attempts to read the modified property. For instance, if the user manually changes the TextBox
description to any non-empty string, I don't see any workaround to safely access that custom value. There are also problems with defaults in various circumstances.
So my assumption: there is a sync issue between GUI settings and their reflection in the scripting DOM—especially when no behaviors and/or states are defined for the control.
• Original discussion: forums.adobe.com/thread/1566300
3/ Event Management
Understanding stopPropagation()
As suggested by Robert Kyle let's consider the following snippet:
var beforeCloseListenerTest = app.addEventListener("beforeClose", beforeCloseListenerTest); function beforeCloseListenerTest(beforeCloseEvent) { if( beforeCloseEvent.target.constructor === LayoutWindow ) { // first beforeClose event // --- alert("LayoutWindow is closing. Propagation will be stopped."); beforeCloseEvent.stopPropagation(); } else { alert(beforeCloseEvent.target.constructor.name + " is closing."); } }
Robert wrote: “When this event listener is running and a document is closed the alert is displayed saying that the Layout Window is closing. As expected. But the second alert also displays when the Document closes. My brain is stuck on the idea that stopPropagation() should prevent that.”
But Event.stopPropagation()
does not operate the way one could expect it to. In fact, there are very few cases where stopping the propagation of an event is relevant in the InDesign DOM event field, because most of the time we only manage an event from a single listener and through a single event handler. That is, our code usually manages a single propagation step and there is no further stage to be stopped at all.
What is confusing in the above code is that two distinct events are actually considered, each having its own propagation process. The first event that occurs—say ev1—is a beforeClose
on the LayoutWindow
target, then a distinct beforeClose
event occurs—ev2—on the Document
target.
Stopping event propagation makes sense only if some event has multiple registered listeners and/or handlers that may manage the event instance during its BUBBLING
phase, that is, from the AT_TARGET
step to the very last listener in the hierarchy.
Let's consider ev1 (that is, the layout window's beforeClose
event). It occurs at the LayoutWindow
target, then it ‘bubbles’ to the document level (since LayoutWindow.parent
is a document) and finally to the Application
level. Hence there are three propagation steps for that event.
As for ev2 (that is, the document's beforeClose
event), it occurs separately. Its target is the document being closed, then it bubbles to the app, so there are two propagation steps for that event.
To highlight this, let's create a generic event handler (evHandler) then attach it to any possible listener for the beforeClose
event:
#targetengine 'test01' function evHandler(ev) { alert([ 'evHandler being called.', 'target: ' + ev.target.constructor.name, 'listener: ' + ev.currentTarget.constructor.name, 'phase: ' + ev.eventPhase ].join('\r')); } // assuming that a document is open // just before you execute the script // --- app.activeWindow.addEventListener('beforeClose', evHandler); app.activeDocument.addEventListener('beforeClose', evHandler); app.addEventListener('beforeClose', evHandler);
On closing the document's window, you will observe that evHandler
is called 5 times, that is:
• Three times for the window's beforeClose
event (ev1) which traverses LayoutWindow, Document and Application, in that order;
• Two times for the document's beforeClose
event (ev2) which traverses Document and Application, in that order.
Now if you restart InDesign, set up a similar context and append the line
ev.stopPropagation();
to evHandler
before you run the script, then you observe that the callback is now called twice, that is, once for each event. Only the AT_TARGET
phase is reported, because both the window and the document event propagation is stopped as soon as the event has been managed (at the target stage). That's the actual meaning of stopPropagation()
. This method only prevents a specific event from being handled by higher listeners during the bubbling mechanism. More on this topic can be found here: quirksmode.org/js/events_order.html
But as said above, there is usually no point in handling events from volatile listeners such as Document
or Window
instances when dealing with InDesign DOM events. Most scripts set listeners and handlers at the Application
level, since this is a stable entity that can listen to any event (whatever its target).
• Original discussion: forums.adobe.com/thread/1551372
Dealing with Once-Called Event Installers
There are many situations where event-driven scripts need to check whether some event stuff is installed, then to prevent the script from reloading, re-invoking or duplicating what is already here. A radical approach is to remove everything everywhere at the startup script level in order to make sure that the room is clean before processing. But this is a very bad idea. No scripter should ever release a startup script that contains code like app.eventListeners.everyItem().remove()
—except for clinical purpose!
We need rather to face a generic problem: How to make sure that some routine or action will be performed once (in particular in a session-persistent context)?
There are various ways to reach this goal in ExtendScript. Here are my favorites:
1. At the engine level, you can prevent the entire code from being re-executed, using the following pattern:
#targetengine 'setupOnce' var UID; $[UID='_'+$.engineName] || ($[UID]=function() { // your script goes here })();
2. In a more complex script or library, you can ‘decorate’ a function so that it does not accidentally re-run:
#targetengine 'functionOnce' var fOnce; fOnce || (fOnce = function F() { if( !F.ONCE ) return; // your function code goes here // ... delete F.ONCE; }).ONCE=1; // calling the func somewhere // --- fOnce(); // executed // calling the func somewhere else // --- fOnce(); // noop
Depending on your requirements, one or other of these strategies should help you prevent event listeners from being registered multiple times.
ADDITIONAL NOTE: Functions vs. Listeners Live Cycle
Removing an event listener is something like removing a link between an event and a function (the event handler). Nothing more. Technically, best is probably to backup listener's id
somewhere—using either session-persistent data structure, app.insertLabel()
or whatever—then to call app.eventListeners.itemByID(id).remove()
.
Now let's try to get the whole picture. Say we have a jsx file that contains some code under a #targetengine
directive. This directive creates a persistent scope—or might we say a context—where any defined variable, function, or entity, will persist during the InDesign session. That is, once the script has been executed, all those entities will keep their state or value. In particular, some functions are then available as event handlers, meaning they can be triggered from the external world (i.e. InDesign DOM events, or ScriptUI user events if a palette is active).
Such script as a program is only running when the jsx is executed. This stage might be regarded as the installation of the context. Then, the code contained in this script is not running anymore as long as ID (or ScriptUI) events do not occur. But, thanks to session persistence, functions and data are available on-demand in their very last state. Now if some event occurs which is attached to some event handler defined in the code, then the corresponding function is executed. At this specific time, some part of the code will therefore be running, depending on how the called function interacts with other stuff, data, subroutines, available in the scope.
In short, the whole script installs data and functions to be driven later by incoming events.
Question: “Can the script (itself) be removed, so that the code can no longer react to anything?”
To my knowledge the answer is no. The script can't be removed in the sense of cleaning out the session-persistent engine that owns the code—except of course if you quit/restart InDesign.
But you can inhibit event handler(s) by removing the related listener(s) as said above:
• InDesign DOM: myEventListener.remove()
• ScriptUI: myWidget.removeEventListener(
/*str*/ eventName,
/*fct*/ eventHandler,
/*bool*/ capturePhaseAsDeclared
)
• Original discussion: forums.adobe.com/thread/1551727
ScriptUI: Window.update() and Dynamic Adjustment
Can Window.update()
help us keeping a text widget center-justified during dynamic changes in a ScriptUI container? Here are some details regarding this mysterious method:
ScriptUI Window.update()
“Allows a script to run a long operation (such as copying a large file) and update UI elements to show the status of the operation. Normally, drawing updates to UI elements occur during idle periods, when the application is not doing anything and the OS event queue is being processed, but during a long scripted operation, the normal event loop is not running. Use this method to perform the necessary synchronous drawing updates, and also process certain mouse and keyboard events in order to allow a user to cancel the current operation (by clicking a Cancel button, for instance).
During the update() operation, the application is put into a modal state, so that it does not handle any events that would activate a different window, or give focus to a control outside the window being updated. The modal state allows drawing events for controls in other windows to occur (as is the case during a modal show() operation), so that the script does not prevent the update of other parts of the application's UI while in the operation loop.”
Anyway, I strongly doubt we could reach any satisfactory enhancement using either w.update()
and/or w.layout.layout(…)
.
But here is a promising fact: reassigning StaticText.location
seems much faster than any other approach. So one can manually center the message—keeping the StaticText
left-justified—by simply repositioning the control with respect to the width of the text. That can be done on the fly using StaticText.graphics.measureString(newText). Give a look at the new implementation of my ProgressBar snippet to see how it works.
My tests provide good results (so far) on various ID versions from CS4 to CC.
• Original discussion: forums.adobe.com/thread/1631865
4/ On the Javascript Side
Local Variables, Scopes and Closures
Given two independent function f1 and f2, is it possible for f2 to have access to some locale variable created in f1's body?
No, it is not. A local variable only lives in the scope of the function (body) where it is declared. In that sense any local variable should be considered private (in OOP terms.)
A basic workaround is based on setting a global scope for the variable you want to share throughout the functions, but then it is visible from every place of your code. Technically, the variable is declared outside of the function bodies, which leads to “polluting the global namespace.”
Another solution can be implemented using closure mechanism. You can create a local scope (in an anonymous function which is executed once) then return your main function and additional helpers from that scope. All inner functions have then access to the local variables that have been declared in the scope. For example, you can create a function f1 and attach to it a getter function as follows:
var f1 = (function(/*anonymous*/__) { // Local variable to be nested in the closure // --- var v1; (__ = function(/*optional init value*/x) { // Here is your *actual* function // do what you want with v1 // --- v1 = 'undefined'!=typeof x ? x : Math.random(); } ).getVar = function() { // Here is your getter // --- return v1; }; return __; })(); var f2 = function() { alert( f1.getVar() ); }; // Process // --- f1('init'); f2(); // => 'init' f1(); f2(); // => some random number f1(); f2(); // => another random number // etc
This way v1
remains almost private but the outer code can read it through f1.getVar()
.
However this might be a complicate approach for scripts that don't require high security level. Another option, really easy to set up, is to use the fact that a function is an object. Instead of declaring a local variable, one can just handle v1
as a (public) member of f1
, as follows:
var f1 = function F(/*optional init*/x) { // Do something with F.var // --- F.v1 = 'undefined'!=typeof x ? x : Math.random(); }; var f2 = function() { alert( f1.v1 ); } f1('init'); f2(); // => 'init' f1(); f2(); // => some random number f1(); f2(); // => another random number
• Original discussion: forums.adobe.com/thread/1543304
Adding Hidden Keys to an Array Object
One of my favorite tricks is to add hidden keys into an Array object. These keys remain transparent to ScriptUI—in case you use this array as a data provider for a ListBox
—but they can still be used to save ordering data for additional sort or display features.
Here is a basic example:
var names = ["Alan Turing","Steve Jobs","Bill Gates","John Mac Doe"]; (function(a,i,k,p) { i = a.length; while( i-- ) { p = (k=a[i]).indexOf(' '); 0 <= p && (k=k.substr(1+p)+', '+k.substr(0,p)); a['_'+a[i]] = k; } a.sort(function(x,y){return x=a['_'+x],y=a['_'+y],(-(x<y)||(x>y))}); })(names); // Here your code
The above function performs two tasks:
• It creates hidden keys in the form "_<ItemString>"
associated to <Name>,<Surname>
values.
• It reorders array items with respect to those hidden key-value pairs.
This way you can also handle the associated <Name>,<Surname>
sequence of any item using the "_"
(underscore) prefix.
E.g.: names["_John Mac Doe"] => "Mac Doe, John"
• Original discussion: forums.adobe.com/thread/1595578
Those RegExp that Freeze InDesign
In the field of JS RegExp objects, greedy quantifiers like +
, *
, or {m,n}
cause issues in InDesign—in particular, in CS6 and later—when mixed with optional sub-patterns. For instance, a very simple way to create an infinite loop in CS6-CC is:
alert( /(aA?|bB?)+$/.test("bx") ); // CS4: FALSE ; CS6-CC: FREEEEZE!
or even:
alert( /(a|bB?)+$/.test("bx") ); // CS4: FALSE ; CS6-CC: FREEEEZE!
Those explosive quantifiers are not properly addressed due to various backtracking bugs which Adobe devs don't seem to be aware of. Another fact is that ExtendScript CS6-CC doesn't interpret the scheme /((A|B)|C)/
the right way:
alert( /^((a|b)|c)+$/.test("ac") ); // CC: FALSE! alert( /^((a|b)|c)+$/.test("ca") ); // CC: TRUE // Interestingly: alert( /^(c|(a|b))+$/.test("ac") ); // CC: TRUE alert( /^(c|(a|b))+$/.test("ca") ); // CC: TRUE
But, in fact, /((A|B)|C)/
is equivalent to /(A|B|C)/
isn't it? So there is no reason in principle to use these additional capturing parentheses.
• Original discussion: forums.adobe.com/thread/1639463
5/ BONUS TRACKS
ScanRefs: Keeping Track of Memory References
How to keep track of objects and methods created in a long script? How to check whether some reference (in JS terms) still exists in ExtendScript's memory. A few months ago I wrote a tool that collects and displays those references based on $.list()
. This might help to visualize deep reference relationships in your code.
You just need to include the script at the beginning of your code and call $.scanRefs()
whenever you need it.
ScanRefs.jsx is now available on GitHub:
• github.com/indiscripts/extendscript/blob/master/devtools/ScanRefs.jsx
• See also: forums.adobe.com/thread/1587488
New Version of ProgressBar.jsx
A new version of my ProgressBar component (ScriptUI) has been released on GitHub:
• github.com/indiscripts/extendscript/blob/master/scriptui/ProgressBar.jsx
It now keeps the message centered and provides various improvements.
InGoogle
Based on Martin Fischer's googleSelection.jsx script (Oct. 2010) InGoogle invokes Google from InDesign considering the active text selection.
• github.com/indiscripts/extendscript/blob/master/InGoogle.jsx
• My GitHub main page: github.com/indiscripts
Comments
Hi, Marc!
About: "CS6-CC Bug: Unreachable FormField Properties"
This bug is fixed now in CC v9.3.0 and CC v10.2.0. It is *not* fixed in CS 6.1.0 and the new SSB (Simple Save Back Service) of Adobe Creative Cloud for InDesign.
Uwe
Another comment on:
"Breakable Lines and Overset Text"
As you already stated, a text frame can be overset for many reasons.
One problem, Peter Kahrel has pointed at:
If a footnote is overset "overflows" will return "false" for the Story and the last TextFrame of that story.
Nevertheless this text frame is *indeed* overset, however showing a different symbol of overflow opposed to the symbol you get if the story of the text frame is overset.
Unfortunately the object Footnote has no property "overflows". I think, Adobe should change that or let the Story react on an overset in Footnotes. InDesign's Preflight will notice, if a Footnote is overset.
Uwe
About: "INGOOGLE"
Martin's Name is: "Martin Fischer", not "Fisher" ;-)
Uwe
Hi Uwe,
Thanks for highlighting all this!
I've only fixed what I could—that is, Martin's name ;-)
@+
Marc
A little update on "CS6-CC Bug: Unreachable FormField Properties".
This bug is *now* fixed in the new SSB (Simple Save Back Service) of Adobe Creative Cloud for InDesign. That means Adobe Server behind SSB is fixed.
However, it is still *not* fixed in CS6 v8.1.0. itself.
Uwe
Just tried your progressbar code snippet since my own code for progressbars in CC doesn't seem to work anymore.
I noticed that yours isn't working either.
Do you have any idea on how to get the progressbars working in CC2015?
And thanks for this site, I have really learned a lot from it!
Hi Peter,
Thanks for your feedback.
What OS do you use?
My progress bar seems to work on some platforms while it fails on others :-/
Best,
Marc
Sorry, I should have said that I'm using OSX El Capitan (Version 10.11.2). And I'm having this problem with all version of Indesign CC.
For some reason it seems that In Design isn't updating the screen, so it never draws the progressbar. If I add an alert to the loop it updates the progressbar whenever the alert is displayed.
Thanks for helping out!