Optional content (layers)
Overview
PDF files support optional content, which are also known as layers. Page content can be assigned to an optional content group (OCG). An OCG has properties that control whether the content is hidden for viewing and/or printing.

OCGs can be complicated to work with, but the Mako API makes a good job of simplifying things for us. With just a small amount of code, you can easily:
Check that a document has optional content
Enumerate OCGs
Find out which OCG a node is a member of
Add a node to an OCG
Create a new OCG and assign content to it with a simple API
Checking for optional content
If optional content is present in a PDF, it is accessed from an IOptionalContent
object that is a property of the IDocument
. It is obtained thus:
const IOptionalContentPtr optionalContent = document->getOptionalContent();
If a null object is returned, this indicates there is no extant optional content. If this is the case, create a new optional content object and attach it to the document.
// Create optional content, groups and references
const auto optionalContent = IOptionalContent::create(mako);
document->setOptionalContent(optionalContent);
Enumerating optional content groups
Optional content can be found on the IDocument
object, by calling getOptionalContent(...)
. The groups can then be found by calling getGroups(...)
.
The following code will enumerate and output the names of all optional content groups (layers) in the document.
const auto optionalContent = document->getOptionalContent();
if (!optionalContent)
{
std::cout << "No optional content for the document." << std::endl;
return;
}
const auto optionalContentGroups = optionalContent->getGroups();
std::cout << "Optional content groups found:" << std::endl;
for (const auto& optionalContentGroup : optionalContentGroups)
{
std::cout << "\t" << optionalContentGroup->getName() << std::endl;
}
Finding optional content group membership
Sometimes it's useful to be able to find which optional content group(s) a node is a member of. This is straightforward, but first requires a little knowledge of how optional content groups work.
In PDF, optional content is structured such that only certain PDF content types can be assigned to an optional content group.
Likewise, in Mako, only group nodes (IDOMGroup
) can be assigned to an OCG group. The code below goes will find the first OCG a group node is a member of.
IOptionalContentGroupPtr getOptionalContentGroupForGroupNode(const IDocumentPtr& document, const IDOMGroupPtr& groupNode)
{
const auto optionalContentGroupDetails = groupNode->getOptionalContentDetails();
// Check if it has any optional content group details.
// If not, it's not a member of any group.
if (!optionalContentGroupDetails)
return IOptionalContentGroupPtr();
const auto groupReferences = optionalContentGroupDetails->getGroupReferences();
const auto optionalContentGroups = document->getOptionalContent()->getGroups();
// Go through each group reference in the node.
for (const auto& groupReference : groupReferences)
{
// Iterate through the optional content groups in the document
for (const auto& optionalContentGroup : optionalContentGroups)
{
// And see if one matches.
if (groupReference->equals(optionalContentGroup->getReference()))
{
// Return the first optional content group it's a member of for simplicity. However, It may be a member of multiple groups.
return optionalContentGroup;
}
}
}
return IOptionalContentGroupPtr();
}
If you want to see optional content group membership for a node that isn't a group, you'll need to walk up the node tree, using getParentNode(...)
, until you reach a group, which you can then check.
Adding a node to an optional content group
This requires just a few lines of code. First, we get the optional content from the document, then we call makeNodeOptional(...)
to add the node to the chosen OCG.
const auto optionalContent = document->getOptionalContent();
optionalContent->makeNodeOptional(myNode, myOptionalContentGroup);
Once assigned, the OCG settings will determine the behavior (e.g., visibility) of the node.
To remove a node from an optional content group, you need to reset the optional content details of the corresponding group node. This is either the node itself, if it is an IDOMGroup
, or a parent group node that is created when the node is made optional. Removing the optional content details is achieved by setting its optional content to an empty IOptionalContentDetails
.
const auto groupNode = myNode->getParentNode() // necessary only if myNode is not an IDOMGroup
groupNode->setOptionalContentDetails(IOptionalContentDetailsPtr())
Adding content to a new OCG
A convenience API, IOptionalContent::makeNodeOptionalWithNewGroup()
enables a content node to be added to a new OCG in a single operation. Simply pass a node reference, name for the OCG (this will appear in the Layers panel in Acrobat) and whether the new OCG should initially be visible (true
, the default) or false
(hidden).
const auto optionalContent = document->getOptionalContent();
optionalContent->makeNodeOptionalWithNewGroup(path, ocgName, true);
Rendering with Optional Content
If you need to taking into account optional content when rendering with Mako, make sure you use one of the rendering overloads that accepts the document's optional content object. If you omit the OCG object, all the nodes will render as normal, ignoring the OCG settings.