Para reducir el tamaño de los archivos PDF en Delphi, losLab PDF Library proporciona tres API que atacan las tres fuentes principales de tamaño excesivo: SubsetEmbeddedFonts reescribe cada programa de fuente TrueType incrustado reduciéndolo a los glifos que el documento realmente representa, DownsampleImages vuelve a muestrear las imágenes rasterizadas que superan un PPP objetivo y NormalizeLZWStreams reemplaza la compresión LZWDecode heredada con FlateDecode. Cada una devuelve el número de objetos que modificó, por lo que un cero le indica que la pasada fue una operación sin efecto en lugar de una falla silenciosa
¿Por qué mi PDF fusionado es más grande que sus archivos de origen?
Un PDF fusionado o generado mediante programación suele ser demasiado grande por una de tres razones: fuentes completamente incrustadas, imágenes muestreadas muy por encima de su resolución de pantalla y flujos aún comprimidos con el filtro LZW heredado. La norma ISO 32000-1 §9.9 permite que un productor incruste el programa de fuente completo, y la mayoría de los productores hacen exactamente eso porque es la opción predeterminada segura. Un archivo Arial FontFile2 completo ocupa cientos de kilobytes; incrústelo en una docena de archivos de origen, fusiónelos y estará cargando una docena de copias de contornos de glifos para caracteres que nadie escribió. La fusión en sí misma no crea el desperdicio, simplemente lo concentra en un solo archivo donde el total finalmente se vuelve visible
Las imágenes son el segundo culpable. Un escaneo de 4800 píxeles de ancho colocado en un cuadro de un cuarto de página envía aproximadamente 40 veces más datos de píxeles de los que puede usar un flujo de impresión de 300 PPP. El tercero es más silencioso: flujos filtrados con LZWDecode. La norma ISO 32000-1 §7.4.4 especifica tanto LZWDecode como FlateDecode, y señala que Flate generalmente comprime al menos tan bien; en la práctica, la salida de Flate es consistentemente más pequeña para los mismos datos, y LZW sobrevive principalmente en archivos que pasaron por herramientas de la era de la década de 1990 en algún momento de su historia. El resto de este artículo recorre las tres pasadas de losLab PDF Library que solucionan cada problema, y luego las combina en un solo flujo de trabajo
Creación de subconjuntos de fuentes con SubsetEmbeddedFonts
SubsetEmbeddedFonts reduce cada fuente TrueType incrustada en un documento cargado a los caracteres que el documento realmente utiliza, y no necesita argumentos porque deriva la lista de elementos a conservar de los propios flujos de contenido. Internamente, la pasada recorre el flujo de contenido de cada página con GetTextRuns, recopila los códigos de caracteres referenciados bajo cada recurso de fuente, crea una lista de elementos a conservar y entrega el programa de fuente original al motor FontSub de Windows (CreateFontPackage) para producir un subconjunto. El programa reescrito reemplaza el flujo FontFile2 en su lugar, y el nombre de BaseFont obtiene una etiqueta LOSABC+, la convención de seis letras mayúsculas más un signo más que define la norma ISO 32000-1 §9.6.4 para fuentes de subconjunto. Ese prefijo es también lo que hace que la llamada sea idempotente: ejecute la pasada dos veces y las fuentes que ya son subconjuntos se reconocerán y omitirán, por lo que es seguro integrarla en un trabajo por lotes que pueda volver a examinar archivos
var
Lib: TPDFlib;
Fonts: Integer;
begin
Lib := TPDFlib.Create;
try
if Lib.LoadFromFile('merged-report.pdf', '') = 1 then
begin
Fonts := Lib.SubsetEmbeddedFonts;
// Fonts = number of FontFile2 programs rewritten;
// 0 means nothing embedded, or everything already subsetted
Lib.SaveToFile('merged-report-subset.pdf');
end;
finally
Lib.Free;
end;
end;
Vale la pena conocer dos detalles de implementación porque explican los límites de la API. Primero, la pasada se dirige a FontFile2, por lo que cubre programas TrueType incrustados; las fuentes incrustadas como Type 1 o CFF puro se dejan intactas para evitar riesgos. Segundo, depende de FontSub, lo que hace que SubsetEmbeddedFonts sea exclusivo de Windows. Un punto más sutil de la implementación: si una fuente califica se decide al resolver realmente la cadena de referencia de FontDescriptor → FontFile2, no al confiar en una heurística de indicador de incrustación, porque las fuentes en un documento cargado nunca pasaron por el registro del lado de la creación que establece tales indicadores. Si el flujo resuelto existe, la fuente es candidata; si no, se omite sin error
El compromiso real: una fuente de subconjunto contiene solo los glifos presentes en el momento de la creación del subconjunto. Si una herramienta posterior, o su propio código, añade texto más tarde con esa misma fuente, cualquier carácter fuera del subconjunto no tendrá contorno y se representará como un glifo faltante. Cree el subconjunto como el último paso de cambio de contenido, nunca antes de una etapa de edición. La misma precaución se aplica si planea volver a extraer la fuente más tarde para su reutilización; el artículo sobre extracción de texto, imágenes y fuentes con PDFlibPas cubre lo que un programa de subconjunto extraído puede y no puede darle
¿Cómo decide DownsampleImages qué imágenes reducir?
DownsampleImages(MaxDPI, Quality, Filter) vuelve a muestrear solo las imágenes que puede identificar con confianza como sobremuestreadas, utilizando una estimación de PPP deliberadamente conservadora. Un objeto XObject de imagen PDF almacena dimensiones de píxeles pero no una resolución física confiable, y cualquier etiqueta de PPP de la imagen de origen rara vez sobrevive a un ciclo de cargar-editar-guardar. Así que la pasada estima SrcDPI = PixelWidth / 8.5, preguntando en efecto: si esta imagen abarcara todo el ancho de una página tamaño Carta, ¿cuál sería su resolución? Solo se tocan las imágenes cuya estimación supera MaxDPI. El sesgo es intencional: una imagen colocada en tamaño pequeño en la página tiene un PPP real más alto que la estimación, por lo que la pasada se activa de menos en lugar de degradar un recurso de calidad de impresión que no puede medir
Quality de 1 a 100 selecciona la calidad de re-codificación JPEG, mientras que 0 mantiene la salida como Flate sin pérdidas estilo PNG; Filter elige el núcleo de remuestreo, 0 para un promedio de caja y 1 para bilineal. Para trámites de oficina escaneados, DownsampleImages(150, 75, 1) is a punto de partida sensato; para cualquier cosa que pueda volver a imprimirse, aumente MaxDPI a 300 o ignore la pasada por completo. La reducción de resolución es el único paso con pérdidas de los tres, por lo que pertenece a una configuración que sus usuarios puedan desactivar
Convertir flujos LZW heredados con NormalizeLZWStreams
NormalizeLZWStreams es la ganancia gratuita: descomprime sin pérdidas cada flujo LZWDecode stream y lo vuelve a comprimir con FlateDecode, en su lugar, devolviendo el recuento de flujos convertidos. Maneja tanto una sola entrada /Filter /LZWDecode como la aparición de LZW dentro de una matriz de cadena de filtros, donde solo se reemplaza el enlace LZW y se conserva el resto de la cadena. Los parámetros del predictor (Predictor, Columns, Colors, BitsPerComponent) se leen del DecodeParms del flujo y se pasan al descompresor, de modo que los datos de la imagen codificados por el predictor realicen el ciclo completo correctamente. Debido a que ambos filtros son códecs de bits exactos, los bytes decodificados son idénticos antes y después; solo cambia la compresión del contenedor, razón por la cual esta pasada es segura de ejecutar incondicionalmente en cada archivo
En un documento sin flujos LZW, la llamada simplemente devuelve 0 y no toca nada, lo cual el conjunto de regresión de la biblioteca ejercita explícitamente: un archivo solo Flate recién creado debe informar cero conversiones. Esa garantía de operación sin efecto es importante cuando la pasada se encuentra en un flujo de trabajo que procesa miles de archivos heterogéneos, algunos de 2024 y otros de 1998
El flujo de trabajo completo de optimización de tamaño en Delphi
function OptimizePDF(const Src, Dst: string): Boolean;
var
Lib: TPDFlib;
Fonts, Images, Streams: Integer;
begin
Result := False;
Lib := TPDFlib.Create;
try
if Lib.LoadFromFile(Src, '') <> 1 then
Exit;
Fonts := Lib.SubsetEmbeddedFonts; // TrueType FontFile2 -> subset
Images := Lib.DownsampleImages(150, 75, 1); // >150 DPI -> JPEG q75, bilinear
Streams := Lib.NormalizeLZWStreams; // LZWDecode -> FlateDecode
Result := Lib.SaveToFile(Dst) = 1;
// Log Fonts/Images/Streams: three zeros mean the file was already lean
finally
Lib.Free;
end;
end;
Verifique el flujo de trabajo de la forma en que la biblioteca se verifica a sí misma: ciclo completo. Las pruebas de regresión de la versión v3.130 crean un documento, lo guardan, lo vuelven a cargar, ejecutan la optimización, lo guardan de nuevo y luego afirman tres cosas: la salida es más pequeña, los recuentos devueltos coinciden con las expectativas y una nueva carga del archivo optimizado aún se analiza y representa. Reproducir ese ciclo de crear-optimizar-recargar contra una muestra de sus propios archivos de producción, y comparar el texto extraído antes y después, es una inversión de una hora que detecta errores de integración mucho antes de que un cliente abra una factura rota
// Round-trip check: the optimized file must still load cleanly
Lib := TPDFlib.Create;
try
Assert(Lib.LoadFromFile('merged-report-opt.pdf', '') = 1);
Assert(Lib.GetPageCount > 0);
finally
Lib.Free;
end;
¿Dónde encaja el flujo de trabajo en un proceso de fusión? Después de la fusión, no durante ella. Fusionar primero y optimizar el resultado único significa que cada fuente incrustada se convierte en subconjunto una vez contra la unión de todos los caracteres utilizados, en lugar de hacerlo por archivo de origen. Si el rendimiento de la fusión es el cuello de botella, PDFlibPas ofrece una ruta rápida a nivel de bytes que evita el análisis completo del objeto, descrita en el artículo sobre fusión rápida de PDF con desplazamiento de referencia de bytes; y para entradas demasiado grandes para guardarse completamente en memoria, la guía sobre fusión y división de PDF grandes con acceso directo cubre la ruta de transmisión. Ambas se complementan de manera natural con una pasada de optimización final en la salida fusionada
Lo que las tres pasadas no harán
El trío de optimización de losLab PDF Library excluye deliberadamente cualquier cosa que cambie la semántica del documento. SubsetEmbeddedFonts no unifica fuentes duplicadas entre fuentes fusionadas en un solo programa, reduce cada una de forma independiente; la deduplicación es una transformación diferente y más riesgosa. DownsampleImages pasará por alto una imagen cuya estimación conservadora de PPP permanezca por debajo del umbral, incluso cuando un humano pueda notar que está sobredimensionada para su cuadro. And ninguna de las pasadas toca la estructura del documento, por lo que un archivo sobrecargado por miles de objetos huérfanos necesita un guardado de tipo reescritura en lugar de estas pasadas a nivel de flujo. Dentro de esos límites, la combinación de creación de subconjuntos de fuentes, reducción de resolución de imágenes y normalización de LZW a Flate elimina las tres fuentes clásicas de tamaño excesivo de PDF con una llamada de API predecible para cada una. Las tres funciones se distribuyen como parte de losLab PDF Library para Delphi, C# y VB.NET, junto con las API de fusión, extracción y representación analizadas anteriormente