PinataLayout.DocumentObjectModel 0.1.0

There is a newer version of this package available.
See the version list below for details.
dotnet add package PinataLayout.DocumentObjectModel --version 0.1.0
                    
NuGet\Install-Package PinataLayout.DocumentObjectModel -Version 0.1.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="PinataLayout.DocumentObjectModel" Version="0.1.0" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="PinataLayout.DocumentObjectModel" Version="0.1.0" />
                    
Directory.Packages.props
<PackageReference Include="PinataLayout.DocumentObjectModel" />
                    
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 PinataLayout.DocumentObjectModel --version 0.1.0
                    
#r "nuget: PinataLayout.DocumentObjectModel, 0.1.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 PinataLayout.DocumentObjectModel@0.1.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=PinataLayout.DocumentObjectModel&version=0.1.0
                    
Install as a Cake Addin
#tool nuget:?package=PinataLayout.DocumentObjectModel&version=0.1.0
                    
Install as a Cake Tool

PdfPinata

NuGet Version CI codecov

PdfPinata is a partial port of PdfSharp.Xamarin for .NET Standard. Additionally, MigraDoc has been ported as well (from version 1.32). The core PdfPinata package carries no imaging or font dependency of its own. Pick a backend package and register it once at startup.

Backends

Package Backend License Notes
PdfPinata.Skia SkiaSharp MIT Default. Native library — see below.
PdfPinata.ImageSharp SixLabors.ImageSharp / Fonts Apache-2.0 Pinned to the Apache-2.0 licensed 2.1.x / 1.0.x lines — see below.

Register the backend before creating any font or loading any image:

using PinataLayout.DocumentObjectModel.MigraDoc.DocumentObjectModel.Shapes;
using PdfPinata.Fonts;
using PdfPinata.Utils;

GlobalFontSettings.FontResolver = new SkiaFontResolver();
ImageSource.ImageSourceImpl = new SkiaImageSource();

Both throw a descriptive InvalidOperationException if you use them without registering a backend first.

Text shaping

Package Backend License Notes
PdfPinata.HarfBuzz HarfBuzzSharp MIT Optional. Works with either imaging backend. Native library — see below.

Without it, a string goes to the page one character at a time, each mapped to the glyph the font's cmap gives for that code point. For Latin that is nearly right — it loses kerning and ligatures. For Arabic it is wrong, because the alphabet has initial, medial, final and isolated forms and Unicode stores the letter rather than the form, so the letters never join. Registering a shaper runs the font's own GSUB and GPOS tables instead:

using PdfPinata.Fonts;
using PdfPinata.HarfBuzz;

GlobalFontSettings.TextShaper = new HarfBuzzTextShaper();

Unlike the other seams this one is optional and reads as null until you set it, so nothing changes by leaving it alone. Setting it will change where lines wrap, because kerning and ligatures change how wide text measures — register it before you pin any layout you care about.

It is a package of its own rather than part of a backend so that shaping does not oblige you to pick one, and it needs the same kind of native asset reference SkiaSharp does:

<PackageReference Include="HarfBuzzSharp.NativeAssets.Win32" Version="14.2.1.2" />
<PackageReference Include="HarfBuzzSharp.NativeAssets.Linux" Version="14.2.1.2" />
<PackageReference Include="HarfBuzzSharp.NativeAssets.macOS" Version="14.2.1.2" />

Bidirectional reordering does not need the shaper and is on already. Every DrawString and every MeasureString runs the Unicode Bidirectional Algorithm over the string, cuts it into runs of one direction and one script, and draws them in the order they are read — so "سلام" comes out as "سلام" and not as "م ا ل س", with or without PdfPinata.HarfBuzz. Without the shaper the letters do not join, which needs the font's GSUB; with it, they do. Text made only of characters below U+02B0 skips the whole thing, so plain Latin costs nothing.

A character the chosen face has no glyph for can be drawn by one that does. Name the families to fall back to, in order of preference, and a run is cut wherever coverage runs out:

GlobalFontSettings.FontFallback = new FontFallbackList("Noto Sans Arabic", "Noto Sans Devanagari");

Optional, like the shaper: with nothing registered, a missing glyph is .notdef — an empty box — exactly as before, and nothing is asked about coverage at all. The families are named rather than discovered because working out unprompted which of a machine's installed faces can draw a character means reading the cmap of every one of them; implement IFontFallback yourself if you want that.

Whole paragraphs are laid out in the order they are read, by XTextFormatter (justified lines included) and by MigraDoc. Either can be told which way a paragraph runs instead of leaving it to be guessed from the first strong character:

formatter.TextDirection = BidiParagraphDirection.RightToLeft;
paragraph.Format.TextDirection = BidiParagraphDirection.RightToLeft;

One case is left as written: a MigraDoc line containing a tab. Where a tab stop belongs when the text runs the other way is a design question this does not answer. docs/specs/text-shaping-and-bidi.md has the rest.

Fonts

The resolver shipped with each backend discovers .ttf, .otf, .ttc and .otc in the platform's font directories, and every face of a collection separately. Fonts are always embedded — there is no setting for it and never has been, on any platform.

Two limits are worth knowing before you pick a font:

  • OpenType fonts with PostScript (CFF) outlines embed whole. TrueType fonts are subsetted down to the glyphs a document actually draws; CFF outlines cannot be, so an .otf goes in at its full size. For a CJK face that is megabytes per document.
  • A weight or slant a family ships no file for is drawn on, by stroking the glyphs and skewing them. It looks like what a word processor does in the same situation, which is to say noticeably worse than a real bold or italic. Ship the real faces where output quality matters.

SkiaSharp native assets

SkiaSharp is a native library, so an application using PdfPinata.Skia must also reference the native asset package for each platform it runs on. PdfPinata deliberately does not pull these in, so that you can choose the right Linux variant:

<PackageReference Include="SkiaSharp.NativeAssets.Win32" Version="4.150.1" />
<PackageReference Include="SkiaSharp.NativeAssets.Linux.NoDependencies" Version="4.150.1" />
<PackageReference Include="SkiaSharp.NativeAssets.macOS" Version="4.150.1" />

Use SkiaSharp.NativeAssets.Linux instead of ...NoDependencies if libfontconfig1 is available on your Linux image. The PdfPinata.ImageSharp backend is fully managed and needs none of this.

ImageSharp 3.x and later are not supported

PdfPinata.ImageSharp requires SixLabors.ImageSharp 2.1.x. SixLabors relicensed from Apache-2.0 to the Six Labors Split License in ImageSharp 3.0 and Fonts 2.0, so this package stays on the last Apache-2.0 versions rather than pushing that licence onto everyone who installs PdfPinata.

ImageSharp 3.0 is also not binary compatible with 2.1.x — it removed the Image.Load(..., out IImageFormat) overloads and made every encoder property init-only, which changes the setter signature. A build compiled against 2.1.x therefore fails at runtime with MissingMethodException if 3.x is loaded instead (#348).

The dependency is declared as the range [2.1.13,3.0.0), so a mismatch surfaces at restore rather than at runtime:

  • If ImageSharp 3.x arrives transitively, restore fails with NU1107 (version conflict).

  • If your project references ImageSharp 3.x directly, a direct reference wins and you get only a NU1608 warning. Treat it as an error, because the build will still crash at runtime:

    <WarningsAsErrors>$(WarningsAsErrors);NU1608</WarningsAsErrors>
    

Should it get through anyway, the backend reports the mismatch as a descriptive InvalidOperationException naming the loaded version instead of a bare MissingMethodException.

Only ImageSharp carries an upper bound. SixLabors.Fonts is referenced at 1.0.1 for the same licensing reason, but ImageSharpFontResolver uses the small part of its API that 2.x keeps unchanged, so resolving Fonts to 2.x is a licence decision for you to make rather than something that breaks at runtime.

If your application needs ImageSharp 3.x or later, use the PdfPinata.Skia backend instead — it has no ImageSharp dependency, so both can coexist in one project.

Target frameworks

Every shipped package targets netstandard2.1;net8.0;net10.0.

netstandard2.1 is kept for Unity. Unity's scripting runtime tops out at the .NET Standard 2.1 API compatibility level — it cannot consume a net8.0 or net10.0 assembly — so dropping the netstandard2.1 target would drop Unity as a consumer entirely. Please don't remove it without checking that first.

It is not free. netstandard2.1 predates the trimming annotations in System.Diagnostics.CodeAnalysis, so DynamicallyAccessedMembersAttribute and DynamicallyAccessedMemberTypes are polyfilled in-repo, guarded by #if !NET5_0_OR_GREATER so they compile to nothing on the modern targets:

  • PdfPinata/!internal/
  • PinataLayout.DocumentObjectModel/CompileFixes/

Both copies are internal and there is no InternalsVisibleTo, which is why each assembly needs its own. They look like dead code on a net10.0-only glance; they are not. When netstandard2.1 is eventually dropped, both directories can be deleted together.

Table of Contents

Example

The following code snippet creates a simple PDF-file with the text 'Hello World!'. The code is written for a .NET 8 console app with top level statements.

using PdfPinata.Drawing;
using PdfPinata.Fonts;
using PdfPinata.Pdf;
using PdfPinata.Utils;

GlobalFontSettings.FontResolver = new SkiaFontResolver();

var document = new PdfDocument();
var page = document.AddPage();

var gfx = XGraphics.FromPdfPage(page);
var font = new XFont("Arial", 20, XFontStyle.Bold);

var textColor = XBrushes.Black;
var layout = new XRect(20, 20, page.Width, page.Height);
var format = XStringFormats.Center;

gfx.DrawString("Hello World!", font, textColor, layout, format);

document.Save("helloworld.pdf");

Running the demos

SampleApp builds one PDF per feature area and prints the code that drew it.

dotnet run --project SampleApp -- list                       # what each demo shows
dotnet run --project SampleApp -- run                        # all of them, into SampleApp/output
dotnet run --project SampleApp -- run --example Fonts Text   # just these two
dotnet run --project SampleApp -- run --no-code              # PDFs only, no source printed

There are eleven demos, from HelloWorld through Fonts, Orientation, Images, Text, Layout, Tables and PageResize to three real layouts — Invoice, Newspaper and Magazine. Each writes output/<Name>.pdf, deleting what a previous run left rather than writing over it.

The app carries its own fonts — Liberation Sans and Serif, and Source Code Pro for its PostScript outlines — so the PDFs are identical wherever they are built, including a machine with no fonts installed. docs/specs/demonstration-app.md records what it covers and what it deliberately does not.

Running the tests

dotnet test needs no setup. Ghostscript, used to rasterize PDFs for the visual comparison tests, comes from the Ghostscript.NativeAssets package, so there is nothing to install on Windows. On Linux and macOS ImageMagick invokes the system gs delegate instead, so install Ghostscript through your package manager (apt-get install ghostscript, brew install ghostscript) if you want those tests to run.

The visual comparison tests in XTextFormatterTest compare against reference images rendered on Linux. Text rasterizes differently elsewhere because a different set of system fonts is installed, so on other platforms they report as skipped with the reason rather than failing. CI runs on Linux and remains the authority on rendering; if you change text layout, check its result.

Contributing

We appreciate feedback and contribution to this repo!

License

This software is released under the MIT License. See the LICENSE file for more info.

PdfPinata relies on the following projects, that are not under the MIT license:

  • SixLabors.ImageSharp and SixLabors.Fonts
Product Compatible and additional computed target framework versions.
.NET net5.0 was computed.  net5.0-windows was computed.  net6.0 was computed.  net6.0-android was computed.  net6.0-ios was computed.  net6.0-maccatalyst was computed.  net6.0-macos was computed.  net6.0-tvos was computed.  net6.0-windows was computed.  net7.0 was computed.  net7.0-android was computed.  net7.0-ios was computed.  net7.0-maccatalyst was computed.  net7.0-macos was computed.  net7.0-tvos was computed.  net7.0-windows was computed.  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. 
.NET Core netcoreapp3.0 was computed.  netcoreapp3.1 was computed. 
.NET Standard netstandard2.1 is compatible. 
MonoAndroid monoandroid was computed. 
MonoMac monomac was computed. 
MonoTouch monotouch was computed. 
Tizen tizen60 was computed. 
Xamarin.iOS xamarinios was computed. 
Xamarin.Mac xamarinmac was computed. 
Xamarin.TVOS xamarintvos was computed. 
Xamarin.WatchOS xamarinwatchos was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages (1)

Showing the top 1 NuGet packages that depend on PinataLayout.DocumentObjectModel:

Package Downloads
PinataLayout.Rendering

PinataLayout.Rendering for .NET Core PinataLayout.Rendering was ported from MigraDoc version 1.32

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
0.3.0 39 9/22/2026
0.2.1 88 9/21/2026
0.2.0 79 9/20/2026
0.1.0 78 9/18/2026