← All docs

Library (API)

Reference DynDyn.Pdf.Core and call the converter in-process — no external service, no CLI subprocess, no network. The same engine backs the CLI and the Web service, so output is identical regardless of which surface you use.

Install / reference

Target framework: net10.0.

Package Contains Native dependency
DynDyn.Pdf.Core The engine, options, output writers, batch converter None
DynDyn.Pdf.Engine The default fully-managed extraction engine (ManagedExtractionEngine), AddDynDynPdf() DI extension None
DynDyn.Pdf.Interop Managed P/Invoke surface over pdfium: safe handles, runtime initialization Pulls in the native binaries transitively, but the managed shim itself never throws if they're absent
DynDyn.Pdf.Native The pdfium-based native extraction engine, the raster-fallback rasterizer, and the ground-truth renderer Yes — reference this only when you actually need raster output
dotnet add package DynDyn.Pdf.Core
dotnet add package DynDyn.Pdf.Engine   # fully-managed engine + DI helper

The interface

namespace DynDyn.Pdf.Core;

/// Converts a PDF document to pixel-faithful HTML. Stateless and safe for concurrent use.
public interface IPdfToHtmlConverter
{
    Task<ConversionResult> ConvertAsync(
        string inputPdfPath,
        IOutputWriter output,
        ConversionOptions options,
        IProgress<ConversionProgress>? progress = null,
        CancellationToken cancellationToken = default);
}

Two implementations:

Implementation Engine Native dependency
DocumentConverter (recommended) Pluggable IExtractionEngine (pass new ManagedExtractionEngine()) Only if you request a raster background
PdfToHtmlConverter native Always

Quick start (fully-managed, no native dependency)

using DynDyn.Pdf.Core;
using DynDyn.Pdf.Core.Engine;
using DynDyn.Pdf.Core.Output;
using DynDyn.Pdf.Engine;

IPdfToHtmlConverter converter = new DocumentConverter(
    new ManagedExtractionEngine(),
    new NativeRasterizerFactory(), // supplies raster fallback if/when a page needs it; harmless otherwise
    loggerFactory: null);          // ILoggerFactory optional

var result = await converter.ConvertAsync(
    "document.pdf",
    new SingleFileOutputWriter(),  // packaging: see the table below
    new ConversionOptions { Dpi = 150, Pages = PageRange.Parse("1-20") });

Console.WriteLine($"{result.PageCount} pages → {result.OutputFiles[0]} " +
                  $"({result.Warnings.Count} warnings, {result.Elapsed.TotalSeconds:0.0}s)");

The native variant is new PdfToHtmlConverter(loggerFactory) with the same ConvertAsync call — use it only when you specifically want native extraction (requires DynDyn.Pdf.Interop and usually DynDyn.Pdf.Native).

Output writers (IOutputWriter)

Writer Produces
SingleFileOutputWriter One self-contained .html (fonts/images/SVG inlined as data URIs)
MultiPageOutputWriter A folder: per-page page-NNNN.html + shared assets/ (fonts, CSS) + index.html
ViewerOutputWriter One self-contained HTML app: bookmarks/TOC sidebar, page nav, zoom

result.OutputFiles lists every file written (in page order for the multi-page writer).

Dependency injection

using DynDyn.Pdf.Engine;

services.AddDynDynPdf();  // IPdfToHtmlConverter, stateless singleton, fully-managed engine

AddDynDynPdf() lives in DynDyn.Pdf.Engine and registers the fully-managed ManagedExtractionEngine. With nothing else registered, conversions run with zero native dependencies and every page keeps its vector background.

Raster fallback is opt-in: register an IPageRasterizerFactory as well, and the converter picks it up automatically.

using DynDyn.Pdf.Engine;
using DynDyn.Pdf.Native;   // optional package

services.AddDynDynPdfNativeRasterizer();  // pdfium-backed raster fallback
services.AddDynDynPdf();

Order does not matter — AddDynDynPdf() resolves the factory from the provider at construction time, so a deployment that simply omits the native package (or publishes with -p:NativeAssets=false) degrades to the vector background instead of failing.

IPdfToHtmlConverter is stateless and safe for concurrent use — a single instance can serve parallel conversions.

Progress & cancellation

ConvertAsync reports ConversionProgress between pipeline steps and honors a CancellationToken:

var progress = new Progress<ConversionProgress>(p =>
    Console.Write($"\r{p.Stage}: {p.PagesExtracted}/{p.TotalPages} extracted, " +
                  $"{p.PagesComposed}/{p.TotalPages} composed  ({p.Fraction:P0})"));

await converter.ConvertAsync("doc.pdf", writer, options, progress, cts.Token);

ConversionProgress fields: TotalPages, PagesExtracted, PagesComposed, Stage, and Fraction (phase-weighted [0,1], advances monotonically across extract/fonts/compose/write).

Result types

public sealed record ConversionResult(
    int PageCount,
    IReadOnlyList<string> OutputFiles,
    IReadOnlyList<ConversionWarning> Warnings,
    TimeSpan Elapsed);

public readonly record struct ConversionWarning(int? PageIndex, string Message, WarningCode Code = WarningCode.Unspecified)
{
    public WarningSeverity Severity => Code.Severity(); // derived from Code, never set independently
}

Warnings are non-fatal problems (e.g. a font that failed validation and was embedded raw). They are logged and returned — never embedded in the output HTML.

Message stays human-readable free text and may change wording between releases — do not pattern-match it. Code is the stable classifier for programmatic consumers; Severity is derived from Code:

Severity Meaning
Info Nothing was lost — an intended default or deliberate substitution
Degraded Content rendered, but approximated
Loss Content was dropped and is not represented anywhere in the output. An unclassified (Unspecified) code also maps to Loss, fail-safe

Batch conversion (BatchConverter)

Convert many PDFs concurrently with per-document failure isolation:

using DynDyn.Pdf.Core;

var batch = new BatchConverter(
    converterFactory: () => new DocumentConverter(new ManagedExtractionEngine(),
                                                  new NativeRasterizerFactory(), loggerFactory),
    loggerFactory);

BatchResult result = await batch.ConvertAsync(
    inputPaths: ["a.pdf", "b.pdf", "c.pdf"],
    outputWriterFactory: _ => new SingleFileOutputWriter(),  // a FRESH writer per document
    options: new ConversionOptions { Dpi = 120 },
    batchOptions: new BatchOptions { MaxConcurrentDocuments = 8 });

Console.WriteLine($"{result.Succeeded}/{result.TotalDocuments} ok, " +
                  $"{result.TotalPages} pages in {result.Elapsed.TotalSeconds:0.0}s");

foreach (DocumentConversionResult doc in result.Documents)  // INPUT order (deterministic)
{
    Console.WriteLine(doc.Success
        ? $"ok   {doc.InputPath}: {doc.Result!.PageCount} pages"
        : $"FAIL {doc.InputPath}: {doc.Error}");
}

Contract:

BatchOptions:

Property Default Meaning
MaxConcurrentDocuments max(1, cores / 2) How many PDFs convert at once
PerDocumentParallelism null → max(1, cores / MaxConcurrentDocuments) Overrides each document's internal parallelism so jobs × per-doc never oversubscribes the CPU

BatchResult exposes TotalDocuments, Succeeded, Failed, TotalPages, TotalWarnings, OutputFiles (across all succeeded documents, input order), and Elapsed.

Multi-document parallelism is the ~1.35x throughput lever for the managed engine and depends on Server GC being enabled.

ConversionOptions reference

Immutable record; construct with an object initializer or with. Full table (with the CLI flag and REST field that set each) is in configuration.md. Summary:

Property Type Default Notes
Dpi int 120 CSS pixel scale = Dpi / 72. Clamp 18–600 in front-ends
OutputDirectory string? input's dir Where output is written
Pages PageRange PageRange.All PageRange.Parse("1-5,8,10-")
LineGrouping LineGroupingMode Always Always / Auto / Never
LineGroupingOptions LineGroupingOptions tuned defaults Vertical/horizontal/font-size join tolerances
Background BackgroundMode Svg Svg / Raster / None
RasterFallback RasterFallback Auto Never / Auto / Always; no-op without a rasterizer
SemanticStructure bool false Tagged-PDF <p>/headings/lists/tables via display:contents
FontEmbedding FontEmbedding Woff1 Woff1 / RawTtf (debug)
MaxImageDimension int 4096 Cap for resampled background images (device px/axis)
JpegQuality int 85 Opaque background image quality (1–100)
MaxDegreeOfParallelism int ProcessorCount Managed post-processing threads
Deterministic bool false Byte-reproducible output (fixed font timestamps/ordering)
Password string? none User password for an encrypted PDF (managed engine only)
Scale double derived Read-only: Dpi / 72.0

ConversionLimits-derived fields (MaxPages, MaxPagePixels, MaxEstimatedModelBytes, ConversionTimeout) are the DoS-boundary caps applied to untrusted input — they exist on every entry point that accepts an upload (see the WebAssembly build's explicit wiring for a worked example) and are documented in full in configuration.md and security.md.


Related: the same engine is exposed as an HTTP job API for out-of-process callers, as a CLI for scripting, and compiled to WebAssembly for in-browser use with no upload at all. See managed-vs-native.md for when to reach for DynDyn.Pdf.Native instead of the pure-managed path shown above.