Skip to content

Commit 5d39515

Browse files
committed
Extend supported PDF size up to 1 Tb
DEVSIX-1869
1 parent 4bc5b9b commit 5d39515

File tree

4 files changed

+158
-33
lines changed

4 files changed

+158
-33
lines changed

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

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -137,6 +137,30 @@ public PdfOutputStream write(PdfObject pdfObject) {
137137
return this;
138138
}
139139

140+
/**
141+
* Writes corresponding amount of bytes from a given long
142+
*
143+
* @param bytes a source of bytes, must be >= 0
144+
* @param size expected amount of bytes
145+
*/
146+
void write(long bytes, int size) throws IOException {
147+
assert bytes >= 0;
148+
while (--size >= 0) {
149+
write((byte) (bytes >> 8 * size & 0xff));
150+
}
151+
}
152+
153+
/**
154+
* Writes corresponding amount of bytes from a given int
155+
*
156+
* @param bytes a source of bytes, must be >= 0
157+
* @param size expected amount of bytes
158+
*/
159+
void write(int bytes, int size) throws IOException {
160+
//safe convert to long, despite sign.
161+
write(bytes & 0xFFFFFFFFL, size);
162+
}
163+
140164
private void write(PdfArray pdfArray) {
141165
writeByte('[');
142166
for (int i = 0; i < pdfArray.size(); i++) {
@@ -232,7 +256,6 @@ private void write(PdfString pdfString) {
232256
}
233257
}
234258

235-
236259
private void write(PdfName name) {
237260
writeByte('/');
238261
writeBytes(name.getInternalContent());

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

Lines changed: 28 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@ This file is part of the iText (R) project.
5454
import java.io.IOException;
5555
import java.io.Serializable;
5656
import java.util.ArrayList;
57+
import java.util.Arrays;
5758
import java.util.List;
5859
import java.util.Map;
5960
import java.util.TreeMap;
@@ -280,11 +281,10 @@ protected void writeXrefTableAndTrailer(PdfDocument document, PdfObject fileId,
280281
if (crypto != null)
281282
xrefStream.put(PdfName.Encrypt, crypto);
282283
xrefStream.put(PdfName.Size, new PdfNumber(this.size()));
283-
ArrayList<PdfObject> tmpArray = new ArrayList<PdfObject>(3);
284-
tmpArray.add(new PdfNumber(1));
285-
tmpArray.add(new PdfNumber(4));
286-
tmpArray.add(new PdfNumber(2));
287-
xrefStream.put(PdfName.W, new PdfArray(tmpArray));
284+
285+
int offsetSize = getOffsetSize(Math.max(startxref, size()));
286+
xrefStream.put(PdfName.W, new PdfArray(
287+
Arrays.asList((PdfObject) new PdfNumber(1), new PdfNumber(offsetSize), new PdfNumber(2))));
288288
xrefStream.put(PdfName.Info, document.getDocumentInfo().getPdfObject());
289289
xrefStream.put(PdfName.Root, document.getCatalog().getPdfObject());
290290
PdfArray index = new PdfArray();
@@ -304,18 +304,16 @@ protected void writeXrefTableAndTrailer(PdfDocument document, PdfObject fileId,
304304
PdfIndirectReference reference = xrefTable.get(i);
305305
if (reference.isFree()) {
306306
xrefStream.getOutputStream().write(0);
307-
assert reference.getOffset() < Integer.MAX_VALUE;
308-
xrefStream.getOutputStream().write(intToBytes((int) reference.getOffset()));
309-
xrefStream.getOutputStream().write(shortToBytes(reference.getGenNumber()));
307+
xrefStream.getOutputStream().write(reference.getOffset(), offsetSize);
308+
xrefStream.getOutputStream().write(reference.getGenNumber(), 2);
310309
} else if (reference.getObjStreamNumber() == 0) {
311310
xrefStream.getOutputStream().write(1);
312-
assert reference.getOffset() < Integer.MAX_VALUE;
313-
xrefStream.getOutputStream().write(intToBytes((int) reference.getOffset()));
314-
xrefStream.getOutputStream().write(shortToBytes(reference.getGenNumber()));
311+
xrefStream.getOutputStream().write(reference.getOffset(), offsetSize);
312+
xrefStream.getOutputStream().write(reference.getGenNumber(), 2);
315313
} else {
316314
xrefStream.getOutputStream().write(2);
317-
xrefStream.getOutputStream().write(intToBytes(reference.getObjStreamNumber()));
318-
xrefStream.getOutputStream().write(shortToBytes(reference.getIndex()));
315+
xrefStream.getOutputStream().write(reference.getObjStreamNumber(), offsetSize);
316+
xrefStream.getOutputStream().write(reference.getIndex(), 2);
319317
}
320318
}
321319
}
@@ -377,6 +375,23 @@ void clear() {
377375
count = 1;
378376
}
379377

378+
/**
379+
* Gets size of the offset. Max size is 2^40, i.e. 1 Tb.
380+
*/
381+
private int getOffsetSize(long startxref) {
382+
assert startxref >= 0 && startxref < (1L << 40);
383+
//initial size = 5 bytes. It is 1 Tb. Shall be enough.
384+
int size = 5;
385+
long mask = 0xff00000000L;
386+
for (; size > 1; size--) {
387+
if ((mask & startxref) != 0)
388+
break;
389+
// there is no need to use >>> because mask is positive
390+
mask >>= 8;
391+
}
392+
return size;
393+
}
394+
380395
/**
381396
* Convenience method to write the fingerprint preceding the trailer.
382397
* The fingerprint contains information on iText products used in the generation or manipulation
@@ -472,12 +487,4 @@ private void extendXref(int capacity) {
472487
System.arraycopy(xref, 0, newXref, 0, xref.length);
473488
xref = newXref;
474489
}
475-
476-
private static byte[] shortToBytes(int n) {
477-
return new byte[]{(byte) ((n >> 8) & 0xFF), (byte) (n & 0xFF)};
478-
}
479-
480-
private static byte[] intToBytes(int n) {
481-
return new byte[]{(byte) ((n >> 24) & 0xFF), (byte) ((n >> 16) & 0xFF), (byte) ((n >> 8) & 0xFF), (byte) (n & 0xFF)};
482-
}
483490
}

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

Lines changed: 106 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ This file is part of the iText (R) project.
4747
import com.itextpdf.io.image.ImageData;
4848
import com.itextpdf.io.image.ImageDataFactory;
4949
import com.itextpdf.io.source.ByteArrayOutputStream;
50+
import com.itextpdf.io.util.MessageFormatUtil;
5051
import com.itextpdf.io.util.StreamUtil;
5152
import com.itextpdf.io.util.UrlUtil;
5253
import com.itextpdf.kernel.PdfException;
@@ -67,23 +68,24 @@ This file is part of the iText (R) project.
6768
import com.itextpdf.kernel.pdf.colorspace.PdfDeviceCs;
6869
import com.itextpdf.kernel.pdf.colorspace.PdfSpecialCs;
6970
import com.itextpdf.kernel.pdf.extgstate.PdfExtGState;
71+
import com.itextpdf.kernel.pdf.xobject.PdfImageXObject;
7072
import com.itextpdf.kernel.utils.CompareTool;
7173
import com.itextpdf.test.ExtendedITextTest;
7274
import com.itextpdf.test.annotations.LogMessage;
7375
import com.itextpdf.test.annotations.LogMessages;
7476
import com.itextpdf.test.annotations.type.IntegrationTest;
7577
import org.junit.Assert;
7678
import org.junit.BeforeClass;
79+
import org.junit.Ignore;
7780
import org.junit.Test;
7881
import org.junit.experimental.categories.Category;
7982

80-
import java.awt.*;
83+
import java.awt.Toolkit;
8184
import java.io.ByteArrayInputStream;
8285
import java.io.FileInputStream;
8386
import java.io.IOException;
8487
import java.io.InputStream;
8588
import java.nio.charset.StandardCharsets;
86-
import com.itextpdf.io.util.MessageFormatUtil;
8789
import java.util.ArrayList;
8890
import java.util.HashMap;
8991
import java.util.List;
@@ -603,6 +605,56 @@ public void create1000PagesDocumentWithFullCompression() throws IOException {
603605
canvas.rectangle(100, 500, 100, 100).fill();
604606
canvas.release();
605607
page.flush();
608+
609+
}
610+
pdfDoc.close();
611+
612+
PdfReader reader = new PdfReader(filename);
613+
PdfDocument pdfDocument = new PdfDocument(reader);
614+
Assert.assertEquals("Rebuilt", false, reader.hasRebuiltXref());
615+
PdfDictionary info = pdfDocument.getDocumentInfo().getPdfObject();
616+
Assert.assertEquals("Author", author, info.get(PdfName.Author).toString());
617+
Assert.assertEquals("Creator", creator, info.get(PdfName.Creator).toString());
618+
Assert.assertEquals("Title", title, info.get(PdfName.Title).toString());
619+
Assert.assertEquals("Page count", pageCount, pdfDocument.getNumberOfPages());
620+
for (int i = 1; i <= pageCount; i++) {
621+
PdfDictionary page = pdfDocument.getPage(i).getPdfObject();
622+
Assert.assertEquals(PdfName.Page, page.get(PdfName.Type));
623+
}
624+
pdfDocument.close();
625+
}
626+
627+
@Test (timeout = 0)
628+
@Ignore("Too big result file. This test is for manual testing. -Xmx6g shall be set.")
629+
public void hugeDocumentWithFullCompression() throws IOException {
630+
int pageCount = 800;
631+
String filename = destinationFolder + "hugeDocumentWithFullCompression.pdf";
632+
633+
final String author = "Alexander Chingarev";
634+
final String creator = "iText 6";
635+
final String title = "Empty iText 6 Document";
636+
637+
PdfWriter writer = new PdfWriter(filename, new WriterProperties().setFullCompressionMode(true));
638+
PdfDocument pdfDoc = new PdfDocument(writer);
639+
pdfDoc.getDocumentInfo().setAuthor(author).
640+
setCreator(creator).
641+
setTitle(title);
642+
for (int i = 0; i < pageCount; i++) {
643+
PdfPage page = pdfDoc.addNewPage();
644+
PdfCanvas canvas = new PdfCanvas(page);
645+
canvas
646+
.saveState()
647+
.beginText()
648+
.moveText(36, 700)
649+
.setFontAndSize(PdfFontFactory.createFont(StandardFonts.HELVETICA), 72)
650+
.showText(Integer.toString(i + 1))
651+
.endText()
652+
.restoreState();
653+
PdfImageXObject xObject = new PdfImageXObject(ImageDataFactory.create(sourceFolder + "Willaerts_Adam_The_Embarkation_of_the_Elector_Palantine_Oil_Canvas-huge.jpg"));
654+
canvas.addXObject(xObject, 100, 500, 400);
655+
canvas.release();
656+
page.flush();
657+
606658
}
607659
pdfDoc.close();
608660

@@ -621,6 +673,49 @@ public void create1000PagesDocumentWithFullCompression() throws IOException {
621673
pdfDocument.close();
622674
}
623675

676+
@Test
677+
public void smallDocumentWithFullCompression() throws IOException {
678+
String filename = destinationFolder + "smallDocumentWithFullCompression.pdf";
679+
680+
final String author = "Alexander Chingarev";
681+
final String creator = "iText 6";
682+
final String title = "Empty iText 6 Document";
683+
684+
PdfWriter writer = new PdfWriter(filename, new WriterProperties().setFullCompressionMode(true));
685+
PdfDocument pdfDoc = new PdfDocument(writer);
686+
pdfDoc.getDocumentInfo().setAuthor(author).
687+
setCreator(creator).
688+
setTitle(title);
689+
690+
PdfPage page = pdfDoc.addNewPage();
691+
PdfCanvas canvas = new PdfCanvas(page);
692+
canvas
693+
.saveState()
694+
.beginText()
695+
.moveText(36, 700)
696+
.setFontAndSize(PdfFontFactory.createFont(StandardFonts.HELVETICA), 72)
697+
.showText("Hi!")
698+
.endText()
699+
.restoreState();
700+
page.flush();
701+
702+
pdfDoc.close();
703+
704+
PdfReader reader = new PdfReader(filename);
705+
PdfDocument pdfDocument = new PdfDocument(reader);
706+
Assert.assertEquals("Rebuilt", false, reader.hasRebuiltXref());
707+
PdfDictionary info = pdfDocument.getDocumentInfo().getPdfObject();
708+
Assert.assertEquals("Author", author, info.get(PdfName.Author).toString());
709+
Assert.assertEquals("Creator", creator, info.get(PdfName.Creator).toString());
710+
Assert.assertEquals("Title", title, info.get(PdfName.Title).toString());
711+
Assert.assertEquals("Page count", 1, pdfDocument.getNumberOfPages());
712+
713+
page = pdfDocument.getPage(1);
714+
Assert.assertEquals(PdfName.Page, page.getPdfObject().get(PdfName.Type));
715+
716+
pdfDocument.close();
717+
}
718+
624719
@Test
625720
public void create100PagesDocumentWithFullCompression() throws IOException {
626721
int pageCount = 100;
@@ -760,7 +855,7 @@ public void create10PagesDocumentWithFullCompression() throws IOException {
760855
}
761856

762857
@Test
763-
public void copyPagesTest1() throws IOException, InterruptedException {
858+
public void copyPagesTest1() throws IOException, InterruptedException {
764859
String file1 = destinationFolder + "copyPages1_1.pdf";
765860
String file2 = destinationFolder + "copyPages1_2.pdf";
766861

@@ -807,7 +902,7 @@ public void copyPagesTest1() throws IOException, InterruptedException {
807902
}
808903

809904
@Test
810-
public void copyPagesTest2() throws IOException, InterruptedException {
905+
public void copyPagesTest2() throws IOException {
811906
String file1 = destinationFolder + "copyPages2_1.pdf";
812907
String file2 = destinationFolder + "copyPages2_2.pdf";
813908

@@ -861,7 +956,7 @@ public void copyPagesTest2() throws IOException, InterruptedException {
861956
}
862957

863958
@Test
864-
public void copyPagesTest3() throws IOException, InterruptedException {
959+
public void copyPagesTest3() throws IOException {
865960
String file1 = destinationFolder + "copyPages3_1.pdf";
866961
String file2 = destinationFolder + "copyPages3_2.pdf";
867962

@@ -913,7 +1008,7 @@ public void copyPagesTest3() throws IOException, InterruptedException {
9131008
}
9141009

9151010
@Test
916-
public void copyPagesTest4() throws IOException, InterruptedException {
1011+
public void copyPagesTest4() throws IOException {
9171012
String file1 = destinationFolder + "copyPages4_1.pdf";
9181013
PdfDocument pdfDoc1 = new PdfDocument(new PdfWriter(file1));
9191014

@@ -961,7 +1056,7 @@ public void copyPagesTest4() throws IOException, InterruptedException {
9611056

9621057

9631058
@Test
964-
public void copyPagesTest5() throws IOException, InterruptedException {
1059+
public void copyPagesTest5() throws IOException {
9651060

9661061
int documentCount = 3;
9671062

@@ -1013,7 +1108,7 @@ public void copyPagesTest5() throws IOException, InterruptedException {
10131108
}
10141109

10151110
@Test
1016-
public void copyPagesTest6() throws IOException, InterruptedException {
1111+
public void copyPagesTest6() throws IOException {
10171112
String file1 = destinationFolder + "copyPages6_1.pdf";
10181113
String file2 = destinationFolder + "copyPages6_2.pdf";
10191114
String file3 = destinationFolder + "copyPages6_3.pdf";
@@ -1083,7 +1178,7 @@ public void copyPagesTest6() throws IOException, InterruptedException {
10831178
}
10841179

10851180
@Test
1086-
public void markedContentTest1() throws Exception {
1181+
public void markedContentTest1() {
10871182
String message = "";
10881183
PdfDocument document = new PdfDocument(new PdfWriter(new ByteArrayOutputStream()));
10891184
PdfPage page = document.addNewPage();
@@ -1124,7 +1219,7 @@ public void markedContentTest2() throws Exception {
11241219
}
11251220

11261221
@Test
1127-
public void graphicsStateTest1() throws Exception {
1222+
public void graphicsStateTest1() {
11281223
PdfDocument document = new PdfDocument(new PdfWriter(new ByteArrayOutputStream()));
11291224
PdfPage page = document.addNewPage();
11301225
PdfCanvas canvas = new PdfCanvas(page);
@@ -1475,7 +1570,7 @@ public void gifImageTest03() throws IOException, InterruptedException {
14751570
}
14761571

14771572
@Test
1478-
public void gifImageTest04() throws IOException, InterruptedException {
1573+
public void gifImageTest04() throws IOException {
14791574
PdfDocument document = new PdfDocument(new PdfWriter(destinationFolder + "gifImageTest04.pdf"));
14801575
PdfPage page = document.addNewPage();
14811576

Loading

0 commit comments

Comments
 (0)