Skip to content

FAQ & troubleshooting

Common questions and gotchas: Linux fonts, small-font layouts, thread-safety, and known limits.

This page answers the questions that come up most often when adopting Rivoli PDF, and the few sharp edges worth knowing before you hit them. If your question is about how to do something, the how-to guides are the better starting point.

The Rivoli PDF License: source-available; free for individuals and for organizations with annual gross revenue under USD 1,000,000. The project is published by Rivoli AI; the repository is github.com/rivoli-ai/rivoli-pdf.

Does it need Ghostscript, a browser, or any external executable?

Section titled “Does it need Ghostscript, a browser, or any external executable?”

No. Rivoli PDF runs in-process (it generates and reads PDFs with no shell-out to Ghostscript, wkhtmltopdf, a headless browser, or any other executable. Layout and PDF writing are pure managed code; raster image work uses SkiaSharp, whose native assets NuGet restores with the package. So there are no system packages to install and no external process to manage, and behaviour is reproducible across machines), but the library is not 100% managed code end to end. See the dependency table below for what that means when you publish.

What third-party packages does it pull in?

Section titled “What third-party packages does it pull in?”

The libraries depend on a small set of managed NuGet packages, surfaced transitively when you install:

PackageUsed for
SixLabors.ImageSharpDecoding and processing raster images
SkiaSharpImage/graphics operations during rendering
CSJ2KJPEG 2000 (JPX) image decoding
System.Text.Encoding.CodePagesLegacy text encodings
System.Security.Cryptography.PkcsCryptographic primitives (encryption support)

These are restored by NuGet; you do not install anything outside dotnet. SkiaSharp carries a native runtime component, resolved through its own NuGet runtime assets, so there is nothing to install at the OS level, but the runtime identifier you publish for has to be one SkiaSharp ships assets for.

TargetStatus
Windows x64 / arm64Supported; natives ship in the SkiaSharp package
macOS x64 / arm64Supported; natives ship in the SkiaSharp package
Linux glibc x64 / arm64Supported via SkiaSharp.NativeAssets.Linux, referenced for you
Linux musl (Alpine)Needs an extra reference. Add SkiaSharp.NativeAssets.Linux.NoDependencies plus the libfontconfig1 package, or use a glibc-based image
Browser / wasmNot supported

If you are containerising on alpine and see a DllNotFoundException for libSkiaSharp, that table is the reason.

Which .NET version and platforms are supported?

Section titled “Which .NET version and platforms are supported?”

The packages target .NET 8 and .NET 10 and run on Windows, Linux, and macOS. Install them with:

Terminal window
dotnet add package Rivoli.Pdf.Core
dotnet add package Rivoli.Pdf

My text renders with wrong or missing glyphs on Linux, why?

Section titled “My text renders with wrong or missing glyphs on Linux, why?”

This is almost always a missing font on the host, not a library bug. Headless Linux images (containers, CI runners) often ship without the fonts your document references. Two fixes:

  • Install the fonts you use (e.g. apt-get install fonts-dejavu fonts-liberation), or bundle the font files with your app.
  • Better for slim containers: register the font from bytes or a stream so you don’t depend on the host’s font directories at all: document.RegisterFontFamily("Source Serif", fontBytes, FontWeight.Normal) (there is also a Stream overload). Ship the font as an embedded resource or blob and register it at startup. See How do I register a custom font.
  • For CJK content, point the renderer at a font that has the glyphs via PdfSaveOptions.CjkFontPath, or supply the bytes with PdfSaveOptions.CjkFontData (no filesystem dependency). See How do I handle CJK text.

Is the output deterministic / reproducible?

Section titled “Is the output deterministic / reproducible?”

Yes. That is a core design goal. Given the same input model and options, Rivoli PDF produces byte-stable output: rendering is deterministic, with no reliance on wall-clock time, locale-dependent rendering, or external processes. This is what lets the project run a pixel-fidelity harness in CI and lets you check generated PDFs into golden tests. (If you set DocumentMetadata fields like creation dates yourself, those values flow through as you provide them.)

Generate documents on separate instances, one document per thread. Building and saving an independent Document on each thread is the supported model and works well.

Because PDF/A forbids encryption: it is part of the standard, not a library limitation. If you set both SecuritySettings and a PdfAMode on PdfSaveOptions, validation rejects the combination (PdfSaveOptions.Validate() throws a PdfValidationException). Choose one per output: an archival PDF/A file, or an encrypted file.

My layout looks wrong with very small fonts, what’s happening?

Section titled “My layout looks wrong with very small fonts, what’s happening?”

At small font sizes, rounding in line metrics and tight LineHeight/SpaceBefore/SpaceAfter values can compound and visibly shift text. If a dense, small-font block looks off, give it a little breathing room: set an explicit LineHeight (e.g. 1.21.4) on the Style rather than relying on the minimum, and avoid stacking very tight paragraph spacing. The layout is deterministic, so once a block looks right it stays right across renders.

How do I produce accessible (tagged) PDFs?

Section titled “How do I produce accessible (tagged) PDFs?”

Set EnableTaggedPDF = true and a document Language on PdfSaveOptions (or flip the global default PdfGlobalOptions.DefaultEnableTaggedPDF). A language tag is also required for PDF/A-1a conformance.

using Rivoli.Pdf;
var renderer = new PdfDocumentRenderer("1.7");
renderer.RenderToFile(doc, "accessible.pdf", new PdfSaveOptions
{
EnableTaggedPDF = true,
Language = "en-US",
});

Building documents and writing PDFs is the primary path. Reading/import and rasterization are also supported, with some caveats:

  • LoadPdf parses existing PDFs back into the model. Expect excellent round-trips for documents the library itself produced and best-effort results for arbitrary third-party PDFs.
  • Document-level features survive the round-trip: bookmarks, comments/markup annotations, page labels, /Info metadata and the XMP packet, optional-content layers (hidden ones stay toggleable, and OCMD //VE visibility expressions survive), print page boxes, color-space identity (/Separation//DeviceN spot colors, CMYK ink splits, ICC, /Indexed//Lab//Cal*), and prepress graphics-state parameters such as overprint.
  • The rasterizer that backs the fidelity harness has known gaps around certain shadings and image codecs.

When in doubt, treat extraction/reading as “supported with caveats” and validate against your own inputs.

It is the project’s deterministic round-trip test rig. It takes a corpus of PDFs, runs them through parse → rebuild → re-export, and compares the result pixel-for-pixel against a PDFium oracle render. Because rendering is deterministic and needs no external executable, the harness runs in CI without extra tooling. It is how the project measures import and rendering accuracy objectively across a large document corpus.

Yes. Encryption (passwords and permissions via PdfSecuritySettings) and cryptographic signing (via PdfSigner) are both supported. See the security tutorial for a worked example.

How do I report a bug or request a feature?

Section titled “How do I report a bug or request a feature?”

Open an issue on the repository: github.com/rivoli-ai/rivoli-pdf. Reproducible cases (ideally the model or input PDF plus the options you used) are the most useful.