Generate a tagged PDF in C# that is PDF/UA-1 and PDF/A-2a in one file, and see what OnePDF refuses when a title or alternate text is missing.
dotnet add package RecursiveAI.OnePdf.Layout --version 1.11.0using OnePdf; using OnePdf.Layout; byte[] logo = File.ReadAllBytes("logo.png"); LayoutDocument summary = LayoutDocument.Compose(doc => { doc.Title("Harbor trial summary").Author("Port office").Language("en-US"); doc.Page(page => { page.Size(PageSize.Letter); page.Margin(54); page.Header().Text("Port office").FontSize(9); // headers and footers are tagged as artifacts page.Content().Column(col => { col.Spacing(10); col.Item().Width(48).Image(logo).AltText("Port office logo"); col.Item().Heading(1).Text("Harbor trial summary").FontSize(22).Bold(); col.Item().Text("Six crews took part. Catches by quay:"); col.Item().Table(table => { table.Columns(c => { c.Relative(); c.Constant(100); }); table.CellPadding(4); table.CellBorder(0.5, Colors.Grey); table.HeaderRow(r => { r.Cell().Text("Quay").Bold(); r.Cell().AlignEnd().Text("Catch (kg)").Bold(); }); table.Row(r => { r.Cell().Text("North"); r.Cell().AlignEnd().Text("1,204"); }); table.Row(r => { r.Cell().Text("South"); r.Cell().AlignEnd().Text("987"); }); }); col.Item().Heading(2).Text("Next steps").FontSize(14).Bold(); col.Item().List(list => { list.Bulleted(); list.Item().Text("Repeat the trial in spring."); list.Item().Hyperlink("https://example.com/harbor").Text("Read the full report online."); }); }); }); }); // PDF/UA-1 and PDF/A-2a in one file. using (FileStream file = File.Create("summary-ua.pdf")) { summary.Save(file, new PdfSaveOptions { PdfUA = new PdfUAOptions(), PdfA = new PdfAOptions { Part = PdfAPart.Part2, Conformance = PdfAConformance.A }, }); } // What OnePDF cannot decide for you is refused, with every reason listed. LayoutDocument untitled = LayoutDocument.Compose(doc => { doc.Language("en-US"); // no title doc.Page(page => page.Content().Column(col => { col.Item().Width(48).Image(logo); // no alternate text col.Item().Heading(2).Text("Starts at level 2"); // heading levels must start at 1 })); }); try { untitled.Save(new MemoryStream(), new PdfSaveOptions { PdfUA = new PdfUAOptions() }); } catch (PdfUAException e) { foreach (PdfUAViolation v in e.Violations) Console.WriteLine($"checkpoint {v.Checkpoint}: {v.Message}"); }
checkpoint 06: The XMP packet has no dc:title: a PDF/UA file needs a title. checkpoint 07: The catalog's /ViewerPreferences does not set /DisplayDocTitle true, so viewers would show the file name instead of the title. checkpoint 13: A Figure structure element has neither alternate text (/Alt) nor replacement text (/ActualText). checkpoint 14: The first heading is H2; the heading hierarchy has to start at H1.
summary-ua.pdf passes the veraPDF validator as PDF/UA-1 and as PDF/A-2a. The second document was refused and nothing was written.
How it works
OnePdf.Layout tags everything it lays out: headings become H1 to H6, tables Table with TH header cells, lists L with LI items, images Figure with your alternate text, links Link, and headers, footers, borders and backgrounds are marked as artifacts. A save with PdfSaveOptions.PdfUA then:
- repairs the clerical parts: the document language (from
PdfUAOptions.Languagewhen the document has none), showing the title instead of the file name, tab order, link descriptions and the XMP identification; - refuses what only you can provide, listing every violation with its Matterhorn Protocol checkpoint: a title, alternate text for each image (or
Artifact()for decoration), headings that start at level 1 without skipping levels, header cells in every table.
PdfUAOptions { Part = PdfUAPart.Part2 } writes PDF/UA-2 on PDF 2.0 instead. PDF/UA-2 can’t be combined with PDF/A-2 or PDF/A-3, which are PDF 1.7 formats.
Good to know
- OnePDF never guesses alternate text, a title, a language or the role of content. Existing untagged files are refused. You can tag one with roles you supply through the tagging API: see Tagging.
- A clean save is not proof of conformance. PDF/UA also asks for things only a person can judge: a sensible reading order, meaningful alternate text, enough contrast. Check with a validator and a screen reader.
CheckPdfUA()reports the violations of any document, including read-only ones, without changing it.- The PDF/UA save checks the finished file, so the whole document is held in memory while it is written. Plain
Save(path)streams page by page.
The full lists of repairs and checks are in PDF/UA, and the tagging rules of the layout package in Layout.
A longer walk-through with PDF/UA-2 and tagging an existing file: PDF/UA in C#.