A mechanical port of Apache PDFBox to modern .NET.
PDF-to-HTML conversion, the PdfBox.Net.Layout and PdfBox.Net.Html
libraries, and the unpdf CLI live in
erikbra/unpdf.
All 1,067 upstream Java source files from Apache PDFBox have been ported to C#.
| Upstream module | Java files | Ported C# files | Missing | Coverage |
|---|---|---|---|---|
benchmark |
3 | 3 | 0 | 100% |
debugger |
91 | 91 | 0 | 100% |
examples |
94 | 94 | 0 | 100% |
fontbox |
143 | 143 | 0 | 100% |
io |
18 | 18 | 0 | 100% |
pdfbox |
618 | 618 | 0 | 100% |
tools |
26 | 26 | 0 | 100% |
xmpbox |
74 | 74 | 0 | 100% |
| TOTAL | 1,067 | 1,067 | 0 | 100% |
Parity baseline commit: eeb5d611e0cea8beac3d7025a4dbccbef51d5caf (Apache PDFBox trunk).
See reports/pdfbox-main-gap-analysis.md for the full gap analysis report.
The solution (PdfBoxNet.slnx) contains four library projects, two test projects, and one benchmark project:
| Project | Description |
|---|---|
PdfBox.Net.IO |
Low-level random-access IO primitives (ported from io module) |
PdfBox.Net.FontBox |
Font handling — AFM, CFF, CMap, TTF, Type1 (ported from fontbox module) |
PdfBox.Net.XmpBox |
XMP metadata reading/writing (ported from xmpbox module) |
PdfBox.Net |
Core PDF library, published as PdfBox.Net.Core; assembly and namespaces remain PdfBox.Net (ported from pdfbox module) |
PdfBox.Net.Cryptography |
Optional BouncyCastle-backed public-key security provider |
PdfBox.Net.ImageMagick |
Optional Magick.NET-backed JPX/JPEG2000, CMYK JPEG, TIFF, and ICC color provider |
PdfBox.Net.Rendering |
Convenience package that registers the supported SkiaSharp renderer plus ImageMagick providers |
PdfBox.Net.Tests |
xUnit v3 tests for all non-XmpBox modules |
PdfBox.Net.XmpBox.Tests |
xUnit v3 tests for PdfBox.Net.XmpBox |
PdfBox.Net.Benchmark |
BenchmarkDotNet benchmarks (ported from benchmark module, located under src/PdfBox.Net.Benchmark/) |
- .NET 10.0 SDK or later
PdfBox.Net.Corefor the core PDF model/parser/writer package.- Optional backend packages for rendering, cryptography, or platform printing.
Rendering, public-key cryptography, and printer submission are isolated behind small backend/provider contracts so the mechanical Java port stays portable.
PdfBox.Net.Renderingis the recommended full rendering/image package. It registers the supported SkiaSharp renderer plus ImageMagick image/color providers.PdfBox.Net.SkiaSharpis the complete page rendering backend if you only want the renderer without the heavier ImageMagick providers. It also provides the optional SkiaSharp/HarfBuzz glyph-layout backend for shaping Unicode text written into new PDF content streams and form appearances.PdfBox.Net.ImageMagickprovides JPX/JPEG2000, CMYK JPEG, TIFF import, and ICC color conversion support.PdfBox.Net.Cryptographyprovides BouncyCastle-backed public-key encryption support.PdfBox.Net.SystemDrawingprovides WindowsSystem.Drawingimage helpers and a Windows print-spooler backend. Register a complete rendering backend before printing real pages.
See reports/printing-backend-policy-2026-06-28.md
for the printing support matrix and platform limitations.
See docs/glyph-layout-backends.md for the
mapping between Apache PDFBox's new AWT/FOP glyph-layout modules and the
SkiaSharp/HarfBuzz implementation in PDFBox.Net, including branch support and
the release/3.0 backport scope.
PdfBox.Net exposes provider-neutral logging through Microsoft.Extensions.Logging
and does not select a logging provider. Logging routed through this API is disabled by
default. Applications can install their configured factory during startup:
using PdfBox.Net.Logging;
PdfBoxLogging.LoggerFactory = configuredLoggerFactory;The application owns the supplied factory and remains responsible for its configuration and disposal. Logger categories use the fully qualified PdfBox.Net type name, so providers such as Serilog retain the same per-class context as the upstream Java loggers.
dotnet restore PdfBoxNet.slnx
dotnet build PdfBoxNet.slnx --configuration Release
dotnet test PdfBoxNet.slnx --configuration ReleaseCI runs on every push and pull request via .github/workflows/ci.yml.
The PdfBox.Net.Benchmark project uses BenchmarkDotNet and mirrors the Java JMH benchmarks from the upstream benchmark module.
Benchmarks operate on large real-world PDF files that live under target/pdfs/.
Run the provided download script to fetch them automatically:
# Linux / macOS
bash scripts/download-benchmark-pdfs.sh
# Windows (PowerShell)
.\scripts\download-benchmark-pdfs.ps1| File | Benchmark class | Auto-download |
|---|---|---|
target/pdfs/849-42-94772-1-10-20210818.pdf |
LoadAndSaveBenchmarks (medium) |
✅ |
target/pdfs/506-42-86246-2-10-20190822.pdf |
LoadAndSaveBenchmarks (large) |
✅ |
target/pdfs/eci_altona-test-suite-v2_technical2_x4.pdf |
RenderingBenchmarks (Altona) |
✅ |
target/pdfs/PDF32000_2008.pdf |
RenderingBenchmarks + TextExtractionBenchmarks |
✅ |
target/pdfs/Ghent_PDF_Output_Suite_V50_Full/…/Ghent_PDF-Output-Test-V50_CMYK_X4.pdf |
RenderingBenchmarks (Ghent) |
✅ Playwright |
The Ghent PDF Output Suite requires accepting a license agreement on the GWG website.
scripts/download-ghent-pdf.mjs automates this with a headless Playwright browser:
# Install Playwright once
npm install playwright
npx playwright install chromium
# Download and extract the Ghent suite
node scripts/download-ghent-pdf.mjsThe download-benchmark-pdfs.sh / .ps1 scripts call this automatically when node is available.
To run all benchmarks in Release mode:
dotnet run --project src/PdfBox.Net.Benchmark --configuration ReleaseTo run a specific benchmark class:
dotnet run --project src/PdfBox.Net.Benchmark --configuration Release -- --filter "*LoadAndSave*"The benchmarks workflow (.github/workflows/benchmarks.yml) can be triggered manually from the Actions tab. It installs Node.js and Playwright, downloads all PDF fixtures (including the Ghent suite), builds the project in Release mode, runs all benchmarks, and uploads the BenchmarkDotNet JSON results as a 90-day artifact.
Every ported file carries a provenance header recording its upstream origin:
// PDFBOX_SOURCE_PATH: pdfbox/src/main/java/org/apache/pdfbox/.../Foo.java
// PDFBOX_SOURCE_COMMIT: <sha>
// PORT_MODE: mechanical
// PORT_LAST_SYNC_COMMIT: <sha>Traceability records are maintained in:
reports/conversion-records.json— per-file conversion recordsreports/normalization-records.json— compile-normalization recordsreports/traceability-parity-report.json— parity status per upstream source pathreports/upstream-sync-state.json— latest upstream commit tracked and coverage countersreports/upstream-port-coverage-state.json— canonical parity scan snapshotreports/pdfbox-main-gap-analysis.md— human-readable gap analysis
A daily scheduled workflow (.github/workflows/upstream-sync-watch.yml, 07:00 UTC) watches the apache/pdfbox trunk branch for the main port line and the Apache 3.0 branch for the release/3.0 port line. When upstream drift is detected it:
- Creates or updates a matching upstream-sync issue for the affected port line.
- Runs
tools/parity/generate_parity_inventory.pyto regenerate the parity inventory. - Refreshes
reports/upstream-sync-state.json,reports/upstream-port-coverage-state.json,reports/all-upstream-coverage.json, andreports/pdfbox-main-gap-analysis.md.
The workflow can also be triggered manually via workflow_dispatch.
Porting follows a set of defined skills documented in SKILLS.md:
- Skill A — Initial mechanical conversion + provenance stamping
- Skill B — Upstream rewrite/update sync
- Skill C — Upstream deletion handling
- Skill D — Upstream new-file intake
- Skill E — Traceability and parity reporting
- Skill F — Compile-oriented normalization pass
- Skill G — Java → C# API and type mapping reference
- Skill H — Automatic PR approval checklist
- Skill I — Orchestrating sequential issue delivery
The approach is a mechanical-first port that stays close to upstream structure and API to make future re-syncs straightforward. Each converted file keeps the upstream Apache license header verbatim, adds a conversion note, and retains JavaDoc-derived XML documentation comments.
Two-lane strategy for ongoing .NET improvements:
- Mechanical lane — Keep converted files close to upstream, track provenance fields, and re-sync via Skill B.
- Adaptation lane — Place .NET-specific improvements in wrapper/adapter types or isolate them in bounded
PORT-LOCALregions so re-sync can preserve them.