Sunday, March 22, 2009

May 11-15 InDesign Developer Training Sessions

If you’re involved in automation around the Adobe Creative Suite, you need to mark the week of May 11 - 15, 2009 on your calendar - there’s another Creative Suite Developer Summit coming up in Seattle, on the Adobe Fremont Campus

We’re running two training sessions this year - click the lines below for more info:

On May 11: InDesign Plugin Development Workshop - Getting Started With The InDesign SDK

On May 15: Feature Development for InDesign Using ExtendScript

Both workshops are limited to 12 seats, which will be allocated on a first come, first served basis - so don’t delay booking.

More about the Adobe Creative Suite Developer Conference can be found here:


http://niemannross.host.adobe.com/2009csbuDeveloperSummit/

As space is limited to 12 attendees per course, you should make sure you enroll early. There's also a 10% price reduction for people who enroll in both courses before April, 15.

Cheers,


Kris

Thursday, October 16, 2008

New Cookbook Blog

I've started a new blog which will be mainly like a 'cookbook' for Adobe Creative Suite scripters - how-to articles that show how to write much more powerful scripts.

http://www.rorohiko.com/wordpress

The first entry in the blog is up - it's about how you can 'attach' little icons or labels to the four sides of page items as a means of feedback to the user of your scripts. The sample script adds a little floating label with the word count to each text frame - so the user can see the amount of words in each text frame in the blink of an eye.

Check it out!

Cheers,

Kris

Saturday, July 12, 2008

Lightning Brain Podcast: Click here to listen to "Using State Machines: Web Access From Adobe InDesign CS3 ExtendScript"

Sample files for this podcast can be downloaded from:

http://www.rorohiko.com/podcast/geturl.zip

This podcast will explain how you can query web services from within InDesign CS3 ExtendScript. No need for plug-ins, external libraries - just Adobe InDesign ExtendScript, pure and simple.

I'll also present some useful routines I wrote, called GetURL() and ParseURL(). GetURL() is a fairly large routine which demonstrates how you can use a programming pattern called 'a state machine' to process data on-the-fly as it is received from a network connection.

To demonstrate how to use the GetURL() function, I've also added a useful sample script. The script will search your active InDesign document for any page items that have a URL as their script label (entered via Window - Automation - Script Label). It will then fetch the data 'behind' the URL and place that data into the page item.

Install the script GetURLs.jsx in the InDesign scripts folder (the easiest is to bring up the scripts palette from InDesign: select Window - Automation - Scripts, and then right-click the User folder).

Select Reveal in Finder or Reveal in Explorer. Then copy the script into the Scripts Panel folder you should now see appear.

Switch back to InDesign, and open up the sample document GetURLSample.indd. Run the script GetURLs.jsx from the palette by double-clicking it on the palette. The empty frames should fill up with images or text (at least, if you are connected to the Internet).

So, how does it all work?

At the heart of it all is the standard ExtendScript object called Socket. More info about the Socket can be found in the JavaScript Tools Guide for CS3:

http://www.adobe.com/devnet/bridge/pdfs/javascript_tools_guide_cs3.pdf

The socket object gives us the ability to perform low-level network communications - we can set up TCP/IP connections with other computers on the network.

The problem is that the Socket object has no higher-level functionality - it has no support for any protocols, like HTTP for example.

To fix that, you could try and use the protocol support that is available via Bridge with the HttpConnection object. You could also forcibly give InDesign access to the webaccesslib that is used by Bridge, through some 'fiddling around'.

However, personally I am not too keen on either of these approaches - they're either a bit too big or too brittle to my liking; I wanted to have an 'InDesign all by itself' solution.

The alternative approach I used was to provide HTTP support in pure ExtendScript to InDesign.

Now, before diving into this: be warned, this is NOT a fully fledged, fully compliant HTTP client. I've only implemented a subset of the protocol, just enough to let me do what I needed to do.

For example, the code only supports UTF-8 text encoding. If your target web server does not offer that you'll have to add some additional code to the scripts to cope with that. Also, I've only implemented HTTP 'GET' requests, not 'POST'. Adding that functionality would be fairly easy to do - it's left as an exercise.

So, the Socket object allows us to send out requests and receive replies via TCP/IP.

The HTTP protocol is quite extensive, but the basis of it is simple - it is mainly a plain text-based, ping-pong protocol. You send out a request, and you get a reply.

The (incoming) reply is composed of three parts: a start (or status) line, zero or more header lines, and then an optional body (which can be binary data or text).

The (outgoing) request is similarly composed of a request line, and zero or more header lines, and an optional body (for POST requests, which I am not implementing here).

Immediately after the start line, you get zero or more header lines.

Header lines are themselves separated from the following body of the request or reply by an empty line - so a request or reply always has the same 'rhythm' to it: start line, header lines, empty line, body.

The GetURL() script code in the GetURLs.jsx implements this in a rudimentary fashion: the request is a simple multi-line text string, which is fired off to the web server via a Socket object.

Then the bulk of the code is for interpreting the reply from the web server - there are three levels of decoding that need to happen.

First of all, we need to decode the reply itself - separate the start/status line from the headers and from the body.

The headers also will contain some important information: the length of the body that will follow. So we need to interpret that header line in order to know exactly how many bytes to read from the socket.

At a lower level, we need to 'chop' the reply up into individual lines until we reach the body - the status line and the individual lines are separated by CRLF character pairs (CR = ASCII character 13, LF = ASCII character 10).

And finally, at the lowest level, while reading a text-based body, we need to interpret UTF-8 code and convert the UTF-8 codes into plain Unicode. This means that we need to read through codes that are 1, 2, 3 or 4 bytes long, and each of them encodes a single Unicode character.

In the GetURL() routine, these three levels of encoding are decoded concurrently, through three nested 'state machines'. Using state machines makes the code fairly fast - much faster than could ever be achieved through string matching.

I won't go into the intricate details of how GetURL() works - the script is fairly well documented and you should be able to figure out how it works by careful reading and stepping through it with the ExtendScript debugger.

Instead, I want to explain a little bit more about state machines - they are a very powerful technique for fast pattern matching and parsing, and once you 'get' them, they are easy to use. They are used in mechanisms like GREP, in compilers and interpreters, in all kinds of text parsers,...

All too often I see code that uses straight string functions to achieve some matching goals.

A simple example: you get some data thrown at you which has line endings that might be either CR (ASCII 13), LF (ASCII 10) or one of each (CRLF).

Many people will handle that by reading in all the data into a buffer, then do some pattern search-and-replace. For example,

first replace all CRLF with CR
then replace all LF with CR

After that, the line ending has become a CR throughout the text.

This approach is not necessarily the best. Especially if you receive the data character by character, you could do the clean-up on-the-fly, as you receive the data. No need to buffer or no global search-and-replaces - so it might greatly reduce the amount of memory you need and also run a lot faster to boot.

With a state machine, you would go about it as follows. First of all, you create a variable (say, myState), and you create some symbolic numerical constants (for example, kNormalState could be a symbolic name for 0, and kSeenCR could be a symbolic name for 1).

For more complex state machines there might be hundreds, even thousands of different states - but in this case, two states will do.

All we'll now do is play with a simple integer variable, and we'll keep track of where we're at by manipulating the state. The idea is that we don't assemble strings or 'memorize' any other input data - we encode the relevant info about 'what has been' into the state variable.

Data flows through our state machine - we read input data, and immediately get rid of the data - we write or store or process it - and we keep as little data as possible inside our state machine logic.

So, our little state machine is happily reading and writing character after character.

After each character we read and process we also check whether it was a CR (ASCII 13) or not, and we change our state to either kNormalState or kSeenCR.

Now, suppose we now read a line feed character (ASCII 10). Before doing anything with a new character, the state machine will always check its current state first.

If the state is kSeenCR we know that this is a line feed after a preceding CR, so we simply don't write the LF out.

If we read a LF and the state is kNormalState, we know that this is a 'stand alone' LF without preceding CR - so we output a CR character to replace it instead.

The state machine is just simple enough to express in words:

Initialize myState to kNormalState
read character (loop until end of file)

if character is LF then
if myState is NOT kSeenCR then
output CR
end if
else
output character
end if

if character is CR then
myState becomes kSeenCR
else
myState becomes kNormalState
end if

end loop

This might seem overkill, but the advantages of state machines become apparent when you try more complex things - for example, interpreting a quote JavaScript string. That string might contain escape-sequences (backslash, followed by a letter, or 1-3 octal digits). Properly interpreting such an 'escaped' string is hard work without a state machine. With a state machine it's a breeze, with hardly any overhead.

So, that was a quick introduction to state machines - I hope it was enough to pique your interest, and entice you to do a bit of research; once you've added them to your arsenal of techniques, you'll find that some difficult tasks have become a lot easier.

You can download the sample files from the following URL (which is mentioned in the podcast transcript):

http://www.rorohiko.com/podcast/geturl.zip

Sunday, March 30, 2008

Lightning Brain Podcast: Click here to listen to "How To Get Paid For Writing ExtendScripts For Adobe® InDesign®"

In this podcast, I'll be highlighting a single feature of Rorohiko's Active Page Items family of tools that can help you get paid for ExtendScripts you write for Adobe InDesign CS, CS2 or CS3.

To demonstrate how easy it is, I've first created an ExtendScript called FlontFipper. The script itself, and its name, are a bit tongue-in-cheek, but actually might be useful, who knows?

What the script does is look at the currently active document and determine which two fonts are used most often in the document. It then swaps these fonts.

For example, if your document is quite simple, and has headlines in Helvetica and body text in Times Roman, this script would make the headlines Times Roman, and the body text would become Helvetica.

To use the script, the end-user would install the script into the appropriate folder for InDesign scripts. Then he would open a document, and double-click the script name in the InDesign Scripts panel.

Now assume I would like to sell this script to InDesign users.

First of all, I need to download a copy of the APIDToolkit (Active Page Items Developer Toolkit). A functional, time-limited demo version of APIDToolkit is available from the Rorohiko web site.

This toolkit contains lots of stuff, but the single item I am interested in for this podcast is called the InDesignScriptCompiler. Mac and PC versions are provided in the downloadable archive file.

Second, my potential customer needs to install a special runtime plug-in (the APIDKernel) which will enforce the demo- and licensing restrictions I want to impose on the use of my FlontFipper script.

To protect my FlontFipper.jsx script, I simply drag/drop it onto the InDesignScriptCompiler icon, and fill in some parameters - mainly to tell APID what the limitations are for the demo mode and via which URL a license can be purchased from me.

The result is an encrypted and protected version of my script - FlontFipper.compiled.js - which I can then send to my prospective customer, the end user.

My end user then installs FlontFipper.compiled.js just like any normal script, and uses it just like he would use the non-encrypted original. Note that this is also supported on InDesign CS and CS2.

The main difference with an uncompiled script is that each time InDesign is restarted, the first time FlontFipper is used, a 'beg' dialog will appear, which will inform the end-user of the time-limited nature of the FlontFipper demo. There are also two buttons on the dialog: Get License... and Import License File....

If the end-user clicks Get License... his system's web browser will be directed to display the embedded URL, and it will pass through all the data I would need to create a personalized license file for my end-user.

I then proceed through some monetary transaction to receive the payment from the end-user.

How this transaction is conducted is up to the script developer - for example, I could set up a fully automatic web-based pay-and-license system, or I could simply use a manual system based on e-mail.

APID does not dictate how this transaction should be implemented - this part of the system is left open, and for the script developer to fill in.

Once I receive payment from the end-user I will then use the data I received via the URL to generate a license file, which I then e-mail to the end-user.

The end-user uses the license file to convert the time-limited demo of my script into a fully functional version.

The next time the beg dialog appears, he clicks Import License File... and imports the license file I sent. From then on FlontFipper.compiled.js will not show any more beg dialogs, and also will not lapse.

If you want to try these things out: go to

www.rorohiko.com/flontfipper.html

(all lowercase): you can see the transcript of this podcast with additional screenshots, and you can download the source code, the compiled script, and a license file to test things out.

I'll now dive a bit more into the details of the compilation. After I drag-drop my script onto the script compiler, I get to see a main dialog named APID Standalone Script Compiler.

Here I must define how the compiled script will behave. The most important field is a 'Component ID' string. It has quite a few subfields separated by semicolons and commas and is most easily edited by clicking the 'Edit...' button next to it.

Clicking the Edit... button shows the 'Component ID' dialog. In the 'Component ID' Dialog, I need to define a number of parameters

- a 'component name' which I set to be FlontFipper - this is the name that will appear in various dialogs
- a 'compilation password' which will be used in the encryption of the script - in this sample, I made te password guessWhat
- a copyright string

These first three strings are mainly about identifying your compiled script.

In this same dialog there are also a number of fields to restrict the use of your compiled script - there is a minimum APID version (which I set to 1.0.44 or 1.044 - the current version at the time of this podcast - the InDesignScriptCompiler will drop the leading zeroes automatically and display 1.44).

There is also a hard cut-off date for demo versions. Because I don't want to use such a date in this demo, I've set the three date-subfields (year, month, day) to zero.

The most interesting fields for me are the number of 'actual use' days and number of demo days.

#Demo days represents a number of calendar days since the first use of your script by the end user; 30 days here would mean 'about a month'.

#Actual Use Days represent days of actual use; these days are not necessarily consecutive. This is provided to cater for situations with 'infrequent use' - people that only test the script every so often, and might leave long gaps between uses; you could leave #Demo days set to -1, and #Actual Use Days for example set to 5 and the user would be able to try things out for 5 non-consecutive days.

You can use either, or both fields to limit how long a demo version of your script should be usable.

In this sample, I've set the actual use days to 20, and the calendar days to 30 - the demo will time out after 20 days of actual use, or 30 calendar days, whichever comes first.

There is also a checkbox Free which I'd only use if I don't want to sell my script - for example if I just want to give encrypted versions away without divulging my source code. This checkbox is deselected in my example - I want to sell my script.

On the main screen, I must also enter some messages and a License URL.

The URL is set to link to a web page on my web server, and it passes 4 parameters as part of the URL: the serial number of InDesign, the name of the script (as defined in the Component ID dialog), the system identifier (a unique identifier for the computer requesting a license), and a license level (which will be a letter 'D' or 'R' depending on whether the end-user already has purchased a proper license for APIDKernel or not) - the strings ^1, ^2, ^3, and ^4 are placeholders that are replaced by real data when the URL is needed.

There are also two messages that can appear in the beg dialog. In these messages, ^1 is a placeholder for the compiled script name (as defined in the component ID earlier on), and ^2 is a placeholder for the remaining number of demo days.

Once I click Compile for APIDKernel two things will happen: a compiled version of my script will be created (FlontFipper.compiled.js), and my original source code will also be prefixed with a few comment lines that store my compilation parameters - this to avoid having to re-enter the same stuff next time around.

When a user installs FlontFipper.compiled.js in one of the proper script locations for Adobe InDesign, the script will behave mostly like any other script, except for the beg dialog. Clicking Get License... on the beg dialog makes the end-users' system browser connect to the URL embedded into the compiled script.

Once I receive payment from the end-user, I use a command-line tool that is part of the APIDToolkit to generate a license file for him. Mac and Windows versions of this tool are provided. If so desired, this command-line tool can be embedded into a web server setup - this is how Rorohiko does automated software sales, for example of our Sudoku Generator.

Note that the command line tool is not provided with the demo version of APIDToolkit - you need to purchase the APIDToolkit to get access to this generator - a license for the APIDToolkit costs US$149.00.

For simple set-ups you can also create 'generic' license files which are not linked to any particular system ID or InDesign serial number. The demo license file for this podcast is such a generic license file. It will enable FlontFipper.compiled.js on any copy of InDesign.

Costwise, the APIDKernel runtime for your end-user is not free - there is a one-time cost of US$25 or less per seat.

Note: there are multi-seat APIDKernel bundles available. These should only be used for a single end-user company. For example, you're not meant to purchase a 100-seat APIDKernel and then break these up and sell individual seats to different end-user companies.

Another point worth noting is that APIDKernel licenses are linked to a particular serial number of InDesign, as well as to a unique system identifier. That means that if your customer is using both InDesign CS2 and CS3 on the same computer, he'll need to purchase two licenses for APIDKernel.

Depending on how you set things up, there can be two payments to be made by a first-time end-user. First of all, the end-user needs a license for the APIDKernel, and second, he needs a license for your script.

It is up to you, as a script developer, to decide how to handle the cost for APIDKernel.

You can bundle a pre-purchased coupon code for a license for the kernel when you sell your script, or you can ask your customer to license the kernel directly from Rorohiko. That means you can choose to either have a single monetary transaction between the end-user and you, or there can be two separate transactions
- between the end-user and you for your script
- between the end-user and Rorohiko for APIDKernel

Since version 1.0.44, there is also an option to provide your end-user with a single, combined license file which contains both a license for APIDKernel and a license for your script - contact APIDlicenses@rorohiko.com for more info; for this system to be available to you, you need to register with Rorohiko as an APID developer.

Lastly, if you expect to sell more than a few thousand copies of your script, you should probably consider our APIE/APIR combo - these are two other family members of the Active Page Items family. APIR is a runtime which is very similar to APIDKernel, except that it is free for end-users - there is no cost to the end-user for the APIR runtime.

To generate solutions that can use the free APIR instead of APIDKernel, you'd need to license APIE - which is a more expensive version of APIDToolkit, and can create solutions that work with the free APIR instead of the non-free APIDKernel.

Friday, December 7, 2007

Lightning Brain Podcast: Click here to listen to "Refactoring ExtendScripts"

Today we'll talk about cleaning up scripts, and as a bonus, we'll work on a script that adds a 'hand-written' quality to text: changing something like this:

Before Jitter

into

Before Jitter

By clicking the link provided in this sentence, you can download this real-life example of a small script being refactored, from experimental form to a cleaner form - click here to download JitterScripts.zip

Listen to or read the podcast transcript for more info...

On to the podcast - click here to listen to it!


So, the script works, everyone is happy - but maybe you are not done yet...

Imagine that you will have to pick that same script up again twelve months from now. How much time will it take you to 'get back up to speed' and rebuild a mental picture of the script and what it is doing? Probably it will take you many hours of browsing, debugging and fiddling around before you regain enough understanding of the script's inner workings to make a 'safe' change.

Now, what if the circumstances have changed, and the script needs to be adjusted to cope with a new environment - it might need to be moved from InDesign to InDesign Server, there might be new requirements...

How about spending some time now - when your head is still filled with knowledge about how it all works - and make sure the script becomes more future-proof.

In this podcast, I'll list a few of the techniques I personally use to 'future-proof' my scripts.

Before diving into the techniques, I need to explain how we approach ExtendScript development here at Rorohiko; we've adapted an approach that delivers very good quality at a reasonable price - we're not cheap, but as opposed to many other custom developments, our solutions work and work well.

When we're developing custom scripts, we use a 'no cure, no pay' approach, for a number of reasons.

The main reason is, that for this type of development, building a sufficiently accurate quote often costs us more than the development itself.

For an accurate quote, we would first and foremost need an accurate, extensive project brief.

But in our business we're often dealing with creative, fairly a-technical people, and we invariably found it very hard, even impossible to zoom in on an accurate enough technical description of the functionality being looked for.

On top of that, what we're asked to do is often at odds with what is really needed. We're often asked to develop a specific solution, rather than being asked to solve a problem.

From experience we've learned that it pays to dig deeper, and try and find out what the underlying problem is - quite often the asked-for 'solution' only cures a symptom, and leaves the underlying problem unfixed.

The most efficient way we found to get the technical information we need from creative people is to use an iterative approach. Instead nagging people and trying to wring a technical brief out of them, we put the cart before the horse instead, and we create something, anything, as good as we can, based on the still limited understanding we have of the problem at hand.

We present our customer with the attempted solution, and get their feedback - it is much easier for them to explain what is wrong or what is missing from some tangible, real software, rather than trying to come up with blueprint.

Based on the feedback, we adjust the solution (or throw it out and start over), and we go through a few iterations, until we have the thing sussed.

Eventually, we reach a good, smooth solution, and by that time we also know exactly what the cost of that solution is.

At that point, the 'no cure, no pay' system kicks in: our customer can choose to purchase and continue to use the software, or alternatively, in case our solution were to not live up to its promise, the software is simply destroyed, and there is no cost to the customer.

This approach works really well, but the successive iterations cause the software to go through a few swings and roundabouts, and along the way, grow a whole collection of warts if we're not careful.

We'll typically spend some time refactoring the scripts to make sure they're future-proof and self-explanatory - making a small investment of time now in return for a substantial time-saving later.

Here are some of the things we do:

1) Don't rely on app.activeDocument


While in the heat of experimentation, iteration and script development, it's easy to assume that the functionality being created will be applied to the current document, and hence to refer to app.activeDocument

However, there are sizable benefits to removing the reliance on the active document. Our scripts will typically contain a number of functions, and whenever a function uses app.activeDocument, we rework that function to not use 'app.activeDocument', but instead take a 'document' parameter.

The idea is that you get hold of the document 'under consideration' in one single spot in the script, and that from then on you pass a document parameter to any function that is supposed to work on that document.

The two biggest advantages are:

First, your script becomes a lot easier to convert to an InDesign Server environment (where there is no such thing as app.activeDocument), and second, all of your functions have now suddenly become much more re-usable: they can now also be used when the document to be modified is not the active document.

For example, the document might be a temporary, invisible document you're opening in the background - and by NOT using app.activeDocument, you can pass such a document to your functions as a parameter.

2) Test, test, test your preconditions


When using a function, it pays to add tests for preconditions - make sure all parameters being passed as what they are supposed to be, and display an error message if they are not. Whenever possible, we leave all this testing code in the script - so if something goes wrong at the customer's end we get good, specific information about where things go off the rails.

Typically, we'll have a global constant - something like kDebugging - which can be set to true or false to indicate debugging more.

We'll also add a messaging function similar to alert() which can display a dialog box with a message. The difference with alert() is that the dialog box is conditional on kDebugging being set to true. If kDebugging is set to false, the message is ignored.

And then we'll test all the function parameters being passed into a function. Is the document non-null? Is it instanceof Document? Is the percentage a number between 0 and 100? If any of these tests fail, a debug message is emitted, and the function 'bails out'. This guarantees that any unexpected condition can be caught early on.

This works well by wrapping most of the function body inside a do{}while(false) construct, which mis-uses the do-while loop to build a construct that allows a 'ladder-like' function construction.

Inside the do{}while(false) there is a whole series of if tests which verify if all is well, and display a debug message followed by a 'break' statement if not. The break causes the function to 'fall off' the ladder for any failing precondition. The debug message being displayed is specific enough to pinpoint the spot where things went wrong - it will include the name of the function where the problem occurs, and a short description of what is wrong.

This construct is quite similar to using try-catch, but it is 'cheaper' in a number of respects; it causes less overhead than using try-catch, and does not cause the InDesign Debug version to emit assert statements during script execution.

3) Do not spend time optimizing unless it is really necessary.


Now, you'd think that all that debug code from the previous point must cause a lot of overhead.

Well, turns out that is not true most of the time - a typical script will spend 95% of its time in 5% of the script, and all that debug code has very little impact on the script execution time.

In practice, we'll leave all our debug code in the script - all we might do is to set kDebugging (or whatever the constant is called) to false - but even that we often don't do: it's better to be informed of unexpected circumstances, than to have a script silently and mysteriously fail.

Only when there are speed issues might we consider removing some debugging code - but only if we can clearly see that this code is part of the bottleneck.

The current ExtendScript Toolkit contains a nice profiling tool that allows you to see where a script is spending most of its time. Our recommendation is to not bother with optimizing unless there is a time issue, and when optimizing, use proper profiling to solve the bottleneck - but nothing else. Any debug code that you can leave alone should be left alone; it's part of your safety net.

It is very common for our scripts to have 50% or more debugging/testing code in them.

4) Avoid global variables


While experimenting, it is very common and easy to introduce some global variables to keep track of things.

However, global variables can be a recipe for disaster - especially when you need to revisit an older script and make some modifications to it.

Global variables represent a form of communication between different areas of your script - functions can communicate with one another by stuffing data into global variables, and getting it back out again.

Problem is: that type of interaction is very easy to get overlooked, and causes all kinds of unexpected side effects - for example, you add an extra call to a particular function somewhere, the function changes a value of some global variable, and then other functions that rely on that same variable go off the rails.

Because functions don't clearly 'advertise' what global data they consult or modify, it becomes very hard to keep track of interactions. That makes for fun debug sessions, chasing weird bugs after making a 'tiny change' to a year-old script.

Like everyone else, during the initial phase of a project, we often start out stuffing data into globals - but unless there is good reason to, we'll rework the script and move the global variables into function parameters. If there is a lot of data, we'll introduce one or more data structures which are then passed around as a parameter.

An example: we might be parsing a text string, and keep track of where we're at in a global variable gTextPos, and store the string in a global gParseText.

During cleanup, that will be reworked (or 'refactored' as it is often called) - we'll get rid of the globals, and instead we'll put the current 'parse state' into a JavaScript object with at least two attributes: parseText and textPos.

Then we pass that object to the relevant routines using a parameter - say 'parseState'.

This way it becomes immediately clear to the human reader of the script which routines access that data (they need the parameter) and which ones don't access that data (they don't need the parameter) - it's a self-enforcing cleanup. From this moment on, each function that needs access to that data does 'advertise' the fact via its parameter list.

Imagine every JavaScript function as a gob of code floating in space. Then imagine what outside factors influence the function's operation and how, in return, the function influences its environment. There are the parameters coming in at the top, the return value coming out at the bottom. Most of the time these two relations are pretty easy to see.

Then there are any global variables that are modified or consulted by the function - using globals leaves a lot of room for unseen interaction between the function and its environment. Things like app.something and $.something are also globals - they are provided by InDesign, but they are still globals.

The more 'isolated' you can make your functions, the easier it will be to re-use in a different script.

Functions that interact with global data are like a beating heart - very difficult to transplant because there is a lot of stuff to disconnect and reconnect.

Functions that take data via their parameters, and return data via their return value and/or via some of their parameters are much easier to transplant: a few easy connections to their environment; they easily snap in and out.

5) Each function should do one thing well


We always try to create functions that do one thing well; during the 'frantic' phase of a project we often end up with functions that do lots of stuff. These multi-headed monsters need to be divvied up into smaller functions - each doing just one thing. Functions that are initially called something like 'ImportFileAndColorFramesAndDeleteOverrun' are split up into multiple smaller functions.

This increases the chances of making things re-usable - any 'good' function eventually ends up in our growing function library and will be reused, which cuts down our development time on future projects. Multi-headed monsters are never reusable - so cutting them up has distinct advantages.

6) Name constants and move them to the header of the file for easier customization


During the trial and error phase, you'll typically add all kinds of literal constants to the code - it is worthwhile to try and isolate these constants from the code and move them to a seaparate section near the top.

This makes the script easier to adjust, and it also makes it more robust.

Now, if a certain string constant is used twice in the script, there seems to be little advantage to creating a symbolic constant for the string and then use the constant instead of the literal string. Many people think this is a pedantic use of constants - on cursory inspection, the two approaches look not all that different.

However, the advantage is that with a symbolic constant, typing errors can be immediately caught by the computer, whereas with literal strings the computer would not know that these two strings are supposed to be equal.

So, if you'd type two literal strings "TextFrame" and "TextFrome", the computer would accept that - but if you typed two symbolic constants kTextFrame and kTextFrome, the second one would be undefined and cause an error.

By clicking the link provided in this sentence, you can download a real-life example of a small script being refactored, from experimental form to a cleaner form - click here to download JitterScripts.zip

Sunday, November 11, 2007

Virtual Group

In addition to being a software developer and trainer, I am also a member of a New Zealand-based team of business consultants, called the 'Virtual Group'. To better explain what the Virtual Group can do for an organisation, we'll be conducting a number of interviews with our team members.

The first interviewee is Bruce Holland - Bruce is an expert in revitalising large mature organisations.

I interviewed him today, and started a new Virtual Group blog/podcast which can be read and listened to by clicking here.

Monday, October 29, 2007

Lightning Brain Podcast: Click here to listen to "InDesign User Interfaces"

Welcome to another episode of the Lightning Brain Podcast. We'll talk a little bit about InDesign and user-interface code.

When it comes to extending InDesign, there are many options available: you could create a C++-based plug-in, you can create an ExtendScript (JavaScript) solution, you can build an AppleScript or VBScript-based solution, you can build a Flash-based UI and then use it in InDesign, you can glue some other development environment 'into' InDesign, and you can also make a hybrid of the aforementioned solutions...

Which approach to choose depends on what your need is, what portion of the project is user-interface functionality as opposed to faceless functionality, what development environments you're familiar with, what your potential users are willing to accept, what budgets (time, money, resources, people, testers,...) are available for the project, what the politics involved are,... As in all things automation, there is no single 'best' solution - it all depends.

In this podcast I want to describe an approach we've very successfully used for a few real-size, real-life projects, and what the advantages and disadvantages are.

I first want to include a little disclosure: Rorohiko resells the Active Page Items Developer product as a commercial solution, and Active Page Items is very much part of the approach described here - so you might think this is podcast is a veiled advertisement for Active Page Items. Well, it is - but you have to keep in mind that Active Page Items has been created and has grown out of our own need for such a tool - Active Page Items was first; commercializing it came later.

One of the things we've learned is that C++ development for InDesign can be quite expensive: simple principles and algorithms often take a surprising amount of code to express. I'd describe C++ development around InDesign as 'fluffy'. High-level concepts and patterns result in lots of classes and source code files and fairly large amounts of C++ code, much of which is often quite repetitive in a number of respects. Especially the development of user interface elements takes a lot of doing.

At present, the only way to currently get a really 'native' InDesign UI look is to use C++ - e.g. if you want to create floating palettes, with all their end-user flexibility (tearing off, parking to the side,...) you need to use C++.

From a UI perspective, C++ might be 'perfect', but often there are other approaches that could be classified as 'good enough'. They might not look as nice, but they might do the job.

For example, using ExtendScript with InDesign CS3 one can develop quite complex user interfaces which often are 'good enough'. ExtendScript development is easily an order of magnitude cheaper than C++ development - so if the parameters of the project at hand don't require an absolute perfect-looking interface with floating palettes, ExtendScript can be the way to go.

Often there are also user-interfaces that need something a little bit more complex than what can be accomplished using ExtendScript, yet don't need a full blown native InDesign user interface. That's where the 'hybrid' approach that we've been using comes in.

The approach we've chosen basically boils down to: REALbasic, Active Page Items, ExtendScript.

We create our more complex user interfaces in REALbasic. However, this is not an absolute requirement - on the whole, we might have used Java instead.

The main reasons for choosing REALbasic over Java are 1) that it allows us to create cross-platform code that looks sligthly more 'native' on Mac as well as on Windows 2) easy access to global floating windows (both on Mac and Windows) and 3) purely personal preference: I find I personally can build and implement user-interfaces faster with REALbasic than with Java.

Easy access to global floating windows is one feature of REALbasic that comes in really handy, for which I don't know whether Java offers an easy alternative.

Global floating windows are windows that remain 'on top', even if the application that owns them is not the foreground application. This is fairly important for the illusion we want to maintain.

Active Page Items is a fairly large C++ plug-in, which we extend as we need with new functionality.

One of its many functions is coordinating InDesign with external applications. Through Active Page Items, we are able to create an illusion that makes the external 'satellite' apps seem to be part of InDesign.

One of the tricks is to 'lock' InDesign in a modal mode while one of our REALbasic satellite apps is running. That creates the illusion that a dialog owned by the satellite app seems to belong to InDesign.

However, the modal mode is not real - it's a simulated modal mode, and while this simulated modal mode is active, InDesign is actually still very much 'alive' and able to execute ExtendScript code - so it is possible to create a 'live' session between the satellite app and InDesign while the user interacts with this 'simulated modal dialog', while 'locking out' the user from any undesirable interactions with InDesign.

The illusion is not perfect: on the Mac's Dock and on the Windows start bar it is fairly apparent some secondary app is running, but that is a cosmetic issue, and it does not really seem to annoy our end-users too much.

- Active Page Items is also used to manage menu items and context menu items. This is mostly because our solutions needed to support CS2 as well as CS3 - InDesign CS3's ExtendScript has all you need to create menu items and context menus, so if you have the luxury of an InDesign-CS3-only setup, you can stick with standard ExtendScript in that respect.

- For communication between the various disparate components, we use temporary files. This is a very low-tech and crude approach, but it works 'well enough'. In a future version of Active Page Items we might add support for a more 'high-tech' information exchange mechanism, but for now temp files do us just fine.

If you want to try things out for yourself, I've created a very small sample of such a hybrid solution; the source code to it comes as part of our Active Page Items Developer Toolkit. If you download the latest demo version of the Toolkit from our web site, you'll find my example code tucked away amongst the other examples.

Some of the scripts can also be viewed at the end of the blog entry.

The sample, which deals with overset text, has no real practical applications as such, but you should be able to see how it can be made into a practical solution for particular problems.

The sample performs the following function: it looks out for overset text. As soon as a text frame gets overset, some ExtendScript code jumps into action, and fires up an external application, which then shows the contents of the text frame in a scrolling text field. The idea is that the user edits the text down to a shorter version to stop the overset. Of course, is not a practical approach at all, but it does allow us to demonstrate the various techniques involved.

In the sample, the active bit of ExtendScript code is currently 'attached' to a 'dummy' page item that sits on the pasteboard. The page item is not meant to be printed or have any sensibly printable content; all it does is hold some script code. We call such a page item a 'controller'. The controller 'watches' one or more page items, and waits for the events to occur.

In a 'real' solution based on Active Page Items, we'd instead 'package' that script code into a so-called 'Scripted Plug-in', in a .spln file.

In this particular case the controller is set to watch all page items, and the 'interesting events' it might watch out for are

- an event called 'subjectModified-recomposed-overset' which occurs when any page item ends up being overset after something happened to it (user typed something, frame resized,...)

- an event called 'idle' which occurs at regular intervals. In this particular solution, we rarely look out for 'idle' events as to not unnecessarily tax the computer's performance. We only do so while InDesign is in 'simulated modal mode' when the satellite application is running.

So, when any page item becomes overset, the subjectModified-recomposed-overset event is captured by the controller.

The controller's ExtendScript then launches the external satellite application using a special Active Page Items method attached to the application object - app.launchWith().

app.launchWith() has a number of functions. The most common use is as an extension to the File.execute() method.

File.execute() is similar to double-clicking an icon in Explorer or in the Finder, and will pick the default application to open a particular document.

app.launchWith() allows you to designate a particular application to open a particular document with - it is more akin to drag-dropping a document file icon onto an application's icon.

On top of that, app.launchWith() has a special feature - it allows us to lock InDesign into simulated modal model for as long as the launched application continues to run.

That makes for a crude, yet effective way to synchronize a satellite app with InDesign: you launch the satellite application using app.launchWith(), and when the user clicks 'OK' in the dialog presented by the satellite application, the application simply exits. Active Page Items is monitoring the satellite app, and as soon as it sees it exit, it will release the simulated modal lock.

So, the controller's ExtendScript first writes the contents of the overset frame into a temporary text file.

It then uses app.launchWith() to tell the satellite app to open this temp text file and pick up the data being communicated.

The satellite app then runs until the user clicks OK in the dialog, after which it writes the new data to the same temporary text file. When the application exits, Active Page Items will release the simulated modal lock automatically.

While the satellite app is running and InDesign is in simulated modal mode, the controller is catching idle events (roughly once per second). During these events, we could perform more communication backwards and forwards with the satellite application (e.g. for live previews or so), but in this case, all we do is check the simulated modal lock: as long as that is not lifted, we know the app is still running and we do nothing.

When we notice that the simulated modal lock has disappeared upon receiving one of the idle events, we know it the user has clicked OK, the app has written the new data to the temp file, and has quit, and we know we can now read the returned data, and then we can stop looking for idle events - things come back to normal, with the controller only watching out for overset events.

This sample should give you a little bit of insight on how we approached some real-live projects with very good results.

The advantages we had were:
- fast development of a good-looking UI that was beyond what can be accomplished with ExtendScript.
- cross-platform (Mac & Win) support: the same code works on both platforms only minute amounts of conditional code.

The disadvantages:
- don't pay attention to the man behind the curtain. Global floating windows and simulated modal mode allow you to get close, but nothing identical to the real thing (an InDesign-generated dialog or palette). The satellite app is visible - we worked around that by giving it a good-looking icon.

On the whole, the disadvantages were acceptable for the particular projects, and as a result we were able to offer very high efficiency in realizing these projects.

Thanks for your attention!

---

//
// Example of using an external program for dialogs.
//
// This document needs a file called "ExampleInDesignSatellite.app" (on Mac)
// or "ExampleInDesignSatellite.exe" (on Windows) in the same folder
// as the document.
//
// This event handler handles subjectModified-recomposed-overset and
// idle events.
//
// In normal circumstances, the event filter is set to just
// subjectModified-recomposed-overset - i.e. the handler only
// activates when there is a text frame that has just recomposed,
// and shows overset
//
// When that happens, the handler below will launch an external
// program to edit the text frame contents, and also change the event
// filter to "idle" - causing repeated calls to this handler while
// the user is editing the text in the external program. The
// external program is launched using launchWith and a mode equal
// to 4 - meaning: InDesign is modal locked for the user until the
// external program terminates.
//
// So, what happens is that we repeatedly receive and handle
// idle events, until app.callExtension(0x90b6C,10003) returns
// false (meaning: not modal locked), which only happens when
// the external program has terminated.
//
// As soon as the external program terminates, we restore the
// normal event filter, and read the output of the external
// program to stuff into the text frame
//
do
{
//
// tempFile is used to communicate data to and from the
// external program
//
var tempFile = File(Folder.temp + "/tempText.txt");

//
// Check if we're in the "idle" phase - waiting for the external
// program to finish
//
if (theItem.eventCode == "idle")
{
//
// We check whether InDesign is still modal locked. If so, then
// the external program has not finished yet - bail out of the event
// handler. In a second or so, on the next idle event, we'll give
// it another go
//
var indesignModalLocked = app.callExtension(0x90b6C,10003);
if (indesignModalLocked)
break;

//
// The modal lock is gone - so the external program is finished.
// We restore the event filter to what it was before it all started
//
theItem.eventFilter = "subjectModified-recomposed-overset";

//
// Did the external program communicate some data back to us?
// If so, then it is in the temporary file
//
if (! tempFile.exists)
break;

//
// Read the edited text and stuff it back into the story being edited
// We've stored a reference to the story in the data store associated to
// theItem
//
tempFile.open("r");
var editedStory = theItem.getDataStore("editedStory");
editedStory.contents = tempFile.read();
tempFile.close();

//
// And we're done for now!
//
break;
}

//
// Ok, we're handling a subjectModified-recomposed-overset event here.
//
var theDocument = GetParentDocument(theItem);

//
// We need the document's path to find the satellite app. If the document
// has not been saved yet, there is no path - so bail out
//
if (! theDocument.saved)
break;

//
// Is this a Mac or a PC? The Mac uses .app files, the PC uses .exe
//
var isMac = $.os.charAt(0) == "M";
if (isMac)
{
var theSatelliteApp = File(theDocument.fullName.parent + "/ExampleInDesignSatellite.app");
}
else
{
var theSatelliteApp = File(theDocument.fullName.parent + "/ExampleInDesignSatellite.exe");
}

//
// If we cannot find the satellite app, bail out
//
if (! theSatelliteApp.exists)
break;

//
// Write the overset story to a temporary text file
//
var theStory = theItem.eventSource.parentStory;
tempFile.open("w");
tempFile.write(theStory.contents);
tempFile.close();

//
// If we cannot find the temp file we just created, bail out
//
if (! tempFile.exists)
break;

//
// Open the temp file with the satellite app, and use flag "4"
// which means: lock InDesign into a user-modal mode until
// the satellite app terminates
//
app.launchWith(tempFile.fsName,theSatelliteApp.fsName,4);

//
// Change the event filter to process idle events - so we
// can regularly check whether the satellite app has
// terminated or not
//
theItem.eventFilter = "idle";

//
// We need to remember the story so we can put the edited
// text somewhere later on
//
theItem.setDataStore("editedStory",theStory);
}
while (false);

// End of event handler. Utility functions below

function GetParentDocument(pageItem)
{
var document = null;
do
{
var err;
try
{
document = pageItem.parent;
}
catch(err)
{
document = null;
}

if (document == null)
{
break;
}

if (document instanceof Document)
{
break;
}

if (document == pageItem)
{
document = null;
break;
}

pageItem = document;
}
while (true);

return document;
}