---
title: "Embed missing fonts in a PDF in C#"
url: https://recursiveai.org/onepdf/cookbook/embed-missing-fonts/
description: "Embed missing fonts in a PDF in C#: Arial, Helvetica, Times and ZapfDingbats get metric-compatible faces, and no text moves. Runnable code."
version: "1.15.0"
---

# Embed missing fonts in a PDF in C#

Open the PDF with editing allowed, give it the font faces from the RecursiveAI.OnePdf.Fonts package, call `EmbedSubstituteFonts` and save. Each font the file names but doesn’t carry gets a metric-compatible face embedded, so no text moves. The program below does this for the IRS Form W-9, a public PDF you can download to try it.

Want PDF/A? [Convert a PDF to PDF/A](https://recursiveai.org/onepdf/cookbook/convert-pdf-to-pdfa/) embeds missing fonts itself, along with every other repair, in one call.

```shell
dotnet new console -o EmbedFonts && cd EmbedFonts
dotnet add package RecursiveAI.OnePdf --version 1.15.0
dotnet add package RecursiveAI.OnePdf.Fonts --version 1.15.0
curl -LO https://www.irs.gov/pub/irs-pdf/fw9.pdf
```

Put this in `Program.cs` and run `dotnet run`:

```csharp
using OnePdf;

using PdfDocument doc = PdfDocument.Open("fw9.pdf",
    new PdfOpenOptions { AllowEditing = true });
// the Liberation and Noto faces
doc.FontProvider = OnePdf.Fonts.FontsPackage.Provider;
string before = AllText(doc);

// Embed a metric-compatible face for each font the file names but doesn't embed.
PdfSubstituteEmbedReport report = doc.EmbedSubstituteFonts();
foreach (PdfSubstituteEntry e in report.Entries)
    if (e.Outcome != PdfSubstituteOutcome.AlreadyEmbedded)
        Console.WriteLine($"{e.BaseFont}: {e.Message}");

doc.Save("fw9-fonts.pdf");
Console.WriteLine($"Text unchanged: {AllText(doc) == before}");

static string AllText(PdfDocument doc) =>
    string.Concat(doc.ExtractText().Select(p => p.Text));
```

```text
ZapfDingbats: Embedded NotoSansSymbols2-Regular as FPQCCR+ZapfDingbats.
Helvetica: Embedded LiberationSans-Regular as BVXYHA+Helvetica.
HelveticaLTStd-Bold: The name is not a standard face or a common alias of one.
Text unchanged: True
```

The W-9 names three fonts without embedding them, all used by its form fields. ZapfDingbats and Helvetica now have embedded subsets of Noto Sans Symbols 2 and Liberation Sans. The third, HelveticaLTStd-Bold, has no metric-compatible face in the package, so it is reported and left as it was.

## How it works

- **Which face.** Arial and Helvetica get Liberation Sans, Times and Times New Roman Liberation Serif, Courier Liberation Mono, with bold and italic taken from the name. Symbol gets Noto Sans Math and ZapfDingbats Noto Sans Symbols 2.
- **Nothing moves.** Only the glyphs the pages use are embedded, with their widths set to the PDF’s own, so every character keeps its position. `WidthChanges` and `MissingCodes` on each entry say where the substitute differs.
- **Where the faces come from.** Set `doc.FontProvider`, call `FontsPackage.Register()` once at startup, or pass a `Provider` in `PdfSubstituteEmbedOptions`. Without one, nothing is embedded and every entry says `NoFontProvider`.
- **During a save or conversion.** `EmbedSubstituteFonts = true` in `PdfUAOptions` (or `PdfAOptions`) does the same as part of a PDF/UA (or PDF/A) save, and `PdfAConverter.Convert` does it by default.
- **Unicode maps.** `doc.AddMissingToUnicode()` adds text mappings that extraction can already derive, so the text can be searched and read aloud. Nothing is guessed.

## Limits

- **Letter shapes change.** A substitute matches widths, not looks. Liberation Sans is close to Arial; compare the rendered pages where it matters.
- **No substitute for most fonts.** Verdana, Calibri, HelveticaLTStd-Bold and other faces without a metric-compatible match report `NoSubstitute`. If you have the font and the right to embed it, supply it through `FaceForFont` in `PdfSubstituteEmbedOptions`.
- **Signatures** are invalidated by the full save.

Every option and outcome is in [Embedding fonts into existing PDFs](https://recursiveai.org/onepdf/docs/fonts/).
