Poppler : afficher des fichiers PDF avec Qt

| David Boddie | traducteur : Maxime Spriet | Qt Quarterly | gbdivers |

Qt propose depuis longtemps la possibilité d'afficher des documents contenant du texte avec mise en forme, par exemple un document HTML. Mais les utilisateurs peuvent souhaiter d'afficher également du texte mis en forme provenant de type de document non pris en charge par défaut par Qt. Dans cet article, l'auteur présente comment intégrer la bibliothèque Poppler pour afficher des documents PDF dans des fenêtres Qt, faire des recherches dans le texte et extraire du texte.

Cet article est une traduction autorisée de Poppler: Displaying PDF Files with Qt, par David Boddie.

N'hésitez pas à commenter cet article !
Commentez

- Titre : Poppler : afficher des fichiers PDF avec Qt

- Auteurs : David Boddie, traducteur : Maxime Spriet, Qt Quarterly, gbdivers

- Parution : 15 septembre 2010

- Mise à jour : 1 août 2011

- Licence : Copyright ® 2010 David Boddie. Aucune reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents, images, etc. sans l'autorisation expresse de l'auteur. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts.

Sommaire

- I. L'article original

- II. Introduction

- III. Préparation de l'utilisation de Poppler

- IV. Affichage de documents

- V. Recherche de texte

- VI. Extraction de texte

- VII. Exemple complet

- VIII. Compilation de l'exemple

- IX. Autres fonctionnalités et améliorations possibles

- X. Pour en savoir plus

- XI. Divers

I - L'article original

Qt Quarterly est une revue trimestrielle électronique proposée par Nokia à destination des développeurs et utilisateurs de Qt. Vous pouvez trouver les différentes éditions en versions originales.

Nokia, Qt, Qt Quarterly et leurs logos sont des marques déposées de Nokia Corporation en Finlande et/ou dans les autres pays. Les autres marques déposées sont détenues par leurs propriétaires respectifs.

Cet article est la traduction de l'article Poppler: Displaying PDF Files with Qt, par David Boddie paru dans la Qt Quarterly Issue 27.

Cet article est une traduction de l'un des tutoriels en anglais écrits par Nokia Corporation and/or its subsidiary(-ies), inclus dans la documentation de Qt. Les éventuels problèmes résultant d'une mauvaise traduction ne sont pas imputables à Nokia.

II - Introduction

Comme nous venons de le voir dans cette revue, Qt peut être utilisé pour générer des documents en offrant un panel grandissant de formats qui peuvent être lus et modifiés par des applications externes. Qt permet également d'afficher facilement du HTML « out of the box » et peut générer ses propres aperçus avant impression. Cependant, on pourrait se demander comment afficher des fichiers qui ne proviennent pas d'une application Qt.

Heureusement, il existe des librairies tierces pouvant répondre à certains besoins que Qt ne remplit pas. L'une d'elles se nomme Poppler, une librairie de rendu de fichiers PDF (Portable Document Format) qui apporte les bases des applications de rendu de fichiers PDF les plus utilisées. Poppler est un projet dérivant du lecteur de PDF Xpdf disponible sous la licence publique générale GNU. Xpdf peut toutefois s'obtenir sous d'autres licences.

Poppler est conçu pour une utilisation avec n'importe quelle boîte à outils ou framework, pourvu que le backend de rendu soit compatible. Les développeurs d'applications Qt ont une chance inouïe : il existe un frontend viable pour Qt, avec un ensemble de classes de style Qt qui permettent de décrire les parties d'un document PDF.

Poppler

Dans cet article, nous allons introduire quelques fonctionnalités offertes par Poppler dans le but de créer une simple application de rendu PDF.

III - Préparation de l'utilisation de Poppler

Pour les développeurs utilisant Linux, Poppler et le frontend de Qt4 devraient être disponibles sous forme de pakage sur la plupart des distributions récentes. Sous Windows, Mac OS X et d'autres plateformes Unix, le code source peut se télécharger sur le site poppler.freedesktop.org.

Par défaut, Poppler est compilé avec une série de frontends et backends. Si vous compilez Poppler depuis les sources, vous pouvez en enlever certains pour gagner du temps à la compilation. Lors de la configuration de la compilation, il pourrait être plus facile de définir le préfixe d'installation à la valeur utilisée lors de l'installation de Qt. Ce préfixe correspond au répertoire qui contient des sous-répertoires dans lesquels des exécutables, des librairies et des fichiers sources sont disponibles.

Il est important de savoir où la librairie Poppler et les en-têtes seront installés car nous en aurons besoin pour notre exemple.

IV - Affichage de documents

Dans notre exemple, nous allons développer une interface utilisateur simple pour afficher des fichiers PDF en ouvrant une seule page à la fois et en ajoutant des contrôles pour permettre à l'utilisateur de parcourir les pages. Chaque page est affichée dans une fenêtre personnalisée nommée DocumentWidget, contenue dans la partie centrale de la fenêtre principale, une QScrollArea.

L'utilisateur ouvre un nouveau fichier via une boîte de dialogue fileDialog permettant le parcours de fichiers, que nous allons ouvrir en réponse au déclenchement d'une action. Le chemin du fichier est passé à la fenêtre DocumentWidget pour que le contenu du document puisse être exploité par la bibliothèque Poppler.

Contrairement à la manipulation de nombreuses classes proposées par Qt, nous chargeons ici un document en utilisant une fonction statique de la classe Document :

Poppler::Document *doc = Poppler::Document::load(path);

Si la valeur retournée par le document n'est pas nulle, nous avons un document qui peut être exploité. Remarquez que notre exemple implique une réservation en mémoire pour le document, nous devons donc nous rappeler qu'il faudra libérer l'adresse en mémoire lorsque nous aurons fini de le manipuler.

Chaque document contient une série de pages qui peuvent être obtenues une par une en utilisant la fonction Document::page(). Bien que la classe Document possède un ensemble de fonctions pour gérer l'apparence du document, le rendu est en réalité appliqué à chaque objet Page. Dans notre exemple, nous stockons les pages dans des objets QImage que nous affichons en utilisant la fenêtre DocumentWidget, elle-même une simple sous-classe QLabel.

La partie cruciale de notre fonction DocumentWidget::showPage() ressemble à ça :

void DocumentWidget::showPage(int page)
{
QImage image = doc->page(currentPage)->renderToImage(
scaleFactor * physicalDpiX(),
scaleFactor * physicalDpiY());
...
setPixmap(QPixmap::fromImage(image));
}

Dans le code ci-dessus, nous précisons la résolution de l'image pour sa création, multipliée par un facteur d'échelle que l'utilisateur contrôle via l'interface utilisateur de notre exemple. Nous devons faire attention à la validité du facteur d'échelle, car il est facile de se retrouver avec des images très grandes. En pratique, nous restreignons le choix de l'utilisateur à un ensemble de facteurs d'échelle prédéfinis.

V - Recherche de texte

L'une des fonctionnalités les plus utiles que Poppler propose est l'agencement de chaînes de caractères spécifiques dans des documents PDF. Le format PDF étant conçu pour stocker des documents pour l'impression plus que pour leur modification, il n'est pas toujours aisé d'y avoir accès et de reconstruire le texte original, tel que mis en forme par l'auteur. Cependant, Poppler s'en sort plutôt bien pour agencer du texte sur de nombreux documents et nous allons pouvoir le montrer dans notre exemple.

L'API utilisée pour l'agencement du texte propose des fonctionnalités conventionnelles comme la sensibilité à la casse et la recherche orientée mais nous informe aussi sur la position du texte agencé sur la page ; étant donné que le PDF est un format d'affichage, la position est réellement la seule information importante que nous pouvons obtenir sur le texte. Cette information peut être utilisée pour indiquer l'endroit où les futures recherches devront commencer.

En fait, le code pour procéder à une recherche pas à pas dans une page donnée ressemble à ça :

bool found = page->search(text, searchLocation,
Poppler::Page::NextResult,
Poppler::Page::CaseInsensitive);

Ici, searchLocation est un objet QRectF qui indique l'endroit où la recherche doit commencer sur la page concernée. Initialement, lors d'une recherche, nous instancions juste l'objet QRectF avec un constructeur par défaut pour commencer à la première page.

Le rectangle retourné par la fonction Page::search() peut être utilisé pour afficher la page et mettre en surbrillance le contenu recherché une fois localisé et la faire défiler pour s'assurer qu'il est visible à l'affichage. Cependant, la position et les dimensions du rectangle sont données en points (1 inch valant 72 points), nous devons donc transformer le rectangle pour couvrir correctement la zone sur l'écran.

Rechercher un bout de texte dans un document s'avère être un peu plus fastidieux que de faire un simple appel de fonction. Nous reviendrons plus en détail sur ce point.

VI - Extraction de texte

Étant donné que la correspondance entre le texte original de l'auteur et sa position sur l'écran peut être purement visuelle, il est difficile d'automatiser l'extraction de texte d'un fichier PDF, bien que certains outils tentent tant bien que mal d'y parvenir.

De nombreux logiciels de lecture de PDF permettent à l'utilisateur de sélectionner et d'exporter du texte en sélectionnant une région de l'écran, fournissant ainsi à l'application un contenu exploitable ; la bibliothèque Poppler soutient cette approche en proposant une fonction qui retourne une chaîne de caractères pour un rectangle donné que nous appelons comme ceci :

QString text = doc->page(currentPage)->text(selectedRect);

La méthode que nous utilisons est quelque peu différente de celle-ci. Nous l'étudierons plus en détail au moment venu.

VII - Exemple complet

Maintenant que nous connaissons les bases de l'affichage de page, de recherche et d'extraction de texte, regardons de plus près la manière d'utiliser ces fonctionnalités dans notre exemple. Nous allons implémenter deux fonctions pour que l'utilisateur puisse, via l'interface utilisateur, rechercher des chaînes de caractères. Pour effectuer une recherche pas à pas, nous commençons par chercher les chaînes de caractères sur la page actuellement sélectionnée, en débutant par la zone en cours et nous réitérons jusqu'à atteindre la fin du document. Si nous atteignons la fin du document sans obtenir de résultats, nous appliquons alors la recherche entre le début du document et la page considérée.

QRectF DocumentWidget::searchForwards(const QString &text)
{
int page = currentPage;
while (page < doc->numPages()) {

if (doc->page(page)->search(text, searchLocation,
Poppler::Page::NextResult,
Poppler::Page::CaseInsensitive)) {

if (!searchLocation.isNull()) {
showPage(page + 1);
return searchLocation;
}
}
page += 1;
searchLocation = QRectF();
}

Si nous arrivons à la fin du document, sans rien trouver, nous cherchons depuis le début jusqu'à atteindre la page courante.

page = 0;

while (page < currentPage) {

searchLocation = QRectF();

if (doc->page(page)->search(text, searchLocation,
Poppler::Page::NextResult,
Poppler::Page::CaseInsensitive)) {

if (!searchLocation.isNull()) {
showPage(page + 1);
return searchLocation;
}
}
page += 1;
}

return QRectF();
}

Pour obtenir un bon rendu des pages à différentes échelles, comme nous l'avons vu précédemment, nous aimerions mettre en valeur le résultat de nos recherches. Nous allons alors insérer un bout de code pour dessiner sur l'image de la page considérée en utilisant une matrice pour faire correspondre le rectangle avec l'image.

QMatrix DocumentWidget::matrix() const
{
return QMatrix(scaleFactor * physicalDpiX() / 72.0, 0,
0, scaleFactor * physicalDpiY() / 72.0,
0, 0);
}

void DocumentWidget::showPage(int page)
{
...

QImage image = doc->page(currentPage)->renderToImage(
scaleFactor * physicalDpiX(),
scaleFactor * physicalDpiY());

if (!searchLocation.isEmpty()) {
QRect highlightRect = matrix().mapRect(
searchLocation).toRect();
highlightRect.adjust(-2, -2, 2, 2);
QImage highlight = image.copy(highlightRect);
QPainter painter;
painter.begin(&image);
painter.fillRect(image.rect(),
QColor(0, 0, 0, 32));
painter.drawImage(highlightRect, highlight);
painter.end();
}

setPixmap(QPixmap::fromImage(image));
}

L'image suivante montre le résultat de nos efforts : le texte localisé est affiché normalement alors que le reste de la page est légèrement foncé.

Dans notre exemple, nous allons permettre à l'utilisateur de mettre en valeur une sélection sur la page en ré-implémentant trois gestionnaires d'événements de souris dans notre fenêtre DocumentWidget. Dans celles-ci, nous déclarons un objet QRubberBand pour conserver la sélection d'une zone, comme indiqué dans la documentation de la classe QRubberBand. Le gestionnaire de l'événement de relâchement de bouton de la souris marque le début de la sélection du texte.

void DocumentWidget::mouseReleaseEvent(QMouseEvent *)
{
...
if (!rubberBand->size().isEmpty()) {
QRectF rect = QRectF(rubberBand->pos(),
rubberBand->size());
rect.moveLeft(rect.left() -
(width() - pixmap()->width()) / 2.0);
rect.moveTop(rect.top() -
(height() - pixmap()->height()) / 2.0);
selectedText(rect);
}

rubberBand->hide();
}

Quand l'utilisateur relâche le bouton de la souris, nous créons un rectangle dont les coordonnées sont relatives au coin en haut à gauche de l'image dans le label et nous le passons à la fonction selectedText() dont le rôle est d'informer le reste de l'application sur le contenu trouvé dans celui-ci.

Comme nous l'avons vu, la class Page de Poppler fournit une méthode qui retourne le texte contenu dans un rectangle à l'intérieur d'un document. Cependant, avec selectedText(), nous utilisons une méthode plus compliquée pour rendre compte de la richesse d'information que nous pouvons obtenir du document.

Nous allons commencer par faire correspondre le rectangle de sélection avec la page en utilisant la matrice inverse de celle que nous avons utilisée pour mettre en valeur le résultat de nos recherches, avant d'obtenir une liste d'objets TextBox, dont chaque élément décrira une partie du texte.

void DocumentWidget::selectedText(const QRectF &rect)
{
QRectF selectedRect = matrix().inverted()
.mapRect(rect);

QString text;
bool hadSpace = false;
QPointF center;
foreach (Poppler::TextBox *box,
doc->page(currentPage)->textList()) {

if (selectedRect.intersects(box->boundingBox())) {
if (hadSpace)
text += " ";
if (!text.isEmpty() &&
box->boundingBox().top() > center.y())
text += "\n";

text += box->text();
hadSpace = box->hasSpaceAfter();
center = box->boundingBox().center();
}
}

if (!text.isEmpty())
emit textSelected(text);
}

Nous testons si chaque partie du texte colle bien dans la sélection et, si la condition est vérifiée, nous l'ajoutons dans un objet QString. Nous effectuons également quelques vérifications pour voir si nous pouvons facilement insérer une nouvelle ligne de caractères aux endroits appropriés.

Remarquez que, bien que nous soyons satisfaits de pouvoir faire des recherches sur des parties du texte (typiquement les mots d'une phrase), les versions récentes de Poppler permettent d'effectuer des recherches dans des objets TextBox sur un seul caractère à la fois.

Dans l'interface utilisateur, quand l'utilisateur sélectionne du texte, nous l'affichons dans un navigateur pour qu'il puisse être copié et collé n'importe où.

VIII - Compilation de l'exemple

À l'image d'un projet standard réalisé avec Qt, l'exemple est fourni avec un simple fichier pdfviewer.pro. Étant donné qu'il existe une certaine liberté concernant le choix de l'emplacement d'installation de la bibliothèque Poppler et des en-têtes, nous avons besoin de modifier ce fichier pour corriger le chemin d'accès. Sur Ubuntu 8.04 avec le paquet libpoppler-qt4-dev installé, les chemins appropriés sont comme suit :

INCLUDEPATH  += /usr/include/poppler/qt4
LIBS         += -L/usr/lib -lpoppler-qt4

Les autres distributions Linux peuvent installer ces fichiers à des emplacements différents et les développeurs utilisant d'autres plateformes peuvent préférer compiler la bibliothèque en même temps que l'exemple plutôt que de l'installer pour une question de simplicité.

IX - Autres fonctionnalités et améliorations possibles

Notre exemple d'application de rendu PDF utilise seulement les fonctionnalités les plus basiques de la bibliothèque Poppler. Depuis que de nombreux documents utilisent des fonctionnalités comme le cryptage, les diaporamas, les tables des matières et les annotations, les applications de rendu PDF qui sollicitent Poppler pour afficher des documents utilisent des bibliothèques pour supporter celles-ci.

Poppler inclut un certain nombre de fonctionnalités bas niveau très utiles quand il s'agit d'analyser des fichiers PDF. Avoir accès à la liste des polices utilisées dans un document et aux données de ces polices peut être intéressant quand nous préparons la publication de celui-ci. Accéder au corps du texte dans un document permet aux développeurs d'indexer celui-ci afin d'exploiter le texte et en faire des analyses. Cependant, comme nous l'avons mentionné, pour certains documents, l'utilisation de cette fonctionnalité pourrait être limitée. Vous pouvez trouver un bon résumé sur les problèmes d'extraction de texte à la page suivante : http://www.glyphandcog.com/textext.html

L'information qui n'est pas contenue dans la partie visible du document est également exploitable via l'API Poppler. Les commentaires, les scripts (typiquement du JavaScript) et les URL pour les hyperliens peuvent tous être obtenus, bien qu'il appartienne au développeur de présenter l'information de façon claire et significative.

Comme la classe QPainter proposée par Qt, Poppler permet également d'écrire des fichiers PostScript ; nous pourrions donc aisément ajouter la possibilité d'exporter et de convertir des fichiers. Les versions récentes supportent aussi les sorties PDF, ce qui ouvre la porte à l'utilisation de la bibliothèque pour la manipulation de fichiers PDF. En effet, depuis que la bibliothèque nous autorise à analyser un document sans avoir à l'afficher, il est possible d'écrire des outils en ligne de commande pour gérer les documents et un certain nombre d'entre eux est supporté par Poppler.

X - Pour en savoir plus

Poppler est hebergé sur freedesktop.org, un site dédié aux projets de bureaux libres et Open Source :

- http://poppler.freedesktop.org/

Le frontend Qt4 pour Poppler possède sa propre documentation, qui peut être consultée via le wiki du projet :

- http://freedesktop.org/wiki/Software/poppler

Parmi les logiciels de rendu de PDF les plus connus qui utilisent Poppler, on compte Okular et Evince pour les environnements de bureau KDE et GNOME :

- http://okular.kde.org/

- http://www.gnome.org/projects/evince/

L'application Xpdf de laquelle Poppler dérive peut être obtenue sur le site Web suivant :

- http://www.foolabs.com/xpdf/

XI - Divers

Un grand merci à johnlamericain et frifri59 pour leur soutien et les nombreuses suggestions qu'ils ont pu m'apporter tout au long de ma traduction ainsi qu'à gbdivers pour son aide et la mise en page de l'article traduit. Une pensée également à l'équipe des relecteurs, en particulier à ram-0000 et à jacques_jean qui ont relu l'article, pour la correction des éventuelles fautes d'orthographe ou de syntaxe restantes. Aussi, je tiens à remercier particulièrement dourouc05 pour son importante contribution.

Enfin, au nom de toute l'équipe Qt, j'aimerais adresser le plus grand remerciement à Nokia pour nous avoir autorisé à traduire cet article !