IdentifiantMot de passe
Loading...
Mot de passe oublié ?Je m'inscris ! (gratuit)
Voir le flux RSS

blowagie

Utilisation de polices: PDF et iText

Noter ce billet
par , 25/05/2015 à 19h48 (3135 Affichages)
QUESTION:
Je ne réussis pas à utiliser des caractères cyrilliques lors de la création d'un document PDF en utilisant iText. J'ai essayé ceci avec des caractères tchèques:

Code : Sélectionner tout - Visualiser dans une fenêtre à part
document.add(new Paragraph("Všechno v pořádku?"));
Mais certains caractères ne sont pas affichés dans le document PDF:

Všechno v poádku?
Comme vous voyez, le caractère ř manque dans le texte. J'ai le même problème lorsque j'essaye d'utiliser des caractères cyrilliques. Dans ce cas, aucun texte est affiché.

Ce billiet a été inspiré par les questions suivantes sur StackOverflow:


REPONSE:

Même si vous ne partagez qu'une seule ligne de code, je vois déjà de différents problèmes dans votre code. Commençons avec un exemple simple et avec quelques phrases simples en français: F01_Unembedded.java

Code : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7
8
9
10
11
12
Document document = new Document();
PdfWriter.getInstance(document, new FileOutputStream(dest));
document.open();
document.add(new Paragraph("Vous êtes d'où?"));
document.add(new Paragraph("À tout à l'heure. À bientôt."));
document.add(new Paragraph("Je me présente."));
document.add(new Paragraph("C'est un étudiant."));
document.add(new Paragraph("Ça va?"));
document.add(new Paragraph("Il est ingénieur. Elle est médecin."));
document.add(new Paragraph("C'est une fenêtre."));
document.add(new Paragraph("Répétez, s'il vous plaît."));
document.close();
Bien que notre code n'est pas optimale (pour des raisons qui seront expliquées après l'exemple suivant), le résultat semble être parfait (f01_unembedded.pdf):

Nom : f01.png
Affichages : 1292
Taille : 59,7 Ko

J'ai traduit ces simples phrases de français en tchèque en utilisant "Google Translate", et je l'ai remplacé le texte français avec le tchèque dans le code: F02_Unembedded:

Code : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7
8
9
10
11
12
Document document = new Document();
PdfWriter.getInstance(document, new FileOutputStream(dest));
document.open();
document.add(new Paragraph("Odkud jste?"));
document.add(new Paragraph("Uvidíme se za chvilku. Měj se."));
document.add(new Paragraph("Dovolte, abych se představil."));
document.add(new Paragraph("To je studentka."));
document.add(new Paragraph("Všechno v pořádku?"));
document.add(new Paragraph("On je inženýr. Ona je lékař."));
document.add(new Paragraph("Toto je okno."));
document.add(new Paragraph("Zopakujte to prosím."));
document.close();
Lorsque vous comparez le code avec la capture d'écran, vous remarquerez immédiatement que plusieurs caractères manquents (f02_unembedded_and_wrong.pdf):

Nom : f02.png
Affichages : 1119
Taille : 62,5 Ko

Comment résoudre ce problème?

ASCII et les caractères speciaux

Je tiens d'abord à me plaindre de quelque chose que je n'aime pas voir dans le code: des caractères non-ASCII! Par exemple: je ne recommande pas de mettre "Vous êtes d'où?" dans votre code. Il vaut mieux de mettre "Vous \u00eates d'o\u00f9?". Il est dangereux de faire des hypothèses sur l'encodage qui sera utilisé lors de l'enregistrement, la transmission et le stockage de votre code source. Si par accident votre code est converti en ASCII, vous perdrez les caractères avec une valeur supérieure à 127.

Le codage ASCII contient les caractères nécessaires pour écrire en anglais (valeurs de 32 à 126) et des caractères de contrôle (de 0 à 31 et 127). Les caractères accentués sont fournis par d'autres normes, par example ISO 8859-1 (qui est souvent appelée Latin-1), UNICODE, etc...
Quand j'écris du code Java, je convertis toujours les chaînes codées en dur avec des caractères spéciaux en Unicode en utilisant la méthode suivante (F99_ConvertToUnicodeNotation):

Code : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7
8
9
10
String s = "Vous êtes d'où?";
System.out.print("\"");
for (int i = 0; i < s.length(); i++) {
    char c = s.charAt(i);
    if (c > 31 && c < 127)
         System.out.print(String.valueOf(c));
    else
        System.out.print(String.format("\\u%04x", (int)c));
}
System.out.println("\"");
Le résultat ressemble à ceci:

"Vous \u00eates d'o\u00f9?"
Cela peut être exagéré pour les langues occidentales, mais il est certainement une bonne idée face à des caractères cyrilliques, chinois, japonais, coréens,...

Les polices et les glyphes

Le codage des caractères dans le code, n'est pas le problème principal dans ce cas, autrement on aura aussi des problèmes avec les phrases on français. La raison pour laquelle certains caractères manquent dans les phrases tchèque est simple: nous utilisons une police qui ne sait rien sur les glyphes qui correspondent à ces caractères.

Comme nous n'avons pas défini une police, iText a choisi la police par défaut: Helvetica, une police "Standard Type 1". Cette police ne convient pas pour les langues non-occidentales.

Les polices incorporées

En plus, nous ne devrions pas supposer que tout système a accès à toutes les polices qu'on utilise dans nos documents. Si on utilise une police sans la incorporer dans le document, il y a toujours le risque qu'un utilisateur ne sera pas capable de lire notre document parce que la police est pas présent sur son ordinateur.

Les polices et le codage

Finalement, on doit faire attention au codage utilisé par la police. Le tchèque est une langue de l'europe centrale, donc on peut utiliser le codage 1250.

Tout ceci est combiné dans l'extrait de code suivant (F03_Embedded):

Code : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public static final String FONT = "resources/fonts/FreeSans.ttf";

Document document = new Document();
PdfWriter.getInstance(document, new FileOutputStream(dest));
document.open();
Font font = FontFactory.getFont(FONT, "Cp1250", BaseFont.EMBEDDED);
document.add(new Paragraph("Odkud jste?", font));
document.add(new Paragraph("Uvid\u00edme se za chvilku. M\u011bj se.", font));
 document.add(new Paragraph("Dovolte, abych se p\u0159edstavil.", font));
document.add(new Paragraph("To je studentka.", font));
document.add(new Paragraph("V\u0161echno v po\u0159\u00e1dku?", font));
document.add(new Paragraph("On je in\u017een\u00fdr. Ona je l\u00e9ka\u0159.", font));
document.add(new Paragraph("Toto je okno.", font));
document.add(new Paragraph("Zopakujte to pros\u00edm.", font));
document.close();
Au lieu d'Helvetica, nous utilisons FreeSans, une police libre qui est livré avec toutes les distributions Linux. Nous construisons un objet Font en utilisant la méthode createFont() dans la classe FontFactory. Il nous faut au moins les paramètres suivants:
  • Le chemin vers le fichier TTF qui defini la police (FreeSans.ttf),
  • Le codage ("Cp1250"),
  • Une valeur booléenne indiquant si la police doit être intégrée (BaseFont.EMBEDDED = true).

Notez qu'on a remplacé les caractères non-ASCII avec leur notation UNICODE.

Maintenant le résultat est correct. Tous les caractères/glyphes sont là (f03_embedded.pdf):

Nom : f03.png
Affichages : 1063
Taille : 75,2 Ko

Et pour les caractères cyrilliques?

Il est frustrant de voir combien de personnes que ne font que de copier et de coller du code sans comprendre ce qu'ils font. Par example, ils remplacent le text tchèque par du texte russe sans changer le codage (F04_Russian):

Code : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7
8
9
10
11
12
13
Document document = new Document();
PdfWriter.getInstance(document, new FileOutputStream(dest));
document.open();
Font font = FontFactory.getFont(FONT, "Cp1250", BaseFont.EMBEDDED);
document.add(new Paragraph("\u041e\u0442\u043a\u0443\u0434\u0430 \u0442\u044b?", font));
document.add(new Paragraph("\u0423\u0432\u0438\u0434\u0438\u043c\u0441\u044f \u0432 \u043d\u0435\u043c\u043d\u043e\u0433\u043e. \u0423\u0432\u0438\u0434\u0438\u043c\u0441\u044f.", font));
document.add(new Paragraph("\u041f\u043e\u0437\u0432\u043e\u043b\u044c\u0442\u0435 \u043c\u043d\u0435 \u043f\u0440\u0435\u0434\u0441\u0442\u0430\u0432\u0438\u0442\u044c\u0441\u044f.", font));
document.add(new Paragraph("\u042d\u0442\u043e \u0441\u0442\u0443\u0434\u0435\u043d\u0442.", font));
document.add(new Paragraph("\u0425\u043e\u0440\u043e\u0448\u043e?", font));
document.add(new Paragraph("\u041e\u043d \u0438\u043d\u0436\u0435\u043d\u0435\u0440. \u041e\u043d\u0430 \u0434\u043e\u043a\u0442\u043e\u0440.", font));
document.add(new Paragraph("\u042d\u0442\u043e \u043e\u043a\u043d\u043e.", font));
document.add(new Paragraph("\u041f\u043e\u0432\u0442\u043e\u0440\u0438\u0442\u0435, \u043f\u043e\u0436\u0430\u043b\u0443\u0439\u0441\u0442\u0430.", font));
document.close();
Il est évident que cela ne fonctionne pas (f04_russian_wrong_encoding.pdf):

Nom : f04.png
Affichages : 1073
Taille : 32,4 Ko

Pour l'Europe orientale, plus spécifiquement pour l'alphabet cyrillique, on a besoin du codage 1251 (F05_Russian_correct_encoding):

Code : Sélectionner tout - Visualiser dans une fenêtre à part
Font font = FontFactory.getFont(FONT, "Cp1251", BaseFont.EMBEDDED);
Maintenant, nous pouvons voir le texte russe (f05_russian_correct_encoding.pdf):

Nom : f05.png
Affichages : 1079
Taille : 68,0 Ko

Le problème avec les codages...

Lorsque vous voulez ajouter du texte où des différentes langues sont mélangés (par exemple le français, le tchèque, le russe), il faut définir de différents codages (F06_Different_encodings):

Code : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
Document document = new Document();
PdfWriter.getInstance(document, new FileOutputStream(dest));
document.open();
BaseFont bf1 = BaseFont.createFont(FONT, BaseFont.WINANSI, BaseFont.EMBEDDED);
Font french = new Font(bf1, 12);
BaseFont bf2 = BaseFont.createFont(FONT, BaseFont.CP1250, BaseFont.EMBEDDED);
Font czech = new Font(bf2, 12);
BaseFont bf3 = BaseFont.createFont(FONT, "Cp1251", BaseFont.EMBEDDED);
Font russian = new Font(bf3, 12);
document.add(new Paragraph("Vous \u00eates d'o\u00f9?", french));
document.add(new Paragraph("\u00c0 tout \u00e0 l'heure. \u00c0 bient\u00f4t.", french));
document.add(new Paragraph("Je me pr\u00e9sente.", french));
document.add(new Paragraph("C'est un \u00e9tudiant.", french));
document.add(new Paragraph("\u00c7a va?", french));
document.add(new Paragraph("Il est ing\u00e9nieur. Elle est m\u00e9decin.", french));
document.add(new Paragraph("C'est une fen\u00eatre.", french));
document.add(new Paragraph("R\u00e9p\u00e9tez, s'il vous pla\u00eet.", french));
document.add(new Paragraph("Odkud jste?", czech));
document.add(new Paragraph("Uvid\u00edme se za chvilku. M\u011bj se.", czech));
document.add(new Paragraph("Dovolte, abych se p\u0159edstavil.", czech));
document.add(new Paragraph("To je studentka.", czech));
document.add(new Paragraph("V\u0161echno v po\u0159\u00e1dku?", czech));
document.add(new Paragraph("On je in\u017een\u00fdr. Ona je l\u00e9ka\u0159.", czech));
document.add(new Paragraph("Toto je okno.", czech));
document.add(new Paragraph("Zopakujte to pros\u00edm.", czech));
document.add(new Paragraph("\u041e\u0442\u043a\u0443\u0434\u0430 \u0442\u044b?", russian));
document.add(new Paragraph("\u0423\u0432\u0438\u0434\u0438\u043c\u0441\u044f \u0432 \u043d\u0435\u043c\u043d\u043e\u0433\u043e. \u0423\u0432\u0438\u0434\u0438\u043c\u0441\u044f.", russian));
document.add(new Paragraph("\u041f\u043e\u0437\u0432\u043e\u043b\u044c\u0442\u0435 \u043c\u043d\u0435 \u043f\u0440\u0435\u0434\u0441\u0442\u0430\u0432\u0438\u0442\u044c\u0441\u044f.", russian));
document.add(new Paragraph("\u042d\u0442\u043e \u0441\u0442\u0443\u0434\u0435\u043d\u0442.", russian));
document.add(new Paragraph("\u0425\u043e\u0440\u043e\u0448\u043e?", russian));
document.add(new Paragraph("\u041e\u043d \u0438\u043d\u0436\u0435\u043d\u0435\u0440. \u041e\u043d\u0430 \u0434\u043e\u043a\u0442\u043e\u0440.", russian));
document.add(new Paragraph("\u042d\u0442\u043e \u043e\u043a\u043d\u043e.", russian));
document.add(new Paragraph("\u041f\u043e\u0432\u0442\u043e\u0440\u0438\u0442\u0435, \u043f\u043e\u0436\u0430\u043b\u0443\u0439\u0441\u0442\u0430.", russian));
document.close();
Remarquez que cette fois, nous avons utilisé un objet BaseFont pour créer l'objet Font. Ceci est équivalent à ce que nous avons fait avant avec l'objet FontFactory.

Si vous examinez les polices dans le PDF résultant (f06_unicode.pdf), vous découvrirez une police pour chaque encodage: trois différents "Embedded subset"s de la police FreeSans.

Il ya un certain nombre d'autres inconvénients associés à cette approche. Les polices sont utilisées comme des polices simples: chaque police ne peut definir que 256 caractères. Ceci n'est pas assez pour des langues avec beaucoup d'idiogrammes comme le chinois. On peut également être confronté avec un problème à l'égard de l'accessibilité du contenu du document. La tendance en PDF est d'utiliser Unicode et des polices composites.

UNICODE

Au lieu des codages Winansi, Windows-1250, Windows-1251,... on peux utiliser le codage UNICODE (F07_Unicode):

Code : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
Document document = new Document();
PdfWriter.getInstance(document, new FileOutputStream(dest));
document.open();
Font font = FontFactory.getFont(FONT, BaseFont.IDENTITY_H, BaseFont.EMBEDDED);
document.add(new Paragraph("Vous \u00eates d'o\u00f9?", font));
document.add(new Paragraph("\u00c0 tout \u00e0 l'heure. \u00c0 bient\u00f4t.", font));
document.add(new Paragraph("Je me pr\u00e9sente.", font));
document.add(new Paragraph("C'est un \u00e9tudiant.", font));
document.add(new Paragraph("\u00c7a va?", font));
document.add(new Paragraph("Il est ing\u00e9nieur. Elle est m\u00e9decin.", font));
document.add(new Paragraph("C'est une fen\u00eatre.", font));
document.add(new Paragraph("R\u00e9p\u00e9tez, s'il vous pla\u00eet.", font));
document.add(new Paragraph("Odkud jste?", font));
document.add(new Paragraph("Uvid\u00edme se za chvilku. M\u011bj se.", font));
document.add(new Paragraph("Dovolte, abych se p\u0159edstavil.", font));
document.add(new Paragraph("To je studentka.", font));
document.add(new Paragraph("V\u0161echno v po\u0159\u00e1dku?", font));
document.add(new Paragraph("On je in\u017een\u00fdr. Ona je l\u00e9ka\u0159.", font));
document.add(new Paragraph("Toto je okno.", font));
document.add(new Paragraph("Zopakujte to pros\u00edm.", font));
document.add(new Paragraph("\u041e\u0442\u043a\u0443\u0434\u0430 \u0442\u044b?", font));
document.add(new Paragraph("\u0423\u0432\u0438\u0434\u0438\u043c\u0441\u044f \u0432 \u043d\u0435\u043c\u043d\u043e\u0433\u043e. \u0423\u0432\u0438\u0434\u0438\u043c\u0441\u044f.", font));
document.add(new Paragraph("\u041f\u043e\u0437\u0432\u043e\u043b\u044c\u0442\u0435 \u043c\u043d\u0435 \u043f\u0440\u0435\u0434\u0441\u0442\u0430\u0432\u0438\u0442\u044c\u0441\u044f.", font));
document.add(new Paragraph("\u042d\u0442\u043e \u0441\u0442\u0443\u0434\u0435\u043d\u0442.", font));
document.add(new Paragraph("\u0425\u043e\u0440\u043e\u0448\u043e?", font));
document.add(new Paragraph("\u041e\u043d \u0438\u043d\u0436\u0435\u043d\u0435\u0440. \u041e\u043d\u0430 \u0434\u043e\u043a\u0442\u043e\u0440.", font));
document.add(new Paragraph("\u042d\u0442\u043e \u043e\u043a\u043d\u043e.", font));
document.add(new Paragraph("\u041f\u043e\u0432\u0442\u043e\u0440\u0438\u0442\u0435, \u043f\u043e\u0436\u0430\u043b\u0443\u0439\u0441\u0442\u0430.", font));
document.close();
Nous utilisons la même police (FreeSans.ttf), mais nous construisons l'objet BaseFont avec le paramètre BaseFont.IDENTITY_H pour le codage. Maintenant, il n'y a qu'une police dans notre document PDF (f07_unicode.pdf). La police est intégrée en tant que police composite. Un police composite peut contenir jusqu'à 65.535 caractères, ce qui est beaucoup plus qu'une police simple qui ne peut contenir que 256 caractères.

Un petit "Le saviez-vous?"

Le paramètre BaseFont.NOT_EMBEDDED est ignoré si on utilise le codage BaseFont.IDENTITY_H. Ceci est démontré dans le dernier exemple de ce billiet (F08_Unicode). Dans le code, on a:

Code : Sélectionner tout - Visualiser dans une fenêtre à part
Font font = FontFactory.getFont(FONT, BaseFont.IDENTITY_H, BaseFont.NOT_EMBEDDED);
Mais quand on examine le PDF (f08_unicode.pdf), on voit que la police est incorporée, malgré le paramètre BaseFont.NOT_EMBEDDED. Dans ce cas, iText n'obéit pas ce paramètre parce que la création d'un PDF avec le codage Identity-H sans intégrer la police ne conforme pas à la spécification PDF:

Section 9.7.5.2:
The Identity-H and Identity-V CMaps shall not be used with a non-embedded font.
En savoir plus? Téléchargez le ebook gratuit "The Best iText Questions on StackOverflow" pour la version anglaise de cette réponse, et pour la réponse a beaucoup d'autres questions sur le format PDF et iText.

Envoyer le billet « Utilisation de polices: PDF et iText » dans le blog Viadeo Envoyer le billet « Utilisation de polices: PDF et iText » dans le blog Twitter Envoyer le billet « Utilisation de polices: PDF et iText » dans le blog Google Envoyer le billet « Utilisation de polices: PDF et iText » dans le blog Facebook Envoyer le billet « Utilisation de polices: PDF et iText » dans le blog Digg Envoyer le billet « Utilisation de polices: PDF et iText » dans le blog Delicious Envoyer le billet « Utilisation de polices: PDF et iText » dans le blog MySpace Envoyer le billet « Utilisation de polices: PDF et iText » dans le blog Yahoo

Mis à jour 26/05/2015 à 09h53 par blowagie

Catégories
Java , Programmation