TweetFollow Us on Twitter

Acrobat Plugins

Volume Number: 16 (2000)
Issue Number: 6
Column Tag: Programming Techniques

How to Write Plug-Ins for Adobe Acrobat

by Kas Thomas

The Acrobat SDK provides a bewilderingly diverse assortment of tools for manipulating PDF files

Adobe's Portable Document Format (PDF) is the premier electronic document format for "rich content" on the web, not just because of its ability to convey the appearance attributes of a paper document (which it is very good at, since the underlying technology is Postscript-based) but also because of its annotation features, hyperlink embedding, support for form widgets (and JavaScript), Unicode compatibility, security (encryption) handlers, and accessibility features. (PDF is also evolving to become more adept at encapsulating document metastructure and private data types.) Access to all these types of functionality is available to creators of PDF documents via Adobe Acrobat, the commercial version of Reader. Unlike Reader, the full Acrobat product (street price: $200 or so) lets you mark up a PDF document in various ways, add links and form fields, attach JavaScript to documents, etc. — and save the modified PDF file to a storage device.

Most of the kinds of functionality you'd ever want to see in a PDF file can be added using Acrobat or created in the source file at the time of the document's creation (via MS Word, FrameMaker, or whatever). But in the PDF world, as in the world of Photoshop, Quark, After Effects, etc., there are times when the functionality you need just isn't available from the host application. In such a situation, it makes sense to consider a plug-in.

As with its other flagship products, Adobe has incorporated a plug-in architecture into Acrobat to enable third-party developers to extend the functionality of the core product. Quite a few of the key features of Acrobat (features most users think of as being "core functionality") are, in fact, implemented in the form of plug-ins. For example, form-editing and form-fillout functions are done as plug-ins. Acrobat also uses plug-ins to implement weblinks, annotations, text touchup, Quicktime movie attachments, scanning ("paper capture"), image-import, and quite a few other functions. (Check the plug-ins folder of your Acrobat installation.)

Extending Acrobat's capabilities via custom plug-ins is a powerful (and popular) way to give users extra control over PDF documents. But this capability comes at a cost. Learning to use the Acrobat plug-in API (one of Adobe's most complex APIs) can be a formidable challenge. There are well over 1,000 functions in the Acrobat API; just finding the ones you need to carry out a specific task can be daunting. Suffice it to say, this is one API you won't learn in a weekend.

Because of the sheer enormity of the API, there is no way I can give you more than a very abbreviated introduction to Acrobat plug-ins in the limited space available here. What I can do is briefly outline the available tools, talk about a few of the most common idioms of Acrobat programming, and point you toward resources for further study. In addition, just so you won't think writing an Acrobat plug-in is totally beyond reach, we'll walk through the code for a small plug-in designed to do something at least vaguely useful: namely, add a text-based "watermark" to a page, in response to a menu command.

Getting Started with the SDK

You can download a copy of the Acrobat software developer's kit (SDK) from http://partners.adobe.com/asn/developer/acrosdk/acrobat.html. The SDK is also available on CD-ROM to members of the Adobe Solutions Network Developer Program. (See http://partners.adobe.com/asn/developer for details.) The complete SDK requires over 30 megabytes of disk space, including 27 megs for documentation alone. Example code comes in the form of Code Warrior projects for two dozen sample plug-ins and a handful of AppleScripts showing how to control Acrobat via IAC (Inter-Application Communication). The code is well-packaged, thoroughly commented, and well-summarized in a separate docfile, Samples.pdf, which gives a brief overview of what each example is meant to demonstrate.

The documentation that accompanies the SDK is so overwhelming that Adobe thoughtfully includes a "Start Here" document as well as a flowchart-style roadmap diagram in which every box is a clickable link taking you to the appropriate docfile. The roadmap occurs at the top of nearly every docfile in the package, which is a nice touch. Just by clicking on various portions of the roadmap, you can jump from doc to doc. Of course, all the docs are PDF files with navigation bookmarks.

The 213-page Acrobat Core API Overview (Tech Note No. 5190) is a good document to start with, since it serves as a kind of giant FAQ for developers, explaining how the various components of the API work together. But the two most important docfiles in the package — the two you'll spend the most time with — are The PDF Reference Manual and the Acrobat Core API Reference. The 513-page PDF Reference Manual, also known as the PDF Specification, contains the formal language specification for PDF. It spells out all the keywords, data types, architectural schemas, and syntax constructs that make a PDF file a PDF file. Just as it would be hard to write a Netscape browser plug-in without understanding how HTTP and HTML work, it's inconceivable that you would write an Acrobat plug-in without knowing a fair amount about the low-level organization of PDF files. The PDF Reference Manual gives you this information. Plan on spending many hours reading it.

Your primary reference manual for the plug-in API itself will be the 2,795-page Acrobat Core API Reference (Tech Note No. 5191). Yes, I said 2,795 pages. But don't worry, that's sure to get bigger as Acrobat evolves. (Version 5.0 is set to appear sometime within the next nine months.)

The Acrobat Core API Reference is not a book that you sit down and read cover to cover; rather, it's a reference you consult as the need arises. Inside it you'll find descriptions (calling syntax, etc.) of the 1,100-or-so core routines that make up the Acrobat API. You should understand that one reason this API is so voluminous is that it comprises the bulk of the library routines from which Acrobat itself was (and is) written. Adobe uses these same routines internally for Acrobat development.

What You Can and Can't Do

It's possible to write a plug-in for Reader as well as for Acrobat. (There are also mini-APIs for other products in the Acrobat suite: Capture, Distiller, Catalog, etc.) By default, any plug-ins you write using Adobe's SDK will run under Acrobat (the commercial product) with no problem. Writing plug-ins for Reader is another matter. To get Reader to load your plug-in, you will need to obtain an Acrobat Reader Integration Key. Getting a key is a matter of filling out the Acrobat Reader Integration Key License Agreement (available on Adobe's developer web site), signing it, and submitting it — along with a $100 payment (in the form of credit card only) — to Adobe's Klamath Falls, Oregon group (which administers the developer programs). When you sign the agreement, you are agreeing to some fairly serious limitations as to what you will and won't try to do in your plug-in. Specifically, you are agreeing never to include any of the following kinds of functionality in your Reader plug-in:

  • Anything that would allow Reader to permanently modify, save, or write files, including PDF files, annotations for PDF files, form data, etc. Included in this is any functionality that would enable another process (on a server, say) to save such data.
  • Anything that opens encrypted documents by bypassing normal security measures.
  • Anything that would display a PDF file in the window of another application.
  • Accepting navigational commands from an application other than Reader itself.
  • Making use of any function call in the Forms HFT (host function table).
  • Implementing a replacement file system for Reader.
  • Anything that would remove the menu item that calls up the "About" screen for Reader.

In other words, forget about making permanent changes to files (or doing disk writes) in a Reader plug-in. The whole idea is that Reader must remain read-only. (Otherwise it wouldn't be called Reader!) If your plug-in does any of the things listed above, it has to be written as an Acrobat plug-in, targeting the commercial product only (which does modify and save files). This cuts the audience for your plug-in down substantially (probably to one percent or less of the audience of Reader). But there's still a sizable market left. In fact, the market for Acrobat plug-ins is surprisingly brisk, reflecting the product's widespread use in prepress and enterprise settings.

Other Limitations

Not everything you might want to do to a PDF file can be done using the Acrobat API anyway, it turns out. It's important to realize up-front that some things just can't be done. For example, let's say you wanted to implement a new image-compression codec. (PDF already implements JPEG, LZW, CCITT, and Flate compression filters for image streams.) Unfortunately, the Acrobat plug-in API does not expose this kind of functionality — purposely, to ensure maximum interoperability of PDF files. On the other hand, you can write an entirely new security handler, should you want to implement a different kind of encryption or passwording than is available by default in Acrobat. With security, interoperability is not a consideration.

Another kind of functionality you'll have trouble modifying is changing the way Acrobat draws things to the screen. For example, if you want to change the default order in which various objects are drawn, apply a different kind of antialiasing, or improve the blitting performance — you're basically out of luck. These kinds of functionality aren't exposed in the API. That's not to say you can't try going outside the API; but in that case, maybe what you're trying to do would best be implemented as a helper app, system extension, WDEF, or something other than an Acrobat plug-in per se.

Layers in the API

Figure 1 shows how the Acrobat plug-in API is organized into a number of layers, designed to provide functionality at high and low levels. At the highest level is the Acrobat Viewer (AV) Layer. In this layer are hundreds of routines for manipulating the Viewer's windows, menus, toolbars, etc. as well as for querying the runtime environment. This is where, for example, you can call AVAppRegisterForPageViewClicks() if you want to track mouse hits. Most menu commands can be programmatically driven at this level. That means you can control the page view, open and close windows, control the cursor's appearance, and drive just about any GUI-related action. But you cannot do file I/O, inspect or modify stream contents, or perform low-level operations on document internals from this layer.


Figure 1. Layers in the Acrobat API.

The Portable Document Layer implements the PDModel, which is an assortment of objects allowing access to individual components of the PDF file, such as annotations, bookmarks, links, or text selections. The objects in this model are generally opaque, which is to say they are neither pointers to data structures nor pointers to pointers. Access to the contents of objects that live at this level occurs via special accessor functions. For example, to get an annotation you would use PDPageGetAnnot(); then you might inspect its flags with PDAnnotGetFlags()and change the flags with PDAnnotSetFlags().

At the very lowest level, the Cos Layer allows direct access to stream contents and page resources. (Cos is a recursive acronym for "Cos Object System.") Cos objects come in seven main varieties, mirroring the data types upon which PDF is built: booleans, numbers, names, dictionaries, streams, strings, and arrays. Cos methods go by names like CosArrayGet() and CosArrayInsert(). They allow "subatomic particle" access to the PDF file. Of course, as with other low-level programming tools, operating at the Cos level brings risk as well as rewards. As Adobe says in the Core API Overview: "Unlike the AcroView and PDModel layers, it is very easy to produce an invalid file using Cos methods. For this reason, they should not be used unless necessary, for example to add private data to portions of a PDF file that cannot be accessed in other ways." In other words, don't go here unless you know darn well what you are doing. And even then, look out for land mines.

Cutting across all three major layers of API support is one additional layer, known as the Acrobat Support Layer. You can think of this as a "utility" layer where you can find platform-independent functions for doing file I/O, memory allocation, etc.

PDFEdit Layer

One "mini-layer" we didn't talk about (which is shown in Fig. 1 as a small layer residing midway between the PD Layer and the Cos Object level) is the PDFEdit Layer. In a nutshell, this layer was created in order to provide easier (and safer) access to page contents than would otherwise be obtainable. At the PD Layer level, you have access to page content objects, but the objects are opaque and not always easy to modify using the available accessor functions. At the Cos level, on the other hand, you're dealing with raw streams consisting of arbitrary mixtures of page operators, numbers, and text. To manipulate those items, you have to "parse them out" yourself, which is not always easy. The PD Layer offers some parsing and enumeration methods, but in general the objects returned are not easily modified. The problem is even worse than that, however. Resource handling is difficult because stream contents and resources are treated as disjoint entities. (A given piece of text is not easily associated with its font resource, for example.) Also, stream data are often encoded via LZW or Flate compression. Merely retrieving the raw stream data usually isn't helpful.

The PDFEdit methods were invented to overcome these obstacles by providing access to low-level page components in such a way that related items are grouped together, so that objects encapsulate any relevant information about themselves. (A text object contains font information, for instance.) PDFEdit methods thus allow you not only to access but also create page contents, including whole pages, from scratch. Most of the relevant data structures are public (rather than opaque), so that you can manipulate attribute values directly.

You can think of the PDFEdit Layer as offering much of the power of the Cos Layer, but with the safety and convenience of the PD Layer.

PDSEdit Methods

There is actually one more mini-layer in the Acrobat API, not shown in Fig. 1, called PDSEdit. The 'S' in PDSEdit refers to Structure. Prior to version 1.3 of the PDF Specification (which became current with the introduction of Acrobat 4.0), there was really no convenient way to store metastructural information — information about structural relationships of document components — in a PDF file. As a result, PDF gave you a very accurate representation of document appearance, but no way to correlate text strings with paragraphs, say, or paragraphs with topics. (There is no easy way, for example, to reflow text inside a PDF file.) The PDSEdit Layer, building on new features in PDF 1.3, allows authoring processes to build a structure tree into PDF files, such that document content can be mapped to structure. The various methods of the PDSEdit Layer let you access, create, insert, remove, inspect, reorder, and otherwise manipulate nodes and leaves in the structure tree and/or traverse contents in a structured fashion. (A high-level Adobe official gave a talk not long ago in which he touted PDF's ability to act as a container for XML. Adobe is known to be devoting a good deal of in-house R&D to finding ways to make PDF and XML play together.)

The PDSEdit Layer offers some interesting, cutting-edge functionality for developers interested in beefing up the metastructural capabilities of PDF. Most plug-in developers will never use this layer, however, which is why it's not included in Fig. 1 and won't be discussed further here.

Cutting Across Layers

It may be possible for some plug-ins to implement all of their functionality by relying entirely on methods from just a single API layer; but in general, this is rarely the case. It's more often the case that a plug-in needs to draw on many types of functionality, using methods from more than one layer. It's not unusual, accordingly, to have to call on conversion routines that derive one type of "doc object" from another. Figure 2 shows some of the more commonly accessed doc object types and their associated conversion routines.


Figure 2. How to get from one type of "doc object" to another.

In the example plug-in for this article (discussed in further detail below), we make use of methods from the AV Layer, the PD Layer, and PDFEdit. For example, we get the AVDoc object with AVAppGetActiveDoc(), obtain an AVPageView using AVDocGetPageView(), and get a PDPage from the page view using AVPageViewGetPage(). Not all of these methods are shown in Figure 2; it would take many additional flow charts to show all methods.

As I said at the outset, this is one of Adobe's most complex APIs. Just finding your way around can be a challenge — with or without flow chart diagrams.

Notifications

The Acrobat API implements a notification mechanism whereby plug-ins can register to receive notification whenever an event of interest occurs. For example, say your plug-in has a need to know when a document has been opened, so it can traverse the document looking for resources of a particular kind. In that case, you'd probably want to register with AVAppRegisterNotification() to receive AVDocDidOpen() notifications (which are broadcast whenever a document first opens). Over 80 different kinds of notifications are available, corresponding to an amazing variety of user actions and updates. In many cases, the notification will provide pointers to data which may have changed as part of the update action. In all cases, you can register a callback routine of your own to be executed whenever an event of interest occurs.

For more information, see the section on Notifications in the Core API Overview.

Enumeration

It is often desirable to be able to enumerate through all the components of a page or all the objects of a given type in a document, etc. Fortunately, the API provides many enumeration methods that do this while also calling a custom callback procedure for each target object encountered. For example, when PDDocEnumFonts() is called, Acrobat enumerates all fonts used in the document and calls a user-specified callback for each font. (In Acrobat-API argot, a user callback in this situation is often called a monitor.)

Exception Handling

In general, API methods do not return error codes but instead raise exceptions when an error occurs. Developers can write their own exception handlers, since the default handler provided by the viewer app cannot always back out of unfinished operations gracefully. Depending on the severity of the condition, your exception handler can either "fix" the problem and go on (thus absorbing the exception) or do whatever damage control is appropriate and then re-raise the exception so as to pass it up to the next exception handler in the stack. There is no definitive list of which of Acrobat's 1,100 API methods raise exceptions and which do not. The safest policy is: When in doubt, assume that code can raise exceptions and surround it with handler calls. The idiom for doing this is:

DURING

	avd = AVDocOpenFromFile(asp, NULL,(char *)NULL);

HANDLER

	avd = NULL;
	errorCode = ERRORCODE;
	AVAlertNote("Error opening file");

END_HANDLER

The DURING/HANDLER/END_HANDLER macros (defined in CorCalls.h) expand to:

#define DURING {					\
	ACROjmp_buf ASException; \	ACPushExceptionFrame(&ASException, \
(ACRestoreEnvironProc)&RestorePlugInFrame);	 \
	if (!ACROsetjmp(ASException)) {

#define HANDLER _E_RESTORE; } else { _E_RESTORE;

#define END_HANDLER }}

If you want the host app to handle the exception, use the RERAISE() macro. However, don't exit the current function while you're still inside an exception-handled block of code. Instead, use E_RETURN(x) to return gracefully (with 'x' as the return value) from inside a handler block. It also goes without saying that you shouldn't execute code that might (itself) raise an exception, inside a handler, and you should avoid nesting handlers.

For more information, be sure to read the section on exception handling in the Core API Overview.

Handshaking and Initialization

Acrobat performs a special handshake with each plug-in at load time, to give each plug-in a chance to register its name with the host, perform initializations, request notifications, install callbacks, and install an optional unload procedure. In order for handshaking to occur, the plug-in must implement the following method:

ACCB1 boolean ACCB2 PIHandshake(Uns32
handshakeVersion, void *hsData)

The ACCB1 macro expands to the pascal keyword (while the ACCB2 macro currently expands to nothing). The hsData pointer will point to a data structure that currently looks like:

typedef struct {
	ASUns32		handshakeVersion;	 // IN value: HANDSHAKE_V0200 
	ASAtom			appName;		// IN value: Name of host app
	ASAtom			extensionName;	// OUT: Name of plug-in 
	ASCallback	exportHFTsCallback;		// OUT: User routine to register any
						// HFTs this plug-in is providing 
	ASCallback	importReplaceAndRegisterCallback; // OUT: User routine
     					// to import other plug-in's HFTs, replace HFT functions, 
						// and register notifications 
	ASCallback	initCallback;	// OUT: User callback for doing inits
	ASCallback	unloadCallback;		
} PIHandshakeData_V0200;

The extensionName field must be filled out with an ASAtom corresponding to the name of the plug-in, formatted according to ABCD_Name, where ABCD is a four-character "vendor" prefix while Name is the name of the plug-in. The AS Layer utility routine ASAtomFromString() can be used to convert a string literal to an ASAtom, which is a hashed representation of the string that Acrobat uses internally.

The exportHFTsCallback field can contain NULL but can optionally be filled out with a user callback (which has been converted to an ASCallback) to a routine to call if the current plug-in is exporting any HFTs, or Host Function Tables, of its own. The HFT is an abstraction layer through which plug-ins and other processes can expose and/or access each other's functions. Several of the Adobe plug-ins that ship with Acrobat export their own HFTs for other plug-ins to use. For example, the Forms plug-in exports an HFT containing pointers to functions that other plug-ins can use to traverse and/or manipulate form elements.

After all plug-ins have loaded, the viewer (normally Acrobat) will call the callback, if any, stored in the importReplaceAndRegisterCallback field. This callback is where you can import other plug-ins' HFTs or register for notifications. (There is no need to import Acrobat's own HFT, since that's done automatically for you.)

The initCallback should point to a user routine that performs any initializations that the plug-in needs done before it can function. For example, if the plug-in is going to install its own menu commands or toolbar buttons, this is where it shoudl be done.

The unloadCallback should point to a routine that handles any memory-freeing or other unload-related operations your plug-in may have.

Compilation

Acrobat is a PPC-native product, so there is no need to compile for a 68K target unless you are compiling a Reader plug-in and want to include owners of older machines. (As mentioned earlier, there are significant limitations on what your plug-in can do if it is to use Reader as a host; and you must obtain a special key from Adobe.) You should plan on using a creator type of CARO and a filetype of XTND for your plug-in. You can place it, or an alias to it, in Acrobat's Plug-Ins folder after linking.

Access to Resources

Beginning with version 2.1, Acrobat opens a plug-in's resource fork with read-only permissions. So don't plan on using your resource fork for persistent storage. What's more, to use resources from your resource fork safely, you should plan on using the functions described in SafeResources.h. That's because plug-ins cannot assume that their resources are located at the top of the resource chain. Calls like GetResource() can fail unexpectedly unless plug-in resources are explicitly moved to the top of the chain. The resource calls in SafeResources.h accomplish this. The functions described there are safe replacements for all Toolbox routines that call, or can call, GetResource() because they move plug-in resources to the top of the chain before each call, then restore the resource chain to its original configuration after the call is finished.

See Acrobat API Development (Tech Note No. 5167) for more information.

Example Plug-In: AddWatermark

The example-code CW Pro 5 project for this article (available at ftp://www.mactech.com) illustrates many of the programming issues described in this article. The compiled plug-in, named AddWatermark, adds a light-gray text string (in 72-pt. Times-Roman) at a 45-degree angle across the current page, in response to an Acrobat menu command. For example purposes, I decided to use a text string of "Rough Draft." An example of the plug-in's output is shown in Figure 3.


Figure 3. AddWatermark applied to a page. (Note the words "Rough Draft" in gray.)

The code for the plug-in comprises 260 lines of straight procedural C (in just five functions) in a file named AddWatermark.c. To compile the plug-in, you will need the Adobe SDK files PIMain.c and SafeResources.c in addition to AddWatermark.c. The project is compiled as a Shared Library. There are no resource (.rsrc) files in the project.

Code for the plug-in's handshake routine is shown in Listing 1. This is where we fill out various fields of the PIHandshakeData_V0200 structure so that the host will know who we are and what services we need. Note that we use a fictitious "vendor prefix" of PLUG on the plug-in's name. (This will not show up in the menu bar.) We do assign callbacks to two fields, but note that we don't simply stuff function pointers into the relevant fields. Rather, we use the API method ASCallbackCreateProto() to create properly formatted function pointers to our MyInit() and MyUnload() procedures.

Listing 1: PIHandshake()

PIHandshake()
This is the plug-in's handshake routine. See article for details.

extern ACCB1 ASBool ACCB2 PIHandshake(Uns32 handshakeVersion, 		void *handshakeData)
{
	if (handshakeVersion == HANDSHAKE_V0200) {

			// Cast handshakeData to the appropriate type 
		PIHandshakeData_V0200 *hsData = 
			(PIHandshakeData_V0200 *)handshakeData;

			// Set the name you want to go by 
		hsData->extensionName = 
			ASAtomFromString("PLUG_AddWatermark"); 

			// If you export your own HFT, do so here 
		hsData->exportHFTsCallback = NULL;
		
			// If you import HFTs, replace functionality, 
			// or want to register for notifications before
			// the user has a chance to do anything, do so here.
		hsData->importReplaceAndRegisterCallback = NULL;
		
			// Perform your plug-in's initialization here 
		hsData->initCallback = 
			ASCallbackCreateProto(PIInitProcType, MyInit);
	
			// Free memory or save state on "quit" here 
		hsData->unloadCallback = 
			ASCallbackCreateProto(PIUnloadProcType, MyUnload);

			// Done 
		return true;
	
	}	 // Each time the handshake version changes, add new "else if" branch here
	
	return false;
}



Code for MyInit() and MyUnload() can be seen in Listing 2. The real purpose of MyInit() is to set up a custom menu command for the plug-in. Notice that to do this we call a method named AVMenubarAcquireMenuByName(). Methods that have 'Acquire' in their names increment a reference count for the object returned. It's important that we later decrement the reference count by calling the appropriate Release method on the object. We do this at the end of MyInit(). Since we created a new menu item (storing it in the global variable AddPluginMenuItem) in this routine, we later have to release it as well. We do that in MyUnload().

Listing 2: MyInit() and MyUnload()

MyInit() and MyUnload()

// This is our initialization callback routine. (It is optional.)
// If the plug-in can't initialize for one reason or another, return
// false. Otherwise return true.

// Here, we set up our plug-in's own menu item in the Extensions menu.

static ACCB1 ASBool ACCB2 MyInit(void)
{	
	AVMenubar bar = AVAppGetMenubar();
	AVMenu extensionsMenu = 
		AVMenubarAcquireMenuByName(bar, "Extensions");
	
	AddPluginMenuItem = 
		AVMenuItemNew("AddWatermark", 
			"PLUG:AddWatermark", NULL, true,
			NO_SHORTCUT, 0, NULL, gExtensionID);
		
	AVMenuItemSetExecuteProc(AddPluginMenuItem, 
	  ASCallbackCreateProto(AVExecuteProc, 
				AddWatermarkCommand), 
	  		NULL);
	  
	AVMenuItemSetComputeEnabledProc(AddPluginMenuItem, 
	 	ASCallbackCreateProto( AVComputeEnabledProc, 
			AddWatermarkEnabled), 
	 		(void *)pdPermEdit);
	
	AVMenuAddMenuItem(extensionsMenu, AddPluginMenuItem, 0);
	
		// We did an Acquire (above), so now Release:
	AVMenuRelease(extensionsMenu);

	return true;
}

static ACCB1 ASBool ACCB2 MyUnload(void)
{
		// Remove and release any menus or menu items we created
	if (AddPluginMenuItem) {
			AVMenuItemRemove(AddPluginMenuItem);
			AVMenuItemRelease(AddPluginMenuItem);
		}
	return true;
} 



The API routine AVMenuItemSetComputeEnabledProc() lets us specify a callback for determining whether our plug-in's menu command should be "greyed out" under conditions when it would not make sense to let the user invoke the plug-in. We use this routine to install our custom function AddWatermarkEnabled(), shown in Listing 3. The AddWatermarkEnabled() function checks to see if a document is currently being viewed and if so, whether the document is edit-enabled. (Some PDF documents are set up so that text cannot be selected or altered.) If there is no doc open, or the current doc is not edit-enabled, then it doesn't make sense to let the user try to use the plug-in for anything. Hence, we tell Acrobat to disable (grey out) the menu item.

Listing 3: AddWatermarkEnabled()

AddWatermarkEnabled()
This routine just determines whether or not our menu command should be enabled 
at a given instant.

static ACCB1 ASBool ACCB2 AddWatermarkEnabled(void *permRequired)
{
	AVDoc avDoc = AVAppGetActiveDoc();
	PDPerms docPerms = 
		PDDocGetPermissions(AVDocGetPDDoc(avDoc));
	
	if (!avDoc)
		return false;
	
	return ((PDPerms)permRequired & docPerms);
}

The main action (creating the watermark) takes place in AddWatermarkCommand(), shown in Listing 4. You'll notice that the entire body of the function is wrapped in exception-handler macros. We begin by fetching various doc and page objects, but since we use "get" (rather than "acquire") methods to obtain them, no reference counts are incremented and we don't need to Release those objects later. We do create or acquire other kinds of objects in this function, however, that do need to be released later. See if you can pick out what they are.

The font we choose for our text (Times-Roman) happens to be one of the base-14 printer fonts that is guaranteed to be available on every machine that has Acrobat. We create the relevant font object for it so we can add that to the PDEText object that will comprise our watermark. That text object also needs some graphics state info and a default transform matrix. (See my articles in the Sept. 1999 and March 2000 MacTech for more information on transform matrices in PDF.) The graphics-state information has to include a color-space designation and an ink color, as well as a miter limit, etc. (In truth, some of these values will be defaulted to reasonable values if we leave them out.) We're using a greyscale color space and an ink color of 0.875 (12.5% grey), specified as a Fixed number.

Note that all of the numbers in the transform matrix must be supplied as Fixed numbers. Since we want the text in our watermark to be rotated 45 degrees counterclockwise, we have to factor the sine and cosine of 45 degrees into the first four positions of the matrix. (The first and fourth numbers have to be premultiplied by the cosine of the angle of rotation; the second number has to incorporate the sine of the rotation angle; and the third number has to incorporate the negated sine of the angle.) The text will begin drawing at a point one quarter of the way across the page horizontally and 50% of the way up the page vertically. The fifth and sixth coefficients of the transform matrix accomplish this translation for us.

Listing 4: AddWatermarkCommand()

AddWatermarkCommand()
static ACCB1 void ACCB2 AddWatermarkCommand(void *data)
{
	ASInt32 num;
	ASBool b;
	char buf[256];
	AVDoc avDoc;					// Viewer's doc object
	PDPage pdPage;				// reference to an editable page object
	ASFixedRect cropBox;		// doc's crop box
	AVPageView pgView;			// current page in viewer
	PDEContent pdeContent;	// container for page content 
	PDEFont pdeFont;				// reference to a font used on a page 
	PDEFontAttrs pdeFontAttrs;	// font attributes  
	PDEText pdeText;				// container for text 
	FixedMatrix textMatrix;			// transform matrix for text 
	PDEGraphicState graphicsState;	// graphic state to apply to operation 
	PDEElement pdeElement;			// element of page content  
	FixedMatrix elemMatrix;			// element matrix   
	PDEColorSpace pdeColorSpace;	// ColorSpace 
	ASInt32 i,j, numElems, numRuns; // for enumeration of elements
	

DURING
  		// Get the currently displayed page and its cropbox 
	avDoc = AVAppGetActiveDoc();
	pgView = AVDocGetPageView (avDoc);
	pdPage = AVPageViewGetPage(pgView);
	PDPageGetCropBox (pdPage, &cropBox);
		
		// Create Font object 
	memset(&pdeFontAttrs, 0, sizeof(pdeFontAttrs));
	pdeFontAttrs.name = ASAtomFromString("Times-Roman");
	pdeFontAttrs.type = ASAtomFromString("Type1");

	pdeFont = PDEFontCreate(&pdeFontAttrs, 
					sizeof(pdeFontAttrs), 
					0, 
					255, 
					0, 
					0, ASAtomNull,
					0, 0, 0, 0); 

	// The following code sets up the default Graphics State. 	 
	pdeColorSpace = PDEColorSpaceCreateFromName(ASAtomFromString("DeviceGray"));

	memset(&graphicsState, 0, sizeof(PDEGraphicState));
	graphicsState.strokeColorSpec.space = 
		graphicsState.fillColorSpec.space = pdeColorSpace;
	graphicsState.fillColorSpec.value.color[0] = fixedSevenEighths;
	graphicsState.miterLimit = fixedTen;
	graphicsState.flatness = fixedOne;
	graphicsState.lineWidth = fixedOne;	

	// Fill out the text matrix, which determines the point size of the font	 
	memset(&textMatrix, 0, sizeof(textMatrix)); // clear it 
	textMatrix.a = FloatToFixed(.707 * WATERMARK_PTSIZE);
	textMatrix.b = textMatrix.a; // set font width and height 
	textMatrix.c = FloatToFixed(-.707 * WATERMARK_PTSIZE);
	textMatrix.d = textMatrix.a; 	    
	textMatrix.h = cropBox.right / 4;  // x coord of start point
	textMatrix.v = cropBox.top / 2;   // y coord of start point
	
	// create new text run 
	pdeText = PDETextCreate();       
	PDETextAdd(pdeText,      // text container to add to 
		kPDETextRun,       // kPDETextRun, kPDETextChar 
		0,            // index 
		(Uns8 *)WATERMARK_STR,  // text to add  
		strlen(WATERMARK_STR),  // length of text 
		pdeFont,         // font to apply to text 
		&graphicsState, sizeof(graphicsState), // graphic state
		NULL, 0,         // text state and size of structure
		&textMatrix,       // transformation matrix for text 
		NULL);          // stroke matrix 

	// Acquire the page contents as a PDEContent object
 pdeContent = PDPageAcquirePDEContent(pdPage, gExtensionID);
  
	// insert text into page content 
	PDEContentAddElem(pdeContent, kPDEBeforeFirst, 
																	(PDEElement) pdeText);

	// Set the PDEContent for the page 
	b = PDPageSetPDEContent(pdPage, gExtensionID);
	
	// remember to release all objects that were created 	
	num = PDPageReleasePDEContent(pdPage, gExtensionID);

	// Remember to release the objects we acquired or created 
	PDERelease((PDEObject) pdeText);
	PDERelease((PDEObject) pdeColorSpace);
	PDERelease((PDEObject) pdeFont);

	// Announce to the world that we changed the contents 
	// so the viewer will refresh the page
	PDPageNotifyContentsDidChange (pdPage);

// Now some exception-handler stuff, in case of failure
HANDLER

	AVAlertNote(ASGetErrorString(ERRORCODE, buf, 255));
	
	if (pdeText)
		PDERelease((PDEObject) pdeText);
	if (pdeColorSpace)
		PDERelease((PDEObject) pdeColorSpace);
	if (pdeFont)
		PDERelease((PDEObject) pdeFont);

	num = 
		PDPageReleasePDEContent(pdPage, gExtensionID);
END_HANDLER

}

The rest is pretty straightforward. We acquire the page contents as a PDEContent object, add our text element to the contents list (specifying kPDEBeforeFirst, to be sure the watermark text is the first thing drawn on the page, making it draw "in back of" all other page elements), set the page's content to the new content list, then release the page contents object and notify the host app that the page has changed (so it will be redrawn).

Because PDF's graphics model uses opaque ink (at least in version 4.0 of Acrobat), objects draw on top of each other in the order they're laid down. Fortunately, the API lets us add new objects to either end of the "draw list." Thus, we can draw our watermark text either on top of all other items (partially obscuring other page elements) or underneath all other items (in which case the watermark itself may be partially obscured). The disadvantage of drawing the watermark first is that if the page is particularly "busy" or has some very large graphics, the watermark may be mostly obscured. One workaround for this is to use a watermark that forms a tiled pattern across the page, with small individual elements. (PDF does allow tiled vector graphics, incidentally.) Another tactic is simply to draw the watermark on top of other page elements, but make it less conspicuous by (for example) drawing outlined text rather than solid-filled text.

There are many ways in which the AddWatermark plug-in could be improved. Now that you have the basic code for adding text to a page, maybe you can implement some of those enhancements yourself. (If you do, please send me a copy.)

Conclusion

PDF is a complex and sophisticated document format. Not surprisingly, the Acrobat plug-in API is correspondingly complex and sophisticated. In fact, with 1,100 methods (covered in a reference document that's almost 3,000 pages long), it is arguably Adobe's most elaborate API — outdoing even the Photoshop plug-in API for sheer intricacy. One well-known Acrobat plug-in developer routinely tells clients that to accomplish any non-trivial task using the Acrobat API can take an experienced programmer (i.e., one who is experienced at C/C++ development, but new to the Acrobat API) a minimum of six months. That may be overstating it a bit. But you can probably appreciate the fact that Acrobat plug-in development is not something you become adept at in a weekend. The API is rich to the point of being unwieldy at times (and suffers from subtle architectural shortcomings, which only become obvious after you've spent many hours working with it). But although the Acrobat API lacks the elegance of, say, the After Effects API and fails, for the most part, to make a complex document format (PDF) easy to understand and work with, it does expose an amazingly diverse set of capabilities to programmers who might need to extend Acrobat's already impressive arsenal of PDF editing and annotation tools. And that, in and of itself, is saying quite a lot.


Kas Thomas <kt@acroforms.com> has been programming in C and assembly on the Mac for more than ten years and consults for Adobe Systems and others on PDF-related development issues. You can read his work at Acroforms.com, PlanetPDF.com, and MightyWords.com.

 
AAPL
$524.94
Apple Inc.
+5.93
MSFT
$40.01
Microsoft Corpora
-0.39
GOOG
$536.10
Google Inc.
-20.44

MacTech Search:
Community Search:

Software Updates via MacUpdate

Mac DVDRipper Pro 4.1.7 - Copy, backup,...
Mac DVDRipper Pro is the DVD backup solution that lets you protect your DVDs from scratches, save your batteries by reading your movies from your hard disk, manage your collection with just a few... Read more
PDFpenPro 6.2 - Advanced PDF toolkit for...
PDFpenPro allows users to edit PDF's easily. Add text, images and signatures. Fill out PDF forms. Merge or split PDF documents. Reorder and delete pages. Even correct text and edit graphics! Create... Read more
PDFpen 6.2 - Edit and annotate PDFs with...
PDFpen allows users to easily edit PDF's. Add text, images and signatures. Fill out PDF forms. Merge or split PDF documents. Reorder and delete pages. Even correct text and edit graphics! Features... Read more
Monolingual 1.5.9 - Remove unwanted OS X...
Monolingual is a program for removing unnecesary language resources from OS X, in order to reclaim several hundred megabytes of disk space. It requires a 64-bit capable Intel-based Mac and at least... Read more
Maya 2015 - Professional 3D modeling and...
Maya is an award-winning software and powerful, integrated 3D modeling, animation, visual effects, and rendering solution. Because Maya is based on an open architecture, all your work can be scripted... Read more
Starcraft II: Wings of Liberty 1.1.1.180...
Download the patch by launching the Starcraft II game and downloading it through the Battle.net connection within the app. Starcraft II: Wings of Liberty is a strategy game played in real-time. You... Read more
Sibelius 7.5.0 - Music notation solution...
Sibelius is the world's best-selling music notation software for Mac. It is as intuitive to use as a pen, yet so powerful that it does most things in less than the blink of an eye. The demo includes... Read more
Typinator 5.9 - Speedy and reliable text...
Typinator turbo-charges your typing productivity. Type a little. Typinator does the rest. We've all faced projects that require repetitive typing tasks. With Typinator, you can store commonly used... Read more
MYStuff Pro 2.0.16 - Create inventories...
MYStuff Pro is the most flexible way to create detail-rich inventories for your home or small business. Add items to MYStuff by dragging and dropping existing information, uploading new images, or... Read more
TurboTax 2013.r17.002 - Manage your 2013...
TurboTax guides you through your tax return step by step, does all the calculations, and checks your return for errors and overlooked deductions. It lets you file your return electronically to get... Read more

Latest Forum Discussions

See All

Trials Frontier Review
Trials Frontier Review By Carter Dotson on April 18th, 2014 Our Rating: :: A ROUGH LANDINGUniversal App - Designed for iPhone and iPad Trials Frontier finally brings the famed stunt racing franchise to mobile, but how much does its... | Read more »
Evernote Business Notebook by Moleskin I...
Evernote Business Notebook by Moleskin Introduced – Support Available in Evernote for iOS Posted by Tre Lawrence on April 18th, 2014 [ permalink ] | Read more »
Sparkle Unleashed Review
Sparkle Unleashed Review By Jennifer Allen on April 18th, 2014 Our Rating: :: CLASSY MARBLE FLINGINGUniversal App - Designed for iPhone and iPad It’s a concept we’ve seen before, but Sparkle Unleashed is a solidly enjoyable orb... | Read more »
Tilt to Live 2: Redonkulus Receives its...
Tilt to Live 2: Redonkulus Receives its First Update – Gets New Brimstone Pinball DLC Posted by Rob Rich on April 18th, 2014 [ permalink ] | Read more »
Union Review
Union Review By Jennifer Allen on April 18th, 2014 Our Rating: :: LAYERED RESULTSUniversal App - Designed for iPhone and iPad Create some stylish imagery with this layers focused photography app.   | Read more »
Where's My Water? Featuring XYY (G...
Where's My Water? Featuring XYY 1.0 Device: iOS Universal Category: Games Price: $.99, Version: 1.0 (iTunes) Description: iPod Touch 4th Gen, iPhone 3GS or iPad 1 are not compatible with this app. | Read more »
The Amazing Spider-Man 2 (Games)
The Amazing Spider-Man 2 1.0.0 Device: iOS Universal Category: Games Price: $4.99, Version: 1.0.0 (iTunes) Description: *IMPORTANT* The Amazing Spider-Man 2 requires an iPad 2 (or newer), iPhone 4s (or newer), or iPod touch 5th... | Read more »
Living Planet - Tiny Planet Videos and P...
Living Planet - Tiny Planet Videos and Photos 1.0 Device: iOS Universal Category: Photography Price: $.99, Version: 1.0 (iTunes) Description: 50% OFF LAUNCH SPECIAL! BUY NOW BEFORE THE PRICE GOES UP... | Read more »
Livescribe 3 Smartpen Review
Made by: Livescribe Price: $149.99 for Livescribe 3 Hardware/iOS Integration Rating: 4.5 out of 5 stars Usability Rating: 4 out of 5 stars Reuse Value Rating: 4.75 out of 5 stars Build Quality Rating: 4.5 out of 5 stars Overall Rating: 4.44 out of... | Read more »
Unpossible Review
Unpossible Review By Carter Dotson on April 17th, 2014 Our Rating: :: RALPH WIGGUM APPROVESUniversal App - Designed for iPhone and iPad Unpossible is much better than its English!   | Read more »

Price Scanner via MacPrices.net

iMacs on sale for up to $160 off MSRP this we...
Best Buy has iMacs on sale for up to $160 off MSRP for a limited time. Choose free home shipping or free instant local store pickup (if available). Prices are valid for online orders only, in-store... Read more
iPad Airs on sale this weekend for up to $100...
Best Buy has WiFi iPad Airs on sale for $50 off MSRP and WiFi + Cellular iPad Airs on sale for $100 off MSRP on their online store for a limited time, with prices now starting at $449. Choose free... Read more
Apple restocks refurbished Mac minis starting...
The Apple Store has restocked Apple Certified Refurbished Mac minis for up to $150 off the cost of new models. Apple’s one-year warranty is included with each mini, and shipping is free: - 2.5GHz Mac... Read more
Hyundai Brings Apple CarPlay To The 2015 Sona...
Hyundai Motor America has announced it will bring Apple CarPlay functionality to the 2015 Sonata. CarPlay is pitched as a smarter, safer and easier way to use iPhone in the car and gives iPhone users... Read more
Updated iPads Coming Sooner Than We Had Thoug...
MacRumors, cites KGI securities analyst Ming Chi Kuo, well-respected as an Apple product prognisticator, saying that Apple will introduce an upgraded iPad Air and iPad mini in 2014/Q3, meaning the... Read more
Toshiba Unveils New High And Low End Laptop M...
Toshiba has announced new laptop models covering both the high-end and low-end of the notebook computer spectrum. Toshiba 4K Ultra HD Laptop Toshiba’s new Satellite P55t features one of the world’s... Read more
Save up to $270 with Apple refurbished 13-inc...
The Apple Store has Apple Certified Refurbished October 2013 13″ Retina MacBook Pros available starting at $1099, with models up to $270 off MSRP. Apple’s one-year warranty is standard, and shipping... Read more
Apple now offering refurbished iPad mini with...
The Apple Store has Certified Refurbished 2nd generation iPad minis with Retina Displays now available starting at $339. Apple’s one-year warranty is included with each model, and shipping is free.... Read more
Microsoft Blinks – Drops Microsoft Office 365...
Microsoft has dropped the annual subscription fee for Microsoft Office 365 Personal – which is needed in order to create and edit documents in Microsoft Office for iPad. However, Apple’s iOS and OS X... Read more
New AVG Vault Apps for iOS and Android Help K...
AVG Technologies N.V. an online security company for 177 million active users, has announced the launch of its latest mobile application, AVG Vault. The free app introduces an innovative user... Read more

Jobs Board

*Apple* Solutions Consultant (ASC) - Apple (...
**Job Summary** The ASC is an Apple employee who serves as an Apple brand ambassador and influencer in a Reseller's store. The ASC's role is to grow Apple Read more
*Apple* Retail - Manager - Holyoke - Apple I...
Job Summary Keeping an Apple Store thriving requires a diverse set of leadership skills, and as a Manager, you’re a master of them all. In the store’s fast-paced, Read more
*Apple* Retail - Manager - Apple (United Sta...
Job SummaryKeeping an Apple Store thriving requires a diverse set of leadership skills, and as a Manager, you're a master of them all. In the store's fast-paced, dynamic Read more
*Apple* Solutions Consultant (ASC) - Apple (...
**Job Summary** The ASC is an Apple employee who serves as an Apple brand ambassador and influencer in a Reseller's store. The ASC's role is to grow Apple Read more
*Apple* Retail - Market Leader - Cincinnati...
…challenges of developing individuals, building teams, and affecting growth across Apple Stores. You demonstrate successful leadership ability - focusing on excellence Read more
All contents are Copyright 1984-2011 by Xplain Corporation. All rights reserved. Theme designed by Icreon.