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

Opera 47.0.2631.83 - High-performance We...
Opera is a fast and secure browser trusted by millions of users. With the intuitive interface, Speed Dial and visual bookmarks for organizing favorite sites, news feature with fresh, relevant content... Read more
Vivaldi 1.12.955.36 - An advanced browse...
Vivaldi is a browser for our friends. In 1994, two programmers started working on a web browser. Our idea was to make a really fast browser, capable of running on limited hardware, keeping in mind... Read more
Apple Configurator 2.5 - Configure and d...
Apple Configurator makes it easy to deploy iPad, iPhone, iPod touch, and Apple TV devices in your school or business. Use Apple Configurator to quickly configure large numbers of devices connected to... Read more
Smultron 10.0 - Easy-to-use, powerful te...
Smultron 10 is an elegant and powerful text editor that is easy to use. You can use Smultron 10 to create or edit any text document. Everything from a web page, a note or a script to any single piece... Read more
BetterTouchTool 2.304 - Customize multi-...
BetterTouchTool adds many new, fully customizable gestures to the Magic Mouse, Multi-Touch MacBook trackpad, and Magic Trackpad. These gestures are customizable: Magic Mouse: Pinch in / out (zoom... Read more
Drive Genius 5.0.5 - $49.50 (50% off)
Drive Genius features a comprehensive Malware Scan. Automate your malware protection. Protect your investment from any threat. The Malware Scan is part of the automated DrivePulse utility. DrivePulse... Read more
Apple Keynote 7.3 - Apple's present...
Easily create gorgeous presentations with the all-new Keynote, featuring powerful yet easy-to-use tools and dazzling effects that will make you a very hard act to follow. The Theme Chooser lets you... Read more
Apple Numbers 4.3 - Apple's spreads...
With Apple Numbers, sophisticated spreadsheets are just the start. The whole sheet is your canvas. Just add dramatic interactive charts, tables, and images that paint a revealing picture of your data... Read more
Apple Pages 6.3 - Apple's word proc...
Apple Pages is a powerful word processor that gives you everything you need to create documents that look beautiful. And read beautifully. It lets you work seamlessly between Mac and iOS devices, and... Read more
Smultron 9.4.2 - Easy-to-use, powerful t...
Smultron 9 is an elegant and powerful text editor that is easy to use. Use it to create or edit any text document. Everything from a web page, a note or a script to any single piece of text or code.... Read more

The Witness (Games)
The Witness 1.0 Device: iOS Universal Category: Games Price: $9.99, Version: 1.0 (iTunes) Description: You wake up, alone, on a strange island full of puzzles that will challenge and surprise you. You don't remember who you are, and... | Read more »
Egg, Inc. guide - how to build your gold...
Egg, Inc.'s been around for some time now, but don't you believe for one second that this quirky clicker game has gone out of style. The game keeps popping up on Reddit and other community forums thanks to the outlandish gameplay (plus, the... | Read more »
The best deals on the App Store this wee...
Good news, everyone! Your favorite day of the week has arrived at last -- it's discount roundup day! This fine Wednesday evening we're gathering up the hottest deals on the App Store. We've got action platformers, we've got puzzle games, we've got... | Read more »
Morphite (Games)
Morphite 1.08 Device: iOS Universal Category: Games Price: $7.99, Version: 1.08 (iTunes) Description: | Read more »
Fitness AR (Healthcare & Fitness)
Fitness AR 0.1.1 Device: iOS Universal Category: Healthcare & Fitness Price: $2.99, Version: 0.1.1 (iTunes) Description: Explore your Strava bike rides and runs in augmented reality. A beautiful 3D terrain map, powered by Mapbox... | Read more »
ARise (Games)
ARise 1.0 Device: iOS Universal Category: Games Price: $2.99, Version: 1.0 (iTunes) Description: **ARKit only Puzzle game****Chapter 1 available now - More worlds coming soon!** ARise is an experience about perspective. Using the AR... | Read more »
The best games to play while you wait fo...
SteamWorld Dig 2 is out this week on PC and Switch, and people are understandably excited. This clever series by Image and Form combines our favorite metroidvania mechanics with an esquisite universe, excellent storytelling, and true wit. While... | Read more »
Drag'n'Boom beginner's gu...
Have you ever wanted to burn and pillage a village as a bloodthirsty dragon? If you answered yes to that question, Drag'n'Boom offers you the perfect chance to do so, casting you as an adorable little dragon that wants to set humankind aflame. It... | Read more »
Thimbleweed Park (Games)
Thimbleweed Park 1.0.0 Device: iOS Universal Category: Games Price: $9.99, Version: 1.0.0 (iTunes) Description: A brand new adventure game from Ron Gilbert and Gary Winnick, creators of the classics Monkey Island and Maniac Mansion!... | Read more »
The best simulation games on mobile
There's nothing like a good sim -- from the seemingly ridiculous to the incredibly mundane, you can be there's a simulation game out there for your every whim. [Read more] | Read more »

Price Scanner via

Apple restocks Certified Refurbished 13-inch...
Apple has Certified Refurbished 2015 13″ MacBook Airs available starting at $719 and 2016 models available starting at $809. An Apple one-year warranty is included with each MacBook, and shipping is... Read more
Is iPhone X Really The Future Of The Smartpho...
Should iPhone X even be called a telephone? It does of course support telephony and texting, but its main feature set is oriented to other things. It is also debatable whether it makes any rational... Read more
OtterBox Announces Full Case Lineup for iPhon...
Apple revolutionized the smartphone industry 10 years ago with the original iPhone, and OtterBox has set the standard of protection from the very beginning by protecting every generation of iPhone.... Read more
LifeProof Introduces What’s NEXT Cases for iP...
LifeProof built its reputation on sleek, ultra-protective iPhone cases. From 360-degree coverage to the first screenless waterproof case, the protection pioneer has always pushed the limits.... Read more
Apple Refurbished 2016 15-inch MacBook Pros a...
Apple has Certified Refurbished 2016 15″ Touch Bar MacBook Pros available starting at $1949. An Apple one-year warranty is included with each model, and shipping is free: – 15″ 2.7GHz Touch Bar Space... Read more
Wednesday deal: 15-inch MacBook Pros for up t...
B&H Photo has 2017 15″ MacBook Pros on sale for $150-$200 off MSRP. Shipping is free, and B&H charges sales tax in NY & NJ only: – 15″ 2.8GHz MacBook Pro Space Gray: $2199, $200 off MSRP... Read more
2.6GHz Mac mini on sale for $599, $100 off MS...
B&H Photo has the 2.6GHz Mac mini (MGEN2LL/A) on sale for $599 including free shipping plus NY sales tax only. Their price is $100 off MSRP. Read more
Snag a 15-inch 2.2GHz Retina MacBook Pro, App...
Apple has Certified Refurbished 2015 15″ 2.2GHz Retina MacBook Pros available for $1699. That’s $300 off original MSRP, and it’s the lowest price available for a 15″ MacBook Pro currently offered by... Read more
Apple Refurbished 3TB Time Capsule for $279,...
Apple has Certified Refurbished 3TB Time Capsules available for $279 including free shipping plus Apple’s standard one-year warranty. Their price is $120 off MSRP. Read more
19% off Smart Battery Cases for iPhone 7
Amazon has both Black and White Smart Battery Cases for iPhone 7s available for $80.41 including free shipping. Their price is $18.59, or 19%, off MSRP. Read more

Jobs Board

Development Operations and Site Reliability E...
Development Operations and Site Reliability Engineer, Apple Payment Gateway Job Number: 57572631 Santa Clara Valley, California, United States Posted: Jul. 27, 2017 Read more
*Apple* Store - Technical Specialist - Apple...
…customers purchase our products, you're the one who helps them get more out of their new Apple technology. Your day in the Apple Store is filled with a range of Read more
*Apple* Retail - Multiple Positions - Apple,...
Job Description: Sales Specialist - Retail Customer Service and Sales Transform Apple Store visitors into loyal Apple customers. When customers enter the store, Read more
*Apple* News Product Marketing Mgr., Publish...
Job Summary The Apple News Product Marketing Manager will work closely with a cross-functional group to assist in defining and marketing new features and services. Read more
Development Operations and Site Reliability E...
Development Operations and Site Reliability Engineer, Apple Payment Gateway Job Number: 57572631 Santa Clara Valley, California, United States Posted: Jul. 27, 2017 Read more
All contents are Copyright 1984-2011 by Xplain Corporation. All rights reserved. Theme designed by Icreon.