High level C# library for the easy creation of PDF documents using PdfSharp. Use this library to create beautiful and complex PDFs using a structure similar to XAML with model binding. Objects called PdfSections are aligned to a grid by stacking or creating a parent/child hierarchy. The rendering engine flows the sections and aligns them to the grid, creating perfect PDF documents every time.
- NuGet Packages
- Examples
- Quick Start
- Section Types
- Fluent Style Builder
- Data Binding
- Conditional Rendering
- Font Resolvers
- Dependency Injection
- Barcode Integration
- Debug Mode
| Package | Description |
|---|---|
| PdfDocuments | Core library for PDF document generation |
| PdfDocuments.FontResolver.Windows | Font resolver using installed Windows system fonts |
| PdfDocuments.FontResolver.Folder | Font resolver loading fonts from a local folder (Linux/Docker friendly) |
| PdfDocuments.IronBarcode | Optional barcode/QR code section powered by IronBarcode |
Install the core package:
PM> Install-Package PdfDocuments
The library has debug flags to assist in troubleshooting layout issues while developing.
The full source for the simple example can be found in the Examples folder of the project source code.
Before generating any PDF, register an encoding provider and configure the font resolver. On Windows, use the Windows font resolver. On Linux or Docker, use the folder font resolver and point it at a directory containing .ttf / .otf files.
using System.Text;
using PdfSharp.Fonts;
// Register an encoding provider (required by PdfSharp)
Encoding.RegisterProvider(CodePagesEncodingProvider.Instance);
// Windows: use system-installed fonts
GlobalFontSettings.FontResolver = new FontResolver.Windows.FontResolver();
// Set the default fallback font
GlobalPdfDocumentsSettings.DefaultFontName = "Arial";Every PDF document is driven by a data model. The model must implement IPdfModel, which requires a single Id string property.
Create Message.cs:
public class Message : IPdfModel
{
public string Id { get; set; }
public string Text { get; set; }
}Create HelloWorld.cs by extending PdfGenerator<TModel> and overriding two lifecycle methods:
OnInitializeStylesAsync— define named stylesOnAddContentAsync— build and return the root section tree
using PdfSharp.Drawing;
public class HelloWorld : PdfGenerator<Message>
{
public HelloWorld(IPdfStyleManager<Message> styleManager)
: base(styleManager)
{
}
protected override Task OnInitializeStylesAsync(IPdfStyleManager<Message> styleManager)
{
this.StyleManager.Add("HelloWorld.Text", Style.Create<Message>()
.UseFont("Arial", 48)
.UseForegroundColor(XColors.Purple)
.UseBorderWidth(1)
.UseTextAlignment(XStringFormats.Center)
.UsePadding(10, 10, 10, 10)
.Build());
return Task.CompletedTask;
}
protected override Task<IPdfSection<Message>> OnAddContentAsync()
{
return Task.FromResult(Pdf.TextBlockSection<Message>()
.WithText((g, m) => m.Text)
.WithStyles("HelloWorld.Text")
.WithStyleManager(this.StyleManager));
}
}OnInitializeStylesAsync registers a named style. The style name can be anything — it only needs to match the name passed to .WithStyles(...) on the section that uses it.
OnAddContentAsync returns the root section. Here a single TextBlockSection is created whose Text property is bound to the Text property of the Message model at render time.
Open Program.cs and wire everything together:
// Create the style manager and the generator
PdfStyleManager<Message> styleManager = new();
HelloWorld helloWorld = new(styleManager);
// Create the data model
Message model = new() { Id = "12345", Text = "Hello World" };
// Generate the PDF as a byte array — no disk I/O required
(bool success, byte[] pdfData) = await helloWorld.BuildAsync(model);
if (success)
{
// Save to disk
string path = Path.Combine(
Environment.GetFolderPath(Environment.SpecialFolder.DesktopDirectory),
$"{model.GetType().Name} [{model.Id}].pdf");
File.WriteAllBytes(path, pdfData);
// Open in the default PDF viewer
Process.Start(new ProcessStartInfo(path) { UseShellExecute = true });
}BuildAsync returns a (bool success, byte[] pdfData) tuple. The byte array can be saved to disk, returned from a REST API, or streamed directly — no temporary file is ever required.
Shortcut: Use
SaveAndOpenPdfAsync(model)to generate, save, and open the PDF in one call (useful during development).
If everything worked, the PDF should look like this:
Sections are the composable building blocks of every document. Nest them in vertical and horizontal stacks to create complex layouts.
| Section | Factory Method | Description |
|---|---|---|
| PdfVerticalStackSection | Pdf.VerticalStackSection<M>(children…) |
Arranges child sections top-to-bottom |
| PdfHorizontalStackSection | Pdf.HorizontalStackSection<M>(children…) |
Arranges child sections left-to-right |
| PdfOverlayStackSection | Pdf.OverlayStackSection<M>(children…) |
Overlays sections on top of each other |
| PdfTextBlockSection | Pdf.TextBlockSection<M>() |
Renders a single line of styled text |
| PdfWrappingTextSection | Pdf.WrappingTextSection<M>() |
Renders text with automatic word-wrap |
| PdfStackedTextSection | Pdf.StackedTextSection<M>() |
Renders multiple text lines vertically |
| PdfImageSection | Pdf.ImageSection<M>() |
Renders an image from a file path |
| PdfPageHeaderSection | Pdf.PageHeaderSection<M>() |
Header with optional logo and title |
| PdfPageFooterSection | Pdf.PageFooterSection<M>() |
Footer with four bindable text corners |
| PdfDataGridSection | Pdf.DataGridSection<M, TItem>() |
Tabular data grid with typed columns |
| PdfDataRowsSection | Pdf.DataRowsSection<M, TItem>() |
Repeating data rows |
| PdfKeyValueSection | Pdf.KeyValueSection<M>(items…) |
Key-value pair list |
| PdfSignatureSection | Pdf.SignatureSection<M>() |
Signature area with optional image |
| PdfContentSection | Pdf.ContentSection<M>(child) |
Wrapper for a single child section |
| PdfHeaderContentSection | Pdf.HeaderContentSection<M>() |
Combined header and content area |
| PdfSectionTemplate | — | Base template for custom sections |
| PdfEmptySection | Pdf.EmptySection<M>() |
Spacer / placeholder |
| PdfBarcodeSection (add-on) | PdfBarcode.BarcodeSection<M>(…) |
Barcode/QR code (requires PdfDocuments.IronBarcode) |
protected override Task<IPdfSection<Invoice>> OnAddContentAsync()
{
return Task.FromResult(
Pdf.VerticalStackSection<Invoice>(
Pdf.PageHeaderSection<Invoice>()
.WithLogo((g, m) => "./Images/logo.png")
.WithTitle((g, m) => "INVOICE")
.WithStyles("Header")
.WithStyleManager(this.StyleManager),
Pdf.HorizontalStackSection<Invoice>(
Pdf.KeyValueSection<Invoice>(
new PdfKeyValueItem<Invoice> { Key = "Invoice #", Value = (g, m) => m.Id },
new PdfKeyValueItem<Invoice> { Key = "Date", Value = (g, m) => m.InvoiceDate.ToShortDateString() }
).WithStyles("KeyValue").WithStyleManager(this.StyleManager),
Pdf.EmptySection<Invoice>()
).WithStyles("Row").WithStyleManager(this.StyleManager),
Pdf.DataGridSection<Invoice, InvoiceItem>()
.UseItems<Invoice, InvoiceItem>((g, m) => m.Items)
.AddColumn<Invoice, InvoiceItem, int>(
"Qty", i => i.Quantity, 0.1, "{0}", "ColHeader", "ColCell")
.AddColumn<Invoice, InvoiceItem, decimal>(
"Price", i => i.UnitPrice, 0.2, "{0:C}", "ColHeader", "ColCell")
.AddColumn<Invoice, InvoiceItem, decimal>(
"Amount", i => i.Amount, 0.2, "{0:C}", "ColHeader", "ColCell")
.WithStyles("DataGrid")
.WithStyleManager(this.StyleManager),
Pdf.PageFooterSection<Invoice>()
.WithBottomLeftText((g, m) => "Thank you for your business.")
.WithBottomRightText((g, m) => $"Page 1")
.WithStyles("Footer")
.WithStyleManager(this.StyleManager)
)
);
}Create named styles using Style.Create<TModel>() and the fluent builder chain.
styleManager.Add("MyStyle", Style.Create<MyModel>()
// Font
.UseFont("Arial", 12, XFontStyleEx.Bold)
// Colors
.UseForegroundColor(XColors.White)
.UseBackgroundColor(XColors.DarkBlue)
.UseBorderColor(XColors.Black)
// Borders
.UseBorderWidth(1)
// Spacing
.UseMargin(5, 5, 5, 5) // left, top, right, bottom
.UsePadding(5, 5, 5, 5)
.UseCellPadding(2, 2, 2, 2) // for data grid cells
// Sizing
.UseRelativeHeight(0.1) // fraction of available height
.UseRelativeWidths(0.5, 0.5) // column width fractions
// Text alignment
.UseTextAlignment(XStringFormats.Center)
.UseParagraphAlignment(XParagraphAlignment.Center)
.Build());Copy and modify an existing style:
styleManager.Add("MyStyleBold", Style.Copy(baseStyle)
.UseFont("Arial", 12, XFontStyleEx.Bold)
.Build());All builder methods also accept a lambda (PdfGridPage grid, TModel model) => value for model-driven, dynamic style properties:
.UseForegroundColor((g, m) => m.IsOverdue ? XColors.Red : XColors.Black)
.UseFont((g, m) => new XFont("Arial", m.FontSize))Text and style properties accept a BindProperty<TValue, TModel> which resolves at render time. Three forms are supported:
// 1. Static value (implicit conversion)
BindProperty<string, MyModel> text = "Hello";
// 2. Model-driven lambda
BindProperty<string, MyModel> text = (grid, model) => model.Title;
// 3. Used inline with section methods
Pdf.TextBlockSection<MyModel>()
.WithText((g, m) => $"Invoice #{m.Id}")Use .WithRenderCondition(...) to show or hide a section based on model data:
Pdf.TextBlockSection<Invoice>()
.WithText((g, m) => "PAID")
.WithRenderCondition((g, m) => m.Paid)
.WithStyles("PaidStamp")
.WithStyleManager(this.StyleManager)PdfSharp requires a font resolver to be registered before any PDF is generated. Choose the one that fits your deployment environment.
Resolves fonts from the Windows system font registry. Ideal for Windows desktop and server applications.
PM> Install-Package PdfDocuments.FontResolver.Windows
Manual setup:
GlobalFontSettings.FontResolver = new FontResolver.Windows.FontResolver();Dependency injection setup:
services.AddWindowsFontResolver();Resolves fonts from a local directory containing .ttf or .otf files. Ideal for Linux, Docker, and cross-platform environments.
PM> Install-Package PdfDocuments.FontResolver.Folder
Manual setup:
GlobalFontSettings.FontResolver = new FontResolver.Folder.FontResolver("./Fonts");Dependency injection setup:
services.AddFolderFontResolver("./Fonts");The library integrates cleanly with Microsoft.Extensions.DependencyInjection. Register the core services and your generators in ConfigureServices:
services.AddPdfDocuments() // registers IPdfGeneratorFactory
.AddScoped<IPdfGenerator, InvoicePdf>() // your custom generator
.AddPdfStyleManager<Invoice>(); // style manager for Invoice modelInject IPdfGeneratorFactory wherever PDF generation is needed. The factory resolves the correct generator from the DI container by matching the TModel type parameter to the registered IPdfGenerator implementation (e.g. InvoicePdf is matched when TModel is Invoice because InvoicePdf extends PdfGenerator<Invoice>):
public class InvoiceService
{
private readonly IPdfGeneratorFactory _factory;
public InvoiceService(IPdfGeneratorFactory factory) => _factory = factory;
public async Task<byte[]> GenerateAsync(Invoice invoice)
{
IPdfGenerator<Invoice> generator = await _factory.GetAsync<Invoice>();
(bool success, byte[] pdf) = await generator.BuildAsync(invoice);
return success ? pdf : null;
}
}See the Invoice example for a full hosted-service integration using Serilog and Microsoft.Extensions.Hosting.
The optional PdfDocuments.IronBarcode package adds barcode and QR code rendering.
PM> Install-Package PdfDocuments.IronBarcode
Set your IronBarcode license key once at startup (skip for development/trial):
PdfBarcode.SetLicense("YOUR-IRONBARCODE-LICENSE-KEY");Add a barcode section anywhere in your document tree:
// QR code bound to model data
PdfBarcode.BarcodeSection<MyModel>(
data: (g, m) => m.TrackingNumber,
barcodeEncoding: BarcodeEncoding.QRCode)
.WithStyles("Barcode")
.WithStyleManager(this.StyleManager)
// Barcode with custom size multipliers
PdfBarcode.BarcodeSection<MyModel>(
data: (g, m) => m.Sku,
barcodeEncoding: BarcodeEncoding.Code128,
heightMultiplier: (g, m) => 0.8,
widthMultiplier: (g, m) => 1.0)
.WithStyles("Barcode")
.WithStyleManager(this.StyleManager)In more complex documents it can be helpful to visualize the grid and section outlines. Set debug flags on the generator before calling BuildAsync:
helloWorld.DebugMode = helloWorld.DebugMode
.SetFlag(DebugMode.RevealGrid, true) // draw the grid lines
.SetFlag(DebugMode.RevealLayout, true) // draw section bounding boxes
.SetFlag(DebugMode.HideDetails, false) // show content inside sections
.SetFlag(DebugMode.RevealFontDetails, true) // overlay font name and size
.SetFlag(DebugMode.OutlineText, false); // draw text outlinesThe available flags are:
| Flag | Effect |
|---|---|
RevealGrid |
Draws the underlying grid lines |
RevealLayout |
Draws a bounding box around each section |
HideDetails |
Hides section content (shows layout only) |
RevealFontDetails |
Overlays font name and size on text sections |
OutlineText |
Renders text with an outline stroke |
The result with grid and layout revealed:



