web-dev-qa-db-fra.com

Unicode en PDF

Mon programme génère des documents PDF PDF sur demande relativement simples, mais j'ai des problèmes avec les caractères unicode, comme les kanji ou les symboles mathématiques impairs. Pour écrire une chaîne normale en PDF, vous la placez entre crochets:

(something)

Il y a aussi la possibilité d'échapper un caractère avec des codes octaux:

(\527)

mais cela ne monte que jusqu'à 512 caractères. Comment encodez-vous ou échappez-vous aux personnages supérieurs? J'ai vu des références à des flux d'octets et des chaînes codées hexadécimales, mais aucune des références que j'ai lues ne semble vouloir me dire comment le faire.


Edit: Sinon, dirigez-moi vers une bonne bibliothèque Java PDF qui fera le travail pour moi. Celle que je suis actuellement en cours d'utilisation est une version de gnujpdf (dans laquelle j'ai corrigé plusieurs bogues, puisque l'auteur d'origine semble avoir disparu AWOL), qui vous permet de programmer contre une interface graphique AWT, et idéalement tout remplacement devrait faire de même.

Les alternatives semblent être soit HTML -> PDF, soit un modèle programmatique basé sur des paragraphes et des cases qui ressemble beaucoup au HTML. iText en est un exemple. Cela signifierait réécrire mon code existant, et je ne suis pas convaincu qu'ils me donneraient la même flexibilité dans la mise en page.


Edit 2: Je ne m'en étais pas rendu compte auparavant, mais la bibliothèque iText possède une API Graphics2D et semble gérer parfaitement l'unicode, c'est donc ce que j'utiliserai. Bien que ce ne soit pas une réponse à la question posée, cela résout le problème pour moi.


Edit 3: iText fonctionne bien pour moi. Je suppose que la leçon est que, face à quelque chose qui semble inutilement difficile, recherchez quelqu'un qui en sait plus que vous.

35
Marcus Downing

La réponse simple est qu'il n'y a pas de réponse simple. Si vous jetez un œil à la spécification PDF, vous verrez un chapitre entier - et un long à ce sujet - consacré aux mécanismes d'affichage du texte. J'ai implémenté toutes les PDF support pour mon entreprise, et la gestion du texte était de loin la partie la plus complexe de l'exercice. La solution que vous avez découverte - utiliser une bibliothèque tierce pour faire le travail pour vous - est vraiment le meilleur choix, sauf si vous ont des exigences très spécifiques et spécifiques pour vos fichiers PDF.

12
Derek Clegg

Dans la référence PDF dans le chapitre 3, voici ce qu'ils disent à propos d'Unicode:

Les chaînes de texte sont codées en PDFDocEncoding ou en codage de caractères Unicode. PDFDocEncoding est un sur-ensemble du codage ISO Latin 1 et est documenté dans l'annexe D. Unicode est décrit dans la norme Unicode par le consortium Unicode (voir la bibliographie). Pour les chaînes de texte codées en Unicode, les deux premiers octets doivent être 254 suivis de 255. Ces deux octets représentent le marqueur d'ordre d'octets Unicode, U + FEFF, indiquant que la chaîne est codée dans le schéma de codage UTF-16BE (big-endian) spécifié dans la norme Unicode. (Ce mécanisme empêche de commencer une chaîne à l'aide de PDFDocEncoding avec les deux caractères épineux ydieresis, qui est peu susceptible d'être un début significatif d'un mot ou d'une phrase).

35
plinth

La réponse d'Algoman est fausse à bien des égards. Vous pouvez créer un document PDF contenant Unicode et ce n'est pas sorcier, même si cela nécessite un peu de travail. Oui il a raison, pour utiliser plus de 255 caractères dans une police, vous devez créer un objet pdf de police composite (CIDFont). Ensuite, vous mentionnez simplement la police TrueType réelle que vous souhaitez utiliser comme entrée DescendatFont de CIDFont. L'astuce est qu'après que vous devez utiliser les indices de glyphe d'une police au lieu de codes de caractères. Pour obtenir cette carte d'indices, vous devez analyser la section cmap de une police - obtenez le contenu de la police avec la fonction GetFontData et prenez en main la spécification TTF. Et c'est tout! Je viens de le faire et maintenant j'ai un PDF Unicode!

Un exemple de code pour analyser la section cmap est ici: https://web.archive.org/web/20150329005245/http://support.Microsoft.com/en-us/kb/24102

Et oui, n'oubliez pas l'entrée/ToUnicode comme l'a souligné @ user2373071 ou l'utilisateur ne pourra pas rechercher votre PDF ou copier du texte à partir de celui-ci.

10
dredkin

Comme l'a souligné dredkin, vous devez utiliser les indices de glyphe au lieu de la valeur de caractère Unicode dans le flux de contenu de la page. Cela suffit pour afficher le texte Unicode au format PDF, mais le texte Unicode ne sera pas consultable. Pour rendre le texte consultable ou faire un copier/coller dessus, vous devrez également inclure un flux/ToUnicode. Ce flux doit traduire chaque glyphe du document en caractère Unicode réel.

5
user2373071

Reportez-vous à l'annexe D (page 995) de la spécification PDF. Il existe un nombre limité de polices et de jeux de caractères prédéfinis dans une application grand public PDF PDF. = afficher les autres caractères dont vous avez besoin pour incorporer une police qui les contient. Il est également préférable d'incorporer uniquement un sous-ensemble de la police, y compris uniquement les caractères requis, afin de réduire la taille du fichier. Je travaille également sur l'affichage des caractères Unicode dans PDF et c'est un gros problème.

Consultez PDFBox ou iText.

http://www.Adobe.com/devnet/pdf/pdf_reference.html

4
jm4

J'ai travaillé plusieurs jours sur ce sujet maintenant et ce que j'ai appris, c'est que l'unicode est (aussi bien que) impossible en pdf. En utilisant des caractères de 2 octets, la façon dont le socle décrit ne fonctionne qu'avec les polices CID.

apparemment, les polices CID sont une construction interne au pdf et ce ne sont pas vraiment des polices dans ce sens - elles semblent être plus comme des sous-routines graphiques, qui peuvent être invoquées en les adressant (avec des adresses 16 bits).

Donc, pour utiliser unicode en pdf directement

  1. vous devrez convertir des polices normales en polices CID, ce qui est probablement extrêmement difficile - vous devrez générer les routines graphiques à partir de la police d'origine (?), extraire les métriques de caractères, etc.
  2. vous ne pouvez pas utiliser les polices CID comme les polices normales - vous ne pouvez pas les charger ou les mettre à l'échelle comme vous chargez et mettez à l'échelle les polices normales
  3. aussi, les caractères à 2 octets ne couvrent même pas tout l'espace Unicode

À mon humble avis, ces points rendent absolument impossible l'utilisation directe de l'unicode .



Ce que je fais maintenant, c'est utiliser les caractères indirectement de la manière suivante: pour chaque police, je génère une page de code (et une table de recherche pour recherches) - en c ++, ce serait quelque chose comme

std::map<std::string, std::vector<wchar_t> > Codepage;
std::map<std::string, std::map<wchar_t, int> > LookupTable;

puis, chaque fois que je veux mettre une chaîne unicode sur une page, j'itère ses caractères, les recherche dans la table de recherche et - s'ils sont nouveaux, je les ajoute à la page de codes comme ceci:

for(std::wstring::const_iterator i = str.begin(); i != str.end(); i++)
{                
    if(LookupTable[fontname].find(*i) == LookupTable[fontname].end())
    {
        LookupTable[fontname][*i] = Codepage[fontname].size();
        Codepage[fontname].Push_back(*i);
    }
}

puis, je génère une nouvelle chaîne, où les caractères de la chaîne d'origine sont remplacés par leurs positions dans la page de code comme ceci:

static std::string hex = "0123456789ABCDEF";
std::string result = "<";
for(std::wstring::const_iterator i = str.begin(); i != str.end(); i++)
{                
    int id = LookupTable[fontname][*i] + 1;
    result += hex[(id & 0x00F0) >> 4];
    result += hex[(id & 0x000F)];
}
result += ">";

par exemple, "H € llo World!" pourrait devenir <01020303040506040703080905> et maintenant vous pouvez simplement mettre cette chaîne dans le pdf et l'imprimer, en utilisant l'opérateur Tj comme d'habitude ...

mais vous avez maintenant un problème: le pdf ne sait pas que vous voulez dire "H" par un 01. Pour résoudre ce problème, vous devez également inclure la page de code dans le fichier pdf. Pour ce faire, ajoutez un /Encodage à l'objet Font et définissez ses différences

Pour le "H € llo World!" Par exemple, cet objet Font fonctionnerait:

5 0 obj 
<<
    /F1
    <<
        /Type /Font
        /Subtype /Type1
        /BaseFont /Times-Roman
        /Encoding
        <<
          /Type /Encoding
          /Differences [ 1 /H /Euro /l /o /space /W /r /d /exclam ]
        >>
    >> 
>>
endobj 

Je le génère avec ce code:

ObjectOffsets.Push_back(stream->tellp()); // xrefs entry
(*stream) << ObjectCounter++ << " 0 obj \n<<\n";
int fontid = 1;
for(std::list<std::string>::iterator i = Fonts.begin(); i != Fonts.end(); i++)
{
    (*stream) << "  /F" << fontid++ << " << /Type /Font /Subtype /Type1 /BaseFont /" << *i;

    (*stream) << " /Encoding << /Type /Encoding /Differences [ 1 \n";
    for(std::vector<wchar_t>::iterator j = Codepage[*i].begin(); j != Codepage[*i].end(); j++)
        (*stream) << "    /" << GlyphName(*j) << "\n";
    (*stream) << "  ] >>";

    (*stream) << " >> \n";
}
(*stream) << ">>\n";
(*stream) << "endobj \n\n";

Remarquez que j'utilise un registre de polices global - J'utilise les mêmes noms de police/F1,/F2, ... dans tout le document pdf. Le même objet de police-registre est référencé dans l'entrée /Resources de toutes les pages. Si vous faites cela différemment (par exemple, vous utilisez un registre de polices par page) - vous devrez peut-être adapter le code à votre situation ...

Alors, comment trouvez-vous les noms des glyphes (/ Euro pour "€",/exclam pour "!" Etc.)? Dans le code ci-dessus, cela se fait en appelant simplement "GlyphName (* j)". J'ai généré cette méthode avec un BASH-Script à partir de la liste trouvée sur

http://www.jdawiseman.com/papers/trivia/character-entities.html

et ça ressemble à ça

const std::string GlyphName(wchar_t UnicodeCodepoint)
{
    switch(UnicodeCodepoint)
    {
        case 0x00A0: return "nonbreakingspace";
        case 0x00A1: return "exclamdown";
        case 0x00A2: return "cent";
        ...
    }
}

Un problème majeur que j'ai laissé ouvert est que cela ne fonctionne que tant que vous utilisez au plus 254 caractères différents de la même police. Pour utiliser plus de 254 caractères différents, vous devez créer plusieurs pages de code pour la même police.

À l'intérieur du pdf, différentes pages de code sont représentées par différentes polices, donc pour basculer entre les pages de code, vous devrez changer de police, ce qui pourrait théoriquement faire exploser votre pdf un peu, mais pour ma part, je peux vivre avec ça ...

2
Algoman