Examples
Dieser Inhalt ist noch nicht in deiner Sprache verfügbar.
Every file below is a runnable profile from the examples/ directory of
the pdflang repository, reproduced with its comments. Copy one out, point
it at a PDF, and it works — the usage line at the top of each says how.
barcodes.pdfl
Section titled “barcodes.pdfl”codes:: namespace demonstration
// barcodes.pdfl — codes:: namespace demonstration// Usage: pdfl run examples/barcodes.pdfl document.pdf
profile "barcode-validation" {
check "Batch Code" tags: ["codes"] { require codes::detect_barcodes() code = codes::decode_barcode(1) print("Code detected:", codes::get_barcode_type(1), "=", code) assert codes::validate_barcode_checksum(1), "invalid check digit in code #{code}" }
check "Code Matches the Text" tags: ["codes"] { assert codes::compare_barcode_with_text(), "the barcode content does not appear in the document text" }
check "EAN-13 Format" tags: ["codes"] { assert codes::validate_barcode_format("^\\d{13}$"), "code in an unexpected format (expected 13 digits)" }}basic.pdfl
Section titled “basic.pdfl”basic PDF validation
// basic.pdfl — basic PDF validation// Usage: pdfl run examples/basic.pdfl document.pdf --output json
profile "basic-validation" {
const A4_WIDTH = 595.0 const A4_HEIGHT = 842.0 const TOLERANCE = 5.0
check "Structure" tags: ["basic"] { require doc.page_count > 0 }
check "Metadata" tags: ["metadata"] { assert doc.title != "", "PDF has no title in its metadata" assert doc.author != "", "PDF has no author in its metadata" }
check "Embedded Fonts" tags: ["fonts"] { doc.fonts.each { |font| assert font.is_embedded, "Font #{font.name} is not embedded" } }
check "Text Present" tags: ["text"] { assert doc.extract_text().trim().length > 0, "PDF has no text (blank pages?)" }
check "A4 Dimensions" tags: ["basic"] { doc.pages.each { |page| assert abs(page.width - A4_WIDTH) < TOLERANCE, "Page #{page.number}: width #{page.width}pt outside A4" assert abs(page.height - A4_HEIGHT) < TOLERANCE, "Page #{page.number}: height #{page.height}pt outside A4" } }}data.pdfl
Section titled “data.pdfl”data:: namespace demonstration
// data.pdfl — data:: namespace demonstration// Usage: pdfl run examples/data.pdfl document.pdf// (run from the project root: the paths are relative)
profile "validation-with-references" {
check "Required Terms" tags: ["glossary"] { missing = data::validate_against_reference("examples/glossary.txt") assert missing.length == 0, "glossary terms missing from the document: #{missing.join(", ")}" }
check "Approved Batch" tags: ["data"] { // cross-checks the barcode in the PDF against the local batch table code = codes::decode_barcode(1) description = data::lookup_value("examples/batches.csv", code) assert description, "code #{code} is not in the batch table" print("Batch found:", description) }}images.pdfl
Section titled “images.pdfl”visual:: namespace demonstration
// images.pdfl — visual:: namespace demonstration// Usage: pdfl run examples/images.pdfl document.pdf
profile "image-validation" {
const MIN_DPI = 300
check "Inventory" tags: ["images"] { require visual::detect_images() print("Images found:", visual::count_images()) print("Color spaces:", visual::detect_image_color_space().join(", ")) }
check "Minimum Resolution" tags: ["images", "prepress"] { doc.images.each { |img| assert img.dpi >= MIN_DPI, "Image #{img.width}x#{img.height}px on page #{img.page_number} at #{img.dpi} DPI (minimum #{MIN_DPI})" } }
check "Color Mode" tags: ["prepress"] { // for offset printing, RGB images need conversion doc.images.each { |img| assert img.color_space != "DeviceRGB", "RGB image on page #{img.page_number} — convert to CMYK before printing" } }}intake.pdfl
Section titled “intake.pdfl”receiving desk: why to reject a resubmitted file
// intake.pdfl — receiving desk: why to reject a resubmitted file// Usage:// pdfl run examples/intake.pdfl resubmitted.pdf --var accepted=last-accepted.pdf//// A supplier sends a corrected file. Before it goes into production someone has// to decide whether this is the same document with the fix in it, or something// else wearing the same name.//// "Page 4 is different" is not a useful answer. The checks below separate the// reasons, because they lead to different conversations: a page that moved is a// layout slip, a page that was replaced is content nobody approved, and a page// that lost quality means the file was exported the wrong way.
profile "intake-review" {
// Below this, the page is not the page that was accepted. const SAME = 0.995
// A page that turns up at another position at this similarity was moved // rather than rewritten. const MOVED = 0.98
// Is this page where it was? function in_place(n) { visual::measure_ssim(n, vars.accepted, n) >= SAME }
// Does it appear at some *other* position in the accepted file? That is what // separates "moved" from "rewritten". function found_elsewhere(n) { doc.pages.any { |other| other.number != n && visual::measure_ssim(n, vars.accepted, other.number) >= MOVED } }
check "Nothing was reordered" tags: ["intake", "rejection"] { // Read it as: either the page is where it belongs, or it is not anywhere // else either. // // The first half matters. Without it, a file with two blank pages would // report them as swapped forever, because blank pages are identical to one // another — a false alarm, which this language treats as a defect. doc.pages.each { |page| assert in_place(page.number) || !found_elsewhere(page.number), "page #{page.number} is not in its place but exists elsewhere in the accepted file — the pages were reordered" } }
check "Nothing was rewritten" tags: ["intake", "rejection"] { // The complement: out of place *and* nowhere else means this page was not // moved, it was replaced. doc.pages.each { |page| assert in_place(page.number) || found_elsewhere(page.number), "page #{page.number} was rewritten — this was not in the correction request" } }
check "No image swapped" tags: ["intake", "rejection"] { // Perceptual hash catches a replaced photo that structural similarity can // average away on a busy page — a label, a price, a certification mark. // // Skipped for a page that merely moved: the image there does differ from // the image at that position, but the reordering check already said why, // and repeating it buries the finding that matters. doc.pages.each { |page| assert !visual::detect_image_replacement(page.number, vars.accepted, page.number) || found_elsewhere(page.number), "page #{page.number}: an image was replaced" } }
check "Quality survived the round trip" tags: ["intake", "rejection"] { // A file re-saved instead of re-exported arrives visually similar and // technically worse. Easiest rejection reason to miss by eye, most // expensive to find on press. doc.pages.each { |page| assert !visual::detect_image_artifacts(page.number), "page #{page.number}: compression artifacts — re-export from the source, do not re-save the PDF" } }
check "Flat areas still flat" tags: ["intake"] severity: warning { doc.pages.each { |page| assert !visual::detect_posterization(page.number), "page #{page.number}: posterization in a flat tint or gradient" } }}normalize.pdfl
Section titled “normalize.pdfl”fix:: namespace demonstration
// normalize.pdfl — fix:: namespace demonstration// Usage: pdfl fix document.pdf examples/normalize.pdfl --output fixed.pdf
profile "basic-normalization" {
// Boxes for A4 printing with 8.5pt (~3mm) of bleed fix::set_trim_box(8.5, 8.5, 586.5, 833.5) fix::set_bleed_box(0, 0, 595, 842)
// Duplicates the cover and numbers the pages fix::duplicate_page(1) fix::add_page_numbers() fix::add_watermark("DRAFT")
// A check can validate the document BEFORE the fixes check "Preconditions" { require doc.page_count > 0 }}prepress.pdfl
Section titled “prepress.pdfl”prepress:: namespace demonstration
// prepress.pdfl — prepress:: namespace demonstration// Usage: pdfl run examples/prepress.pdfl document.pdf
profile "offset-prepress" {
const TAC_LIMIT = 300% const BLEED = 3mm
check "Ink Coverage" tags: ["prepress"] { doc.pages.each { |page| assert prepress::calculate_tac(page) <= TAC_LIMIT, "Page #{page.number}: TAC #{page.tac} above the limit of #{TAC_LIMIT}%" } print("Average ink coverage:", prepress::calculate_ink_coverage(), "%") }
check "Thin Lines" tags: ["prepress"] { assert !prepress::detect_hairlines(0.25), "hairline detected (strokes below 0.25pt disappear in print)" }
check "Fonts" tags: ["fonts"] { assert prepress::validate_font_embedding(), "fonts not embedded: #{doc.fonts.filter { |f| !f.is_embedded }.map { |f| f.name }.join(", ")}" }
check "Boxes and Bleed" tags: ["prepress", "boxes"] { require prepress::validate_trim_box() require prepress::validate_bleed_box() assert prepress::check_page_geometry(BLEED), "bleed smaller than 3mm on some page" }
check "Color Mode" tags: ["prepress"] { mode = prepress::detect_color_mode() assert mode != "RGB" && mode != "Mixed", "document contains RGB images (detected mode: #{mode})" }}proof.pdfl
Section titled “proof.pdfl”print shop: the press file against the approved proof
// proof.pdfl — print shop: the press file against the approved proof// Usage:// pdfl run examples/proof.pdfl press-ready.pdf --var approved=signed.pdf//// The client signed off on a proof. Days later a "final" file arrives, and the// question is whether anything moved since the signature — a swapped photo, a// price corrected without telling anyone, an image degraded by a round trip// through the wrong export preset.//// The proof is passed in rather than written here, because the same profile// has to serve every job that comes through the shop.
profile "press-file-vs-approved-proof" {
// Below this, the page is not the page that was approved. const IDENTICAL = 0.995
// Reprinting costs money; a page that only *nearly* matches is worth a look // before it goes on press, not after. const SUSPICIOUS = 0.999
check "Page count" tags: ["proof"] { // Compared page by page below, so a mismatch here would make every // later comparison meaningless. assert doc.page_count == 8, "the approved proof has 8 pages, this file has #{doc.page_count}" }
check "Nothing moved since sign-off" tags: ["proof"] { doc.pages.each { |page| ssim = visual::measure_ssim(page.number, vars.approved) assert ssim >= IDENTICAL, "page #{page.number} differs from the approved proof (SSIM #{ssim})" } }
check "Close but not identical" tags: ["proof"] severity: warning { // Sub-pixel differences are normal between exports. A page that lands // between the two thresholds is usually a re-rendered font or a nudged // element — worth a human look, not worth stopping the press. doc.pages.each { |page| ssim = visual::measure_ssim(page.number, vars.approved) assert ssim >= SUSPICIOUS || ssim < IDENTICAL, "page #{page.number} is nearly identical but not exactly (SSIM #{ssim}) — check it" } }
check "No image swapped" tags: ["proof"] { // Perceptual hash catches a replaced photo that SSIM might average away // on a busy page. doc.pages.each { |page| assert !visual::detect_image_replacement(page.number, vars.approved), "page #{page.number}: an image looks replaced since the proof" } }
check "Not damaged by re-export" tags: ["quality"] { // A file that went through a JPEG round trip arrives with block edges the // proof never had. doc.pages.each { |page| assert !visual::detect_image_artifacts(page.number), "page #{page.number}: compression artifacts — re-export from the source" } }
check "Gradients still smooth" tags: ["quality"] severity: warning { // Banding survives to paper and is expensive to discover there. doc.pages.each { |page| assert !visual::detect_banding(page.number), "page #{page.number}: banding in a gradient" } }}regions.pdfl
Section titled “regions.pdfl”region type and rule block demonstration
// regions.pdfl — region type and rule block demonstration// Usage: pdfl run examples/regions.pdfl document.pdf
profile "regions" { check "Text by Region" { // adjust the coordinates to your page size (in points) header = region(0, 600, 467, 66, "header") footer = region(0, 0, 467, 60, "footer") print("header:", text::extract_from_region(3, header).trim()) print("footer:", text::extract_from_region(3, footer).trim()) print("TAC in the footer region:", prepress::calculate_tac_by_region(3, footer).first(), "%") }
rule "Numbered Footer" on doc.pages.filter { |p| p.number > 2 } { footer = region(0, 0, 467, 60) content = text::extract_from_region(page.number, footer).trim() assert content != "", "page #{page.number} has nothing in the footer" }}reprint.pdfl
Section titled “reprint.pdfl”publisher: a reprint against the edition already on shelves
// reprint.pdfl — publisher: a reprint against the edition already on shelves// Usage:// pdfl run examples/reprint.pdfl 3rd.pdf --var previous=2nd-printing.pdf//// A reprint is supposed to be the same book. In practice one page is meant to// change — the colophon carries the new print run and date — and every other// page changing is a mistake someone will find after ten thousand copies.//// The interesting part is the asymmetry: one page must differ, the rest must// not. The edition that shipped is passed in, so one profile serves every// title on the list.
profile "reprint-against-shipped-edition" {
// The page carrying the print run and date. const COLOPHON = 4
const IDENTICAL = 0.995
// The cover is reproduced at a larger size than anything inside, so a drop in // quality shows there first. const COVER_QUALITY = 70.0
rule "Body pages unchanged" on doc.pages.filter { |p| p.number != COLOPHON } { ssim = visual::measure_ssim(page.number, vars.previous) assert ssim >= IDENTICAL, "page #{page.number} changed since the previous printing (SSIM #{ssim})" }
check "The colophon did change" tags: ["reprint"] { // If it did not, this file is the old edition under a new name — the // reprint statement was never updated. changed = visual::measure_ssim(COLOPHON, vars.previous) < IDENTICAL assert changed, "the colophon on page #{COLOPHON} is identical to the previous printing — was it updated?" }
check "Cover quality held up" tags: ["reprint", "cover"] { quality = visual::estimate_image_quality(1) assert quality >= COVER_QUALITY, "cover quality #{quality} is below #{COVER_QUALITY} — check the source image" }
check "Cover free of banding" tags: ["cover"] { // Flat tints and gradients on a cover band visibly at trade-paperback size. assert !visual::detect_banding(1), "banding on the cover" assert !visual::detect_posterization(1), "posterization on the cover" }
check "How far each page moved" tags: ["reprint"] { // Reports rather than judges: every page's distance goes to stderr, so a // human deciding what to look at can see which pages drifted and by how // much. Nothing here can fail — the judging is done by the checks above. doc.pages.each { |page| similarity = visual::diff_pages(page.number, vars.previous) print("page", page.number, "similarity", similarity) } }}scope.pdfl
Section titled “scope.pdfl”did the correction stay inside what was asked for?
// scope.pdfl — did the correction stay inside what was asked for?// Usage:// pdfl run examples/scope.pdfl corrected.pdf --var previous=before-correction.pdf//// A supplier was asked to fix a price on one page. The file comes back and// twelve pages are different. Whatever happened, this is no longer the change// that was approved, and nobody should be reading twelve diffs to find out why.//// This is a gate, not a diagnosis: it decides whether the file is worth// reviewing at all. Run examples/intake.pdfl afterwards to find out what// actually changed on the pages it names.
profile "scope-of-change" {
const SAME = 0.995
// A correction that touches more pages than this goes back for a new // approval round instead of through the usual review. const MAX_CHANGED = 2
check "Change stayed within scope" tags: ["scope", "rejection"] { changed = doc.pages.filter { |page| visual::measure_ssim(page.number, vars.previous, page.number) < SAME }
assert changed.length <= MAX_CHANGED, "#{changed.length} pages changed, at most #{MAX_CHANGED} was agreed — this needs a new approval round, not a review" }
check "Something did change" tags: ["scope"] { // The opposite failure, and the quieter one: a file resubmitted unchanged // because the fix was made in the wrong copy. It passes every comparison, // which is exactly why it slips through. changed = doc.pages.filter { |page| visual::measure_ssim(page.number, vars.previous, page.number) < SAME }
assert changed.length > 0, "nothing changed from the previous file — was the correction saved in another copy?" }
check "Which pages moved" tags: ["scope"] { // Reports, does not judge. Gives the reviewer the list to open. doc.pages.each { |page| similarity = visual::diff_pages(page.number, vars.previous) print("page", page.number, "similarity", similarity) } }}structure.pdfl
Section titled “structure.pdfl”struct:: namespace demonstration
// structure.pdfl — struct:: namespace demonstration// Usage: pdfl run examples/structure.pdfl document.pdf
profile "structure-validation" {
check "Required Metadata" tags: ["metadata"] { assert struct::get_title() != "", "PDF has no title in its metadata" assert struct::get_creation_date() != "", "PDF has no creation date" print("Metadata found:", struct::list_metadata_entries().join("; ")) }
check "File Size" tags: ["structure"] { // files above 1 MB per page suggest bloat assert !struct::detect_file_bloat(1024), "bloated file: #{struct::file_size() / 1024} KB for #{doc.page_count} page(s)" }
check "Content" tags: ["structure"] { require struct::count_objects() > 0 }
check "Traceability" tags: ["audit"] { // the hash goes into the report for the audit trail print("SHA-256:", struct::calculate_sha256()) require struct::calculate_sha256().length == 64 }}text.pdfl
Section titled “text.pdfl”text:: namespace demonstration
// text.pdfl — text:: namespace demonstration// Usage: pdfl run examples/text.pdfl document.pdf
profile "text-validation" {
check "Required Content" tags: ["text"] { require text::count_words() > 0 assert text::require_text("mundo"), "word 'mundo' not found in the document" assert text::forbid_text("confidential"), "document contains the forbidden term 'confidential'" }
check "Patterns" tags: ["text"] { // dates in dd/mm/yyyy format are forbidden in this example assert text::forbid_match("\\d{2}/\\d{2}/\\d{4}"), "document contains a date in a forbidden format" }
check "Personal Data" tags: ["pii"] { found = text::detect_personal_data() assert found.length == 0, "personal data found: #{found.join(", ")}" }
check "Language" tags: ["text"] { language = text::detect_language() assert language != "unknown", "could not detect the document language" }}Data files
Section titled “Data files”Inputs the examples above read from.
batches.csv
Section titled “batches.csv”batch,description7891234567895,Batch approved August 2026glossary.txt
Section titled “glossary.txt”# Terms that must appear in the documentHelloworldTotal Warranty