Skip to main content
Skip table of contents

Finding Optional Content Groups by page

📌 Overview

This knowledge base article is an extension to Optional Content (Layers) which outlines how optional content is managed in Mako. This article explains specifically how to find all the optional content groups on a page.

📗 Instructions

In Mako, optional content is marked up either by IDOMGroup objects or annotations. In this example, all groups are checked for optional content membership. This is complicated by the fact that optional content can be almost anywhere, including in patterns, soft masks, or glyphs in Type 3 fonts. To save extra work, a custom transform is used that will walk into every nook and cranny in the content for us, avoiding repeatedly revisiting shared resources.

🪜 Steps

  1. Create a custom transform that overrides the transformGroup() method

  2. In the custom transform find optional content groups with group->getOptionalContentDetails()->getGroupReferences()

  3. Call the custom transform on each page and return the result

⌨️ Sample Code

Sample code is available below and on our GitHub Gists page. See OptionalContentSearch.cpp, OptionalContentSearch.cs.

C++

C#

CPP
class COptionalContentSearchImplementation : public ICustomTransform::IImplementation
{
public:
    void reset()
    {
        m_foundGroups.clear();
    }

    const COptionalContentGroupReferenceVect& getFoundGroups()
    {
        return m_foundGroups;
    }

    IDOMNodePtr transformGroup(IImplementation* genericImplementation, const IDOMGroupPtr& group, bool& changed, bool transformChildren, const CTransformState& state)
    {
        // Does this group have optional content information?
        const IOptionalContentDetailsPtr details = group->getOptionalContentDetails();
        if (details)
        {
            // What groups does this reference?
            COptionalContentGroupReferenceVect referencedGroups = details->getGroupReferences();

            // Unfortunately for now we need to laboriously check to see if we've seen this before
            for (const IOptionalContentGroupReferencePtr& referencedGroup : referencedGroups)
            {
                bool found = false;
                for (const IOptionalContentGroupReferencePtr& foundGroup : m_foundGroups)
                {
                    if (foundGroup->equals(referencedGroup))
                    {
                        found = true;
                        break;
                    }
                }
                if (!found)
                {
                    m_foundGroups.append(referencedGroup);
                }
            }
        }
        // Descend further
        return genericImplementation->transformGroup(nullptr, group, changed, transformChildren, state);
    }

private:
    COptionalContentGroupReferenceVect m_foundGroups;
};
CPP
// Create a custom transform to do our searching
COptionalContentSearchImplementation optionalContentSearchImplementation;
const ICustomTransformPtr optionalContentSearchTransform = ICustomTransform::create(mako, &optionalContentSearchImplementation);

for (uint32 pageIndex = 0; pageIndex < document->getNumPages(); pageIndex++)
{
    std::cout << "Page " << pageIndex + 1 << ":" << std::endl;

    // What groups does page 1 reference?
    {
        // Ask the transform. Note that here we want to clear caches between checks as we always
        // want the transform to descend into shared resources for each page. Caching would ordinarily
        // preclude that.
        //
        // We operate on a clone to ensure no changes to the tree. Custom transforms automatically
        // clean up duplicated resources which could cause an edit.
        optionalContentSearchImplementation.reset();
        optionalContentSearchTransform->flushCaches();
        optionalContentSearchTransform->transformPage(document->getPage()->clone());

        // So what groups do we have?
        COptionalContentGroupReferenceVect foundGroups = optionalContentSearchImplementation.getFoundGroups();
        for (const IOptionalContentGroupReferencePtr& foundGroup : foundGroups)
        {
            IOptionalContentGroupPtr group = optionalContent->getGroup(foundGroup);

            // This group is present - do something with this information
            std::cout << "Found group: " << group->getName() << std::endl;
        }
    }
C#
class COptionalContentSearchImplementation : ICustomTransform.IImplementation
{
    public COptionalContentSearchImplementation()
    {
        m_foundGroups = new List<IOptionalContentGroupReference>();
    }

    public void reset()
    {
        m_foundGroups.Clear();
    }

    public List<IOptionalContentGroupReference> getFoundGroups()
    {
        return m_foundGroups;
    }

    public override IDOMNode transformGroup(ICustomTransform.IImplementation genericImplementation,
        IDOMGroup group,
        ref bool changed, bool transformChildren, CTransformState state)
    {
        // Does this group have optional content information?
        IOptionalContentDetails details = group.getOptionalContentDetails();
        if (details != null)
        {
            // What groups does this reference?
            var referencedGroups = details.getGroupReferences().toVector();

            // Unfortunately for now we need to laboriously check to see if we've seen this before
            foreach (IOptionalContentGroupReference referencedGroup in referencedGroups)
            {
                bool found = false;
                foreach (IOptionalContentGroupReference foundGroup in m_foundGroups)
                {
                    if (foundGroup.Equals(referencedGroup))
                    {
                        found = true;
                        break;
                    }
                }

                if (!found)
                {
                    m_foundGroups.Add(referencedGroup);
                }
            }
        }
        // Descend further
        return genericImplementation.transformGroup(null, group, ref changed, transformChildren, state);
    }

    private readonly List<IOptionalContentGroupReference> m_foundGroups;
}
C#
// Create a custom transform to do our searching
var optionalContentSearchImplementation = new COptionalContentSearchImplementation();
var optionalContentSearchTransform = ICustomTransform.create(mako, optionalContentSearchImplementation);

for (uint pageIndex = 0; pageIndex < document.getNumPages(); pageIndex++)
{
    Console.WriteLine($"Page {pageIndex + 1}:");

    // How we can know if the optionalContent is from this page?
    // Ask the transform. Note that here we want to clear caches between checks as we always
    // want the transform to descend into shared resources for each page. Caching would ordinarily
    // preclude that.
    //
    // We operate on a clone to ensure no changes to the tree. Custom transforms automatically
    // clean up duplicated resources which could cause an edit.
    optionalContentSearchImplementation.reset();
    optionalContentSearchTransform.flushCaches();
    optionalContentSearchTransform.transformPage(document.getPage(pageIndex).clone());

    // So what groups do we have?
    var foundGroups = optionalContentSearchImplementation.getFoundGroups();
    foreach (var foundGroup in foundGroups)
    {
        IOptionalContentGroup group = optionalContent.getGroup(foundGroup);

        // This group is present - do something with this information
        Console.WriteLine($"  Found group: {group.getName()}");
    }
}

☑️ Conclusion

Identifying optional content groups within a page in Mako is a crucial task for managing complex documents with layered content. By utilizing custom transforms to navigate through all potential locations of optional content, users can efficiently gather necessary information without redundant processing. This approach ensures a comprehensive understanding of content layers, enhancing document manipulation and rendering capabilities.

📚 Additional Resources

If you need additional help, see our API documentation for detailed information on class/method usage, or raise a support ticket via our customer portal.

JavaScript errors detected

Please note, these errors can depend on your browser setup.

If this problem persists, please contact our support.