Utilisation de polices: PDF et iText
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:
Mais certains caractères ne sont pas affichés dans le document PDF:
Code : Sélectionner tout - Visualiser dans une fenêtre à part document.add(new Paragraph("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é.Všechno v poádku?
Ce billiet a été inspiré par les questions suivantes sur StackOverflow:
- Why doesn't FontFactory.GetFont(“Known Font Name”, floatSize) work?
- Can't get Czech characters while generating a PDF
- Why is iText embedding font even if I have specified not to embed?
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
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):
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();
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:
Lorsque vous comparez le code avec la capture d'écran, vous remarquerez immédiatement que plusieurs caractères manquents (f02_unembedded_and_wrong.pdf):
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();
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.
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):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...
Le résultat ressemble à ceci:
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("\"");
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,..."Vous \u00eates d'o\u00f9?"
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):
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:
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();
- 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):
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):
Il est évident que cela ne fonctionne pas (f04_russian_wrong_encoding.pdf):
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();
Pour l'Europe orientale, plus spécifiquement pour l'alphabet cyrillique, on a besoin du codage 1251 (F05_Russian_correct_encoding):
Maintenant, nous pouvons voir le texte russe (f05_russian_correct_encoding.pdf):
Code : Sélectionner tout - Visualiser dans une fenêtre à part Font font = FontFactory.getFont(FONT, "Cp1251", BaseFont.EMBEDDED);
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):
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.
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();
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):
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.
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();
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:
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:
Code : Sélectionner tout - Visualiser dans une fenêtre à part Font font = FontFactory.getFont(FONT, BaseFont.IDENTITY_H, BaseFont.NOT_EMBEDDED);
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.Section 9.7.5.2:
The Identity-H and Identity-V CMaps shall not be used with a non-embedded font.