@@ -616,23 +616,88 @@ Public Function NewOutlines(ByRef parent As pdfValue, Optional ByRef defaults As
616616 Set NewOutlines = NewTopLevelDictionary("/Outlines" , parent, defaults)
617617End Function
618618
619- ' adds a /Outlines to document, specifies top level /Outline item(s)
619+ ' add (or sets) top level /Root /Outlines of document
620+ ' if anOutlineItem is Nothing then creates default toplevel /Outlines item if doesn't already exist
621+ ' otherwise sets Outlines to specified object
622+ Public Sub AddOutlines (Optional ByRef anOutlineItem As pdfValue = Nothing )
623+ If anOutlineItem Is Nothing Then
624+ If Outlines.valueType = PDF_ValueType.PDF_Null Then
625+ Set Outlines = NewOutlines(Nothing )
626+ ' don't forget to add to our object cache
627+ Set objectCache(Outlines.ID) = Outlines
628+ End If
629+ Else
630+ Set Outlines = anOutlineItem
631+ End If
632+ End Sub
633+
634+ ' follows a linked list and returns pdfValue at end
635+ ' item can be obj or obj reference, returns obj reference
636+ Private Function GetEndOfChain (ByRef item As pdfValue , ByRef linkName As String ) As pdfValue
637+ Dim nullValue As pdfValue : Set nullValue = pdfValue.NewValue(Null )
638+ Dim obj As pdfValue
639+ Dim nextItem As pdfValue : Set nextItem = item
640+ If nextItem Is Nothing Then Set nextItem = nullValue
641+ ' we expect item and nextItem to object references, but we need actual obj to follow linked list
642+ If nextItem.valueType = PDF_ValueType.PDF_Reference Then Set obj = getObject(nextItem.value, nextItem.generation)
643+ Set GetEndOfChain = nextItem.referenceObj
644+ Do While nextItem.valueType = PDF_ValueType.PDF_Dictionary
645+ Dim dict As Dictionary : Set dict = nextItem.asDictionary()
646+ Set nextItem = nullValue
647+ If dict.Exists(linkName) Then
648+ Set nextItem = dict.item(linkName)
649+ If nextItem.valueType = PDF_ValueType.PDF_Reference Then Set obj = getObject(nextItem.value, nextItem.generation)
650+ If nextItem.valueType = PDF_ValueType.PDF_Dictionary Then
651+ Set GetEndOfChain = nextItem.referenceObj
652+ End If
653+ End If
654+ Loop
655+ End Function
656+
657+
658+ ' creates a default initialized /Outline item object
659+ ' defaults should include /First /Last along with /Count, /Title /Prev /Next and optionally /Dest
660+ Public Function NewOutlineItem (ByRef parent As pdfValue , Optional ByRef defaults As Dictionary ) As pdfValue
661+ If defaults Is Nothing Then Set defaults = New Dictionary
662+ With defaults
663+ 'If Not .Exists("/Next") Then Set .Item("/Next") = pdfValue.NewValue(Null)
664+ 'If Not .Exists("/Prev") Then Set .Item("/Prev") = pdfValue.NewValue(Null)
665+ 'If Not .Exists("/Count") Then Set .item("/Count") = pdfValue.NewValue(1)
666+ End With
667+ ' Note: /Type /OutlineItem doesn't exist in spec, but should be safe as no /Type specified & additional entries allowed
668+ Set NewOutlineItem = NewTopLevelDictionary("/OutlineItem" , parent, defaults)
669+ End Function
670+
671+ ' adds a /Outlines to document, specifies parent /Outline
672+ ' if parent is Nothing or PDF_Null then will use root /Outline (.Outlines)
620673' if Outlines is Nothing then will also set top level /Root /Outlines to newOutlines
621- ' IF OutlineItem does not have its /Next and /Prev values set already then adds to end of list
674+ '
675+ ' If OutlineItem does not have its /Next and /Prev values set already then adds to end of list
622676' we update it's /Parent and if needed /First and /Last of /Outline
623677' OutlineItem's /Count is added to Outline's /Count
624- Public Sub AddOutlines ( Optional ByRef anOutlineItem As pdfValue = Nothing )
678+ Public Sub AddOutlineItem ( ByRef parent As pdfValue , Optional ByRef anOutlineItem As pdfValue = Nothing )
625679 ' ensure top level dictionary is initialized
680+ If parent Is Nothing Then Set parent = pdfValue.NewValue(Null )
626681 If Outlines.valueType = PDF_ValueType.PDF_Null Then
627682 Set Outlines = NewOutlines(Nothing )
628683 End If
684+ If parent.valueType = PDF_ValueType.PDF_Null Then Set parent = Outlines
629685
630686 ' insert a new /OutlineItem into our outlines
631687 If Not anOutlineItem Is Nothing Then
688+ ' ensure parent is correctly set
689+ Set anOutlineItem.asDictionary("/Parent" ) = parent.referenceObj
690+ ' see if we need to assign and id and add to our objectcache
691+ If anOutlineItem.ID < 0 Then
692+ anOutlineItem.ID = nextObjId
693+ anOutlineItem.generation = 0
694+ objectCache.Add anOutlineItem.ID, anOutlineItem
695+ End If
696+
632697 ' determine if /First and /Last exists or need to be created
633698 ' determine if this item is being appended to end or has picked its place
634699 Dim first As pdfValue , last As pdfValue , prevItem As pdfValue , nextItem As pdfValue
635- With Outlines .asDictionary()
700+ With parent .asDictionary()
636701 If .Exists("/First" ) Then
637702 Set first = .item("/First" )
638703 Else
@@ -699,57 +764,6 @@ Public Sub AddOutlines(Optional ByRef anOutlineItem As pdfValue = Nothing)
699764 End If
700765End Sub
701766
702- ' follows a linked list and returns pdfValue at end
703- ' item can be obj or obj reference, returns obj reference
704- Private Function GetEndOfChain (ByRef item As pdfValue , ByRef linkName As String ) As pdfValue
705- Dim nullValue As pdfValue : Set nullValue = pdfValue.NewValue(Null )
706- Dim obj As pdfValue
707- Dim nextItem As pdfValue : Set nextItem = item
708- If nextItem Is Nothing Then Set nextItem = nullValue
709- ' we expect item and nextItem to object references, but we need actual obj to follow linked list
710- If nextItem.valueType = PDF_ValueType.PDF_Reference Then Set obj = getObject(nextItem.value, nextItem.generation)
711- Set GetEndOfChain = nextItem.referenceObj
712- Do While nextItem.valueType = PDF_ValueType.PDF_Dictionary
713- Dim dict As Dictionary : Set dict = nextItem.asDictionary()
714- Set nextItem = nullValue
715- If dict.Exists(linkName) Then
716- Set nextItem = dict.item(linkName)
717- If nextItem.valueType = PDF_ValueType.PDF_Reference Then Set obj = getObject(nextItem.value, nextItem.generation)
718- If nextItem.valueType = PDF_ValueType.PDF_Dictionary Then
719- Set GetEndOfChain = nextItem.referenceObj
720- End If
721- End If
722- Loop
723- End Function
724-
725-
726- ' creates a default initialized /Outline item object
727- ' defaults should include /First /Last along with /Count, /Title /Prev /Next and optionally /Dest
728- Public Function NewOutlineItem (ByRef parent As pdfValue , Optional ByRef defaults As Dictionary ) As pdfValue
729- If defaults Is Nothing Then Set defaults = New Dictionary
730- With defaults
731- 'If Not .Exists("/Next") Then Set .Item("/Next") = pdfValue.NewValue(Null)
732- 'If Not .Exists("/Prev") Then Set .Item("/Prev") = pdfValue.NewValue(Null)
733- 'If Not .Exists("/Count") Then Set .item("/Count") = pdfValue.NewValue(1)
734- End With
735- ' Note: /Type /OutlineItem doesn't exist in spec, but should be safe as no /Type specified & additional entries allowed
736- Set NewOutlineItem = NewTopLevelDictionary("/OutlineItem" , parent, defaults)
737- End Function
738-
739- ' adds a /Outlines to document, specifies top level /Outline item(s)
740- ' if Outlines is Nothing then will also set top level /Root /Outlines to newOutlines
741- Public Sub AddOutlineItem (ByRef parent As pdfValue , Optional ByRef anOutlineItem As pdfValue = Nothing )
742- ' ensure top level dictionary is initialized
743- If Outlines.valueType = PDF_ValueType.PDF_Null Then
744- Set Outlines = NewOutlines(Nothing )
745- End If
746-
747- ' insert a new /OutlineItem into our outlines
748- If Not anOutlineItem Is Nothing Then
749- ' create /First and /Last if needed, otherwise update /Last and /Count and /Next & /Prev
750- End If
751- End Sub
752-
753767
754768
755769' updates id of all objects under root pdfValue beginning at baseId
@@ -1111,7 +1125,7 @@ Sub SavePdfObject(ByRef outputFileNum As Integer, ByRef obj As pdfValue, _
11111125 ByRef offset As Long , Optional ByVal baseId As Long = 0 , Optional ByVal prettyPrint As Boolean = True )
11121126 On Error GoTo errHandler
11131127
1114- If False And (obj.valueType = PDF_ValueType.PDF_Stream) Then
1128+ If (obj.valueType = PDF_ValueType.PDF_Stream) Then
11151129 ' see if its a stream object stream (any other object stream written out asis)
11161130 If obj.asDictionary.Exists("/Type" ) Then
11171131 Dim pdfNameValue As pdfValue
@@ -1130,6 +1144,35 @@ errHandler:
11301144 Stop
11311145End Sub
11321146
1147+
1148+ ' save all nested /Outlines objects beginning with outlineParentObj
1149+ ' pass in .Outlines to save full outline hierarchy, or any subtree as needed
1150+ Sub SaveOutlinePdfObjects (ByRef outputFileNum As Integer , ByRef outlineParentObj As pdfValue , _
1151+ ByRef offset As Long , Optional ByVal baseId As Long = 0 , Optional ByVal prettyPrint As Boolean = True )
1152+ On Error GoTo ErrHandler
1153+ SavePdfObject outputFileNum, outlineParentObj, offset, baseId, prettyPrint
1154+ If outlineParentObj.hasKey("/First" ) Then
1155+ Dim outlineObj As pdfValue
1156+ Set outlineObj = outlineParentObj.asDictionary().item("/First" )
1157+ ' next line should always be true, only an indirect reference not object itself
1158+ If outlineObj.valueType = PDF_ValueType.PDF_Reference Then Set outlineObj = getObject(outlineObj.value, outlineObj.generation)
1159+ Do While Not outlineObj Is Nothing
1160+ ' recursively save outline tree
1161+ SaveOutlinePdfObjects outputFileNum, outlineObj, offset, baseId, prettyPrint
1162+ If outlineObj.hasKey("/Next" ) Then
1163+ Set outlineObj = outlineObj.asDictionary().item("/Next" )
1164+ Set outlineObj = getObject(outlineObj.value, outlineObj.generation)
1165+ Else
1166+ Set outlineObj = Nothing
1167+ End If
1168+ Loop
1169+ End If
1170+ Exit Sub
1171+ ErrHandler:
1172+ Debug.Print "Error: " & Err.Description & " (" & Err.Number & ")"
1173+ Stop
1174+ End Sub
1175+
11331176' given current position in file, updates cross reference table and output serialized object
11341177' for each object in pdfObjs dictionary.
11351178' Note: assumes PDF Objects all have valid id values (not modified)
0 commit comments