Skip to content

PDF/X Print Production

PDF/X (ISO 15930) is the standard for reliable exchange of print-ready PDF files. PDF Oxide validates all major PDF/X levels, checking page boxes, color spaces, transparency, ICC profiles, and output intents.

Binding coverage. PDF/X validation is exposed in Python (doc.validate_pdf_x(level)), Rust (validate_pdf_x + PdfXValidator builder), and Go (doc.ValidatePdfX(level)). WASM and C# do not yet surface PDF/X validation — use the Rust CLI (pdf-oxide validate --pdfx 4 doc.pdf) or invoke through one of the supported bindings.

Supported Levels

Level Standard Transparency RGB Layers External ICC External Graphics
X-1a:2001 ISO 15930-1 No No No No No
X-1a:2003 ISO 15930-4 No No No No No
X-3:2002 ISO 15930-3 No Yes No No No
X-3:2003 ISO 15930-6 No Yes No No No
X-4 ISO 15930-7 Yes Yes Yes No No
X-4p ISO 15930-7 Yes Yes Yes Yes No
X-5g ISO 15930-8 Yes Yes Yes No Yes
X-5n ISO 15930-8 Yes Yes Yes No Yes
X-5pg ISO 15930-8 Yes Yes Yes Yes Yes
X-6 ISO 15930-9 Yes Yes Yes No No
X-6n ISO 15930-9 Yes Yes Yes No Yes
X-6p ISO 15930-9 Yes Yes Yes Yes No

PDF/X-1a is the most restrictive: CMYK-only, no transparency, no layers. PDF/X-4 is the most commonly used modern level, allowing transparency and RGB with ICC profiles.

Quick Validation

from pdf_oxide import PdfDocument

doc = PdfDocument("document.pdf")
result = doc.validate_pdf_x("4")
print(f"Valid: {result.valid}")
use pdf_oxide::PdfDocument;
use pdf_oxide::compliance::pdf_x::{validate_pdf_x, PdfXLevel};

let mut doc = PdfDocument::open("print-ready.pdf")?;
let result = validate_pdf_x(&mut doc, PdfXLevel::X4)?;

if result.has_errors() {
    println!("Not PDF/X-4 compliant ({} errors):", result.errors.len());
    for error in &result.errors {
        println!("  [{}] {} (clause {})",
            error.code, error.message,
            error.clause.as_deref().unwrap_or("n/a"));
    }
} else {
    println!("Document is PDF/X-4 compliant");
}
doc, _ := pdfoxide.Open("print-ready.pdf")
defer doc.Close()

// Level encoding: 0 = X-1a:2001, 1 = X-1a:2003, 2 = X-3:2002, 3 = X-3:2003, 4 = X-4, ...
valid, errs, err := doc.ValidatePdfX(4) // PDF/X-4
if err != nil { log.Fatal(err) }

if valid {
    fmt.Println("Document is PDF/X-4 compliant")
} else {
    fmt.Printf("Not PDF/X-4 compliant (%d errors):\n", len(errs))
    for _, e := range errs {
        fmt.Printf("  %s\n", e)
    }
}

Validator API

The PdfXValidator builder configures the validation run:

use pdf_oxide::PdfDocument;
use pdf_oxide::compliance::pdf_x::{PdfXValidator, PdfXLevel};

let mut doc = PdfDocument::open("artwork.pdf")?;

let result = PdfXValidator::new(PdfXLevel::X1a2001)
    .stop_on_first_error(false)
    .include_warnings(true)
    .validate(&mut doc)?;

println!("Errors: {}", result.errors.len());
println!("Warnings: {}", result.warnings.len());
println!("Total issues: {}", result.total_issues());

What Gets Checked

XMP Identification

The validator confirms that XMP metadata declares the correct PDF/X version:

  • pdfxid:GTS_PDFXVersion must match the target level
  • The declared version is compared against the gts_pdfx_version() for the target level

Page Box Relationships

PDF/X requires specific nesting of page boxes:

TrimBox <= BleedBox <= MediaBox
ArtBox  <= MediaBox

The validator checks every page to ensure:

  • TrimBox is present (required by all PDF/X levels)
  • TrimBox is contained within BleedBox (if BleedBox is defined)
  • BleedBox is contained within MediaBox
  • ArtBox is contained within MediaBox (if ArtBox is defined)
  • Tolerance of 0.01 points for floating-point rounding

Transparency Detection

For PDF/X-1a and PDF/X-3, no transparency is permitted. The validator checks:

  • SMask in ExtGState dictionaries (must be /None or absent)
  • CA (stroke opacity) must equal 1.0
  • ca (fill opacity) must equal 1.0
  • BM (blend mode) must be Normal or Compatible

PDF/X-4 and later levels permit transparency.

Color Space Validation

The validator checks for device-dependent color usage:

  • DeviceRGB is not allowed in PDF/X-1a (CMYK-only)
  • DeviceRGB, DeviceCMYK, and DeviceGray used without an output intent trigger errors in stricter levels
  • Color operators rg, RG, k, K, g, G in page content streams are scanned

ICC Profile Validation

For ICCBased color spaces, the validator checks:

  • The profile stream contains the required /N (number of components) entry
  • The /N value matches the expected color space dimension (1 for gray, 3 for RGB, 4 for CMYK)
  • The profile data is present and non-empty

Output Intent

PDF/X requires an output intent that describes the intended print condition:

  • /OutputIntents array must be present in the document catalog
  • At least one entry with subtype GTS_PDFX is required
  • The output intent should reference an ICC profile or a registered print condition

XValidationResult

pub struct XValidationResult {
    pub level: PdfXLevel,
    pub errors: Vec<XComplianceError>,
    pub warnings: Vec<XComplianceError>,
    pub stats: XValidationStats,
}

XComplianceError

pub struct XComplianceError {
    pub code: XErrorCode,
    pub message: String,
    pub severity: XSeverity,
    pub page: Option<usize>,
    pub object_id: Option<u32>,
    pub clause: Option<String>,
}

Errors include the page number and object ID where the violation was found, making it straightforward to locate and fix issues in the source file.

XErrorCode Categories

The XErrorCode enum contains 40+ specific error codes organized by category:

Metadata: MissingOutputIntent, InvalidGtsPdfxVersion, MissingXmpIdentification

Page boxes: MissingTrimBox, TrimBoxOutsideBleedBox, BleedBoxOutsideMediaBox, ArtBoxOutsideMediaBox

Transparency: TransparencyNotAllowed, InvalidBlendMode, InvalidSMask, InvalidOpacity

Color: DeviceRgbNotAllowed, DeviceDependentColorWithoutIntent, InvalidIccProfile, MissingIccComponents

Content: ExternalContentNotAllowed, EncryptionNotAllowed, JavaScriptNotAllowed

PdfXLevel Methods

Method Return Description
iso_standard() &str ISO standard number (e.g., "ISO 15930-7")
required_pdf_version() &str Minimum PDF version (e.g., "1.6")
allows_transparency() bool Whether transparency groups are permitted
allows_rgb() bool Whether RGB color space is permitted
allows_layers() bool Whether optional content groups are permitted
allows_external_icc() bool Whether external ICC profiles are permitted
allows_external_graphics() bool Whether external graphics references are permitted
gts_pdfx_version() &str Expected GTS_PDFXVersion value
xmp_version() &str Expected XMP version identifier
from_gts_version(version) Option<Self> Parse level from a GTS version string

Practical Example: Prepress Check

use pdf_oxide::PdfDocument;
use pdf_oxide::compliance::pdf_x::{validate_pdf_x, PdfXLevel, XSeverity};

let mut doc = PdfDocument::open("magazine-cover.pdf")?;
let result = validate_pdf_x(&mut doc, PdfXLevel::X4)?;

println!("=== PDF/X-4 Prepress Report ===");
println!("Status: {}", if result.has_errors() { "REJECT" } else { "ACCEPT" });

// Group errors by severity
let critical: Vec<_> = result.errors.iter()
    .filter(|e| e.is_error())
    .collect();
let advisory: Vec<_> = result.warnings.iter().collect();

if !critical.is_empty() {
    println!("\nCritical ({}):", critical.len());
    for e in &critical {
        let page_str = e.page
            .map(|p| format!("page {}", p + 1))
            .unwrap_or_else(|| "document".into());
        println!("  [{}] {} ({})", e.code, e.message, page_str);
    }
}

if !advisory.is_empty() {
    println!("\nAdvisory ({}):", advisory.len());
    for w in &advisory {
        println!("  [{}] {}", w.code, w.message);
    }
}

Next Steps