Utilisation de polices: PDF et iText

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:

  1. 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 Stack Overflow:

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

  1. Document document = new Document();
  2. PdfWriter.getInstance(document, new FileOutputStream(dest));
  3. document.open();
  4. document.add(new Paragraph("Vous êtes d'où?"));
  5. document.add(new Paragraph("À tout à l'heure. À bientôt."));
  6. document.add(new Paragraph("Je me présente."));
  7. document.add(new Paragraph("C'est un étudiant."));
  8. document.add(new Paragraph("Ça va?"));
  9. document.add(new Paragraph("Il est ingénieur. Elle est médecin."));
  10. document.add(new Paragraph("C'est une fenêtre."));
  11. document.add(new Paragraph("Répétez, s'il vous plaît."));
  12. 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:

Un PDF en français
Un PDF en français

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.java

  1. Document document = new Document();
  2. PdfWriter.getInstance(document, new FileOutputStream(dest));
  3. document.open();
  4. document.add(new Paragraph("Odkud jste?"));
  5. document.add(new Paragraph("Uvidíme se za chvilku. Měj se."));
  6. document.add(new Paragraph("Dovolte, abych se představil."));
  7. document.add(new Paragraph("To je studentka."));
  8. document.add(new Paragraph("Všechno v pořádku?"));
  9. document.add(new Paragraph("On je inženýr. Ona je lékař."));
  10. document.add(new Paragraph("Toto je okno."));
  11. document.add(new Paragraph("Zopakujte to prosím."));
  12. document.close();

Lorsque vous comparez le code avec la capture d'écran, vous remarquerez immédiatement que plusieurs caractères manquents:

Incorrect: des caractères tchèques manquent
Incorrect: des caractères tchèques manquent

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:

  1. String s = "Vous êtes d'où?";
  2. System.out.print("\"");
  3. for (int i = 0; i < s.length(); i++) {
  4. char c = s.charAt(i);
  5. if (c > 31 && c < 127)
  6. System.out.print(String.valueOf(c));
  7. else
  8. System.out.print(String.format("\\u%04x", (int)c));
  9. }
  10. 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:

  1. public static final String FONT = "resources/fonts/FreeSans.ttf";
  2. // main method:
  3. Document document = new Document();
  4. PdfWriter.getInstance(document, new FileOutputStream(dest));
  5. document.open();
  6. Font font = FontFactory.getFont(FONT, "Cp1250", BaseFont.EMBEDDED);
  7. document.add(new Paragraph("Odkud jste?", font));
  8. document.add(new Paragraph("Uvid\u00edme se za chvilku. M\u011bj se.", font));
  9. document.add(new Paragraph("Dovolte, abych se p\u0159edstavil.", font));
  10. document.add(new Paragraph("To je studentka.", font));
  11. document.add(new Paragraph("V\u0161echno v po\u0159\u00e1dku?", font));
  12. document.add(new Paragraph("On je in\u017een\u00fdr. Ona je l\u00e9ka\u0159.", font));
  13. document.add(new Paragraph("Toto je okno.", font));
  14. document.add(new Paragraph("Zopakujte to pros\u00edm.", font));
  15. 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à:

Exemple avec des caractères tchèques
Exemple avec des caractères tchèques

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:

  1. Document document = new Document();
  2. PdfWriter.getInstance(document, new FileOutputStream(dest));
  3. document.open();
  4. Font font = FontFactory.getFont(FONT, "Cp1250", BaseFont.EMBEDDED);
  5. document.add(new Paragraph("\u041e\u0442\u043a\u0443\u0434\u0430 \u0442\u044b?", font));
  6. document.add(new Paragraph(
  7. "\u0423\u0432\u0438\u0434\u0438\u043c\u0441\u044f \u0432 \u043d\u0435\u043c\u043d\u043e\u0433\u043e. "
  8. + \u0423\u0432\u0438\u0434\u0438\u043c\u0441\u044f.",
  9. font));
  10. document.add(new Paragraph(
  11. "\u041f\u043e\u0437\u0432\u043e\u043b\u044c\u0442\u0435 \u043c\u043d\u0435 "
  12. + "\u043f\u0440\u0435\u0434\u0441\u0442\u0430\u0432\u0438\u0442\u044c\u0441\u044f.",
  13. font));
  14. document.add(new Paragraph("\u042d\u0442\u043e \u0441\u0442\u0443\u0434\u0435\u043d\u0442.", font));
  15. document.add(new Paragraph("\u0425\u043e\u0440\u043e\u0448\u043e?", font));
  16. document.add(new Paragraph(
  17. "\u041e\u043d \u0438\u043d\u0436\u0435\u043d\u0435\u0440. \u041e\u043d\u0430 \u0434\u043e\u043a\u0442\u043e\u0440.",
  18. font));
  19. document.add(new Paragraph("\u042d\u0442\u043e \u043e\u043a\u043d\u043e.", font));
  20. document.add(new Paragraph("\u041f\u043e\u0432\u0442\u043e\u0440\u0438\u0442\u0435, "
  21. + "\u043f\u043e\u0436\u0430\u043b\u0443\u0439\u0441\u0442\u0430.", font));
  22. document.close();

Il est évident que cela ne fonctionne pas:

Incorrect: tous les caractères cyrilliques manquent
Incorrect: tous les caractères cyrilliques manquent

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

  1. Font font = FontFactory.getFont(FONT, "Cp1251", BaseFont.EMBEDDED);

Maintenant, nous pouvons voir le texte russe:

Exemple correct
Exemple correct

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:

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

  1. Document document = new Document();
  2. PdfWriter.getInstance(document, new FileOutputStream(dest));
  3. document.open();
  4. Font font = FontFactory.getFont(FONT, BaseFont.IDENTITY_H, BaseFont.EMBEDDED);
  5. document.add(new Paragraph("Vous \u00eates d'o\u00f9?", font));
  6. document.add(new Paragraph("\u00c0 tout \u00e0 l'heure. \u00c0 bient\u00f4t.", font));
  7. document.add(new Paragraph("Je me pr\u00e9sente.", font));
  8. document.add(new Paragraph("C'est un \u00e9tudiant.", font));
  9. document.add(new Paragraph("\u00c7a va?", font));
  10. document.add(new Paragraph("Il est ing\u00e9nieur. Elle est m\u00e9decin.", font));
  11. document.add(new Paragraph("C'est une fen\u00eatre.", font));
  12. document.add(new Paragraph("R\u00e9p\u00e9tez, s'il vous pla\u00eet.", font));
  13. document.add(new Paragraph("Odkud jste?", font));
  14. document.add(new Paragraph("Uvid\u00edme se za chvilku. M\u011bj se.", font));
  15. document.add(new Paragraph("Dovolte, abych se p\u0159edstavil.", font));
  16. document.add(new Paragraph("To je studentka.", font));
  17. document.add(new Paragraph("V\u0161echno v po\u0159\u00e1dku?", font));
  18. document.add(new Paragraph("On je in\u017een\u00fdr. Ona je l\u00e9ka\u0159.", font));
  19. document.add(new Paragraph("Toto je okno.", font));
  20. document.add(new Paragraph("Zopakujte to pros\u00edm.", font));
  21. document.add(new Paragraph("\u041e\u0442\u043a\u0443\u0434\u0430 \u0442\u044b?", font));
  22. document.add(new Paragraph("\u0423\u0432\u0438\u0434\u0438\u043c\u0441\u044f "
  23. + "\u0432 \u043d\u0435\u043c\u043d\u043e\u0433\u043e. "
  24. + "\u0423\u0432\u0438\u0434\u0438\u043c\u0441\u044f.", font));
  25. document.add(new Paragraph("\u041f\u043e\u0437\u0432\u043e\u043b\u044c\u0442\u0435 "
  26. + "\u043c\u043d\u0435 \u043f\u0440\u0435\u0434\u0441\u0442\u0430\u0432\u0438\u0442\u044c\u0441\u044f.",
  27. font));
  28. document.add(new Paragraph("\u042d\u0442\u043e \u0441\u0442\u0443\u0434\u0435\u043d\u0442.", font));
  29. document.add(new Paragraph("\u0425\u043e\u0440\u043e\u0448\u043e?", font));
  30. document.add(new Paragraph("\u041e\u043d \u0438\u043d\u0436\u0435\u043d\u0435\u0440. "
  31. + "\u041e\u043d\u0430 \u0434\u043e\u043a\u0442\u043e\u0440.", font));
  32. document.add(new Paragraph("\u042d\u0442\u043e \u043e\u043a\u043d\u043e.", font));
  33. document.add(new Paragraph("\u041f\u043e\u0432\u0442\u043e\u0440\u0438\u0442\u0435, "
  34. + "\u043f\u043e\u0436\u0430\u043b\u0443\u0439\u0441\u0442\u0430.", font));
  35. 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. 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:

  1. Font font = FontFactory.getFont(FONT, BaseFont.IDENTITY_H, BaseFont.NOT_EMBEDDED);

Mais quand on examine le 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.

Ce tutoriel explique comment éviter les pièges les plus courants lors de l'utilisation des polices avec iText.