Skip to content

6. `prepress::` namespace — prepress

30 functions covering what a print shop must verify before going to plate: ink coverage, color separations, fonts, strokes and page boxes.


TAC (Total Area Coverage) is the sum of all four inks at a single point. Going over the press limit causes smearing, slow drying and set-off between sheets. The usual limit for offset on coated stock is 300%.

There are two ways to measure it, and the difference matters:

prepress::calculate_exact_tac([page]) — the trustworthy number

Section titled “prepress::calculate_exact_tac([page]) — the trustworthy number”

Reads the colors declared in the file (the PDF color operators). This is the real value.

check "Ink limit" {
tac = prepress::calculate_exact_tac()
assert tac <= 300,
"ink coverage of #{tac}% exceeds the 300% limit"
// Per page, to locate the problem
doc.pages.each { |page|
value = prepress::calculate_exact_tac(page.number)
assert value <= 300,
"page #{page.number}: #{value}% ink"
}
}

prepress::calculate_tac([page]) — the estimate

Section titled “prepress::calculate_tac([page]) — the estimate”

Computed by rendering the page in RGB. It is a lower bound: dark neutral colors (rich black) collapse toward 100% in the estimate.

check "Comparing both methods" {
print("exact (declared in the file):", prepress::calculate_exact_tac(), "%")
print("estimated (by rendering):", prepress::calculate_tac(), "%")
// In real files under test: exact 324% vs estimated 299%
// — only the exact number reveals the file is over the limit.
}

Always use calculate_exact_tac to validate an ink limit. The estimate is for a quick read when colors are not declared.

True when every page is within the limit (default 300). Uses the render-based estimate.

check "Limit by paper profile" {
// newsprint takes far less ink than coated stock
assert prepress::validate_tac_limits(240),
"exceeds the 240% limit for newsprint"
}

Average ink coverage (%) — an indicator of consumption, not of smear risk.

check "Ink consumption" {
average = prepress::calculate_ink_coverage()
print("average coverage:", average, "%")
// Heavily covered pages make the print run more expensive
assert average < 200, "high average coverage: #{average}%"
}

prepress::calculate_tac_by_region(page, region)

Section titled “prepress::calculate_tac_by_region(page, region)”

[max_tac, average_coverage] within a specific area.

check "Ink on the fold" {
// Too much ink on the fold cracks during finishing
fold = region(290, 0, 15, 842, "center fold")
measured = prepress::calculate_tac_by_region(1, fold)
assert measured.first() < 240,
"TAC of #{measured.first()}% on the fold (max 240%)"
print("average on the fold:", measured.last(), "%")
}

Lists special inks (Pantone, varnish, die lines) declared as Separation or DeviceN.

The reserved separations All and None are excluded — All is a registration mark, not an ink.

check "Special colors" {
spots = prepress::detect_spot_colors()
print("special inks:", spots.join(", "))
// A job quoted as 4-color cannot carry extra inks
assert spots.length == 0,
"file uses an unquoted special ink: #{spots.join(", ")}"
}
check "Varnish expected" {
// When the special ink IS expected
spots = prepress::detect_spot_colors()
assert spots.contains("Varnish"),
"the spot varnish layer is missing"
}

Returns "CMYK", "RGB", "Mixed", "None" or "Other", based on the images.

check "Document color mode" {
mode = prepress::detect_color_mode()
assert mode == "CMYK" || mode == "None",
"document is #{mode} — offset printing requires CMYK"
}

True when all images are in the given space.

check "Everything in CMYK" {
assert prepress::validate_color_space("DeviceCMYK"),
"there are images outside CMYK"
}

prepress::compare_colors_delta_e(color_a, color_b)

Section titled “prepress::compare_colors_delta_e(color_a, color_b)”

Perceptual difference between two colors (Delta-E CIE76). Colors are lists: 4 values = CMYK, 3 = RGB, 1 = gray.

Rule of thumb: ΔE below 1 is imperceptible; up to 3 is acceptable in print; above 5 is visibly different.

check "Brand color" {
// The approved corporate blue
brand = [1.0, 0.6, 0.0, 0.1]
used = [1.0, 0.62, 0.0, 0.12]
difference = prepress::compare_colors_delta_e(brand, used)
assert difference < 3.0,
"brand color out of tolerance (ΔE #{difference})"
}

True when black is built from several inks (K ≥ 60% with C+M+Y ≥ 20%).

check "Correct black for text" {
// Small text in rich black shows registration wobble
assert !prepress::detect_rich_black(),
"rich black found — use flat black (0/0/0/100) for text"
}

True when no overprint is enabled.

check "Overprint" {
// Accidental overprint makes elements disappear on press
assert prepress::validate_overprint_settings(),
"overprint is enabled — verify that it is intentional"
}

Without an argument: true when an Output Intent is declared. With a name: true when the intent contains that text.

check "Output profile" {
assert prepress::validate_output_intent(),
"PDF has no Output Intent — the shop cannot know the target color profile"
// Requiring a specific profile
assert prepress::validate_output_intent("Coated FOGRA39"),
"Output Intent differs from the shop standard"
}

prepress::check_rendering_intent([expected])

Section titled “prepress::check_rendering_intent([expected])”

Without an argument: lists the declared intents. With one: true when all of them match.

check "Rendering intent" {
print("intents in the file:", prepress::check_rendering_intent().join(", "))
assert prepress::check_rendering_intent("RelativeColorimetric"),
"rendering intent differs from the production standard"
}

Very thin lines disappear in print or come out uneven.

True when a stroke falls below the limit (default 0.25 pt).

check "No hairlines" {
assert !prepress::detect_hairlines(0.25),
"there are strokes below 0.25 pt — they will disappear in print"
}

True when a stroke has zero width — the classic PostScript hairline, which the press renders at the device minimum (unpredictable).

check "Zero-width stroke" {
assert !prepress::detect_hairlines_exact(),
"there is a stroke with 0 width — set a real thickness"
}

Like detect_hairlines, with a wider limit (default 1 pt).

check "Thin lines over color" {
// Over a background, lines below 1pt vanish
assert !prepress::detect_fine_lines(1.0),
"there are lines below 1 pt"
}

prepress::validate_minimum_stroke_width(minimum)

Section titled “prepress::validate_minimum_stroke_width(minimum)”

True when no stroke falls below the required minimum.

check "Minimum thickness in the contract" {
assert prepress::validate_minimum_stroke_width(0.5),
"the shop contract requires strokes of at least 0.5 pt"
}

Names of the fonts in use.

check "Font inventory" {
fonts = prepress::list_fonts()
print("fonts:", fonts.join(", "))
assert fonts.length <= 8,
"#{fonts.length} different fonts — inconsistent design?"
}

True when every font is embedded.

check "Embedded fonts" {
assert prepress::validate_font_embedding(),
"there are non-embedded fonts — the text will change at the RIP"
}

Lists the non-embedded fonts, which the reader will substitute.

check "Which fonts are missing" {
missing = prepress::detect_text_substitution()
assert missing.length == 0,
"fonts not embedded: #{missing.join(", ")}"
}

Lists fonts with no widths table — the reader has to guess the metrics, which misaligns the text.

check "Complete metrics" {
problems = prepress::detect_missing_glyphs()
assert problems.length == 0,
"fonts without a widths table: #{problems.join(", ")}"
}

True when every embedded font is subset (only the glyphs actually used), which keeps the file lean.

check "Fonts are subset" {
assert prepress::subset_fonts(),
"a full font is embedded — the file is larger than it needs to be"
}

Lists fonts that carry licensing risk: Type3 or non-embedded.

check "Licensing" {
risky = prepress::check_font_licensing()
assert risky.length == 0,
"fonts with licensing risk: #{risky.join(", ")}"
}

True when no text falls below the minimum size (default 6 pt).

check "Legibility" {
// Regulators require minimum body size on package inserts;
// contracts often have similar requirements
assert prepress::validate_font_size(6),
"there is text below 6 pt — illegible once printed"
}

PDF boxes define the working areas: MediaBox (sheet), BleedBox (bleed), TrimBox (final trim), CropBox (viewing), ArtBox (content).

[width, height] in points.

check "Format" {
size = prepress::get_page_size(1)
print("page 1:", size.first(), "x", size.last(), "pt")
assert abs(size.first() - 595.0) < 5, "width is outside A4"
}

List of the defined boxes, formatted as text.

check "Boxes on the first page" {
prepress::get_page_boxes(1).each { |box| print(box) }
// Sample output:
// MediaBox: [0, 0, 467.2, 665.6]
// TrimBox: [35.2, 35.2, 432, 630.4]
}

validate_media_box(), validate_trim_box(), validate_bleed_box()

Section titled “validate_media_box(), validate_trim_box(), validate_bleed_box()”

True when the box exists on every page.

check "Boxes required for printing" {
require prepress::validate_media_box()
assert prepress::validate_trim_box(),
"no TrimBox — the shop cannot know where to trim"
assert prepress::validate_bleed_box(),
"no BleedBox — no bleed area is defined"
}

True when the BleedBox exceeds the TrimBox by the given margin on all sides, on every page. Default: 3 mm.

check "Sufficient bleed" {
// Use unit literals: readable, and conversion is automatic
assert prepress::check_page_geometry(3mm),
"bleed smaller than 3 mm on some page"
// Packaging shops usually demand more
assert prepress::check_page_geometry(5mm),
"this shop requires 5 mm of bleed"
}

// offset_magazine.pdfl — full preflight for offset printing
// Usage: pdfl run offset_magazine.pdfl magazine.pdf --output html --output-file report.html
profile "offset-magazine" {
const TAC_LIMIT = 300%
const BLEED = 3mm
const MIN_DPI = 300
check "Ink coverage" tags: ["prepress", "colors"] {
// Always the exact figure when validating a limit
doc.pages.each { |page|
tac = prepress::calculate_exact_tac(page.number)
assert tac <= TAC_LIMIT,
"page #{page.number}: #{tac}% ink (limit #{TAC_LIMIT}%)"
}
print("average coverage:", prepress::calculate_ink_coverage(), "%")
}
check "Colors" tags: ["prepress", "colors"] {
assert prepress::detect_color_mode() != "RGB", "document is in RGB"
spots = prepress::detect_spot_colors()
assert spots.length == 0, "unquoted special ink: #{spots.join(", ")}"
assert !prepress::detect_rich_black(), "rich black in text"
assert prepress::validate_output_intent(), "no Output Intent"
}
check "Fonts" tags: ["fonts"] {
missing = prepress::detect_text_substitution()
assert missing.length == 0, "fonts not embedded: #{missing.join(", ")}"
assert prepress::validate_font_size(6), "text below 6 pt"
print("fonts:", prepress::list_fonts().join(", "))
}
check "Strokes" tags: ["prepress"] {
assert !prepress::detect_hairlines(0.25), "strokes below 0.25 pt"
assert !prepress::detect_hairlines_exact(), "stroke with 0 width"
}
check "Geometry" tags: ["prepress", "boxes"] {
require prepress::validate_trim_box()
require prepress::validate_bleed_box()
assert prepress::check_page_geometry(BLEED),
"bleed smaller than 3 mm"
}
check "Images" tags: ["images"] {
doc.images.each { |img|
assert img.dpi >= MIN_DPI,
"image on page #{img.page_number}: #{round(img.dpi)} DPI"
assert img.color_space != "DeviceRGB",
"RGB image on page #{img.page_number}"
}
}
}

visual:: · Index · Next: codes::

DigitalOceanThanks to DigitalOcean for hosting this site.