El error dice Please load the document before using BeginDoc, y casi siempre aparece la segunda vez. El primer documento se escribe bien. Luego se le pide a la misma instancia de THotPDF que inicie un segundo, BeginDoc lanza la excepción y el mensaje apunta a cargar un documento, que es lo opuesto a lo que intenta hacer el código. La discrepancia entre el síntoma y el mensaje es lo que hace que este error se quede grabado. El verdadero tema es el ciclo de vida del componente, y una vez que eso queda claro, el error deja de ser misterioso

Una instancia de THotPDF es un documento, no una fábrica de documentos
El modelo mental tentador es que THotPDF es un objeto de servicio que se pone en marcha una vez y al que se le van enviando documentos, de la misma manera que se mantiene una conexión de base de datos abierta y se ejecutan consultas una tras otra. No es así. Una instancia modela un único documento en construcción, y su máquina de estados interna lleva la suposición de que recorre el camino una sola vez: desde vacío, a través de un documento abierto, hasta un fichero guardado. BeginDoc abre ese camino y marca la instancia como si tuviera un documento en curso. EndDoc serializa todo en FileName y lo cierra. Llamar de nuevo a BeginDoc en la misma instancia terminada le pide que vuelva a entrar en un estado que nunca abandonó limpiamente, y la guardia que se dispara es la cuyo mensaje menciona la carga, porque internamente las condiciones «listo para comenzar» y «tiene un documento cargado» se comprueban juntas
Así que el mensaje es engañoso, pero la guardia está haciendo su trabajo. Se niega a dejaros iniciar un documento nuevo encima de un componente que todavía cree que está a mitad de un documento. La solución no es derrotar a la guardia. Es dejar de reutilizar una instancia agotada
El ciclo de vida, en el orden en que debe ocurrir
Cada documento que HotPDF escribe desde cero sigue los mismos cuatro pasos, y el orden no es negociable. Create asigna el componente. BeginDoc abre el documento y fija las elecciones estructurales, de modo que todo lo que afecta al fichero completo (tamaño de página, compresión, cifrado, nombre del fichero de salida) debe establecerse entre Create y BeginDoc. Luego dibujáis. Luego EndDoc escribe los bytes en el disco. Free libera la instancia. Las llamadas de dibujo colocadas antes de BeginDoc no tienen página donde aterrizar; las propiedades de todo el documento asignadas después se ignoran sin queja
var
Pdf: THotPDF;
begin
Pdf := THotPDF.Create(nil);
try
Pdf.FileName := 'invoice.pdf';
Pdf.BeginDoc; // opens the document
Pdf.CurrentPage.SetFont('Arial', [], 11);
Pdf.CurrentPage.TextOut(50, 760, 0, 'Invoice 2026-042');
Pdf.EndDoc; // writes invoice.pdf, closes it out
finally
Pdf.Free; // one instance, one document
end;
end;
Entendedlo como la unidad de trabajo. Un Create, un BeginDoc, un EndDoc, un Free, un fichero en disco. En cuanto queréis un segundo fichero, estáis comenzando una nueva unidad de trabajo, lo que significa una nueva instancia
Lo que «reutilizar» debería significar: una instancia nueva por fichero
La versión que falla intenta ser frugal con la asignación de memoria: construir el componente una vez, iterar sobre un lote, llamar a BeginDoc y EndDoc dentro del bucle. La segunda iteración lanza la excepción. La versión que funciona trata cada salida como su propio objeto de corta duración, y el coste de crear un componente es insignificante comparado con el trabajo de componer y serializar un PDF, así que no hay nada que ganar acumulando la instancia
procedure WriteBatch(const Names: TArray<string>);
var
I: Integer;
Pdf: THotPDF;
begin
for I := 0 to High(Names) do
begin
Pdf := THotPDF.Create(nil); // new instance each pass
try
Pdf.FileName := Names[I] + '.pdf';
Pdf.BeginDoc;
Pdf.CurrentPage.SetFont('Arial', [], 12);
Pdf.CurrentPage.TextOut(50, 760, 0, 'Statement for ' + Names[I]);
Pdf.EndDoc;
finally
Pdf.Free;
end;
end;
end;
El try/finally dentro del bucle es la parte que vale la pena defender en una revisión de código. Si BeginDoc o cualquier llamada de dibujo lanza una excepción a mitad de un documento, la instancia de esa iteración se libera antes de que comience la siguiente, de modo que un registro defectuoso no deja abandonado un componente a medio construir ni envenena el resto de la ejecución. Sacar el Create fuera del bucle para «optimizar» os devuelve al error original, ahora envuelto en un bucle de lote
Modificar un fichero existente es un punto de entrada distinto
Existe una segunda lectura de «reutilizar» que es completamente legítima: no queréis un documento en blanco, sino abrir un PDF que ya existe y modificarlo. Ese camino no pasa por BeginDoc en absoluto, que es exactamente la razón por la que el mensaje de error menciona la carga. Cargáis el fichero, lo editáis y lo guardáis con el nombre que elijáis
var
Pdf: THotPDF;
PageCount: Integer;
begin
Pdf := THotPDF.Create(nil);
try
PageCount := Pdf.LoadFromFile('contract.pdf');
if PageCount > 0 then
begin
Pdf.CurrentPage.SetFont('Arial', [fsBold], 10);
Pdf.CurrentPage.TextOut(40, 30, 0, 'REVIEWED');
Pdf.SaveLoadedDocument('contract-reviewed.pdf');
end;
finally
Pdf.Free;
end;
end;
LoadFromFile devuelve el número de páginas, y un valor de cero o inferior significa que la carga ha fallado, por lo que conviene comprobarlo antes de tocar CurrentPage. El emparejamiento importa: un documento abierto con LoadFromFile se guarda con SaveLoadedDocument, no con el par BeginDoc/EndDoc, que corresponde a documentos creados desde cero. Mezclar los dos es la forma más habitual de confundir la misma máquina de estados que produjo el error original. Mantened los dos flujos separados mentalmente: BeginDoc ... EndDoc crea, LoadFromFile ... SaveLoadedDocument edita
El problema del bloqueo de ficheros es real, y la solución no es cerrar las ventanas del visor
El error de reutilización suele venir acompañado de una segunda queja, y los dos se mezclan porque afloran en el mismo flujo de trabajo de regeneración del fichero. Un usuario abre el PDF que acabáis de producir, lo deja abierto en Acrobat o Foxit, y luego desencadena una reconstrucción. EndDoc intenta escribir en la misma ruta, el sistema operativo lo rechaza porque el visor mantiene un acceso de lectura que bloquea la escritura, y se obtiene un error de acceso denegado. Este es genuinamente un problema de bloqueo de ficheros de Windows, no un problema del estado del componente, y merece una respuesta real en lugar de un parche
El remedio que circula, enumerar las ventanas de nivel superior y enviar WM_CLOSE a cualquiera cuyo título parezca el de un visor PDF, es el instinto equivocado. Cruza los límites de proceso para cerrar ventanas que vuestro programa no posee, adivina los visores por el texto del título y puede descartar las anotaciones no guardadas de un usuario sin pedirle permiso. Tratad todo ese enfoque como una señal de alarma. La solución fiable es no escribir nunca en una ruta que otro proceso pueda estar usando. Serializad en un fichero temporal en el mismo directorio y luego intercambiadlo mediante un renombrado atómico una vez que EndDoc tenga éxito. Si un visor todavía tiene el fichero antiguo abierto, el renombrado o tiene éxito sin problemas o falla de forma clara, y podéis mostrar un mensaje de error en lugar de luchar contra el bloqueo
Para un servidor de alto volumen que regenera documentos constantemente, la disciplina más limpia es escribir cada salida con un nombre único (una marca de tiempo o un identificador de tarea) de modo que dos ejecuciones nunca compitan por la misma ruta, y dejar que una política de retención independiente limpie los ficheros antiguos. En cualquier caso el principio es el mismo: diseñad de modo que el fichero que estáis escribiendo sea exclusivamente vuestro en el momento de escribirlo. El bloqueo desaparece no porque hayáis forzado el cierre de una ventana, sino porque nada más está tocando los bytes
La forma de la solución
Si se despojan los dos problemas hasta su raíz, ambos tienen que ver con respetar los límites. El error de la máquina de estados pide que respetéis el límite de instancia: un THotPDF, un documento, luego liberadlo y cread otro. El error de bloqueo de ficheros pide que respetéis el límite del fichero: escribid donde nada más esté leyendo y luego moved el resultado a su lugar definitivo. Ninguno requiere parchear la biblioteca ni automatizar el escritorio. Ambos se resuelven tratando cada documento como una unidad de trabajo autónoma, creada limpia, escrita correctamente y liberada, que es el mismo patrón que hace que el resto del componente sea predecible
Las llamadas BeginDoc, EndDoc, LoadFromFile y SaveLoadedDocument mostradas aquí forman parte del HotPDF Component para Delphi y C++Builder
Control de estado al reutilizar HotPDF
El punto crítico de este caso no es crear otro objeto, sino definir con claridad cuándo una instancia de HotPDF vuelve a estar en estado inicial. Si el flujo genera varios documentos en una misma sesión, cada ejecución debe cerrar el documento anterior, liberar recursos asociados y volver a establecer las propiedades que influyen en salida, fuentes, páginas y seguridad
En una aplicación real conviene tratar la reutilización como una política explícita. Para lotes cortos puede ser más claro crear y liberar una instancia por documento; para lotes largos puede ser razonable conservar una instancia, siempre que exista una rutina de reinicio probada. Esa rutina debe limpiar nombres de archivo, páginas pendientes, metadatos, manejadores de imagen, perfiles de fuente y cualquier estado de protección antes de comenzar el siguiente documento
La validación debe reproducir exactamente el fallo original: generar dos o más PDF consecutivos con datos distintos, abrirlos de nuevo, comprobar número de páginas, texto visible, recursos incrustados y tamaño de archivo, y repetir la prueba después de una excepción. Si el segundo documento hereda algo del primero, el problema no está resuelto aunque el ejemplo simple parezca funcionar