8. Espacio de nombres fix:: — normalización

codes:: · Índice · Siguiente: data::

19 operaciones que modifican el PDF y guardan un archivo nuevo. El original no se altera nunca.


8.1 Cómo se usa

fix:: es el único espacio de nombres que escribe, así que se ejecuta con un comando propio:

pdfl fix entrada.pdf script.pdfl --output corregido.pdf

Opciones:

Opción Qué hace
--output <archivo> PDF de salida (obligatorio)
--dry-run Lista las operaciones sin guardar nada
--report json|csv|html|pdf Formato del informe
--report-file <archivo> Escribe el informe en un archivo

En pdfl run, cualquier llamada a fix:: da un error que indica el comando correcto: así nadie aplica correcciones creyendo que solo está validando.

Cómo funcionan las operaciones

// Este script NO necesita checks: son comandos, ejecutados en orden.
fix::set_trim_box(8.5, 8.5, 586.5, 833.5)
fix::add_page_numbers()
fix::add_watermark("BORRADOR")

Cada llamada se valida en el momento (página inexistente, rotación inválida, archivo ausente) y solo después se aplica. El informe trae el campo fixes con lo que se hizo:

"fixes": [
  "TrimBox set to [8.5, 8.5, 586.5, 833.5]",
  "page numbering added",
  "watermark \"BORRADOR\" added"
]

Nada impide mezclar validación y corrección en el mismo script:

// Valida antes de corregir: si la precondición falla, sale en el informe
check "Precondiciones" {
  require doc.page_count > 0
  assert !struct::check_encryption(), "archivo cifrado, no se puede corregir"
}

fix::add_page_numbers()

8.2 Cajas de página

fix::set_page_size(anchura, altura)

Define la MediaBox de todas las páginas.

// A4 en puntos — o usa unidades y deja la conversión al lenguaje
fix::set_page_size(595, 842)
fix::set_page_size(210mm, 297mm)    // idéntico, y más legible

fix::set_crop_box(x0, y0, x1, y1), set_trim_box, set_bleed_box

Definen la caja correspondiente en todas las páginas. Coordenadas en puntos, de la esquina inferior izquierda a la superior derecha.

// El archivo llegó de la editorial sin las cajas de producción:
// TrimBox = área final; BleedBox = con 3 mm de sangrado alrededor
fix::set_trim_box(8.5, 8.5, 586.5, 833.5)
fix::set_bleed_box(0, 0, 595, 842)

8.3 Páginas

fix::rotate_page([pagina,] grados)

Rota 90, 180 o 270 grados. Sin el número de página, las rota todas.

fix::rotate_page(90)        // todas las páginas
fix::rotate_page(3, 180)    // solo la página 3

fix::delete_page(n) y fix::duplicate_page(n)

fix::delete_page(1)         // quita la portada de borrador
fix::duplicate_page(1)      // duplica la portada (la copia entra justo después)

Eliminar la única página del documento se rechaza con un mensaje claro.

fix::reorder_pages([nuevo, orden])

Nuevo orden de las páginas. La lista debe usar cada página exactamente una vez.

// Documento de 4 páginas con la portada al final: la trae al principio
fix::reorder_pages([4, 1, 2, 3])

fix::split_document(desde, hasta, "salida.pdf")

Guarda un intervalo de páginas en otro archivo. El documento en edición sigue intacto.

// Separa el interior de la cubierta para enviarlos a proveedores distintos
fix::split_document(1, 2, "cubierta.pdf")
fix::split_document(3, 50, "interior.pdf")

fix::merge_documents("otro.pdf")

Añade las páginas de otro PDF al final.

fix::merge_documents("anexos/certificado_de_garantia.pdf")
fix::merge_documents("anexos/tabla_de_medidas.pdf")

8.4 Contenido

fix::add_watermark("texto")

Marca de agua diagonal gris, en todas las páginas.

fix::add_watermark("BORRADOR — NO IMPRIMIR")

fix::add_stamps("texto")

Sello en rojo en la esquina superior derecha de cada página.

fix::add_stamps("APROBADO 02/08/2026")

fix::add_page_numbers()

Numeración n / total en el pie de cada página.

fix::add_page_numbers()

fix::remove_annotations() y fix::remove_attachments()

Eliminan las anotaciones (comentarios, marcas de revisión) y los archivos adjuntos.

// Antes de mandarlo a la imprenta: los comentarios de revisión no pueden
// aparecer, y los adjuntos solo engordan el archivo
fix::remove_annotations()
fix::remove_attachments()

fix::flatten_layers()

Elimina la estructura de capas opcionales (OCG) y deja todo el contenido permanentemente visible.

// Las capas con la "versión en inglés" apagada pueden reactivarse por error
// en la imprenta; aplanarlas elimina el riesgo
fix::flatten_layers()

8.5 Optimización

Las operaciones de esta sección solo guardan si el archivo encoge. Si la reescritura da un archivo mayor, se conserva el original.

fix::remove_unused_resources()

Descarta los objetos a los que no se llega desde el tráiler.

fix::remove_unused_resources()

fix::downsample_images([dpi])

Remuestrea las imágenes por encima del DPI objetivo (300 por defecto). El DPI se calcula por el tamaño impreso real de la imagen en la página.

// La versión para aprobación por correo no necesita 300 DPI
fix::downsample_images(96)

// Versión para impresión digital
fix::downsample_images(200)

Las imágenes CMYK se conservan. Remuestrearlas exigiría convertirlas a RGB, lo que destruiría las separaciones de preimpresión. En archivos de imprenta, la ganancia viene de las imágenes RGB.

fix::compress_images([calidad])

Recodifica las imágenes en JPEG con la calidad indicada (de 1 a 100, 85 por defecto).

fix::compress_images(70)

No disponibles

subset_fonts y linearize_document no existen como operaciones de fix:: y dan error de función desconocida:

  • subset_fonts: se implementó y se midió. Los generadores profesionales ya incrustan solo los glifos usados, así que la ganancia medida fue del 0,5% en el mejor caso y nula en el resto: no compensa el riesgo de corromper fuentes. Para comprobar si las fuentes están en subconjunto, usa prepress::subset_fonts().
  • linearize_document: exige generar hint tables (§7.14 de la especificación PDF). Ninguna biblioteca de Rust lo hace, y una versión parcial no la reconocerían los lectores como «Fast Web View».

8.6 Ejemplos completos

Preparar el archivo de la editorial para la imprenta

// preparar_para_imprenta.pdfl
// Uso: pdfl fix editorial.pdf preparar_para_imprenta.pdfl --output imprenta.pdf

check "Precondiciones" {
  require doc.page_count > 0
  assert !struct::check_encryption(),
    "archivo cifrado — pide la versión abierta a la editorial"
}

// Cajas de producción que la editorial no definió
fix::set_trim_box(8.5, 8.5, 586.5, 833.5)
fix::set_bleed_box(0, 0, 595, 842)

// Limpieza: los comentarios de revisión y los adjuntos no van a imprenta
fix::remove_annotations()
fix::remove_attachments()
fix::flatten_layers()
fix::remove_unused_resources()

Versión ligera para aprobación por correo

// version_correo.pdfl
// Uso: pdfl fix final.pdf version_correo.pdfl --output aprobacion.pdf

fix::downsample_images(96)
fix::compress_images(70)
fix::add_watermark("PRUEBA — NO ES LA VERSIÓN FINAL")
fix::add_page_numbers()

Comprobando el resultado con el propio pdfl:

pdfl fix final.pdf version_correo.pdfl --output aprobacion.pdf
pdfl inspect aprobacion.pdf          # tamaño, DPI y avisos del archivo generado

Separar un libro en cubierta e interior

// separar.pdfl
// Uso: pdfl fix libro.pdf separar.pdfl --output libro_procesado.pdf

check "Estructura esperada" {
  assert doc.page_count > 4,
    "libro con solo #{doc.page_count} páginas — estructura inesperada"
}

fix::split_document(1, 2, "salida/cubierta.pdf")
fix::split_document(3, doc.page_count, "salida/interior.pdf")

codes:: · Índice · Siguiente: data::