# PDOutlineItem.java

    /*
     * Licensed to the Apache Software Foundation (ASF) under one or more
     * contributor license agreements.  See the NOTICE file distributed with
     * this work for additional information regarding copyright ownership.
     * The ASF licenses this file to You under the Apache License, Version 2.0
     * (the "License"); you may not use this file except in compliance with
     * the License.  You may obtain a copy of the License at
     *
     *      http://www.apache.org/licenses/LICENSE-2.0
     *
     * Unless required by applicable law or agreed to in writing, software
     * distributed under the License is distributed on an "AS IS" BASIS,
     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     * See the License for the specific language governing permissions and
     * limitations under the License.
     */
    package org.apache.pdfbox.pdmodel.interactive.documentnavigation.outline;

    import java.awt.Color;
    import java.io.IOException;

    import org.apache.pdfbox.cos.COSArray;
    import org.apache.pdfbox.cos.COSDictionary;
    import org.apache.pdfbox.cos.COSFloat;
    import org.apache.pdfbox.cos.COSName;
    import org.apache.pdfbox.pdmodel.PDDocument;
    import org.apache.pdfbox.pdmodel.PDPage;
    import org.apache.pdfbox.pdmodel.documentinterchange.logicalstructure.PDStructureElement;
    import org.apache.pdfbox.pdmodel.graphics.color.PDColor;
    import org.apache.pdfbox.pdmodel.graphics.color.PDDeviceRGB;
    import org.apache.pdfbox.pdmodel.interactive.action.PDActionFactory;
    import org.apache.pdfbox.pdmodel.interactive.action.PDAction;
    import org.apache.pdfbox.pdmodel.interactive.action.PDActionGoTo;
    import org.apache.pdfbox.pdmodel.interactive.documentnavigation.destination.PDDestination;
    import org.apache.pdfbox.pdmodel.interactive.documentnavigation.destination.PDNamedDestination;
    import org.apache.pdfbox.pdmodel.interactive.documentnavigation.destination.PDPageDestination;
    import org.apache.pdfbox.pdmodel.interactive.documentnavigation.destination.PDPageXYZDestination;

    /**
     * This represents an outline item in a pdf document. The items at each level of the hierarchy form an iterable linked
     * list, chained together through their Prev and Next entries.
     *
     * @author Ben Litchfield
     */
    public final class PDOutlineItem extends PDOutlineNode
    {
        private static final int ITALIC_FLAG = 1;
        private static final int BOLD_FLAG = 2;

        /**
         * Default Constructor.
         */
        public PDOutlineItem()
        {
            super();
        }

        /**
         * Constructor for an existing outline item.
         *
         * @param dic The storage dictionary.
         */
        public PDOutlineItem( COSDictionary dic )
        {
            super( dic );
        }

        /**
         * Insert a single sibling after this node.
         *
         * @param newSibling The item to insert.
         * @throws IllegalArgumentException if the given sibling node is part of a list
         * (i.e. if it has a previous or a next sibling)
         */
        public void insertSiblingAfter(PDOutlineItem newSibling)
        {
            requireSingleNode(newSibling);
            PDOutlineNode parent = getParent();
            newSibling.setParent(parent);
            PDOutlineItem next = getNextSibling();
            setNextSibling(newSibling);
            newSibling.setPreviousSibling(this);
            if (next != null)
            {
                newSibling.setNextSibling(next);
                next.setPreviousSibling(newSibling);
            }
            else if (parent != null)
            {
                getParent().setLastChild(newSibling);
            }
            updateParentOpenCountForAddedChild(newSibling);
        }

        /**
         * Insert a single sibling before this node.
         *
         * @param newSibling The item to insert.
         * @throws IllegalArgumentException if the given sibling node is part of a list
         * (i.e. if it has a previous or a next sibling)
         */
        public void insertSiblingBefore(PDOutlineItem newSibling)
        {
            requireSingleNode(newSibling);
            PDOutlineNode parent = getParent();
            newSibling.setParent(parent);
            PDOutlineItem previous = getPreviousSibling();
            setPreviousSibling(newSibling);
            newSibling.setNextSibling(this);
            if (previous != null)
            {
                previous.setNextSibling(newSibling);
                newSibling.setPreviousSibling(previous);
            }
            else if (parent != null)
            {
                getParent().setFirstChild(newSibling);
            }
            updateParentOpenCountForAddedChild(newSibling);
        }

        /**
         * Return the previous sibling or null if there is no sibling.
         *
         * @return The previous sibling.
         */
        public PDOutlineItem getPreviousSibling()
        {
            return getOutlineItem(COSName.PREV);
        }

        /**
         * Set the previous sibling, this will be maintained by this class.
         *
         * @param outlineNode The new previous sibling.
         */
        void setPreviousSibling(PDOutlineNode outlineNode)
        {
            getCOSObject().setItem(COSName.PREV, outlineNode);
        }

        /**
         * @return The next sibling or null if there is no next sibling.
         */
        public PDOutlineItem getNextSibling()
        {
            return getOutlineItem(COSName.NEXT);
        }

        /**
         * Set the next sibling, this will be maintained by this class.
         *
         * @param outlineNode The new next sibling.
         */
        void setNextSibling(PDOutlineNode outlineNode)
        {
            getCOSObject().setItem(COSName.NEXT, outlineNode);
        }

        /**
         * Get the title of this node.
         *
         * @return The title of this node.
         */
        public String getTitle()
        {
            return getCOSObject().getString(COSName.TITLE);
        }

        /**
         * Set the title for this node.
         *
         * @param title The new title for this node.
         */
        public void setTitle(String title)
        {
            getCOSObject().setString(COSName.TITLE, title);
        }

        /**
         * Get the page destination of this node.
         *
         * @return The page destination of this node.
         * @throws IOException If there is an error creating the destination.
         */
        public PDDestination getDestination() throws IOException
        {
            return PDDestination.create(getCOSObject().getDictionaryObject(COSName.DEST));
        }

        /**
         * Set the page destination for this node.
         *
         * @param dest The new page destination for this node.
         */
        public void setDestination(PDDestination dest)
        {
            getCOSObject().setItem(COSName.DEST, dest);
        }

        /**
         * A convenience method that will create an XYZ destination using only the defaults.
         *
         * @param page The page to refer to.
         */
        public void setDestination(PDPage page)
        {
            PDPageXYZDestination dest = null;
            if( page != null )
            {
                dest = new PDPageXYZDestination();
                dest.setPage( page );
            }
            setDestination( dest );
        }

        /**
         * This method will attempt to find the page in this PDF document that this outline points to.
         * If the outline does not point to anything then this method will return null. If the outline
         * is an action that is not a GoTo action then this method will also return null.
         *
         * @param doc The document to get the page from.
         *
         * @return The page that this outline will go to when activated or null if it does not point to
         * anything.
         * @throws IOException If there is an error when trying to find the page.
         */
        public PDPage findDestinationPage(PDDocument doc) throws IOException
        {
            PDDestination dest = getDestination();
            if (dest == null)
            {
                PDAction outlineAction = getAction();
                if (outlineAction instanceof PDActionGoTo)
                {
                    dest = ((PDActionGoTo) outlineAction).getDestination();
                }
            }
            if (dest == null)
            {
                return null;
            }

            PDPageDestination pageDestination = null;
            if (dest instanceof PDNamedDestination)
            {
                pageDestination = doc.getDocumentCatalog().findNamedDestinationPage((PDNamedDestination) dest);
                if (pageDestination == null)
                {
                    return null;
                }
            }
            else if (dest instanceof PDPageDestination)
            {
                pageDestination = (PDPageDestination) dest;
            }
            else
            {
                throw new IOException("Error: Unknown destination type " + dest);
            }

            PDPage page = pageDestination.getPage();
            if (page == null)
            {
                // Malformed PDF: local destinations must have a page object,
                // not a page number, these are meant for remote destinations.
                int pageNumber = pageDestination.getPageNumber();
                if (pageNumber != -1)
                {
                    page = doc.getPage(pageNumber);
                }
            }
            return page;
        }

        /**
         * Get the action of this node.
         *
         * @return The action of this node.
         */
        public PDAction getAction()
        {
            return PDActionFactory.createAction(getCOSObject().getCOSDictionary(COSName.A));
        }

        /**
         * Set the action for this node.
         *
         * @param action The new action for this node.
         */
        public void setAction( PDAction action )
        {
            getCOSObject().setItem(COSName.A, action);
        }

        /**
         * Get the structure element of this node.
         *
         * @return The structure element of this node.
         */
        public PDStructureElement getStructureElement()
        {
            COSDictionary dic = getCOSObject().getCOSDictionary(COSName.SE);
            return dic != null ? new PDStructureElement(dic) : null;
        }

        /**
         * Set the structure element for this node.
         *
         * @param structureElement The new structure element for this node.
         */
        public void setStructureElement( PDStructureElement structureElement )
        {
            getCOSObject().setItem(COSName.SE, structureElement);
        }

        /**
         * Get the RGB text color of this node.  Default is black and this method
         * will never return null.
         *
         * @return The structure element of this node.
         */
        public PDColor getTextColor()
        {
            COSArray csValues = getCOSObject().getCOSArray(COSName.C);
            if( csValues == null )
            {
                csValues = new COSArray();
                csValues.growToSize( 3, new COSFloat( 0 ) );
                getCOSObject().setItem( COSName.C, csValues );
            }
            return new PDColor(csValues, PDDeviceRGB.INSTANCE);
        }

        /**
         * Set the RGB text color for this node.
         *
         * @param textColor The text color for this node.
         */
        public void setTextColor( PDColor textColor )
        {
            getCOSObject().setItem( COSName.C, textColor.toCOSArray() );
        }

        /**
         * Set the RGB text color for this node.
         *
         * @param textColor The text color for this node.
         */
        public void setTextColor( Color textColor )
        {
            COSArray array = new COSArray();
            array.add( new COSFloat( textColor.getRed()/255f));
            array.add( new COSFloat( textColor.getGreen()/255f));
            array.add( new COSFloat( textColor.getBlue()/255f));
            getCOSObject().setItem( COSName.C, array );
        }

        /**
         * A flag telling if the text should be italic.
         *
         * @return The italic flag.
         */
        public boolean isItalic()
        {
            return getCOSObject().getFlag( COSName.F, ITALIC_FLAG );
        }

        /**
         * Set the italic property of the text.
         *
         * @param italic The new italic flag.
         */
        public void setItalic( boolean italic )
        {
            getCOSObject().setFlag( COSName.F, ITALIC_FLAG, italic );
        }

        /**
         * A flag telling if the text should be bold.
         *
         * @return The bold flag.
         */
        public boolean isBold()
        {
            return getCOSObject().getFlag( COSName.F, BOLD_FLAG );
        }

        /**
         * Set the bold property of the text.
         *
         * @param bold The new bold flag.
         */
        public void setBold( boolean bold )
        {
            getCOSObject().setFlag( COSName.F, BOLD_FLAG, bold );
        }

    }