PDF Library for Delphi (PDFlibPas) escribe los metadatos de comandos Web Capture en el catálogo del documento mediante AddWebCaptureCommand, rellenando el diccionario /SpiderInfo definido en la ISO 32000-1 §14.10. Cada comando registra la URL que se obtuvo, cuántos niveles de profundidad alcanzó la obtención y un pequeño conjunto de flags. Registra la intención, no obtiene nada
Esa distinción es la razón de que esta API se lea mal con tanta facilidad. Un ingeniero de soporte te entrega un PDF montado a partir de la documentación online de un proveedor y te pregunta de dónde salió la página 40. Si el productor rellenó /SpiderInfo, la respuesta está en el catálogo: una URL, una profundidad de rastreo, quizá el cuerpo POST que generó la página. Si no lo hizo, por mucho que leas el content stream no lo vas a averiguar. Escribir esa procedencia es barato, estructuralmente quisquilloso y casi siempre se omite
¿Qué registra en realidad el diccionario /SpiderInfo del catálogo?
/SpiderInfo es un diccionario de información Web Capture colgado del catálogo del documento, y guarda exactamente dos cosas que le importan a PDF Library for Delphi: /V, la versión numérica de la base de datos Web Capture que la librería escribe siempre como 1.0, y /C, un array de diccionarios de comando. El número de versión no es la versión de PDF: identifica el modelo de datos de Web Capture en sí, y 1.0 es el único valor que define la ISO 32000-1 §14.10. Todo lo interesante vive en /C. Cada diccionario de comando lleva /URL (la dirección que se solicitó), /L (cuántos niveles de enlaces se siguieron desde ella) y /F (los flags de recorrido). Tres claves opcionales describen la propia petición cuando no era un GET simple: /P para los datos POST, /CT para el content type de esos datos y /H para cabeceras de petición adicionales. PDF Library for Delphi mapea las seis sobre un único registro, TPDFlibWebCaptureCommand, cuyos campos son URL, Levels, Flags, PostData, ContentType y Headers, todos AnsiString salvo los dos contadores Integer. La presencia de /SpiderInfo también eleva la versión mínima del documento: AddWebCaptureCommand llama a EnsureMinVersion('1.3', 'Catalog /SpiderInfo Web Capture command'), y las reglas de detección de versión de PDF Library for Delphi registran una regla PDF 1.3 equivalente para que un documento leído después reporte el mismo suelo
Por qué un comando Web Capture debe ser un objeto indirecto
La única regla estructural que hace tropezar a la gente: un diccionario de comando tiene que registrarse como objeto indirecto antes de entrar en /C, porque la ISO 32000-1 §14.10 especifica /C como un array de referencias indirectas a diccionarios de comando, no un array de diccionarios. Si metes un diccionario directo en ese array obtienes algo que se parsea como PDF válido y que no significa nada como registro Web Capture: un visor o una herramienta de archivo que recorra la lista de comandos encuentra un objeto inline donde debería haber una referencia, y la entrada sencillamente no es un comando. PDF Library for Delphi lo gestiona dentro de AddWebCaptureCommand: construye el diccionario, lo entrega a OnNewIndObj para obtener un número de objeto y un número de generación, envuelve ese par en una referencia indirecta y añade la referencia al array. Si OnNewIndObj no produce un número de objeto positivo, la función devuelve 0 y el array se queda como estaba. La razón por la que la spec insiste en la indirección es que un único comando puede compartirse: la misma obtención puede referenciarse desde más de un sitio en una estructura Web Capture, y los objetos compartidos en PDF son, por definición, indirectos
¿Cómo se añade un comando Web Capture desde Delphi?
Rellena el registro por completo y llama a AddWebCaptureCommand, que devuelve el índice 1-based del nuevo comando o 0 si la validación lo rechaza. Aquí la completitud importa más de lo habitual. Delphi inicializa a vacío los campos AnsiString gestionados de un registro local, pero los campos Integer — Levels y Flags — no reciben ese trato, así que un registro que olvides rellenar llega cargando lo que hubiera en la pila. AddWebCaptureCommand exige que Levels sea al menos 1 y que Flags no sea negativo, y un valor de basura de pila o bien se rechaza de plano o, peor, se acepta en silencio como una profundidad de aspecto plausible
var
Lib: TPDFlib;
Command: TPDFlibWebCaptureCommand;
Index: Integer;
begin
Lib := TPDFlib.Create;
try
if Lib.LoadFromFile('captured-site.pdf', '') = 1 then
begin
// Asigna cada campo: Levels y Flags son Integers planos y
// arrastran basura de la pila si los saltas.
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 := '';
// Devuelve el índice 1-based del nuevo comando, o 0 si se rechaza.
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;
La primera llamada que tiene éxito es también la que crea la estructura. AddWebCaptureCommand pide el array de comandos al helper interno WebCaptureCommands(True), y ese helper crea /SpiderInfo con su entrada /V y luego un array /C vacío solo si faltan. Es deliberadamente conservador con lo que encuentra: si el catálogo ya tiene una clave /SpiderInfo cuyo valor no es un diccionario, o una clave /C cuyo valor no es un array, el helper devuelve nil en lugar de sobrescribir el objeto existente. Una estructura heredada malformada hace que falle la adición; no hace que PDF Library for Delphi destruya lo que escribió el productor anterior
La lista blanca de flags: tres bits definidos y nada más
AddWebCaptureCommand valida Flags contra una lista blanca y rechaza cualquier valor con un bit puesto fuera de las tres constantes que define PDF Library for Delphi: PDF_WEB_CAPTURE_SAME_SITE = 1 (sigue solo enlaces que se quedan en el mismo sitio), PDF_WEB_CAPTURE_SAME_PATH = 2 (sigue solo enlaces bajo la misma ruta) y PDF_WEB_CAPTURE_SUBMIT = 4 (esta petición fue un envío de formulario, no una recuperación simple). Pasa un 8, o un 16, o un -1 accidental, y la llamada devuelve 0 sin tocar el documento
Esto es más estricto de lo que suelen ser los parsers de PDF, y a propósito. /F es un campo de bits sin estructura autodescriptiva, así que un bit indefinido es indistinguible de uno corrupto, y un consumidor que después lea el valor no tiene forma de distinguir una extensión futura de un error de teclado. Rechazar al escribir saca la ambigüedad del archivo por completo. Fíjate además en que /P, /CT y /H solo se escriben cuando sus campos de registro correspondientes no están vacíos, así que un GET normal produce un diccionario de comando de tres claves — /URL, /L, /F — sin placeholders de cadena vacía que lo ensucien, que es lo que espera un lector que parsea el archivo para una petición que no llevaba cuerpo
// Un envío de formulario: cuerpo POST más su content type.
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');
// Mismo sitio más misma ruta es una combinación legal: 1 or 2 = 3.
Command.Flags := PDF_WEB_CAPTURE_SAME_SITE or PDF_WEB_CAPTURE_SAME_PATH;
// El bit 3 no lo define la spec, así que este nunca llega al archivo.
Command.Flags := 8;
if Lib.AddWebCaptureCommand(Command) <> 0 then
raise Exception.Create('undefined flag bit should have been refused');
Leer comandos: una clave, dos representaciones válidas
GetWebCaptureCommand devuelve 1 en caso de éxito y 0 en caso contrario, y tiene que ser más tolerante que el escritor, porque lee archivos que escribieron otros productores. Dos asimetrías importan. Primera: el lector acepta una entrada de comando tanto si es una referencia indirecta como si es un diccionario directo, dereferenciando cuando hace falta — PDF Library for Delphi escribe siempre la forma indirecta que exige la spec, pero negarse a leer un archivo descuidado no ayuda a nadie. Segunda: /P puede ser una cadena o un stream, y esa generalización va mucho más allá de Web Capture. La ISO 32000-1 deja repetidamente que una única clave contenga una cadena o un stream cuando el valor puede ser arbitrariamente grande — aquí los cuerpos POST, y el mismo patrón aparece en el formato para metadatos y payloads embebidos. El helper interno ReadString de GetWebCaptureCommand recibe por tanto un flag AllowStream, y solo la lectura de /P pasa True: un valor de stream se decodifica a través de su cadena de filtros y se devuelve como campo PostData, mientras que /URL, /CT y /H siguen siendo solo cadena porque la spec no les ofrece la alternativa de stream. Si escribes tu propio código de lectura de PDF, este es el hábito que merece la pena copiar: mira en la spec qué claves tienen doble tipo en lugar de asumir que son cadenas
var
Lib: TPDFlib;
Command: TPDFlibWebCaptureCommand;
I, Total, Removed: Integer;
begin
Total := Lib.GetWebCaptureCommandCount;
// La indexación pública es 1-based, como el resto de PDF Library for 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)]));
// Elimina el diccionario /SpiderInfo entero y reporta cuántos
// comandos se descartaron; devuelve 0 cuando no había nada que limpiar.
Removed := Lib.ClearWebCaptureCommands;
Writeln(Format('cleared %d command(s)', [Removed]));
end;
ClearWebCaptureCommands cuenta los comandos y luego purga la clave /SpiderInfo entera del catálogo en lugar de limitarse a vaciar /C. Dejar un /SpiderInfo con un array de comandos vacío afirmaría que el documento tiene procedencia Web Capture y que no se obtuvo nada, que es una afirmación distinta y falsa respecto a no tener procedencia registrada. El mismo instinto se aplica a los metadatos del catálogo en general: un diccionario cáscara vacío es una declaración, no un resto neutral. Si estás auditando lo que un documento ya afirma antes de reescribirlo, el patrón de introspección de leer outlines y acciones de anotación de un PDF existente encaja de forma natural con una pasada de leer-y-limpiar aquí
Escribir metadatos no es implementar Web Capture
Se sincero contigo mismo sobre el alcance: AddWebCaptureCommand guarda un registro de una obtención prevista o completada, y eso es todo lo que hace. PDF Library for Delphi no realiza peticiones HTTP, no sigue los enlaces que describe tu valor de Levels y no honra los flags SAME_SITE o SAME_PATH — esos flags son documentación de cómo se comportó algún crawler, no instrucciones que la librería ejecute. Tu propio código de obtención, sea el que sea, sigue siendo enteramente problema tuyo
Dos piezas más de la ISO 32000-1 §14.10 quedan también fuera de esta API. Web Capture define content sets — las estructuras /Contents que atan piezas individuales de página al material de origen del que vinieron — y un name tree que mapea identificadores digitales a esos content sets. PDF Library for Delphi no mantiene ninguna de las dos. Lo que obtienes es la lista de comandos: la respuesta de nivel superior a «de dónde se obtuvo este documento y hasta qué profundidad llegó el rastreo», que para fines de procedencia, auditoría y entrada en archivo suele ser la parte que de verdad pregunta cualquiera. Si necesitas que las URLs sean clicables para los lectores y no meramente registradas para los auditores, ese es otro trabajo — mira construir acciones GoToR, GoToE y Launch para el lado de las acciones, y los disparadores de acciones del ciclo de vida de documento y página para el comportamiento al abrir o cerrar. Los metadatos de comando Web Capture de /SpiderInfo del catálogo descritos aquí forman parte de PDF Library for Delphi, la librería de PDF nativa para Delphi y C++Builder