Wednesday, October 26, 2005
Page Numbers are Squirrelly
The name of a page is a string and might include the section prefix, so subtracting 1 from it might fail.
It depends, I think, on three things:
OK, so now let's try this script (with a new, single-page document open):
Another test:
But is it a number?
But it has a bug as big as your nose right there staring you in the face and you won't ever find it until you start working with multi-section documents or you take advantage of the section prefix in one of your documents.
It depends, I think, on three things:
- The state of the page numbering option in general preferences.
- The presence of a section prefix in the Section and Numbering Options panel.
- The state of the Include Prefix in Page Numbers option in the same panel.
app.generalPreferences.pageNumbering = [PageNumberingOptions.section,You can try both settings by changing the [0] to [1] at the end of the first statement (on the second line).
PageNumberingOptions.absolute][0];
alert(decodePageNumbering(app.generalPreferences.pageNumbering,true));
function decodePageNumbering(theCode,verbose) {
var theCodes = [[1096971116, ["absolute", "absolute numbering"]],
nbsp;[1935897710, ["section", "section numbering"]]];
var verbosePtr = 0;
if (verbose) { verbosePtr = 1 }
switch (theCode) {
case PageNumberingOptions.absolute :
return theCodes[0][1][verbosePtr];
case PageNumberingOptions.section :
return theCodes[1][1][verbosePtr];
default :
throw (String(theCode) + "not recognized")
}
}
OK, so now let's try this script (with a new, single-page document open):
app.generalPreferences.pageNumbering = [PageNumberingOptions.section,and you'll quickly find that toggling the state of the page numbering options (by changing that [0] to [1] again) has no effect at all on what the alert says. So much for my assertion 1 above. I think that what that option does is determine the form you must use when you supply a page number to certain methods, for example if you want to export a pdf or print a page range from your document. Otherwise, it has no effect on working with page numbers.
PageNumberingOptions.absolute][0];
var myPage = app.activeDocument.pages[0];
myPage.appliedSection.sectionPrefix = "Test-";
myPage.appliedSection.includeSectionPrefix = true;
alert(myPage.name);
Another test:
var myPage = app.activeDocument.pages[0];This gives you an alert that just says 1
myPage.appliedSection.sectionPrefix = "Test-";
myPage.appliedSection.includeSectionPrefix = false;
alert(myPage.name);
But is it a number?
var myPage = app.activeDocument.pages[0];No it isn't; it's a string. But what if we try to treat it as a number?
myPage.appliedSection.sectionPrefix = "Test-";
myPage.appliedSection.includeSectionPrefix = false;
alert(myPage.name.constructor.name);
var myPage = app.activeDocument.pages[0];Well, what do you know, it works! This is one of those things about JavaScript that on the one hand allows you to be lazy; after all, most people have a prefix of "" in most of their single-section documents because that's the default when a new document is created, and so most scripts that just treat the page name as a number just work. And that leads you down the path of thinking that the page name is a number -- but it isn't. It's a string, that most of the time contains the string representation of a number, so most of the time a script that thinks it's a number actually does work.
myPage.appliedSection.sectionPrefix = "Test-";
myPage.appliedSection.includeSectionPrefix = false;
alert(myPage.name - 1);
But it has a bug as big as your nose right there staring you in the face and you won't ever find it until you start working with multi-section documents or you take advantage of the section prefix in one of your documents.
Sunday, October 23, 2005
Vertical Justification Undo
Already, I need a second script that will walk through a column restoring the space before to that used by assigned paragraph styles. This is an easy fall-out from the previous script. Instead of the spreadText() function, I have an unSpreadText() function that looks like this:
function unSpreadText(theText) {Well, that was easy.
for (var j = theText.paragraphs.length - 1; j > 0; j--) {
theText.paragraphs[j].spaceBefore = theText.paragraphs[j].appliedParagraphStyle.spaceBefore;
}
}
Vertical Justification
You'd think this was easy -- just turn it on in text frame options. But I've run into a case where that doesn't work. My client wants two columns in a text frame vertically aligned with each other, but the second column contains an inline graphic which is overlaid by some text. The graphic extends beyond the bottom of the text frame -- well, it does if I switch on vertical justification via text frame options. So that doesn't do the job because what he really wants is for the text in the left column to align with the bottom of the graphic in the right column.
So, we need a script.
The concept of the script would be that the size of the text frame has been manually set. The bottom falls where it falls and the need to is fit the text so that it nestles to that bottom. But which text? If I do this with all the text in each column of the text frame, then it will have precisely the effect that switching on VJ has. So, the user must click in the text column of interest.
So, task number one is to determine which column of text contains the insertion point. Usually, when I write scripts that depend on a text insertion point, I don't really care if the user has made a larger selection -- in that case, I just take the first insertion point of the selection, but in this case, I'm going to be more fussy. The script will insist on a single insertion point and will decline to proceed for any other kind of selection.
So, to get started, let's deal with that and then call a function to tell us which column contains the insertion point (note: the errorExit() function is discussed here):
Interesting questions! Text on a path is clearly ineligible for this kind of processing so we must trap for that and issue an error message. Text in a cell could be eligible, but perhaps the user clicked in it by accident -- it is after all also in a text column -- the column that contains the table that contains the cell. So, for now, I'm also going to trap that possibility out of consideration (a decision aided by the fact that my immediate need doesn't involve tables).
Now the body of the script looks like this:
Now it's time to take a crack at the getColNum function. Here's an irony: I don't care which column it is. All I really need to do this job is a reference to the text in the column. So, let's change the name of the function and the call to it to:
Thinking about errors makes me wonder what will happen if the user tries to run this script on an empty (or nearly empty) text column. The logic of adding space before to all except the first paragraph isn't going to get very far if there's one or fewer paragraphs. We'll need to check for that right now:
That's serious enough to warrant trapping the error because otherwise we could leave the text frame with the wrong vertical alignment. So
Uh oh! A quick test reveals that attempting to set justifyAlign when it's ineligible doesn't cause a run-time error. The command is just ignored. That sounds like a bug to me. However, it does mean that the script will do nothing in that case. Perhaps it's worth adding a test to make sure that targetBase and firstBase are different from each other (indeed, that the former is larger than the latter) otherwise the user might not notice that nothing happened.
And that's basically it. From here on, everything is simple math and a loop:
So, we need a script.
The concept of the script would be that the size of the text frame has been manually set. The bottom falls where it falls and the need to is fit the text so that it nestles to that bottom. But which text? If I do this with all the text in each column of the text frame, then it will have precisely the effect that switching on VJ has. So, the user must click in the text column of interest.
So, task number one is to determine which column of text contains the insertion point. Usually, when I write scripts that depend on a text insertion point, I don't really care if the user has made a larger selection -- in that case, I just take the first insertion point of the selection, but in this case, I'm going to be more fussy. The script will insist on a single insertion point and will decline to proceed for any other kind of selection.
So, to get started, let's deal with that and then call a function to tell us which column contains the insertion point (note: the errorExit() function is discussed here):
//DESCRIPTION: Vertically "justify" indicated text column by adding space before.Well, it didn't take long for me to have second thoughts. Just writing this amount of code caused me to take pause and ask: What if the insertion point is in a cell of a table or in text on a path?
if ((app.documents.length != 0) && (app.selection.length != 0)) {
if (app.selection[0].constructor.name != "InsertionPoint") {
errorExit("Indicate column by clicking an insertion point with the text tool.");
}
var myCol = getColNum(app.selection[0]);
} else {
errorExit();
}
Interesting questions! Text on a path is clearly ineligible for this kind of processing so we must trap for that and issue an error message. Text in a cell could be eligible, but perhaps the user clicked in it by accident -- it is after all also in a text column -- the column that contains the table that contains the cell. So, for now, I'm also going to trap that possibility out of consideration (a decision aided by the fact that my immediate need doesn't involve tables).
Now the body of the script looks like this:
if ((app.documents.length != 0) && (app.selection.length != 0)) {You'll see that I quickly tired of typing "app.selection[0]" so I created a variable to hold a reference to the selection. Because the parent of a text object (insertion point is a kind of text object) is either "Cell" or "Story" differentiating text on a path from text in a text frame requires a different technique from determining if the text is in a cell in a table.
var mySel = app.selection[0];
if (mySel.constructor.name != "InsertionPoint") {
errorExit("Indicate column by clicking an insertion point with the text tool.");
}
// What if insertion point is in a cell or text on a path?
if (mySel.parent.constructor.name == "Cell") {
errorExit("Script does not operate on text inside tables.");
}
if (mySel.parentTextFrames[0].constructor.name == "TextPath") {
errorExit("Script does not operate on text on a path.");
}
var myCol = getColNum(mySel);
} else {
errorExit();
}
Now it's time to take a crack at the getColNum function. Here's an irony: I don't care which column it is. All I really need to do this job is a reference to the text in the column. So, let's change the name of the function and the call to it to:
var myText = getColTextRef(mySel);Here's the function:
function getColTextRef(myIP) {That final error ought to be impossible. The script will always find the column and exit accordingly. But because it is a conceptually possible logic path, I've put an error message in there to let me know if for some unforesee reason we do get there.
// returns reference to text of text column that holds insertion point
var myTF = myIP.parentTextFrames[0];
var myIndex = myIP.index;
var colIndexes = myTF.textColumns.everyItem().index;
for ( j= colIndexes.length - 1; j>= 0; j--) {
if (myIndex >= colIndexes[j]) {
return myTF.textColumns[j].texts[0];
}
}
errorExit("Something is ghastly wrong");
}
Thinking about errors makes me wonder what will happen if the user tries to run this script on an empty (or nearly empty) text column. The logic of adding space before to all except the first paragraph isn't going to get very far if there's one or fewer paragraphs. We'll need to check for that right now:
var myText = getColTextRef(mySel);So now we need the spreadText() function. All the while I've been writing about this script, I've been mulling over the best way to tackle the job of spreading vertically. The process has two parts:
if (myText.paragraphs.length > 1) {
spreadText(myText);
} else {
errorExit("Indicated column has fewer than two paragraphs.");
}
- Determine how much space needs to be consumed
- Apply to each paragraph in the text, except the first, its portion of that space as extra space before
function spreadText(theText) {Although this function, so far, does nothing more than collect information, we can use ESTK to test that it works. And indeed a quick test seems to confirm that it does work. I worry that the process of changing the vertical justification to collect information won't always work. For example, what if there is a text wrap interfering with the shape of the text frame? In that case, the attempt to set justifyAlign will probably fail with an error.
// Calculate spare space by comparing last baseline with VJ on and off
// VJ is not a property of text but its container. So:
var origVJ = theText.parentTextFrames[0].textFramePreferences.verticalJustification;
theText.parentTextFrames[0].textFramePreferences.verticalJustification = VerticalJustification.topAlign;
theText.recompose();
var firstBase = theText.lines[-1].baseline;
theText.parentTextFrames[0].textFramePreferences.verticalJustification = VerticalJustification.justifyAlign;
theText.recompose();
var targetBase = theText.lines[-1].baseline;
theText.parentTextFrames[0].textFramePreferences.verticalJustification = origVJ;
}
That's serious enough to warrant trapping the error because otherwise we could leave the text frame with the wrong vertical alignment. So
try {OK, I'm being a wimp and rolling over in the face of text wrap, but dealing with that is a very big deal and I certainly don't have the time right now.
theText.parentTextFrames[0].textFramePreferences.verticalJustification = VerticalJustification.justifyAlign;
} catch (e) {
// restore VJ
theText.parentTextFrames[0].textFramePreferences.verticalJustification = origVJ;
errorExit("Frame has text wrap and can't be processed.");
}
Uh oh! A quick test reveals that attempting to set justifyAlign when it's ineligible doesn't cause a run-time error. The command is just ignored. That sounds like a bug to me. However, it does mean that the script will do nothing in that case. Perhaps it's worth adding a test to make sure that targetBase and firstBase are different from each other (indeed, that the former is larger than the latter) otherwise the user might not notice that nothing happened.
And that's basically it. From here on, everything is simple math and a loop:
if (firstBase >= targetBase) {Notice that the first paragraph is ignored. That's because space before the first paragraph in a text column is ignored by the composer.
errorExit("Text Column apparently already spread—perhaps a text wrap is interfering.");
}
// Calculate extra space needed per paragraph
var myExtraSpace = (targetBase - firstBase)/(theText.paragraphs.length - 1);
for (var j = theText.paragraphs.length - 1; j > 0; j--) {
theText.paragraphs[j].spaceBefore = theText.paragraphs[j].spaceBefore + myExtraSpace;
}
Thursday, October 20, 2005
Text Styles Reporter
I've just posted my first "Donationware". It's a script that produces a sectonalized report of your text styles. First the paragraph styles and then the character styles. You get to choose which aspects of your styles are reported, and you can choose to have them listed alphabetically or in based-On order. The script also lets you choose a document preset for the report it generates.
The script works only with InDesign CS2. It comes in two parts, the script and a snippet, both of which must be stored together in the Scripts folder (or a subfolder thereof) in the Presets folder of your InDesign CS2 application folder. The dependency on a snippet and the differences between CS and CS2 styles means that this script works only with CS2.
If you feel moved to donate, open the script in a text editor or ESTK for instructions. Note that the script will not run to completion if you run it from ESTK. It needs to run from the palette so that the snippet can be located.
See the Featured Downloads at left to download a copy.
The script works only with InDesign CS2. It comes in two parts, the script and a snippet, both of which must be stored together in the Scripts folder (or a subfolder thereof) in the Presets folder of your InDesign CS2 application folder. The dependency on a snippet and the differences between CS and CS2 styles means that this script works only with CS2.
If you feel moved to donate, open the script in a text editor or ESTK for instructions. Note that the script will not run to completion if you run it from ESTK. It needs to run from the palette so that the snippet can be located.
See the Featured Downloads at left to download a copy.
Saturday, October 15, 2005
with and Preferences
All the time I've being using JS with ID, I've avoided the use of the with statement because various people advised me that it was dodgy. I've even seen it in print. In the JavaScript Pocket Reference published by O'Reilly, David Flanagan writes:
The with statement has some complex and non-intuitive side effects; its use is strongly discouraged.
So I've not been using it. But this morning it occurred to me that I could surely save myself a lot of typing in this function:
And while I'm sitting here pleased with myself, notice how I set the values of the two enumerations. Doing it this way saves me from having to look them up next time I want to use a variant of these preferences in a script.
-- Later that morning --
That's interesting. Using the preferences as I have them here caused the import I did next to fail with "User canceled this action." Perhaps I'd better set the errorCode to 0 after all.
Oh dear. That didn't help. I seem to have broken my ability to import Excel spreadsheets. And there I was just a few minutes ago feeling so pleased with myself. Let's see if I can still do it in the UI. ...
Yes I can.
Hmm. If I switch off the call to this function, it still works in my script, so setting this particular set of preferences causes the import to fail. Changing the preserveGraphics to true didn't help either (it seems to control the import of inline graphics -- I didn't know that Excel supported inline graphics).
Aha! The errorCode to the rescue. It tells me "Invalid Sheet" -- I bet Excel starts counting from 1 ... Yep! That was it.
I still don't understand why the errorCode is read/write, but I'm sure glad it is there.
-- Later still --
It is such an easy mistake to make and I seem to trap myself every time I do it. If, in the course of investigating a problem, I disable a call to a function, it is vital to re-enable the call before making changes in the function because otherwise your changes will go unnoticed.
Sad to say, this happened here. I still don't know what the ultimate issue is because the changes caused InDesign to start crashing rather frequently, so now I'm going through the process of restarting my computer and trying to get back to where I was so I can pin this down.
The with statement has some complex and non-intuitive side effects; its use is strongly discouraged.
So I've not been using it. But this morning it occurred to me that I could surely save myself a lot of typing in this function:
function setXLimportPrefs() {And it seems to work very well. Perhaps in simple cases like this it is fine to use with. I can see how it might get more hairy if I were to start nesting them.
with (app.excelImportPreferences) {
alignmentStyle = [AlignmentStyleOptions.spreadsheet,
AlignmentStyleOptions.leftAlign,
AlignmentStyleOptions.rightAlign,
AlignmentStyleOptions.centerAlign][0];
decimalPlaces = 3;
// Ignoring errorCode for now; I don't know what it's for
preserveGraphics = false; // Not sure what this does either
rangeName = ""; // Hopefully leaving this blank will cause ranges to be ignored
sheetIndex = 0;
sheetName = "";
showHiddenCells = true;
tableFormatting = [TableFormattingOptions.excelFormattedTable,
TableFormattingOptions.excelUnformattedTable,
TableFormattingOptions.excelUnformattedTabbedText][0];
useTypographersQuotes = true;
viewName = "";
}
}
And while I'm sitting here pleased with myself, notice how I set the values of the two enumerations. Doing it this way saves me from having to look them up next time I want to use a variant of these preferences in a script.
-- Later that morning --
That's interesting. Using the preferences as I have them here caused the import I did next to fail with "User canceled this action." Perhaps I'd better set the errorCode to 0 after all.
Oh dear. That didn't help. I seem to have broken my ability to import Excel spreadsheets. And there I was just a few minutes ago feeling so pleased with myself. Let's see if I can still do it in the UI. ...
Yes I can.
Hmm. If I switch off the call to this function, it still works in my script, so setting this particular set of preferences causes the import to fail. Changing the preserveGraphics to true didn't help either (it seems to control the import of inline graphics -- I didn't know that Excel supported inline graphics).
Aha! The errorCode to the rescue. It tells me "Invalid Sheet" -- I bet Excel starts counting from 1 ... Yep! That was it.
I still don't understand why the errorCode is read/write, but I'm sure glad it is there.
-- Later still --
It is such an easy mistake to make and I seem to trap myself every time I do it. If, in the course of investigating a problem, I disable a call to a function, it is vital to re-enable the call before making changes in the function because otherwise your changes will go unnoticed.
Sad to say, this happened here. I still don't know what the ultimate issue is because the changes caused InDesign to start crashing rather frequently, so now I'm going through the process of restarting my computer and trying to get back to where I was so I can pin this down.
Saturday, October 01, 2005
Regex Tester Revisited
It has come to me that for this script to be really useful, it needs to remember the previous version of your Regex find string and use it from run to run so you can refine the string until you get it the way you really want it.
So, where to keep it between runs? I'm thinking perhaps as a label to the document you're working on or perhaps the object containing the text you're working on.
Thinks: do stories support labels? Yes, but text in cells don't. On the other hand, text in cells have parent stories, so that's one possibility. While in CS2, cells support labels.
So many choices!
Perhaps to get started I should just use the document itself. After all, you're not likely to be working on more than one of these at any particular time, and when you've got it right you can call the script one last time and copy it out of the prompt to where you really need it (probably in some other script).
OK, but let's not screw up the possibility that you are already using the document label, so I'll use a keyed label. I'll give it the key "PDSregex".
Changing the script is pretty simple. Once it is determined that there is an active document, I can create a reference to it:
So, the first time you use the script with a particular document open, you'll get a blank field to start your Regex string, but after that, each time you run the script it will pick up the most recent version of your string and you can continue refining it until you get it right!
I've updated the linked script to include this logic.
So, where to keep it between runs? I'm thinking perhaps as a label to the document you're working on or perhaps the object containing the text you're working on.
Thinks: do stories support labels? Yes, but text in cells don't. On the other hand, text in cells have parent stories, so that's one possibility. While in CS2, cells support labels.
So many choices!
Perhaps to get started I should just use the document itself. After all, you're not likely to be working on more than one of these at any particular time, and when you've got it right you can call the script one last time and copy it out of the prompt to where you really need it (probably in some other script).
OK, but let's not screw up the possibility that you are already using the document label, so I'll use a keyed label. I'll give it the key "PDSregex".
Changing the script is pretty simple. Once it is determined that there is an active document, I can create a reference to it:
if ((app.documents.length != 0) && (app.selection.length != 0)) {And then, the part that issues the prompt needs a couple of extra lines:
var myDoc = app.activeDocument;
// Invite user to type regular expression
var curExp = myDoc.extractLabel("PDSregex");
app.activate();
var myREtext = prompt("Type your regular expresion",curExp);
if (myREtext == null) { errorExit() }
myDoc.insertLabel("PDSregex",myREtext);
So, the first time you use the script with a particular document open, you'll get a blank field to start your Regex string, but after that, each time you run the script it will pick up the most recent version of your string and you can continue refining it until you get it right!
I've updated the linked script to include this logic.
Regex Tester
It occurred to me that constructing a script that helps test Regex commands would be a good idea, so I came up initially with this script (which uses all three of the methods and functions in the previous blog entry):
First, the script focuses on the first matched substring of text. But what if you're not interested in substrings? Well, my "solution" to that is a bit clunky: surround your whole search string in parentheses.
Second, although the script looks as though it deals with non-text selections, it actually doesn't. The "else" statement is only triggered if there is either no document open or no selection.
Let's try to fix both of these by (a) being a bit smarter with the results of the exec() call and (b) by giving the script a better internal structure.
There, now that's what I call a useful script!
if ((app.documents.length != 0) && (app.selection.length != 0)) {To use this script, select some text in an InDesign document (or click in a story or table cell) and run the script. It prompts you for a Regex string and displays the result in an alert. However, even as I sit here describing this version, I see a couple of problems.
var myRange = app.selection[0];
if (myRange.isText()) {
// if selection is only an insertion point, process parent text range
if (myRange.constructor.name == "InsertionPoint") {
myRange = getParentTextFlow(myRange);
}
// Invite user to type regular expression
app.activate();
var myREtext = prompt("Type your regular expresion","");
if (myREtext == null) { errorExit() }
myRE = new RegExp(myREtext);
var myTest = myRE.exec(myRange.contents);
app.activate();
alert(myRE + " finds:\n" + myTest[1]);
}
} else {
errorExit("Please select some text and try again");
}
First, the script focuses on the first matched substring of text. But what if you're not interested in substrings? Well, my "solution" to that is a bit clunky: surround your whole search string in parentheses.
Second, although the script looks as though it deals with non-text selections, it actually doesn't. The "else" statement is only triggered if there is either no document open or no selection.
Let's try to fix both of these by (a) being a bit smarter with the results of the exec() call and (b) by giving the script a better internal structure.
var myErr = "Please select some text and try again";
if ((app.documents.length != 0) && (app.selection.length != 0)) {
var myRange = app.selection[0];
if (!myRange.isText()) {
errorExit(myErr);
} else {
// if selection is only an insertion point, process parent text range
if (myRange.constructor.name == "InsertionPoint") {
myRange = getParentTextFlow(myRange);
}
// Invite user to type regular expression
app.activate();
var myREtext = prompt("Type your regular expresion","");
if (myREtext == null) { errorExit() }
myRE = new RegExp(myREtext);
var myTest = myRE.exec(myRange.contents);
var myReport = myRE + " finds:";
if (myTest == null) {
myReport = myReport + " no match";
} else {
myReport = myReport + "\n" + myTest[0];
for (var j = 1; myTest.length > j; j++) {
myReport = myReport + "\n$" + String(j) + " = " + myTest[j];
}
}
app.activate();
alert(myReport);
}
} else {
errorExit(myErr);
}
There, now that's what I call a useful script!
Standard Methods and Functions
I'm becoming concerned that my script postings here are become unwieldy because of the length of the standard methods and functions I use over and over. By way of an experiment, I'm going to post some of the more common ones here so I won't have to repeat them in posts about specific scripts:
isText() Method
This method returns true if an object is any kind of text object and false otherwise. Constructing it this ways allows it to be called like this:if (myObj.isText()) { // myObj is text so continueHere's the definition of the method. I always put my method definitions at the top of my scripts, immediately after the description.
Object.prototype.isText = function() {
switch(this.constructor.name){
case "InsertionPoint":
case "Character":
case "Word":
case "TextStyleRange":
case "Line":
case "Paragraph":
case "TextColumn":
case "Text":
case "TextFrame":
return true;
default :
return false;
}
}
errorExit(myMsg) function
This function throws up an alert if an argument is present, otherwise it just exits. For CS2 or later, it issues a beep immediately before displaying the alert.function errorExit(message) {I always put my functions after the main body of the script, usually preceded by a comment line that indicates the start.
if (arguments.length > 0) {
if (app.version != 3) { beep() } // CS2 includes beep() function.
alert(message);
}
exit(); // CS exits with a beep; CS2 exits silently.
}
getParentTextFlow(theTextRef) Function
This function returns the parent text flow of the referenced text item. Notice that the function does no error checking; if you pass it something that doesn't have a parentStory property you'll get an error. The script checks to see if the referenced text is in a cell in a table, and if so it returns a reference to the text of the cell rather than the parentStory which in this case is the parentStory of the table.function getParentTextFlow(theTextRef) {That's it for now. Last updated 10/1/05.
// Returns reference to parent story or text of cell, as appropriate
if (theTextRef.parent.constructor.name == "Cell") {
return theTextRef.parent.texts[0];
} else {
return theTextRef.parentStory;
}
}
Heavy Week
This week has been so intense with production work that I hardly had a moment for scripting let alone posting about scripting here. However, come the weekend, and while I'm still as busy as ever, I feel it's time to reawaken my blogging instincts or the blog will simply die.
So, it happens that I've started the day by posting a useful script on the InDesign Scripting forum at the Adobe U2U forums, so I'll pick it apart some here and improve it.
So, it happens that I've started the day by posting a useful script on the InDesign Scripting forum at the Adobe U2U forums, so I'll pick it apart some here and improve it.