Créé en 1993, le PDF est devenu le format de fichier incontournable pour son rendu immuable fidèle à ce que l’auteur a prévu, en particulier lors de l’impression.
Sa portabilité (OS, matériel), son standard ouvert et les nombreux logiciels de lecture ont permis une large adoption : documents contractuels, factures, manuels utilisateurs, ebooks, formulaires… À noter que pour un besoin d’archivage, les fichiers PDF doivent respecter la version normalisée PDF/A qui est plus restrictive pour garantir une lecture sans souci, quelque soit la plateforme utilisée pour le créer ou le lire.
Les applications que nous fabriquons exigent fréquemment la production de ce genre de documents où l’on doit mettre en forme plusieurs informations. Cependant, il n’est pas toujours évident de produire un document de qualité.
Dans cet article, nous allons voir différents moyens de générer un document PDF, quels sont les avantages et contraintes de chaque méthode.
iText, pour générer un document « manuellement »
iText est une librairie Java OpenSource (licence AGPL) permettant de créer manuellement un document PDF. Cette méthode se rapproche de la création d’un fichier XML avec DOM : vous créez l’arbre de bout en bout.
Voici la dépendance Maven à rajouter :
<dependency>
<groupId>com.itextpdf</groupId>
<artifactId>itext-core</artifactId>
<version>8.0.2</version>
<type>pom</type>
</dependency>
Et un exemple de code qui l’utilise :
public void generatePDF(String suffix) throws IOException {
try(Document doc = new Document(new PdfDocument(new PdfWriter(this.generateName(suffix))))){
doc.add(new Paragraph("Génération de PDF avec IText"));
Movie m = Movie.goldfinger();
Text title = new Text(String.format("%s (%d)",m.title(), m.year()));
title.setBold();
title.setFontSize(20);
Div d = new Div();
d.add(new Paragraph(title));
d.add(new Paragraph(String.format("Réalisateur : %s", m.director())));
d.add(new Paragraph("Acteurs : "));
List l = new List(ListNumberingType.ROMAN_UPPER);
l.setListSymbol("\u2022");
for(String a : m.actors()){
l.add(a);
}
d.add(l);
URL u = new URL("https://avatars.githubusercontent.com/u/1869588?v=4");
Image img = new Image(ImageDataFactory.create(u));
img.setWidth(200);
img.setHeight(200);
img.setFixedPosition(350,600);
d.add(img);
doc.add(d);
}
}
}
La syntaxe est assez verbeuse et il faut naviguer dans la documentation pour comprendre l’ensemble des termes et options. Cependant, la page github fournit de nombreux exemples et la librairie reste assez explicite.
Voici le résultat :

L’avantage d’une librairie Java est la possibilité d’intégrer des tests unitaires de génération dans votre développement. Vous bénéficiez également du compilateur pour garantir du bon usage de la librairie et des données.
Du HTML au PDF avec un moteur de template
iText est une solution qui fonctionne mais il est difficile de créer un document PDF avec une mise en page complexe. Une solution pour laquelle un rendu serait disponible immédiatement permettrait des itérations plus courtes pour se concentrer sur le rendu final.
L’utilisation d’un moteur de template basée sur du HTML est une approche intéressante pour un développeur fullstack que le front n’effraiera pas. Mais attention, il n’est pas question d’utiliser du React, de l’Angular ou tout autre framework front en JS. Ici, la page devra être générée côté serveur (comme au temps des JSP). Cela implique également que pour injecter des graphiques (Camembert, à barre…) il faudra générer ces graphiques côté serveur, par exemple avec JFreeChart.
Quelque soit le moteur de template, l’idée sera la même :
- Créer un template
- Générer une page HTML à partir du template et des données
- Générer un PDF à partir du HTML.
Thymleaf
Voici un exemple de templating avec Thymleaf. Les variables ne sont pas définies directement dans le contenu des balises mais par le biais d’attributs dédiés dans chaque balise HTML, par exemple th:text pour définir une valeur.
Lorsque le fichier HTML sera généré, le contenu des balises HTML sera remplacé par la valeur de th:text.
<html>
<head>
<meta charset="UTF-8"/>
</head>
<body>
<h1>Génération de PDF avec Thymleaf</h1>
<h2><span th:text="${movie.title}">Titre du film</span> (<span th:text="${movie.year}">Année</span>)</h2>
<div>
Réalisateur : <span th:text="${movie.director}" style="font-weight:bold">Réalisateur</span>
</div>
<div>
Acteurs
<ul th:each="actor: ${movie.actors}">
<li th:text="${actor}">Acteur</li>
</ul>
</div>
</body>
</html>
Nous pouvons définir des valeurs par défaut pour chaque élément et ainsi avoir un rendu immédiat dans un navigateur, sans lancer la génération du document final. Il est également possible d’utiliser des boucles, des conditions…
En ouvrant le fichier directement, j’obtiens :

Voici la dépendance Maven et le code pour générer un fichier HTML à partir du template et des données :
<dependency>
<groupId>org.thymeleaf</groupId>
<artifactId>thymeleaf</artifactId>
<version>3.1.2.RELEASE</version>
</dependency>
public String generate() {
ClassLoaderTemplateResolver resolver = new ClassLoaderTemplateResolver();
resolver.setTemplateMode(TemplateMode.HTML);
TemplateEngine engine = new TemplateEngine();
engine.setTemplateResolver(resolver);
Context context = new Context();
context.setVariable("movie",Movie.goldfinger());
return engine.process("test_file.html",context);
}
Freemarker
A la différence de Thymleaf, avec Freemarker, les expressions sont affichées directement dans les balises HTML et une syntaxe particulière est utilisée pour gérer des instructions comme les boucles et les conditions… Le template est moins lisible dans un navigateur mais nous verrons plus tard que l’on peut tirer partie de cette différence.
Voici le même exemple, mais écrit avec Freemarker :
<html>
<head>
<meta charset="UTF-8"/>
</head>
<body>
<h1>Génération de PDF avec Freemarker</h1>
<h2>${movie.title()} (${movie.year()})</h2>
<div>
Réalisateur : <span style="font-weight:bold">${movie.director()}</span>
</div>
<div>
Acteurs :
<ul>
<#list movie.actors() as actor><li>${actor}</li></#list>
</ul>
</div>
</body>
</html>
Freemarker utilise des directives spécifiques de la forme <#...></#...>. Vous pouvez les retrouver dans leur documentation.
Plusieurs fonctionnalités sont très intéressantes :
- La directive
includepermet de découper son template en plusieurs fichiers afin de simplifier l’écriture ; - Les built-in functions permettent de manipuler simplement et rapidement de nombreux types comme les nombres, les dates, les chaînes de caractères…
- Des mécanismes pour afficher des valeurs par défaut quand un objet ou sous objet est nul.
Pour transformer ce template en HTML, il faut utiliser la librairie suivante :
<dependency>
<groupId>org.freemarker</groupId>
<artifactId>freemarker</artifactId>
<version>2.3.32</version>
</dependency>
Et voici le code correspondant :
Configuration cfg = new Configuration(Configuration.VERSION_2_3_32);
cfg.setDirectoryForTemplateLoading(new File(getClass().getResource("/freemarker").getFile()));
cfg.setDefaultEncoding("UTF-8");
Template template = cfg.getTemplate("test_file.ftlh");
Map<String, Object> data = Map.of("movie", Movie.goldfinger());
StringWriter out = new StringWriter();
template.process(data, out);
return out.toString();
Markdown
Le Markdown est un langage simple souvent utilisé pour de la documentation, du wiki… Il ne gère pas le templating mais permet de créer rapidement un document avec une mise en page simple.
Un exemple de fichier en Markdown.
# Voici un titre ## Un sous titre On affiche du texte simple en **gras** * Une liste * avec * plusieurs elements
Voici le rendu

Il est possible, à l’aide de la librairie CommonMark de transformer le rendu du Markdown en HTML. Voici la dépendance Maven et le code qui le permet :
<dependency>
<groupId>org.commonmark</groupId>
<artifactId>commonmark</artifactId>
<version>0.22.0</version>
</dependency>
String content = new String(getClass().getResourceAsStream("/test_file.md").readAllBytes());
return "<html><body>" + HtmlRenderer.builder().build().render(p.parse(content))+ "</body></html>";
Markdown + Freemarker
Cependant, l’impossibilité native d’utiliser des variables et des mécanismes logiques ne permettra pas de générer un document complexe. Une alternative intéressante est d’utiliser du templating Freemarker dans un document écrit en Markdown. Vous aurez ainsi la simplicité de la syntaxe Markdown avec la puissance de templating de Freemarker.
Voici un exemple où du Freemarker a été intégrer dans un fichier Markdown :
# ${movie.title()} (${movie.year()})
## Réalisateur : ${movie.director()}
Acteurs
<#list movie.actors() as actor>
* ${actor}
</#list>

Pour transformer ce fichier en HTML, il faudra d’abord transformer le fichier avec Freemarker, pour interpréter les variables et autres directives, puis transformer le Markdown produit en HTML.
Le format Markdown est clairement moins efficace que le HTML pour obtenir une belle mise en forme, mais ce n’est pas son rôle qui est de proposer une syntaxe simple pour un rendu qui l’est tout autant,
Générer un fichier PDF à partir d’un document HTML
Dans les exemples précédents, nous avons vu différentes façons de générer un document HTML à partir de nos données. Il nous reste une dernière étape pour générer notre fichier PDF : grâce à l’utilisation de la librairie, Flying Saucer (qui utilise iText), nous allons convertir l’HTML généré en un fichier PDF.
Voici la dépendance Maven et le code qui le permet :
<dependency>
<groupId>org.xhtmlrenderer</groupId>
<artifactId>flying-saucer-pdf</artifactId>
<version>9.7.0</version>
</dependency>
// Create the pdf document
private static void generatePDF(String content)throws IOException{
FileOutputStream file = new FileOutputStream("test.pdf");
ITextRenderer renderer = new ITextRenderer();
renderer.setDocumentFromString(content);
renderer.layout();
renderer.createPDF(file);
file.close();
}
Le rendu final où les bonnes valeurs des variables ont été injectées :

Le cas particulier de Freemarker + Markdown aura un rendu particulier, notamment car la mise en forme du Markdown est plus limitée :

JasperReports ?
Les vieux développeurs connaissent cette solution qui se basait sur une librairie qui générait des PDF à partir de templates créés avec iReport. Cette solution a changé de nom et s’appelle désormais Jaspersoft Studio et inclut à la fois un IDE pour créer des templates et une librairie pour la génération.

L’IDE va vous pour permettre de créer graphiquement des templates très complexe grâce à de nombreuses fonctionnalités :
- Configuration de datasources (base de données, fichier CSV, XML, JSON, Excel, objet Java…) ;
- Génération de graphiques complexes comme des camemberts, barres, Spider…
- Utilisation d’éléments graphiques ;
- Utilisation de code Java dans le template, par exemple pour transformer des données, afficher la date…
- Aperçu en temps réel du résultat (après l’ajout de la WebView Edge).
L’outil est complexe à prendre en main, nécessite une compréhension du fonctionnement des différentes parties et des nombreuses options permettant une personnalisation très poussée. La documentation est bien faite avec de nombreuses captures.
Voici un exemple de template, avec des données statiques définies dans un jar et leur utilisation dans une boucle :

L’onglet « Preview » permet d’observer le résultat :

À partir du template produit, au format XML, vous pouvez générer des documents PDF en utilisant la librairie Jasper Java et en fournissant les éléments de la datasource (accès à la base de données, données au format JSON, objets Java…).
Attention, la montée en compétence en assez lente et l’usage d’un tel outil doit être réservé uniquement si vous avez plusieurs rapports complexes à produire, avec par exemple des graphiques de données, afin d’amortir le temps de prise en main.
Jaspersoft peut potentiellement être utilisé par des non développeurs pour créer des templates métiers, il faut cependant garder à l’esprit que la partie relative à l’alimentation des données n’est pas forcément simple et une formation sera nécessaire.
Birt ?
Birt est une solution opensource basée sur Eclipse analogue à Jaspersoft. Sa première version date de 2005 et le produit est toujours maintenu. Il se présente comme un plugin à Eclipse qu’il faut donc installer avant. Une fois le plugin installé, vous créez un nouveau projet, ajoutez des datasources et créez un template dans lequel des graphiques peuvent être générés à partir de vos données.
L’outil demande un temps d’adaptation mais son utilisation se rapproche beaucoup de Jasper et un utilisateur ne sera pas très perdu.
Quelle solution choisir ?
Une bonne réponse dans l’informatique est souvent « cela dépend » (à défaut de 42) et je ne vais pas couper à la règle.
Pour un petit besoin avec une mise en forme très basique, iText est une bonne solution : il est rapide, on peut faire un test JUnit facilement et le compilateur va nous protéger d’un mauvais usage d’un objet.
Pour des besoins qui ne nécessitent aucune mise en forme avancée et de positionnement des éléments précis, Markdown + Freemarker est une solution assez simple et élégante.
Pour la production d’un vrai document avec plusieurs parties, de la mise en forme, des couleurs, des images… du templating comme Thymleaf et Freemarker est très bien adapté, en particulier si vous êtes à l’aise en HTML et CSS. Vous choisirez le moteur de template en fonction de votre expérience.
Dernière solution, si vous souhaitez proposer dans votre PDF des graphiques avancés ou proposer à des fonctionnels d’écrire leur propre template, Jaspersoft ou Brit peuvent être des solutions intéressantes. Attention toutefois à la complexité de ces produits qui nécessitera une vraie formation et un accompagnement des utilisateurs.
Une dernière solution dont je n’ai pas parlé mais qu’il faut garder en mémoire : la possibilité d’imprimer directement une page en PDF, par exemple avec un CSS adapté…
Vous pouvez retrouver l’ensemble du code utilisé sur mon compte github.