A practical guide for Rust developers working with PDF/A standards. Learn how to convert, validate, and correct documents for long-term archiving.
Convert a standard PDF into a PDF/A archiving format. PDFluent embeds fonts, adds XMP metadata, and fixes common compliance issues automatically.
use pdfluent::{PdfDocument, PdfALevel, ConvertOptions};
fn main() -> Result<(), Box<dyn std::error::Error>> {
let mut doc = PdfDocument::open("report.pdf")?;
let opts = ConvertOptions::default()
.level(PdfALevel::PdfA2b)
.embed_missing_fonts(true);
let report = doc.convert_to_pdf_a(&opts)?;
doc.save("report_pdfa2b.pdf")?;
println!("Converted. Remaining issues: {}", report.unresolved.len());
Ok(())
}Open the document you want to convert. PDFluent will modify it in place before you save it.
use pdfluent::PdfDocument;
let mut doc = PdfDocument::open("report.pdf")?;Choose the target PDF/A level and configure automatic fixes. embed_missing_fonts will substitute missing fonts with embedded versions where possible.
use pdfluent::{PdfALevel, ConvertOptions};
let opts = ConvertOptions::default()
.level(PdfALevel::PdfA2b)
.embed_missing_fonts(true)
.add_xmp_metadata(true)
.remove_javascript(true)
.remove_transparency(false); // only needed for PDF/A-1convert_to_pdf_a() applies all automatic fixes and returns a ConversionReport listing what was fixed and what could not be fixed automatically.
let report = doc.convert_to_pdf_a(&opts)?;
println!("Fixed: {}", report.fixes.len());
for fix in &report.fixes {
println!(" + {}", fix.description);
}Some problems cannot be fixed automatically. Check report.unresolved before saving to decide whether to proceed.
if !report.unresolved.is_empty() {
eprintln!("Unresolved issues:");
for issue in &report.unresolved {
eprintln!(" ! {}", issue.message);
}
}Save to a new path to keep the original intact.
doc.save("report_pdfa2b.pdf")?;Check whether a PDF meets PDF/A-1b, PDF/A-2b, or PDF/A-3b archiving requirements. Get a structured list of violations with page and object references.
use pdfluent::{PdfDocument, PdfAProfile};
fn main() -> pdfluent::Result<()> {
let doc = PdfDocument::open("invoice.pdf")?;
let report = doc.validate_pdfa(PdfAProfile::A2b)?;
if report.is_compliant() {
println!("PDF/A-2B compliant");
}
Ok(())
}Open the document you want to validate. The validator reads the entire structure, so larger files take slightly longer.
use pdfluent::PdfDocument;
let doc = PdfDocument::open("archive_candidate.pdf")?;PDFluent supports PDF/A-1b, PDF/A-2b, and PDF/A-3b. Choose the level your archiving policy requires.
use pdfluent::PdfAProfile;
// Options: PdfAProfile::A1b, A2b, A3b
let level = PdfAProfile::A2b;Call validate_pdf_a() with the level. It returns a ValidationReport.
let report = doc.validate_pdfa(level)?;report.is_conformant is true when no violations were found. report.violations is a Vec<Violation> with details for each issue.
println!("Compliant: {}", report.is_compliant());
println!("Violations: {}", report.violations.len());
for v in &report.violations {
println!(
"Rule {} on page {:?}: {}",
v.rule, v.page, v.message,
);
}In a CI pipeline, return exit code 1 when violations are found so the build fails automatically.
if !report.is_compliant() {
eprintln!("{} violation(s) found", report.violations.len());
std::process::exit(1);
}Read the XMP metadata to determine whether a PDF claims PDF/A-1, 2, or 3 conformance, and run a structural validation check.
use pdfluent::{PdfDocument, PdfAProfile};
fn main() -> pdfluent::Result<()> {
let doc = PdfDocument::open("invoice.pdf")?;
let report = doc.validate_pdfa(PdfAProfile::A2b)?;
println!("compliant: {}", report.is_compliant());
Ok(())
}Read-only access is sufficient for conformance checking.
let doc = PdfDocument::open("archive.pdf")?;pdf_a_level() inspects the pdfaid:part and pdfaid:conformance XMP fields. It returns the declared level, not a validated one.
use pdfluent::PdfAProfile;
let report = doc.validate_pdfa(PdfAProfile::A2b)?;
println!("PDF/A-2b compliant: {}", report.is_compliant());validate_pdf_a() checks the rules for the claimed level: embedded fonts, no encryption, no transparency (A-1), color spaces, output intent, and XMP metadata.
let report = doc.validate_pdfa(PdfAProfile::A2b)?;
println!("Compliant: {}", report.is_compliant());
println!("Violations: {}", report.violations.len());Each ValidationError carries a rule ID (e.g. "6.3.3-1"), a human-readable message, and optionally the object number of the offending PDF object.
for v in &report.violations {
println!("[{}] {} (page {:?})", v.rule, v.message, v.page);
}To validate against a target level regardless of the XMP claim, pass it explicitly.
use pdfluent::PdfAProfile;
// Validate against a specific profile (A1b, A2b, or A3b)
let report = doc.validate_pdfa(PdfAProfile::A1b)?;
println!("A1b compliant: {}", report.is_compliant());Auto-repair the most frequent PDF/A conformance problems: missing output intent, unembedded fonts, transparency, and missing XMP metadata.
use pdfluent::{PdfDocument, pdfa::{PdfALevel, PdfAFixOptions}};
fn main() -> pdfluent::Result<()> {
let mut doc = PdfDocument::open("noncompliant.pdf")?;
let opts = PdfAFixOptions::for_level(PdfALevel::A2b)
.embed_missing_fonts(true)
.flatten_transparency(true)
.add_xmp_metadata(true);
let report = doc.fix_pdf_a_errors(opts)?;
println!("Fixed {} issue(s)", report.fixed_count());
doc.save("fixed.pdf")?;
Ok(())
}Run validate_pdf_a() before fixing to get a baseline list of issues.
let doc = PdfDocument::open("noncompliant.pdf")?;
let before = doc.validate_pdf_a()?;
println!("Errors before: {}", before.error_count());
for e in before.errors() {
println!(" [{}] {}", e.rule_id(), e.message());
}PdfAFixOptions controls which repairs are attempted. Each fix is opt-in to avoid unintended side effects.
use pdfluent::pdfa::{PdfALevel, PdfAFixOptions};
let opts = PdfAFixOptions::for_level(PdfALevel::A2b)
.embed_missing_fonts(true)
.flatten_transparency(true)
.add_xmp_metadata(true)
.add_output_intent_if_missing(true)
.remove_javascript(true)
.remove_embedded_files(false); // keep attachments for PDF/A-3fix_pdf_a_errors returns a FixReport listing what was changed. Inspect it to confirm each fix was applied.
let mut doc = PdfDocument::open("noncompliant.pdf")?;
let report = doc.fix_pdf_a_errors(opts)?;
for fix in report.applied_fixes() {
println!("Fixed: {}", fix.description());
}
for skipped in report.skipped_fixes() {
println!("Skipped (manual): {}", skipped.description());
}Confirm no errors remain. Some issues (e.g. non-embeddable fonts) require manual intervention.
let after = doc.validate_pdf_a()?;
if after.is_conformant() {
println!("Document is now PDF/A-2b conformant.");
} else {
println!("Remaining errors: {}", after.error_count());
}Write the fixed file. The output is a full save, not an incremental update.
doc.save("fixed_a2b.pdf")?;Embed an ICC color profile as the /OutputIntent to satisfy the PDF/A requirement for a declared color space.
use pdfluent::{PdfDocument, pdfa::OutputIntent};
fn main() -> pdfluent::Result<()> {
let mut doc = PdfDocument::open("input.pdf")?;
// Use the built-in sRGB IEC61966-2.1 profile
let intent = OutputIntent::srgb();
doc.set_output_intent(intent)?;
doc.save("with_output_intent.pdf")?;
Ok(())
}The output intent is a document-level property, not page-level. Open with mutable access.
let mut doc = PdfDocument::open("input.pdf")?;PDFluent ships sRGB IEC61966-2.1 and ISO Coated v2 (FOGRA39) profiles. These cover the vast majority of PDF/A archiving requirements.
use pdfluent::pdfa::OutputIntent;
// For screen/web documents:
let srgb = OutputIntent::srgb();
// For print/CMYK documents:
let fogra39 = OutputIntent::iso_coated_v2();If you have a specific ICC profile file, load the bytes and construct an OutputIntent from them.
let icc_bytes = std::fs::read("custom_profile.icc")?;
let intent = OutputIntent::from_icc_bytes(
&icc_bytes,
"Custom CMYK Profile", // OutputConditionIdentifier
"GTS_PDFA1", // S (subtype)
)?;set_output_intent replaces any existing output intent. A PDF/A document must have exactly one.
doc.set_output_intent(intent)?;Re-validate to confirm the output intent requirement is now satisfied.
let report = doc.validate_pdf_a()?;
let output_intent_errors: Vec<_> = report
.errors()
.filter(|e| e.rule_id().starts_with("6.2"))
.collect();
println!("Output intent errors: {}", output_intent_errors.len());
doc.save("with_output_intent.pdf")?;