2 puntos por GN⁺ 2023-09-05 | 1 comentarios | Compartir por WhatsApp
  • 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

 
GN⁺ 2023-09-05
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

    • En el contexto de que originalmente era un formato propietario, no estaba ampliamente abierto a todo el mundo y se creó en una época en la que el cifrado estaba fuertemente regulado por leyes de exportación, ese tipo de seguridad por oscuridad era muy común.
      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.
    • Método: https://www.cyberciti.biz/faq/removing-password-from-pdf-on-...
    • ¿Los modos de fusión admitidos no son simplemente los modos de composición Porter-Duff? Puede parecer excesivo, pero se corresponden muy bien con lo que ofrecen otros pipelines de renderizado, y ayudan bastante a reducir el trabajo necesario para generar PDFs.
    • Si te metes a escarbar en el campo de permisos, terminas en una madriguera de conejo donde descubres cómo algunos generadores de PDF no siguen la especificación, y los tratamientos alternativos que pueden o no existir según el lector o la biblioteca de PDF.
    • Para ser justos, si quieres impedir la copia de texto, lo más fácil es quitar el mapeo ToUnicode de la fuente. Entonces la gente tiene que rehacerlo manualmente.
  • 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.

    • Si la lees “por diversión”, estás más cerca de ser masoquista que sádico. Si hubieras participado en escribir la especificación o hubieras hecho que otros la leyeran, entonces sí serías sádico.
    • Es una lástima que Adobe haya diseñado un formato tan difícil de manejar que la gente se sorprenda cuando alguien logra hacer algo que debería ser una tarea básica.
      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?
    • Si disfrutas sentir dolor, eres masoquista; si te gusta causárselo a otros, eres sádico. Como parece que intentas ayudar a la gente, estás más cerca de lo primero. Aprecio el consejo sobre mutool.
    • Dependo mucho de Amazon Textract para parsear PDFs.
      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.ps con pdf2ps a.pdf
    Editar directamente el PostScript con vim a.ps
    Convertir de nuevo a PDF con ps2pdf a.ps
    Los 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.

    • En una empresa de procesamiento postal donde trabajé antes, hacíamos parte de la preparación para impresión casi de esta forma. Normalmente generábamos los documentos para impresión con herramientas adecuadas, pero a veces un cliente creía saber más y enviaba un PDF, y por lo general implicaba más trabajo y más posibilidades de error.
      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.
    • Si se puede meter JavaScript y animaciones en un PDF, ¿qué impide crear un frontend dentro de él? Me da curiosidad cuáles son los límites de lo que se puede hacer con PDF.
      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.

    • Por eso se decodifica con qpdf y luego se vuelve a codificar. Así qpdf se encarga de esa parte.
      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.
    • Para mí, esa es la parte más rara de la especificación de PDF. Mezcla binario y texto, y los offsets de bytes se especifican como texto.
      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.
    • En mi experiencia, lo más fácil es simplemente romper la tabla xref y luego arreglarla ejecutando algo como mutool clean. Es seguro porque se puede derivar completamente del contenido.
    • Es bastante parecido a editar un binario compilado.
      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 --qdf de 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:

    1. Los documentos PDF suelen ser archivos binarios de 8 bits, pero si usas el filtro ASCII85 puedes crear un PDF de texto plano UTF-8 válido, incluso con imágenes.
    2. PDF permite una enorme cantidad de funciones extrañas, como objetos 3D, JavaScript, videos dentro de objetos Flash integrados y anotaciones invisibles. PDF/A es un subconjunto mucho más sensato y seguro.
    3. La especificación de PDF permite escribir widgets, por ejemplo controles de formulario, como texto enriquecido, que es un subconjunto de XHTML y CSS. Pero esta función casi no tiene soporte fuera del Adobe Reader oficial.
      [0] Ejemplo: https://lab6.com/2
    • Después de terminar la edición, basta con pasarlo por qpdf sin parámetros, como se explica al principio del artículo. Eso recomprime los datos y también reconstruye la tabla xref. No hace falta otra herramienta.
  • 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.

    • El objetivo original de PDF era crear una copia portátil con fidelidad de impresión. Verdadero WYSIWYG. Un archivo PDF debía verse igual si se enviaba a una impresora láser, a una linotipia o a una pantalla.
      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.

    • Cuando la gente deje de enviarme PDF que necesito editar, yo también dejaré de editar 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.
    • PDF también admite funciones para incluir información de estructura lógica del documento. Es lo que se llama Tagged PDF.
      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...
    • Aquí decir “no se debería hacer” no significa mucho. En la realidad hay muchísimas situaciones en las que, de una u otra forma, te piden editar PDF.
    • Una dificultad es marcar correcciones que deben volver al documento original. Recibes pruebas del maquetador y tienes que marcarlas para que ellos las corrijan.
      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.
    • Si es un “formato de descripción de página” y no representa la estructura del documento sino solo lo que aparece sobre la página, ¿no deberían haberlo llamado Page Description Format en vez de Portable Document Format?
  • 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