PDF Page Coordinates (page size, field placement, etc.)
Contents
- Coordinate Systems (Page Geometry)
- Page Rotations
- Field and Annotation Rotations
- Getting and Setting Page Size
- Placing and Moving Form Fields
- Converting Coordinates
- Finding Words, and Handling Word Locations
Related Resources
- Sample Files that demonstrate page geometry operations
- Bouncing Button
- 2D Matrix Mulitplier (Discussed in Converting Coordinates)
- Swat the Fly Game (Variation on Bouncing Button)
- Automation tools that demonstrate page geometry operations
Coordinate Systems

However, PDF pages are a bit more complex than they might seem from the user's perspective. The edges of a page are bound by several different page boxes (Figure 1). Ideally these boxes are concentric (each one fully inside the next) as shown in Figure 1, but this arrangement is not absolutley required. Each of these boxes has a different meaning and all of them except for the BBox can be modified with a script.

The outer most box is the Media box. This box represents the full page size. Originally this meant the paper size the page was to be printed on. And all the other bounding boxes are inside this one. The Media Box doesn't have quite the same importance for an interactive document displayed on the sceen. But it is still very important to page geomentry, as will be explained below.
The next 3 boxes, Art, Bleed, and Trim, have special meaning to printers. They represent imporant stages in the printing of a document, but are invisible to the average user and unimportant for our purposes here, so they won't be discussed.
The two most important boxes for scripting, and handling page geometry in general, are the Media Box and the Crop Box. As explained earlier, the Media Box is meant to represent what the user would see if they printed out the PDF page. The Crop box is what the user sees on the computer screen. These two boxes are often exactly the same, but they can of course be different sizes, and they can also be different rotations. The only restriction is that the Crop Box is always inside the Media Box. If a script attempts to make the Media Box smaller than the Crop Box, then Acrobat will automatically adjust the size of the Crop Box to be smaller. And vise-a-versa, if a script tries to make the Crop Box larger than the Media box Acrobat will automatically grow the Media Box.
To handle these two situations Acrobat JavaScript uses two different coordinate systems, Default User Space which represents the printed view, and Rotated User Space which represents the on-screen view. Default User Space is measured with the Media Box. In Default User Space the origin (0,0 point) is always the bottom left hand corner of the printed page. Rotated user space is measured with the Crop Box. In Rotated User Space the origin is always the bottom left hand corner of the page shown on the screen. This difference is illustrated in Figure 3 using the example discussed above.

You'll find in most documents that both these User Spaces are exactly the same, which means that the Crop and Media Boxes are also exactly the same. However, unless you know for sure that there isn't a difference, all code must be written to take rotations and cropping in to consideration.
Page Rotations
Because Rotation is part of the difference between the User Spaces it's useful to understand a bit about how page rotation works in Acrobat and PDF. Pages can only be rotated in 90° increments. For example, a page cannot be skewed 45°. Each page in a PDF document has it's own unique geometry. The size and rotation of pages in a PDF are unrelated to one another. Of course, in real documents it's a standard practice to make all the pages the same. But keep in mind that there is no guarentee. Code that works on every page in a PDF has to treat each page individually, unless it's known up front that document page geometry is homogeneous.
Unfortunately, the Acrobat user interface does not provide information on Page Rotation. Even though pages can be rotated with the "Document > Rotate Pages..." menu item, there is no feedback to indicate the current page rotation. Fortunately we can get this information from JavaScript with the doc.getPageRotation()function. The following line of code returns the page rotation for the current page in the current document. Try running it in the Console Window.
var nRot = this.getPageRotation(this.pageNum);
// nStart = 0; first page in PDF // nEnd = 2; page 3 in PDF // nRotation = 90° this.setPageRotations(0,2,90);
Field and Annotation Rotation
When a page is rotated, the form fields and markup annotations on the page are rotated with it, so that the presentation of all the elements on the page stays in sync. The rotation value of markup annotations cannot be seen or controlled from the Acrobat UI. For all practical purposes it is whatever Acrobat decides to make it. But fields have a rotation property that can be set in the Properties Dialog (Figure 4). Setting the Rotation of a field sets the orientation of the content (the text), it does not rotate the field box.

In JavaScript, the rotation of fields can be ananlyzed and set through the "field.rotation" property.
var oFld = this.getField("MyText"); // Get Field Rotation. var nCurRot = oFld.rotation; // Rotate 90° more var nNewRot = nCurRot + 90; // Fix up for large rotations if(nNewRot >= 360) nNewRot -= 360; // new Rotation oFld.roation = nNewRot;
The rotation value shown on the Properties Dialog is different from the value acquired in the JavaScript model. The Properties Dialog value shows the rotation of the field relative to the view of the page on screen. The rotation acquired from the Field's JavaScript object is the real rotation of the element that you'll need for doing geometry calculations. It's the rotation of the field in Default User Space, or to put it qualitatively, it's the fields rotation relative to the orientation of the printed page. And just like the rotation set in the Properties Dialog it only rotates text and graphics shown in the field. It does not rotate the field boundaries
Getting and Setting Page Size
var aCropRect = this.getPageBox("Crop",0);
This function has only two inputs, the name of the box we want to acquire, and the zero-based page number. The return value is an array of four values representing the coordinates of the four edges of the page rectangle.
var aPageBox = [nLeft, nTop, nRight, nBottom];
//** For Figure 1 // Original Page is 8.5x11 inches, no rotation or cropping var aCropRect = this.getPageBox("Crop",0); // Returns: [0,792,612,0] var aMediaRect = this.getPageBox("Media",0); // Returns: [0,792,612,0] //** For Figure 3 // Original Page is 8.5x11 inches, rotated 90°, // and cropped 1/2 inch on all sides var aCropRect = this.getPageBox("Crop",0); // Returns: [0,540,720,0] var aMediaRect = this.getPageBox("Media",0); // Returns: [-36,576,756,-36]
Notice that in the example for Figure 1 the Crop and Media boxes are the same. This means that Default and Rotated User Space are also the same.

Notice that changing the page size changes the Media Box. This reinforces the idea that the Media Box is the base page size (or paper size), and all the other boudaries are variations on the Media Box. This is why Default user space is based on the Media Box. What the user sees on the screen, Rotated User Space is a variation. This gives us some perspecitive on Adobe's thinking at the time they created PDF, i.e. that it was primarily a print, rather than screen, based model.
// Original Page is 8.5x11 inches // First, acquire the current Media Box var aMediaRect = this.getPageBox("Media",this.pageNum); // Returns: [0,792,612,0] // Now remove 1/2 inch, 36 points from all sides var aNewCrop = aMediaRect; aNewCrop[0] += 36; // Move Left edge to the right aNewCrop[1] -= 36; // Move Top edge down aNewCrop[2] -= 36; // Move Right edge to the Left aNewCrop[3] += 36; // Move Bottom edge up // Returns: [36,756,576,36] // Set Crop Box of the current page to the new rectangle this.setPageBoxes("Crop",this.pageNum,this.pageNum, aNewCrop); // Get the new Crop Box var aCropRect = this.getPageBox("Crop",this.pageNum); // Returns: [0,720,540,0] // Get the new Media Box var aMediaRect = this.getPageBox("Media",this.pageNum); // Returns: [-36,756,576,-36]
The script gets the current Media Box, shrinks it by 1/2 inch on all sides, and then applies that new rectangle to the Crop Box. Notice that the setPageBoxes function takes four inputs, the name of the Page Box that will be changed, the start and end page numbers (zero based), and the new rectangle. The rectangle is always in Rotated User Space.
Placing Form Fields
// First, acquire the Crop Box for Page #1 var aCropRect = this.getPageBox("Crop",0); // Calculate the placement rectangle for the text box. // Field is centered so find the mid point of the page var nMiddle = aCropRect[2]/2; // Field is along bottom edge of crop, 150 points wide, 20 points tall var rectFld = []; rectFld[0] = nMiddle-75; // Left side is 75pts to the left of the middle rectFld[1] = aCropRect[3]+20; // Top side is 20pts above the bottom rectFld[2] = nMiddle+75; // Right side is 75pts to the righ of the middle rectFld[3] = aCropRect[3]; // Bottom is the same as the Crop Box // Add Field var oFld = this.addField("MyDateFld", "text", 0, rectFld); oFld.value = util.printd("ddd mmm d, yyyy",new Date()); oFld.alignment = "center";
The doc.addField() function takes 4 inputs, the name of the field, the field type, the zero based page number, and the field placement rectangle in Rotated User Space Coordinates. This rectangle is setup in exactly the same way as the Page Box Rectangles. It's an array of four numbers where each number is a coordinate for the edges of the field, in the order Left, Top, Right, Bottom.
Adjusting the location and size of the field is a simple matter of adjusting the parameters used to calculate the field placment rectangle. Fields can even be placed outside, or partially outside the crop box. If there is room in the viewing area Acrobat will draw fields that are outside the Crop Box, so this placement can be used to create visual effects. Becareful though, because this may be a bug in older versions and Adobe could easily make it so that nothing is drawn outside the crop area. But one legitimate use for placing fields off the page is for hidden fields you don't want the user interacting with.
var rectFld = event.target.rect; var newRect = [rectFld[0]+10, rectFld[1]+10, rectFld[2]+10, rectFld[3]+10]; event.target.rect = newRect;
Converting Coordinates
It's partially documented because while you won't find it listed in the Acrobat JavaScript Reference, it is used for a couple offical examples within the reference. Matrix2D is a generic JavaScript defined in the "JSByteCodeWin.bin" file, which can be found in the Acrobat JavaScript folder. This is a binary file so you can't open it up find the code that defines the Matrix2D object. But if you are really interested in seeing the code run the following line in the console window.
Matrix2D.toString().replace(/([\;\{])/g,"$1\n")
The main purpose of this object is to perform 3x3 matrix multiplications. This is the standard methodology for transforming and moving objects about in 2-D space. If you're interested in mathamatics and 2D graphics transformations then see the Matrix Multiplier example file.
// Create matrix that converts from Rotated User Space // to Default User Space var mxToDefaultCoords = (new Matrix2D()).fromRotated(this,this.pageNum); // To transform a point or rect var rectDefault = mxToDefaultCoords.transform(rectRotated); // Create Matrix that converts from Default User Space // to Rotated User Space var mxToRotatedCoords = mxToDefaultCoords.invert(); // To transform a point or rect var rectRotated = mxToRotatedCoords.transform(rectDefault);
The matrix for converting to Default User Space is created with the Matrix2D.fromRotated() function. This function has two inputs, the document object and the zero based page within the document where the transform is needed. To convert in the other direction, from Default to Rotated User Space, the first matrix is inverted using the Matrix2D.invert() function
Coordinate conversions are performed with the Matrix2D.transform() function. The input to this function is a flat list of points such as the rectangle arrays used in the previous example. A single point is an (X,Y) coordinate pair. So the rectangle can be though of as two points. The first point is the Top Left corner of the rectangle and the second point is the Bottom Right corner. In Acrobat JavaScript here are many different kinds of coordinate structures and not all of them are flat like the rectangle array. Some are arrays within arrays, so these items will need to be reformatted to be used with the transform function, which we'll see in the next section
Finding Word Locations

The coordinates used by a Quad are in Default User Space. If we wanted to use a word location to place a form field or link on the page it would be necessary to convert the quad into a Default User Space rectangle, as shown in the following code:
// Create Matrix that converts from Default User Space // to Rotated User Space var mxToDefaultCoords = (new Matrix2D()).fromRotated(this,this.pageNum); var mxToRotatedCoords = mxToDefaultCoords.invert(); // Get the quad for the 2nd word on the page var quads = this.getPageNthWordQuads(this.pageNum,1); // Flatten Quads into simple array of numbers var pts = quads.toString().split(","); // Convert pts to Rotated User Space var ptsNew = mxToRotatedCoords.transform(pts); // Find points of rectangle from min and max of points // start off with rectangle set to first point var rect = [ ptsNew[0], ptsNew[1], ptsNew[0], ptsNew[1]]; for(var i=0;i<ptsNew.length;i+=2) { // Test Left if(ptsNew[i] < rect[0]) rect[0] = ptsNew[i]; // Test Right if(ptsNew[i] > rect[2]) rect[2] = ptsNew[i]; // Test Top if(ptsNew[i+1] > rect[1]) rect[1] = ptsNew[i+1]; // Test bottom if(ptsNew[i+1] < rect[3]) rect[3] = ptsNew[i+1]; } // Add Link to page using rectangle in Rotated User Space this.addLink(this.pageNum,rect);
The ultimate goal of the script is to place a link over a specific word on the page. The problem is that the coordinates for a single word can be in a series of Default User Space Quads, and the link is placed using a single rectangle in Rotated User Space. So it's not just a conversion between coordinate spaces, it's also a conversion between point structures, from multiple quads to a single rectangle. Because there are so many different ways to describe the geometric strutures used on a PDF, this type of conversion is acutally pretty common.
The first thing the script does is to create the Matrix2D object we'll need for the conversion from Default to Rotated User Space. Next it acquires the Quads for the word of interest. We don't know how many quads are used for the word so the conversion technique has to be generalized for any number. This is easily done by first converting the Quads structure into a string, and then splitting the string by the "," dilimiter. The fact that we can do this is an artifact of the "toString()" function. Try this line by itself in the Console Window to see how it works. The result is a large list of points, which are then converted to Rotated User Space using the matrix created at the top of the script.
Now we need to sort through all these points to find the edges of a rectangle. This is done by setting up a loop to find the largest and smallest x and y values, i.e., the code finds the boundaries of the set of points, and this is the placement rectangle that's used to create the link. For a real automation script it would be more efficient to place the code for converting a quad in to a rectangle into a function since we might need this ability in several places. Placing the code in a function also makes it easier to use in the future.