Unicode en PDF
utf-8 pdf-generation (7)
Mi programa genera documentos PDF relativamente simples a petición, pero estoy teniendo problemas con los caracteres Unicode, como kanji o símbolos matemáticos extraños. Para escribir una cadena normal en PDF, la coloca entre corchetes:
(something)
También existe la opción de escapar de un personaje con códigos octales:
(/527)
pero esto solo sube a 512 caracteres. ¿Cómo codificas o escapas personajes más altos? He visto referencias a secuencias de bytes y cadenas codificadas con hexadecimal, pero ninguna de las referencias que he leído parece estar dispuesta a decirme cómo hacerlo realmente.
Editar: Alternativamente, apúntame a una buena biblioteca de PDF de Java que hará el trabajo por mí. El que estoy usando actualmente es una versión de gnujpdf (que he corregido varios errores, ya que el autor original parece haber desaparecido sin permiso), que le permite programar contra una interfaz AWT Graphics, e idealmente cualquier reemplazo debería hacer lo mismo.
Las alternativas parecen ser HTML -> PDF, o un modelo programático basado en párrafos y cuadros que se parece mucho al HTML. iText es un ejemplo de esto último. Esto significaría volver a escribir mi código existente, y no estoy convencido de que me den la misma flexibilidad para diseñarlo.
Edición 2: no me había dado cuenta antes, pero la biblioteca iText tiene una API Graphics2D y parece manejar el Unicode perfectamente, así que eso es lo que voy a usar. Aunque no es una respuesta a la pregunta como se le preguntó, me resuelve el problema.
Edición 3: iText funciona muy bien para mí. Creo que la lección es que cuando te encuentres con algo que parece inútilmente difícil, busca a alguien que sepa más sobre ti que tú.
Como señaló Dredkin, debe usar los índices de glifos en lugar del valor del carácter Unicode en la secuencia de contenido de la página. Esto es suficiente para mostrar texto Unicode en PDF, pero el texto Unicode no se puede buscar. Para hacer que el texto pueda buscarse o copiar / pegar, también deberá incluir una secuencia / ToUnicode. Esta secuencia debe traducir cada glifo en el documento al carácter Unicode real.
Consulte el Apéndice D (página 995) de la especificación PDF. Existe una cantidad limitada de fuentes y juegos de caracteres predefinidos en una aplicación de consumidor de PDF. Para mostrar otros caracteres, debe incrustar una fuente que los contenga. También es preferible incrustar solo un subconjunto de la fuente, incluidos solo los caracteres necesarios, para reducir el tamaño del archivo. También estoy trabajando en mostrar caracteres Unicode en PDF y es una gran molestia.
Ver PDFBox o iText.
En la referencia en PDF del capítulo 3, esto es lo que dicen sobre Unicode:
Las cadenas de texto están codificadas en codificación de caracteres PDFDocEncoding o Unicode. PDFDocEncoding es un superconjunto de la codificación ISO Latin 1 y está documentado en el Apéndice D. Unicode se describe en el Estándar Unicode por el Consorcio Unicode (ver la Bibliografía). Para cadenas de texto codificadas en Unicode, los primeros dos bytes deben ser 254 seguidos por 255. Estos dos bytes representan el marcador de orden de bytes Unicode, U + FEFF, que indica que la cadena está codificada en el esquema de codificación UTF-16BE (big-endian) especificado en el estándar Unicode. (Este mecanismo impide comenzar una cadena usando PDFDocEncoding con los dos caracteres thorn ydieresis, que es poco probable que sea un comienzo significativo de una palabra o frase).
He trabajado varios días en este tema ahora y lo que he aprendido es que unicode es (tan bueno como) imposible en pdf. Usando caracteres de 2 bytes, la forma en que se describe el plinto solo funciona con CID-Fonts.
Aparentemente, CID-Fonts es un constructo interno de pdf y no son realmente fuentes en ese sentido; parecen ser más como subrutinas de gráficos, que pueden invocarse al abordarlas (con direcciones de 16 bits).
Entonces, usar Unicode en pdf directamente
- tendrías que convertir las fuentes normales a CID-Fonts, que probablemente sea extremadamente difícil: tendrías que generar las rutinas gráficas desde la fuente original (?), extraer las métricas de los caracteres, etc.
- no puede usar CID-Fonts como fuentes normales; no puede cargarlas o escalarlas de la misma forma que carga y escala las fuentes normales
- Además, los caracteres de 2 bytes ni siquiera cubren el espacio Unicode completo
En mi humilde opinión, estos puntos hacen que sea absolutamente inviable utilizar unicode directamente .
Lo que estoy haciendo ahora es usar los caracteres indirectamente de la siguiente manera: para cada fuente, genero una página de códigos (y una tabla de búsqueda para búsquedas rápidas) - en c ++ esto sería algo así como
std::map<std::string, std::vector<wchar_t> > Codepage;
std::map<std::string, std::map<wchar_t, int> > LookupTable;
luego, cada vez que quiero poner unicode-string en una página, repito sus caracteres, los busco en la tabla de búsqueda y, si son nuevos, los agrego a la página de códigos de esta manera:
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);
}
}
luego, genero una nueva cadena, donde los caracteres de la cadena original son reemplazados por sus posiciones en la página de códigos de esta manera:
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 += ">";
por ejemplo, "H € llo World!" podría convertirse en <01020303040506040703080905> y ahora puede simplemente poner esa cadena en el pdf y hacerla imprimir, usando el operador Tj como de costumbre ...
pero ahora tiene un problema: el pdf no sabe que quiere decir "H" por 01. Para resolver este problema, también debe incluir la página de códigos en el archivo pdf. Esto se hace agregando una / Codificación al objeto Font y estableciendo sus diferencias
Para el "H € llo World!" ejemplo, este Font-Object funcionaría:
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
Lo genero con este código:
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";
Tenga en cuenta que utilizo un registro de fuente global: uso los mismos nombres de fuente / F1, / F2, ... en todo el documento pdf. Se hace referencia al mismo objeto de registro de fuente en la entrada / Recursos de todas las páginas. Si haces esto de otra manera (p. Ej., Utilizas un registro de fuente por página), es posible que tengas que adaptar el código a tu situación ...
Entonces, ¿cómo se encuentran los nombres de los glifos (/ Euro para "€", / exclam para "!", Etc.)? En el código anterior, esto se hace simplemente llamando a "GlyphName (* j)". He generado este método con un BASH-Script de la lista encontrada en
http://www.jdawiseman.com/papers/trivia/character-entities.html
y parece que esto
const std::string GlyphName(wchar_t UnicodeCodepoint)
{
switch(UnicodeCodepoint)
{
case 0x00A0: return "nonbreakingspace";
case 0x00A1: return "exclamdown";
case 0x00A2: return "cent";
...
}
}
Un problema importante que he dejado abierto es que esto solo funciona siempre que use como máximo 254 caracteres diferentes de la misma fuente. Para usar más de 254 caracteres diferentes, debería crear múltiples páginas de códigos para la misma fuente.
Dentro del pdf, diferentes páginas de códigos están representadas por diferentes fuentes, por lo que para cambiar entre páginas de códigos, tendría que cambiar las fuentes, lo que en teoría podría explotar mucho su pdf, pero yo puedo vivir con eso ...
La respuesta de Algoman es incorrecta en muchas cosas. Puedes hacer un documento PDF con unicode ''y no es una ciencia de cohetes, aunque necesita algo de trabajo. Sí, tiene razón, para usar más de 255 caracteres en una fuente, debe crear un objeto pdf de fuente compuesta (CIDFont). Luego, solo mencione la fuente TrueType real que desea usar como una entrada DescendatFont de CIDFont. El truco es que después de eso tienes que usar índices de glifo de una fuente en lugar de códigos de caracteres. Para obtener este mapa de índices, debe analizar la sección cmap
de una fuente: obtener el contenido de la fuente con la función GetFontData
y tomar las manos en la especificación TTF. ¡Y eso es! ¡Acabo de hacerlo y ahora tengo un pdf Unicode!
El código de ejemplo para analizar la sección de cmap
está aquí: https://support.microsoft.com/en-us/kb/241020
Y sí, no olvide la entrada de ToUnicode como @ user2373071 señalado o el usuario no podrá buscar su PDF o copiar texto de él.
La respuesta simple es que no hay una respuesta simple. Si echas un vistazo a la especificación de PDF, verás un capítulo entero, y uno largo dedicado a los mecanismos de visualización de texto. Implementé todo el soporte de PDF para mi empresa, y el manejo de texto fue, con mucho, la parte más compleja del ejercicio. La solución que descubrió, usar una biblioteca de terceros para hacer el trabajo por usted, es realmente la mejor opción, a menos que tenga requisitos muy específicos y de propósito especial para sus archivos PDF.
No soy un experto en PDF, y (como dijo Ferruccio) las especificaciones de PDF en Adobe deberían decirle todo, pero un pensamiento apareció en mi mente:
¿Estás seguro de que estás usando una fuente que admite todos los caracteres que necesitas?
En nuestra aplicación, creamos PDF a partir de páginas HTML (con una biblioteca de terceros), y tuvimos este problema con caracteres cirílicos ...