Un equipo de procesamiento de reclamos tenía treinta años de archivos en papel pasando por un escáner de hojas. El escáner arrojaba un JPEG por página en una carpeta, con nombres como 0001.jpg, 0002.jpg, y así sucesivamente. Lo que el archivo realmente necesitaba era un PDF por cada expediente (case file), con las páginas en orden, para que un revisor pudiera abrir un solo documento en lugar de hacer clic a través de cien miniaturas de imágenes. Ese último paso, convertir una pila numerada de escaneos en un solo PDF ordenado, es el trabajo que realizaremos aquí
PDFium Component lo maneja directamente. Más allá de renderizar y extraer texto, el componente puede construir un PDF desde cero: crear un documento vacío, agregar una página en blanco del tamaño que desee, colocar una imagen en esa página en coordenadas del espacio de usuario, y luego guardar. Toda la secuencia (pipeline) reside en el componente TPdf, por lo que un convertidor por lotes es un bucle sobre los nombres de archivo más un puñado de llamadas
La forma de la conversión
Deben suceder tres cosas por cada escaneo. Usted decide el tamaño de la página, coloca la imagen dentro de la página dejando un margen, y avanza a la siguiente página. PDFium Component le brinda un método para cada una: AddPage crea una página en blanco con un tamaño dado, AddImage (o AddPicture si ya tiene un TPicture) dibuja el mapa de bits (bitmap) en la página actual, y PageNumber le dice al componente a qué página se dirigen las siguientes llamadas de dibujo
El único detalle con el que la gente tropieza es el sistema de coordenadas. El espacio de usuario del PDF coloca el origen en la esquina inferior izquierda de la página, con Y aumentando hacia arriba, lo opuesto a las coordenadas de pantalla que los desarrolladores de Delphi usan por reflejo. El X, Y que pasa a AddImage es la esquina inferior izquierda del rectángulo de la imagen, y Width, Height son el tamaño de colocación en puntos, no el tamaño en píxeles del archivo de origen. Hágalo al revés y sus escaneos caerán fuera de la página o al revés en relación con donde esperaba que estuvieran
Crear el documento y una página por escaneo
Comience con un documento vacío. CreateDocument asigna un PDF nuevo y deja el componente activo, por lo que no hay un paso de apertura separado. A partir de ahí, recorre la lista de archivos escaneados y, por cada uno, agrega una página, la hace actual y coloca la imagen. Las dimensiones de la página aquí son A4 en puntos (595 × 842 en vertical), el tamaño de hoja estándar para la correspondencia archivada
procedure TArchiveForm.ScansToPdf(const Files: TStrings; const OutputPath: string);
const
PageW = 595.0; // Ancho A4 en puntos
PageH = 842.0; // Alto A4 en puntos
Margin = 36.0; // Borde de media pulgada alrededor de cada escaneo
var
I: Integer;
Pdf: TPdf;
begin
Pdf := TPdf.Create(nil);
try
Pdf.CreateDocument; // nuevo, vacío, ya activo
for I := 0 to Files.Count - 1 do
begin
Pdf.AddPage(I + 1, PageW, PageH); // índice de página basado en 1
Pdf.PageNumber := I + 1; // hace actual a la nueva página
PlaceScan(Pdf, Files[I], PageW, PageH, Margin);
end;
Pdf.SaveAs(OutputPath);
finally
Pdf.Free;
end;
end;
Cada iteración crea una página e inmediatamente establece PageNumber en ella. Esa segunda línea es importante: AddPage inserta la página, pero los métodos de dibujo actúan sobre cualquier página que sea actual, por lo que establecer PageNumber es lo que apunta AddImage a la página que acaba de hacer. Omítala y sus imágenes se apilarán en la página que haya estado cargada antes
Una suposición se esconde en ese bucle: el orden de Files. Un escáner nombra las páginas desde 0001.jpg hasta 0100.jpg, pero una enumeración de directorios no siempre las devuelve ordenadas, y en el momento en que se encuentra con page9.jpg junto a page10.jpg, una simple ordenación de cadenas (string sort) pone la página 10 antes que la página 9. Ordene la lista explícitamente antes del bucle, y prefiera nombres rellenados con ceros al momento de escanear para que el orden léxico coincida con el orden de las páginas. La secuencia de páginas es lo primero que nota un revisor, y es el error más barato de prevenir
Colocar un escaneo y mantener su relación de aspecto
Un escaneo rara vez tiene la misma forma que la página. Si lo estira para llenar la hoja, distorsiona el texto; si lo coloca a tamaño completo de píxeles, se desborda. La solución es escalar por la menor de las dos proporciones, ajuste al ancho o ajuste al alto, y centrar lo que sobra. Como el origen se encuentra en la parte inferior izquierda, centrar significa dividir el espacio sobrante en partes iguales y sumarlo tanto a X como a Y
procedure TArchiveForm.PlaceScan(Pdf: TPdf; const FileName: string;
PageW, PageH, Margin: Double);
var
Pic: TPicture;
AvailW, AvailH, Scale, DrawW, DrawH, X, Y: Double;
begin
Pic := TPicture.Create;
try
Pic.LoadFromFile(FileName); // BMP, JPG, PNG, etc. a través de unidades gráficas de la VCL
AvailW := PageW - 2 * Margin;
AvailH := PageH - 2 * Margin;
// Ajustar dentro de los márgenes sin distorsionar el escaneo.
Scale := Min(AvailW / Pic.Width, AvailH / Pic.Height);
DrawW := Pic.Width * Scale;
DrawH := Pic.Height * Scale;
// Centrar: espacio sobrante dividido en partes iguales. Y se mide desde el fondo de la página.
X := (PageW - DrawW) / 2;
Y := (PageH - DrawH) / 2;
Pdf.AddImage(FileName, X, Y, DrawW, DrawH);
finally
Pic.Free;
end;
end;
Esto carga el archivo una vez para leer sus dimensiones en píxeles, calcula una única escala uniforme y pasa el rectángulo de colocación a AddImage. AddImage acepta una ruta de archivo directamente y la enruta a través de la misma secuencia de imágenes que AddPicture, por lo que cualquier formato que las unidades de gráficos de la VCL reconozcan funciona sin tener que tratar casos especiales. Si ya tiene la imagen decodificada en un TPicture desde un panel de vista previa, llame a AddPicture(Pic, X, Y, DrawW, DrawH) con el mismo rectángulo y omita la segunda lectura de archivo
Omitir la decodificación para escaneos en JPEG
Los escáneres casi siempre emiten JPEG. Cargar un JPEG en un TPicture lo decodifica a un mapa de bits, y luego PDFium lo vuelve a codificar al guardar, dos procesos con pérdida (lossy round trips) que no necesita. AddJpegImage incrusta los bytes originales comprimidos directamente en la página desde un flujo (stream), lo cual es más rápido y visualmente más limpio para un lote de alto volumen
var
Stream: TFileStream;
begin
// ... después de AddPage + PageNumber para la página actual ...
Stream := TFileStream.Create(FileName, fmOpenRead);
try
// Incrusta los bytes del JPEG tal cual; sin ciclo de decodificación/recodificación.
Pdf.AddJpegImage(Stream, X, Y, DrawW, DrawH);
finally
Stream.Free;
end;
end;
Aún necesita calcular X, Y, DrawW y DrawH de la misma manera, ya que necesita las dimensiones en píxeles para escalar. Léalos del archivo o a través de un análisis rápido (parse) del encabezado, luego pase el flujo sin procesar (raw stream) a AddJpegImage. Para escaneos en PNG o TIFF, la ruta de AddImage es la correcta; reserve el atajo JPEG para el formato al que realmente se aplica
Etiquetar cada página
Los escaneos archivados son más fáciles de auditar cuando cada página lleva el nombre de su archivo fuente. AddText dibuja una cadena (string) en una coordenada del espacio de usuario, para que la leyenda se asiente justo debajo de la imagen. Recuerde el eje Y invertido: para poner una etiqueta debajo del escaneo, debe restar desde el borde inferior de la imagen en lugar de sumar
// Leyenda debajo del escaneo: Y disminuye hacia la parte inferior de la página.
Pdf.AddText('File: ' + ExtractFileName(FileName), 'Helvetica', 9,
X, Y - 14, clGray);
Un último punto sobre cómo guardar. SaveAs es una función que devuelve un Boolean, así que en código de producción verifique su resultado en lugar de asumir que la escritura tuvo éxito; un disco lleno o una ruta de salida bloqueada fallan silenciosamente de otra manera. Una vez que el bucle termina y el archivo está escrito, usted tiene exactamente lo que el archivo necesitaba: un PDF ordenado por expediente, páginas escaladas para ajustarse, listo para leer en cualquier visor
Los mismos componentes básicos cubren trabajos relacionados. Cambie la regla de tamaño por página y obtendrá un álbum de fotos con una imagen por hoja; mantenga el bucle pero lea desde un origen TIFF de varias páginas y tendrá un convertidor de archivo de fax. Si desea ver el panorama más amplio sobre la creación de archivos PDF mediante programación, consulte crear documentos PDF desde cero con PDFium Component; para renderizar el resultado de nuevo a la pantalla más adelante, consulte convertir páginas PDF en imágenes JPEG con PDFium Component
PDFium Component de loslab.com agrupa las API de creación de documentos, renderizado y texto utilizadas a lo largo de esta serie