Pregunta

We are attempting to generate documents using iText that are formed largely from "template" files - smaller PDF files that are combined together into one composite file using the PdfContentByte.addTemplate method. We then automatically and silently print the new file using the *nix command lp. This usually works; however occasionally, some files that are generated will fail to print. The document proceeds through all queues and arrives at the printer proper (a Lexmark T652n, in this case), its physical display gives a message of pending progress, and even its mechanical components whir up in preparation - then, the printing job vanishes spontaneously without a trace, and the printer returns to being ready.

The oddity in how specific this issue tends to be. For starters, the files in question print without fail when done manually through Adobe PDF Viewer, and can be read fine by editors like Adobe Live Cycle. Furthermore, the content of the file effects whether it is plagued by this issue, but not in a clear way - adding a specific template 20 times could cause the problem, while doing it 19 or 21 times might be fine, or using a different template will change the pattern entirely and might cause it to happen instead after 37 times. Generating a document with the exact same content will be consistent on whether or not the issue occurs, but any subtle and seemingly irrelevant change in content will change whether the problem happens.

While it could be considered a hardware issue, the fact remains that certain iText-generated files have this issue while others do not. Is our method of file creation sometimes creating files that are somehow considered corrupt only to the printer and only sometimes?

Here is a relatively small code example that generates documents using the repetitive template method similar to our main program. It uses this file as a template and repeats it a specified number of times.

public class PDFFileMaker {

    private static final int INCH = 72;

    final private static float MARGIN_TOP = INCH / 4;
    final private static float MARGIN_BOTTOM = INCH / 2;

    private static final String DIREC = "/pdftest/";
    private static final String OUTPUT_FILEPATH = DIREC + "cooldoc_%d.pdf";
    private static final String TEMPLATE1_FILEPATH = DIREC + "template1.pdf";
    private static final Rectangle PAGE_SIZE = PageSize.LETTER;
    private static final Rectangle TEMPLATE_SIZE = PageSize.LETTER;

    private ByteArrayOutputStream workingBuffer;
    private ByteArrayOutputStream storageBuffer;
    private ByteArrayOutputStream templateBuffer;

    private float currPosition;
    private int currPage;
    private int formFillCount;
    private int templateTotal;

    private static final int DEFAULT_NUMBER_OF_TIMES = 23;

    public static void main (String [] args) {

        System.out.println("Starting...");

        PDFFileMaker maker = new PDFFileMaker();

        File file = null;
        try {
            file = maker.createPDF(DEFAULT_NUMBER_OF_TIMES);
        }
        catch (Exception e) {
            e.printStackTrace();
        }

        if (file == null || !file.exists()) {
            System.out.println("File failed to be created.");
        }
        else {
            System.out.println("File creation successful.");
        }
    }

    public File createPDF(int inCount) throws Exception {

        templateTotal = inCount;

        String sFilepath = String.format(OUTPUT_FILEPATH, templateTotal);

        workingBuffer = new ByteArrayOutputStream();
        storageBuffer = new ByteArrayOutputStream();
        templateBuffer = new ByteArrayOutputStream();

        startPDF();
        doMainSegment();
        finishPDF(sFilepath);

        return new File(sFilepath);
    }

    private void startPDF() throws DocumentException, FileNotFoundException {

        Document d = new Document(PAGE_SIZE);
        PdfWriter w = PdfWriter.getInstance(d, workingBuffer);
        d.open();
        d.add(new Paragraph(" "));
        d.close();
        w.close();

        currPosition = 0;
        currPage = 1;
        formFillCount = 1;
    }

    protected void finishPDF(String sFilepath) throws DocumentException, IOException {
        //Transfers data from buffer 1 to builder file
        PdfReader r = new PdfReader(workingBuffer.toByteArray());
        PdfStamper s = new PdfStamper(r, new FileOutputStream(sFilepath));
        s.setFullCompression();
        r.close();
        s.close();
    }

    private void doMainSegment() throws FileNotFoundException, IOException, DocumentException {
        File fTemplate1 = new File(TEMPLATE1_FILEPATH);
        for (int i = 0; i < templateTotal; i++) {
            doTemplate(fTemplate1);
        }
    }

    private void doTemplate(File f) throws FileNotFoundException, IOException, DocumentException {

        PdfReader reader = new PdfReader(new FileInputStream(f));

        //Transfers data from the template input file to temporary buffer
        PdfStamper stamper = new PdfStamper(reader, templateBuffer);
        stamper.setFormFlattening(true);

        AcroFields form = stamper.getAcroFields();

        //Get size of template file via looking for "end" Acrofield
        float[] area = form.getFieldPositions("end");
        float size = TEMPLATE_SIZE.getHeight() - MARGIN_TOP - area[4];

        //Requires Page Break
        if (size >= PAGE_SIZE.getHeight() - MARGIN_TOP - MARGIN_BOTTOM + currPosition) {

            PdfReader subreader = new PdfReader(workingBuffer.toByteArray());
            PdfStamper substamper = new PdfStamper(subreader, storageBuffer);

            currPosition = 0;
            currPage++;

            substamper.insertPage(currPage, PAGE_SIZE);

            substamper.close();
            subreader.close();

            workingBuffer = storageBuffer;
            storageBuffer = new ByteArrayOutputStream();
        }

        //Set Fields
        form.setField("field1", String.format("Form Text %d", formFillCount));
        form.setField("page", String.format("Page %d", currPage));
        formFillCount++;

        stamper.close();
        reader.close();

        //Read from working buffer, stamp to storage buffer, stamp template from template buffer
        reader = new PdfReader(workingBuffer.toByteArray());
        stamper = new PdfStamper(reader, storageBuffer);
        reader.close();
        reader = new PdfReader(templateBuffer.toByteArray());

        PdfImportedPage page = stamper.getImportedPage(reader, 1);
        PdfContentByte cb = stamper.getOverContent(currPage);
        cb.addTemplate(page, 0, currPosition);

        stamper.close();
        reader.close();

        //Reset buffers - working buffer takes on storage buffer data, storage and template buffers clear
        workingBuffer = storageBuffer;
        storageBuffer = new ByteArrayOutputStream();
        templateBuffer = new ByteArrayOutputStream();

        currPosition -= size;
    }

Running this program with a DEFAULT_NUMBER_OF_TIMES of 23 produces this document and causes the failure when sent to the printer. Changing it to 22 times produces this similar-looking document (simply with one less "line") which does not have the problem and prints successfully. Using a different PDF file as a template component completely changes these numbers or makes it so that it may not happen at all.

While this problem is likely too specific and with too many factors for other people to reasonably be expected to reproduce, the question of possibilities remains. What about the file generation could cause this unusual behavior? What might cause one file to be acceptable to a specific printer but another, generated in the same manner in different only in seemingly non-trivial ways, to be unacceptable? Is there a bug in iText produced by using the stamper template commands too heavily? This has been a long-running bug with us for a while now, so any assistance is appreciate; additionally, I am willing to answer questions or have extended conversations in chat as necessary in an effort to get to the bottom of this.

¿Fue útil?

Solución

The design of your application more or less abuses the otherwise perfectly fine PdfStamper functionality.

Allow me to explain.

The contents of a page can be expressed as a stream object or as an array of a stream objects. When changing a page using PdfStamper, the content of this page is always an array of stream objects, consisting of the original stream object or the original array of stream objects to which extra elements are added.

By adding the same template creating a PdfStamper object over and over again, you increase the number of elements in the page contents array dramatically. You also introduce a huge number of q and Q operators that save and restore the stack. The reason why you have random behavior is clear: the memory and CPU available to process the PDF can vary from one moment to another. One time, there will be sufficient resources to deal with 20 q operators (saves the state), the next time there will only be sufficient resources to deal with 19. The problem occurs when the process runs out of resources.

While the PDFs you're creating aren't illegal according to ISO-32000-1, some PDF processors simply choke on these PDFs. iText is a toolbox that allows you to create PDFs that can make me very happy when I look under the hood, but it also allows you to create horrible PDFs if you don't use the toolbox wisely. The latter is what happened in your case.

You should solve this be reusing the PdfStamper instance instead of creating a new PdfStamper over and over again. If that's not possible, please post another question, using less words, explaining exactly what you want to achieve.

Suppose that you have many different source files with PDF snippets that need to be added to a single page. For instance: suppose that each PDF snippet was a coupon and you need to create a sheet with 30 coupons. Than you'd use a single PdfWriter instance, import pages with getImportedPage() and add them at the correct position using addTemplate().

Of course: I have no idea what your project is about. The idea of coupons of a page was inspired by your test PDF.

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top