/* * 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.documentinterchange.logicalstructure; import java.util.ArrayList; import java.util.Iterator; import java.util.List; import org.apache.pdfbox.cos.COSArray; import org.apache.pdfbox.cos.COSBase; import org.apache.pdfbox.cos.COSDictionary; import org.apache.pdfbox.cos.COSInteger; import org.apache.pdfbox.cos.COSName; import org.apache.pdfbox.cos.COSObject; import org.apache.pdfbox.pdmodel.common.COSArrayList; import org.apache.pdfbox.pdmodel.common.COSObjectable; import org.apache.pdfbox.pdmodel.graphics.xobject.PDXObject; import org.apache.pdfbox.pdmodel.interactive.annotation.PDAnnotation; /** * A node in the structure tree. * * @author Koch * @version $Revision: $ */ public abstract class PDStructureNode implements COSObjectable { /** * Creates a node in the structure tree. Can be either a structure tree root, * or a structure element. * * @param node the node dictionary * @return the structure node */ public static PDStructureNode create(COSDictionary node) { String type = node.getNameAsString(COSName.TYPE); if ("StructTreeRoot".equals(type)) { return new PDStructureTreeRoot(node); } if ((type == null) || "StructElem".equals(type)) { return new PDStructureElement(node); } throw new IllegalArgumentException("Dictionary must not include a Type entry with a value that is neither StructTreeRoot nor StructElem."); } private COSDictionary dictionary; protected COSDictionary getCOSDictionary() { return dictionary; } /** * Constructor. * * @param type the type */ protected PDStructureNode(String type) { this.dictionary = new COSDictionary(); this.dictionary.setName(COSName.TYPE, type); } /** * Constructor for an existing structure node. * * @param dictionary The existing dictionary. */ protected PDStructureNode(COSDictionary dictionary) { this.dictionary = dictionary; } /** * {@inheritDoc} */ public COSBase getCOSObject() { return this.dictionary; } /** * Returns the type. * * @return the type */ public String getType() { return this.getCOSDictionary().getNameAsString(COSName.TYPE); } /** * Returns a list of objects for the kids (K). * * @return a list of objects for the kids */ public List<Object> getKids() { List<Object> kidObjects = new ArrayList<Object>(); COSBase k = this.getCOSDictionary().getDictionaryObject(COSName.K); if (k instanceof COSArray) { Iterator<COSBase> kids = ((COSArray) k).iterator(); while (kids.hasNext()) { COSBase kid = kids.next(); Object kidObject = this.createObject(kid); if (kidObject != null) { kidObjects.add(kidObject); } } } else { Object kidObject = this.createObject(k); if (kidObject != null) { kidObjects.add(kidObject); } } return kidObjects; } /** * Sets the kids (K). * * @param kids the kids */ public void setKids(List<Object> kids) { this.getCOSDictionary().setItem(COSName.K, COSArrayList.converterToCOSArray(kids)); } /** * Appends a structure element kid. * * @param structureElement the structure element */ public void appendKid(PDStructureElement structureElement) { this.appendObjectableKid(structureElement); structureElement.setParent(this); } /** * Appends an objectable kid. * * @param objectable the objectable */ protected void appendObjectableKid(COSObjectable objectable) { if (objectable == null) { return; } this.appendKid(objectable.getCOSObject()); } /** * Appends a COS base kid. * * @param object the COS base */ protected void appendKid(COSBase object) { if (object == null) { return; } COSBase k = this.getCOSDictionary().getDictionaryObject(COSName.K); if (k == null) { // currently no kid: set new kid as kids this.getCOSDictionary().setItem(COSName.K, object); } else if (k instanceof COSArray) { // currently more than one kid: add new kid to existing array COSArray array = (COSArray) k; array.add(object); } else { // currently one kid: put current and new kid into array and set array as kids COSArray array = new COSArray(); array.add(k); array.add(object); this.getCOSDictionary().setItem(COSName.K, array); } } /** * Inserts a structure element kid before a reference kid. * * @param newKid the structure element * @param refKid the reference kid */ public void insertBefore(PDStructureElement newKid, Object refKid) { this.insertObjectableBefore(newKid, refKid); } /** * Inserts an objectable kid before a reference kid. * * @param newKid the objectable * @param refKid the reference kid */ protected void insertObjectableBefore(COSObjectable newKid, Object refKid) { if (newKid == null) { return; } this.insertBefore(newKid.getCOSObject(), refKid); } /** * Inserts an COS base kid before a reference kid. * * @param newKid the COS base * @param refKid the reference kid */ protected void insertBefore(COSBase newKid, Object refKid) { if ((newKid == null) || (refKid == null)) { return; } COSBase k = this.getCOSDictionary().getDictionaryObject(COSName.K); if (k == null) { return; } COSBase refKidBase = null; if (refKid instanceof COSObjectable) { refKidBase = ((COSObjectable) refKid).getCOSObject(); } else if (refKid instanceof COSInteger) { refKidBase = (COSInteger) refKid; } if (k instanceof COSArray) { COSArray array = (COSArray) k; int refIndex = array.indexOfObject(refKidBase); array.add(refIndex, newKid.getCOSObject()); } else { boolean onlyKid = k.equals(refKidBase); if (!onlyKid && (k instanceof COSObject)) { COSBase kObj = ((COSObject) k).getObject(); onlyKid = kObj.equals(refKidBase); } if (onlyKid) { COSArray array = new COSArray(); array.add(newKid); array.add(refKidBase); this.getCOSDictionary().setItem(COSName.K, array); } } } /** * Removes a structure element kid. * * @param structureElement the structure element * @return <code>true</code> if the kid was removed, <code>false</code> otherwise */ public boolean removeKid(PDStructureElement structureElement) { boolean removed = this.removeObjectableKid(structureElement); if (removed) { structureElement.setParent(null); } return removed; } /** * Removes an objectable kid. * * @param objectable the objectable * @return <code>true</code> if the kid was removed, <code>false</code> otherwise */ protected boolean removeObjectableKid(COSObjectable objectable) { if (objectable == null) { return false; } return this.removeKid(objectable.getCOSObject()); } /** * Removes a COS base kid. * * @param object the COS base * @return <code>true</code> if the kid was removed, <code>false</code> otherwise */ protected boolean removeKid(COSBase object) { if (object == null) { return false; } COSBase k = this.getCOSDictionary().getDictionaryObject(COSName.K); if (k == null) { // no kids: objectable is not a kid return false; } else if (k instanceof COSArray) { // currently more than one kid: remove kid from existing array COSArray array = (COSArray) k; boolean removed = array.removeObject(object); // if now only one kid: set remaining kid as kids if (array.size() == 1) { this.getCOSDictionary().setItem(COSName.K, array.getObject(0)); } return removed; } else { // currently one kid: if current kid equals given object, remove kids entry boolean onlyKid = k.equals(object); if (!onlyKid && (k instanceof COSObject)) { COSBase kObj = ((COSObject) k).getObject(); onlyKid = kObj.equals(object); } if (onlyKid) { this.getCOSDictionary().setItem(COSName.K, null); return true; } return false; } } /** * Creates an object for a kid of this structure node. * The type of object depends on the type of the kid. It can be * <ul> * <li>a {@link PDStructureElement},</li> * <li>a {@link PDAnnotation},</li> * <li>a {@link PDXObject},</li> * <li>a {@link PDMarkedContentReference}</li> * <li>a {@link Integer}</li> * </ul> * * @param kid the kid * @return the object */ protected Object createObject(COSBase kid) { COSDictionary kidDic = null; if (kid instanceof COSDictionary) { kidDic = (COSDictionary) kid; } else if (kid instanceof COSObject) { COSBase base = ((COSObject) kid).getObject(); if (base instanceof COSDictionary) { kidDic = (COSDictionary) base; } } if (kidDic != null) { String type = kidDic.getNameAsString(COSName.TYPE); if ((type == null) || PDStructureElement.TYPE.equals(type)) { // A structure element dictionary denoting another structure // element return new PDStructureElement(kidDic); } else if (PDObjectReference.TYPE.equals(type)) { // An object reference dictionary denoting a PDF object return new PDObjectReference(kidDic); } else if (PDMarkedContentReference.TYPE.equals(type)) { // A marked-content reference dictionary denoting a // marked-content sequence return new PDMarkedContentReference(kidDic); } } else if (kid instanceof COSInteger) { // An integer marked-content identifier denoting a // marked-content sequence COSInteger mcid = (COSInteger) kid; return mcid.intValue(); } return null; } }