Skip to content

Installation

npond edited this page Aug 27, 2026 · 6 revisions

Installation

Requirements

  • .NET 10 (net10.0). The library targets .NET 10 and uses nothing beyond the base class library — System.IO.Compression and System.Xml.Linq are the whole of what it builds on.
  • Fonts. By default the converter discovers the platform's installed fonts (macOS, Windows and Linux font directories). For reproducible output regardless of what a machine has installed, register fonts explicitly instead — see below.

There is no native code, no external process, and no service to stand up. The assembly is self-contained by construction: the project fails its own build if a PackageReference is ever added (Promises).

Getting the library

NuGet.

dotnet add package n8PDF

The package carries its symbols and XML documentation, and its origin is verifiable — see Verifying the package.

From source (today).

git clone https://github.com/nathanpond/n8PDF.git
dotnet build n8PDF/src/n8PDF --configuration Release

Then either reference the project directly:

<ProjectReference Include="path/to/n8PDF/src/n8PDF/n8PDF.csproj" />

or pack it locally and consume the .nupkg:

dotnet pack n8PDF/src/n8PDF -c Release

Builds are deterministic — the same input builds the same assembly, whoever builds it.

Verifying the package

n8PDF reads other people's files, so its releases are traceable to the CI that built them.

Build provenance. Every release attaches a signed SLSA build-provenance attestation that binds the .nupkg and .snupkg to the exact commit and workflow run that produced them. Download the package — from the GitHub release, or nuget install n8PDF -DirectDownload — and verify it against this repository:

gh attestation verify n8PDF.0.1.0.nupkg --repo nathanpond/n8PDF

A pass means the file was built by n8PDF's own release.yml and has not been altered since. The attestation is signed with the workflow's own OIDC identity, so the pipeline cannot be impersonated.

Repository signature. nuget.org counter-signs every package it accepts, and n8PDF publishes through NuGet trusted publishing — an OIDC exchange from CI, with no long-lived key — so the publish itself is tied to this repository and this workflow. The signature shows on the package page and can be inspected locally:

dotnet nuget verify n8PDF.0.1.0.nupkg

Author signing — a publisher signature from a code-signing certificate — is not applied: it needs a certificate this project does not hold, and the repository signature together with the build-provenance attestation already establish where a package came from. It is the natural next step should the project take on a certificate.

First conversion

using n8PDF;

Converter.ConvertFile("report.docx", "report.pdf");

Streams and byte arrays work too:

byte[] pdf = Converter.Convert(File.ReadAllBytes("report.docx"));

using var input  = File.OpenRead("report.docx");
using var output = File.Create("report.pdf");
Converter.Convert(input, output);

Options

Every knob lives on ConversionOptions, passed as the optional last argument. The defaults are chosen so that a plain call does the right thing; see The API for the full surface.

var options = new ConversionOptions
{
    Title = "Quarterly Report",          // PDF information dictionary
    PdfA = true,                         // claim and honour PDF/A-2b
    CreationDate = DateTimeOffset.Parse("2026-01-01T00:00:00Z"), // byte-identical output
};
Converter.ConvertFile("report.docx", "report.pdf", options);

Reproducible fonts

Leaving Fonts null discovers the platform's fonts — read once per process and shared, so only the first conversion pays the scan (~600ms for a typical system collection; conversions after that pay nothing, about 1.4ms for a page of text). To make output identical across machines, register exactly the faces you mean:

var fonts = new FontLibrary { UseSystemFonts = false };
fonts.RegisterFile("fonts/TimesNewRoman.ttf");
fonts.RegisterDirectory("fonts/", recursive: true);

Converter.ConvertFile("report.docx", "report.pdf", new ConversionOptions { Fonts = fonts });

Registering a file that is not a font throws FontFormatException. Keep and reuse the library — anything registered into it is registered once.

Converting documents you did not write

⚠️ n8PDF is early — 0.1.0, freshly published and pre-1.0. The audit register of hostile-input findings is fully closed and fuzz-guarded (Security), but for genuinely untrusted sources, pair the limits below with process isolation as Prerelease Considerations describes.

A .docx from an untrusted source is attacker-controlled input. Set Limits and catch PackageTooLargeException:

var options = new ConversionOptions
{
    Limits = new PackageLimits
    {
        MaximumPartBytes   = 32 * 1024 * 1024,  // per decompressed part
        MaximumTotalBytes  = 128 * 1024 * 1024, // across the package
        MaximumPartCount   = 1024,
        MaximumImagePixels = 50_000_000,        // the default: a 600dpi A4 scan with room to spare
        MaximumFontBytes   = 16 * 1024 * 1024,  // an embedded face past this is left out
    }
};

try
{
    Converter.ConvertFile(untrusted, output, options);
}
catch (PackageTooLargeException)
{
    // The document asked to decompress past the bounds you set.
}

Read Security before putting untrusted documents through any converter, this one included — it states plainly what is defended, how the register was closed, and why isolation is still the right posture.

Mail merge

A document written for a mail merge names a data source only the machine it was written on can reach. Converted as it stands, its fields show what Word shows — «FieldName» in guillemets. Give it a record and the letter fills itself in:

var record = new MailMergeRecord(new Dictionary<string, string>
{
    ["Title"] = "Dr", ["LastName"] = "Hopper"
});
Converter.ConvertFile("letter.docx", "letter.pdf", new ConversionOptions { MergeRecord = record });

Text around a field prints only where the field has something to print, exactly as Word does it.

Clone this wiki locally