O HotPDF Component consegue pesquisar e substituir texto dentro de um PDF existente a partir de Delphi e C++Builder. O SearchLoadedPageText e o SearchLoadedDocumentText localizam cada ocorrência de uma string com precisão ao nível dos glifos, e o ReplaceLoadedPageText e o ReplaceLoadedDocumentText reescrevem os bytes correspondentes no local — desde que cada caráter de substituição possa ser novamente codificado através do tipo de letra original, uma restrição física que este artigo trata com honestidade em vez de a esconder numa nota de rodapé
O pedido subjacente a esta funcionalidade é sempre comum. Uma empresa muda de nome e três mil faturas arquivadas ainda contêm a designação antiga. Um modelo de contrato foi distribuído com a data de validade do ano passado. Um código de produto foi descontinuado e cada ficha técnica que o menciona necessita do código do sucessor. Num processador de texto, cada uma destas tarefas demora trinta segundos. Num PDF, constitui um problema genuinamente difícil, e compreender o motivo faz a diferença entre utilizar corretamente a API e registar um relatório de erro que, na verdade, corresponde apenas a uma especificação da norma
Por que razão a substituição de texto num PDF é tão difícil?
Substituir texto num PDF é difícil porque uma página PDF não contém texto editável — contém glifos posicionados. Sob o modelo de exibição de texto da norma ISO 32000-1 §9.4, um fluxo de conteúdo direciona operadores como Tj and TJ que desenham sequências de códigos de carateres em coordenadas estabelecidas pela matriz de texto. Esses códigos não são Unicode; são índices de qualquer codificação que o tipo de letra da página declare, e o mapeamento de volta para carateres legíveis pode residir num CMap /ToUnicode, num array de diferenças de codificação ou numa cadeia de mapeamento CID. Não existe um objeto de parágrafo, nem fluxo de texto, nem garantia de que uma palavra visual seja armazenada como uma única string
A substituição adiciona uma segunda camada de dificuldade à descodificação: deve saber exatamente quais os bytes do fluxo original que produziram cada glifo, para poder inserir novos bytes precisamente nesse intervalo e em nenhum outro local. Um extrator de texto pode dar-se ao luxo de descartar as posições dos bytes assim que obtém o Unicode. Um substituidor não o pode fazer. Foi por isso que o HotPDF dividiu o trabalho em duas versões — a v2.251.0 construiu a camada de rastreio de desvios e pesquisa, e a v2.252.0 desenvolveu a camada de reescrita sobre ela
Localizar texto: pesquisa ao nível dos glifos com rastreio de desvios de bytes
O SearchLoadedDocumentText do HotPDF localiza cada ocorrência de um termo pesquisado comparando-o com a sequência de glifos Unicode descodificada de cada página, e não com bytes de fluxo bruto, pelo que uma correspondência é exata independentemente de como o tipo de letra a codificou. A infraestrutura subjacente foi introduzida na v2.251.0: o tokenizador de fluxo de conteúdo regista um intervalo de bytes StartOfs/EndOfs para cada operando de string — incluindo os seus delimitadores ( ) ou < > — e cada glifo descodificado transporta um trio TokenIndex/ItemIndex/ByteOffset que aponta para o operando exato, item do array TJ e unidade de código que o produziu. O mesmo intérprete de glifos baseia a API de extração descrita em extrair texto de um PDF carregado no Delphi; a pesquisa apenas mantém a proveniência que a extração descarta
Cada correspondência é devolvida como um registo THPDFTextMatch que contém o índice da página, o intervalo de glifos inclusivo, a origem X/Y no espaço do utilizador e a largura da ocorrência, o token de origem e o índice do item, e o próprio texto correspondente. Isto é suficiente para direcionar uma sobreposição de destaque, uma interface de revisão ou o passo de substituição. Uma pesquisa que não encontre nada devolve um array vazio em vez de falhar, mantendo simples o padrão de chamada
var
Pdf: THotPDF;
Matches: THPDFTextMatchArray;
I: Integer;
begin
Pdf := THotPDF.Create(nil);
try
if Pdf.LoadFromFile('invoices-2025.pdf') > 0 then
begin
if Pdf.SearchLoadedDocumentText('Acme Corp', False, Matches) then
for I := 0 to Length(Matches) - 1 do
WriteLn(Format('page %d at (%.1f, %.1f): "%s"',
[Matches[I].PageIndex, Matches[I].X, Matches[I].Y,
Matches[I].Text]));
end;
finally
Pdf.Free;
end;
end;
Uma escolha de design deliberada merece uma nota. Quando CaseSensitive é False, a comparação converte maiúsculas e minúsculas apenas para carateres ASCII, por conceção: a conversão completa em Unicode comporta-se de forma diferente entre as ferramentas do Delphi 5 ao XE que o HotPDF suporta, e uma API de pesquisa que encontre correspondências diferentes dependendo de qual compilador construiu a sua aplicação é pior do que uma com um limite documentado e previsível. Para texto comercial latino — nomes, códigos, datas — a conversão ASCII cobre os cenários práticos
Substituir texto: codificação inversa e junção cirúrgica
O ReplaceLoadedDocumentText, adicionado no HotPDF v2.252.0, reescreve cada ocorrência de um termo pesquisado executando o mecanismo de descodificação ao contrário. A função HPDFEncodeUnicode é o inverso do descodificador de códigos de carateres: percorre a mesma cadeia de estratégias em sentido inverso — pesquisa /ToUnicode em bfchar e bfrange, mapeamento CID de fluxo de codificação, mapeamentos de identidade Type0 e as tabelas predefinidas WinAnsi e MacRoman — para transformar cada caráter de substituição de volta nos bytes de código de caráter que o tipo de letra original espera. Os bytes codificados de novo são então serializados num literal de string bem-formado ou numa string hexadecimal, refletindo as próprias regras de escape do tokenizador para que o ciclo análise → reserialização seja estável
A junção em si é cirúrgica e não global. Apenas o intervalo de bytes de código coberto pela correspondência é substituído dentro do operando da string; os bytes não correspondidos no mesmo operando, o espaço em branco entre tokens e todos os operadores envolventes são preservados textualmente, byte a byte. Substituir bca dentro de abcabc resulta em a + substituição + bc, e não num operando corrompido. As substituições podem ser mais curtas ou mais longas do que o termo pesquisado — o literal é serializado de novo e o /Length do fluxo é atualizado — e cada fluxo /Contents de uma página multifluxo é processado isoladamente para que a página permaneça bem-formada
var
Pdf: THotPDF;
ReplaceCount: Integer;
begin
Pdf := THotPDF.Create(nil);
try
if Pdf.LoadFromFile('contract-draft.pdf') > 0 then
begin
if Pdf.ReplaceLoadedDocumentText('2025-12-31', '2026-12-31',
True, ReplaceCount) then
WriteLn(Format('%d operand rewrites performed', [ReplaceCount]));
Pdf.SaveLoadedDocument('contract-final.pdf');
end;
finally
Pdf.Free;
end;
end;
Note o que a API não faz: não volta a estruturar a página. O PDF não tem reprocessamento de texto (reflow), pelo que uma substituição que seja visualmente mais larga do que a original ocupará simplesmente mais espaço horizontal e poderá sobrepor-se ao que estiver desenhado à sua direita. Substituições com o mesmo tamanho ou tamanho aproximado — datas, strings de versão, números de peça, correções de nomes — constituem o cenário ideal. Reformulações globais pertencem ao documento original, e não ao PDF
Por que razão não se pode substituir texto por carateres que o subconjunto do tipo de letra nunca incluiu?
Não se pode substituir texto por um caráter que o subconjunto do tipo de letra incorporado nunca tenha incluído, porque a sequência de bytes que selecionaria esse caráter simplesmente não existe nas tabelas de mapeamento do tipo de letra. Quando um gerador de PDF incorpora um tipo de letra de subconjunto, o seu CMap /ToUnicode e estruturas de codificação abrangem apenas os glifos que o documento original realmente utilizou. O HPDFEncodeUnicode apenas consegue inverter um mapeamento que esteja presente: se o documento nunca tiver contido a letra E nesse tipo de letra, não existe um código de caráter para E ao qual reverter. Esta é uma propriedade física do ficheiro, e não uma limitação de uma biblioteca específica — nenhuma ferramenta pode criar um mapeamento de glifo que nunca tenha sido incorporado
O HotPDF lida com a falha de forma conservadora. Se um único caráter da substituição não puder ser codificado de novo, essa ocorrência do termo pesquisado é totalmente ignorada — sem exceções, sem texto parcial corrompido, e a ocorrência simplesmente não é contabilizada no ReplaceCount. A consequência prática: compare o ReplaceCount com a contagem de correspondências de uma pesquisa anterior e trate qualquer diferença como um sinal. No exemplo de data acima, o dígito 6 deve surgir em algum ponto do texto do documento com esse mesmo tipo de letra para que a reescrita seja bem-sucedida — provável numa fatura, mas nunca garantido de forma geral. Quando os carateres de que necessita simplesmente não estão disponíveis e o objetivo é remover texto sensível em vez de reformular a frase, la remoção real do conteúdo constitui de qualquer modo a melhor ferramenta; consulte o artigo sobre ocultação de informação (redaction) e reestruturação de PDFs carregados no Delphi para essa via
var
Matches: THPDFTextMatchArray;
Expected, Replaced: Integer;
begin
Pdf.SearchLoadedDocumentText('Acme Corp', True, Matches);
Expected := Length(Matches);
Pdf.ReplaceLoadedDocumentText('Acme Corp', 'Apex Corp', True, Replaced);
if Replaced < Expected then
WriteLn(Format('%d occurrence(s) skipped: characters missing ' +
'from the font subset, or match spans multiple operands',
[Expected - Replaced]));
end;
A segunda condição de omissão nessa mensagem é o outro limite documentado: um termo de pesquisa que abranja múltiplos operandos de string — por exemplo, Hello dividido entre itens [(He)(llo)] TJ — é localizado pela pesquisa, porque esta faz corresponder a sequência de glifos descodificada, mas é ignorado pela substituição, porque reescrever além dos limites do operando exigiria fundir intervalos de bytes adjacentes. Pesquisar e depois verificar torna ambos os limites visíveis em vez de silenciosos
O que muda no ficheiro ao gravar?
Um fluxo /Contents substituído é gravado descompromido. Os fluxos comprimidos com FlateDecode são descomprimidos para edição e, quando o HotPDF escreve os bytes reconstruídos, remove a entrada /Filter do fluxo e atualiza o /Length em vez de voltar a comprimir. O PDF resultante é totalmente válido e é renderizado normalmente nos visualizadores comuns; a contrapartida é um ficheiro maior para cada fluxo editado. Para um processamento em lote que lide com milhares de documentos, planeie esse crescimento ou execute uma passagem de compressão separada a jusante. A forma como os objetos reescritos interagem com a estrutura de referências cruzadas do documento ao gravar constitui um tema próprio, abordado em fluxos de objetos e atualizações incrementais no HotPDF
Tudo o resto no ficheiro permanece inalterado. Os fluxos não modificados mantêm a sua compressão, os tipos de letra e imagens não são reescritos, e a junção ao nível do operando significa que mesmo os fluxos editados diferem do original apenas onde ocorreu uma correspondência. Esse conservadorismo é intencional: quanto mais um documento carregado for reescrito por uma biblioteca, mais oportunidades surgirão para quebrar alguma particularidade de criação do ficheiro que não tenha sido antecipada
A pesquisa e substituição de texto junta-se à extração, ocultação de informação (redaction) e renderização de páginas no conjunto de ferramentas para documentos carregados do HotPDF, todas baseadas no mesmo intérprete de fluxo de conteúdo e disponíveis desde o Delphi 5 até às versões atuais do RAD Studio sem dependências externas. A referência completa da API e a versão experimental para descarregar encontram-se na página do produto HotPDF Component