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++
CPP
/* --------------------------------------------------------------------------------
 *  <copyright file="OptionalContentSearch.cpp" company="Global Graphics Software Ltd">
 *    Copyright (c) 2023 Global Graphics Software Ltd. All rights reserved.
 *  </copyright>
 *  <summary>
 *    This example is provided on an "as is" basis and without warranty of any kind.
 *    Global Graphics Software Ltd. does not warrant or make any representations
 *    regarding the use or results of use of this example.
 *  </summary>
 * ---------------------------------------------------------------------------------
 */

#include <iostream>
#include <jawsmako/jawsmako.h>
#include <jawsmako/pdfinput.h>
#include <jawsmako/customtransform.h>

using namespace JawsMako;

// In Mako, optional content is marked up either by IDOMGroup objects or annotations.
// Here we will check groups.
//
// This is complicated by the fact that optional content can be almost anywhere, including
// in patterns, soft masks, or glyphs in Type 3 fonts. So here to save some work we'll use
// a custom transform that will walk into every nook and cranny in the content for us, and
// avoid repeatedly revisiting shared resources.
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;
};



/**
 * Entry point for the command line tool.
 */
int main(const int argc, const char* argv[])
{
    try
    {
        const IJawsMakoPtr mako = IJawsMako::create();
        mako->enableAllFeatures(mako);
        const auto document = IPDFInput::create(mako)->open(argv[1])->getDocument();
        const auto optionalContent = document->getOptionalContent();

        // 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;
                }
            }
        }
    }
    catch (IError& e)
    {
        const String errorFormatString = getEDLErrorString(e.getErrorCode());
        std::wcerr << L"Mako exception thrown: " << e.getErrorDescription(errorFormatString) << std::endl;
        return 1;
    }
    catch (std::exception& e)
    {
        std::wcerr << L"std::exception thrown: " << e.what() << std::endl;
        return 1;
    }

    return 0;
}

C#
C#
/* --------------------------------------------------------------------------------
 *  <copyright file="OptionalContentSearch.cs" company="Global Graphics Software Ltd">
 *    Copyright (c) 2023 Global Graphics Software Ltd. All rights reserved.
 *  </copyright>
 *  <summary>
 *    This example is provided on an "as is" basis and without warranty of any kind.
 *    Global Graphics Software Ltd. does not warrant or make any representations
 *    regarding the use or results of use of this example.
 *  </summary>
 * ---------------------------------------------------------------------------------
 */

using JawsMako;

namespace OptionalContentSearch
{
    internal class OptionalContentSearch
    {
        static int Main(string[] args)
        {
            try
            {
                var mako = IJawsMako.create();
                using var document = IPDFInput.create(mako).open(args[0]).getDocument();

                // Input is a multi-page .pdf and on each page, we have different optional content (Layer).
                var optionalContent = document.getOptionalContent();

                // 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()}");
                    }
                }
            }
            catch (MakoException e)
            {
                Console.WriteLine("Exception thrown: " + e.m_msg);
                return 1;
            }
            catch (Exception e)
            {
                Console.WriteLine($"Exception thrown: {e}");
            }

            return 0;
        }

        /// <summary>
        /// Custom transform to find relevant optional content
        /// </summary>
        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;
        }
    }
}

☑️ 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.