A PDF Library for Delphi (PDFlibPas) grava metadados de comandos Web Capture no catálogo do documento via AddWebCaptureCommand, preenchendo o dicionário /SpiderInfo definido na ISO 32000-1 §14.10. Cada comando registra a URL que foi buscada, quantos níveis de profundidade a busca percorreu e um pequeno conjunto de flags. Ela registra intenção — não busca nada
Essa distinção é exatamente o motivo de essa API ser fácil de interpretar errado. Um engenheiro de suporte te entrega um PDF montado a partir da documentação online de um fornecedor e pergunta de onde veio a página 40. Se o produtor preencheu /SpiderInfo, a resposta está no catálogo: uma URL, uma profundidade de crawl, talvez o corpo do POST que gerou a página. Se o produtor não preencheu, nenhuma leitura do content stream vai te dizer isso. Gravar essa procedência é barato, estruturalmente chato e quase sempre pulado
O que o dicionário /SpiderInfo do catálogo registra de fato?
/SpiderInfo é um dicionário de informações de Web Capture pendurado no catálogo do documento e guarda exatamente duas coisas que a PDF Library for Delphi considera: /V, uma versão numérica do banco de dados de Web Capture que a biblioteca sempre grava como 1.0, e /C, um array de dicionários de comando. O número de versão não é a versão do PDF — ele identifica o próprio modelo de dados de Web Capture, e 1.0 é o único valor que a ISO 32000-1 §14.10 define. Tudo que interessa vive em /C. Cada dicionário de comando carrega /URL (o endereço que foi solicitado), /L (quantos níveis de links foram seguidos a partir dele) e /F (flags de travessia). Três chaves opcionais descrevem a própria requisição quando ela não era um GET simples: /P para dados de POST, /CT para o content type desses dados e /H para cabeçalhos adicionais. A PDF Library for Delphi mapeia todos os seis em um único record, TPDFlibWebCaptureCommand, cujos campos são URL, Levels, Flags, PostData, ContentType e Headers, todos AnsiString exceto os dois contadores Integer. A presença de /SpiderInfo também eleva a versão mínima do documento: AddWebCaptureCommand chama EnsureMinVersion('1.3', 'Catalog /SpiderInfo Web Capture command'), e as regras de detecção de versão da PDF Library for Delphi registram uma regra PDF 1.3 correspondente, de modo que um documento relido mais tarde reporte o mesmo piso
Por que um comando de Web Capture precisa ser um objeto indireto
A única regra estrutural que derruba todo mundo: um dicionário de comando precisa ser registrado como objeto indireto antes de ir para /C, porque a ISO 32000-1 §14.10 especifica /C como um array de referências indiretas a dicionários de comando, não um array de dicionários. Empurre um dicionário direto para esse array e você obtém algo que faz parse como PDF válido e não significa nada como registro de Web Capture — um viewer ou uma ferramenta de arquivamento percorrendo a lista de comandos encontra um objeto inline onde deveria haver uma referência, e a entrada simplesmente não é um comando. A PDF Library for Delphi cuida disso dentro de AddWebCaptureCommand: ela monta o dicionário, entrega a OnNewIndObj para obter um object number e um generation number, embrulha esse par em uma referência indireta e anexa a referência ao array. Se OnNewIndObj não produzir um object number positivo, a função retorna 0 e o array fica intocado. O motivo de a spec insistir em indireção é que um único comando pode ser compartilhado — a mesma busca pode ser referenciada de mais de um lugar em uma estrutura de Web Capture — e objetos compartilhados em PDF são, por definição, indiretos
Como você adiciona um comando de Web Capture a partir do Delphi?
Preencha o record por completo e então chame AddWebCaptureCommand, que retorna o índice 1-based do novo comando ou 0 se a validação o rejeitou. Completude importa mais do que o normal aqui. O Delphi inicializa os campos gerenciados AnsiString de um record local como vazios, mas os campos Integer — Levels e Flags — não recebem esse tratamento, então um record que você esqueceu de preencher chega carregando o que estivesse na stack. AddWebCaptureCommand exige Levels de pelo menos 1 e Flags não negativo, e um valor-lixo de stack ou é rejeitado na hora ou, pior, aceito silenciosamente como uma profundidade que parece plausível
var
Lib: TPDFlib;
Command: TPDFlibWebCaptureCommand;
Index: Integer;
begin
Lib := TPDFlib.Create;
try
if Lib.LoadFromFile('captured-site.pdf', '') = 1 then
begin
// Atribua todos os campos: Levels e Flags são Integers comuns e
// carregam lixo de pilha se você os pular.
Command.URL := 'https://example.com/docs/index.html';
Command.Levels := 2;
Command.Flags := PDF_WEB_CAPTURE_SAME_SITE or PDF_WEB_CAPTURE_SAME_PATH;
Command.PostData := '';
Command.ContentType := '';
Command.Headers := '';
// Retorna o novo índice de comando 1-based, ou 0 em caso de rejeição.
Index := Lib.AddWebCaptureCommand(Command);
if Index = 0 then
raise Exception.Create('Web Capture command rejected');
Lib.SaveToFile('captured-site.pdf');
end;
finally
Lib.Free;
end;
end;
A primeira chamada bem-sucedida também é o que cria a estrutura. AddWebCaptureCommand pede o array de comandos ao helper interno WebCaptureCommands(True), e esse helper cria /SpiderInfo com sua entrada /V e depois um array /C vazio apenas se eles não existirem. Ele é deliberadamente conservador sobre o que encontra: se o catálogo já tem uma chave /SpiderInfo cujo valor não é um dicionário, ou uma chave /C cujo valor não é um array, o helper retorna nil em vez de sobrescrever o objeto existente. Uma estrutura herdada malformada faz a adição falhar; ela não faz a PDF Library for Delphi destruir o que o produtor anterior gravou
A whitelist de flags: três bits definidos e nada mais
AddWebCaptureCommand valida Flags contra uma whitelist e rejeita qualquer valor com um bit definido fora das três constantes que a PDF Library for Delphi define: PDF_WEB_CAPTURE_SAME_SITE = 1 (seguir apenas links que permanecem no mesmo site), PDF_WEB_CAPTURE_SAME_PATH = 2 (seguir apenas links sob o mesmo path) e PDF_WEB_CAPTURE_SUBMIT = 4 (esta requisição foi um submit de formulário, não uma recuperação simples). Passe 8, ou 16, ou um -1 acidental, e a chamada retorna 0 sem tocar no documento
Isso é mais rígido do que parsers de PDF normalmente são, e de propósito. /F é um campo de bits sem estrutura autodescritiva, então um bit indefinido é indistinguível de um corrompido, e um consumidor que depois relê o valor não tem como distinguir uma extensão futura de um typo. Recusar no momento da escrita mantém a ambiguidade totalmente fora do arquivo. Note também que /P, /CT e /H só são gravados quando os campos correspondentes do record não estão vazios, então um GET comum produz um dicionário de comando de três chaves — /URL, /L, /F — sem placeholders de string vazia poluindo, que é o que um leitor fazendo parse do arquivo espera para uma requisição que não carregou corpo
// Um envio de formulário: corpo POST mais o tipo de conteúdo.
Command.URL := 'https://example.com/search';
Command.Levels := 1;
Command.Flags := PDF_WEB_CAPTURE_SUBMIT; // 4
Command.PostData := 'q=iso32000&scope=all';
Command.ContentType := 'application/x-www-form-urlencoded';
Command.Headers := 'Accept-Language: en-US';
if Lib.AddWebCaptureCommand(Command) = 0 then
raise Exception.Create('submission command rejected');
// Same-site mais same-path é uma combinação válida: 1 ou 2 = 3.
Command.Flags := PDF_WEB_CAPTURE_SAME_SITE or PDF_WEB_CAPTURE_SAME_PATH;
// O bit 3 não é definido pela especificação, então este nunca chega ao arquivo.
Command.Flags := 8;
if Lib.AddWebCaptureCommand(Command) <> 0 then
raise Exception.Create('undefined flag bit should have been refused');
Lendo comandos de volta: uma chave, duas representações legais
GetWebCaptureCommand retorna 1 em caso de sucesso e 0 caso contrário, e precisa ser mais tolerante que o writer, porque está lendo arquivos que outros produtores gravaram. Duas assimetrias importam. Primeiro, o reader aceita uma entrada de comando seja ela uma referência indireta ou um dicionário direto, dereferenciando quando necessário — a PDF Library for Delphi sempre grava a forma indireta que a spec exige, mas recusar ler um arquivo relaxado não ajuda ninguém. Segundo, /P pode ser tanto uma string quanto um stream, e isso generaliza bem além de Web Capture. A ISO 32000-1 repetidamente permite que uma única chave guarde uma string ou um stream quando o valor pode ser arbitrariamente grande — corpos de POST aqui, e o mesmo padrão aparece em outros lugares do formato para metadados e payloads embutidos. O helper interno ReadString em GetWebCaptureCommand portanto recebe uma flag AllowStream, e apenas a leitura de /P passa True: um valor de stream é decodificado pela sua cadeia de filtros e retornado como o campo PostData, enquanto /URL, /CT e /H continuam apenas strings porque a spec não oferece a alternativa de stream para eles. Se você escreve seu próprio código de leitura de PDF, esse é o hábito que vale roubar — cheque na spec quais chaves são dual-typed em vez de assumir uma string
var
Lib: TPDFlib;
Command: TPDFlibWebCaptureCommand;
I, Total, Removed: Integer;
begin
Total := Lib.GetWebCaptureCommandCount;
// A indexação pública é 1-based, igual ao resto da PDF Library para Delphi.
for I := 1 to Total do
if Lib.GetWebCaptureCommand(I, Command) = 1 then
Writeln(Format('%s levels=%d flags=%d post=%d bytes',
[Command.URL, Command.Levels, Command.Flags, Length(Command.PostData)]));
// Remove o dicionário /SpiderInfo inteiro e informa quantos
// comandos foram descartados; retorna 0 quando não havia nada a limpar.
Removed := Lib.ClearWebCaptureCommands;
Writeln(Format('cleared %d command(s)', [Removed]));
end;
ClearWebCaptureCommands conta os comandos e então purga a chave /SpiderInfo inteira do catálogo em vez de apenas esvaziar /C. Deixar um /SpiderInfo com um array de comandos vazio afirmaria que o documento tem procedência de Web Capture e que nada foi buscado, o que é uma afirmação diferente e falsa em relação a não ter procedência registrada. O mesmo instinto vale para metadados de catálogo em geral — um dicionário casca vazio é uma declaração, não um resto neutro. Se você está auditando o que um documento já afirma antes de reescrevê-lo, o padrão de introspecção em lendo ações de outline e anotação de volta de um PDF existente combina naturalmente com uma passada de ler-e-limpar aqui
Gravar metadados não é implementar Web Capture
Seja franco com você mesmo sobre o escopo: AddWebCaptureCommand salva um registro de uma busca pretendida ou concluída, e é só isso que ele faz. A PDF Library for Delphi não executa requisições HTTP, não segue os links que seu valor de Levels descreve e não honra as flags SAME_SITE ou SAME_PATH — essas flags são documentação de como algum crawler se comportou, não instruções que a biblioteca executa. Seu próprio código de busca, seja ele qual for, continua sendo inteiramente problema seu
Mais duas partes da ISO 32000-1 §14.10 também ficam fora dessa API. Web Capture define content sets — as estruturas /Contents que amarram pedaços individuais de página de volta ao material de origem — e uma name tree que mapeia identificadores digitais para esses content sets. A PDF Library for Delphi não mantém nenhuma das duas. O que você recebe é a lista de comandos: a resposta de nível superior para "de onde este documento foi buscado, e quão fundo o crawl foi", que para fins de procedência, auditoria e ingestão de arquivamento costuma ser a parte que qualquer um de fato pergunta. Se você precisa que as URLs sejam clicáveis para os leitores em vez de meramente registradas para auditores, isso é um trabalho separado — veja construindo ações GoToR, GoToE e Launch para o lado das ações, e gatilhos de ações de ciclo de vida de documento e página para comportamento de disparo ao abrir ou fechar. Os metadados de comando do catálogo /SpiderInfo descritos aqui fazem parte da PDF Library for Delphi, a biblioteca de PDF nativa para Delphi e C++Builder