PDF Library for Delphi (PDFlibPas) writes Web Capture command metadata into a document's catalog through AddWebCaptureCommand, filling the /SpiderInfo dictionary defined in ISO 32000-1 §14.10. Each command records the URL that was fetched, how many levels deep the fetch went, and a small set of flags. It records intent — it does not fetch anything
That distinction is the whole reason this API is easy to misread. A support engineer hands you a PDF assembled from a vendor's online documentation and asks where page 40 came from. If the producer filled in /SpiderInfo, the answer is sitting in the catalog: a URL, a crawl depth, maybe the POST body that produced the page. If the producer did not, no amount of reading the content stream will tell you. Writing that provenance is cheap, structurally fussy, and almost always skipped
What does the Catalog /SpiderInfo dictionary actually record?
/SpiderInfo is a Web Capture information dictionary hanging off the document catalog, and it holds exactly two things PDF Library for Delphi cares about: /V, a numeric Web Capture database version that the library always writes as 1.0, and /C, an array of command dictionaries. The version number is not the PDF version — it identifies the Web Capture data model itself, and 1.0 is the only value ISO 32000-1 §14.10 defines. Everything interesting lives in /C. Each command dictionary carries /URL (the address that was requested), /L (how many levels of links were followed from it), and /F (traversal flags). Three optional keys describe the request itself when it was not a plain GET: /P for POST data, /CT for the content type of that data, and /H for additional request headers. PDF Library for Delphi maps all six onto a single record, TPDFlibWebCaptureCommand, whose fields are URL, Levels, Flags, PostData, ContentType, and Headers, all AnsiString except the two Integer counters. The presence of /SpiderInfo also pushes the document's minimum version: AddWebCaptureCommand calls EnsureMinVersion('1.3', 'Catalog /SpiderInfo Web Capture command'), and the version-detection rules in PDF Library for Delphi register a matching PDF 1.3 rule so a document read back later reports the same floor
Why a Web Capture command must be an indirect object
The single structural rule that trips people up: a command dictionary has to be registered as an indirect object before it goes into /C, because ISO 32000-1 §14.10 specifies /C as an array of indirect references to command dictionaries, not an array of dictionaries. Push a direct dictionary into that array and you get something that parses as valid PDF and means nothing as a Web Capture record — a viewer or an archival tool walking the command list finds an inline object where a reference should be, and the entry is simply not a command. PDF Library for Delphi handles this inside AddWebCaptureCommand: it builds the dictionary, hands it to OnNewIndObj to obtain an object number and generation number, wraps that pair in an indirect reference, and appends the reference to the array. If OnNewIndObj fails to produce a positive object number, the function returns 0 and the array is left alone. The reason the spec insists on indirection is that a single command can be shared — the same fetch can be referenced from more than one place in a Web Capture structure — and shared objects in PDF are, by definition, indirect ones
How do you add a Web Capture command from Delphi?
Fill the record completely, then call AddWebCaptureCommand, which returns the new command's 1-based index or 0 if validation rejected it. Completeness matters more than usual here. Delphi initializes the managed AnsiString fields of a local record to empty, but the Integer fields — Levels and Flags — get no such treatment, so a record you forget to fill arrives carrying whatever was on the stack. AddWebCaptureCommand requires Levels to be at least 1 and Flags to be non-negative, and a stack-garbage value will either be rejected outright or, worse, silently accepted as a plausible-looking depth
var
Lib: TPDFlib;
Command: TPDFlibWebCaptureCommand;
Index: Integer;
begin
Lib := TPDFlib.Create;
try
if Lib.LoadFromFile('captured-site.pdf', '') = 1 then
begin
// Assign every field: Levels and Flags are plain Integers and
// carry stack garbage if you skip them.
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 := '';
// Returns the new 1-based command index, or 0 on rejection.
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;
The first successful call is also what creates the structure. AddWebCaptureCommand asks the internal WebCaptureCommands(True) helper for the command array, and that helper creates /SpiderInfo with its /V entry and then an empty /C array only if they are missing. It is deliberately conservative about what it finds: if the catalog already has a /SpiderInfo key whose value is not a dictionary, or a /C key whose value is not an array, the helper returns nil rather than overwriting the existing object. A malformed inherited structure makes the add fail; it does not make PDF Library for Delphi destroy whatever the previous producer wrote
The flag whitelist: three defined bits and nothing else
AddWebCaptureCommand validates Flags against a whitelist and rejects any value with a bit set outside the three constants PDF Library for Delphi defines: PDF_WEB_CAPTURE_SAME_SITE = 1 (follow only links that stay on the same site), PDF_WEB_CAPTURE_SAME_PATH = 2 (follow only links under the same path), and PDF_WEB_CAPTURE_SUBMIT = 4 (this request was a form submission rather than a plain retrieval). Pass 8, or 16, or an accidental -1, and the call returns 0 without touching the document
This is stricter than PDF parsers usually are, and on purpose. /F is a bit field with no self-describing structure, so an undefined bit is indistinguishable from a corrupted one, and a consumer that later reads the value back has no way to tell a future extension from a typo. Refusing at write time keeps the ambiguity out of the file entirely. Note also that /P, /CT, and /H are written only when their corresponding record fields are non-empty, so an ordinary GET produces a three-key command dictionary — /URL, /L, /F — with no empty-string placeholders cluttering it, which is what a reader parsing the file expects for a request that carried no body
// A form submission: POST body plus its 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');
// Same-site plus same-path is a legal combination: 1 or 2 = 3.
Command.Flags := PDF_WEB_CAPTURE_SAME_SITE or PDF_WEB_CAPTURE_SAME_PATH;
// Bit 3 is not defined by the spec, so this one never reaches the file.
Command.Flags := 8;
if Lib.AddWebCaptureCommand(Command) <> 0 then
raise Exception.Create('undefined flag bit should have been refused');
Reading commands back: one key, two legal representations
GetWebCaptureCommand returns 1 on success and 0 otherwise, and it has to be more tolerant than the writer, because it is reading files other producers wrote. Two asymmetries matter. First, the reader accepts a command entry whether it is an indirect reference or a direct dictionary, dereferencing when needed — PDF Library for Delphi always writes the indirect form the spec requires, but refusing to read a sloppy file helps nobody. Second, /P is allowed to be either a string or a stream, and that one generalizes well beyond Web Capture. ISO 32000-1 repeatedly lets a single key hold either a string or a stream when the value can be arbitrarily large — POST bodies here, and the same pattern shows up elsewhere in the format for metadata and embedded payloads. The internal ReadString helper in GetWebCaptureCommand therefore takes an AllowStream flag, and only the /P read passes True: a stream value is decoded through its filter chain and returned as the PostData field, while /URL, /CT, and /H stay string-only because the spec does not offer them the stream alternative. If you write your own PDF reading code, this is the habit worth stealing — check the spec for which keys are dual-typed rather than assuming a string
var
Lib: TPDFlib;
Command: TPDFlibWebCaptureCommand;
I, Total, Removed: Integer;
begin
Total := Lib.GetWebCaptureCommandCount;
// Public indexing is 1-based, matching the rest of 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)]));
// Removes the whole /SpiderInfo dictionary and reports how many
// commands were dropped; returns 0 when there was nothing to clear.
Removed := Lib.ClearWebCaptureCommands;
Writeln(Format('cleared %d command(s)', [Removed]));
end;
ClearWebCaptureCommands counts the commands, then purges the entire /SpiderInfo key from the catalog rather than just emptying /C. Leaving behind a /SpiderInfo with an empty command array would assert that the document has Web Capture provenance and that nothing was fetched, which is a different and false claim from having no provenance recorded. The same instinct applies to catalog metadata generally — an empty shell dictionary is a statement, not a neutral leftover. If you are auditing what a document already claims before rewriting it, the introspection pattern in reading outline and annotation actions back out of an existing PDF pairs naturally with a read-then-clear pass here
Writing metadata is not implementing Web Capture
Be blunt with yourself about the scope: AddWebCaptureCommand saves a record of an intended or completed fetch, and that is all it does. PDF Library for Delphi does not perform HTTP requests, does not follow the links your Levels value describes, and does not honor the SAME_SITE or SAME_PATH flags — those flags are documentation of how some crawler behaved, not instructions the library executes. Your own fetching code, whatever it is, remains entirely your problem
Two further pieces of ISO 32000-1 §14.10 are also outside this API. Web Capture defines content sets — the /Contents structures that tie individual page pieces back to the source material they came from — and a name tree that maps digital identifiers to those content sets. PDF Library for Delphi maintains neither. What you get is the command list: the top-level answer to "where was this document fetched from, and how deep did the crawl go", which for provenance, audit, and archival-intake purposes is usually the part anyone actually asks about. If you need the URLs to be clickable for readers rather than merely recorded for auditors, that is a separate job — see building GoToR, GoToE, and Launch actions for the action side, and document and page lifecycle action triggers for firing behavior on open or close. Catalog /SpiderInfo command metadata as described here is part of PDF Library for Delphi, the native PDF library for Delphi and C++Builder