diff --git a/src/main/java/com/itextpdf/rups/controller/PdfReaderController.java b/src/main/java/com/itextpdf/rups/controller/PdfReaderController.java index edf8fa41..809f73df 100644 --- a/src/main/java/com/itextpdf/rups/controller/PdfReaderController.java +++ b/src/main/java/com/itextpdf/rups/controller/PdfReaderController.java @@ -44,6 +44,7 @@ This file is part of the iText (R) project. import com.itextpdf.kernel.pdf.PdfArray; import com.itextpdf.kernel.pdf.PdfDictionary; +import com.itextpdf.kernel.pdf.PdfIndirectReference; import com.itextpdf.kernel.pdf.PdfName; import com.itextpdf.kernel.pdf.PdfObject; import com.itextpdf.kernel.pdf.PdfStream; @@ -61,6 +62,7 @@ This file is part of the iText (R) project. import com.itextpdf.rups.event.OpenStructureEvent; import com.itextpdf.rups.event.RupsEvent; import com.itextpdf.rups.io.listeners.PdfTreeNavigationListener; +import com.itextpdf.rups.model.IndirectObjectFactory; import com.itextpdf.rups.model.ObjectLoader; import com.itextpdf.rups.model.PdfSyntaxParser; import com.itextpdf.rups.model.TreeNodeFactory; @@ -79,11 +81,13 @@ This file is part of the iText (R) project. import com.itextpdf.rups.view.itext.StructureTree; import com.itextpdf.rups.view.itext.SyntaxHighlightedStreamPane; import com.itextpdf.rups.view.itext.XRefTable; +import com.itextpdf.rups.view.itext.treenodes.ObjectStreamTreeNode; import com.itextpdf.rups.view.itext.treenodes.PdfObjectTreeNode; import com.itextpdf.rups.view.itext.treenodes.PdfTrailerTreeNode; import java.awt.Color; import java.awt.event.KeyListener; +import java.util.List; import java.util.Observable; import java.util.Observer; import java.util.Stack; @@ -310,6 +314,13 @@ public void update(Observable observable, Object obj) { root.setTrailer(loader.getFile().getPdfDocument().getTrailer()); root.setUserObject(String.format(Language.PDF_OBJECT_TREE.getString(), loader.getLoaderName())); nodes.expandNode(root); + IndirectObjectFactory objects = loader.getObjects(); + List objectStreams = objects.getObjectStreams(); + if ( objectStreams != null && !objectStreams.isEmpty() ) { + ObjectStreamTreeNode objStreamNode = new ObjectStreamTreeNode(new PdfArray(objectStreams)); + root.add(objStreamNode); + nodes.expandNode(objStreamNode); + } navigationTabs.setSelectedIndex(0); setChanged(); super.notifyObservers(event); diff --git a/src/main/java/com/itextpdf/rups/model/IndirectObjectFactory.java b/src/main/java/com/itextpdf/rups/model/IndirectObjectFactory.java index ff28014a..3ca47b20 100644 --- a/src/main/java/com/itextpdf/rups/model/IndirectObjectFactory.java +++ b/src/main/java/com/itextpdf/rups/model/IndirectObjectFactory.java @@ -46,15 +46,18 @@ This file is part of the iText (R) project. import com.itextpdf.kernel.exceptions.PdfException; import com.itextpdf.kernel.pdf.PdfDictionary; import com.itextpdf.kernel.pdf.PdfDocument; +import com.itextpdf.kernel.pdf.PdfIndirectReference; import com.itextpdf.kernel.pdf.PdfName; import com.itextpdf.kernel.pdf.PdfNull; import com.itextpdf.kernel.pdf.PdfObject; +import com.itextpdf.kernel.pdf.PdfStream; import com.itextpdf.rups.view.Language; import java.lang.reflect.Field; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.ArrayList; +import java.util.List; /** * A factory that can produce all the indirect objects in a PDF file. @@ -77,6 +80,11 @@ public class IndirectObjectFactory { * A list of all the indirect objects in a PDF file. */ protected ArrayList objects = new ArrayList<>(); + + /** + * List of all Object Streams in a PDF file. + */ + protected List objectStreams = new ArrayList<>(); /** * Mapping between the index in the objects list and the reference number in the xref table. */ @@ -161,6 +169,14 @@ public boolean storeNextObject() { final int idx = size(); idxToRef.put(idx, current); refToIdx.put(current, idx); + + if ( object.getType() == PdfObject.STREAM ) { + PdfStream stream = (PdfStream) object; + if ( PdfName.ObjStm.equals(stream.get(PdfName.Type) )) { + this.objectStreams.add(stream.getIndirectReference()); + } + } + store(object); return true; } @@ -254,6 +270,10 @@ public boolean isLoadedByReference(int ref) { return isLoaded.get(getIndexByRef(ref)); } + public List getObjectStreams() { + return objectStreams; + } + /** * Loads an object based on its reference number in the xref table. * diff --git a/src/main/java/com/itextpdf/rups/model/ObjectLoader.java b/src/main/java/com/itextpdf/rups/model/ObjectLoader.java index 5c29ccbc..373c4998 100644 --- a/src/main/java/com/itextpdf/rups/model/ObjectLoader.java +++ b/src/main/java/com/itextpdf/rups/model/ObjectLoader.java @@ -117,6 +117,7 @@ public TreeNodeFactory getNodes() { return nodes; } + /** * getter for a human readable name representing this loader * diff --git a/src/main/java/com/itextpdf/rups/view/Language.java b/src/main/java/com/itextpdf/rups/view/Language.java index 6e84da90..2cbffc39 100644 --- a/src/main/java/com/itextpdf/rups/view/Language.java +++ b/src/main/java/com/itextpdf/rups/view/Language.java @@ -188,6 +188,7 @@ public enum Language { PAGES, PAGES_TABLE_OBJECT, PDF_READING, + PDF_OBJECT_STREAMS_TREE_NODE, PDF_OBJECT_TREE, PLAINTEXT, PLAINTEXT_DESCRIPTION, @@ -228,10 +229,15 @@ public enum Language { WARNING, XREF, + XREF_BYTE_OFFSET, + XREF_BYTE_OFFSET_OBJECT_STREAM, XREF_DESCRIPTION, + XREF_NA, + XREF_NOT_LOADED_YET, XREF_NUMBER, XREF_OBJECT, - XREF_READING; + XREF_READING + ; /** * The location of the resource bundles. diff --git a/src/main/java/com/itextpdf/rups/view/itext/ObjectStreamParser.java b/src/main/java/com/itextpdf/rups/view/itext/ObjectStreamParser.java new file mode 100644 index 00000000..9e72883c --- /dev/null +++ b/src/main/java/com/itextpdf/rups/view/itext/ObjectStreamParser.java @@ -0,0 +1,55 @@ +package com.itextpdf.rups.view.itext; + +import com.itextpdf.io.source.PdfTokenizer; +import com.itextpdf.io.source.RandomAccessFileOrArray; +import com.itextpdf.io.source.RandomAccessSourceFactory; +import com.itextpdf.kernel.pdf.PdfName; +import com.itextpdf.kernel.pdf.PdfStream; +import com.itextpdf.rups.model.LoggerHelper; +import com.itextpdf.rups.view.Console; + +import java.io.IOException; +import java.util.Arrays; + +/** + * Utility class to parse ObjectStreams to extract the offset of a given object id within the stream. + */ +public class ObjectStreamParser { + /** + * Parses an ObjectStream to find the offset to the passed parameter, compressedObjectNumber. This offset is + * relative to the ObjectStream and not to the complete file, as described in the specification. + * + * If an Object ID isn't found inside the ObjectStream, this will return -1. + * + * @param objStm The ObjectStream to parse + * @param compressedObjectNumber the ID of the object of which you want the offset + * @return the offset of the object or -1 if the object is not found + */ + public static int parseObjectStream(PdfStream objStm, int compressedObjectNumber) { + byte[] objStmBytes = objStm.getBytes(true); + int byteOffsetOfFirst = objStm.getAsInt(PdfName.First); + + PdfTokenizer pdfTokenizer = new PdfTokenizer( + new RandomAccessFileOrArray( + new RandomAccessSourceFactory() + .createSource( + Arrays.copyOfRange(objStmBytes, 0, byteOffsetOfFirst)))); + + try { + while (pdfTokenizer.nextToken()) { + if ( pdfTokenizer.getTokenType().equals(PdfTokenizer.TokenType.Number )) { + int objNumber = pdfTokenizer.getIntValue(); + pdfTokenizer.nextToken(); + if ( objNumber == compressedObjectNumber ) { + return pdfTokenizer.getIntValue(); + } + } + } + } catch (IOException e) { + LoggerHelper.error(e.getMessage(), e, ObjectStreamParser.class); + return -1; + } + + return -1; + } +} diff --git a/src/main/java/com/itextpdf/rups/view/itext/XRefTable.java b/src/main/java/com/itextpdf/rups/view/itext/XRefTable.java index 9377ac1a..d07141dc 100644 --- a/src/main/java/com/itextpdf/rups/view/itext/XRefTable.java +++ b/src/main/java/com/itextpdf/rups/view/itext/XRefTable.java @@ -42,8 +42,7 @@ This file is part of the iText (R) project. */ package com.itextpdf.rups.view.itext; -import com.itextpdf.kernel.pdf.PdfNull; -import com.itextpdf.kernel.pdf.PdfObject; +import com.itextpdf.kernel.pdf.*; import com.itextpdf.rups.controller.PdfReaderController; import com.itextpdf.rups.event.RupsEvent; import com.itextpdf.rups.model.IndirectObjectFactory; @@ -114,7 +113,7 @@ public void update(Observable observable, Object obj) { * @see javax.swing.JTable#getColumnCount() */ public int getColumnCount() { - return 2; + return 3; } /** @@ -134,6 +133,8 @@ public Object getValueAt(int rowIndex, int columnIndex) { return getObjectReferenceByRow(rowIndex); case 1: return getObjectDescriptionByRow(rowIndex); + case 2: + return getByteOffSetByRow(rowIndex); default: return null; } @@ -164,6 +165,47 @@ protected String getObjectDescriptionByRow(int rowIndex) { return PdfObjectTreeNode.getCaption(object); } + /** + * Returns the byte offset of the selected XREF entry. If the entry has no real, actual offset. + * i.e. it is compressed in a PDF Object Stream, then this shall return The ID of the Object Stream + * and the offset of the Object Stream. + * + * @param rowIndex the index of the selected XREF entry + * @return byte offset of the XREF entry or the ID and byte offset of the encompassing Object Stream + */ + private String getByteOffSetByRow(int rowIndex) { + final PdfObject object = objects.getObjectByIndex(rowIndex); + PdfIndirectReference indirectReference = object.getIndirectReference(); + if ( indirectReference != null ) { + if (isObjectStream(indirectReference)) { + int compressedObjectNumber = indirectReference.getObjNumber(); + PdfStream objStm = getObjectStream(indirectReference); + int internalCompressedObjectOffset + = ObjectStreamParser.parseObjectStream(objStm, compressedObjectNumber); + + return String.format( + Language.XREF_BYTE_OFFSET_OBJECT_STREAM.getString(), + objStm.getIndirectReference().getObjNumber(), internalCompressedObjectOffset + ); + } + + return String.valueOf(indirectReference.getOffset()); + } + return Language.XREF_NOT_LOADED_YET.getString(); + } + + private PdfStream getObjectStream(PdfIndirectReference indirectReference) { + int objStreamNumber = indirectReference.getObjStreamNumber(); + PdfObject objectByIndex = objects.loadObjectByReference(objStreamNumber); + + return (PdfStream) objectByIndex; + } + + private boolean isObjectStream(PdfIndirectReference indirectReference) { + return indirectReference.getOffset() == -1; + } + + /** * @see javax.swing.JTable#getColumnName(int) */ @@ -173,6 +215,8 @@ public String getColumnName(int columnIndex) { return Language.XREF_NUMBER.getString(); case 1: return Language.XREF_OBJECT.getString(); + case 2: + return Language.XREF_BYTE_OFFSET.getString(); default: return null; } diff --git a/src/main/java/com/itextpdf/rups/view/itext/treenodes/ObjectStreamTreeNode.java b/src/main/java/com/itextpdf/rups/view/itext/treenodes/ObjectStreamTreeNode.java new file mode 100644 index 00000000..9f2b94a4 --- /dev/null +++ b/src/main/java/com/itextpdf/rups/view/itext/treenodes/ObjectStreamTreeNode.java @@ -0,0 +1,13 @@ +package com.itextpdf.rups.view.itext.treenodes; + +import com.itextpdf.kernel.pdf.PdfObject; +import com.itextpdf.rups.view.Language; + +public class ObjectStreamTreeNode extends PdfObjectTreeNode { + + public ObjectStreamTreeNode(PdfObject object) { + super(object); + setUserObject(Language.PDF_OBJECT_STREAMS_TREE_NODE.getString()); + } + +} diff --git a/src/main/resources/bundles/rups-lang.properties b/src/main/resources/bundles/rups-lang.properties index 9fd4a5be..99c2fc64 100644 --- a/src/main/resources/bundles/rups-lang.properties +++ b/src/main/resources/bundles/rups-lang.properties @@ -144,6 +144,7 @@ PAGES=Pages PAGES_TABLE_OBJECT=Object %d PDF_READING=Reading PDF document... +PDF_OBJECT_STREAMS_TREE_NODE=PDF Object Streams PDF_OBJECT_TREE=PDF Object Tree (%s) PLAINTEXT=Plain Text @@ -192,7 +193,11 @@ TOOLTIP_HEX=Hex-editable binary content WARNING=Warning XREF=XREF +XREF_BYTE_OFFSET=Byte Offset +XREF_BYTE_OFFSET_OBJECT_STREAM=Object Stream #%d (%d) XREF_DESCRIPTION=Cross-reference table +XREF_NA=N/A +XREF_NOT_LOADED_YET=Offset not loaded yet XREF_NUMBER=Number XREF_OBJECT=Object XREF_READING=Reading the Cross-Reference table \ No newline at end of file diff --git a/src/main/resources/bundles/rups-lang_nl_NL.properties b/src/main/resources/bundles/rups-lang_nl_NL.properties index b1b298e3..d15e28ce 100644 --- a/src/main/resources/bundles/rups-lang_nl_NL.properties +++ b/src/main/resources/bundles/rups-lang_nl_NL.properties @@ -189,6 +189,7 @@ WARNING=Waarschuwing XREF=XREF XREF_DESCRIPTION=Cross-reference tabel +XREF_NOT_LOADED_YET=Offset is nog niet ingeladen XREF_NUMBER=Nummer XREF_OBJECT=Object XREF_READING=Lezen van de Cross-Reference tabel \ No newline at end of file