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


Community Search:
MacTech Search:

Software Updates via MacUpdate

FileMaker Pro 19.4.2 - Quickly build cus...
FileMaker Pro is the tool you use to create a custom app. You also use FileMaker Pro to access your app on a computer. Start by importing data from a spreadsheet or using a built-in Starter app to... Read more
Adobe Illustrator 26.0.3 - Professional...
You can download Adobe Illustrator for Mac as a part of Creative Cloud for only $20.99/month. Adobe Illustrator for Mac is the vector graphics classics in the design industry. It is a digital... Read more
WhatRoute 2.4.9 - Geographically trace o...
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 router... Read more
Notion 2.0.20 - A unified workspace for...
Notion is the unified workspace for modern teams. Notion Features: Integration with Slack Documents Wikis Tasks Release notes were unavailable when this listing was updated. Download Now]]> Read more
Monterey Cache Cleaner 17.0.2 - Clear ca...
Monterey Cache Cleaner is an award-winning general-purpose tool for macOS X. MCC makes system maintenance simple with an easy point-and-click interface to many macOS X functions. Novice and expert... Read more
Firetask Pro 4.6.8 - Innovative task man...
Firetask Pro represents the next generation of easy-to-use, project-oriented task management apps. By combining David Allen's powerful Getting Things Done (GTD®) approach with classical task... Read more
Smultron 13.0.4 - Easy-to-use, powerful...
Smultron 13 is the text editor for all of us. Smultron is powerful and confident without being complicated. Its elegance and simplicity helps everyone being creative and to write and edit all sorts... Read more
Box Sync 4.0.8057 - Online synchronizati...
Box Sync gives you a hard-drive in the Cloud for online storage. Note: You must first sign up to use Box. What if the files you need are on your laptop -- but you're on the road with your iPhone? No... Read more
Audio Hijack 3.8.10 - Record and enhance...
Audio Hijack (was 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... Read more
Direct Mail 6.0.1 - Create and send grea...
Direct Mail is an easy-to-use, fully-featured email marketing app purpose-built for macOS. Create, send, and track great looking email campaigns that get results. Start your newsletter by selecting... Read more

Latest Forum Discussions

See All

Hopefully Not Jared’s Last Show – The To...
My suspicions from last week were correct, and after my two kids tested positive for Covid last week both my wife and I have now tested positive as well. It seems you just can’t escape this stuff lately. Thankfully the two little ones are pretty... | Read more »
TouchArcade Game of the Week: ‘Micro RPG...
I feel like idle games are one of those perfect fits for the mobile platform. Not that they replace more involved gaming experiences when you’re in the mood for that, but they do fit in alongside other types of games just fine as a “go to" when you... | Read more »
‘Phantom Blade: Executioners’ Closed Bet...
Phantom Blade: Executioners is holding a small-scale technical test that lets players get first dibs on the KungFuPunk action RPG. Offered to selected players only, S-Game’s first Closed Beta Test will provide players with limited edition in-game... | Read more »
New ‘Warhammer 40,000: Tacticus’ Video S...
Back in September Snowprint Studios, who you may know from their previous Legend of Solgard or Rivengard, announced that they’d partnered up with Games Workshop to put out a new tactical game in the Warhammer 40,000 universe titled Warhammer 40,000... | Read more »
SwitchArcade Round-Up: ‘Pokemon Legends:...
Hello gentle readers, and welcome to the SwitchArcade Round-Up for January 28th, 2022. We’ve got a bunch of new releases to look at today, with a few big hitters, a few mid-level diversions, and a healthy supply of compost. Since it’s Friday, we... | Read more »
Phantom Blade: Executioners, S-Game...
S-Game has kicked off its first Closed Beta Test for Phantom Blade: Executioners, inviting a selected few to get first dibs on the upcoming KungFuPunk action RPG on mobile. The CBT officially begins this January 28th, and beta testers will receive... | Read more »
‘Infinite Galaxy’ First Anniversary: Cel...
Cultivating a new generation of valiant commanders across 240 countries worldwide, Infinite Galaxy has quenched players’ thirst to explore the vastness of space – and there are only more intergalactic adventures to embark on from here on out. Camel... | Read more »
War and Order: How to brave the cold in...
War and Order's 6th-anniversary celebrations are underway, and all in good time too - this season not only brings about fabulous festivities, but it also lets players experience the harsh winter in an entirely new way. [Read more] | Read more »
‘Hidden Folks+’ Is This Week’s New Apple...
The original Hidden Folks from Adriaan de Jongh is an excellent hidden objects game featuring hand drawn visuals. It is an absolute joy to play, and it has now released on Apple Arcade in the form of Hidden Folks+ () as an App Store great. If you’... | Read more »
Mini Metro’s First Big Update of 2022 Ad...
Last year saw great updates for Dinosaur Polo Club’s Mini Metro ($3.99) which is also available on Apple Arcade as an App Store Great. | Read more »

Price Scanner via

Apple has clearance 2020 13″ MacBook Airs ava...
Apple has clearance, Certified Refurbished, 2020 13″ Intel-based MacBook Airs in stock today starting at only $719 and up to $370 off original MSRP. Each MacBook features a new outer case, comes with... Read more
The cheapest iPhones for sale today at Apple...
Apple has restocked Apple Certified Refurbished iPhone 8 models starting at only $359. Each refurbished iPhone comes with a fresh external case, standard Apple 1-year warranty, and free shipping.... Read more
14″ MacBook Pro with Apple M1 Max CPU now in...
Looking for a new 14″ MacBook Pro with an Apple M1 Max CPU? Stock is finally trickling into Apple resellers. B&H has Silver 14″ M1 Max MacBook Pros in stock today for $2899 including free 1-2 day... Read more
14″ MacBook Pros with Apple M1 Pro CPUs are i...
Amazon is reporting stock of 14″ MacBook Pros with M1 Pro CPUs today with a $50 discount. Shipping is free, and delivery is available by February 1st for most configurations. Be sure to make your... Read more
Apple has restocked 13″ M1 MacBook Pros for $...
Apple has restocked a full line of 13″ M1 MacBook Pros available Certified Refurbished, starting at only $1099 and up to $230 off original MSRP. These are the cheapest M1 MacBook Pros for sale today... Read more
Apple’s AirPods Max headphones are on sale fo...
Amazon has Silver, Blue, and Space Gray Apple AirPods Max headphones on sale today for $100 off MSRP. Shipping is free, and all models are in stock today. Their price is the lowest currently... Read more
Open a new line of service at Verizon and get...
Verizon is giving away 64GB Apple iPhone 12 minis or your choice of an iPhone 11 to customers who choose one of these phones and open a new line of service. Offer is available online only, and no... Read more
Open-box 13″ M1 MacBook Airs now available st...
QuickShip Electronics has open-box return 13″ M1 MacBook Airs in stock and on sale for $200-$400 off MSRP on their eBay store right now with free express delivery. According to QuickShip, “The item... Read more
Verizon’s 2022 iPad promo: $100-$310 off any...
Verizon has cellular-capable iPads on sale for $100-$310 off MSRP when purchased with an Unlimited service plan. Sale price is applied to your account monthly over a 24 or 30 month period, depending... Read more
Sunday Sale: Apple AirPods are on sale for up...
Amazon has Apple AirPods on sale for $10-$100 off MSRP today, depending on the model. All are in stock today with free delivery: – AirPods Max headphones (Blue): $449 $100 off MSRP – AirPods Max... Read more

Jobs Board

Registered Nurse (RN) Employee Health PSJH -...
…is calling for a Registered Nurse (RN) Employee Health PSJH to our location in Apple Valley, CA.** We are seeking a Registered Nurse (RN) Employee Health PSJH to be Read more
Systems Administrator - Pearson (United State...
…and troubleshoot Windows operating systems (workstation and server), laptop computers, Apple iPads, Chromebooks and printers** + **Administer and troubleshoot all Read more
IT Assistant Level 1- IT Desktop Support Anal...
…providing tier-1 or better IT help desk support in a large Windows and Apple environment * Experience using IT Service Desk Management Software * Knowledge of IT Read more
Human Resources Business Partner PSJH - Provi...
…**is calling a** **Human Resources Business Partner, PSJH** **to our location in Apple Valley, CA.** **Applicants that meet qualifications will receive a text with Read more
Manager Community Health Investment Programs...
…is calling a Manager Community Health Investment Programs PSJH to our location in Apple Valley, CA.** **Qualified candidates will be invited to do a self-paced video Read more
All contents are Copyright 1984-2011 by Xplain Corporation. All rights reserved. Theme designed by Icreon.