Reputation: 57
I'm using iText 7 to duplicate pdf pages and numbering these pages. So I don't need to numbering them manually. But there's a problem with the numbers in the generated pdf file. Here's what it looks like :
And I have think about it for many hours, still can't figure it out.
My code :
import com.itextpdf.io.font.FontConstants;
import com.itextpdf.kernel.font.PdfFontFactory;
import com.itextpdf.kernel.pdf.PdfDocument;
import com.itextpdf.kernel.pdf.PdfPage;
import com.itextpdf.kernel.pdf.PdfReader;
import com.itextpdf.kernel.pdf.PdfWriter;
import com.itextpdf.kernel.pdf.canvas.PdfCanvas;
import java.io.File;
import java.io.IOException;
public class NumberingInJava {
public static final String SRC = "D:/temp/num_src.pdf";
public static final String DEST = "D:/temp/edited_numbering.pdf";
public static final String[] NUM4SAMPLE = {"02A", "03A", "03B", "03C", "04A", "08A"};
public static final double XCOOR = 230;
public static final double YCOOR = 795;//755
public static void main(String[] args) throws IOException {
File file = new File(DEST);
file.getParentFile().mkdirs();
new NumberingInJava().manipulatePdf(SRC, DEST, NUM4SAMPLE);
}
private void manipulatePdf(String src, String dest, String[] numbering4what) throws IOException {
//Initialize PDF document
PdfDocument pdfDocToRead = new PdfDocument(new PdfReader(src));
PdfDocument pdfDocToWrite = new PdfDocument(new PdfWriter(dest));
for(String s : numbering4what) {
println(s);
}
String number = null;
PdfPage tempPage = null;
for (int i=0; i<numbering4what.length; i++) {
pdfDocToRead.copyPagesTo(1, 2, pdfDocToWrite);
number = numbering4what[i];
println(number);
tempPage = pdfDocToWrite.getPage(2*(i+1)-1);
numberingPage(tempPage, number);
println("pdfDocToWrite.numberOfPages : "+pdfDocToWrite.getNumberOfPages());
}
pdfDocToRead.close();
pdfDocToWrite.close();
println("\nNumber added!");
}
private void numberingPage(PdfPage pdfPage, String number) throws IOException {
println(pdfPage);
PdfCanvas canvas = new PdfCanvas(pdfPage);
canvas.beginText().setFontAndSize(PdfFontFactory.createFont(FontConstants.HELVETICA), 22)
.moveText(XCOOR, YCOOR)
.showText(number)
.endText();
println("number: "+number);
}
private void println(Object obj) {
System.out.println(obj);
}
}
The console output:
02A 03A 03B 03C 04A 08A 02A com.itextpdf.kernel.pdf.PdfPage@cf768c 17:58:07,457 |-INFO in ch.qos.logback.classic.LoggerContext[default] - Could NOT find resource [logback.groovy] 17:58:07,458 |-INFO in ch.qos.logback.classic.LoggerContext[default] - Could NOT find resource [logback-test.xml] 17:58:07,458 |-INFO in ch.qos.logback.classic.LoggerContext[default] - Found resource [logback.xml] at [jar:file:/D:/Java_Packages/ext_lib/iText/itext7-7.0.1/itext7-itext-rups-7.0.1.jar!/logback.xml] 17:58:07,459 |-WARN in ch.qos.logback.classic.LoggerContext[default] - Resource [logback.xml] occurs multiple times on the classpath. 17:58:07,459 |-WARN in ch.qos.logback.classic.LoggerContext[default] - Resource [logback.xml] occurs at [jar:file:/D:/Java_Packages/ext_lib/iText/itext7-7.0.1/itext7-itext-rups-7.0.1-jar-with-dependencies.jar!/logback.xml] 17:58:07,459 |-WARN in ch.qos.logback.classic.LoggerContext[default] - Resource [logback.xml] occurs at [jar:file:/D:/Java_Packages/ext_lib/iText/itext7-7.0.1/itext7-itext-rups-7.0.1-sources.jar!/logback.xml] 17:58:07,459 |-WARN in ch.qos.logback.classic.LoggerContext[default] - Resource [logback.xml] occurs at [jar:file:/D:/Java_Packages/ext_lib/iText/itext7-7.0.1/itext7-itext-rups-7.0.1.jar!/logback.xml] 17:58:07,501 |-INFO in ch.qos.logback.core.joran.spi.ConfigurationWatchList@8080bb - URL [jar:file:/D:/Java_Packages/ext_lib/iText/itext7-7.0.1/itext7-itext-rups-7.0.1.jar!/logback.xml] is not of type file 17:58:07,662 |-INFO in ch.qos.logback.classic.joran.action.ConfigurationAction - debug attribute not set 17:58:07,829 |-INFO in ch.qos.logback.core.joran.action.AppenderAction - About to instantiate appender of type [com.itextpdf.rups.view.DebugAppender] 17:58:07,851 |-INFO in ch.qos.logback.core.joran.action.AppenderAction - Naming appender as [DEFAULT_APP] 17:58:07,940 |-INFO in ch.qos.logback.core.joran.action.NestedComplexPropertyIA - Assuming default type [ch.qos.logback.classic.encoder.PatternLayoutEncoder] for [encoder] property 17:58:08,045 |-INFO in ch.qos.logback.core.joran.action.AppenderAction - About to instantiate appender of type [com.itextpdf.rups.view.StyleAppender] 17:58:08,046 |-INFO in ch.qos.logback.core.joran.action.AppenderAction - Naming appender as [INFO_APP] 17:58:08,062 |-INFO in ch.qos.logback.core.joran.action.NestedComplexPropertyIA - Assuming default type [ch.qos.logback.classic.encoder.PatternLayoutEncoder] for [encoder] property 17:58:08,063 |-INFO in ch.qos.logback.core.joran.action.AppenderAction - About to instantiate appender of type [com.itextpdf.rups.view.DebugAppender] 17:58:08,063 |-INFO in ch.qos.logback.core.joran.action.AppenderAction - Naming appender as [DEBUG_APP] 17:58:08,065 |-INFO in ch.qos.logback.core.joran.action.NestedComplexPropertyIA - Assuming default type [ch.qos.logback.classic.encoder.PatternLayoutEncoder] for [encoder] property 17:58:08,066 |-INFO in ch.qos.logback.core.joran.action.AppenderAction - About to instantiate appender of type [com.itextpdf.rups.view.DebugAppender] 17:58:08,066 |-INFO in ch.qos.logback.core.joran.action.AppenderAction - Naming appender as [TRACE_APP] 17:58:08,067 |-INFO in ch.qos.logback.core.joran.action.NestedComplexPropertyIA - Assuming default type [ch.qos.logback.classic.encoder.PatternLayoutEncoder] for [encoder] property 17:58:08,069 |-INFO in ch.qos.logback.core.joran.action.AppenderAction - About to instantiate appender of type [com.itextpdf.rups.view.StyleAppender] 17:58:08,069 |-INFO in ch.qos.logback.core.joran.action.AppenderAction - Naming appender as [IMPORTANT_APP] 17:58:08,075 |-INFO in ch.qos.logback.core.joran.action.NestedComplexPropertyIA - Assuming default type [ch.qos.logback.classic.encoder.PatternLayoutEncoder] for [encoder] property 17:58:08,076 |-INFO in ch.qos.logback.classic.joran.action.LoggerAction - Setting additivity of logger [com.itextpdf] to false 17:58:08,077 |-INFO in ch.qos.logback.core.joran.action.AppenderRefAction - Attaching appender named [IMPORTANT_APP] to Logger[com.itextpdf] 17:58:08,078 |-INFO in ch.qos.logback.core.joran.action.AppenderRefAction - Attaching appender named [INFO_APP] to Logger[com.itextpdf] 17:58:08,078 |-INFO in ch.qos.logback.core.joran.action.AppenderRefAction - Attaching appender named [DEBUG_APP] to Logger[com.itextpdf] 17:58:08,078 |-INFO in ch.qos.logback.core.joran.action.AppenderRefAction - Attaching appender named [TRACE_APP] to Logger[com.itextpdf] 17:58:08,078 |-INFO in ch.qos.logback.classic.joran.action.RootLoggerAction - Setting level of ROOT logger to TRACE 17:58:08,078 |-INFO in ch.qos.logback.core.joran.action.AppenderRefAction - Attaching appender named [DEFAULT_APP] to Logger[ROOT] 17:58:08,078 |-INFO in ch.qos.logback.classic.joran.action.ConfigurationAction - End of configuration. 17:58:08,082 |-INFO in ch.qos.logback.classic.joran.JoranConfigurator@1c24521 - Registering current configuration as safe fallback pointnumber: 02A pdfDocToWrite.numberOfPages : 2 03A com.itextpdf.kernel.pdf.PdfPage@11aa95a number: 03A pdfDocToWrite.numberOfPages : 4 03B com.itextpdf.kernel.pdf.PdfPage@bc05a6 number: 03B pdfDocToWrite.numberOfPages : 6 03C com.itextpdf.kernel.pdf.PdfPage@ef309d number: 03C pdfDocToWrite.numberOfPages : 8 04A com.itextpdf.kernel.pdf.PdfPage@1fc609f number: 04A pdfDocToWrite.numberOfPages : 10 08A com.itextpdf.kernel.pdf.PdfPage@173813a number: 08A pdfDocToWrite.numberOfPages : 12
Number added!
Process finished with exit code 0
EDIT: I have uploaded a simulated document and an edited document on dropbox, here it is: Simulated doc
Upvotes: 0
Views: 611
Reputation: 95928
The problem is caused iText trying to make the result PDF small:
When you copy the pages of pdfDocToRead
multiple times to pdfDocToWrite
, the actual page content stream, page resources, etc. are copied only once, merely a small object referencing these data is generated once for each copy and page.
This optimization is not yet the problem, but a further micro-optimization is, in
PdfCanvas canvas = new PdfCanvas(pdfPage);
this PdfCanvas
helper method is used to retrieve the content stream to which content will be added:
private static PdfStream getPageStream(PdfPage page) {
PdfStream stream = page.getContentStream(page.getContentStreamCount() - 1);
return stream == null || stream.getOutputStream() == null || stream.containsKey(PdfName.Filter) ? page.newContentStreamAfter() : stream;
}
As you see, usually a new content stream is added to the page (page.newContentStreamAfter()
); only if there already is a content stream and this stream has no filter (e.g. for compression), this existing content stream is used to append data to.
In case of your document the single content stream copied for each source page is not compressed. Thus, those two optimizations result in all your PdfCanvas canvas
instances appending to the same single content stream.
The obvious work-around consists of circumventing the latter optimization: Replace the line
PdfCanvas canvas = new PdfCanvas(pdfPage);
by
PdfCanvas canvas = new PdfCanvas(pdfPage.newContentStreamAfter(), pdfPage.getResources(), pdfPage.getDocument());
(in StampPageNumbers.java method numberingPage
)
This essentially is what also would have happened if the original content stream was compressed.
(In general one might actually have to add a new content stream before the current with a save-graphics-state instruction and initially add a restore-graphics-state instruction to the new content stream after the current; in case of your sample document, though, this is not necessary because the graphics state is not adversely changed by the current content.)
Upvotes: 0