---
title: "Accessible PDF: PDF/UA-1 and PDF/A-2a"
url: https://recursiveai.org/onerender/cookbook/accessible-pdf/
description: "Write HTML to a PDF that declares PDF/UA-1, PDF/A-2a or both with OneRender, and see how a document that breaks a rule is refused with the reasons."
version: "1.0.0"
---

# Accessible PDF: PDF/UA-1 and PDF/A-2a

Write HTML to a PDF that declares PDF/UA-1, PDF/A-2a or both with OneRender, and see how a document that breaks a rule is refused with the reasons.

```csharp
using OneRender.Pdf;

string html = """
<!DOCTYPE html>
<html lang="en">
<head><title>Return policy</title></head>
<body style="font: 12pt/1.5 serif">
  <h1>Return policy</h1>
  <p>Items can be returned within 30 days. <a href="https://example.com/returns">Start a return</a>.</p>
  <table>
    <thead><tr><th>Item type</th><th>Window</th></tr></thead>
    <tbody><tr><td>Books</td><td>30 days</td></tr><tr><td>Gift cards</td><td>Not returnable</td></tr></tbody>
  </table>
  <img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNkYPhfDwAChwGA60e6kgAAAABJRU5ErkJggg==" alt="Store logo" width="40" height="40">
</body>
</html>
""";

foreach (PdfProfile profile in new[] { PdfProfile.PdfUA1, PdfProfile.PdfA2a, PdfProfile.PdfA2aAndUA1 })
{
    var options = new HtmlPdfOptions { Output = new PdfOutputOptions { Profile = profile, Author = "Harbor Street Books" } };
    HtmlPdfResult r = HtmlPdf.ToPdf(html, File.Create($"policy-{profile}.pdf"), options);
    Console.WriteLine($"{profile}: {r.PageCount} page, {new FileInfo($"policy-{profile}.pdf").Length:N0} bytes");
}

// A document that breaks a rule is refused, with the rules it breaks.
try
{
    HtmlPdf.ToPdf("<p>No title, no language</p>", new HtmlPdfOptions { Output = new PdfOutputOptions { Profile = PdfProfile.PdfUA1 } });
}
catch (PdfRenderException e)
{
    Console.WriteLine($"refused: {e.Message}");
}
```

```text
PdfUA1: 1 page, 17,082 bytes
PdfA2a: 1 page, 19,746 bytes
PdfA2aAndUA1: 1 page, 20,918 bytes
refused: The document does not meet PDF/UA-1: 7.2: The catalog has no /Lang entry naming the document's natural language.; 7.1: The XMP packet has no dc:title: a PDF/UA file needs a title.; 7.1: The catalog's /ViewerPreferences does not set /DisplayDocTitle true, so viewers would show the file name instead of the title.
```

## What makes it pass

- **Structure from the HTML.** The heading, paragraph, link, the table with column header scopes, and the image with its `alt` become the structure tree. An image with an empty `alt`, or `role="presentation"`, is marked as an artifact, and so are backgrounds and borders.
- **A title and a language**, from `<title>` and `<html lang>` or from `Title` and `Language` in `PdfOutputOptions`. The title is shown in the viewer’s title bar.
- **Text that maps to Unicode**, through `/ToUnicode` and `/ActualText`, so assistive technology and extraction read the source text.
- **A missing glyph isn’t drawn as a box.** With a profile set, a character no font has keeps its space and stays in the text.

## How it is written

A profile other than `None` builds the file in memory, then saves it again with the XMP identification and checks it against the rules OnePDF knows. If any fail, `ToPdf` throws `PdfRenderException` listing them, so you never ship a file that claims a standard it breaks. Without a profile the file streams and is still tagged; the profile adds the identification and the check.

## Good to know

- `PdfA2aAndUA1` writes one file that is both.
- In the upstream test suite, the six document templates and the paged sample pass veraPDF’s PDF/UA-1 validation with zero failed rules. veraPDF runs as a separate program and isn’t part of the packages.
- Whole sentences in complex scripts (Arabic, Devanagari, Thai) can fail veraPDF rule 7.21.7 in 1.0.0, because glyphs inside multi-glyph clusters have no `ToUnicode` entry. The text still extracts correctly.
- To check the HTML itself before rendering, see [Check accessibility with rendered facts](https://recursiveai.org/onerender/cookbook/accessibility-facts/).

The tag mapping and the checks are in [PDF output](https://recursiveai.org/onerender/docs/pdf/#tags).
