As atualizações incrementais de PDF permitem que uma aplicação Delphi modifique um documento anexando apenas os objetos alterados, deixando cada byte original intocado. A losLab PDF Library implementa isso por meio do AppendToStream, que grava apenas a seção incremental definida pela ISO 32000-1 §7.5.6, de modo que a edição de um único marcador em um arquivo de 2 GB custe kilobytes de saída em vez de uma regravação completa. Esse mesmo mecanismo é a razão pela qual documentos assinados podem ser atualizados sem invalidar suas assinaturas
O problema que isso resolve é concreto. Um salvamento completo regrava todo o arquivo: cada objeto é desserializado novamente, cada deslocamento (offset) de referência cruzada é recalculado e a saída não mantém nenhuma relação a nível de byte com a entrada. Para uma fatura de 40 KB, isso é aceitável. Para um arquivo digitalizado de 2 GB onde você apenas corrigiu um erro de digitação no título do documento, regravar dois gigabytes para alterar vinte bytes é absurdo — e se o arquivo contivesse uma assinatura digital, a regravação simplesmente a destruiria
Por que salvar um PDF corrompe sua assinatura digital?
Uma assinatura digital de PDF não assina o conteúdo lógico do documento; ela assina intervalos de bytes (byte ranges) do arquivo físico. A entrada /ByteRange no dicionário de assinaturas registra exatamente quais trechos do arquivo o resumo criptográfico (digest) cobre. Qualquer operação de salvamento que desserializar novamente esses bytes — mesmo uma que produza um documento semanticamente idêntico — altera o resumo criptográfico, e todo validador relará a assinatura como corrompida. Isso ocorre por design: a assinatura atesta os bytes que o signatário viu, não um modelo abstrato de documento
As atualizações incrementais são a válvula de escape que a especificação do PDF fornece. Como um salvamento incremental anexa novos dados após o %%EOF original e nunca toca nos intervalos de bytes assinados, a assinatura existente continua validando em relação aos bytes que ela cobre. Os validadores então classificam as alterações anexadas separadamente — uma segunda assinatura, um preenchimento de formulário, uma anotação — e decidem se são modificações permitidas. Todo fluxo de trabalho de múltiplas assinaturas depende disso: cada signatário adiciona uma seção incremental sobre a anterior. Se você estiver criando pipelines de assinatura, o artigo complementar sobre assinatura e validação PAdES no Delphi aborda detalhadamente como os intervalos de bytes de assinatura e as seções incrementais interagem
Como funcionam as atualizações incrementais sob a ISO 32000-1 §7.5.6
A ISO 32000-1 §7.5.6 define o modelo em três regras. Primeiro, o conteúdo original do arquivo é deixado inteiramente intacto — nenhum byte é movido. Segundo, os objetos alterados e os recém-criados são anexados após o último %%EOF, cada um com o mesmo número de objeto que tinha antes (objetos alterados simplesmente obtêm uma definição mais recente que sombreia a antiga). Terceiro, uma nova seção de referência cruzada (cross-reference) e um trailer são anexados; a entrada /Prev do trailer aponta de volta para o deslocamento de byte (byte offset) da seção de referência cruzada anterior, formando uma cadeia pela qual o leitor caminha do mais novo ao mais antigo para resolver cada objeto em sua definição mais recente
Duas propriedades úteis derivam dessa estrutura. As atualizações são baratas na proporção do que foi alterado, e não do tamanho do documento — o custo de anexação é o tamanho dos objetos modificados mais uma pequena sobrecarga de xref/trailer. E o arquivo se torna seu próprio histórico de versões: cada revisão anterior ainda está fisicamente presente, de modo que um auditor pode truncar o arquivo em qualquer %%EOF anterior e recuperar exatamente o documento que existia naquele ponto. Para fluxos de trabalho de conformidade que precisam provar como era a aparência de um documento antes de cada alteração, essa trilha de auditoria integrada costuma ser o argumento decisivo para salvamentos incrementais
Gravando uma atualização incremental com o AppendToStream
A losLab PDF Library expõe a saída incremental por meio de AppendToStream(AppendMode: Integer; OutStream: TStream): Integer, que retorna 1 em caso de sucesso e 0 em caso de falha. O parâmetro AppendMode seleciona o que vai para o fluxo (stream) de destino. O Modo 0 grava um arquivo completo: os bytes originais da origem são copiados primeiro para o fluxo e, em seguida, a seção incremental é anexada. O Modo 1 grava apenas a própria seção incremental — o delta — e ignora completamente os bytes de origem. O Modo 2 primeiro grava um prefixo fornecido pelo chamador registrado via SetAppendInputFromString e, em seguida, anexa a seção de atualização sobre ele
var
Doc: TPDFlib;
Delta: TMemoryStream;
begin
Doc := TPDFlib.Create;
try
if Doc.LoadFromFile('contract.pdf', '') <= 0 then
Exit;
// Small edit: the kind of change that should not
// trigger a rewrite of the whole file
Doc.SetInformation(3, 'Amended 2026-07-04'); // key 3 = /Subject
Delta := TMemoryStream.Create;
try
// AppendMode = 1: write only the incremental section.
// Original bytes + Delta = a complete, valid PDF.
if Doc.AppendToStream(1, Delta) = 1 then
Delta.SaveToFile('contract.delta.bin');
finally
Delta.Free;
end;
finally
Doc.Free;
end;
end;
O Modo 1 é o mais interessante para o design do sistema. Como o delta é autônomo, você pode enviá-lo independentemente do original: armazene revisões como blobs separados no armazenamento de objetos, replique apenas deltas para um site remoto ou reconstrua qualquer revisão concatenando o arquivo base com sua cadeia de incrementos. A regra de reconstrução é a concatenação simples de bytes — o arquivo original primeiro, depois cada delta em ordem — porque esse é exatamente o layout que a seção 7.5.6 prescreve para um arquivo atualizado de forma incremental
Como a biblioteca calcula os deslocamentos de xref sem copiar o arquivo original?
As entradas de referência cruzada dentro de uma seção incremental devem conter deslocamentos de bytes (byte offsets) absolutos — posições medidas a partir do início do arquivo completo, não a partir do início do delta. Isso cria um quebra-cabeça para o modo 1: o gravador nunca emite os bytes originais, mas cada deslocamento que ele registra precisa fingir que eles estão lá. A losLab PDF Library resolve isso com um adaptador de fluxo interno, o TPDFAppendSectionStream, que apresenta um espaço de coordenadas virtuais para o serializador. O adaptador é criado com o comprimento de bytes do arquivo original como seu deslocamento base, relata sua posição e tamanho como essa base mais o que quer que tenha sido anexado até o momento, e encaminha apenas os bytes recém-gravados para o fluxo de destino do chamador
A consequência é que o modo 1 nunca materializa uma cópia do documento de origem — nem em disco, nem em memória. A implementação ingênua (gravar o arquivo completo em um buffer temporário e depois cortar a cauda) carregaria uma cópia transitória de todo o PDF original, o que, para entradas em escala de gigabytes, é precisamente o custo que as atualizações incrementais existem para evitar. Essa técnica de virtualização de deslocamento é prima próxima do deslocamento de referência de byte (byte-reference shifting) usado em outras partes da biblioteca; o artigo sobre mesclagem rápida de PDF com deslocamento de referência de byte mostra a mesma ideia aplicada à combinação de documentos, e o guia sobre mesclagem e divisão de grandes PDFs com acesso direto a arquivos aborda a arquitetura de E/S subjacente para arquivos que não cabem confortavelmente na RAM
Salvamento completo em fluxo com o SaveToStream
A saída incremental é metade da história do fluxo; la outra metade é o que acontece em um salvamento completo. O SaveToStream na losLab PDF Library direciona o serializador de documentos diretamente contra o fluxo (stream) de destino, em vez de renderizar primeiro o documento inteiro em uma AnsiString intermediária e depois gravar esse buffer em uma única chamada. A abordagem anterior funcionava, mas significava que cada salvamento completo mantinha transitoriamente uma segunda cópia completa da saída na memória — inofensiva com 10 MB, incômoda com 500 MB e uma barreira intransponível para saídas de vários gigabytes em processos de 32 bits. A serialização direta faz com que o pico de memória rastreie as estruturas de objetos do documento em vez de seu comprimento serializado
var
Doc: TPDFlib;
Output: TFileStream;
begin
Doc := TPDFlib.Create;
try
if Doc.LoadFromFile('archive.pdf', '') <= 0 then
Exit;
// ... edits that justify a full rewrite ...
Output := TFileStream.Create('archive-rewritten.pdf', fmCreate);
try
if Doc.SaveToStream(Output) = 0 then
Writeln('Save failed, error ', Doc.LastErrorCode);
finally
Output.Free;
end;
finally
Doc.Free;
end;
end;
Uma lição sobre modo de compartilhamento: quando AppendToFile retornava 0
Vale a pena recontar uma regressão nessa área porque o padrão de falha se generaliza. O AppendToFile(FileName) anexa uma atualização incremental diretamente a um PDF existente no disco — a chamada natural para um fluxo de trabalho de trilha de auditoria no próprio local: carregar um arquivo, fazer uma alteração e anexar ao mesmo caminho. Na versão 3.71.2, essa sequência exata começou a retornar 0. A causa raiz estava no carregador (loader), não no gravador: para suportar a leitura sob demanda de documentos grandes, o LoadFromFile mantém o identificador (handle) do arquivo de origem aberto durante o tempo de vida do objeto do documento, e esse identificador era aberto com fmShareDenyWrite. Quando o AppendToFile tentava reabrir o mesmo arquivo para gravação, o próprio modo de compartilhamento do carregador o negava, e a API falhava antes de gravar um único byte
A correção relaxou o modo de compartilhamento do carregador para fmShareDenyNone, o que é seguro precisamente pelo que é uma anexação incremental: ela adiciona bytes estritamente após o fim do arquivo e nunca regravar a região que o identificador de longa duração do leitor está servindo. A lição geral para quem encapsula esta biblioteca — ou constrói carregadores de fluxo semelhantes — é que leitores lentos que retêm identificadores (lazy readers) e gravadores do mesmo arquivo estão em tensão, e o modo de compartilhamento que você escolhe no momento da abertura é um contrato de API, não um detalhe de implementação. Se o AppendToFile retornar 0 em seu código, verifique primeiro se algo mais em seu processo ainda retém o arquivo de destino com um modo de compartilhamento restritivo
Os custos reais: quando as atualizações incrementais são a ferramenta errada
As atualizações incrementais trocam o tamanho do arquivo pela eficiência de gravação, e essa troca nem sempre é favorável. Cada revisão anexa seus objetos alterados enquanto as definições substituídas permanecem no arquivo, de modo que um documento editado centenas de vezes acumula objetos mortos e uma longa cadeia /Prev pela qual cada leitor deve caminhar. Pior ainda, o conteúdo "excluído" não desapareceu: o texto removido na revisão cinco ainda está fisicamente presente nos bytes da revisão quatro, recuperável por qualquer pessoa que trunque o arquivo. A eliminação (redaction), higienização ou qualquer remoção de conteúdo sensível, portanto, exige uma regravação completa — um salvamento incremental de uma eliminação é um vazamento de dados com etapas adicionais
Um salvamento completo também é a escolha certa quando o objetivo é a compactação (eliminação de incrementos acumulados e objetos não utilizados), ao alterar propriedades de todo o documento, como criptografia — a criptografia novamente afeta cada string e fluxo, de modo que não resta nada de "incremental" na alteração — ou ao produzir um entregável limpo no qual o histórico de edição não deve viajar com o arquivo. Uma regra razoável: use AppendToStream ou AppendToFile enquanto um documento estiver ativo e mudando, especialmente depois de conter assinaturas; use uma regravação completa com SaveToStream nos limites do ciclo de vida, quando o documento sair do seu sistema ou quando seu histórico precisar ser nivelado (flattened)
As atualizações incrementais, a saída delta de deslocamento virtual e a serialização direta em fluxo fazem parte da losLab PDF Library padrão para Delphi, C# e VB.NET; a página do produto lista a interface de API completa de salvamento e anexação, juntamente com os recursos de assinatura e arquivos grandes discutidos acima