2014-06-25 2 views
4

Je lis les données de la base de données à partir de laquelle je génère HTML DOM. Le volume de données est énorme donc il ne peut pas entrer dans la mémoire à la fois, mais il peut être fourni morceau par morceau.Générer un gros PDF à partir d'une énorme quantité de données

Je voudrais transformer en résultant HTML PDF en utilisant Flying Saucer:

import org.xhtmlrenderer.pdf.ITextRenderer; 
import org.dom4j.DocumentFactory; 
import org.dom4j.Element; 
import org.dom4j.io.DOMWriter; 

OutputStream bodyStream = outputMessage.getBody(); 

ITextRenderer renderer = new ITextRenderer(); 

DocumentFactory documentFactory = DocumentFactory.getInstance(); 
DOMWriter domWriter = new DOMWriter(); 

Element htmlNode = documentFactory.createElement("html"); 
Document htmlDocument = documentFactory.createDocument(htmlNode); 

int currentLine = 1; 
int currentPage = 1; 

try { 
    while (currentLine <= numberOfLines) { 
     currentLine += loadDataToDOM(documentFactory, htmlNode, currentLine, CHUNK_SIZE); 

     renderer.setDocument(domWriter.write(htmlDocument), null); 
     renderer.layout(); 

     if (currentPage == 1) { 
      // For the first page the PDF writer is created: 
      renderer.createPDF(bodyStream, false); 
     } 
     else { 
      // Other documents are appended to current PDF writer: 
      renderer.writeNextDocument(currentPage); 
     } 

     currentPage += renderer.getRootBox().getLayer().getPages().size(); 
    } 

    // Finalise the PDF: 
    renderer.finishPDF(); 
} 
catch (DocumentException e) { 
    throw new IOException(e); 
} 
catch (org.dom4j.DocumentException e) { 
    throw new IOException(e); 
} 
finally { 
    IOUtils.closeQuietly(bodyStream); 
} 

Le problème avec cette approche est que la dernière page du morceau est pas nécessairement complètement rempli de données. Y a-t-il une solution pour remplir l'espace? Par exemple je pourrais penser à l'approche qui vérifiera que la dernière page n'est pas complètement classée et puis la jeter (pas écrire au pdf), découvrir également quelles données ont été rendues sur cette page et rembobiner la position dans la base de données (exemple) . Ce serait bien si l'on peut poster une solution complète.

+2

Mauvaise idée. D'abord vous créez le HTML qui prend beaucoup d'espace, puis vous utilisez ce HTML pour créer des PDF. Si la mémoire compte, vous devez créer le PDF directement à partir des données sans d'abord créer le code HTML. –

+0

Oui, mais combien de code devrais-je écrire pour rendre le HTML en utilisant les primitives de bas niveau iText ('moveTo()', 'lineTo()', 'beginText()')? Maintenant, j'ai 50 lignes de code, facile à gérer. HTML et CSS sont familiers à tout le monde. Changer la disposition ou les couleurs n'est pas un problème. Bruno, j'ai regardé brièvement ton livre "iText en action" (merci beaucoup!) Et déjà la magie des en-têtes/pieds de page à la page 430 (chapitre 14) fait peur. J'utiliserais volontiers 'com.itextpdf.tool.xml.pipeline.html.HtmlPipeline' mais il ne supporte pas les sélecteurs CSS de base, ne parle pas de boîtes flottantes. –

+0

Pourquoi utiliseriez-vous des primitives de bas niveau? Je vais vous donner quelques indications sur des exemples faciles dans une réponse. –

Répondre

5

Comme je l'ai déjà mentionné dans les commentaires, vous gaspillez de la mémoire et du temps de traitement en créant un PDF à partir d'une source de données en créant HTML d'abord, puis en convertissant le HTML en PDF. Vous introduisez également beaucoup de complexité inutile.

Dans votre commentaire, vous mentionnez des fonctionnalités de bas niveau telles que moveTo() et lineTo(). Il serait en effet fou de dessiner une table en utilisant des opérations de bas niveau qui dessinent chaque ligne et chaque mot. La classe PdfPTable doit être utilisée. L'exemple ArrayToTable est un POC très simple où les données se présentent sous la forme d'un List<List<String>>. Le code est aussi simple que cela:

PdfPTable table = new PdfPTable(8); 
table.setWidthPercentage(100); 
List<List<String>> dataset = getData(); 
for (List<String> record : dataset) { 
    for (String field : record) { 
     table.addCell(field); 
    } 
} 
document.add(table); 

Bien sûr: vous parlez d'un vaste ensemble de données, auquel cas, vous voudrez peut-être de ne pas construire la table en mémoire d'abord, puis rincer la mémoire lorsque le la table est ajoutée au document. Vous voudrez ajouter de petites parties de la table pendant que vous le construisez. C'est ce qui se passe dans l'exemple MemoryTests. Ajoutez cette ligne:

table.setComplete(false); 

Et vous pouvez ajouter la table petit à petit (dans l'exemple: toutes les 10 lignes). Lorsque vous avez terminé d'ajouter des cellules à la table, vous devez le faire:

table.setComplete(true); 
document.add(table); 

Ceci ajoutera les lignes finales.

Si vous voulez une table avec un en-tête répéter et/ou pied de page, un regard sur les tableaux de ce PDF: header_footer_1.pdf

Les HeaderFooter1 et HeaderFooter2 exemples vous montrer comment il est fait.

+0

Merci pour la réponse détaillée, j'apprécie. En principe, j'ai "déposé des données" (voici [un exemple avec chaque ell ayant une bordure] (https://www.dropbox.com/s/9wc0bvrc0vcj7d8/flying-saucer-dotted.pdf) alors que voici un [non -Draft version] (https://www.dropbox.com/s/3wizcp7wk1c5yc8/flying-saucer.pdf)). Chaque cellule peut à son tour contenir d'autres zones de texte avec un arrière-plan. Si je comprends bien, je dois représenter chaque pièce avec l'objet 'com.itextpdf.text.Chunk' et les combiner dans' com.itextpdf.text.Phrase'? –

+1

Les arrière-plans colorés pour les parties arbitraires du texte sont en effet quelque chose que vous pouvez réaliser avec'Chunk.setBackground() 'ou avec la fonctionnalité * tag générique * (par exemple: si l'arrière-plan n'est pas un rectangle). En regardant la sortie désirée, je n'utiliserais pas 'PdfPTable'. Au lieu de cela, j'utiliserais un objet 'ColumnText' et' Chunk.TABBING' pour les onglets séparant les numéros '' et les données réelles. –

3

Ceci n'est pas une réponse à la question précise que vous avez posée, donc si ce post est inutile je vais le supprimer.

Étant donné que le document est énorme, vous pouvez obtenir les meilleurs résultats en émettant les données en tant que LaTeX et en l'exécutant par pdflatex.

Avantages:

  • source LaTeX du genre dont vous avez besoin est simple à émettre - pas plus compliqué que HTML.
  • L'ensemble du système TeX est conçu pour produire de très beaux documents.LaTeX est traitée comme un flux de pages. Le nombre de pages n'a pratiquement aucun effet sur les ressources RAM requises.
  • Vous obtenez la pleine puissance d'un langage de composition pour que vos pages soient belles. Vous voulez des en-têtes de fantaisie? Numéros de page bien positionnés? Rubriques de section? Table des matières cliquable, etc. etc. Pas de problème.
  • LaTeX est disponible gratuitement pour tous les principaux systèmes d'exploitation.

Inconvénients:

  • LATEX est un exécutable natif, pas un lib Java.

Si vous êtes intéressé par cela, je peux étoffer plus de détails.

+0

Je suis au courant de LaTeX. Il y a deux autres inconvénients: (1) le temps de traitement. L'appel de l'utilitaire externe est coûteux en temps. Plus de LaTeX a un gros éco-système, qui prend du temps à charger. (2) L'ajout d'une autre technologie au projet rend le maintien plus difficile. Le HTML est plus ou moins familier à tout le monde. Mais des instructions comme '\ rfoot {Page \ thepage}' ont besoin d'efforts pour explorer. Je suppose que '\ textbf {\ thepage}' fonctionnera bien dans la définition d'en-tête/pied de page, mais un style plus exotique comme la création d'une boîte colorée dépasse déjà ma compréhension de ce qui est "simple". –

Questions connexes