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.interactive.documentnavigation.outline; 18 19 import java.awt.Color; 20 import java.io.IOException; 21 import java.util.List; 22 23 import org.apache.pdfbox.cos.COSArray; 24 import org.apache.pdfbox.cos.COSDictionary; 25 import org.apache.pdfbox.cos.COSFloat; 26 import org.apache.pdfbox.exceptions.OutlineNotLocalException; 27 import org.apache.pdfbox.pdmodel.PDDestinationNameTreeNode; 28 import org.apache.pdfbox.pdmodel.PDDocument; 29 import org.apache.pdfbox.pdmodel.PDDocumentNameDictionary; 30 import org.apache.pdfbox.pdmodel.PDPage; 31 import org.apache.pdfbox.pdmodel.documentinterchange.logicalstructure.PDStructureElement; 32 import org.apache.pdfbox.pdmodel.graphics.color.PDColorState; 33 import org.apache.pdfbox.pdmodel.graphics.color.PDDeviceRGB; 34 import org.apache.pdfbox.pdmodel.interactive.action.PDActionFactory; 35 import org.apache.pdfbox.pdmodel.interactive.action.type.PDAction; 36 import org.apache.pdfbox.pdmodel.interactive.action.type.PDActionGoTo; 37 import org.apache.pdfbox.pdmodel.interactive.documentnavigation.destination.PDDestination; 38 import org.apache.pdfbox.pdmodel.interactive.documentnavigation.destination.PDNamedDestination; 39 import org.apache.pdfbox.pdmodel.interactive.documentnavigation.destination.PDPageDestination; 40 import org.apache.pdfbox.pdmodel.interactive.documentnavigation.destination.PDPageXYZDestination; 41 import org.apache.pdfbox.util.BitFlagHelper; 42 43 /** 44 * This represents an outline in a pdf document. 45 * 46 * @author <a href="mailto:ben@benlitchfield.com">Ben Litchfield</a> 47 * @version $Revision: 1.7 $ 48 */ 49 public class PDOutlineItem extends PDOutlineNode 50 { 51 52 private static final int ITALIC_FLAG = 1; 53 private static final int BOLD_FLAG = 2; 54 55 /** 56 * Default Constructor. 57 */ 58 public PDOutlineItem() 59 { 60 super(); 61 } 62 63 /** 64 * Constructor for an existing outline item. 65 * 66 * @param dic The storage dictionary. 67 */ 68 public PDOutlineItem( COSDictionary dic ) 69 { 70 super( dic ); 71 } 72 73 /** 74 * Insert a sibling after this node. 75 * 76 * @param item The item to insert. 77 */ 78 public void insertSiblingAfter( PDOutlineItem item ) 79 { 80 item.setParent( getParent() ); 81 PDOutlineItem next = getNextSibling(); 82 setNextSibling( item ); 83 item.setPreviousSibling( this ); 84 if( next != null ) 85 { 86 item.setNextSibling( next ); 87 next.setPreviousSibling( item ); 88 } 89 updateParentOpenCount( 1 ); 90 } 91 92 /** 93 * {@inheritDoc} 94 */ 95 public PDOutlineNode getParent() 96 { 97 return super.getParent(); 98 } 99 100 /** 101 * Return the previous sibling or null if there is no sibling. 102 * 103 * @return The previous sibling. 104 */ 105 public PDOutlineItem getPreviousSibling() 106 { 107 PDOutlineItem last = null; 108 COSDictionary lastDic = (COSDictionary)node.getDictionaryObject( "Prev" ); 109 if( lastDic != null ) 110 { 111 last = new PDOutlineItem( lastDic ); 112 } 113 return last; 114 } 115 116 /** 117 * Set the previous sibling, this will be maintained by this class. 118 * 119 * @param outlineNode The new previous sibling. 120 */ 121 protected void setPreviousSibling( PDOutlineNode outlineNode ) 122 { 123 node.setItem( "Prev", outlineNode ); 124 } 125 126 /** 127 * Return the next sibling or null if there is no next sibling. 128 * 129 * @return The next sibling. 130 */ 131 public PDOutlineItem getNextSibling() 132 { 133 PDOutlineItem last = null; 134 COSDictionary lastDic = (COSDictionary)node.getDictionaryObject( "Next" ); 135 if( lastDic != null ) 136 { 137 last = new PDOutlineItem( lastDic ); 138 } 139 return last; 140 } 141 142 /** 143 * Set the next sibling, this will be maintained by this class. 144 * 145 * @param outlineNode The new next sibling. 146 */ 147 protected void setNextSibling( PDOutlineNode outlineNode ) 148 { 149 node.setItem( "Next", outlineNode ); 150 } 151 152 /** 153 * Get the title of this node. 154 * 155 * @return The title of this node. 156 */ 157 public String getTitle() 158 { 159 return node.getString( "Title" ); 160 } 161 162 /** 163 * Set the title for this node. 164 * 165 * @param title The new title for this node. 166 */ 167 public void setTitle( String title ) 168 { 169 node.setString( "Title", title ); 170 } 171 172 /** 173 * Get the page destination of this node. 174 * 175 * @return The page destination of this node. 176 * @throws IOException If there is an error creating the destination. 177 */ 178 public PDDestination getDestination() throws IOException 179 { 180 return PDDestination.create( node.getDictionaryObject( "Dest" ) ); 181 } 182 183 /** 184 * Set the page destination for this node. 185 * 186 * @param dest The new page destination for this node. 187 */ 188 public void setDestination( PDDestination dest ) 189 { 190 node.setItem( "Dest", dest ); 191 } 192 193 /** 194 * A convenience method that will create an XYZ destination using only the defaults. 195 * 196 * @param page The page to refer to. 197 */ 198 public void setDestination( PDPage page ) 199 { 200 PDPageXYZDestination dest = null; 201 if( page != null ) 202 { 203 dest = new PDPageXYZDestination(); 204 dest.setPage( page ); 205 } 206 setDestination( dest ); 207 } 208 209 /** 210 * This method will attempt to find the page in this PDF document that this outline points to. 211 * If the outline does not point to anything then this method will return null. If the outline 212 * is an action that is not a GoTo action then this methods will throw the OutlineNotLocationException 213 * 214 * @param doc The document to get the page from. 215 * 216 * @return The page that this outline will go to when activated or null if it does not point to anything. 217 * @throws IOException If there is an error when trying to find the page. 218 */ 219 public PDPage findDestinationPage( PDDocument doc ) throws IOException 220 { 221 PDPage page = null; 222 PDDestination rawDest = getDestination(); 223 if( rawDest == null ) 224 { 225 PDAction outlineAction = getAction(); 226 if( outlineAction instanceof PDActionGoTo ) 227 { 228 rawDest = ((PDActionGoTo)outlineAction).getDestination(); 229 } 230 else if( outlineAction == null ) 231 { 232 //if the outline action is null then this outline does not refer 233 //to anything and we will just return null. 234 } 235 else 236 { 237 throw new OutlineNotLocalException( "Error: Outline does not reference a local page." ); 238 } 239 } 240 241 PDPageDestination pageDest = null; 242 if( rawDest instanceof PDNamedDestination ) 243 { 244 //if we have a named destination we need to lookup the PDPageDestination 245 PDNamedDestination namedDest = (PDNamedDestination)rawDest; 246 PDDocumentNameDictionary namesDict = doc.getDocumentCatalog().getNames(); 247 if( namesDict != null ) 248 { 249 PDDestinationNameTreeNode destsTree = namesDict.getDests(); 250 if( destsTree != null ) 251 { 252 pageDest = (PDPageDestination)destsTree.getValue( namedDest.getNamedDestination() ); 253 } 254 } 255 } 256 else if( rawDest instanceof PDPageDestination) 257 { 258 pageDest = (PDPageDestination) rawDest; 259 } 260 else if( rawDest == null ) 261 { 262 //if the destination is null then we will simply return a null page. 263 } 264 else 265 { 266 throw new IOException( "Error: Unknown destination type " + rawDest ); 267 } 268 269 if( pageDest != null ) 270 { 271 page = pageDest.getPage(); 272 if( page == null ) 273 { 274 int pageNumber = pageDest.getPageNumber(); 275 if( pageNumber != -1 ) 276 { 277 List allPages = doc.getDocumentCatalog().getAllPages(); 278 page = (PDPage)allPages.get( pageNumber ); 279 } 280 } 281 } 282 283 return page; 284 } 285 286 /** 287 * Get the action of this node. 288 * 289 * @return The action of this node. 290 */ 291 public PDAction getAction() 292 { 293 return PDActionFactory.createAction( (COSDictionary)node.getDictionaryObject( "A" ) ); 294 } 295 296 /** 297 * Set the action for this node. 298 * 299 * @param action The new action for this node. 300 */ 301 public void setAction( PDAction action ) 302 { 303 node.setItem( "A", action ); 304 } 305 306 /** 307 * Get the structure element of this node. 308 * 309 * @return The structure element of this node. 310 */ 311 public PDStructureElement getStructureElement() 312 { 313 PDStructureElement se = null; 314 COSDictionary dic = (COSDictionary)node.getDictionaryObject( "SE" ); 315 if( dic != null ) 316 { 317 se = new PDStructureElement( dic ); 318 } 319 return se; 320 } 321 322 /** 323 * Set the structure element for this node. 324 * 325 * @param structureElement The new structure element for this node. 326 */ 327 public void setStructuredElement( PDStructureElement structureElement ) 328 { 329 node.setItem( "SE", structureElement ); 330 } 331 332 /** 333 * Get the text color of this node. Default is black and this method 334 * will never return null. 335 * 336 * @return The structure element of this node. 337 */ 338 public PDColorState getTextColor() 339 { 340 PDColorState retval = null; 341 COSArray csValues = (COSArray)node.getDictionaryObject( "C" ); 342 if( csValues == null ) 343 { 344 csValues = new COSArray(); 345 csValues.growToSize( 3, new COSFloat( 0 ) ); 346 node.setItem( "C", csValues ); 347 } 348 retval = new PDColorState(csValues); 349 retval.setColorSpace( PDDeviceRGB.INSTANCE ); 350 return retval; 351 } 352 353 /** 354 * Set the text color for this node. The colorspace must be a PDDeviceRGB. 355 * 356 * @param textColor The text color for this node. 357 */ 358 public void setTextColor( PDColorState textColor ) 359 { 360 node.setItem( "C", textColor.getCOSColorSpaceValue() ); 361 } 362 363 /** 364 * Set the text color for this node. The colorspace must be a PDDeviceRGB. 365 * 366 * @param textColor The text color for this node. 367 */ 368 public void setTextColor( Color textColor ) 369 { 370 COSArray array = new COSArray(); 371 array.add( new COSFloat( textColor.getRed()/255f)); 372 array.add( new COSFloat( textColor.getGreen()/255f)); 373 array.add( new COSFloat( textColor.getBlue()/255f)); 374 node.setItem( "C", array ); 375 } 376 377 /** 378 * A flag telling if the text should be italic. 379 * 380 * @return The italic flag. 381 */ 382 public boolean isItalic() 383 { 384 return BitFlagHelper.getFlag( node, "F", ITALIC_FLAG ); 385 } 386 387 /** 388 * Set the italic property of the text. 389 * 390 * @param italic The new italic flag. 391 */ 392 public void setItalic( boolean italic ) 393 { 394 BitFlagHelper.setFlag( node, "F", ITALIC_FLAG, italic ); 395 } 396 397 /** 398 * A flag telling if the text should be bold. 399 * 400 * @return The bold flag. 401 */ 402 public boolean isBold() 403 { 404 return BitFlagHelper.getFlag( node, "F", BOLD_FLAG ); 405 } 406 407 /** 408 * Set the bold property of the text. 409 * 410 * @param bold The new bold flag. 411 */ 412 public void setBold( boolean bold ) 413 { 414 BitFlagHelper.setFlag( node, "F", BOLD_FLAG, bold ); 415 } 416 417 }