PDFium Component expone la fusión de PDF a través de un único método: ImportPages. El patrón es siempre el mismo: crear un documento de destino vacío, abrir cada archivo fuente, llamar a ImportPages para copiar las páginas, cerrar la fuente y repetir. Cuando el bucle termina, SaveAs escribe el resultado en el disco. No hay un modo de fusión especial ni configuración que cambiar. La complejidad reside en los casos extremos, y hay algunos que pueden dar problemas sin previo aviso
El bucle principal
Dos instancias de TPdf son todo lo que necesita. Una contiene el documento de destino, creado vacío con CreateDocument. La otra abre cada archivo fuente por turno. A continuación, se muestra un procedimiento que toma una lista de rutas de archivos y escribe la salida fusionada en una única ruta:
procedure MergeFiles(const FileList: TStrings; const OutputPath: string);
var
PdfDest, PdfSrc: TPdf;
InsertAt, I: Integer;
begin
PdfDest := TPdf.Create(nil);
PdfSrc := TPdf.Create(nil);
try
PdfDest.CreateDocument;
InsertAt := 1; // ImportPages uses 1-based destination position
for I := 0 to FileList.Count - 1 do
begin
PdfSrc.FileName := FileList[I];
PdfSrc.Active := True;
if not PdfSrc.Active then
raise Exception.CreateFmt('Cannot open: %s', [FileList[I]]);
PdfDest.ImportPages(
PdfSrc,
'1-' + IntToStr(PdfSrc.PageCount), // full document range
InsertAt);
Inc(InsertAt, PdfSrc.PageCount);
PdfSrc.Active := False;
end;
PdfDest.SaveAs(OutputPath);
finally
PdfSrc.Free;
PdfDest.Free;
end;
end;
Hay dos cosas en ese código que son fáciles de pasar por alto en una primera lectura. La primera es cómo PDFium informa de los errores de carga. Active := True nunca lanza una excepción: si el archivo falta, está dañado o protegido por contraseña, PDFium captura el error internamente y deja Active como False. Sin la comprobación explícita en la línea 10, un archivo defectuoso se excluiría silenciosamente de la fusión sin ninguna indicación en la salida. El PDF final tendría menos páginas de las esperadas y usted no sabría qué archivo fue el culpable
La segunda es el contador InsertAt. El tercer argumento de ImportPages es la posición (basada en 1) en el destino donde aterriza la primera página importada. Comenzar en 1 coloca el primer documento fuente al principio de un archivo por lo demás vacío. Después de cada fuente, el contador avanza según PdfSrc.PageCount, por lo que el siguiente lote de páginas se agrega después del último. Si olvida incrementarlo, cada fuente subsiguiente sobrescribirá las páginas en la posición 1, dándole el último documento de la lista y nada más
Rangos de páginas selectivos
No es necesario tomar todas las páginas de una fuente. La cadena de rango que se pasa como segundo argumento sigue un formato simple de comas y guiones: "1-3" toma las páginas de la 1 a la 3, "2,4,6" selecciona tres páginas específicas y "1-" significa desde la página 1 hasta el final del documento. Los rangos se pueden combinar en una sola cadena, por lo que "1-3,5,7-" omite las páginas 4 y 6. Aquí importa una sutileza: los números siempre se refieren a las páginas en el documento fuente, comenzando en 1, independientemente de dónde terminen esas páginas en el destino. Si desea las páginas de la 40 a la 50 de un catálogo de 200 páginas, la cadena de rango es "40-50", no una posición relativa a lo que ya está en el destino
// Extract cover plus a three-page executive summary from a long report
PdfSrc.FileName := 'annual-report.pdf';
PdfSrc.Active := True;
if PdfSrc.Active then
begin
// Page 1 is the cover; pages 3-5 are the summary
PdfDest.ImportPages(PdfSrc, '1,3-5', InsertAt);
Inc(InsertAt, 4); // 1 cover + 3 summary pages = 4 pages added
PdfSrc.Active := False;
end;
Al calcular el incremento para InsertAt, cuente las páginas que realmente importó, no el conteo de páginas de la fuente. Si pasa '1,3-5', importó 4 páginas, así que avance 4. Avanzar por PdfSrc.PageCount dejaría un espacio de posiciones de destino en blanco y colocaría el siguiente documento fuente más adentro del archivo de lo previsto
Qué preserva ImportPages y qué no
Las páginas copiadas por ImportPages llevan su contenido visible intacto. El texto, los gráficos vectoriales, las imágenes de trama, las fuentes incrustadas y los XObjects de formulario se transfieren como parte de los flujos de contenido de la página. Las anotaciones a nivel de página, incluyendo comentarios, resaltados y trazos de tinta, también se transfieren porque se almacenan dentro del diccionario de la página en lugar de a nivel de documento
Los metadatos a nivel de documento son otra historia. Las cadenas de título, autor, asunto y palabras clave en el diccionario Info de la fuente se quedan atrás. El documento de destino comienza con metadatos vacíos después de CreateDocument, por lo que si la salida fusionada necesita que esos campos se completen, debe asignarlos a PdfDest directamente antes de llamar a SaveAs. Las propiedades Title, Author, Subject, Keywords y Creator en TPdf toman cadenas simples y las escriben en el diccionario Info al guardar
Los campos de formulario interactivos son más complicados. Las definiciones de campos AcroForm residen en un diccionario a nivel de documento en lugar de dentro de los flujos de páginas individuales. Cuando ImportPages copia una página que contiene campos de formulario, la apariencia visual de esos campos se transfiere porque se renderiza en el flujo de contenido de la página, pero los widgets de campo que los hacen interactivos son parte de la estructura de AcroForm y no se incluyen. En una fusión típica, un campo de texto de un documento fuente mostrará el valor que tenía en el momento de la importación, pero no será editable en el archivo fusionado. Si necesita que los campos sigan siendo rellenables, acóplelos (flatten) en cada documento fuente antes de importarlos: eso integra los valores actuales en el flujo de contenido y elimina la capa interactiva, dándole un resultado visual limpio sin widgets rotos en la salida
Archivos fuente cifrados
Los documentos fuente protegidos por contraseña se abren de la misma manera que los no cifrados, con una propiedad adicional que configurar primero. Asigne la contraseña a PdfSrc.Password antes de cambiar Active := True, y PDFium la usará durante la apertura:
PdfSrc.Password := 'user-password';
PdfSrc.FileName := 'protected.pdf';
PdfSrc.Active := True;
if not PdfSrc.Active then
raise Exception.Create('Wrong password or file cannot be opened');
PdfDest.ImportPages(PdfSrc, '1-' + IntToStr(PdfSrc.PageCount), InsertAt);
Inc(InsertAt, PdfSrc.PageCount);
PdfSrc.Active := False;
Una contraseña incorrecta provoca el mismo resultado silencioso de Active = False que un archivo faltante, por lo que la comprobación explícita es igual de necesaria aquí. El cifrado no se transfiere al destino: las páginas importadas de una fuente protegida aterrizan en el destino como contenido sin protección. Si la salida fusionada también necesita cifrado, configúrelo en PdfDest antes de llamar a SaveAs
Guardar el resultado
SaveAs en TPdf acepta una ruta de archivo o un TStream. Para la mayoría de las fusiones, la sobrecarga de archivo es lo que desea:
PdfDest.SaveAs('merged-output.pdf');
El segundo argumento opcional es un TSaveOption que controla el modo de guardado. El valor predeterminado, saNone, escribe una actualización incremental si el documento se cargó desde un archivo, o una reescritura completa si se creó de cero. Dado que un destino construido con CreateDocument siempre es nuevo, la salida será un archivo compacto de una sola revisión. El tercer argumento, TPdfVersion, le permite fijar el encabezado de la versión de PDF cuando tenga consumidores finales que requieran una versión específica; dejarlo en pvUnknown permite a PDFium elegir según el contenido
Los métodos ImportPages y SaveAs que se muestran aquí son parte del Componente PDFium para Delphi y C++Builder