Recursive AI

Create PDF/A

All onepdf recipes

Write PDF/A-2b and PDF/A-3u files in C# from documents made or edited with OnePDF, attach data to PDF/A-3, and check files from elsewhere first.

OnePDF writes PDF/A for documents it made or edited, and for existing files that already meet the requirements. It doesn’t convert arbitrary files: it embeds no fonts into content it didn’t create and rewrites no colors or transparency. A file that breaks a requirement is refused, with every violation listed, and nothing is written.

Terminal
dotnet add package RecursiveAI.OnePdf --version 1.11.0
dotnet add package RecursiveAI.OnePdf.Layout --version 1.11.0
C#
using OnePdf;
using OnePdf.Layout;
 
// A new document, written straight to PDF/A-2b.
LayoutDocument minutes = LayoutDocument.Compose(doc =>
{
    doc.Title("Minutes, October meeting").Language("en-US");
    doc.Page(page =>
    {
        page.Margin(54);
        page.Content().Text("The committee approved the harbor trial report.");
    });
});
using (FileStream file = File.Create("minutes-pdfa.pdf"))
    minutes.Save(file, new PdfSaveOptions { PdfA = new PdfAOptions { Part = PdfAPart.Part2, Conformance = PdfAConformance.B } });
 
// A file OnePDF made earlier, as PDF/A-3u with its data attached.
using (PdfDocument doc = PdfDocument.Open("invoice.pdf", new PdfOpenOptions { AllowEditing = true }))
{
    doc.AttachFile("invoice.xml", File.ReadAllBytes("invoice.xml"),
        new PdfAttachmentOptions
        {
            MimeType = "text/xml",
            Relationship = PdfAttachmentRelationship.Data,
            ModificationDate = File.GetLastWriteTimeUtc("invoice.xml"),   // PDF/A-3 requires a date on every attachment
        });
    doc.Save("invoice-pdfa3.pdf", new PdfSaveOptions { PdfA = new PdfAOptions { Part = PdfAPart.Part3, Conformance = PdfAConformance.U } });
}
 
// A file from elsewhere: make the repairs OnePDF can make, then see what is still wrong before saving.
using (PdfDocument doc = PdfDocument.Open("of345.pdf", new PdfOpenOptions { AllowEditing = true }))
{
    var options = new PdfAOptions { Part = PdfAPart.Part2, Conformance = PdfAConformance.B };
    IReadOnlyList<PdfAViolation> problems = doc.PreparePdfA(options);
    if (problems.Count == 0)
    {
        doc.Save("of345-pdfa.pdf", new PdfSaveOptions { PdfA = options });
    }
    else
    {
        Console.WriteLine($"of345.pdf cannot be PDF/A-2b as it is: {problems.Count} violations, for example:");
        foreach (PdfAViolation v in problems.Take(4))
            Console.WriteLine($"  {v.Clause}: {v.Message} (object {v.ObjectNumber})");
    }
}
Text
of345.pdf cannot be PDF/A-2b as it is: 6 violations, for example:
  6.2.11.4.1: The font ZapfDingbats is not embedded. (object 20)
  6.2.11.4.1: The font Times-Roman is not embedded. (object 191)
  6.3.3: A button widget's /N must be a dictionary of appearance states. (object 188)
  6.4.1: A widget annotation has an /A or /AA entry. (object 188)

The veraPDF validator passes minutes-pdfa.pdf as PDF/A-2b and invoice-pdfa3.pdf as PDF/A-3u.

What a PDF/A save does

A save with PdfSaveOptions.PdfA makes the repairs that need no judgement and then checks the result:

  • adds an sRGB output intent with a color profile generated by OnePDF,
  • writes the XMP metadata, taken from the document information so the two agree,
  • embeds Liberation faces for standard fonts that OnePDF itself added (Helvetica, Times, Courier), when the RecursiveAI.OnePdf.Fonts package is registered,
  • sets the print flag on annotations and generates missing form-field appearances,
  • writes the header and file identifiers the standard asks for.

Parts 2 and 3 are supported, at levels B (the pages look the same everywhere), U (B, plus Unicode for all text) and A (U, plus tagged structure: see Create PDF/UA). OnePDF writes attachments only in PDF/A-3 files, and each needs a modification date: the sample sets one from the file.

Good to know

  • PreparePdfA makes the repairs in memory and returns what is still wrong. CheckPdfA only reports, and works on read-only documents. A save that fails throws PdfAException with the same list in Violations.
  • OnePDF is not a validator. A clean check covers what OnePDF can verify from the file’s structure, not every rule of the standard. Run a validator such as veraPDF before you rely on a file.
  • DeviceCMYK color is refused, because OnePDF’s output intent is RGB.
  • An encrypted document needs Encryption = PdfEncryptionAction.Remove in the same save, since PDF/A forbids encryption.
  • Existing signatures don’t survive a PDF/A save, which rewrites the file. Sign afterwards.
  • A part of the file that can’t be read, such as a damaged font program, makes the save fail rather than leave the part out.

What is written, checked and not checked, clause by clause, is in PDF/A.

A longer walk-through with PDF/A-3 XML attachments: Create PDF/A in C#.