From bc8039d9a347afa802450a859b400a025ae54c1e Mon Sep 17 00:00:00 2001 From: Igor Luzhanov <4629679+luzhanov@users.noreply.github.com> Date: Thu, 4 Apr 2024 18:51:52 +0200 Subject: [PATCH] PdfStructureTreeRoot update - allowing to save Object References --- .../text/pdf/PdfStructureTreeRoot.java | 59 +++++++++--- .../text/pdf/PdfStructureTreeRootTest.java | 91 +++++++++++++++++++ 2 files changed, 138 insertions(+), 12 deletions(-) create mode 100644 openpdf/src/test/java/com/lowagie/text/pdf/PdfStructureTreeRootTest.java diff --git a/openpdf/src/main/java/com/lowagie/text/pdf/PdfStructureTreeRoot.java b/openpdf/src/main/java/com/lowagie/text/pdf/PdfStructureTreeRoot.java index 631c27030..f743c08dd 100644 --- a/openpdf/src/main/java/com/lowagie/text/pdf/PdfStructureTreeRoot.java +++ b/openpdf/src/main/java/com/lowagie/text/pdf/PdfStructureTreeRoot.java @@ -59,9 +59,15 @@ */ public class PdfStructureTreeRoot extends PdfDictionary { - private final Map parentTree = new HashMap<>(); + private final Map parentTree = new HashMap<>(); private final PdfIndirectReference reference; + /** Next key to be used for adding to the parentTree */ + private int parentTreeNextKey = 0; + + /** Map which connects [page number] with corresponding [parentTree entry key] */ + private final Map pageKeysMap = new HashMap<>(); + /** * Holds value of property writer. */ @@ -111,14 +117,32 @@ public PdfIndirectReference getReference() { return this.reference; } + /** + * Adds a reference to the existing (already added to the document) object to the parentTree. + * This method can be used when the object is need to be referenced via /StructParent key. + * + * @return key which define the object record key (e.g. in /NUMS array) + */ + public int addExistingObject(PdfIndirectReference reference) { + int key = parentTreeNextKey; + parentTree.put(key, reference); + parentTreeNextKey++; + return key; + } + void setPageMark(int page, PdfIndirectReference reference) { - Integer i = page; - PdfArray ar = parentTree.get(i); - if (ar == null) { - ar = new PdfArray(); - parentTree.put(i, ar); + Integer entryForPageArray = pageKeysMap.get(page); + if (entryForPageArray == null) { + //putting page array + PdfArray ar = new PdfArray(); + entryForPageArray = parentTreeNextKey; + parentTree.put(entryForPageArray, ar); + parentTreeNextKey++; + pageKeysMap.put(page, entryForPageArray); } - ar.add(reference); + + PdfArray pageArray = (PdfArray) parentTree.get(entryForPageArray); + pageArray.add(reference); } private void nodeProcess(PdfDictionary dictionary, PdfIndirectReference reference) @@ -128,9 +152,15 @@ private void nodeProcess(PdfDictionary dictionary, PdfIndirectReference referenc .get(0).isNumber()) { PdfArray ar = (PdfArray) obj; for (int k = 0; k < ar.size(); ++k) { - PdfStructureElement e = (PdfStructureElement) ar.getDirectObject(k); - ar.set(k, e.getReference()); - nodeProcess(e, e.getReference()); + PdfObject pdfObj = ar.getDirectObject(k); + + if (pdfObj instanceof PdfStructureElement) { + PdfStructureElement e = (PdfStructureElement) pdfObj; + ar.set(k, e.getReference()); + nodeProcess(e, e.getReference()); + } else if (pdfObj instanceof PdfIndirectReference) { + ar.set(k, pdfObj); + } } } if (reference != null) { @@ -141,8 +171,13 @@ private void nodeProcess(PdfDictionary dictionary, PdfIndirectReference referenc void buildTree() throws IOException { Map numTree = new HashMap<>(); for (Integer i : parentTree.keySet()) { - PdfArray ar = parentTree.get(i); - numTree.put(i, writer.addToBody(ar).getIndirectReference()); + PdfObject ar = parentTree.get(i); + if (ar instanceof PdfIndirectReference) { + //saving the reference to the object which was already added to the body + numTree.put(i, (PdfIndirectReference) ar); + } else { + numTree.put(i, writer.addToBody(ar).getIndirectReference()); + } } PdfDictionary dicTree = PdfNumberTree.writeTree(numTree, writer); if (dicTree != null) { diff --git a/openpdf/src/test/java/com/lowagie/text/pdf/PdfStructureTreeRootTest.java b/openpdf/src/test/java/com/lowagie/text/pdf/PdfStructureTreeRootTest.java new file mode 100644 index 000000000..43a67fed8 --- /dev/null +++ b/openpdf/src/test/java/com/lowagie/text/pdf/PdfStructureTreeRootTest.java @@ -0,0 +1,91 @@ +package com.lowagie.text.pdf; + +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertSame; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.doThrow; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import java.io.IOException; +import org.junit.jupiter.api.Test; + +class PdfStructureTreeRootTest { + + @Test + void shouldCreateNewInstanceSuccessfully() { + PdfWriter writer = mock(PdfWriter.class); + when(writer.getPdfIndirectReference()).thenReturn(mock(PdfIndirectReference.class)); + + PdfStructureTreeRoot root = new PdfStructureTreeRoot(writer); + + assertNotNull(root); + assertEquals(PdfName.STRUCTTREEROOT, root.get(PdfName.TYPE)); + assertNotNull(root.getReference()); + assertSame(writer, root.getWriter()); + } + + @Test + void shouldMapUserTagToStandardTag() { + PdfStructureTreeRoot root = new PdfStructureTreeRoot(mock(PdfWriter.class)); + PdfName userTag = new PdfName("MyTag"); + PdfName standardTag = PdfName.H1; + + root.mapRole(userTag, standardTag); + + PdfDictionary roleMap = (PdfDictionary) root.get(PdfName.ROLEMAP); + assertNotNull(roleMap); + assertEquals(standardTag, roleMap.get(userTag)); + } + + @Test + void getWriterShouldReturnCorrectWriter() { + PdfWriter mockWriter = mock(PdfWriter.class); + PdfStructureTreeRoot treeRoot = new PdfStructureTreeRoot(mockWriter); + + PdfWriter result = treeRoot.getWriter(); + + assertSame(mockWriter, result); + } + + @Test + void addExistingObjectShouldIncreaseParentTreeNextKey() { + PdfWriter mockWriter = mock(PdfWriter.class); + PdfStructureTreeRoot treeRoot = new PdfStructureTreeRoot(mockWriter); + PdfIndirectReference mockRef = mock(PdfIndirectReference.class); + + int firstKey = treeRoot.addExistingObject(mockRef); + int secondKey = treeRoot.addExistingObject(mockRef); + + assertNotEquals(firstKey, secondKey); + assertEquals(firstKey + 1, secondKey); + } + + @Test + void buildTreeShouldGenerateParentTreeWithoutException() throws IOException { + PdfWriter mockWriter = mock(PdfWriter.class); + when(mockWriter.addToBody(any(PdfObject.class))).thenAnswer(invocation -> { + PdfObject arg = invocation.getArgument(0); + return new PdfIndirectObject(0, arg, mockWriter); + }); + + PdfStructureTreeRoot treeRoot = new PdfStructureTreeRoot(mockWriter); + + assertDoesNotThrow(() -> treeRoot.buildTree()); + } + + @Test + void buildTreeShouldHandleIOException() throws IOException { + PdfWriter mockWriter = mock(PdfWriter.class); + doThrow(IOException.class).when(mockWriter).addToBody(any(PdfObject.class)); + + PdfStructureTreeRoot treeRoot = new PdfStructureTreeRoot(mockWriter); + treeRoot.setPageMark(1, mock(PdfIndirectReference.class)); + + assertThrows(IOException.class, () -> treeRoot.buildTree()); + } +} \ No newline at end of file