Acrobat

Doing an IAmA

Michael Geary | Thu, 2011-05-05 16:08

I’m doing an IAmA on Reddit:

I was one of the first people Steve Jobs ever fired

Geek history! :-)

Ajax-style PDF part 1: fading highlight setup

Michael Geary | Tue, 2005-06-28 07:54

If you haven’t already seen it, take a look at Adobe’s walking talking PDF tour of Acrobat 7.0. It’s one of the most creative PDF files I’ve ever seen. (Don’t stop after the first few pages; there are some funny bits near the end.)

Adobe Acrobat Professional Get Adobe Reader

Obviously, a lot of work went into making this PDF, but the technical side of it is actually pretty simple. We can add some scripting magic to make it even better.

Take page 11, where our narrator explains the links he’s standing next to, pointing to each one as he describes it:

The links just sit there when he points at them. It would look good, and be a nice usability touch, if we could apply an Ajax-style fading highlight to each link as he points to it:

First we need to find out how the existing page works. If you have Acrobat Professional, you can see it by using the Select Object tool on the Advanced Editing toolbar. That’s a Flash movie on the right with the narrator in it. Right click it and open its Properties to see the Page Enter event and a .swf rendition (Acrobat’s term for a media clip and its associated settings):

Each page is like this, with a Flash movie embedded in the page that runs on the Page Enter event.

The links on the left are (no surprise) PDF pushbutton form fields.

With our narrator in a Flash movie and the links being PDF buttons, is there a way to connect the two? We can write some JavaScript code in the PDF to fade a highlight on and off for a link, but how do we trigger that code at the right time as the movie plays?

Well, one thing at a time. It would be fun to just see the fading highlight in action, so we’ll write that bit of code first and hook it up to a temporary button to test it. The code will use doc.getField(name) to get a JavaScript Field object, and then it can set the field’s fillColor property to change its background color. If we do that on a repeating fast timer we’ll have the fading highlight effect.

For a quick test before we write any code, we can right click one of the buttons and open its Properties to change its fill color manually:

Oops. That worked, but it didn’t do what we want. We got the fill color but the icon and text went away. Let’s Undo it and try something else. (And note that the fill color doesn’t extend all the way to the right end of the field. That’s because the Flash movie overlaps the field. Hopefully this won’t cause any problem.)

We can create a separate field that is a solid rectangle, and if we get the Z-order right it should do what we want. A text field with no text in it will do the trick. Let’s try it without worrying about the exact layout first:

I guess that’s some kind of progress. Maybe changing the Z-order will fix it. The tool to change that is tucked away in Acrobat’s Advanced/Forms/Fields/Set Tab Order menu command:

Now we can click on the fields in order to set their tab order (which is also their Z-order), and if we put the text field in the tab order before the button, we get the transparent background highlight we were looking for:

Finally, we move and resize the text field and we have our field highlight, at least in static form. Here’s the page after a Select All (Ctrl+A) to show all the field rectangles:

Creating a separate text field for the highlight was a minor nuisance, but does have one benefit: we were able to fine tune the highlight position relative to the button icon and text:

The blue outline is the pushbutton field that we tried to work with originally. As you can see, the pushbutton field rectangle doesn’t have consistent margins around the icon and text (and no margin on the left). With a separate text field—the red outline—we can adjust it so the highlight is positioned nicely:

Now that we have a highlighter field, we should be able to write some code to control it. While I was editing the field I changed its name from the default Text1 to Hilite, so we should be able to use getField and set its fillColor. Let’s try it in the JavaScript console first:

Looks good! We got a reference to the field in the hilite variable and looked at its current fillColor property. Then we changed the fillColor and the visible field changed as expected.

The last statement in the JavaScript console (not yet executed in the screen shot) hides the field, so we can save the file and it looks normal. It doesn’t matter that we left the field the wrong color; we’ll take care of that in the code that makes it visible again. For now, it’s time to save the file and take a break. In the next installment we’ll write some code to create the fading highlight effect.

p.s. Here’s an Acrobat editing tip: Open the General tab of Acrobat’s Preferences dialog and turn on the single-key accelerators. Then you can use the H key for “hand” (normal) mode in Acrobat, R for the object selector, and so on. Hover the mouse over a toolbar button to see its shortcut key. It makes this kind of editing a lot easier where you switch tools so often.

Disclaimer: I work for Adobe, but this is my own summer vacation project, not any kind of offical Adobe code.

Invisible JavaScript functions

Michael Geary | Thu, 2004-09-16 16:24

If you’re writing document-level JavaScript code in a PDF file, any variables you declare or assign to outside a function are created in the global object, which is the PDF document.

Suppose we have a PDF with this code as a document script, which creates three variables in three different ways:

var myVar = 11;    // declare a variable
myNoVar = 22;      // assign to a global variable
this.myProp = 33// create a document property
 

If we load this PDF and examine its variables in the JavaScript console, we see that each of the three variables is defined both as a global variable and as a property of this, the document object. That seems surprising until you consider that in Acrobat JavaScript, the document object is the global object.

myVar
11
this.myVar
11
myNoVar
22
this.myNoVar
22
myProp
33
this.myProp
33
(Keyboard input is in bold, and we type Ctrl+Enter at the end of each line to evaluate the expression.) When you’re writing code, you don’t want to pollute the document object’s namespace needlessly. You can wrap up your code in a function, so that variables you create with a var statement are local to the function. Adobe Acrobat encourages this by creating a function for you when you add a document script. If you use Acrobat’s Document JavaScripts dialog to add a script called DocScript, you’ll get an empty function to fill in:
function DocScript()
{
}
Of course, you need to call this function, so you may end up with code like this:
function DocScript( doc )
{
    var myVar1 = 11;
    myNoVar1 = 22;
    doc.myProp1 = 33;
}

DocScript( this );
Besides creating a scope for local variables, the function lets the code refer to the document object as doc instead of this. We didn’t have to do that, but I like to be able to use doc.whatever instead of this.whatever for document properties. If we look at these variables in the console window, the results are different:
myVar1
ReferenceError: myVar1 is not defined
1:Console:Exec
undefined
this.myVar1
undefined
myNoVar1
22
this.myNoVar1
22
myProp1
33
this.myProp1
33
As expected, myVar1 does not show up either in the global object or the document object; it is local to the function. myVar2 does (because we didn’t use var), as does myProp1 (because we created it as a property of the doc object). The difference between myVar1 and this.myVar1 is expected too. It’s an error to reference a variable name that does not exist, so myVar1 throws a ReferenceError exception. But it’s not an error to reference a nonexistent property of an object—the reference merely returns the undefined value. So this.myVar1 returns undefined without error. But we’ve still put one name in the document object:
DocScript

function DocScript(doc) {
    var myVar1 = 11;
    myNoVar1 = 22;
    doc.myProp1 = 33;
}

this.DocScript

function DocScript(doc) {
    var myVar1 = 11;
    myNoVar1 = 22;
    doc.myProp1 = 33;

}

To avoid even this bit of namespace clutter, we can define an anonymous function and call it on the spot:

(function( doc )
{
    var myVar2 = 11;
    myNoVar2 = 22;
    doc.myProp2 = 33;
}
)( this );

The console results for myVar2, etc. are the same as the previous example with the named function, so I won’t repeat them. But unlike the named function, we haven’t added any symbols at all to the document or global object.

Why the extra parentheses? function(){} is the simplest possible anonymous function, but if we try to call it using function(){}() it’s a syntax error. However, if we wrap the entire function inside a pair of parentheses, then we can follow that with () to call it: (function(){})() is legal JavaScript.

The same trick works when you want to add a nested scope inside a function. The way you would do this in C doesn’t work in JavaScript:

function noscope()
{
    var i = 1;
    console.println( i );

    // This {} does not introduce a scope as in C
    {
        var i = 2;
        console.println( i );
    }

    console.println( i );
}

noscope();

That code prints:

1
2
2

because there is only one variable named i in the entire function, even though we’ve (incorrectly) tried to add an inner scope with its own variable i.

In fact, if you load that function into ActiveState Komodo, it puts a green squiggly line under the inner var i = 2; complaining “strict warning: redeclaration of var i”.

But if we use a nested anonymous function:

function withscope()
{
    var i = 1;
    console.println( i );

    (function()
    {
        var i = 2;
        console.println( i );
    })();

    console.println( i );
}

withscope();

it prints:

1
2
1

which indicates that the nested function introduced a new scope.