This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
Morph.PDFium is a .NET wrapper over the PDFium C API. It started as a render-to-PNG + metadata wrapper and now covers a broad slice of the PDFium surface: rendering (DPI, clip/region, grayscale, render flags), text extraction & search, navigation (bookmarks/destinations/actions/links), annotations, AcroForm fields, page manipulation (import/rotate/insert/delete/flatten), content editing, save, attachments, signatures, tagged-structure and thumbnails. Native binaries come from the bblanchon.PDFium.* NuGet packages (Windows/Linux/macOS). The public API lives in the Morph.PDFium namespace — entry point PdfiumDocument, with PdfPage and PdfForm for page- and form-scoped work; Verify.PDFium consumes this package. See docs/native-api-coverage.md for exactly which of the 460 native exports are wrapped and which are intentionally not.
Tests use TUnit, not VSTest. dotnet test is unsupported on .NET 10 SDK and will error. Use dotnet run against the test project, and TUnit's --treenode-filter (not --filter) for narrowing:
# Build
dotnet build src --configuration Release
# All tests
dotnet run --project src/Morph.PDFium.Tests --configuration Release
# Single class
dotnet run --project src/Morph.PDFium.Tests --configuration Release -- --treenode-filter "/*/*/PdfiumDocumentTests/*"
# Single test
dotnet run --project src/Morph.PDFium.Tests --configuration Release -- --treenode-filter "/*/*/PdfiumDocumentTests/PageCount"All source lives under src/. Solution file is src/Morph.PDFium.slnx.
The native bindings and the public API are each split into per-feature partial files:
- PdfiumNative.cs + PdfiumNative.*.cs (
.Document,.Text,.Doc,.Edit,.More,.Render,.Form,.Objects) —[LibraryImport]bindings grouped by source header.PdfiumNative.csowns the process-wideSynclock (PDFium is not thread safe; every native call must hold it) and one-timeFPDF_InitLibraryvia the static constructor. The library is never destroyed. PDFium'sunsigned longlength parameters are 32 bit on Windows / 64 bit elsewhere;uintis correct for both. - Interop.cs — shared marshalling helpers for PDFium's "call twice" string protocol (
Utf16ByLength/Utf8ByLength/Utf16ByUnits,ToWideString) plus the blittable structsFsRectF/FsMatrix/FsQuadPoints. All assume the caller holdsSync. - PdfiumDocument.cs + PdfiumDocument.*.cs — public document API: load/render core plus
.Info,.Pages,.Bookmarks,.Edit,.Save,.Attachments,.Signatures,.Render,.Forms. Loading pins the source bytes for the document lifetime;CreateNewmakes an empty document with no pinned buffer. The shared rasteriserRenderPixels(index, dpi, flags, region, formHandle)renders into a pinned managed buffer withFPDF_REVERSE_BYTE_ORDER(RGBA), then PNG-encodes outside the lock; pass aClipRegionforFPDF_RenderPageBitmapWithMatrix, or a form handle to overlay widgets viaFPDF_FFLDraw. - PdfPage.cs + PdfPage.*.cs (
.Text,.Links,.Annotations,.Edit,.Objects) — a disposable page handle wrappingFPDF_LoadPage. Text and web-link sub-handles are loaded lazily and closed on dispose. - PdfForm.cs — disposable
FPDF_FORMHANDLEsession. TheFPDF_FORMFILLINFOstruct (oneversionint + 32 callback slots, all null for headless use) must stay pinned for the handle's lifetime, since PDFium retains the pointer. - Save uses a
FPDF_FILEWRITEwhoseWriteBlockis an[UnmanagedCallersOnly]cdecl function pointer; the destinationStreamis recovered via a GCHandle stored in a trailing struct slot. - PngEncoder.cs — dependency-free PNG writer: RGBA, Up filter,
ZLibStream(SmallestSize),pHYschunk for dpi. Deflate output is technically allowed to change between .NET runtime versions; if a runtime upgrade shifts snapshot bytes, regenerate verified files.
Style note: only public types get a namespace declaration (Morph.PDFium); internal types (PdfiumNative, Interop, the Fs* structs, Navigation) live in the global namespace.
- Snapshot tests use Verify.TUnit with SSIM comparison for PNGs (
VerifierSettings.UseSsimForPng()); verified PNG/txt files are committed beside the tests. - Test assets
sample.pdf(1 page) andmulti-page.pdf(4 pages), both US Letter, were produced by Morph's PDF exporter with embedded font subsets, so rendering is machine-independent. - PDFium rasterization is deterministic for a pinned bblanchon.PDFium version. Bumping those packages may shift pixels — expect to regenerate
*.verified.pngfiles in the same commit. The threebblanchon.PDFium.*packages must stay on the same version.
Central Package Management; versions live in src/Directory.Packages.props.