Skip to content

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.

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.

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:

  • profile groups the checks and names the set.
  • check is one rule, and becomes one section of the report.
  • require stops the check when a precondition is not met.
  • assert records a finding, with the message you wrote.

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%.

Seven namespaces, one per kind of work:

  • text:: — extraction, normalization, search, validations, personal data
  • struct:: — objects, metadata, XMP, security, hashing
  • visual:: — resolution, visual comparison, perceptual hashing, quality
  • prepress:: — ink coverage, separations, spot colours, fonts, boxes
  • codes:: — barcode and QR detection, decoding, validation
  • fix:: — boxes, pages, watermarks, merging and splitting, optimization
  • data:: — glossaries, datasets and lookup tables from outside the file

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.

Terminal window
pdfl run press-ready.pdfl magazine.pdf > report.json

The 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.

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.

DigitalOceanThanks to DigitalOcean for hosting this site.