Generazione di file PDF utilizzando singoli componenti del modello

2

EDIT: diagramma di flusso aggiornato per spiegare meglio la complessità (probabilmente inutile) di ciò che sto facendo.

Noi della società per cui lavoro provo a creare file PDF complessi utilizzando Java iText (la versione gratuita della linea 2.1). I documenti sono costruiti pezzo per pezzo dai singoli file "modello" , che vengono aggiunti al documento finale uno dopo l'altro utilizzando la classe PdfStamper e riempiti con AcroForms.

Ildesignattualeesegueunciclochevieneeseguitoperognimodellochedeveessereaggiuntoinordine(oltreallalogicapersonalizzatanecessariaperriempireciascuno).Inogniiterazionedelciclo,effettualeseguentioperazioni:

  1. CreaunPdfReaderperaprireilfilemodello
  2. CreaunPdfStampercheleggedaPdfReaderescriveinun"buffer modello"
  3. Compila i campi AcroForm e misura l'altezza del modello ottenendo la posizione di una "fine" AcroField .
  4. Chiude PdfReader e PdfStamper
  5. Crea un PdfReader per leggere un "buffer di lavoro" che memorizza il documento finale corrente in corso
  6. Crea un PdfStamper che legge da PdfReader e scrive in un "buffer di archiviazione"
  7. Chiude PdfReader , apre un nuovo PdfReader al "buffer template"
  8. Importa la pagina dal "buffer modello", la aggiunge al ContentByte di PdfStamper
  9. Chiude PdfReader e PdfStamper
  10. Scambia il "buffer di archiviazione" con il "buffer di lavoro" per essere pronto a ripetere.

Ecco un diagramma che illustra visivamente il processo di cui sopra, che viene eseguito per ogni iterazione del "loop" che esegue ogni modello:

Tuttavia,comerilevatoattraverso questa domanda di overflow dello stack (dove è possibile visualizzare anche il codice di esempio) e la risposta dell'autore iText Bruno Lowagie, questa metodologia di utilizzo di PdfStamper può causare problemi significativi. "Abusare" il PdfStamper creando e chiudendo lo stampatore troppe volte può causare corruzioni nel file risultante che agiscono solo su alcuni programmi, generando un documento apparentemente buono che potrebbe non funzionare in determinati contesti.

Qual è l'alternativa? La risposta di Mr. Lowagie suggerisce che esiste un modo più semplice o più diretto di usare PdfStamper s, anche se non lo capisco ancora. Questo potrebbe essere fatto usando solo un singolo stamper? Potrebbe essere fatto senza utilizzare una serie di buffer rotanti?

    
posta Southpaw Hare 15.05.2014 - 00:08
fonte

2 risposte

0

Un mio collega è stato in grado di capire come rimuovere gli usi estranei di stampatori e buffer e aprirli alla combinazione corretta di luoghi in modo che il documento finale rimanga aperto per l'intero processo e sia chiuso solo una volta, creando così le tabelle sottostanti e tali nel file correttamente come suggeriva il signor Lowagie era il problema.

Ecco una versione riveduta del precedente esempio di test con queste modifiche:

public class PDFFileMakerRevised {

    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 + "coolerdoc_%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 static final int DEFAULT_NUMBER_OF_TIMES = 200;

    private float currPosition;
    private int currPage;

    public static void main (String [] args) {

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

        PDFFileMakerRevised maker = new PDFFileMakerRevised();

        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 numTimes) throws FileNotFoundException, IOException, DocumentException, InterruptedException {

        currPosition = 0;
        currPage = 1;

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

        Document d = new Document(PAGE_SIZE);
        PdfWriter w = PdfWriter.getInstance(d, new FileOutputStream(sFilepath));
        d.open();

        PdfContentByte cb = w.getDirectContent();
        ByteArrayOutputStream stampedBuffer;
        for (int i = 0; i < numTimes; i++) {
            PdfReader templateReader = new PdfReader(new FileInputStream(TEMPLATE1_FILEPATH));

            stampedBuffer = new ByteArrayOutputStream();

            PdfStamper stamper = new PdfStamper(templateReader, stampedBuffer);

            stamper.setFormFlattening(true);
            AcroFields form = stamper.getAcroFields();

            // Get Size
            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) {
                currPosition = 0;
                currPage += 1;
                d.newPage();
            }

            form.setField("field1", String.format("Form Text %d", i+1));
            form.setField("page", String.format("Page %d", currPage));
            stamper.close();
            templateReader.close();
            form = null;

            PdfReader stampedReader = new PdfReader(stampedBuffer.toByteArray());
            PdfImportedPage page = w.getImportedPage(stampedReader, 1);

            cb.addTemplate(page, 0, currPosition);
            currPosition = currPosition - size;

        }
        d.close();
        w.close();

        return new File(sFilepath);
    }
}
    
risposta data 17.05.2014 - 00:01
fonte
2

Sono sempre deluso quando leggo "stiamo usando iText 2.1" perché non è davvero una scelta saggia, come spiegato qui , ma questa è una domanda sul design, quindi ecco un possibile approccio:

CreiunnuovodocumentoDocumentdocument=newDocument();(passaggio1),creiun'istanzaPdfWriter(passaggio2),apriildocumento(passaggio4)eaggiungicontenutoinunciclo(passaggio4):

  1. Haidiversimodelliepermodelliintendiamo:documentiPDFesistenticoncampicompilabili(AcroForms).LiriempiusandoPdfStampereAcroFields(vediiltuocodicesuStackOverflow).Ciòsitraduceinframmentidiformaseparati"appiattiti" conservati in memoria.
  2. Se vuoi mantenere questi snippet insieme, puoi farlo creando un'istanza Document / PdfWriter per creare un nuovo PDF in memoria che combini tutti i frammenti che appartengono insieme. Ottieni uno snippet come questo: PdfImportedPage snippet = writer.getImportedPage(reader, 1); e aggiungi snippet a writer utilizzando il metodo addTemplate() .
  3. Ottenete il risultato combinato utilizzando PdfImportedPage combined = writer.getImportedPage(reader, 1); , il risultato è racchiuso in un'immagine come questa: Image image = Image.getInstance(combined); Si aggiunge l'immagine al documento: document.add(image);

Il passaggio 2 potrebbe essere omesso. Puoi aggiungere i diversi frammenti direttamente al document creato inizialmente. Ripete i passaggi da 1 a 3 tutte le volte che è necessario e chiudete il documento (passaggio 5).

Omettere il passaggio 2 comporterà un conteggio di nidificazione di XObject inferiore, ma mantenere il passaggio 2 non è problematico.

In pseudo codice, avremmo:

[1.] Il ciclo esterno (la parte grande a destra dello schema, contrassegnata PdfWriter )

// step 1
Document document = new Document();
// step 2
PdfWriter writer = PdfWriter.getInstance(document, os);
// step 3
document.open();
// step 4
for (int i = 0; i < parameters.length; i++)
    document.add(getSnippetCombination(writer, parameters[i]));
// step 5
document.close();

[2.] La creazione di un'unità (la freccia segnata PdfWriter nel mezzo)

public Image getSnippetCombination(PdfWriter w, Parameters parameters) {
    // step 1
    Document document = new Document();
    // step 2
    ByteArrayOutputStream baos = new ByteArrayOutputStream();
    PdfWriter writer = PdfWriter.getInstance(document, baos);
    // step 3
    document.open();
    // step 4
    PdfContentByte canvas = writer.getDirectContent();
    for (int i = 0; i < parameters.getNumberOfSnippets(); i++)
        canvas.addTemplate(getSnippet(writer, parameters.getSnippet(i)),
            parameters.getX(i), parameters.getY(i));
    // step 5
    document.close();
    // Convert PDF in memory to From XObject wrapped in Image object
    PdfReader reader = new PdfReader(baos.toByteArray());
    PdfImportedPage page = w.getImportedPage(reader, 1);
    return Image.getInstance(page);
}

[3.] Compilare i dati in snippet separati (le frecce contrassegnate con PdfStamper )

public PdfTemplate getSnippet(PdfWriter w, Snippet snippet) {
    // Using PdfStamper to fill out the fields
    PdfReader reader = new PdfReader(snippet.getBytes());
    ByteArrayOutputStream baos = new ByteArrayOutputStream();
    PdfStamper stamper = new PdfStamper(reader, baos);
    stamper.setFormFlattening(true);
    AcroFields form = stamper.getAcroFields();
    // fill out the fields; you've already implemented this
    stamper.close();
    // return the template
    PdfReader reader = new PdfReader(baos.toByteArray());
    return w.getImportedPage(reader, 1);
}

Potrebbero esserci soluzioni migliori, ad esempio il coinvolgimento di XFA, ma non so se sia fattibile dato che non so se i modelli (la parte blu chiaro nel mio schema) sono sempre gli stessi. Ciò comporterebbe anche la creazione di nuovi modelli nell'architettura XML Forms.

    
risposta data 15.05.2014 - 16:36
fonte

Leggi altre domande sui tag