What is the PDFLang language?
PDFLang is a scripting language with one job: writing down what a correct PDF looks like, so a machine can check files against it.
Scripts use the extension .pdfl. They are run by pdfl, a command line tool
written in Rust.
The problem it solves
Section titled “The problem it solves”Somebody has to decide whether a PDF can go to press, be filed, or be sent to a client.
The rules behind that decision are concrete. Ink coverage under 300%. Every font embedded. A trim box on every page. The barcode matching the number printed beside it. No page rotated by accident.
Those rules usually live in three places at once: in the head of whoever has done the job longest, in a checklist nobody opens, and in a script only its author can maintain. Then the person leaves, the checklist goes stale, and the script becomes untouchable.
PDFLang exists so the rules can live in one file that the person who knows them can read.
What a script looks like
Section titled “What a script looks like”profile "press-ready" {
check "Ink within limit" tags: ["prepress"] { require prepress::validate_tac_limits(300) }
check "Fonts embedded" { doc.fonts.each { |font| assert font.is_embedded, "Font #{font.name} is not embedded" } }}Four words carry most of the language:
profilegroups the checks and names the set.checkis one rule, and becomes one section of the report.requirestops the check when a precondition is not met.assertrecords a finding, with the message you wrote.
Why it reads that way
Section titled “Why it reads that way”No classes, no inheritance, no type declarations, no semicolons. That is a decision rather than an omission: the reader it was designed for knows prepress, not programming.
The same intent shows in smaller places. Pages count from 1, the way everyone
outside a compiler counts them. Measurements take 3mm and 1in directly.
Percentages are written 300%.
What it can check
Section titled “What it can check”Seven namespaces, one per kind of work:
text::— extraction, normalization, search, validations, personal datastruct::— objects, metadata, XMP, security, hashingvisual::— resolution, visual comparison, perceptual hashing, qualityprepress::— ink coverage, separations, spot colours, fonts, boxescodes::— barcode and QR detection, decoding, validationfix::— boxes, pages, watermarks, merging and splitting, optimizationdata::— glossaries, datasets and lookup tables from outside the file
What comes back
Section titled “What comes back”A report. Abbreviated, one failing check looks like this:
{ "script_name": "prepress.pdfl", "input_file": "magazine.pdf", "status": "FAIL", "error_count": 2, "diagnostics": [ { "id": "PDFL-093751a2", "severity": "error", "check_name": "Ink coverage", "message": "page 7: 324% ink (limit 300%)" } ]}Look at that message. It is not a stack trace. It is a sentence somebody can
forward to the supplier who sent the file — which is why the language makes you
write it yourself, next to the rule it belongs to.
JSON is the default. CSV, HTML and PDF are there for people who need to read the report rather than parse it, and SARIF and JUnit for when a finding should land where the team already looks.
Running it in a pipeline
Section titled “Running it in a pipeline”pdfl run press-ready.pdfl magazine.pdf > report.jsonThe exit code carries the verdict, so a shell script or a CI job can branch on it without parsing anything:
| Code | Meaning |
|---|---|
0 |
everything passed |
1 |
warnings only |
2 |
validation errors |
3 |
a syntax error in the script |
10 |
the document could not be read at all |
The tool speaks English, your scripts need not
Section titled “The tool speaks English, your scripts need not”The messages pdfl prints itself — diagnostics, errors, command line help — are
in English, as command line tools generally are. This documentation is
translated into seven languages, but a failing check still reports
page 7: 324% ink (limit 300%).
The messages inside your own scripts are yours. Write them in whatever language the person reading the report speaks.
Where to start
Section titled “Where to start”The language manual is the one chapter worth reading start to finish. Everything after it is reference: a chapter per namespace, the CLI commands, and recipes built around complete cases — a print shop, a legal publisher, a laboratory, a CI pipeline.
If you would rather read code than prose, the examples are runnable profiles with their comments intact.