@@ -248,7 +248,9 @@ public void setLang(PdfString lang) {
248248 put (PdfName .Lang , lang );
249249 }
250250
251- public PdfString getLang (){ return getPdfObject ().getAsString (PdfName .Lang );}
251+ public PdfString getLang () {
252+ return getPdfObject ().getAsString (PdfName .Lang );
253+ }
252254
253255 public void addDeveloperExtension (PdfDeveloperExtension extension ) {
254256 PdfDictionary extensions = getPdfObject ().getAsDictionary (PdfName .Extensions );
@@ -381,8 +383,7 @@ PdfOutline getOutlines(boolean updateOutlines) {
381383 }
382384 outlines = new PdfOutline (getDocument ());
383385 } else {
384- outlines = new PdfOutline (OutlineRoot , outlineRoot , getDocument ());
385- getNextItem (outlineRoot .getAsDictionary (PdfName .First ), outlines , destsTree .getNames ());
386+ constructOutlines (outlineRoot , destsTree .getNames ());
386387 }
387388
388389 return outlines ;
@@ -504,26 +505,62 @@ private void addOutlineToPage(PdfOutline outline, Map<String, PdfObject> names)
504505 }
505506 }
506507
507- private void getNextItem (PdfDictionary item , PdfOutline parent , Map <String , PdfObject > names ) {
508- if (null == item ) {
509- return ;
508+ /**
509+ * Get the next outline of the current node in the outline tree by looking for a child or sibling node.
510+ * If there is no child or sibling of the current node {@link PdfCatalog#getParentNextOutline(PdfDictionary)} is called to get a hierarchical parent's next node. {@code null} is returned if one does not exist.
511+ *
512+ * @return the {@link PdfDictionary} object of the next outline if one exists, {@code null} otherwise.
513+ */
514+ private PdfDictionary getNextOutline (PdfDictionary first , PdfDictionary next , PdfDictionary parent ) {
515+ if (first != null ) {
516+ return first ;
517+ } else if (next != null ) {
518+ return next ;
519+ } else {
520+ return getParentNextOutline (parent );
521+ }
522+
523+ }
524+
525+ /**
526+ * Gets the parent's next outline of the current node.
527+ * If the parent does not have a next we look at the grand parent, great-grand parent, etc until we find a next node or reach the root at which point {@code null} is returned to signify there is no next node present.
528+ *
529+ * @return the {@link PdfDictionary} object of the next outline if one exists, {@code null} otherwise.
530+ */
531+ private PdfDictionary getParentNextOutline (PdfDictionary parent ) {
532+ if (parent == null ) {
533+ return null ;
510534 }
511- PdfOutline outline = new PdfOutline (item .getAsString (PdfName .Title ).toUnicodeString (), item , parent );
535+ PdfDictionary current = null ;
536+ while (current == null ) {
537+ current = parent .getAsDictionary (PdfName .Next );
538+ if (current == null ) {
539+ parent = parent .getAsDictionary (PdfName .Parent );
540+ if (parent == null ) {
541+ return null ;
542+ }
543+ }
544+ }
545+ return current ;
546+ }
547+
548+ private void addOutlineToPage (PdfOutline outline , PdfDictionary item , Map <String , PdfObject > names ) {
512549 PdfObject dest = item .get (PdfName .Dest );
513550 if (dest != null ) {
514551 PdfDestination destination = PdfDestination .makeDestination (dest );
515552 outline .setDestination (destination );
516553 addOutlineToPage (outline , names );
517- }else {
554+ } else {
518555 //Take into account outlines that specify their destination through an action
519556 PdfDictionary action = item .getAsDictionary (PdfName .A );
520- if (action != null ){
557+ if (action != null ) {
521558 PdfName actionType = action .getAsName (PdfName .S );
522- //Check if it a go to action
523- if (PdfName .GoTo .equals (actionType )) {
559+ //Check if it is a go to action
560+ if (PdfName .GoTo .equals (actionType )) {
524561 //Retrieve destination if it is.
525562 PdfObject destObject = action .get (PdfName .D );
526- if (destObject != null ){
563+ if (destObject != null ) {
527564 //Page is always the first object
528565 PdfDestination destination = PdfDestination .makeDestination (destObject );
529566 outline .setDestination (destination );
@@ -532,15 +569,43 @@ private void getNextItem(PdfDictionary item, PdfOutline parent, Map<String, PdfO
532569 }
533570 }
534571 }
535- parent . getAllChildren (). add ( outline );
572+ }
536573
537- PdfDictionary processItem = item .getAsDictionary (PdfName .First );
538- if (processItem != null ) {
539- getNextItem (processItem , outline , names );
574+ /**
575+ * Constructs {@link PdfCatalog#outlines} iteratively
576+ */
577+ private void constructOutlines (PdfDictionary outlineRoot , Map <String , PdfObject > names ) {
578+ if (outlineRoot == null ) {
579+ return ;
540580 }
541- processItem = item .getAsDictionary (PdfName .Next );
542- if (processItem != null ) {
543- getNextItem (processItem , parent , names );
581+ PdfDictionary first = outlineRoot .getAsDictionary (PdfName .First );
582+ PdfDictionary current = first ;
583+ PdfDictionary next ;
584+ PdfDictionary parent ;
585+ HashMap <PdfDictionary , PdfOutline > parentOutlineMap = new HashMap <>();
586+
587+ outlines = new PdfOutline (OutlineRoot , outlineRoot , getDocument ());
588+ PdfOutline parentOutline = outlines ;
589+ parentOutlineMap .put (outlineRoot , parentOutline );
590+
591+ while (current != null ) {
592+ first = current .getAsDictionary (PdfName .First );
593+ next = current .getAsDictionary (PdfName .Next );
594+ parent = current .getAsDictionary (PdfName .Parent );
595+
596+ parentOutline = parentOutlineMap .get (parent );
597+ PdfOutline currentOutline = new PdfOutline (current .getAsString (PdfName .Title ).toUnicodeString (), current , parentOutline );
598+ addOutlineToPage (currentOutline , current , names );
599+ parentOutline .getAllChildren ().add (currentOutline );
600+
601+ if (first != null ) {
602+ parentOutlineMap .put (current , currentOutline );
603+ } else if (current == parent .getAsDictionary (PdfName .Last )) {
604+ parentOutlineMap .remove (parent );
605+ }
606+ current = getNextOutline (first , next , parent );
607+
544608 }
545609 }
610+
546611}
0 commit comments