Etiquetas

, , , ,

Para manipular documentos PDF existe una herramienta de software libre llamada pdftk. Es capaz de hacer de todo: combinar, separar páginas, descifrar un documento con contraseña, cifrar, descomprimir y volver a comprimir páginas… En su página web podréis encontrar muchos ejemplos de uso.

Hace unos días me tocó juguetear con esta herramienta para conseguir que un documento que contenía dos páginas por hoja tuviese un formato convencional. Comencé trabajando con este script (unpnup) que combina el uso de pdftk con otro programa llamado poster. El problema de esta propuesta es que poster sólo funciona con ficheros Postscript así que, en primer lugar, hay que convertir el documento PDF en Postscript. No siempre se obtienen resultados satisfactorios. Descubrí que existe una alternativa que trabaja directamente con ficheros PDF: pdfposter. Así evitaba la conversión de PDF a Postcript y de Postscritpt a PDF, y con éste los resultados sí son buenos.

Y el resultado final es este programita escrito en Python, fruto del buen hacer de Pedro Lobo, profesor de la UPM:

 #! /usr/bin/env python
# -*- coding: utf-8 -*-

import sys, os, tempfile, re, subprocess, glob, shutil
import pyPdf

def numero_paginas(f_pdf):
 """Devuelve en número de páginas del fichero PDF f_pdf"""
 return pyPdf.PdfFileReader(file(f_pdf, 'rb')).getNumPages()

def secuencia_paginas(num_pags_A3, rotacion = ""):
 """
 Calcula la secuencia en la que las páginas tamaño A4 están colocadas
 en pliegos de tamaño A3 y la devuelve en forma de lista.
 """
 p = 1
 S = []
 while p < num_pags_A3 * 2:
 S.append("%d%s" % (p, rotacion))
 S.append("%d%s" % (p + 3, rotacion))
 p = p + 4
 p = p - 2
 while p > 0:
 S.append("%d%s" % (p, rotacion))
 S.append("%d%s" % (p - 1, rotacion))
 p = p - 4
 return S

rpdf = re.compile('\.pdf$', re.IGNORECASE)
rnombre = re.compile('(.*)\.pdf$', re.IGNORECASE)

for f in sys.argv[1:]:
 if not rpdf.search(f):
 print "Se ignora \"%s\" porque no acaba en .pdf" % (f)
 continue
 if not os.path.isfile(f):
 print "No existe el fichero \"%s\"" % (f)
 continue
 print "Procesando %s..." % (f)
 tmpd = tempfile.mkdtemp()
 print "Separando las páginas individuales...",
 cmd = ['pdfposter', '-p2x1a4', f, "%s/A4.pdf" % (tmpd)]
 subprocess.call(cmd)
 print " hecho."
 np = numero_paginas(f)
 print "El original tiene %d páginas" % (np)
 print "Reordenando y rotando las páginas...",
 cmd = ['pdftk', "%s/A4.pdf" % (tmpd), 'cat']
 cmd = cmd + secuencia_paginas(np, 'W')
 ff = "%s_A4.pdf" % (rnombre.match(os.path.basename(f)).group(1))
 cmd = cmd + ['output', ff]
 subprocess.call(cmd)
 print " hecho."
 print "Recogiendo...",
 shutil.rmtree(tmpd)
 print " hecho."
 print "El resultado está en el fichero \"%s\"" % (ff)

Para hacer la operación inversa, esto es, construir un documento PDF con varias páginas por hoja, existe un conjunto de herramientas llamado pdfjam, y en particular una llamada pdfnup. Nuevamente Pedro ha escrito un programa en Python para hacer su uso más amigable:

#! /usr/bin/env python
# -*- coding: utf-8 -*-

import sys, os, re, subprocess, glob, shutil
import pyPdf

def numero_paginas(f_pdf):
 """Devuelve en número de páginas del fichero PDF f_pdf"""
 return pyPdf.PdfFileReader(file(f_pdf, 'rb')).getNumPages()

__re_nombre = re.compile('(.*)\.pdf$', re.IGNORECASE)

def generar_2up(f, verboso=False):
 """
 Manipula un documento PDF para generar una versión en tamaño A4 con
 dos páginas por hoja. Se supone que la primera página del documento
 de partida es una portada, y que la última es una contraportada; al
 generar la versión 2x1 la contraportada se mueve a la página 1, de
 modo que la primera hoja contiene la contraportada y la portada, y
 el resto de hojas contienen una pareja de páginas impar y par
 consecutivas.
 """
 ff = "%s_A4_2up.pdf" % (__re_nombre.match(os.path.basename(f)).group(1))
 np = numero_paginas(f)
 cmd = ['pdfnup', '--nup', '2x1', \
 '--pages', "%s, 1-%s" % (np, np - 1), '--outfile', ff, f]
 subprocess.call(cmd)
 return ff

if __name__ == '__main__':
 re_pdf = re.compile('\.pdf$', re.IGNORECASE)
 for f in sys.argv[1:]:
 if not re_pdf.search(f):
 print "Se ignora \"%s\" porque no acaba en .pdf" % (f)
 continue
 if not os.path.isfile(f):
 print "No existe el fichero \"%s\"" % (f)
 continue
 print "Procesando %s..." % (f)
 ff = generar_2up(f, True)
 print " hecho."
 print "El resultado está en el fichero \"%s\"" % (ff)

Y termino con un pequeño script basado en Ghostscript que permite bajar la resolución de un documento PDF para reducir su tamaño.

#!/bin/sh

#
# Uso: pdfdpi <DPI> <fichero_PDF> [<fichero_PDF> ...]
#

DPI=$1
d=`basename $2 | perl -pe "s/^(.*)(\.pdf)$/\1-${1}dpi.pdf/i"`

gs    -q -dNOPAUSE -dBATCH -dSAFER \
 -sDEVICE=pdfwrite -dCompatibilityLevel=1.4 \
 -dPDFSETTINGS=/screen -dEmbedAllFonts=true -dSubsetFonts=true \
 -dColorImageDownsampleType=/Bicubic -dColorImageResolution=$DPI \
 -dGrayImageDownsampleType=/Bicubic -dGrayImageResolution=$DPI \
 -dMonoImageDownsampleType=/Bicubic -dMonoImageResolution=$DPI \
 -sOutputFile="$d" \
 $2