Skip to content

Commit 57c22eb

Browse files
Fix issues with free references writing to xref table
- avoid rereading deleted objects; - update generation number only on objects deletion; - always write all free references to xref table (even in append mode). DEVSIX-1428
1 parent f66f0b4 commit 57c22eb

File tree

5 files changed

+147
-68
lines changed

5 files changed

+147
-68
lines changed

kernel/src/main/java/com/itextpdf/kernel/pdf/PdfIndirectReference.java

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -265,11 +265,8 @@ protected PdfReader getReader() {
265265
return null;
266266
}
267267

268-
// NOTE In append mode object could be OriginalObjectStream, but not Modified,
269-
// so information about this reference would not be added to the new Cross-Reference table.
270-
// In stamp mode without append the reference will be free.
271268
protected boolean isFree() {
272-
return checkState(FREE) || checkState(ORIGINAL_OBJECT_STREAM);
269+
return checkState(FREE);
273270
}
274271

275272
@Override

kernel/src/main/java/com/itextpdf/kernel/pdf/PdfReader.java

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -830,8 +830,6 @@ protected PdfDictionary readXrefSection() throws IOException {
830830
} else if (reference.checkState(PdfObject.READING) && reference.getGenNumber() == gen) {
831831
reference.setOffset(pos);
832832
reference.clearState(PdfObject.READING);
833-
} else if (reference.objNr == 0 && pos != 0L) {
834-
reference.setIndex(pos);
835833
} else {
836834
continue;
837835
}
@@ -843,7 +841,7 @@ protected PdfDictionary readXrefSection() throws IOException {
843841
}
844842
} else if (tokens.tokenValueEqualsTo(PdfTokenizer.F)) {
845843
if (xref.get(num) == null) {
846-
reference.setFree();
844+
xref.freeReference(reference, true);
847845
xref.add(reference);
848846
}
849847
} else

kernel/src/main/java/com/itextpdf/kernel/pdf/PdfXrefTable.java

Lines changed: 29 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,8 @@ This file is part of the iText (R) project.
5555
import java.util.List;
5656
import java.util.TreeSet;
5757

58+
import static com.itextpdf.kernel.pdf.PdfObject.ORIGINAL_OBJECT_STREAM;
59+
5860
class PdfXrefTable implements Serializable {
5961

6062
private static final long serialVersionUID = 4171655392492002944L;
@@ -68,7 +70,7 @@ class PdfXrefTable implements Serializable {
6870
private PdfIndirectReference[] xref;
6971
private int count = 0;
7072

71-
private final TreeSet<Integer> freeReferences;
73+
private final TreeSet<Integer> freeReferences; // TODO not using this collection for now
7274

7375
public PdfXrefTable() {
7476
this(INITIAL_CAPACITY);
@@ -129,16 +131,20 @@ PdfIndirectReference createNewIndirectReference(PdfDocument document) {
129131
}
130132

131133
protected void freeReference(PdfIndirectReference reference) {
134+
freeReference(reference, false);
135+
}
136+
137+
void freeReference(PdfIndirectReference reference, boolean readingFreeReference) {
138+
reference.setOffset(0);
132139
reference.setState(PdfObject.FREE);
133140
if (!reference.checkState(PdfObject.FLUSHED)) {
134141
if (reference.refersTo != null) {
135142
reference.refersTo.setIndirectReference(null).setState(PdfObject.MUST_BE_INDIRECT);
136143
reference.refersTo = null;
137144
}
138-
if (reference.getGenNumber() < MAX_GENERATION) {
139-
freeReferences.add(reference.getObjNumber());
140-
ensureCount(Math.max(this.count, reference.getObjNumber()));
141-
}
145+
}
146+
if (!readingFreeReference && reference.getGenNumber() < MAX_GENERATION) {
147+
reference.genNr++;
142148
}
143149
}
144150

@@ -155,10 +161,6 @@ protected void setCapacity(int capacity) {
155161
*/
156162
protected void writeXrefTableAndTrailer(PdfDocument document, PdfObject fileId, PdfObject crypto) throws IOException {
157163
PdfWriter writer = document.getWriter();
158-
// Increment generation number for all freed references.
159-
for (Integer objNr : freeReferences) {
160-
xref[(int) objNr].genNr++;
161-
}
162164

163165
for (int i = count; i > 0; --i) {
164166
PdfIndirectReference lastRef = xref[i];
@@ -172,17 +174,27 @@ protected void writeXrefTableAndTrailer(PdfDocument document, PdfObject fileId,
172174
}
173175
}
174176

177+
int lastFreeObjNr = 0;
178+
for (int i = count; i >= 0; --i) {
179+
PdfIndirectReference ref = xref[i];
180+
if (ref == null) {
181+
ref = new PdfIndirectReference(document, i, 0).setState(PdfObject.FREE);
182+
xref[i] = ref;
183+
}
184+
if (ref.isFree()) {
185+
ref.setOffset(lastFreeObjNr);
186+
lastFreeObjNr = i;
187+
}
188+
}
189+
175190
List<Integer> sections = new ArrayList<>();
176191
int first = 0;
177192
int len = 1;
178-
if (document.isAppendMode()) {
179-
first = 1;
180-
len = 0;
181-
}
182193
for (int i = 1; i < size(); i++) {
183194
PdfIndirectReference reference = xref[i];
184195
if (reference != null) {
185-
if (document.properties.appendMode && !reference.checkState(PdfObject.MODIFIED)) {
196+
if (document.properties.appendMode && !reference.checkState(PdfObject.MODIFIED)
197+
&& !reference.isFree()) {
186198
reference = null;
187199
}
188200
}
@@ -206,7 +218,8 @@ protected void writeXrefTableAndTrailer(PdfDocument document, PdfObject fileId,
206218
sections.add(first);
207219
sections.add(len);
208220
}
209-
if (document.properties.appendMode && sections.size() == 0) { // no modifications.
221+
if (document.properties.appendMode && sections.size() == 2
222+
&& sections.get(0) == 0 && sections.get(1) == 1) { // no modifications.
210223
xref = null;
211224
return;
212225
}
@@ -273,17 +286,7 @@ protected void writeXrefTableAndTrailer(PdfDocument document, PdfObject fileId,
273286
for (int i = first; i < first + len; i++) {
274287
PdfIndirectReference reference = xrefTable.get(i);
275288

276-
StringBuilder off = new StringBuilder("0000000000");
277-
if (reference.isFree()) {
278-
if (!freeReferences.isEmpty()) {
279-
off.append(freeReferences.pollFirst());
280-
}
281-
/* if (freeReferences.isEmpty()), then we are at the
282-
last free reference. Its referral value must be object 0.
283-
*/
284-
} else {
285-
off.append(reference.getOffset());
286-
}
289+
StringBuilder off = new StringBuilder("0000000000").append(reference.getOffset());
287290
StringBuilder gen = new StringBuilder("00000").append(reference.getGenNumber());
288291
writer.writeString(off.substring(off.length() - 10, off.length())).writeSpace().
289292
writeString(gen.substring(gen.length() - 5, gen.length())).writeSpace();
@@ -312,7 +315,6 @@ protected void writeXrefTableAndTrailer(PdfDocument document, PdfObject fileId,
312315
writer.write(document.getTrailer());
313316
writer.write('\n');
314317
}
315-
freeReferences.clear();
316318
writeKeyInfo(writer);
317319
writer.writeString("startxref\n").
318320
writeLong(startxref).

kernel/src/test/java/com/itextpdf/kernel/pdf/FreeReferencesTest.java

Lines changed: 101 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -32,15 +32,19 @@ public void freeReferencesTest01() throws IOException {
3232
String[] xrefString = extractXrefTableAsStrings(out);
3333
String[] expected = new String[] {
3434
"xref\n" +
35-
"0 5\n" +
36-
"0000000010 65535 f \n" +
35+
"0 15\n" +
36+
"0000000004 65535 f \n" +
3737
"0000000269 00000 n \n" +
3838
"0000000561 00000 n \n" +
3939
"0000000314 00000 n \n" +
40-
"0000000011 65535 f \n" +
41-
"10 5\n" + // TODO first xref shall have no subsections
40+
"0000000005 65535 f \n" +
41+
"0000000006 00000 f \n" +
42+
"0000000007 00000 f \n" +
43+
"0000000008 00000 f \n" +
44+
"0000000009 00000 f \n" +
45+
"0000000010 00000 f \n" +
46+
"0000000011 00000 f \n" +
4247
"0000000000 00001 f \n" +
43-
"0000000000 00002 f \n" + // TODO linked list of refs is invalid
4448
"0000000133 00000 n \n" +
4549
"0000000015 00000 n \n" +
4650
"0000000613 00000 n \n" };
@@ -54,7 +58,7 @@ public void freeReferencesTest02() throws IOException {
5458

5559
PdfDocument pdfDocument = new PdfDocument(new PdfReader(sourceFolder + src), new PdfWriter(destinationFolder + out),
5660
new StampingProperties().useAppendMode());
57-
pdfDocument.close(); // TODO exception is thrown on attempt to read free reference
61+
pdfDocument.close();
5862

5963
String[] xrefString = extractXrefTableAsStrings(out);
6064
String[] expected = new String[] {
@@ -73,8 +77,18 @@ public void freeReferencesTest02() throws IOException {
7377
"0000000480 00000 n \n",
7478

7579
"xref\n" +
76-
"3 1\n" +
77-
"0000000995 00000 n \n"};
80+
"0 1\n" +
81+
"0000000004 65535 f \n" +
82+
"3 9\n" +
83+
"0000000995 00000 n \n" +
84+
"0000000005 65535 f \n" +
85+
"0000000006 00000 f \n" +
86+
"0000000007 00000 f \n" +
87+
"0000000008 00000 f \n" +
88+
"0000000009 00000 f \n" +
89+
"0000000010 00000 f \n" +
90+
"0000000011 00000 f \n" +
91+
"0000000000 00001 f \n"};
7892
Assert.assertArrayEquals(expected, xrefString);
7993
}
8094

@@ -111,10 +125,13 @@ public void freeReferencesTest03() throws IOException {
111125
"0000001303 00000 n \n",
112126

113127
"xref\n" +
114-
"1 3\n" +
128+
"0 4\n" +
129+
"0000000005 65535 f \n" +
115130
"0000001706 00000 n \n" +
116131
"0000001998 00000 n \n" +
117132
"0000001751 00000 n \n" +
133+
"5 1\n" +
134+
"0000000000 00002 f \n" +
118135
"8 2\n" +
119136
"0000002055 00000 n \n" +
120137
"0000002156 00000 n \n"};
@@ -133,9 +150,22 @@ public void freeReferencesTest04() throws IOException {
133150
PdfIndirectReference contentsRef = (PdfIndirectReference) contentsObj;
134151
contentsRef.setFree();
135152
PdfObject freedContentsRefRefersTo = contentsRef.getRefersTo();
136-
Assert.assertNull(freedContentsRefRefersTo); // TODO assertion fails. Free reference should not be reread, see freeReferencesTest02
153+
Assert.assertNull(freedContentsRefRefersTo);
137154
pdfDocument.close();
138155

156+
String[] xrefString = extractXrefTableAsStrings(out);
157+
String[] expected = new String[] {
158+
"xref\n" +
159+
"0 7\n" +
160+
"0000000005 65535 f \n" +
161+
"0000000133 00000 n \n" +
162+
"0000000425 00000 n \n" +
163+
"0000000178 00000 n \n" +
164+
"0000000015 00000 n \n" +
165+
"0000000000 00001 f \n" +
166+
"0000000476 00000 n \n"
167+
};
168+
Assert.assertArrayEquals(expected, xrefString);
139169
}
140170

141171
@Test
@@ -148,13 +178,19 @@ public void freeReferencesTest05() throws IOException {
148178
String[] xrefString = extractXrefTableAsStrings(out);
149179
String[] expected = new String[] {
150180
"xref\n" +
151-
"0 4\n" +
152-
"0000000000 65535 f \n" +
181+
"0 14\n" +
182+
"0000000004 65535 f \n" +
153183
"0000000269 00000 n \n" +
154184
"0000000561 00000 n \n" +
155185
"0000000314 00000 n \n" +
156-
"11 3\n" +
157-
"0000000133 00000 n \n" + // TODO first xref shall have no subsections
186+
"0000000005 00000 f \n" +
187+
"0000000006 00000 f \n" +
188+
"0000000007 00000 f \n" +
189+
"0000000008 00000 f \n" +
190+
"0000000009 00000 f \n" +
191+
"0000000010 00000 f \n" +
192+
"0000000000 00000 f \n" +
193+
"0000000133 00000 n \n" +
158194
"0000000015 00000 n \n" +
159195
"0000000613 00000 n \n"
160196
};
@@ -184,8 +220,17 @@ public void freeReferencesTest06() throws IOException {
184220
"0000000480 00000 n \n",
185221

186222
"xref\n" +
187-
"3 1\n" +
188-
"0000000935 00000 n \n"
223+
"0 1\n" +
224+
"0000000004 65535 f \n" +
225+
"3 8\n" +
226+
"0000000935 00000 n \n" +
227+
"0000000005 00000 f \n" +
228+
"0000000006 00000 f \n" +
229+
"0000000007 00000 f \n" +
230+
"0000000008 00000 f \n" +
231+
"0000000009 00000 f \n" +
232+
"0000000010 00000 f \n" +
233+
"0000000000 00000 f \n"
189234
};
190235
Assert.assertArrayEquals(expected, xrefString);
191236
}
@@ -215,6 +260,46 @@ public void freeReferencesTest07() throws IOException {
215260
Assert.assertArrayEquals(expected, xrefString);
216261
}
217262

263+
@Test
264+
public void freeReferencesTest08() throws IOException {
265+
String src = "simpleDoc.pdf";
266+
String out = "freeReferencesTest08.pdf";
267+
268+
PdfDocument pdfDocument = new PdfDocument(new PdfReader(sourceFolder + src), new PdfWriter(destinationFolder + out),
269+
new StampingProperties().useAppendMode());
270+
PdfObject contentsObj = pdfDocument.getPage(1).getPdfObject().remove(PdfName.Contents);
271+
pdfDocument.getPage(1).setModified();
272+
Assert.assertTrue(contentsObj instanceof PdfIndirectReference);
273+
274+
PdfIndirectReference contentsRef = (PdfIndirectReference) contentsObj;
275+
contentsRef.setFree();
276+
PdfObject freedContentsRefRefersTo = contentsRef.getRefersTo();
277+
Assert.assertNull(freedContentsRefRefersTo);
278+
pdfDocument.close();
279+
280+
String[] xrefString = extractXrefTableAsStrings(out);
281+
String[] expected = new String[] {
282+
"xref\n" +
283+
"0 7\n" +
284+
"0000000000 65535 f \n" +
285+
"0000000265 00000 n \n" +
286+
"0000000564 00000 n \n" +
287+
"0000000310 00000 n \n" +
288+
"0000000132 00000 n \n" +
289+
"0000000015 00000 n \n" +
290+
"0000000476 00000 n \n",
291+
292+
"xref\n" +
293+
"0 1\n" +
294+
"0000000005 65535 f \n" +
295+
"3 3\n" +
296+
"0000000923 00000 n \n" +
297+
"0000001170 00000 n \n" +
298+
"0000000000 00001 f \n"
299+
};
300+
Assert.assertArrayEquals(expected, xrefString);
301+
}
302+
218303
private String[] extractXrefTableAsStrings(String out) throws IOException {
219304
byte[] outPdfBytes = readFile(destinationFolder + out);
220305
String outPdfContent = new String(outPdfBytes, StandardCharsets.US_ASCII);

0 commit comments

Comments
 (0)