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 The SDK is also available on CD-ROM to members of the Adobe Solutions Network Developer Program. (See 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.


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.


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:


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


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


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.


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 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()

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 = 

			// 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 = 
			"PLUG:AddWatermark", NULL, true,
			NO_SHORTCUT, 0, NULL, gExtensionID);
	 	ASCallbackCreateProto( AVComputeEnabledProc, 
	 		(void *)pdPermEdit);
	AVMenuAddMenuItem(extensionsMenu, AddPluginMenuItem, 0);
		// We did an Acquire (above), so now Release:

	return true;

static ACCB1 ASBool ACCB2 MyUnload(void)
		// Remove and release any menus or menu items we created
	if (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()

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 = 
	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()

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

  		// 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)); = ASAtomFromString("Times-Roman");
	pdeFontAttrs.type = ASAtomFromString("Type1");

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

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

	memset(&graphicsState, 0, sizeof(PDEGraphicState)); = = 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 = / 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

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

	num = 
		PDPageReleasePDEContent(pdPage, gExtensionID);


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.)


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 <> 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,, and

Apple Inc.
Microsoft Corpora
Google Inc.

MacTech Search:
Community Search:

Software Updates via MacUpdate

Audio Hijack Pro 2.11.3 - Record and enh...
Audio Hijack Pro drastically changes the way you use audio on your computer, giving you the freedom to listen to audio when you want and how you want. Record and enhance any audio with Audio Hijack... Read more
Airfoil 4.8.9 - Send audio from any app...
Airfoil allows you to send any audio to AirPort Express units, Apple TVs, and even other Macs and PCs, all in sync! It's your audio - everywhere. With Airfoil you can take audio from any... Read more
WhatRoute 1.13.0 - Geographically trace...
WhatRoute is designed to find the names of all the routers an IP packet passes through on its way from your Mac to a destination host. It also measures the round-trip time from your Mac to the... Read more
Chromium 37.0.2062.122 - Fast and stable...
Chromium is an open-source browser project that aims to build a safer, faster, and more stable way for all Internet users to experience the web. FreeSMUG-Free OpenSource Mac User Group build is... Read more
Attachment Tamer 3.1.14b9 - Take control...
Attachment Tamer gives you control over attachment handling in Apple Mail. It fixes the most annoying Apple Mail flaws, ensures compatibility with other email software, and allows you to set up how... Read more
Duplicate Annihilator 5.0 - Find and del...
Duplicate Annihilator takes on the time-consuming task of comparing the images in your iPhoto library using effective algorithms to make sure that no duplicate escapes. Duplicate Annihilator detects... Read more
jAlbum Pro 12.2 - Organize your digital...
jAlbum Pro has all the features you love in jAlbum, but comes with a commercial license. With jAlbum, you can create gorgeous custom photo galleries for the Web without writing a line of code!... Read more
jAlbum 12.2 - Create custom photo galler...
With jAlbum, you can create gorgeous custom photo galleries for the Web without writing a line of code! Beginner-friendly, with pro results Simply drag and drop photos into groups, choose a design... Read more
Quicken 2015 2.0.4 - Complete personal f...
Quicken 2015 helps you manage all your personal finances in one place, so you can see where you're spending and where you can save. Quicken automatically categorizes your financial transactions,... Read more
iMazing 1.0 - Complete iOS device manage...
iMazing (formerly DiskAid) is the ultimate iOS device manager with capabilities far beyond what iTunes offers. With iMazing and your iOS device (iPhone, iPad, or iPod), you can: Copy music to and... Read more

Latest Forum Discussions

See All

View Source – HTML, JavaScript and CSS...
View Source – HTML, JavaScript and CSS 1.0 Device: iOS Universal Category: Utilities Price: $.99, Version: 1.0 (iTunes) Description: View Source is an app plus an iOS 8 Safari extension that makes it easy to do one key web developer... | Read more »
Avenged Sevenfold’s Hail To The King: De...
Avenged Sevenfold’s Hail To The King: Deathbat is Coming to iOS on October 16th Posted by Jessica Fisher on September 19th, 2014 [ permalink ] Just in time for Halloween, on October 16 Avenged Sevenfold will be launching | Read more »
Talisman Has Gone Universal – Can Now be...
Talisman Has Gone Universal – Can Now be Played on the iPhone Posted by Jessica Fisher on September 19th, 2014 [ permalink ] | Read more »
Tap Army Review
Tap Army Review By Jennifer Allen on September 19th, 2014 Our Rating: :: SHOOT EM ALLUniversal App - Designed for iPhone and iPad Mindless but fun, Tap Army is a lane-based shooter that should help you relieve some stress.   | Read more »
Monsters! Volcanoes! Loot! Epic Island f...
Monsters! Volcanoes! Loot! | Read more »
Plunder Pirates: Tips, Tricks, Strategie...
Ahoy There, Seadogs: Interested in knowing our thoughts on all this plundering and pirating? Check out our Plunder Pirates Review! Have you just downloaded the rather enjoyable pirate-em-up Plunder Pirates and are in need of some assistance? Never... | Read more »
Goat Simulator Review
Goat Simulator Review By Lee Hamlet on September 19th, 2014 Our Rating: :: THE GRUFFEST OF BILLY GOATSUniversal App - Designed for iPhone and iPad Unleash chaos as a grumpy goat in this humorous but short-lived casual game.   | Read more »
A New and Improved Wunderlist is Here fo...
A New and Improved Wunderlist is Here for iOS 8 Posted by Jessica Fisher on September 19th, 2014 [ permalink ] Universal App - Designed for iPhone and iPad | Read more »
Evernote Update for iOS 8 Adds Web Clipp...
Evernote Update for iOS 8 Adds Web Clipping, Quick Notes, and More Posted by Ellis Spice on September 19th, 2014 [ permalink ] | Read more »
Apple Names Ultimate Productivity Bundl...
Apple Names Ultimate Productivity Bundle by Readdle as the Essential Bundle on the App Store Posted by Jessica Fisher on September 19th, 2014 [ permalink | Read more »

Price Scanner via

iFixIt Tears Down iPhone 6; Awards Respectabl...
iFixit notes that even the smaller 4.7″ iPhone 6 is a giant among iPhones; so big that Apple couldn’t fit it into the familiar iPhone form factor. In a welcome reversal of a recent trend to more or... Read more
Phone 6 Guide – Tips Book For Both iPhone 6...
iOS Guides has announced its latest eBook: iPhone 6 Guide. Brought to you by the expert team at iOS Guides, and written by best-selling technology author Tom Rudderham, iPhone 6 Guide is packed with... Read more
How to Upgrade iPhone iPad to iOS 8 without D...
PhoneClean, a iPhone cleaner utility offered by iMobie Inc., reveals a solution for upgrading iPhone and iPad to iOS 8 without deleting photos, apps, the new U2 album or anything. Thanks to more than... Read more
Inpaint 6 – Photo Retouching Tool Gets Faster...
TeoreX has announced Inpaint 6, a simple retouching tool for end users that helps remove scratches, watermarks, and timestamps as well as more complex objects like strangers, unwanted elements and... Read more
Worldwide PC Monitor Market Sees Growth in To...
Worldwide PC monitor shipments totaled 32.5 million units in the second quarter of 2014 (2Q14), a year-over-year decline of -2.9%, according to the International Data Corporation (IDC) Worldwide... Read more
Updated Price Trackers
We’ve updated our Mac Price Trackers with the latest information on prices, bundles, and availability on systems from Apple’s authorized internet/catalog resellers: - 15″ MacBook Pros - 13″ MacBook... Read more
Mac Pros available for up to $260 off MSRP
Adorama has Mac Pros on sale for up to $260 off MSRP. Shipping is free, and Adorama charges sales tax in NY & NJ only: - 4-core Mac Pro: $2839.99, $160 off MSRP - 6-core Mac Pro: $3739.99, $260... Read more
13-inch 2.6GHz/256GB Retina MacBook Pros avai...
B&H Photo has the 13″ 2.6GHz/256GB Retina MacBook Pro on sale for $1379 including free shipping plus NY sales tax only. Their price is $120 off MSRP. Read more
Previous-generation 15-inch 2.0GHz Retina Mac...
B&H Photo has leftover previous-generation 15″ 2.0GHz Retina MacBook Pros now available for $1599 including free shipping plus NY sales tax only. Their price is $400 off original MSRP. B&H... Read more
21″ 2.7GHz iMac available for $1179, save $12...
Adorama has 21″ 2.7GHz Hawell iMacs on sale for $1179.99 including free shipping. Their price is $120 off MSRP. NY and NJ sales tax only. Read more

Jobs Board

*Apple* Retail - Multiple Positions (US) - A...
Sales Specialist - Retail Customer Service and Sales Transform Apple Store visitors into loyal Apple customers. When customers enter the store, you're also the Read more
Position Opening at *Apple* - Apple (United...
**Job Summary** At the Apple Store, you connect business professionals and entrepreneurs with the tools they need in order to put Apple solutions to work in their Read more
Position Opening at *Apple* - Apple (United...
**Job Summary** The Apple Store is a retail environment like no other - uniquely focused on delivering amazing customer experiences. As an Expert, you introduce people Read more
Position Opening at *Apple* - Apple (United...
**Job Summary** As businesses discover the power of Apple computers and mobile devices, it's your job - as a Solutions Engineer - to show them how to introduce these Read more
Position Opening at *Apple* - Apple (United...
…Summary** As a Specialist, you help create the energy and excitement around Apple products, providing the right solutions and getting products into customers' hands. You Read more
All contents are Copyright 1984-2011 by Xplain Corporation. All rights reserved. Theme designed by Icreon.