user6839234
user6839234

Reputation: 57

iText7 Java adding text issue

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 :

Issue image

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 point

number: 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

edited Simulated doc

Upvotes: 0

Views: 611

Answers (1)

mkl
mkl

Reputation: 95928

The cause

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.

A work-around

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

Related Questions