Contar as páginas num arquivo digitalizado de 1,4 GB deveria ser uma operação barata. Mas chame LoadFromFile nesse ficheiro e ela deixa de ser barata: o HotPDF analisa os dados de referência cruzada e constrói um objeto em memória para cada um dos centenas de milhares de objetos indiretos do documento, e um processo de 32 bits atinge o limite de espaço de endereçamento de 2 GB a meio dessa análise. A operação que pretendia, a contagem de páginas, nunca necessitou de nenhum desses objetos. Precisava da árvore de páginas e de nada mais. Essa lacuna, entre o que um trabalho solicita e o que um carregamento completo entrega, é a razão de ser da API Direct File.
A API Direct File concede ao Delphi e ao C++Builder acesso ao PDF ao nível do ficheiro: contagem de páginas, cópias, desencriptação, anexos incrementais, lendo do disco apenas o que realmente necessitam em vez de reconstruir todo o modelo do documento na RAM. A competência consiste em fazer corresponder cada trabalho ao nível mais leve que o possa resolver. Acerte nessa correspondência e o serviço manterá uma utilização de memória estável para qualquer tamanho de input. Erre, e o primeiro ficheiro sobredimensionado deitará abaixo o processo de execução.
O que lhe custa um carregamento completo
O LoadFromFile não é o inimigo. Justifica a memória que consome: assim que a árvore estiver na RAM, o utilizador passa a ter acesso aleatório a cada página e a cada objeto, que é exatamente o que o InsertPagesFromDocument, o MovePage e a resserialização através de SaveLoadedDocument exigem. Não há atalhos para uma reestruturação genuína; é necessário manter o documento para o reorganizar.
O problema começa quando os tamanhos dos inputs não estão sob o seu controlo. Os uploads dos clientes, os resultados do digitalizador e os arquivos de há uma década ignoram tudo o que o seu conjunto de testes assumiu. Se carregar todos os inputs incondicionalmente, o seu limite de memória será ditado pelo maior ficheiro individual que alguém submeter. O tempo de análise acompanha a contagem de objetos, e a memória residente fixa-se em várias vezes o tamanho do ficheiro após a contagem das estruturas de objetos e dos fluxos descodificados, pelo que um gigabyte em disco pode significar múltiplos gigabytes residentes.
A recompilação para 64 bits elimina o limite do espaço de endereçamento, mas mantém a mesma fatura. O processo de execução continua a consumir segundos de CPU e um múltiplo do tamanho do ficheiro na RAM para responder a uma questão que a própria estrutura do ficheiro poderia ter respondido em milissegundos. Em cenários de concorrência, a matemática torna-se hostil: quatro carregamentos pesados a correr em simultâneo partilham o mesmo orçamento de memória, e o rendimento (throughput) desaba precisamente quando a fila está mais cheia e menos se pode dar a esse luxo.
Ler um ficheiro através de um handle
O nível de apenas leitura abre um ficheiro como um handle, responde a questões estruturais sobre o mesmo e fecha-o. Sem árvore de objetos, sem renderização de páginas, sem crescimento de memória com o input.
var
Pdf: THotPDF;
Handle, PageCount: Integer;
begin
Pdf := THotPDF.Create(nil);
try
Handle := Pdf.DAOpenFileReadOnly('archive-2026-06.pdf', '');
if Handle > 0 then
try
PageCount := Pdf.DAGetPageCount(Handle);
RouteByPageCount('archive-2026-06.pdf', PageCount);
finally
Pdf.DACloseFile(Handle);
end;
finally
Pdf.Free;
end;
end;
Três hábitos mantêm este nível fiável. Primeiro, verifique o valor de retorno. Um handle não positivo significa que a abertura falhou, e executar o DAGetPageCount num handle inválido é o tipo de bug que permanece oculto até ao dia em que um cliente envia um ficheiro corrompido. Segundo, emparelhe cada abertura bem-sucedida com o DACloseFile dentro de um bloco finally; um serviço com fuga de handles não crasha, apenas degrada-se com o tempo, o que é pior. Terceiro, respeite o que o parâmetro de palavra-passe realmente faz. O DAOpenFileReadOnly aceita uma, mas para inputs encriptados descamba silenciosamente para uma análise completa para ler a contagem de páginas, pelo que a garantia de memória estável evapora-se. Encaminhe os ficheiros protegidos primeiro através de DecryptFile e o resto do pipeline continuará a ser barato.
A mesma verificação serve também como uma porta de triagem. Os ficheiros aparecem com etiquetas incorretas, parcialmente carregados ou renomeados a partir de outro formato, e uma validação com DAOpenFileReadOnly rejeita tudo isto logo à entrada em milissegundos, associando o erro diretamente ao ficheiro problemático. A alternativa é permitir que um ficheiro inválido avance profundamente na fila de execução e rebente aí, onde descobrir qual o input que causou o problema pode custar uma tarde de trabalho.
Copiar, desencriptar e encriptar ficheiros completos
O segundo nível move e transforma ficheiros completos sem nunca expor a sua estrutura interna. Estas são as chamadas em que os pipelines de receção mais se apoiam.
// Structural copy: validate-and-move without parsing the object tree
Status := Pdf.DACopyFile('incoming\statement.pdf', 'verified\statement.pdf');
LogDirectFileStatus('copy', Status);
// Decrypt while copying: the Direct File route into protected inputs
Status := Pdf.DecryptFile('incoming\protected.pdf',
'verified\plain.pdf', 'batch-password');
LogDirectFileStatus('decrypt-copy', Status);
// Encrypt while copying: protect an output without a full load
Status := Pdf.EncryptFile('verified\statement.pdf',
'outbound\statement.pdf', 'owner-secret', '', aes256, [prPrint]);
LogDirectFileStatus('encrypt-copy', Status);
Cada chamada justifica o seu lugar. O DACopyFile é a cópia validada de uma pasta de quarentena para o armazenamento gerido: abre e indexa a estrutura do PDF à medida que avança, pelo que um input truncado ou que não seja PDF falha logo aqui, em vez de falhar três etapas mais à frente. O DecryptFile escreve uma cópia desencriptada através de um caminho de reescrita direta AES-256 que ignora a árvore de objetos sempre que o input o permite, sendo a contrapartida para ficheiros grandes do fluxo de desencriptação carregar-e-guardar abordado no artigo sobre encriptação AES-256. O EncryptFile faz o mesmo movimento no sentido inverso, aplicando proteção por palavra-passe durante uma cópia ao nível do ficheiro com os parâmetros de tipo de chave e permissão que o caminho em memória já utiliza.
Anexar alterações em vez de reescrever
A atualização incremental, definida na ISO 32000-1 §7.5.6, é o terceiro nível. Os bytes originais permanecem onde estão no disco e quaisquer objetos novos ou modificados são anexados a seguir aos mesmos, seguidos de uma nova secção de referência cruzada que se liga de volta à original. Para um arquivo de 900 MB que necessite de adicionar uma única página, o custo de escrita limita-se ao delta, e não ao ficheiro completo.
// Append an audit page to a large archive without rewriting it
Pdf.BeginIncrementalUpdate('archive-2026-06.pdf');
Pdf.AddPage;
Pdf.CurrentPage.SetFont('Arial', [], 10);
Pdf.CurrentPage.TextOut(50, 760, 0, 'Processed by intake service 2026-06-11');
Pdf.SaveIncrementalUpdate('archive-2026-06-stamped.pdf'); // original bytes + delta
Aqui importam dois pontos de disciplina. O BeginIncrementalUpdate tem de apontar para o ficheiro original, uma vez que os dados de referência cruzada anexados ligam-se de volta aos offsets de bytes dentro do mesmo. E o modelo é do tipo apenas anexo (append-only) por conceção: cada gravação incremental aumenta o ficheiro, nunca o reduz. Um documento com aposição de carimbo todas as noites irá expandir-se sem limite até que uma resserialização periódica, carregando-o e gravando-o de volta através de SaveLoadedDocument, o compacte. Essa mesma natureza de apenas anexo é o que torna a atualização incremental a única forma segura de alterar um documento assinado digitalmente, uma limitação analisada no artigo sobre assinaturas digitais e PAdES. O mecanismo de referência cruzada subjacente é detalhado no artigo sobre fluxos de objetos e atualizações incrementais.
Há uma armadilha nas gravações apenas anexo que escapa à maioria das revisões. Os bytes originais permanecem no ficheiro, legíveis para quem quiser procurar. Uma atualização incremental que "substitui" uma página não elimina a antiga; apenas a substitui na revisão atual, enquanto a revisão anterior continua lá, totalmente recuperável. Portanto, as atualizações incrementais são a ferramenta errada para remover conteúdo confidencial. Para descartar genuinamente o histórico que o destinatário nunca deverá ver, necessita de uma resserialização completa: LoadFromFile seguida de SaveLoadedDocument, que escreve apenas o estado atual e deixa as revisões ocultas para trás.
Fazer corresponder o nível à operação
A lógica de seleção é curta o suficiente para caber na sua cabeça, e compensa codificá-la como uma decisão de encaminhamento explícita no topo do pipeline, em vez de deixar que cada trabalho improvise o seu próprio caminho. A operação necessária dita o nível:
- Contar, inspecionar ou classificar abre um handle:
DAOpenFileReadOnly,DAGetPageCount,DACloseFile. - Mover, desencriptar ou encriptar um ficheiro completo permanece ao nível do ficheiro com
DACopyFile,DecryptFileouEncryptFile. - Reestruturar páginas ou fundir documentos exige o carregamento completo:
LoadFromFile, depoisInsertPagesFromDocumentorMovePage, e por fimSaveLoadedDocument. - Adicionar um pequeno delta a um ficheiro gigante ou assinado requer a chamada a
BeginIncrementalUpdatee gravação.
Os pipelines mistos beneficiam ao colocar um limite de tamanho antes do caminho de carregamento completo. Envie tudo o que exceda algumas centenas de megabytes através dos níveis Direct File, e reserve o carregamento completo para reestruturações genuínas num processo de 64 bits com um orçamento de memória real. O limite converte uma falha por falta de memória (out-of-memory) numa decisão de encaminhamento que pode monitorizar e afinar.
Independentemente do nível que trate de um trabalho, escreva o seu resultado para um nome temporário e renomeie-o para a posição final apenas após a validação do resultado. Um ficheiro parcialmente gravado com o nome definitivo parecerá idêntico a um correto para a etapa seguinte do pipeline, e as chamadas Direct File tornam essa validação barata: confirmar o resultado é apenas uma verificação de handle de uma linha.
A API Direct File é disponibilizada como parte do HotPDF Component para Delphi e C++Builder. A página do produto fornece a ligação para a referência completa de funções, incluindo as chamadas de atualização incremental apresentadas aqui.