Markup Annotation Class

Table of Contents

  1. Introduction
    1. Methods
      1. Example

        Introduction

        Many annotation types are defined as markup annotations because they are used primarily to mark up PDF documents. You may know these annotation types as entries in the comments pane in your PDF reader. To manage these annotation types the SetaPDF-Core component offers the SetaPDF_Core_Document_Page_Annotation_Markup class. 

        Following annotations are markup annotations:

        All annotations which are not explicit linked (high level implementation available) are still available through an instance of the SetaPDF_Core_Document_Page_Annotation_Markup class. 

        Methods

        The class implements getter and setter methods to access markup specific annotation data:

        addReply()

        Adds a reply to this annotation.

        createPopup()

        Create a popup annotation object for this annotation.

        getCreationDate()

        Get the creation date.

        getInReplyTo()

        Get the in reply to annotation (if available).

        getPopup()

        Get the associated popup object if available.

        getReplies()

        Get all annotations which refer this annotation as an reply.

        getSubject()

        Get the subject.

        getTextLabel()

        Get the text label.

        hasReplies()

        Check whether this annotation has a reply or not.

        isReplyTo()

        Checks if this annotation is a reply to another annotation.

        setCreationDate()

        Set the creation date.

        setInReplyTo()

        Set the in reply to annotation object.

        setPopup()

        Set the pop-up annotation object.

        setSubject()

        Get the subject.

        setTextLabel()

        Set the text label.

        Example

        As you may have noticed, by the available method names, it is possible to create a kind of comment structure for these annotation types. Following a demo, that dumps the comments outline from this document:

        PHP
        <?php
        require_once('library/SetaPDF/Autoload.php');
        
        class CommentsDumper
        {
            /**
             * @var SetaPDF_Core_Document
             */
            protected $_document;
        
            /**
             * The constructor
             *
             * @param SetaPDF_Core_Document $document
             */
            public function __construct(SetaPDF_Core_Document $document)
            {
                $this->_document = $document;
            }
        
            /**
             * Dump all comments
             */
            public function dump()
            {
                $pages = $this->_document->getCatalog()->getPages();
        
                // iterate over all available pages
                for ($pageNo = 1, $pageCount = $pages->count(); $pageNo <= $pageCount; $pageNo++) {
                    $annotations = $pages->getPage($pageNo)->getAnnotations();
                    $allAnnotations = $annotations->getAll();
                    $rootAnnotations = array();
                    // extract all root annotations
                    foreach ($allAnnotations AS $annotation) {
                        if (!$annotation instanceof SetaPDF_Core_Document_Page_Annotation_Markup) {
                            continue;
                        }
        
                        if ($annotation->isReplyTo()) {
                            continue;
                        }
        
                        $rootAnnotations[] = $annotation;
                    }
        
                    usort($rootAnnotations, array($this, '_orderByDate'));
                    foreach ($rootAnnotations AS $annotation) {
                        $this->_dumpReplies($annotations, $annotation);
                    }
                }
            }
        
            /**
             * Compare the dates of two annotation objects.
             *
             * @param SetaPDF_Core_Document_Page_Annotation_Markup $a
             * @param SetaPDF_Core_Document_Page_Annotation_Markup $b
             * @return integer
             */
            protected function _orderByDate(
                SetaPDF_Core_Document_Page_Annotation_Markup $a,
                SetaPDF_Core_Document_Page_Annotation_Markup $b
            )
            {
                $dateA = $a->getModificationDate(false) ? $a->getModificationDate(false) : $a->getCreationDate(false);
                $dateB = $b->getModificationDate(false) ? $b->getModificationDate(false) : $b->getCreationDate(false);
        
                $_a = (int)$dateA->getAsDateTime()->format('U');
                $_b = (int)$dateB->getAsDateTime()->format('U');
        
                return $_a < $_b ? -1 : 1;
            }
        
            /**
             * Dumps the replies (recursively).
             *
             * @param SetaPDF_Core_Document_Page_Annotations $annotations
             * @param SetaPDF_Core_Document_Page_Annotation $annotation
             * @param int $level
             */
            protected function _dumpReplies(
                SetaPDF_Core_Document_Page_Annotations $annotations,
                SetaPDF_Core_Document_Page_Annotation_Markup $annotation,
                $level = 0
            ) {
                echo str_repeat(' ', $level * 4);
                // get a date
                $date = $annotation->getModificationDate(false)
                      ? $annotation->getModificationDate(false)
                      : $annotation->getCreationDate(false);
                echo $date->getAsDateTime()->format('Y-m-d H:i:s') . ': ';
                echo $annotation->getTextLabel() . ' (' . $annotation->getSubject() . '): ' . $annotation->getContents() . "\n";
        
                // check if this annotation has replies
                if ($annotation->hasReplies($annotations)) {
                    // get the replies
                    $replies = $annotation->getReplies($annotations);
        
                    // states and replies are created in the same structure
                    $repliesByStateModel = array();
                    $realReplies = array();
        
                    // iterate over all replies and
                    foreach ($replies AS $reply) {
                        if ($reply->getStateModel()) {
                            // get last state for the current reply
                            while (true) {
                                $repliesByStateModel[$reply->getStateModel()][] = $reply;
                                $_replies = $reply->getReplies($annotations);
                                if (count($_replies) === 0)
                                    break;
                                $reply = $_replies[0];
                            }
        
                        } else {
                            $realReplies[] = $reply;
                        }
                    }
        
                    /* list the state changes by their state model:
                     * Foxit sorts this by Creation/Modification date.
                     * Acrobat simply takes the last one.
                     * We simply show all changes.
                     */
                    foreach ($repliesByStateModel AS $stateModel => $replies) {
                        echo str_repeat(' ', ($level + .5) * 4);
                        echo $stateModel . ":\n";
                        foreach ($replies AS $reply) {
                            echo str_repeat(' ', ($level + 1) * 4);
                            $date = $reply->getModificationDate(false) ? $reply->getModificationDate(false) : $reply->getCreationDate(false);
                            echo $date->getAsDateTime()->format('Y-m-d H:i:s') . ': ';
                            echo $reply->getState() . ' - ' . $annotation->getTextLabel() . "\n";
                        }
                    }
        
                    if (count($realReplies) == 0)
                        return;
        
                    // recursively dump further replies
                    echo str_repeat(' ', ($level + .5) * 4);
                    echo "Replies:\n";
                    usort($realReplies, array($this, '_orderByDate'));
                    foreach ($realReplies AS $reply) {
                        $this->_dumpReplies($annotations, $reply, $level + 1);
                    }
                }
            }
        }
        
        $filename = 'files/pdfs/Brand-Guide - with-comments.pdf';
        
        // load the document
        $document = SetaPDF_Core_Document::loadByFilename($filename);
        
        // pass it to the helper class
        $commentsDumper = new CommentsDumper($document);
        // and dump the comments hierarchy
        echo '<pre>';
        $commentsDumper->dump();
        echo '</pre>';