PDF page boxes and Mako
The basics
Every page in a PDF defines a rectangle that defines a media box (the overall size of the media, such as an 8½" x 11" sheet of paper). Although optional, most PDFs also define a crop box, which defines the viewable area of the page. A crop box cannot not be larger than the media box, or more accurately, may not extend outside the media box. For the majority of PDFs, the media size and the crop box have the same dimensions, and are assumed to be so should the crop box be left undefined.
PDF 1.3 introduced three further boxes to support print workflows. They are the trim box, bleed box, and art box. As with the crop box, they cannot extend outside the media box. Their purpose:
Trim box – specifies the trimmed size of the final printed product, often a specific page size such as A5 or US Letter
Bleed box – extends the edge of the page by a small amount so that images or areas of color can print right to the edge of the trimmed area. It is typically 3mm larger than the trimmed size in all dimensions
Art box – For a page that contains artwork as well as other information, the art box defines the area of the page that comprises the actual artwork. It is intended to speed up placing PDF content on to a layout in a program such as Adobe InDesign
The following diagram shows a practical application of the different page dimensions:
The two most important sets of dimensions are the MediaBox and the CropBox.
Page boxes in Mako
As stated above, the various dimensions are properties of a page. They can be obtained from an IPage
object as shown here.
auto mako = IJawsMako::create();
IJawsMako::enableAllFeatures(mako);
// Open the PDF
auto assembly = IPDFInput::create(mako)->open(L"..\\..\\TestFiles\\EdgeTestA5.pdf");
// Get the first page
auto page = assembly->getDocument()->getPage(0);
// Get all the boxes
FBox mediaFBox = page->getMediaBox();
FRect mediaBox = page->getMediaBox().asRect();
FRect cropBox = page->getCropBox();
FRect bleedBox = page->getBleedBox();
FRect trimBox = page->getTrimBox();
FRect artBox = page->getContentBox();
IPage::getWidth()
and IPage::getHeight()
also return the dimensions of the media box, in Mako native units (1/96th in). These APIs are slightly quicker than IPage::getMediaBox()
.
FRect and FBox
FRect and FBox are structs
that express dimensions, but they are defined in different ways.
An FRect defines a top left origin (x, y
), a horizontal, left-to-right dimension (dX
) and a vertical, top-to-bottom dimension (dy
).
From the example above, the width of the crop box (in 1/96th inch) is given by:
double cropWidth = cropBox.dX;
An FBox, on the other hand, defines values for left (left
), bottom (bottom
), top (top
), right (right
). The origin in this case is bottom left, so the top value increases from bottom to top, and the right value increases from left to right.
From the example above, the width of the media (in 1/72nd inch) is given by:
double mediaWidth = mediaFBox.right - mediaFBox.left
The distinction between these two definitions is relevant because both have their purposes.
Mako APIs
A typical PDF consisting of a single A5 portrait page reports these dimensions, all in millimeters:
FBox ------------- Dimensions -------------
Left Top Right Bottom
Media 0.000 210.000 148.000 0.000
FRect ----- Origin ----- --- Dimensions ---
X Y Width Height
Media 0.000 0.000 148.000 210.000
Crop 0.000 0.000 148.000 210.000
Bleed 0.000 0.000 148.000 210.000
Trim 0.000 0.000 148.000 210.000
Art 0.000 0.000 148.000 210.000
The code to produce the table above can be found at the end of this page.
The following code was then used to modify the media size, specifically to expand it by 25mm in every direction:
#define MM2PDFUNITS(value) ((value) / 25.4 * 72.0)
// Modify size
mediaFBox.left -= MM2PDFUNITS(25);
mediaFBox.top += MM2PDFUNITS(25);
mediaFBox.right += MM2PDFUNITS(25);
mediaFBox.bottom -= MM2PDFUNITS(25);
page->setMediaBox(mediaFBox);
These images represent the page before and after the change. In the second image we can see a wide margin around the content, which is centered, as all the dimensions were altered by the same amount. Note that to increase the left and bottom margin, a negative value is required.
The media size can be obtained from the page both as an FBox (the default) or an FRect (by appending .asRect()
). Both methods return their values in PDF units (1/72nd inch).
However, only an FBox can be used to set the media box values, so it is more convenient to use an FBox to begin with.
Here we compare the box dimensions, before and after:
Numeric results of expanding | |
Before
CODE
| After
CODE
|
The crop, bleed, trim, and art boxes have a new origin (their top-left corner is now 25mm in and 25mm down on the page) but their size is unaltered.
If you want the dimensions of these boxes to match those of the MediaBox, either explicitly set them, or remove them so that they inherit the MediaBox size. See 'Inheritance' below for more information.
Setting the media box
Setting the media box requires passing an FBox, with PDF units, as in the above example.
In that example, the result means the origin of the MediaBox is no longer 0,0. If you wish to reset it, include this code when writing the PDF:
const auto pdfOutput = IPDFOutput::create(mako);
...
pdfOutput->setForceMediaBoxOriginZero(true);
This will adjust the mediabox so that its origin is 0,0
Scaling the media box
A convenience method to scale the media box is provided. For example, to scale an A5 media box to A4 (or to A4 to A3, etc.) simply apply a scale of 141.42%:
// Scale media box
mediaFBox.scale(1.4142);
page->setMediaBox(mediaFBox);
Numeric results of scaling | |
Before
CODE
| After
CODE
|
Page content does not scale with the media box, and remains at the same size and in the same position, relative to the bottom-left corner of the page.
Scaling the media box with a value below 100% may result in page boxes acquiring negative origin values to compensate for their dimensions exceeding the size of the media box. This is expected and not invalid.
Saving to PDF/X-4 will "normalize" the page boxes, adjusting them to the same size or smaller than the MediaBox, as this is a requirement of the PDF/X-4 standard.
Appearance in Acrobat and other PDF viewers
Increasing the media size does not by itself change how the document is presented in Acrobat or other PDF viewer, since the crop box controls the apparent page size. Nor does it scale page content, as noted earlier.
Decreasing the media size may change how the document is presented in a viewer if one or both MediaBox dimensions are less than those of the original crop box, in which case the new dimensions take priority.
Scaling of content
If scaling the page and its content is required, it is necessary to obtain an IDOMFixedPage
from an IPage
first, and operate on that.
A code sample showing how this is done is available in the Mako SDK. Look in the simpleexamples folder for imposition.cpp
. This shows a worked example of copying page content and scaling as needed (in this case for the purposes of creating a booklet imposition).
Inheriting values from MediaBox
Obtaining page box sizes from an IDOMFixedPage
and an IPage
will give different results depending on whether the dimension is set in the PDF. Consider this result of taking page box sizes from a PDF that sets only the ArtBox (and MediaBox, that’s mandatory):
From IDOMFixedPage:
FRect ----- Origin ----- --- Dimensions ---
X Y Height Width
Media 0.000 0.000 215.900 279.400
Crop 0.000 0.000 -0.265 -0.265
Bleed 0.000 0.000 -0.265 -0.265
Trim 0.000 0.000 -0.265 -0.265
Art 0.000 0.025 215.900 279.400
From IPage:
FRect ----- Origin ----- --- Dimensions ---
X Y Height Width
Media 0.000 0.000 215.900 279.400
Crop 0.000 0.000 215.900 279.400
Bleed 0.000 0.000 215.900 279.400
Trim 0.000 0.000 215.900 279.400
Art 0.000 0.025 215.900 279.400
You will notice that some of the values from the fixed page are empty, whereas the IPage
methods return results that inherit from the MediaBox.
You can check the value returned from the IDOMFixedPage
is valid by checking the FRect::isEmpty()
property, a Boolean value. For example:
IDOMFixedPagePtr fixedPage = page->getContent();
// Get the cropbox
FRect cropBox = fixedPage->getCropBox();
bool cropBoxIsEmpty = cropBox.isEmpty();
Removing page boxes
If you need to remove page box definitions from a PDF, you can either set the pagebox with an empty FRect()
. It’s also possible to “empty” an existing with FRect::setEmpty()
. Either way, when Mako comes to write the PDF and the box is empty, it will not be written to the PDF. For example, code such as this will remove all the page boxes:
// Remove the page boxes
for (uint32 pageIndex = 0; pageIndex < document->getNumPages(); pageIndex++)
{
// Get page from the document
const IPagePtr page = document->getPage(pageIndex);
// Get fixed page from IPage
const IDOMFixedPagePtr fixedPage = page->edit();
FRect trimBox = fixedPage->getTrimBox();
trimBox->setEmpty();
// Clear the page boxes
fixedPage->setCropBox(FRect());
fixedPage->setBleedBox(FRect());
fixedPage->setTrimBox(trimBox);
fixedPage->setContentBox(FRect());
}
Explicitly setting page box values
When you remove page boxes in the manner described above, meaning they are no longer present in the PDF, downstream PDF consumers assume their value to be the same as the MediaBox. This can be useful.
However, if you would prefer to explicitly set the values, code like this snippet will do this for you. In this example, the MediaBox is first resized, effectively expanding the page by 25mm in all directions, then the page boxes are updated to suit.
#define MM2PDFUNITS(value) ((value) / 25.4 * 72.0)
// Resize all page boxes
for (uint32 pageIndex = 0; pageIndex < document->getNumPages(); pageIndex++)
{
// Get page from the document
const IPagePtr page = document->getPage(pageIndex);
FBox mediaFBox = page->getMediaBox();
// Modify size
mediaFBox.left -= MM2PDFUNITS(25);
mediaFBox.top += MM2PDFUNITS(25);
mediaFBox.right += MM2PDFUNITS(25);
mediaFBox.bottom -= MM2PDFUNITS(25);
page->setMediaBox(mediaFBox);
// Update the other page boxes to match
page->edit()->setCropBox(FRect(0.0, 0.0, page->getWidth(), page->getHeight()));
page->edit()->setBleedBox(FRect(0.0, 0.0, page->getWidth(), page->getHeight()));
page->edit()->setTrimBox(FRect(0.0, 0.0, page->getWidth(), page->getHeight()));
page->edit()->setContentBox(FRect(0.0, 0.0, page->getWidth(), page->getHeight()));
}
Code used to display page box dimensions
#define PDFUNITS2MM(value) ((value) / 72.0 * 25.4)
#define XPSUNITS2MM(value) ((value) / 96.0 * 25.4)
void reportPageDimensions(FRect mediaBox, FRect cropBox, FRect bleedBox, FRect trimBox, FRect artBox)
{
printf("FRect ----- Origin ----- --- Dimensions ---\n");
printf(" X Y Height Width\n");
printf("Media %8.3f %8.3f %8.3f %8.3f\n", PDFUNITS2MM(mediaBox.x), PDFUNITS2MM(mediaBox.y),
PDFUNITS2MM(mediaBox.dX), PDFUNITS2MM(mediaBox.dY));
printf("Crop %8.3f %8.3f %8.3f %8.3f\n", XPSUNITS2MM(cropBox.x), XPSUNITS2MM(cropBox.y),
XPSUNITS2MM(cropBox.dX), XPSUNITS2MM(cropBox.dY));
printf("Bleed %8.3f %8.3f %8.3f %8.3f\n", XPSUNITS2MM(bleedBox.x), XPSUNITS2MM(bleedBox.y),
XPSUNITS2MM(bleedBox.dX), XPSUNITS2MM(bleedBox.dY));
printf("Trim %8.3f %8.3f %8.3f %8.3f\n", XPSUNITS2MM(trimBox.x), XPSUNITS2MM(trimBox.y),
XPSUNITS2MM(trimBox.dX), XPSUNITS2MM(trimBox.dY));
printf("Art %8.3f %8.3f %8.3f %8.3f\n\n", XPSUNITS2MM(artBox.x), XPSUNITS2MM(artBox.y),
XPSUNITS2MM(artBox.dX), XPSUNITS2MM(artBox.dY));
}
void reportMediaBoxDimensions(FBox mediaBox)
{
printf("FBox ------------- Dimensions -------------\n");
printf(" Left Top Right Bottom\n");
printf("Media %8.3f %8.3f %8.3f %8.3f\n\n", PDFUNITS2MM(mediaBox.left), PDFUNITS2MM(mediaBox.top),
PDFUNITS2MM(mediaBox.right), PDFUNITS2MM(mediaBox.bottom));
}