- Si quieres cambiar el contenido de texto de un PDF sin usar herramientas PDF comerciales, tienes que descomprimir los streams comprimidos, seguir la codificación de cada fuente y reemplazar el texto original y el texto sustituto de la misma manera
- Muchos PDF no guardan el texto real del documento como texto plano, sino en streams comprimidos y cadenas que parecen hexadecimales; con
qpdf --qdf --object-streams=disable puedes desarmarlos hasta una forma más cercana a algo editable
- Los bloques de texto están compuestos por comandos PDF como
BT, Tf, Td, Tj y ET, y valores como <002a004800570003003600480057> Tj podrían no ser simples codepoints Unicode
- Si el PDF incluye un mapeo ToUnicode por fuente, puedes revisar con
pdffonts si emb y uni están en yes, y seguir referencias como /ToUnicode 19 0 R para encontrar la tabla de codificación
- Por la complejidad del estándar PDF, este método no se puede generalizar fácilmente a todos los archivos, pero en ciertos PDF sí permite encontrar la codificación original y reemplazar texto con find-and-replace
Descomprimir streams de texto comprimidos en PDF
- Al abrir un PDF en un editor de texto, parte de la estructura puede leerse, pero el texto real del documento podría no verse
- Muchos PDF guardan los datos de texto en formato comprimido
- Puedes usar
qpdf para descomprimir los streams de texto y revisarlos
- En Mac existe una fórmula de Homebrew
- En distribuciones Linux también se pueden usar herramientas relacionadas
qpdf --qdf --object-streams=disable in.pdf out.pdf
- Después de editar, puedes volver a comprimir los streams
qpdf out-edited.pdf out-recompressed.pdf
- El comando de recompresión dio un error, pero el PDF resultante podía leerse en Preview
Encontrar texto dentro de comandos PDF
- Incluso después de descomprimirlo, el texto sigue dentro de bloques de comandos PDF
- Un bloque de texto básico tiene esta forma
BT
/Font_0 12 Tf
288 720 Td
<002a004800570003003600480057> Tj
ET
- Según la referencia de PDF, este bloque realiza las siguientes acciones en orden
- Inicia un objeto de texto
- Configura la fuente y el tamaño que se usarán
- Define la posición inicial en la página
- Dibuja los glifos de la cadena en esa posición
- Finaliza el objeto de texto
Por qué el texto no se puede leer directamente
- Valores como
<002a004800570003003600480057> parecen cadenas hexadecimales, pero podrían no ser una simple lista de codepoints Unicode
- PDF tiene muchas formas de especificar codificación y puede incluir codificaciones personalizadas dentro del archivo
- La mayoría de esas codificaciones pueden mapearse a codepoints Unicode
- Este procedimiento asume que el PDF con el que trabajas incluye una codificación embebida
- No se cubre cómo manejar otros casos
Verificar la fuente y el mapeo Unicode
- La codificación de texto de un PDF está asociada a una fuente específica
pdffonts muestra información sobre las fuentes del PDF y su codificación
$ pdffonts sample.pdf
name type emb sub uni prob object ID
------------------------------------ ----------------- --- --- --- ---- ---------
CLDQZB+TrebuchetMS,Bold CID TrueType yes yes yes 9 0
YQBAIZ+TrebuchetMS CID TrueType yes yes yes 10 0
- Los campos que conviene revisar aquí son los siguientes
emb: significa que la fuente está embebida en el PDF
uni: significa que existe un mapeo Unicode
- Si ambos valores están en
yes, hay posibilidades de encontrar el mapeo necesario
- Un identificador de fuente como
/Font_0 en el ejemplo puede corresponder, según el caso, al nombre de fuente que aparece en la salida de pdffonts
- En ese caso,
/Font_0 apuntaba a CLDQZB+TrebuchetMS,Bold
Seguir la tabla de mapeo ToUnicode
- Después de encontrar el nombre completo de la fuente usada en el texto, búscalo dentro del PDF
- En cierta ubicación puedes encontrar una referencia como esta
/ToUnicode 19 0 R
- Este valor apunta al ID del objeto de la tabla de codificación
- Luego, al buscar
19 0 obj, se pudo encontrar esa tabla
Reemplazar texto usando la tabla de codificación
- La parte clave de la tabla de mapeo puede verse así
38 beginbfrange^M
<0036><0036><0053>^M
<0057><0057><0074>^M
<0044><0044><0061>^M
<0048><0048><0065>^M
<0050><0050><006D>^M
...
- Este ejemplo mapea rangos de puntos de codificación personalizada a puntos Unicode
- Aquí el rango tiene un solo carácter
0036 se mapea al punto Unicode 0053
- Si el formato de la tabla es distinto, puedes consultar el tutorial del archivo de mapeo ToUnicode
- Para automatizar la conversión, se convirtió la tabla a un diccionario en Python y se escribieron funciones simples de codificación/decodificación
- El flujo final de reemplazo fue el siguiente
- Verificar el valor de codificación personalizada del texto existente que quieres cambiar
- Codificar el texto de reemplazo con la misma codificación personalizada
- Reemplazar el valor anterior en el PDF usando find-and-replace
1 comentarios
Opiniones de Hacker News
La especificación de PDF es bastante áspera. Mi dato curioso favorito últimamente es que, al renderizar elementos superpuestos, admite todos los modos de fusión de capas de Photoshop.
Mi segundo favorito es que el contenido agregado al final puede modificar el contenido anterior, así que hay que revisar forénsicamente todas las distintas versiones representadas dentro de un mismo archivo.
También es un ejemplo de lo inútil que es el DRM. La especificación incluye cifrado basado en contraseña, permite tener contraseñas separadas de “propietario” y “usuario”, y también tiene campos de bits como “no imprimir” y “no copiar texto”. Pero para leer el documento, al final hay que descifrarlo, así que si abres un PDF cifrado con la contraseña de “usuario” en una herramienta que no cumple esas restricciones y lo guardas como una versión sin cifrar, obtienes un equivalente editable.
[1] Sección “More than just transparency” de https://blog.adobe.com/en/publish/2022/01/31/20-years-of-tra...
[2] https://blog.didierstevens.com/2008/05/07/solving-a-little-p...
[3] Página 61 de https://opensource.adobe.com/dc-acrobat-sdk-docs/pdfstandard...
[4] Por ejemplo, un script que usa la biblioteca pypdf
Uno de los tutoriales de cracking famosos de la época consistía en eliminar el DRM del lector oficial parcheando la función que verificaba los permisos.
Este tema aparece periódicamente, y la mayoría piensa que PDF es un formato binario difícil de penetrar, pero en realidad no es así.
PDF es un grafo de objetos de distintos tipos, y los tipos en sí están bien explicados en la especificación oficial. Tengo el gusto doloroso de leerla por diversión.
Lo que siempre recomiendo es, como el autor, convertir el PDF a una versión sin datos comprimidos. Mi herramienta preferida es mutool, y basta con usar
mutool clean -d in.pdf out.pdf. Después, si te pones a hurgar, te sorprende cuántas partes puedes seguir.En el artículo faltó el paso de mirar el objeto de página y revisar los recursos. Ahí es donde el nombre de la fuente usado en el stream de contenido se vincula con el subobjeto real.
Otra parte importante que falta es que la mayoría de las fuentes se incrustan en el PDF como subconjuntos. Solo se conservan en la fuente los glifos necesarios. La recodificación parece ocurrir normalmente ahí, y ToUnicode se conserva para permitir copiar texto o buscar dentro del PDF. Es algo útil para el usuario y, en mi experiencia, por lo general existe y suele ser correcto.
Para empezar, la filosofía de diseño de crear un formato de solo lectura ya estaba equivocada. ¿No era obvio cuál sería la primera función que la gente pediría?
Me pregunto si alguien tiene otras ideas sobre cómo manejarlo bien de forma nativa, sin un proveedor cloud. En especial al trabajar con tablas.
Se puede hacer así:
Convertir a PostScript
a.psconpdf2ps a.pdfEditar directamente el PostScript con
vim a.psConvertir de nuevo a PDF con
ps2pdf a.psLos PDF complejos que tienen cosas como JavaScript integrado o animaciones a veces no funcionan bien después de este viaje de ida y vuelta. Aun así, para documentos comunes funciona bastante bien. Quitar marcas de agua y cambiar palabras y números es fácil; ajustar el espaciado es más difícil. Claro, hay que saber algo de PostScript.
Aunque la salida estuviera bien, hacía falta reordenar páginas, aplicar códigos de barras para el procesamiento en máquinas postales y la clasificación postal, y generar reportes. Normalmente usábamos Perl y otras herramientas para extraer texto de las páginas y obtener direcciones; PS solía ser mucho más fácil que PDF, aunque a veces era muy frágil, como cuando un PDF “seguro” no tenía el mapeo correcto de glifos.
En el peor caso, el cliente enviaba PDFs de documentos individuales, y al combinarlos explotaba la cantidad de fuentes subconjuntadas en la salida, llenando la memoria de la impresora hasta matarla. Cuando dejé ese trabajo, creo que todavía no había una herramienta útil para unificar y combinar fuentes subconjuntadas que venían de la misma fuente original. Debería ser posible y habría sido muy útil, pero no tenía el tiempo ni el conocimiento para investigarlo.
Sinceramente, parece que los únicos que se benefician de la complejidad de PDF son los creadores de malware.
Creo que falta un punto importante. Al final de un PDF hay una tabla que guarda los offsets de bytes de varios objetos dentro del archivo, es decir, una tabla de referencias cruzadas.
Si modificas el contenido del archivo, normalmente esos offsets cambian y el archivo se rompe. Parece que el artículo solo se interesaba en cambiar un número por otro, así que tal vez las posiciones no cambiaron.
Pero, en general, si agregas, eliminas o modificas algo en medio del archivo, hay que recalcular la tabla xref, por lo que usar una biblioteca se vuelve mucho más fácil que editar texto a mano.
qpdf parece reconstruir la estructura original del PDF e intentar mantener también los números de objeto, pero los offsets se recalculan por completo.
Si algún día los autores lo cuentan, me gustaría leer por qué el formato terminó así. Parece que al principio lo pensaron como un formato completamente de texto, y luego decidieron que lo binario era más eficiente. Aunque el hecho de que las entradas de la tabla xref tengan que tener una longitud fija también encaja de forma algo extraña con esa suposición.
mutool clean. Es seguro porque se puede derivar completamente del contenido.Mientras conserves la longitud, puedes modificar el binario a gusto.
Un software que usaba antes se autenticaba contra un servidor, pero todo el procesamiento ocurría en el cliente. El cliente ejecutaba SQL directamente en el servidor, y el servidor solo verificaba si ese cliente excedía la cantidad de licencias compradas.
Podías pasarlo por un desensamblador, encontrar la parte que hacía la comprobación, cambiarla directamente por un JMP y rellenar el espacio restante con NOP.
Creo que falta un paso importante al usar el modo
--qdfde qpdf. Después de terminar la edición, hay que pasar el archivo por la utilidad fix-pdf para recalcular todos los offsets de objetos y reconstruir la tabla de referencias cruzadas al final del archivo. La excepción es si solo hiciste cambios en el mismo lugar sin agregar ni quitar bytes.Mis 3 datos curiosos principales sobre PDF son estos:
[0] Ejemplo: https://lab6.com/2
Si quieres seguir el recorrido por PDF y aprender más, puedes leer este documento reciente que preparé diseccionando archivos:
https://pdfsyntax.dev/introduction_pdf_syntax.html
Lo que la gente suele pasar por alto sobre los PDF es que, en cierto sentido, se parecen más a un formato de imagen que a un documento de Word. En la edición de documentos, los documentos de Word, los PDF y las imágenes son como un proyecto DAW, MIDI y MP3 en música, y se parecen al código fuente Java, el bytecode de JVM y el código máquina x86 puro en software.
El propósito principal de un archivo PDF es indicar con total claridad qué mostrar o imprimir usando muchos menos bytes que una imagen real. Aprovecha que el autor del documento conoce los patrones de la estructura del documento para que, si se expresa correctamente, se comprima mucho mejor que con algoritmos reales de compresión de imágenes. Por ejemplo, si se tiene acceso a la fuente real, es mejor decir “coloca estos caracteres en estas coordenadas con este espaciado” que incluir cada aparición de una letra como parte de una imagen y esperar que el algoritmo de compresión reduzca las repeticiones por su cuenta. Si el objetivo es solo transmitir eficientemente la imagen del documento, básicamente no importa qué carácter pertenece a qué palabra, ni siquiera qué punto de código Unicode corresponde a qué glifo de una fuente.
En un documento editable, no solo importa la representación, sino mucho más la estructura semántica del contenido. Importa si cierto corte en el texto son varios espacios, la siguiente columna de una tabla o una disposición rara de la página causada por una imagen. También importa si el texto al pie de cada página fue insertado varias veces por el autor o si se escribió una vez y se configuró como pie de página. Si al agregar un nuevo párrafo hay que cambiar el diseño de la página, importa saber que el último párrafo de esta página es una nota al pie y no debe pasar a la página siguiente. Importa que, si el título de una sección se mueve a otra página, la tabla de contenido se actualice automáticamente, no que sea simplemente texto escrito manualmente por el autor.
Desde el punto de vista de una impresora o una pantalla, nada de eso importa. Solo tiene que imprimir o mostrar lo que se le indicó. En PDF, las notas al pie, los títulos de sección, los pies de página y las tablas de contenido no tienen por qué ser especiales; también pueden ser texto con formato sin significado. Por eso, si se intenta usar un PDF para algo que no sea mostrar o imprimir, no se puede lograr una precisión del 100%. Claro que hay esfuerzos para compensar esto, y los programas que generan PDF pueden incluir libremente los metadatos que quieran, pero no es obligatorio.
Puede que este no sea el modelo mental que los creadores de PDF tenían en mente, pero es una perspectiva útil para entender por qué el PDF tiene esa forma.
Al imprimirlo en una impresora PostScript se veía exactamente igual, y con fuentes Type 1 incluso podía verse mejor.
Quien intenta hacer esto está pasando por alto la esencia de PDF. PDF es un formato de descripción de página, así que no representa la estructura del documento, sino solo las marcas visibles sobre la página.
No deberías intentar editar el PDF; deberías editar el documento original que generó el PDF.
De alguna manera, enviar simplemente el documento creado para ser editado se volvió algo “poco profesional”, y por eso terminamos en esta situación.
Es opcional, pero se recomienda por accesibilidad. Un ejemplo es PDF/UA. Ver los capítulos 14.7–14.8 de [1].
[1] https://opensource.adobe.com/dc-acrobat-sdk-docs/pdfstandard...
No puedes cambiar directamente el texto del PDF, porque el maquetador no verá ese cambio. Las herramientas de marcado de Acrobat no son horribles, pero no llegan al nivel de la época del papel y el lápiz rojo. A menos que uses la herramienta “pencil” de Acrobat. Ojalá esto mejorara.
Hace 20 años trabajé en un periódico como encargado de las planchas de impresión. Teníamos dos “impresoras” Kodak de planchas de un millón de dólares, aunque llamarlas impresoras no es del todo correcto. Si recuerdo bien, la emulsión de la plancha recibía luz ultravioleta y luego se disolvía en un baño químico.
El equipo Kodak fallaba seguido, y mi jefe solía abrir directamente archivos PostScript o EPS para corregir encabezados o partes incorrectas que había enviado el software de maquetación. Después de eso volvía a funcionar bien. Como dato al margen, nuestra enorme rotativa offset alemana corría Linux.
Creo que mi jefe se llamaba Bill. Era buena persona; llevó a mi yo de 17 años a un concierto de Sigur Ros. Ya van dos historias que no tienen que ver con PDF.
Buen artículo. Durante los últimos cinco años más o menos estuve creando DocSpring [1] y leí bastante la especificación de PDF, pero aun así siento que apenas rasqué la superficie. qpdf es una herramienta excelente, y otra herramienta que me gusta es RUPS [2], que permite explorar a fondo la estructura de un PDF.
[1] https://docspring.com
[2] https://github.com/itext/i7j-rups