O erro diz Please load the document before using BeginDoc, e surge quase sempre à segunda tentativa. O primeiro documento é gravado sem problemas. Depois, é solicitado à mesma instância THotPDF que inicie um segundo documento, o BeginDoc gera uma exceção e a mensagem aponta para o carregamento de um documento, o que é o oposto do que o código está a tentar fazer. A discrepância entre o sintoma e a mensagem é o que torna este problema persistente. O verdadeiro tema é o ciclo de vida do componente e, uma vez compreendido, o erro deixa de ser misterioso.

Uma instância THotPDF é um único documento, não uma fábrica de documentos
O modelo mental tentador é que o THotPDF seja um objeto de serviço que criamos uma vez e ao qual fornecemos documentos, da mesma forma que mantemos uma ligação a uma base de dados aberta e executamos consulta após consulta. Mas não é assim. Uma instância modela um único documento a ser construído, e a sua máquina de estados interna parte do princípio de que percorre o caminho uma única vez: de vazio, passando por um documento aberto, até a um ficheiro guardado. O método BeginDoc abre esse caminho e marca a instância como tendo um documento em curso. O método EndDoc serializa tudo para o FileName e fecha-o. Chamar BeginDoc novamente na mesma instância já finalizada pede-lhe para reentrar num estado do qual nunca saiu de forma limpa, e a validação que dispara é aquela cuja mensagem menciona o carregamento, porque internamente as condições "pronto para começar" e "tem um documento carregado" são verificadas em conjunto.
Portanto, a mensagem é enganadora, mas a validação está a cumprir a sua função. Recusa-se a permitir que inicie um novo documento sobre um componente que ainda acredita estar a meio de outro documento. A solução não passa por contornar a validação, mas sim por deixar de reutilizar uma instância já gasta.
O ciclo de vida, na ordem em que deve ocorrer
Cada documento que o HotPDF escreve do zero segue os mesmos quatro passos, e a ordem não é negociável. O método Create aloca o componente. O método BeginDoc abre o documento e define as escolhas estruturais, pelo que qualquer alteração que afete todo o ficheiro (tamanho da página, compressão, encriptação, nome do ficheiro de saída) deve ser configurada entre o Create e o BeginDoc. Em seguida, desenha-se. Depois, o método EndDoc escreve os bytes no disco. Por fim, o método Free liberta a instância. As chamadas de desenho feitas antes de BeginDoc não têm página onde ficar; as propriedades globais do documento atribuídas após o mesmo são ignoradas sem qualquer aviso.
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;
Considere isto como a unidade de trabalho. Um Create, um BeginDoc, um EndDoc, um Free, um ficheiro no disco. No instante em que pretender um segundo ficheiro, estará a iniciar uma nova unidade de trabalho, o que significa uma nova instância.
O que "reutilização" deveria significar: uma nova instância por ficheiro
A versão que falha tenta ser económica com a alocação: constrói o componente uma vez, percorre um lote num ciclo e chama BeginDoc e EndDoc dentro desse ciclo. A segunda iteração falha. A versão que funciona trata cada saída como o seu próprio objeto de curta duração, e o custo de alocação de criar um componente é insignificante em comparação com o trabalho de estruturar e serializar um PDF, pelo que não se ganha nada em reter a instância.
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;
O bloco try/finally dentro do ciclo é a parte que vale a pena defender numa revisão de código. Se o BeginDoc ou qualquer chamada de desenho gerar uma exceção a meio de um documento, a instância dessa iteração continuará a ser libertada antes do início da seguinte, pelo que um registo com problemas não bloqueia um componente construído a meio nem corrompe o resto da execução. Se retirar o Create para fora do ciclo com o intuito de "otimizar", voltará ao bug original, agora disfarçado de ciclo em lote.
Modificar um ficheiro existente é um ponto de entrada diferente
Existe uma segunda interpretação de "reutilização" que é inteiramente legítima: não quer um documento em branco, quer abrir um PDF que já existe e modificá-lo. Esse caminho não passa de todo pelo BeginDoc, e é exatamente por isso que a mensagem de erro menciona o carregamento. Carrega o ficheiro, edita-o e guarda-o com o nome que escolher.
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;
O método LoadFromFile devolve o número de páginas, e um valor igual ou inferior a zero significa que o carregamento falhou, pelo que vale a pena verificar antes de aceder a CurrentPage. A associação correta é importante: um documento aberto com LoadFromFile deve ser guardado com SaveLoadedDocument, e não com o par BeginDoc/EndDoc, que se destina a documentos criados do zero. Misturar os dois fluxos é a forma mais comum de confundir a máquina de estados que gerou o erro original. Mantenha os dois fluxos mentalmente separados: BeginDoc ... EndDoc cria; LoadFromFile ... SaveLoadedDocument edita.
O problema do bloqueio de ficheiros é real, e a solução não é fechar as janelas do visualizador
O erro de reutilização surge frequentemente acompanhado de uma segunda queixa, e os dois misturam-se porque surgem no mesmo fluxo de trabalho de regeneração de ficheiros. Um utilizador abre o PDF que acabou de gerar, deixa-o aberto no Acrobat ou Foxit, e depois aciona uma nova compilação. O EndDoc tenta escrever no mesmo caminho, o sistema operativo recusa porque o visualizador mantém uma partilha de leitura que bloqueia a escrita, e obtém uma falha de acesso negado. Este é genuinamente um problema de bloqueio de ficheiros do Windows, e não um problema de estado do componente, e merece uma resposta real em vez de um atalho.
A solução alternativa que costuma circular (listar as janelas de topo e enviar WM_CLOSE a qualquer uma cujo título se assemelhe a um visualizador de PDF) é um mau instinto. Intervém fora dos limites do processo para fechar janelas que o seu programa não possui, adivinha os visualizadores pelo texto do título e pode descartar anotações não guardadas do utilizador sem perguntar. Trate toda essa abordagem como um mau sinal de design. A solução fiável é nunca escrever num caminho que outro processo possa estar a utilizar. Serialize para um ficheiro temporário no mesmo diretório e, em seguida, substitua-o pelo ficheiro final com um renomeamento atómico assim que o EndDoc for concluído com sucesso. Se um visualizador ainda tiver o ficheiro antigo aberto, o renomeamento será bem-sucedido ou falhará claramente, permitindo apresentar uma mensagem explícita em vez de combater o bloqueio.
Para um servidor de elevado volume que gera documentos constantemente, a melhor prática é escrever cada saída sob um nome único (uma marca temporal ou um ID de tarefa) para que duas execuções nunca disputem o mesmo caminho, deixando que uma política de retenção separada elimine os ficheiros antigos. De qualquer forma, o princípio é o mesmo: desenhe o sistema para que o ficheiro que está a escrever seja apenas seu no momento da escrita. O bloqueio desaparece não porque forçou o fecho de uma janela, mas porque mais nada está a aceder aos bytes.
A estrutura da solução
Reduzindo os dois problemas à sua origem, ambos se resumem ao respeito pelos limites. O erro da máquina de estados exige que respeite o limite da instância: um THotPDF, um documento, e depois libertá-lo e criar outro. O erro de bloqueio de ficheiro exige que respeite o limite do ficheiro: escrever onde mais nada está a ler e, em seguida, mover o resultado para o local definitivo. Nenhum deles exige correções na biblioteca ou automatizações no ambiente de trabalho. Ambos resultam de tratar cada documento como uma unidade de trabalho independente, criada de raiz, escrita de forma limpa e libertada, que é o mesmo padrão que torna previsível o resto do componente.
As chamadas de BeginDoc, EndDoc, LoadFromFile e SaveLoadedDocument aqui apresentadas fazem parte do Componente HotPDF para Delphi e C++Builder.