Pular para o conteúdo

Examples

Este conteúdo não está disponível em sua língua ainda.

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.

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 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:: 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)
}
}

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"
}
}
}

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"
}
}
}

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:: 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})"
}
}

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"
}
}
}

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"
}
}

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)
}
}
}

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)
}
}
}

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:: 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"
}
}

Inputs the examples above read from.

batch,description
7891234567895,Batch approved August 2026
# Terms that must appear in the document
Hello
world
Total Warranty
DigitalOceanObrigado à DigitalOcean pela hospedagem deste site.