Ank.DocToolkit.Extensions.DependencyInjection 0.60.0

There is a newer version of this package available.
See the version list below for details.
dotnet add package Ank.DocToolkit.Extensions.DependencyInjection --version 0.60.0
                    
NuGet\Install-Package Ank.DocToolkit.Extensions.DependencyInjection -Version 0.60.0
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="Ank.DocToolkit.Extensions.DependencyInjection" Version="0.60.0" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="Ank.DocToolkit.Extensions.DependencyInjection" Version="0.60.0" />
                    
Directory.Packages.props
<PackageReference Include="Ank.DocToolkit.Extensions.DependencyInjection" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add Ank.DocToolkit.Extensions.DependencyInjection --version 0.60.0
                    
#r "nuget: Ank.DocToolkit.Extensions.DependencyInjection, 0.60.0"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package Ank.DocToolkit.Extensions.DependencyInjection@0.60.0
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=Ank.DocToolkit.Extensions.DependencyInjection&version=0.60.0
                    
Install as a Cake Addin
#tool nuget:?package=Ank.DocToolkit.Extensions.DependencyInjection&version=0.60.0
                    
Install as a Cake Tool

Ank.DocToolkit.Extensions.DependencyInjection

DocToolkit - convert HTML to PDF and DOCX in C#, no browser, no native binaries

NuGet NuGet Downloads License: MIT

Dependency-injection registration for Ank.DocToolkit — services.AddDocToolkit() registers an injectable interface per capability, over the same pure-managed HTML/DOCX/PDF/XLSX/PPTX/Markdown conversion and editing logic. They are named in full below.

dotnet add package Ank.DocToolkit.Extensions.DependencyInjection

Targets net8.0 and net10.0. MIT licensed.

📖 Dependency injection guide — registration, options, and why they are read per call rather than captured at startup · 🔎 API reference

Usage

AddDocToolkit is an extension method in the DocToolkit.Extensions.DependencyInjection namespace — import it to bring services.AddDocToolkit() into scope:

services.AddDocToolkit();

// Or opt in to remote image download for HTML->DOCX/PDF. This still succeeds in an
// air-gapped environment - an unreachable host leaves that image out rather than failing
// the conversion.
services.AddDocToolkit(o => o.AllowRemoteImageDownload = true);

Configuration reload takes effect immediately

DocToolkitOptions is consumed through IOptionsMonitor and read on every call, so changing configuration at runtime applies without restarting the process.

That matters most for AllowRemoteImageDownload, which is the only switch that lets this library open a socket. Setting it to false in configuration - as an incident response, say - stops the next conversion fetching, rather than the next deployment.

The services remain singletons; only the option read is live.

Fonts for non-Latin text

Whether a document containing Cyrillic, Greek or CJK renders to PDF is otherwise a property of the machine — the renderer falls back to whatever fonts the host has, so the same document converts on one and is refused on another. Fonts takes that out of the answer:

services.AddDocToolkit(o =>
    o.Fonts = new PdfFontOptions("Noto Sans", File.ReadAllBytes("NotoSans-Regular.ttf"))
                  .Add("Noto Sans CJK", File.ReadAllBytes("NotoSansCJK-Regular.ttf")));

Configured once rather than passed per call, because needing a font is a property of the deployment rather than of the document: somebody converting Cyrillic needs it for every document, not for some. Nothing is fetched and nothing is read from disk by this library — the bytes come from you.

Supply fonts covering everything your documents use, not only the script that failed. They replace the host's own fallbacks rather than adding to them, so too few is worse than none — measured over 99 real documents, one font rendered 63 where none rendered 71, and four rendered 77.

Fonts applies to every converter that renders a PDF and takes fonts — IDocxToPdfConverter, IHtmlToPdfConverter, IXlsxToPdfConverter and IPptxToPdfConverter — and on the same conversion as your page setup and remote-image settings. IMarkdownToPdfConverter is the exception: the core Markdown converter takes no fonts.

Supplying fonts also turns on right-to-left shaping. A Hebrew line comes out in reading order, and Arabic-script letters that have Unicode presentation forms take their contextual (joined) forms; a few letters have none (Urdu ۃ and Kurdish ە among them), so joining stops next to them. The font must cover Arabic Presentation Forms-B (and -A for Persian letters). A worksheet's right-to-left direction is still ignored. See the core package's README for the detail.

If you already set Fonts and convert XLSX or PPTX to PDF, this changes your output. Before 0.59.0 those two converters ignored the setting, because the core gained the overloads only in 0.58.0. They now use your fonts, which replace the host's fallbacks — see the note on HTML below.

It reached only the first before 0.35.0, because the core package had no overload carrying fonts alongside the other two; wiring it anyway would have made the setting apply only when neither of the others was in play, and a setting that silently stops taking effect depending on unrelated configuration is worse than one documented as absent.

If you already set Fonts and convert HTML to PDF, this changes your output. Those conversions previously ignored the setting and used the host's fonts. They now use yours — and supplied fonts replace the host's fallbacks rather than adding to them, so a list that covers less than your documents do can render fewer of them than before. Supply fonts covering everything you convert, or clear Fonts if you were relying on the host.

Bounding the remote-image opt-in

AllowRemoteImageDownload is the only switch that decides whether anything is fetched. When it is true, every fetch is bounded by RemoteImage, whose defaults are already the restrictive ones — loopback, private and link-local addresses are refused (including 169.254.169.254, the cloud metadata endpoint), only http and https are spoken, redirects are not followed, and each fetch is capped at 10 seconds and 5 MB counted on bytes actually read.

services.AddDocToolkit(o =>
{
    o.AllowRemoteImageDownload = true;
    o.RemoteImage.Timeout = TimeSpan.FromSeconds(3);
    o.RemoteImage.AllowedHosts.Add("cdn.example.com");   // empty means "any public host"
});

RemoteImage is configured in place, not assigned: the property is get-only so that a restrictive default cannot be lost by dropping in an object that missed one.

Fetching from an intranet image host? The address block refuses private ranges by default, so that image is skipped silently. Set o.RemoteImage.AllowPrivateAddresses = true to allow it — and be aware that doing so is what re-opens the SSRF reach if any caller converts untrusted HTML.

This is not a complete SSRF defence. A host's address is resolved and checked, then resolved again by the HTTP stack when it connects; a DNS answer that changes in between defeats the check. See the core package README and SECURITY.md.

public class InvoiceService
{
    private readonly IHtmlToDocxConverter _toDocx;
    private readonly IHtmlToPdfConverter _toPdf;

    public InvoiceService(IHtmlToDocxConverter toDocx, IHtmlToPdfConverter toPdf)
    {
        _toDocx = toDocx;
        _toPdf = toPdf;
    }

    public Task<byte[]> RenderDocxAsync(string html) => _toDocx.ConvertAsync(html);
    public Task<byte[]> RenderAsync(string html) => _toPdf.ConvertAsync(html);
}
// Every interface also has Stream-based async members, so a large document never has to be
// duplicated into a caller-visible byte[] — write straight to an HTTP response body instead:
record InvoiceRequest(string Html);

app.MapPost("/invoices/pdf", async (InvoiceRequest request, IHtmlToPdfConverter toPdf, HttpResponse response) =>
{
    response.ContentType = "application/pdf";
    // The PDF is written to response.Body as it is rendered, not assembled first, so the
    // status code and headers are committed on the first write. A failure part-way through
    // cannot be turned into a clean 500 — the response is already underway.
    await toPdf.ConvertAsync(request.Html, response.Body);
});

Every interface mirrors Ank.DocToolkit's static API, including both its byte[] and its Stream-based async overloads:

Convert IHtmlToDocxConverter, IHtmlToPdfConverter, IDocxToPdfConverter, IXlsxToPdfConverter, IPptxToPdfConverter, IDocxToHtmlConverter, IDocxToMarkdownConverter, IMarkdownToDocxConverter, IMarkdownToPdfConverter, IXlsxToCsvConverter, IXlsxToHtmlConverter, IDocToDocxConverter
Edit IDocxEditor, IWorkbookEditor, IPresentationEditor, IPdfEditor, IMarkdownEditor
Templates & review IDocxMailMerge, IDocxForm, IDocxReview, IDocxCompare
Preflight IDocxToPdfPreflight

That mirroring is now enforced rather than asserted. It had gone stale nine times — most recently with seven gaps at once, four of them whole interfaces that simply did not exist — because the only check was a snippet someone had to remember to run against a hand-written list of pairs. A test in this package now derives both sides by reflection and fails naming anything missing.

The count is deliberately not written down here any more. The interface NAMES above are checked against the shipped API by check-readme-coverage.py, so the list cannot go stale silently — but a number never was checked, and this file said "six" while ten shipped. The package <Description> made the same mistake independently and now says nothing countable either. They are registered as singletons (each wraps stateless logic) and are safe to inject and call concurrently. See the core package's README for what each one does and the offline/licensing guarantees behind them.

Two things on the static API deliberately do not appear on these interfaces:

  • The file-path helpers (ConvertToFileAsync, ConvertFile). Inject the converter, take the byte[] or write to a Stream, and put the bytes wherever they belong — that keeps the injected surface free of filesystem coupling.
  • The per-call allowRemoteImageDownload argument and RemoteImageOptions overloads. Remote image download is configured once, at registration, via DocToolkitOptions — so whether an application may reach the network, and how far, is a property of how it is composed rather than a decision at each call site. It is false unless you opt in.

Setting the paper once

DocToolkitOptions.Page is the page every producer lays out on when a call does not name one. It defaults to PageSetup.A4, which is what the static API already uses, so leaving it alone changes nothing.

services.AddDocToolkit(o => o.Page = PageSetup.Letter);

It reaches all three producers - both HTML converters and IDocxEditor.Create - because an option true of two out of three is one a consumer discovers a document at a time.

An explicit argument still wins: ConvertAsync(html, PageSetup.A4) produces A4 whatever the option says, since a call naming a page is answering a narrower question than configuration.

A null assigned to Page throws, but on first use rather than at registration - the configure delegate runs when the options are first materialised, not when AddDocToolkit is called.

Migrating

0.20.x to 0.21.0 - DocxEditor.ExtractText now separates blocks

Before 0.21.0 ExtractText returned the document's raw concatenated text, with no separator between blocks at all. A heading Title followed by a paragraph Body text. came back as the single token TitleBody text., and adjacent table cells A and B came back as AB. Word boundaries were lost, so anything that tokenised, indexed or diffed the result got fused words.

From 0.21.0 blocks are separated by \n and the cells of a table row by \t - which is what Word's own save as plain text writes, and what this method already did between the body and any headers or footers.

// 0.20.x  ->  "TitleBody text."
// 0.21.0  ->  "Title\nBody text."
string text = DocxEditor.ExtractText(docx);

Substring checks such as text.Contains("Title") are unaffected. Exact-match comparisons against the old fused output will need updating - that is the whole of the breaking change. If you need the previous shape, text.Replace("\n", "").Replace("\t", "") reproduces it.

0.15.0 to 0.16.0 - the extensions package needs a newer DI abstraction

No behaviour change, but a floor you may have to satisfy. Ank.DocToolkit.Extensions.DependencyInjection now requires Microsoft.Extensions.DependencyInjection.Abstractions 8.0.2, up from 8.0.0.

It follows from 0.15.0: PDFsharp raised Microsoft.Extensions.Logging.Abstractions from 6.0.0 to 8.0.3 in the core package's graph, and 8.0.3 requires DI abstractions >= 8.0.2. If your application pins 8.0.0 or 8.0.1 you will see NuGet report a package downgrade rather than resolve silently - raise your reference, or remove the pin and let it float.

The core package Ank.DocToolkit is unaffected.

Why a separate package

A console app, Lambda or simple script that only wants the static byte[]-based API installs just Ank.DocToolkit, with zero DI dependencies. ASP.NET Core and worker-service consumers add this package too.

Licence

MIT — see the parent repository's LICENSE.

DocToolkit is maintained by WillowAnk Solution. Its product page is wanksolution.com/doctoolkit.

Product Compatible and additional computed target framework versions.
.NET net8.0 is compatible.  net8.0-android was computed.  net8.0-browser was computed.  net8.0-ios was computed.  net8.0-maccatalyst was computed.  net8.0-macos was computed.  net8.0-tvos was computed.  net8.0-windows was computed.  net9.0 was computed.  net9.0-android was computed.  net9.0-browser was computed.  net9.0-ios was computed.  net9.0-maccatalyst was computed.  net9.0-macos was computed.  net9.0-tvos was computed.  net9.0-windows was computed.  net10.0 is compatible.  net10.0-android was computed.  net10.0-browser was computed.  net10.0-ios was computed.  net10.0-maccatalyst was computed.  net10.0-macos was computed.  net10.0-tvos was computed.  net10.0-windows was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages

This package is not used by any NuGet packages.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
0.60.1 39 11/10/2026
0.60.0 139 11/9/2026
0.59.0 284 11/3/2026
0.58.0 144 11/3/2026
0.57.3 249 10/16/2026
0.57.2 313 10/3/2026
0.57.1 435 9/3/2026
0.57.0 224 9/3/2026
0.56.0 173 9/3/2026
0.55.0 226 9/2/2026
0.54.0 187 9/2/2026
0.53.0 205 9/2/2026
0.52.0 260 9/2/2026
0.51.0 126 9/2/2026
0.50.0 204 9/2/2026
0.49.0 260 9/1/2026
0.48.0 183 9/1/2026
0.47.0 254 9/1/2026
0.46.0 230 8/31/2026
0.45.1 265 8/31/2026
Loading failed

## [0.60.0](https://github.com/Ank-KhoaHo/DocToolkit/compare/v0.59.0...v0.60.0) (2026-11-08)

The renderer beneath every PDF converter moves from OfficeIMO 3.2.6 to 3.5.3. **On a public test
corpus, 1,083 of 1,126 conversions now succeed, up from 1,017, and none that succeeded on 0.59.0
fails.** No public type changes.

### Added

* **Core: Arabic, Persian and Urdu letters take their joined forms in PDF when you pass
 `PdfFontOptions`**, in DOCX, XLSX and PPTX to PDF (HTML to PDF goes through DOCX). DocToolkit now
 rewrites each letter into its Unicode presentation form before rendering, so the result no longer
 depends on the renderer's own shaping. Without fonts nothing changes.
 - The supplied font must cover Arabic Presentation Forms-B (and -A for Persian letters). The
   Windows versions of Arial, Tahoma, Segoe UI and Times New Roman do.
 - A few letters, such as ۃ and ە, have no presentation form, so joining stops next to them.
 - Copy and search return the base letters, and a word with a lam-alef ligature can come back in
   visual order.
 - In XLSX, formulas are calculated before shaping, so `=LEN(A1)` and `=IF(A1="…",…)` see the
   original text.
 - A worksheet's right-to-left direction is still ignored.

### Changed

* **Core: with `PdfFontOptions`, a Hebrew line now comes out in reading order** in DOCX, XLSX and
 PPTX to PDF. Before, each word was right but a line of several words printed them in reverse.
* **Core: `DocxToPdfPreflight` reports one kind of finding, not three.** Footnotes and content
 controls in a table cell render since OfficeIMO 3.5, so the `Footnote` and `ControlInCell`
 findings are gone; `NestedTable` stays. Code that checks a finding's code for those two strings no
 longer sees them.
* **Core: a watermark on a merged document now stamps every section.**
* **Core: documents OfficeIMO 3.5.3 refuses are repaired and rendered again.** A document that
 renders as it is never takes these repairs. The causes covered: empty hyperlink text, a header
 taller than a small top margin, tiny percentage table widths, empty table columns, cell padding
 wider than its column, indents wider than their column, and list-marker tabs past the cell edge.
* **Core: two repairs run on every DOCX to PDF conversion.** A bullet level that substitutes a
 Symbol or Wingdings glyph is drawn in Arial, which has the bullet, and C0/C1 control characters
 are removed from text.
* **Core: a fixed-width table wider than its section's text area is scaled to fit.** A few wide
 tables still run off the page.
* **Core: each DOCX render runs on its own thread with a 16 MB stack**, so the depth a document
 needs no longer depends on the caller's thread. OfficeIMO 3.5.3's frames are large (about 4 KB each), and
 .NET 8 on macOS gives a secondary thread only 512 KB; a stack overflow cannot be caught.
* **Both packages carry a NuGet title** that names their task: "DocToolkit - HTML to PDF and DOCX
 to PDF converter for .NET (also XLSX, PPTX, Markdown)" and "DocToolkit dependency injection -
 HTML to PDF and DOCX to PDF for ASP.NET Core".
* **Dependencies:** OfficeIMO 3.5.3 brings `OfficeIMO.Html.AngleSharp`, `OfficeIMO.Html.Core` and
 `System.Text.Encoding.CodePages`, and AngleSharp 1.8.3. OfficeIMO.Core's licence is now
 "MIT AND Apache-2.0 AND BSD-3-Clause". `THIRD-PARTY-NOTICES.txt` lists them, and a new
 *Vendored code* section carries the MIT notice for the Arabic forms table copied from OfficeIMO.