Saturday, April 29, 2006
Add Object to Group
Here's a first cut at a method to add an object to a group. I find myself wanting to do this kind of thing when I'm using InDesign as an illustration tool. There's a lot more could be done by the method (for example: add at front or back, or even add at a designated point -- in front of/behind a member of the group; even in place of), but this first cut demonstrates feasibility:
1. Trying to ungroup a group that is inside another group or is an anchored object.
2. Trying to add a text object (other than a text frame) to the group.
Probably, in the case of 2, I should first reassemble the original group before throwing the error. But for the moment I'm just playing with an idea.
//DESCRIPTION: Test of method that adds an item to an existing groupThe two error conditions could be caused by:
Group.prototype.addObject = function(theObj) {
var myArray = this.pageItems.everyItem().id;
myArray.push(theObj.id);
var myParent = this.parent;
try { this.ungroup() } catch (e) { throw "Unable to ungroup" }
var gpArray = new Array();
for (var j = 0; myArray.length > j; j++) {
gpArray.push(myParent.pageItems.itemByID(myArray[j]));
}
try { myParent.groups.add(gpArray) } catch (e) { throw "Unable to regroup" }
}
myGroup = app.activeDocument.pages[0].groups[0];
myTF = app.activeDocument.pages[0].textFrames[0];
myGroup.addObject(myTF);
1. Trying to ungroup a group that is inside another group or is an anchored object.
2. Trying to add a text object (other than a text frame) to the group.
Probably, in the case of 2, I should first reassemble the original group before throwing the error. But for the moment I'm just playing with an idea.
Sunday, April 23, 2006
Running an inline script
What follows is an extract from an article I'm writing called How to Pass Information from One Script to Another:
You can if wish use doScript to run a script contained in a variable. For example:
Notice that the error-free versions I dubbed “equivalent” whereas the versions with the error are only “comparable.” The difference is that in the error situations, the call to the alert will immediately give you an error message that hello is not defined whereas the doScript version first gives you that error but then gives you an alert that says “undefined”.
The reason for this is that the call to doScript creates a new script environment to run the script contained in myScript—you can see this very clearly if you run the script in ESTK—and that script is the one that hits the error condition and throws up the error alert. When you dismiss that error, the first script picks up again and completes the original alert command. Because the called script failed, the result is undefined and so that’s what the second alert says.
But enough of trivial examples. If all you could do with this were trivial there’d be no point in having the feature. The power of this kind of doScript is that you can construct scripts from data and run them easily. This is not something that comes up all that often, and I do not propose to construct a fabricated example at this point because it too would likely be trivial.
One not so trivial use for this feature is to provide a JavaScript with a mechanism for contacting the outside world via AppleScript or Visual Basic (depending on the platform in use).
Here’s an example of constructing a script to get the name of the current track being played by iTunes on your Macintosh computer:
You can if wish use doScript to run a script contained in a variable. For example:
myScript = "'hello'"This trivial example is equivalent to:
alert(app.doScript(myScript));
alert(’hello’)and so there doesn’t seem to be much point to the exercise. However, notice the two sets of quotes in the assignment to myScript. With just one set of quotes:
myScript = "hello"The script is comparable to:
alert(app.doScript(myScript));
alert(hello);which results in a run-time error to the effect that hello is undefined.
Notice that the error-free versions I dubbed “equivalent” whereas the versions with the error are only “comparable.” The difference is that in the error situations, the call to the alert will immediately give you an error message that hello is not defined whereas the doScript version first gives you that error but then gives you an alert that says “undefined”.
The reason for this is that the call to doScript creates a new script environment to run the script contained in myScript—you can see this very clearly if you run the script in ESTK—and that script is the one that hits the error condition and throws up the error alert. When you dismiss that error, the first script picks up again and completes the original alert command. Because the called script failed, the result is undefined and so that’s what the second alert says.
But enough of trivial examples. If all you could do with this were trivial there’d be no point in having the feature. The power of this kind of doScript is that you can construct scripts from data and run them easily. This is not something that comes up all that often, and I do not propose to construct a fabricated example at this point because it too would likely be trivial.
One not so trivial use for this feature is to provide a JavaScript with a mechanism for contacting the outside world via AppleScript or Visual Basic (depending on the platform in use).
Here’s an example of constructing a script to get the name of the current track being played by iTunes on your Macintosh computer:
alert(app.doScript(getCurrentSong(),ScriptLanguage.applescriptLanguage));Of course, it goes without saying that this AppleScript could as easily have been in an external file and have been called from there. But non-trivial examples of constructing scripts inside scripts are hard to come by.
function getCurrentSong() {
var myScript = "tell application \"iTunes\"\n"
var myScript = myScript + "get name of current track\n"
var myScript = myScript + "end tell\n"
return myScript
}
Wednesday, April 12, 2006
Can a non-existent file have a parent?
I was suddenly unsure about this, so I ran a quick test:
The explanation is fairly simple. Whether or not the file exists, the file object does exist. And, in order to exist, it must have a parent, the folder in which the corresponding file will be created should the script go the next step and create the file.
//DESCRIPTION: Can a non-existent file have a parent?And the answer is yes!
defFile = File(getScriptPath().parent.fsName + "/test.txt");
parFile = defFile.parent;
alert(parFile.fsName);
function getScriptPath() {
// This function returns the path to the active script, even when running ESTK
try {
return app.activeScript;
} catch(e) {
return File(e.fileName);
}
}
The explanation is fairly simple. Whether or not the file exists, the file object does exist. And, in order to exist, it must have a parent, the folder in which the corresponding file will be created should the script go the next step and create the file.
Monday, April 10, 2006
Swatches vs. Colors as properties
In a large number of places in the Scripting Reference, there are color based properties whose Type field reads:
This is correct for setting these color properties (as long as you realize that the string in question should be the name of a swatch of the active document).
But, if you interrogate any of these properties:
Swatch, Stringwith a description that reads: "The color of the <whatever>".
This is correct for setting these color properties (as long as you realize that the string in question should be the name of a swatch of the active document).
But, if you interrogate any of these properties:
myColor = myWord.underlineGapColor;you'll get a color object, and not a swatch. To detect whether or not the color object returned is represented in the UI by a swatch, check its name. If the name is "" then the color was explicitly applied to the object in question without using a swatch (I don't think that is possible for the particular example chosen above, but it is certainly possible for such properties as strokeColor and fillColor).
Sunday, April 09, 2006
How to check for missing fonts
I've been commissioned to write a script that helps with the back-end of a workflow, exporting/printing completed documents. Or, to be a little pedantic about it, allegedly completed documents. But what if a document has missing fonts or links? Or if links need updating?
Of these three possibilities, only the third can be dealt with automatically. And finding that links are missing is easy (just check the status property of each link). That will allow the script to check for either links that need relinking or updating, taking appropriate action for each. But what about missing fonts?
Well, the first thing I realized was that if fonts are missing, all I need to do is report that to the user and advise them to use the menu item Find Font... on the Type menu to fix it. So, I need a quick way to find out if any font needed by the document is missing. I ran a timing loop against each of the properties of a font, like this:
718 allowEditableEmbedding
922 allowOutlines
856 allowPDFEmbedding
894 allowPrinting
357 fontFamily (doesn't give error)
754 fontType
915 index (doesn't give error)
857 location
460 name (doesn't give error)
371 parent (doesn't give error)
930 postscriptName
4885 properties (doesn't give error)
911 restrictedPrinting
911 status (doesn't give error)
But if all I want to know is whether or not any fonts are missing, I can simply do this:
Of these three possibilities, only the third can be dealt with automatically. And finding that links are missing is easy (just check the status property of each link). That will allow the script to check for either links that need relinking or updating, taking appropriate action for each. But what about missing fonts?
Well, the first thing I realized was that if fonts are missing, all I need to do is report that to the user and advise them to use the menu item Find Font... on the Type menu to fix it. So, I need a quick way to find out if any font needed by the document is missing. I ran a timing loop against each of the properties of a font, like this:
//DESCRIPTION: Checking for missing document fontsThis was the last test I ran, so it shows the alphabetically last propery, status. The results are shown below (the numbers show how many milliseconds it took to attempt to get the properties of a document that had two missing fonts). Notice that some of the properties are available even for missing fonts. If I wanted to warn the user about specific missing fonts, I'd have to use the status property, and then cycle through the results.
var myStartTime = new Date();
aDoc = app.activeDocument;
for (var j = 0; 100 > j; j++) {
try {
aFonts = aDoc.fonts.everyItem().status;
} catch (e) {
beep();
}
}
var myEndTime = new Date();
var myDuration = (myEndTime - myStartTime);
prompt("", "status " + myDuration)
718 allowEditableEmbedding
922 allowOutlines
856 allowPDFEmbedding
894 allowPrinting
357 fontFamily (doesn't give error)
754 fontType
915 index (doesn't give error)
857 location
460 name (doesn't give error)
371 parent (doesn't give error)
930 postscriptName
4885 properties (doesn't give error)
911 restrictedPrinting
911 status (doesn't give error)
But if all I want to know is whether or not any fonts are missing, I can simply do this:
try {Why do I choose the location property? Because that's the first one I thought of user and the timing test says that the results are just about all the same (the minor differences could easily be the result of blips while I was running the script; indeed, I'm showing the lowest result of three or four runs for most of the properties).
fontLocs = app.activeDocument.fonts.everyItem().location;
} catch (e) {
errorExit("Active document has missing fonts; please correct using Type/Find Font and try again.");
}
Friday, April 07, 2006
A better Log function
Yesterday, I posted a function for creating a log file. In testing that function, I used another function called log to write messages to the log file. This morning, I've created a slightly more flexible version of that function. In this version, the date-stamping is optional and the header message is passed as a parameter to the function.
This log function writes messages to the designated text file (File object: aFile). If the file in question does not yet exist, it is created and if requested by the header (string) and incDate (boolean) arguments, the log file is date-stamped and a header is posted.
Once log has been called the first time to kick things off, the third and fourth arguments can be ignored; indeed, they are ignored by the function, so there's no point in including them. If you want to time-stamp the messages, you can include the time string in the message parameter.
At the end of a script that created a log file, I have something like this:
This log function writes messages to the designated text file (File object: aFile). If the file in question does not yet exist, it is created and if requested by the header (string) and incDate (boolean) arguments, the log file is date-stamped and a header is posted.
function log(aFile, message, header, incDate) {Note that if the first call includes neither a header nor a request to stamp the file with the date, the generated text file will start with a blank line. One approach for dealing with this would be to not include the "\n" before each message, leaving it to the user to include them. I'll see how I get on with it in its current form. So far, I've always wanted a header and date-stamp, so it has been something of a non-issue.
var today = new Date();
if (!aFile.exists) {
// make new log file
aFile.open("w");
if (incDate) {
aFile.write(String(today));
}
if (header != null) {
aFile.write(header);
}
aFile.close();
}
aFile.open("e");
aFile.seek(0,2);
aFile.write("\n" + message);
aFile.close();
}
Once log has been called the first time to kick things off, the third and fourth arguments can be ignored; indeed, they are ignored by the function, so there's no point in including them. If you want to time-stamp the messages, you can include the time string in the message parameter.
At the end of a script that created a log file, I have something like this:
if (logFile.exists) {This causes the log file to be displayed in the editor of your choice (that which you have associated with .txt files) if it was created by running the script, or you get the alert message (provided by the errorExit() function I've posted previously).
logFile.execute();
} else {
errorExit("Script ended with no log file.");
}
Thursday, April 06, 2006
Logging to a text file
Many scripts encounter errors or unusual circumstances as they run. Sometimes, it is desirable for the script to simply stop in such circumstances reporting the error to the user for immediate attention. But in other situations it is better for a script to soldier on, simply logging its findings to a text file.
The function makeLogFile() facilitates this. It creates a text file (which, in truth could be used for anything) using the string aName as the basis for naming the file. The file is located in the same folder as the document passed in the argument aDoc. If aDoc is untitled, then the log file is created in the same folder as the script. The third argument (delete) is a Boolean. It determines whether the script should delete an existing file of the designated name or create a unique name based on the first argument.
The function makeLogFile() facilitates this. It creates a text file (which, in truth could be used for anything) using the string aName as the basis for naming the file. The file is located in the same folder as the document passed in the argument aDoc. If aDoc is untitled, then the log file is created in the same folder as the script. The third argument (delete) is a Boolean. It determines whether the script should delete an existing file of the designated name or create a unique name based on the first argument.
function makeLogFile(aName, aDoc, deleteIt) {Notice that this script depends on the function getScriptPath being available. It also doesn't actually create the log file, merely a file object. This means that a script which never actually logs anything won't create a log file. Here is the code I used to test this function:
var logLoc; // path to folder that will hold log file
try {
logLoc = aDoc.filePath;
} catch (e) {
logLoc = getScriptPath().parent.fsName
}
var aFile = File(logLoc + "/" + aName + ".txt");
if (deleteIt) {
aFile.remove();
return aFile;
}
var n = 1;
while (aFile.exists) {
aFile = File(logLoc + "/" + aName + String(n) + ".txt");
n++
}
return aFile
}
//DESCRIPTION: Test making log fileRun this and two text files are opened, one named test.txt and one named test1.txt. That's because the first call has delete it set to true while the second has it set false. As a result, because we logged something to the log1 file before creating the log2 file object, two separate files were created.
myDoc = app.activeDocument;
log1 = makeLogFile("test",myDoc,true);
log(log1, "Text for log1 file");
log2 = makeLogFile("test",myDoc,false);
log(log2, "Text for log2 file");
log1.execute();
log2.execute();
function makeLogFile(aName, aDoc, deleteIt) {
var logLoc; // path to folder that will hold log file
try {
logLoc = aDoc.filePath;
} catch (e) {
logLoc = getScriptPath().parent.fsName
}
var aFile = File(logLoc + "/" + aName + ".txt");
if (deleteIt) {
aFile.remove();
return aFile;
}
var n = 1;
while (aFile.exists) {
aFile = File(logLoc + "/" + aName + String(n) + ".txt");
n++
}
return aFile
}
function getScriptPath() {
// This function returns the path to the active script, even when running ESTK
try {
return app.activeScript;
} catch(e) {
return File(e.fileName);
}
}
function log(aFile, message) {
var today = new Date();
if (!aFile.exists) {
// make new log file
aFile.open("w");
aFile.write(String(today) + "\nThe following messages were logged:\n");
aFile.close();
}
aFile.open("e");
aFile.seek(0,2);
aFile.write("\n" + message);
aFile.close();
}