Redação real de PDF no Delphi significa apagar os bytes subjacentes, não pintar por cima deles. A losLab PDF Library traça essa linha com duas APIs: RedactRegion edita o content stream da página para que o texto, os vetores e as imagens removidos sumam fisicamente, e SanitizeDocument limpa os metadados, os scripts e as ações onde a informação suprimida também se esconde. Erre qualquer uma das duas metades e você publica um arquivo que parece censurado na tela, mas devolve o segredo a quem copiar o texto ou abrir as propriedades do documento
Esse modo de falhar é de conhecimento público. Jornais, tribunais e órgãos de governo já publicaram PDFs com um retângulo preto sobre um nome ou um número, e bastou um leitor selecionar o texto embaixo, copiar e colar para que a string supostamente suprimida aparecesse inteira. O retângulo era um desenho. O texto continuava ali, no content stream, uma camada abaixo da tinta, íntegro e perfeitamente selecionável. A losLab PDF Library, o motor de PDF para Delphi e C++Builder exposto pela classe TPDFlib, trata essa distinção como a essência da redação, não como um detalhe posterior
Por que desenhar uma tarja preta sobre o texto do PDF não é redação de verdade?
A losLab PDF Library expõe MaskRect para os casos em que cobrir é realmente o que você quer: uma marca-d'água, um blecaute visual, um carimbo de prova. MaskRect(Page, Left, Top, Width, Height, Red, Green, Blue) acrescenta um retângulo ao content stream da página e o pinta com o operador de construção de caminho re, seguido do operador de preenchimento f (ISO 32000-1 §8.5). Cada byte que já estava na página — os glifos do nome que você cobriu, os traços vetoriais, a imagem incorporada — permanece exatamente onde estava, uma instrução antes, no mesmo stream. Um visualizador em conformidade desenha a tarja preta por último, então ela vence visualmente. A cobertura não muda nada do que está embaixo
Qualquer pessoa recupera o conteúdo original de três maneiras: seleciona e copia o texto através da caixa opaca, roda um extrator de texto sobre a página ou apaga o retângulo de cobertura do content stream. MaskRect é honesto quanto a isso no próprio nome — ele mascara, não remove. O resultado é um adesivo sobre uma palavra que você continua conseguindo ler ao descolar o adesivo
Como RedactRegion apaga conteúdo no nível da instrução
RedactRegion(Page, Left, Top, Width, Height, Options) trabalha uma camada abaixo da tinta. A losLab PDF Library analisa cada camada de conteúdo da página em um TPDFContentProgram, percorre a lista de instruções com um analisador que rastreia a CTM (FindInstructionsInRect, no modelo de conteúdo) e marca todo operador de exibição de texto, de pintura de caminho e de imagem Do cuja área pintada cruze o seu retângulo. Essas instruções são removidas com TPDFContentProgram.Delete, e a camada editada é gravada de volta com WritePageContentLayer. Os bytes não deslizam para trás de uma cobertura — eles saem do stream. Um extrator de texto executado depois não acha nada, porque não há nada para achar
Vale levar dois detalhes de implementação para o seu próprio raciocínio. RedactRegion recebe um retângulo com origem no canto superior esquerdo, na origem e nas unidades atuais, e o converte internamente para o espaço do usuário com origem no canto inferior esquerdo que o content stream usa, de modo que você informa a região do jeito que a enxerga na página. Quando um retângulo cobre várias instruções, RedactRegion apaga em ordem decrescente de índice, porque remover a instrução 4 antes da 7 deslocaria todos os índices seguintes e corromperia o intervalo. Passe Options = 0 e a losLab PDF Library ainda estampa uma tarja preta sólida sobre a região limpa, como marcador visível — mas esse marcador é cosmético, aplicado depois que o conteúdo real já foi embora
Na prática você raramente fixa o retângulo no código. Você busca a string sensível na página e lê a caixa delimitadora dela — a mesma busca de página e enumeração de elementos tratada em localizar texto e elementos de página no Delphi — e então entrega esse retângulo a RedactRegion:
var
PDF: TPDFlib;
begin
PDF := TPDFlib.Create;
try
if PDF.LoadFromFile('contract.pdf', '') = 1 then
begin
// Apaga tudo que estiver pintado dentro de uma caixa de 220 x 14 pt na página 1.
// As coordenadas são do canto superior esquerdo, na origem e nas unidades atuais.
PDF.RedactRegion(1, 90, 705, 220, 14, 0); // Options=0 estampa um marcador preto
PDF.SaveToFile('contract-redacted.pdf');
end;
finally
PDF.Free;
end;
end;
Onde a informação suprimida ainda se esconde em um PDF?
A redação que para na camada visível deixa um rastro, porque um PDF guarda informação em lugares que o leitor nunca olha. A losLab PDF Library agrupa os vazamentos em canais que vale nomear. O Document Information Dictionary e o stream de metadados XMP costumam carregar um Author, um Producer ou uma string de Keywords que nomeia justamente a pessoa ou o processo que você removeu do corpo. O JavaScript de nível de documento e a OpenAction do catálogo podem executar na abertura e alcançar dados que você julgava eliminados. Toda anotação carrega uma ação /A opcional, e a própria anotação — uma nota adesiva, um campo de formulário esquecido, um link — pode ficar exatamente sobre o ponto suprimido, com a sua própria cópia do texto. Nada disso é tocado pela criptografia; um arquivo criptografado com um pacote XMP em texto puro ainda vaza o título para qualquer indexador, uma brecha que merece auditar a criptografia e as permissões por conta própria. Uma sanitização defensável precisa visitar cada um desses esconderijos, não só a página que você estava olhando
Limpando o documento com SanitizeDocument
A losLab PDF Library responde a essa dispersão de vazamentos com SanitizeDocument(Flags), uma chamada de orquestração que executa um conjunto de limpezas independentes selecionadas por uma máscara de bits e devolve quantas limpezas aplicou. As flags são combináveis. SANITIZE_JAVASCRIPT remove todo pacote de JavaScript de nível de documento e qualquer script de OpenAction. SANITIZE_ACTIONS retira o dicionário de ação /A de cada anotação, por meio do novo TPDFAnnots.RemoveAnnotAction, e limpa a OpenAction do catálogo. SANITIZE_METADATA zera Author, Subject, Keywords, Creator e Producer tanto no Document Information Dictionary (ISO 32000-1 §14.3.3) quanto no stream de metadados XMP (§14.3.2). SANITIZE_ANNOTATIONS apaga todas as anotações do documento inteiro, e SANITIZE_OPEN_ACTION remove a entrada /OpenAction do catálogo. SANITIZE_ALL é a união bit a bit das cinco
As convenções de índice dentro dessas limpezas são exatamente o tipo de coisa que quebra em silêncio um laço escrito à mão, e é boa parte do motivo de SanitizeDocument existir como uma chamada só. GlobalJavaScriptPackageName é baseado em 0, e RemoveGlobalJavaScript encurta a lista conforme avança, então a limpeza de JavaScript sempre lê o pacote de índice 0 e remove até a contagem chegar a zero. DeleteAnnotation, por outro lado, é baseado em 1. AnnotationCount reporta sobre a página selecionada no momento, então as limpezas de anotação selecionam cada página, uma a uma, antes de contar. A losLab PDF Library acerta essas fronteiras internamente, então você passa um conjunto de flags e lê uma contagem de volta, em vez de reproduzir cinco regras diferentes de iteração
var
Applied: Integer;
begin
// Remove metadados, scripts e ações, mas mantém as anotações:
Applied := PDF.SanitizeDocument(SANITIZE_METADATA or SANITIZE_JAVASCRIPT or
SANITIZE_ACTIONS or SANITIZE_OPEN_ACTION);
// Ou limpa todos os canais ocultos, anotações incluídas:
Applied := PDF.SanitizeDocument(SANITIZE_ALL);
end;
O fluxo completo de redação e sanitização
Junte as duas peças e o procedimento inteiro fica curto. A losLab PDF Library remove o conteúdo visível com RedactRegion, página a página, e depois limpa cada canal oculto com SanitizeDocument(SANITIZE_ALL) antes de o arquivo ser gravado. Como a redação quase sempre roda sobre documentos que chegaram de fora do seu controle, vale carregá-los por um caminho endurecido — a mesma postura defensiva descrita em analisar PDFs não confiáveis com segurança — para que uma entrada malformada falhe de forma limpa, e não no meio da passagem de redação
var
PDF: TPDFlib;
Applied: Integer;
begin
PDF := TPDFlib.Create;
try
if PDF.LoadFromFile('incoming.pdf', '') = 1 then
begin
PDF.RedactRegion(1, 90, 705, 220, 14, 0); // apaga a linha sensível
Applied := PDF.SanitizeDocument(SANITIZE_ALL); // limpa metadados, JS, ações, anotações
PDF.SaveToFile('published.pdf');
end;
finally
PDF.Free;
end;
end;
Onde a redação no content stream para
RedactRegion opera sobre o content stream, que é exatamente onde o texto nascido digital vive e exatamente onde o texto digitalizado não vive. Quando a página é uma imagem escaneada, as palavras são pixels dentro de um XObject de imagem, não operadores de texto, e a losLab PDF Library não consegue recortar um nome do meio de um raster do jeito que apaga um glifo. O tratamento de imagens dela é deliberadamente conservador: qualquer imagem cujo posicionamento cruze o retângulo é removida por inteiro, então uma caixa de redação em uma página escaneada leva junto o escaneamento todo, e não um pedaço dele. Suprimir uma região dentro de uma imagem significa rasterizar, pintar e reincorporar no nível do pixel, o que é uma operação diferente da exclusão no content stream. A mesma cautela vale para uma camada de texto de OCR sobre uma página escaneada: RedactRegion remove os operadores de texto que enxerga, mas a figura por baixo continua intocada, a menos que o XObject de imagem dela também cruze a caixa
Dois pontos mais finos mantêm os resultados honestos. O teste de interseção aproxima a largura de cada trecho de texto pela contagem de caracteres e pelo tamanho da fonte, em vez de medir as métricas exatas dos glifos, então uma palavra bem curta em uma fonte proporcional pode escapar de um retângulo desenhado justo — dê uma pequena margem à caixa. A exclusão também tem granularidade de instrução: RedactRegion remove operadores de exibição de texto inteiros, então um retângulo que corta só parte de um trecho pode levar caracteres vizinhos que compartilhavam o mesmo operador com ele. Nenhum dos dois limites é motivo para desconfiar do mecanismo, apenas para delimitar o retângulo com cuidado e conferir o resultado
A disciplina que fecha todo fluxo de redação é a verificação, não a confiança. Suprima a região, sanitize o documento, grave em um arquivo novo, reabra e tente recuperar tanto o texto que você removeu quanto os metadados que você limpou; quando os dois voltarem vazios, a redação se sustentou. As APIs RedactRegion e SanitizeDocument mostradas aqui acompanham a losLab PDF Library para Delphi e C++Builder, junto com a referência completa do modelo de conteúdo e da sanitização