Skip to content

Commit 23c198b

Browse files
committed
Add Byte Offsets to the XREF Table. RES-713
1 parent 62b54e1 commit 23c198b

File tree

8 files changed

+132
-4
lines changed

8 files changed

+132
-4
lines changed

src/main/java/com/itextpdf/rups/controller/PdfReaderController.java

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ This file is part of the iText (R) project.
4444

4545
import com.itextpdf.kernel.pdf.PdfArray;
4646
import com.itextpdf.kernel.pdf.PdfDictionary;
47+
import com.itextpdf.kernel.pdf.PdfIndirectReference;
4748
import com.itextpdf.kernel.pdf.PdfName;
4849
import com.itextpdf.kernel.pdf.PdfObject;
4950
import com.itextpdf.kernel.pdf.PdfStream;
@@ -61,6 +62,7 @@ This file is part of the iText (R) project.
6162
import com.itextpdf.rups.event.OpenStructureEvent;
6263
import com.itextpdf.rups.event.RupsEvent;
6364
import com.itextpdf.rups.io.listeners.PdfTreeNavigationListener;
65+
import com.itextpdf.rups.model.IndirectObjectFactory;
6466
import com.itextpdf.rups.model.ObjectLoader;
6567
import com.itextpdf.rups.model.PdfSyntaxParser;
6668
import com.itextpdf.rups.model.TreeNodeFactory;
@@ -79,11 +81,13 @@ This file is part of the iText (R) project.
7981
import com.itextpdf.rups.view.itext.StructureTree;
8082
import com.itextpdf.rups.view.itext.SyntaxHighlightedStreamPane;
8183
import com.itextpdf.rups.view.itext.XRefTable;
84+
import com.itextpdf.rups.view.itext.treenodes.ObjectStreamTreeNode;
8285
import com.itextpdf.rups.view.itext.treenodes.PdfObjectTreeNode;
8386
import com.itextpdf.rups.view.itext.treenodes.PdfTrailerTreeNode;
8487

8588
import java.awt.Color;
8689
import java.awt.event.KeyListener;
90+
import java.util.List;
8791
import java.util.Observable;
8892
import java.util.Observer;
8993
import java.util.Stack;
@@ -310,6 +314,13 @@ public void update(Observable observable, Object obj) {
310314
root.setTrailer(loader.getFile().getPdfDocument().getTrailer());
311315
root.setUserObject(String.format(Language.PDF_OBJECT_TREE.getString(), loader.getLoaderName()));
312316
nodes.expandNode(root);
317+
IndirectObjectFactory objects = loader.getObjects();
318+
List<PdfIndirectReference> objectStreams = objects.getObjectStreams();
319+
if ( objectStreams != null && !objectStreams.isEmpty() ) {
320+
ObjectStreamTreeNode objStreamNode = new ObjectStreamTreeNode(new PdfArray(objectStreams));
321+
root.add(objStreamNode);
322+
nodes.expandNode(objStreamNode);
323+
}
313324
navigationTabs.setSelectedIndex(0);
314325
setChanged();
315326
super.notifyObservers(event);

src/main/java/com/itextpdf/rups/model/IndirectObjectFactory.java

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,15 +46,18 @@ This file is part of the iText (R) project.
4646
import com.itextpdf.kernel.exceptions.PdfException;
4747
import com.itextpdf.kernel.pdf.PdfDictionary;
4848
import com.itextpdf.kernel.pdf.PdfDocument;
49+
import com.itextpdf.kernel.pdf.PdfIndirectReference;
4950
import com.itextpdf.kernel.pdf.PdfName;
5051
import com.itextpdf.kernel.pdf.PdfNull;
5152
import com.itextpdf.kernel.pdf.PdfObject;
53+
import com.itextpdf.kernel.pdf.PdfStream;
5254
import com.itextpdf.rups.view.Language;
5355

5456
import java.lang.reflect.Field;
5557
import java.lang.reflect.InvocationTargetException;
5658
import java.lang.reflect.Method;
5759
import java.util.ArrayList;
60+
import java.util.List;
5861

5962
/**
6063
* A factory that can produce all the indirect objects in a PDF file.
@@ -77,6 +80,11 @@ public class IndirectObjectFactory {
7780
* A list of all the indirect objects in a PDF file.
7881
*/
7982
protected ArrayList<PdfObject> objects = new ArrayList<>();
83+
84+
/**
85+
* List of all Object Streams in a PDF file.
86+
*/
87+
protected List<PdfIndirectReference> objectStreams = new ArrayList<>();
8088
/**
8189
* Mapping between the index in the objects list and the reference number in the xref table.
8290
*/
@@ -161,6 +169,14 @@ public boolean storeNextObject() {
161169
final int idx = size();
162170
idxToRef.put(idx, current);
163171
refToIdx.put(current, idx);
172+
173+
if ( object.getType() == PdfObject.STREAM ) {
174+
PdfStream stream = (PdfStream) object;
175+
if ( PdfName.ObjStm.equals(stream.get(PdfName.Type) )) {
176+
this.objectStreams.add(stream.getIndirectReference());
177+
}
178+
}
179+
164180
store(object);
165181
return true;
166182
}
@@ -254,6 +270,10 @@ public boolean isLoadedByReference(int ref) {
254270
return isLoaded.get(getIndexByRef(ref));
255271
}
256272

273+
public List<PdfIndirectReference> getObjectStreams() {
274+
return objectStreams;
275+
}
276+
257277
/**
258278
* Loads an object based on its reference number in the xref table.
259279
*

src/main/java/com/itextpdf/rups/model/ObjectLoader.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,7 @@ public TreeNodeFactory getNodes() {
117117
return nodes;
118118
}
119119

120+
120121
/**
121122
* getter for a human readable name representing this loader
122123
*

src/main/java/com/itextpdf/rups/view/Language.java

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -188,6 +188,7 @@ public enum Language {
188188
PAGES,
189189
PAGES_TABLE_OBJECT,
190190
PDF_READING,
191+
PDF_OBJECT_STREAMS_TREE_NODE,
191192
PDF_OBJECT_TREE,
192193
PLAINTEXT,
193194
PLAINTEXT_DESCRIPTION,
@@ -228,10 +229,15 @@ public enum Language {
228229
WARNING,
229230

230231
XREF,
232+
XREF_BYTE_OFFSET,
233+
XREF_BYTE_OFFSET_OBJECT_STREAM,
231234
XREF_DESCRIPTION,
235+
XREF_NA,
236+
XREF_NOT_LOADED_YET,
232237
XREF_NUMBER,
233238
XREF_OBJECT,
234-
XREF_READING;
239+
XREF_READING
240+
;
235241

236242
/**
237243
* The location of the resource bundles.

src/main/java/com/itextpdf/rups/view/itext/XRefTable.java

Lines changed: 74 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -42,8 +42,10 @@ This file is part of the iText (R) project.
4242
*/
4343
package com.itextpdf.rups.view.itext;
4444

45-
import com.itextpdf.kernel.pdf.PdfNull;
46-
import com.itextpdf.kernel.pdf.PdfObject;
45+
import com.itextpdf.io.source.PdfTokenizer;
46+
import com.itextpdf.io.source.RandomAccessFileOrArray;
47+
import com.itextpdf.io.source.RandomAccessSourceFactory;
48+
import com.itextpdf.kernel.pdf.*;
4749
import com.itextpdf.rups.controller.PdfReaderController;
4850
import com.itextpdf.rups.event.RupsEvent;
4951
import com.itextpdf.rups.model.IndirectObjectFactory;
@@ -56,6 +58,8 @@ This file is part of the iText (R) project.
5658
import javax.swing.JTable;
5759
import javax.swing.event.ListSelectionEvent;
5860
import javax.swing.table.TableColumn;
61+
import java.io.IOException;
62+
import java.util.Arrays;
5963
import java.util.Observable;
6064
import java.util.Observer;
6165

@@ -114,7 +118,7 @@ public void update(Observable observable, Object obj) {
114118
* @see javax.swing.JTable#getColumnCount()
115119
*/
116120
public int getColumnCount() {
117-
return 2;
121+
return 3;
118122
}
119123

120124
/**
@@ -134,6 +138,8 @@ public Object getValueAt(int rowIndex, int columnIndex) {
134138
return getObjectReferenceByRow(rowIndex);
135139
case 1:
136140
return getObjectDescriptionByRow(rowIndex);
141+
case 2:
142+
return getByteOffSetByRow(rowIndex);
137143
default:
138144
return null;
139145
}
@@ -164,6 +170,69 @@ protected String getObjectDescriptionByRow(int rowIndex) {
164170
return PdfObjectTreeNode.getCaption(object);
165171
}
166172

173+
/**
174+
* Returns the byte offset of the selected XREF entry. If the entry has no real, actual offset.
175+
* i.e. it is compressed in a PDF Object Stream, then this shall return The ID of the Object Stream
176+
* and the offset of the Object Stream.
177+
*
178+
* @param rowIndex the index of the selected XREF entry
179+
* @return byte offset of the XREF entry or the ID and byte offset of the encompassing Object Stream
180+
*/
181+
private String getByteOffSetByRow(int rowIndex) {
182+
final PdfObject object = objects.getObjectByIndex(rowIndex);
183+
PdfIndirectReference indirectReference = object.getIndirectReference();
184+
if ( indirectReference != null ) {
185+
long offset = indirectReference.getOffset();
186+
187+
if ( offset == -1 ) {
188+
int objStreamNumber = indirectReference.getObjStreamNumber();
189+
PdfObject refersTo = indirectReference.getRefersTo();
190+
int compressedObjectNumber = refersTo.getIndirectReference().getObjNumber();
191+
int internalCompressedObjectOffset = -1;
192+
PdfObject objectByIndex = objects.loadObjectByReference(objStreamNumber);
193+
194+
PdfStream objStm = (PdfStream) objectByIndex;
195+
byte[] objStmBytes = objStm.getBytes(true);
196+
int byteOffsetOfFirst = objStm.getAsInt(PdfName.First);
197+
198+
PdfTokenizer pdfTokenizer = new PdfTokenizer(
199+
new RandomAccessFileOrArray( new RandomAccessSourceFactory().createSource(Arrays.copyOfRange(objStmBytes, 0, byteOffsetOfFirst)))
200+
);
201+
202+
try {
203+
while (pdfTokenizer.nextToken()) {
204+
if ( pdfTokenizer.getTokenType().equals(PdfTokenizer.TokenType.Number )) {
205+
int objNumber = pdfTokenizer.getIntValue();
206+
pdfTokenizer.nextToken();
207+
int internalByteOffset = pdfTokenizer.getIntValue();
208+
if ( objNumber == compressedObjectNumber ) {
209+
internalCompressedObjectOffset = internalByteOffset;
210+
break;
211+
}
212+
}
213+
}
214+
} catch (IOException e) {
215+
throw new RuntimeException(e);
216+
}
217+
218+
219+
220+
221+
PdfIndirectReference streamIndRef = objectByIndex.getIndirectReference();
222+
long streamOffset = streamIndRef.getOffset();
223+
224+
return String.format(
225+
Language.XREF_BYTE_OFFSET_OBJECT_STREAM.getString(),
226+
objStreamNumber, internalCompressedObjectOffset
227+
);
228+
}
229+
230+
return String.valueOf(offset);
231+
}
232+
return Language.XREF_NOT_LOADED_YET.getString();
233+
}
234+
235+
167236
/**
168237
* @see javax.swing.JTable#getColumnName(int)
169238
*/
@@ -173,6 +242,8 @@ public String getColumnName(int columnIndex) {
173242
return Language.XREF_NUMBER.getString();
174243
case 1:
175244
return Language.XREF_OBJECT.getString();
245+
case 2:
246+
return Language.XREF_BYTE_OFFSET.getString();
176247
default:
177248
return null;
178249
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
package com.itextpdf.rups.view.itext.treenodes;
2+
3+
import com.itextpdf.kernel.pdf.PdfObject;
4+
import com.itextpdf.rups.view.Language;
5+
6+
public class ObjectStreamTreeNode extends PdfObjectTreeNode {
7+
8+
public ObjectStreamTreeNode(PdfObject object) {
9+
super(object);
10+
setUserObject(Language.PDF_OBJECT_STREAMS_TREE_NODE.getString());
11+
}
12+
13+
}

src/main/resources/bundles/rups-lang.properties

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -144,6 +144,7 @@ PAGES=Pages
144144
PAGES_TABLE_OBJECT=Object %d
145145
146146
PDF_READING=Reading PDF document...
147+
PDF_OBJECT_STREAMS_TREE_NODE=PDF Object Streams
147148
PDF_OBJECT_TREE=PDF Object Tree (%s)
148149
149150
PLAINTEXT=Plain Text
@@ -192,7 +193,11 @@ TOOLTIP_HEX=Hex-editable binary content
192193
WARNING=Warning
193194
194195
XREF=XREF
196+
XREF_BYTE_OFFSET=Byte Offset
197+
XREF_BYTE_OFFSET_OBJECT_STREAM=Object Stream #%d (%d)
195198
XREF_DESCRIPTION=Cross-reference table
199+
XREF_NA=N/A
200+
XREF_NOT_LOADED_YET=Offset not loaded yet
196201
XREF_NUMBER=Number
197202
XREF_OBJECT=Object
198203
XREF_READING=Reading the Cross-Reference table

src/main/resources/bundles/rups-lang_nl_NL.properties

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -189,6 +189,7 @@ WARNING=Waarschuwing
189189
190190
XREF=XREF
191191
XREF_DESCRIPTION=Cross-reference tabel
192+
XREF_NOT_LOADED_YET=Offset is nog niet ingeladen
192193
XREF_NUMBER=Nummer
193194
XREF_OBJECT=Object
194195
XREF_READING=Lezen van de Cross-Reference tabel

0 commit comments

Comments
 (0)