Accessible PDF (Tagged PDF) and PDF/A | |
SelectPdf can generate tagged (accessible) PDF documents that declare the PDF/UA accessibility standard (PDF/UA-1 or PDF/UA-2), and PDF/A documents suitable for long term archiving, from PDF/A-1 up to PDF/A-4.
A tagged PDF carries a logical structure tree describing the meaning of the content - headings, paragraphs, lists, tables, figures with alternate text, links and the reading order. Assistive technologies such as screen readers use this structure to present the document to users with disabilities. Decorative content (backgrounds, rules, watermarks) is marked as an artifact so it is skipped by assistive technologies.
Tagged (accessible) PDF and PDF/A-3 generation are available only in the full commercial SelectPdf library. They are not available in the free SelectPdf community edition. |
Tagged (PDF/UA) output and PDF/A level A are produced by the Chromium rendering engine, which is the default and only engine, so no engine selection is required. See Chromium Engine. |
These are two different things, and both properties exist:
Tagged (and Tagged) builds the logical structure tree. The document is accessible to a screen reader, but it makes no formal claim about itself.
AccessibilityStandard (and AccessibilityStandard) declares conformance with PdfUAStandard.UA1 or PdfUAStandard.UA2: it turns tagging on and writes the PDF/UA identifier that validators and assistive software look for. UA2 also writes the document as PDF 2.0, which that standard is defined on.
Declare a standard when you want a document that says it is PDF/UA - which is what a compliance requirement usually means. A document that is merely tagged carries no identifier, deliberately: a claim that the file does not live up to is worse than no claim at all.
The claim is verified when the document is saved. Content drawn on a page before the document was tagged is never marked, so declaring a standard afterwards would produce a file that fails validation - the save refuses it instead, naming the page. Set AccessibilityStandard (or Tagged) first, then add pages and elements. |
Both standards require a title and a language, so set Title and Language (or the matching HtmlToPdfOptions properties) as well.
Set AccessibilityStandard to the standard the document should declare. The converter takes the document structure (headings, lists, tables, figures, links, reading order) directly from the rendering engine's accessible output, so a well authored HTML page produces a well structured PDF.
HtmlToPdf converter = new HtmlToPdf(); // produce a document that declares PDF/UA-1 (tagging is enabled automatically) converter.Options.AccessibilityStandard = PdfUAStandard.UA1; converter.Options.Title = "Quarterly Report"; converter.Options.Language = "en-US"; PdfDocument doc = converter.ConvertUrl("https://www.example.com"); doc.Save("accessible.pdf"); doc.Close();
Use PdfUAStandard.UA2 for the newer standard (ISO 14289-2), which is written as a PDF 2.0 document. Internal links and outline entries then also carry a structure destination, so a reader following one lands at the right place in the reading order and not merely on the right page.
The quality of the resulting accessibility depends on the source HTML: images should have an alt attribute, headings should be used in order, tables should use proper table markup, and so on.
Set PdfStandard to the desired PdfStandard value. Archiving and accessibility are separate standards, so the two properties can be combined.
HtmlToPdf converter = new HtmlToPdf(); // PDF/A-3A is archival AND accessible (tagging is enabled automatically) converter.Options.PdfStandard = PdfStandard.PdfA3A; converter.Options.AccessibilityStandard = PdfUAStandard.UA1; converter.Options.Title = "Invoice 2026-0042"; converter.Options.Language = "en-US"; PdfDocument doc = converter.ConvertUrl("https://www.example.com/invoice"); doc.Save("invoice-pdfa3a.pdf"); doc.Close();
When building a PDF document with the SelectPdf creation API, declare the standard on the PdfDocument and assign a structure type to each page element through its TagType property. Use AlternateText to provide a textual description for figures, and mark purely decorative elements with Artifact.
In a tagged document every content element you add to a page must set either TagType (real content) or Artifact = true (decorative). Adding an element with neither raises an error rather than silently producing an inaccessible document - an untagged element would otherwise inherit the structure type of the element drawn before it.
Tagged and PDF/A documents require that every font is embedded. Add fonts created from a PdfSystemFont (or from a TrueType/OpenType file) - these are always embedded automatically (the non-embedded standard base fonts cannot be used). |
// create an accessible PDF/A-3A document PdfDocument doc = new PdfDocument(PdfStandard.PdfA3A); doc.AccessibilityStandard = PdfUAStandard.UA1; doc.Language = "en-US"; doc.Title = "Accessible Document"; PdfPage page = doc.AddPage(); // embedded fonts are required for tagged / PDF/A output PdfFont headingFont = doc.Fonts.Add( new PdfSystemFont("Arial", 16f, PdfFontStyle.Bold)); PdfFont bodyFont = doc.Fonts.Add(new PdfSystemFont("Arial", 11f)); // a heading PdfTextElement heading = new PdfTextElement(10, 10, "Annual Report", headingFont); heading.TagType = PdfTagType.Heading1; page.Add(heading); // a paragraph PdfTextElement para = new PdfTextElement(10, 40, 480, "This document is tagged for accessibility.", bodyFont); para.TagType = PdfTagType.Paragraph; page.Add(para); // a figure with alternate text PdfImageElement figure = new PdfImageElement(10, 90, "chart.png"); figure.TagType = PdfTagType.Figure; figure.AlternateText = "Sales grew 20% year over year"; page.Add(figure); // a decorative rule (excluded from the structure) PdfLineElement rule = new PdfLineElement(10, 80, 480, 80); rule.Artifact = true; page.Add(rule); doc.Save("created-accessible.pdf"); doc.Close();
Tables and lists need a nested structure (a table contains rows, a row contains header and data cells; a list contains list items). Open a container with BeginTag(PdfTagType) and close it with EndTag. Elements added, and further containers opened, before the matching EndTag become children of that container.
doc.BeginTag(PdfTagType.Table);
doc.BeginTag(PdfTagType.TableRow);
page.Add(new PdfTextElement(10, 40, "Region", bodyFont)
{ TagType = PdfTagType.TableHeader });
page.Add(new PdfTextElement(80, 40, "Sales", bodyFont)
{ TagType = PdfTagType.TableHeader });
doc.EndTag();
doc.BeginTag(PdfTagType.TableRow);
page.Add(new PdfTextElement(10, 60, "North", bodyFont)
{ TagType = PdfTagType.TableDataCell });
page.Add(new PdfTextElement(80, 60, "100", bodyFont)
{ TagType = PdfTagType.TableDataCell });
doc.EndTag();
doc.EndTag();The PdfStandard enumeration selects the archiving standard of the generated document:
PdfA - PDF/A-1b, and PdfA2B - PDF/A-2b.
PdfA3B / PdfA3U / PdfA3A - PDF/A-3 at level B (visual reproduction), U (all text has Unicode mapping) and A (accessible: includes the tagged logical structure). PDF/A-3 is the level that may carry embedded files, which is what hybrid electronic invoices are built on.
PdfA4 - PDF/A-4, defined on PDF 2.0, plus the two variants PdfA4E (engineering: 3D and rich media) and PdfA4F (allows arbitrary embedded files).
PdfX - PDF/X-1a:2001, for print production.
Which archiving levels combine with which accessibility standard. PDF/UA-2 is defined on PDF 2.0, so it combines only with PDF/A-4 (PdfA4, PdfA4E, PdfA4F). PDF/UA-1 is defined on PDF 1.7, so it combines with PDF/A-2 and PDF/A-3 (PdfA2B, PdfA3B, PdfA3U, PdfA3A) and with PDF/X-1a, but not with PDF/A-4. PDF/A-1b (PdfA) combines with neither: producing a PDF/A-1b file rebuilds the document in a way that does not keep its logical structure. Asking for a combination that cannot be produced raises an error naming the levels that can be used instead, rather than producing a file that conforms to neither standard. |
PDF/A does not permit certain interactive features - sound, movie and screen annotations, and link actions such as launching an external file or running JavaScript. PDF/UA-2 likewise does not permit sound annotations, which PDF 2.0 deprecates. Adding one of these raises an error that names the unsupported feature, rather than producing a non-conformant file. |
PDF/X-1a output is CMYK. PDF/X-1a is a blind exchange standard for print: it permits DeviceCMYK, DeviceGray and spot colours, and does not permit DeviceRGB. A document produced at PdfX therefore has its colours converted to CMYK - text, vector graphics, images and gradients alike - and carries no transparency, which PDF 1.3 has no notion of. Translucent content is painted opaque rather than flattened. If you need the RGB colours preserved exactly, choose a PDF/A level instead; PDF/X exists for the printer's colour model, not for the screen's. |
PDF/A-3 (and PDF/A-4F) may carry arbitrary embedded files alongside the visible document - the source spreadsheet a report was produced from, a machine readable copy of an invoice, a dataset. Embed one with AddAssociatedFile(Stream, String, PdfAttachmentRelationship, String, String), which records both the file and how it relates to the document.
// PDF/A-3B is the archival level that allows embedded files HtmlToPdf converter = new HtmlToPdf(); converter.Options.PdfStandard = PdfStandard.PdfA3B; PdfDocument doc = converter.ConvertUrl("https://www.example.com/report"); // embed the data the report was produced from using (FileStream data = File.OpenRead("figures.csv")) { doc.AddAssociatedFile(data, "figures.csv", PdfAttachmentRelationship.Source, "text/csv", "Source data"); } doc.Save("report-with-data.pdf"); doc.Close();
The PdfAttachmentRelationship value says what the file is: Source (the document was generated from it), Data (data the document presents), Alternative (the same content in another form), Supplement or Unspecified. The stream is read when the file is added, so you are free to close it as soon as the call returns.
For ZUGFeRD / Factur-X invoices, use AddZugferdInvoice(Stream, ZugferdProfile, PdfAttachmentRelationship) instead - it embeds the xml and writes the invoice metadata in one call. See Electronic Invoices.
A few shapes cannot produce a conformant file, and are refused at the point of use with a message naming the alternative rather than silently producing a document that fails validation:
Content added to an existing (loaded) document. A document loaded from a file is not re-tagged, and content added to one carries no structure. A page added to a loaded document that already carries a structure tree does join it; a document that arrived untagged cannot declare a standard.
A PdfHtmlElement in a tagged document. On a page, the element carries the structure of the html it renders - its headings, paragraphs, lists and tables join the document's reading order with nothing to set. It is refused when it is given a single TagType, because the rendered html is not one element: clear the tag type, or set Artifact = true to declare the whole region decorative. In a header, footer or stamp template it is refused unless it is decorative (Artifact = true): a template repeats on every page, so its html has no single place in the reading order.
Declaring a standard after drawing. See the note above - the save verifies the claim and refuses a document whose content was never marked.
Appending one document to another carries the appended document's structure across, so a tagged document built from several parts stays tagged - see Pdf Merge.