Looking for Computer Science  & Information Technology online courses ?
Check my new web site: https://www.yesik.it !

Pour l'utilisateur final, ed n'est pas ce qui se fait de plus convivial comme éditeur de texte. Par contre, pour le programmeur, il ouvre la possibilité d'écrire des scripts pour automatiser les tâches d'éditions.

Si vous êtes des habitués du monde Unix, vous connaissez sans doute ed ou sa version dérivée sed. Ces deux commandes sont des éditeurs de texte – même s'ils n'ont en apparence pas grand chose à voir avec les éditeurs modernes dotés d'interfaces graphiques sophistiquées. D'ailleurs, sed n'est même pas interactif! Et pourtant, ce sont des outils qui restent pratiques puisqu'ils permettent d'utiliser des scripts pour réaliser des tâches d'édition sans intervention humaine. Indispensable si vous devez éditer plusieurs dizaines ou plusieurs centaines de fichiers. Ou quand un document doit être généré à la volée.

Pourquoi parler ici de ces deux commandes? Parce que, d'une certaine manière, iText est au format PDF, ce que sed ou ed sont au format texte: un couteau suisse qui permet d'effectuer par programme toutes les manipulations dont vous pourriez avoir besoin. C'est ce que nous allons découvrir ici en produisant notre premier document PDF avec iText.

Avant de commencer...

2 ou 5?

La toute dernière version de iText est la version 5. Mais on trouve aussi couramment la version 2. Or, il y a eu un certain nombre de changements incompatibles lors du passage de la version 2 à la version 5. Les plus notables sont;

Pour cet article, je vais rester avec la version traditionnelle (la 2). En effet, elle est la plus répandue actuellement. Et surtout, à cause du changement de licence, elle risque de continuer à être distribuée avec pas mal de produits pendant un bon moment.

Installation

Pour utiliser iText, vous aurez besoin de télécharger le JAR (Java ARchive – Un format d'archive utilisé dans le monde Java pour regrouper des fichiers (classes compilées, méta-données, etc.).) correspondant à partir du site officiel http://itextpdf.com. Pour la rédaction de cet article, j'ai téléchargé le fichier iText-2.1.7.jar.

Il n'y a pas d'installation à proprement parler: pour utiliser iText, il suffit de mettre le JAR correspondant dans votre CLASSPATH. Comme nous le verrons dans quelques minutes au moment de compiler et exécuter un premier programme.

Hello iText

Sautons sans attendre dans le vif du sujet:

import java.io.FileOutputStream;
 
import com.lowagie.text.Document;
import com.lowagie.text.Paragraph;
import com.lowagie.text.pdf.PdfWriter;
 
import com.lowagie.text.DocumentException;
 
public class HelloiText {
    public void run() {
	/* Create a new Document object */
	Document document = new Document();
	try {
	    /* Associate the document with a PDF writer and an output stream */
	    PdfWriter.getInstance(document, new FileOutputStream("HelloiText.pdf"));
 
	    /* Open the document (ready to add items) */
	    document.open();
 
	    /* Populate the document (add items to it) */ 
	    populate(document);
	}
	catch(Exception e) {
	    /* Oups */
	    System.err.println(e);
	}
	finally {
	    /* Don't forget to close the document! */
	    document.close();
	}
    }
 
    /** 
	Populate the document by adding some elements
    */
    public void populate(Document document) throws DocumentException {
	/* add a news paragraph */
	document.add(new Paragraph("Hello iText "));
    }
 
    /**
	Main program.
 
	Build and run an instance of HelloiText.
    */
    public static void main(String[] args) {
	HelloiText  app = new HelloiText();
	app.run();
    }
}

Effectivement ce code peut sembler long. Mais vous remarquerez que c'est surtout parce que j'ai pris la peine de mettre de nombreux commentaires! Et au final ce programme ne devrait pas poser de problème de compréhension majeure pour peu que vous ayez déjà programmé en Java. Comme vous le constatez, iText repose autour du patron de conception monteur (builder pattern).

Ce modèle permet de construire le document PDF au fur et à mesure de l'ajout des éléments. Ceci s'oppose à un modèle objet du document, dans lequel il serait nécessaire de construire tout le document avant de produire le PDF. L'avantage étant évidemment qu'ici, iText permet de construire des documents même très conséquents sans épuiser la mémoire disponible!

C'est à cause de ce choix de conception qu'il est nécessaire d'associer un writer au document avant de commencer à lui ajouter des éléments. C'est le rôle de la ligne suivante:

PdfWriter.getInstance(document, new FileOutputStream("HelloiText.pdf"));

Remarque:

Ce modèle de conception a aussi l'avantage de permettre de fabriquer d'autre types de document simplement en choisissant un autre builder. D'ailleurs, iText supporte la génération de documents sous d'autres formats comme RTF ou HTML. Mais l'ensemble des possibilités d'iText ne s'exprime réellement qu'avec le format PDF.

Vérifions le fonctionnement de ce programme:

sh$ javac HelloiText.java -cp /path/to/iText-2.1.7.jar
sh$ java -cp /path/to/iText-2.1.7.jar:. HelloiText
qh$ ls *.pdf
HelloiText.pdf

Comme vous le constatez, le programme a produit un nouveau document PDF HelloiText.pdf. Que vous pouvez ouvrir avec votre visualisateur préféré.

Le document produit. Celui-ci comporte 1 page, en haut et à gauche de laquelle s'affiche notre paragraphe.

Paragraphe, Phrase et Chunk

Dans notre premier exemple, nous avons créé un document contenant juste deux mots. Un peu court pour se faire une idée du fonctionnement d'iText. Modifions donc le code pour travailler sur un extrait un peu plus long. Au passage, notez aussi que j'en profite pour changer la police de caractère utilisée en associant une instance de la classe com.lowagie.text.Font à chaque paragraphe:

/* ... */
    public void populate(Document document) throws DocumentException {
        /* add a news paragraph */
        String extractsFromDavidCopperfield[] = {
            /* 1 */
            "Whether I shall turn out to be the hero of my own life, or whether that"
            + " station will be held by anybody else, these pages must show. To begin my"
            + " life with the beginning of my life, I record that I was born (as I have"
            + " been informed and believe) on a Friday, at twelve o'clock at night."
            + " It was remarked that the clock began to strike, and I began to cry,"
            + " simultaneously.",
            /* 2 */
            "In consideration of the day and hour of my birth, it was declared by"
            + " the nurse, and by some sage women in the neighbourhood who had taken a"
            + " lively interest in me several months before there was any possibility"
            + " of our becoming personally acquainted, first, that I was destined to be"
            + " unlucky in life; and secondly, that I was privileged to see ghosts and"
            + " spirits; both these gifts inevitably attaching, as they believed, to"
            + " all unlucky infants of either gender, born towards the small hours on a"
            + " Friday night."
        };
 
        document.add(new Paragraph(excerptsFromDavidCopperfield[0], new Font(Font.TIMES_ROMAN)));
        document.add(new Paragraph(excerptsFromDavidCopperfield[1], new Font(Font.HELVETICA,16)));
    }
/* ... */
L'unité élémentaire de texte dans iText est le fragment (chunkcom.lowagie.text.Chunk). Ceux-ci peuvent être combinés pour former des phrases (com.lowagie.text.Phrase) ou des paragraphes (com.lowagie.text.Paragraph). La différence majeure étant que lors du rendu, un retour à la ligne est automatiquement ajouté à la fin de ce dernier.

Comme vous le voyez sur la copie d'écran ci-dessus, iText s'occupe du formatage du texte en générant les retours à la ligne nécessaires et en appliquant la police désignée à l'ensemble du paragraphe. Mais si je ne voulais mettre en gras ou en italique qu'un mot du texte? Comment faire?

La solution consiste à combiner plusieurs fragments – dotés chacun de son propre style – dans un seul paragraphe. Accessoirement, il est aussi possible de grouper des fragments dans une phrase si le retour à la ligne terminal n'est pas souhaité.

Phrase

Le terme de phrase est un peu trompeur. Il ne s'agit bien que d'une collection de fragments. Et cela n'a rien à voir du tout avec la notion de phrase grammaticale!

Dans l'exemple suivant, je vais donc créer plusieurs fragments de texte (dont un qui servira de lien hypertexte), et leur associer diverses variations de style (police, taille, couleur):

/* ... */
    public void populate(Document document) throws DocumentException {
        Paragraph paragraph = new Paragraph();
 
        paragraph.add(new Chunk("I was born at "));
 
        Font blueFont = new Font();
        blueFont.setColor(0, 0, 0xFF);
        paragraph.add(new Chunk("Blunderstone", blueFont).setAnchor("http://en.wikipedia.org/wiki/Blunderstone"));
 
        paragraph.add(new Chunk(", in Suffolk, or "));
        paragraph.add(new Chunk("there by", new Font(Font.HELVETICA, 14, Font.ITALIC)));
        paragraph.add(new Chunk(", as they say in "));
        paragraph.add(new Chunk("Scotland", new Font(Font.HELVETICA, Font.DEFAULTSIZE, Font.BOLD)));
        paragraph.add(new Chunk(
             ". I was a posthumous child. My father's eyes had closed upon"+
            "the light of this world six months, when mine opened on it. There is"+
            "something strange to me, even now, in the reflection that he never saw"+
            "me; and something stranger yet in the shadowy remembrance that I have"+
            "of my first childish associations with his white grave-stone in the"+
            "churchyard, and of the indefinable compassion I used to feel for it"+
            "lying out alone there in the dark night, when our little parlour"+
            "was warm and bright with fire and candle, and the doors of our house"+
            "were--almost cruelly, it seemed to me sometimes--bolted and locked"+
            "against it."
        ));
 
        document.add(paragraph);
    }

Plus?

Le projet Gutenberg (http://www.gutenberg.org) met en ligne des textes littéraires tombés dans le domaine public. Ceux-ci sont disponibles sous divers formats dont texte seul. D'ailleurs c'est de là que provient le texte de David Copperfield qui m'a servi de support jusqu'à présent.

Afin d'illustrer ce qu'il est déjà possible de faire avec le peu que nous avons vu d'iText, voici comment modifier notre programme pour générer un PDF avec un minimum de mise en page à partir du texte d'un ouvrage:

/* ... */
    public void populate(Document document) throws DocumentException {
        Font    headingFont = new Font(Font.HELVETICA, 18, Font.BOLD);
        Font    textFont = new Font(Font.TIMES_ROMAN);
 
        try {
            BufferedReader reader = null;
            try {
                reader = new BufferedReader(new FileReader("DavidCopperfield.txt"));
 
                Pattern titlePattern = Pattern.compile("^(CHAPTER|PREFACE|CONTENTS).*");
 
                Paragraph paragraph = null;
                String line;
                while((line = reader.readLine()) != null) {
                    Boolean isTitle = titlePattern.matcher(line).matches();
 
                    /* flush paragraph on empty lines and titles */
                    if ((line.isEmpty() || isTitle) && (paragraph != null)) {
                        document.add(paragraph);
                        paragraph = null;
                    }
 
                    if (!line.isEmpty()) {
                        if (paragraph == null) {
                            paragraph = new Paragraph();
                            paragraph.setSpacingAfter(8);
                            paragraph.setSpacingBefore(8);
                        }
                        paragraph.add(new Chunk(line+" ", isTitle ? headingFont : textFont));
                    }
 
                    /* New titles are immediately flushed */
                    if (isTitle) {
                        document.add(paragraph);
                        paragraph = null;
                    }
                }
                /* Flush paragraph at the end of file */
                if (paragraph != null)
                    document.add(paragraph);
            }
            finally {
                if (reader != null)
                    reader.close();
            }
        }
        catch(Exception e) {
            /* Wrap any exception in a DocumentException */
            throw new DocumentException(e);
        }
    }

D'accord, c'est vraiment un minimum. Mais au moins, cela prouve que rien qu'avec les quelques possibilités d'iText présentées ici – associées à la puissance de Java – vous pouvez déjà automatiser la création de documents de qualité comparable à ceux que vous auriez pu obtenir en utilisant les fonctionnalités de base d'un traitement de texte. Et le tout, sans nécessiter d'intervention humaine. Bien sûr, un certain nombre de possibilités vont vite vous manquer: création de tableaux, de listes, ou insertion d'images, par exemple. Mais ce sera l'occasion d'un autre article ... ou d'une recherche sur Internet!