A redação real de PDF no Delphi significa eliminar os bytes subjacentes, e não pintar por cima deles. A losLab PDF Library desenha essa linha com duas APIs: o RedactRegion edita o fluxo de conteúdo da página para que o texto, vetores e imagens removidos desapareçam fisicamente, e o SanitizeDocument limpa os metadados, scripts e ações onde a informação redigida também se esconde. Erre em qualquer uma das partes e enviará um ficheiro que parece censurado no ecrã, mas que devolve o segredo a quem copiar o texto ou abrir as Propriedades do Documento
O modo de falha é de conhecimento público. Jornais, tribunais e agências governamentais já publicaram PDFs com um retângulo preto sobreposto a um nome ou número, apenas para que um leitor selecione o texto subjacente, o copie e cole a string supostamente redigida à vista de todos. O retângulo era apenas um desenho. O texto ainda lá estava, no fluxo de conteúdo uma camada abaixo da tinta, totalmente intacto e selecionável. A losLab PDF Library, o motor de PDF para Delphi e C++Builder exposto através da classe TPDFlib, trata essa distinção como o objetivo principal da redação e não como uma reflexão tardia
Por que desenhar uma caixa preta sobre o texto do PDF não é uma redação real?
A losLab PDF Library expõe o MaskRect para os casos em que cobrir é genuinamente o que deseja: uma marca de água (watermark), um apagão visual ou um carimbo de prova. O MaskRect(Page, Left, Top, Width, Height, Red, Green, Blue) anexa um retângulo ao fluxo de conteúdo da página e pinta-o com o operador de construção de caminho re seguido pelo operador de preenchimento f (ISO 32000-1 §8.5). Cada byte que já estava na página — os glifos do nome que cobriu, os traços vetoriais, a imagem incorporada — permanece exatamente onde estava, uma instrução antes no mesmo fluxo. Um visualizador em conformidade desenha a caixa preta em último lugar, vencendo visualmente. A cobertura não altera nada por baixo
Qualquer pessoa pode recuperar o conteúdo original de três formas: selecionar e copiar o texto diretamente através da caixa opaca, executar um extrator de texto na página ou eliminar o retângulo de cobertura do fluxo de conteúdo. O MaskRect é honesto quanto a isso no seu próprio nome — ele mascara, não remove. O resultado é um autocolante sobre uma palavra que ainda pode ler ao levantar o autocolante
Como o RedactRegion elimina conteúdo ao nível da instrução
O RedactRegion(Page, Left, Top, Width, Height, Options) funciona uma camada abaixo da tinta. A losLab PDF Library analisa cada camada de conteúdo da página num TPDFContentProgram, percorre a lista de instruções com um analisador de rastreio de CTM (FindInstructionsInRect no modelo de conteúdo) e marca cada operador de exibição de texto, pintura de caminho e imagem Do cuja área pintada intersete o seu retângulo. Essas instruções são removidas com TPDFContentProgram.Delete, e la camada editada é gravada de volta com WritePageContentLayer. Os bytes não deslizam para trás de uma cobertura — eles saem do fluxo. Um extrator de texto executado posteriormente não encontra nada, porque não há nada para encontrar
Dois detalhes de implementação valem a pena ter em consideração. O RedactRegion recebe um retângulo com origem no canto superior esquerdo nas unidades atuais e converte-o internamente para o espaço do utilizador no canto inferior esquerdo que o fluxo de conteúdo utiliza, de modo que especifica a região da forma como a vê na página. Quando um retângulo cobre várias instruções, o RedactRegion elimina-as por ordem decrescente de índice, porque remover a instrução 4 antes da instrução 7 deslocaria todos os índices posteriores e corromperia o intervalo. Passe Options = 0 e a losLab PDF Library também estampará uma caixa preta sólida sobre a região limpa como uma marca visível — mas essa marca é cosmética, aplicada após o conteúdo real já ter sido removido
Na prática, raramente codifica o retângulo de forma rígida. Procura na página a string sensível e lê de volta a sua caixa delimitadora (bounding box) — a mesma pesquisa de página e enumeração de elementos abordada em localizar texto e elementos de página no Delphi — e depois entrega esse retângulo ao RedactRegion:
var
PDF: TPDFlib;
begin
PDF := TPDFlib.Create;
try
if PDF.LoadFromFile('contract.pdf', '') = 1 then
begin
// Elimina tudo o 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 unidades atuais.
PDF.RedactRegion(1, 90, 705, 220, 14, 0); // Options=0 estampa uma marca preta
PDF.SaveToFile('contract-redacted.pdf');
end;
finally
PDF.Free;
end;
end;
Onde é que a informação redigida ainda se esconde num PDF?
A redação que se limita à camada visível deixa um rasto, porque um PDF armazena informações em locais onde o leitor nunca olha. A losLab PDF Library agrupa as fugas em canais que vale a pena nomear. O Dicionário de Informações do Documento e o fluxo de metadados XMP contêm rotineiramente um Autor, um Produtor ou uma string de Palavras-chave que identifica a própria pessoa ou caso que removeu do corpo. O JavaScript ao nível do documento e o catálogo OpenAction podem ser executados ao abrir e aceder a dados que pensava terem desaparecido. Cada anotação carrega uma ação /A opcional, e a própria anotação — uma nota adesiva (sticky note), um campo de formulário obsoleto, uma ligação (link) — pode situar-se diretamente sobre o local redigido com a sua própria cópia do texto. Nada disto é afetado pela encriptação; um ficheiro encriptado com um pacote XMP em texto limpo continua a divulgar o seu título a qualquer indexador, uma falha que por si só merece auditar a encriptação e permissões. A higienização defensável tem de visitar cada um destes locais ocultos, e não apenas a página que estava a visualizar
Limpar o documento com o SanitizeDocument
A losLab PDF Library responde a essa dispersão de fugas com o SanitizeDocument(Flags), uma chamada de orquestração que executa um conjunto de remoções independentes selecionadas por uma máscara de bits (bitmask) e devolve o número de remoções que aplicou. As flags são combináveis. O SANITIZE_JAVASCRIPT remove todos os pacotes de JavaScript ao nível do documento e qualquer script OpenAction. O SANITIZE_ACTIONS remove o dicionário de ações /A de cada anotação, através do novo TPDFAnnots.RemoveAnnotAction, e limpa o catálogo OpenAction. O SANITIZE_METADATA limpa o Autor, Assunto, Palavras-chave, Criador e Produtor tanto no Dicionário de Informações do Documento (ISO 32000-1 §14.3.3) como no fluxo de metadados XMP (§14.3.2). O SANITIZE_ANNOTATIONS elimina todas as anotações em todo o documento, e o SANITIZE_OPEN_ACTION remove a entrada /OpenAction do catálogo. O SANITIZE_ALL é a união bitwise de todas as cinco
As convenições de indexação dentro destas remoções são exatamente o tipo de coisa que quebra silenciosamente um loop criado manualmente, sendo essa a principal razão pela qual o SanitizeDocument existe como uma chamada única. O GlobalJavaScriptPackageName is baseado em 0, e o RemoveGlobalJavaScript reduz a lista à medida que avança, pelo que a remoção de JavaScript lê sempre o índice de pacote 0 e remove até que a contagem atinja zero. O DeleteAnnotation, em contrapartida, é baseado em 1. O AnnotationCount reporta-se à página atualmente selecionada, pelo que as remoções de anotações selecionam cada página por sua vez antes de contar. A losLab PDF Library lida com estes limites corretamente de forma interna, de modo que passa um conjunto de flags e lê uma contagem de volta em vez de reproduzir cinco regras de iteração diferentes
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);
// Or clear every hidden channel, annotations included:
Applied := PDF.SanitizeDocument(SANITIZE_ALL);
end;
O fluxo de trabalho completo de redação e higienização
Junte os dois e o procedimento completo é curto. A losLab PDF Library remove o conteúdo visível com o RedactRegion, página por página, e depois limpa todos os canais ocultos com o SanitizeDocument(SANITIZE_ALL) antes de o ficheiro ser gravado. Como a redação quase sempre é executada em documentos que vieram de fora do seu controlo, vale a pena carregá-los através de um caminho protegido — a mesma postura defensiva descrita em analisar PDFs não confiáveis de forma segura —, para que uma entrada malformada falhe de forma limpa em vez de falhar a 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); // elimina 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 termina a redação do fluxo de conteúdo
O RedactRegion opera no fluxo de conteúdo, que é exatamente onde vive o texto originalmente digital e exatamente onde o texto digitalizado não vive. Quando uma página é uma imagem digitalizada, as palavras são píxeis dentro de um XObject de imagem, e não operadores de texto, e a losLab PDF Library não consegue extrair um nome do meio de uma imagem rasterizada da forma como elimina um glifo. O seu tratamento de imagens é deliberadamente conservador: qualquer imagem cuja colocação intersete o retângulo é removida por inteiro, pelo que uma caixa de redação numa página digitalizada remove toda a digitalização em vez de apenas uma secção dela. Redigir uma região dentro de uma imagem significa rasterizar, pintar e voltar a incorporar ao nível do píxel, o que é uma operação diferente da eliminação do fluxo de conteúdo. A mesma precaução aplica-se a uma camada de texto OCR sobre uma página digitalizada: o RedactRegion remove os operadores de texto que consegue ver, mas a imagem por baixo permanece intocada, a menos que o seu XObject de imagem também intersete a caixa
Dois pontos mais finos mantêm os resultados fiáveis. O teste de interseção aproxima a largura de cada bloco de texto com base na sua contagem de caracteres e tamanho de fonte, em vez de medir as métricas exatas dos glifos, pelo que uma palavra muito curta escrita numa fonte proporcional pode escapar a um retângulo desenhado com precisão — dê à caixa uma pequena margem. A eliminação também é granular ao nível da instrução: o RedactRegion remove operadores inteiros de exibição de texto, pelo que um retângulo que corte apenas parte de um bloco pode levar caracteres adjacentes que partilhavam o mesmo operador. Nenhum dos limites é motivo para desconfiar do mecanismo, apenas uma razão para delimitar o retângulo com cuidado e verificar o resultado
A disciplina que encerra cada fluxo de trabalho de redação é a verificação, e não a confiança. Redija a região, higienize o documento, guarde num novo ficheiro, reabra-o e tente recuperar o texto que removeu e os metadados que limpou; se ambos voltarem vazios, a redação funcionou. As APIs RedactRegion e SanitizeDocument mostradas aqui são fornecidas com a losLab PDF Library para Delphi e C++Builder, juntamente com o modelo de conteúdo completo e a referência de higienização