1 /* 2 * Licensed to the Apache Software Foundation (ASF) under one or more 3 * contributor license agreements. See the NOTICE file distributed with 4 * this work for additional information regarding copyright ownership. 5 * The ASF licenses this file to You under the Apache License, Version 2.0 6 * (the "License"); you may not use this file except in compliance with 7 * the License. You may obtain a copy of the License at 8 * 9 * http://www.apache.org/licenses/LICENSE-2.0 10 * 11 * Unless required by applicable law or agreed to in writing, software 12 * distributed under the License is distributed on an "AS IS" BASIS, 13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 * See the License for the specific language governing permissions and 15 * limitations under the License. 16 */ 17 package org.apache.pdfbox.pdmodel.documentinterchange.logicalstructure; 18 19 import java.util.ArrayList; 20 import java.util.Iterator; 21 import java.util.List; 22 23 import org.apache.pdfbox.cos.COSArray; 24 import org.apache.pdfbox.cos.COSBase; 25 import org.apache.pdfbox.cos.COSDictionary; 26 import org.apache.pdfbox.cos.COSInteger; 27 import org.apache.pdfbox.cos.COSName; 28 import org.apache.pdfbox.cos.COSObject; 29 import org.apache.pdfbox.pdmodel.common.COSArrayList; 30 import org.apache.pdfbox.pdmodel.common.COSObjectable; 31 import org.apache.pdfbox.pdmodel.graphics.xobject.PDXObject; 32 import org.apache.pdfbox.pdmodel.interactive.annotation.PDAnnotation; 33 34 /** 35 * A node in the structure tree. 36 * 37 * @author Koch 38 * @version $Revision: $ 39 */ 40 public abstract class PDStructureNode implements COSObjectable 41 { 42 43 /** 44 * Creates a node in the structure tree. Can be either a structure tree root, 45 * or a structure element. 46 * 47 * @param node the node dictionary 48 * @return the structure node 49 */ 50 public static PDStructureNode create(COSDictionary node) 51 { 52 String type = node.getNameAsString(COSName.TYPE); 53 if ("StructTreeRoot".equals(type)) 54 { 55 return new PDStructureTreeRoot(node); 56 } 57 if ((type == null) || "StructElem".equals(type)) 58 { 59 return new PDStructureElement(node); 60 } 61 throw new IllegalArgumentException("Dictionary must not include a Type entry with a value that is neither StructTreeRoot nor StructElem."); 62 } 63 64 65 private COSDictionary dictionary; 66 67 protected COSDictionary getCOSDictionary() 68 { 69 return dictionary; 70 } 71 72 /** 73 * Constructor. 74 * 75 * @param type the type 76 */ 77 protected PDStructureNode(String type) 78 { 79 this.dictionary = new COSDictionary(); 80 this.dictionary.setName(COSName.TYPE, type); 81 } 82 83 /** 84 * Constructor for an existing structure node. 85 * 86 * @param dictionary The existing dictionary. 87 */ 88 protected PDStructureNode(COSDictionary dictionary) 89 { 90 this.dictionary = dictionary; 91 } 92 93 /** 94 * {@inheritDoc} 95 */ 96 public COSBase getCOSObject() 97 { 98 return this.dictionary; 99 } 100 101 /** 102 * Returns the type. 103 * 104 * @return the type 105 */ 106 public String getType() 107 { 108 return this.getCOSDictionary().getNameAsString(COSName.TYPE); 109 } 110 111 /** 112 * Returns a list of objects for the kids (K). 113 * 114 * @return a list of objects for the kids 115 */ 116 public List<Object> getKids() 117 { 118 List<Object> kidObjects = new ArrayList<Object>(); 119 COSBase k = this.getCOSDictionary().getDictionaryObject(COSName.K); 120 if (k instanceof COSArray) 121 { 122 Iterator<COSBase> kids = ((COSArray) k).iterator(); 123 while (kids.hasNext()) 124 { 125 COSBase kid = kids.next(); 126 Object kidObject = this.createObject(kid); 127 if (kidObject != null) 128 { 129 kidObjects.add(kidObject); 130 } 131 } 132 } 133 else 134 { 135 Object kidObject = this.createObject(k); 136 if (kidObject != null) 137 { 138 kidObjects.add(kidObject); 139 } 140 } 141 return kidObjects; 142 } 143 144 /** 145 * Sets the kids (K). 146 * 147 * @param kids the kids 148 */ 149 public void setKids(List<Object> kids) 150 { 151 this.getCOSDictionary().setItem(COSName.K, 152 COSArrayList.converterToCOSArray(kids)); 153 } 154 155 /** 156 * Appends a structure element kid. 157 * 158 * @param structureElement the structure element 159 */ 160 public void appendKid(PDStructureElement structureElement) 161 { 162 this.appendObjectableKid(structureElement); 163 structureElement.setParent(this); 164 } 165 166 /** 167 * Appends an objectable kid. 168 * 169 * @param objectable the objectable 170 */ 171 protected void appendObjectableKid(COSObjectable objectable) 172 { 173 if (objectable == null) 174 { 175 return; 176 } 177 this.appendKid(objectable.getCOSObject()); 178 } 179 180 /** 181 * Appends a COS base kid. 182 * 183 * @param object the COS base 184 */ 185 protected void appendKid(COSBase object) 186 { 187 if (object == null) 188 { 189 return; 190 } 191 COSBase k = this.getCOSDictionary().getDictionaryObject(COSName.K); 192 if (k == null) 193 { 194 // currently no kid: set new kid as kids 195 this.getCOSDictionary().setItem(COSName.K, object); 196 } 197 else if (k instanceof COSArray) 198 { 199 // currently more than one kid: add new kid to existing array 200 COSArray array = (COSArray) k; 201 array.add(object); 202 } 203 else 204 { 205 // currently one kid: put current and new kid into array and set array as kids 206 COSArray array = new COSArray(); 207 array.add(k); 208 array.add(object); 209 this.getCOSDictionary().setItem(COSName.K, array); 210 } 211 } 212 213 /** 214 * Inserts a structure element kid before a reference kid. 215 * 216 * @param newKid the structure element 217 * @param refKid the reference kid 218 */ 219 public void insertBefore(PDStructureElement newKid, Object refKid) 220 { 221 this.insertObjectableBefore(newKid, refKid); 222 } 223 224 /** 225 * Inserts an objectable kid before a reference kid. 226 * 227 * @param newKid the objectable 228 * @param refKid the reference kid 229 */ 230 protected void insertObjectableBefore(COSObjectable newKid, Object refKid) 231 { 232 if (newKid == null) 233 { 234 return; 235 } 236 this.insertBefore(newKid.getCOSObject(), refKid); 237 } 238 239 /** 240 * Inserts an COS base kid before a reference kid. 241 * 242 * @param newKid the COS base 243 * @param refKid the reference kid 244 */ 245 protected void insertBefore(COSBase newKid, Object refKid) 246 { 247 if ((newKid == null) || (refKid == null)) 248 { 249 return; 250 } 251 COSBase k = this.getCOSDictionary().getDictionaryObject(COSName.K); 252 if (k == null) 253 { 254 return; 255 } 256 COSBase refKidBase = null; 257 if (refKid instanceof COSObjectable) 258 { 259 refKidBase = ((COSObjectable) refKid).getCOSObject(); 260 } 261 else if (refKid instanceof COSInteger) 262 { 263 refKidBase = (COSInteger) refKid; 264 } 265 if (k instanceof COSArray) 266 { 267 COSArray array = (COSArray) k; 268 int refIndex = array.indexOfObject(refKidBase); 269 array.add(refIndex, newKid.getCOSObject()); 270 } 271 else 272 { 273 boolean onlyKid = k.equals(refKidBase); 274 if (!onlyKid && (k instanceof COSObject)) 275 { 276 COSBase kObj = ((COSObject) k).getObject(); 277 onlyKid = kObj.equals(refKidBase); 278 } 279 if (onlyKid) 280 { 281 COSArray array = new COSArray(); 282 array.add(newKid); 283 array.add(refKidBase); 284 this.getCOSDictionary().setItem(COSName.K, array); 285 } 286 } 287 } 288 289 /** 290 * Removes a structure element kid. 291 * 292 * @param structureElement the structure element 293 * @return <code>true</code> if the kid was removed, <code>false</code> otherwise 294 */ 295 public boolean removeKid(PDStructureElement structureElement) 296 { 297 boolean removed = this.removeObjectableKid(structureElement); 298 if (removed) 299 { 300 structureElement.setParent(null); 301 } 302 return removed; 303 } 304 305 /** 306 * Removes an objectable kid. 307 * 308 * @param objectable the objectable 309 * @return <code>true</code> if the kid was removed, <code>false</code> otherwise 310 */ 311 protected boolean removeObjectableKid(COSObjectable objectable) 312 { 313 if (objectable == null) 314 { 315 return false; 316 } 317 return this.removeKid(objectable.getCOSObject()); 318 } 319 320 /** 321 * Removes a COS base kid. 322 * 323 * @param object the COS base 324 * @return <code>true</code> if the kid was removed, <code>false</code> otherwise 325 */ 326 protected boolean removeKid(COSBase object) 327 { 328 if (object == null) 329 { 330 return false; 331 } 332 COSBase k = this.getCOSDictionary().getDictionaryObject(COSName.K); 333 if (k == null) 334 { 335 // no kids: objectable is not a kid 336 return false; 337 } 338 else if (k instanceof COSArray) 339 { 340 // currently more than one kid: remove kid from existing array 341 COSArray array = (COSArray) k; 342 boolean removed = array.removeObject(object); 343 // if now only one kid: set remaining kid as kids 344 if (array.size() == 1) 345 { 346 this.getCOSDictionary().setItem(COSName.K, array.getObject(0)); 347 } 348 return removed; 349 } 350 else 351 { 352 // currently one kid: if current kid equals given object, remove kids entry 353 boolean onlyKid = k.equals(object); 354 if (!onlyKid && (k instanceof COSObject)) 355 { 356 COSBase kObj = ((COSObject) k).getObject(); 357 onlyKid = kObj.equals(object); 358 } 359 if (onlyKid) 360 { 361 this.getCOSDictionary().setItem(COSName.K, null); 362 return true; 363 } 364 return false; 365 } 366 } 367 368 /** 369 * Creates an object for a kid of this structure node. 370 * The type of object depends on the type of the kid. It can be 371 * <ul> 372 * <li>a {@link PDStructureElement},</li> 373 * <li>a {@link PDAnnotation},</li> 374 * <li>a {@link PDXObject},</li> 375 * <li>a {@link PDMarkedContentReference}</li> 376 * <li>a {@link Integer}</li> 377 * </ul> 378 * 379 * @param kid the kid 380 * @return the object 381 */ 382 protected Object createObject(COSBase kid) 383 { 384 COSDictionary kidDic = null; 385 if (kid instanceof COSDictionary) 386 { 387 kidDic = (COSDictionary) kid; 388 } 389 else if (kid instanceof COSObject) 390 { 391 COSBase base = ((COSObject) kid).getObject(); 392 if (base instanceof COSDictionary) 393 { 394 kidDic = (COSDictionary) base; 395 } 396 } 397 if (kidDic != null) 398 { 399 String type = kidDic.getNameAsString(COSName.TYPE); 400 if ((type == null) || PDStructureElement.TYPE.equals(type)) 401 { 402 // A structure element dictionary denoting another structure 403 // element 404 return new PDStructureElement(kidDic); 405 } 406 else if (PDObjectReference.TYPE.equals(type)) 407 { 408 // An object reference dictionary denoting a PDF object 409 return new PDObjectReference(kidDic); 410 } 411 else if (PDMarkedContentReference.TYPE.equals(type)) 412 { 413 // A marked-content reference dictionary denoting a 414 // marked-content sequence 415 return new PDMarkedContentReference(kidDic); 416 } 417 } 418 else if (kid instanceof COSInteger) 419 { 420 // An integer marked-content identifier denoting a 421 // marked-content sequence 422 COSInteger mcid = (COSInteger) kid; 423 return mcid.intValue(); 424 } 425 return null; 426 } 427 428 }