Skip to content

Write C#. See the page.

A free, fully featured PDF library for .NET. Compose documents in plain C#, export PDF, PDF/A or accessible PDF/UA, and watch the page redraw in your browser as you save.

$ dotnet add package Rustaveli.Pdf
using Rustaveli.Pdf;

Document document = Document.Compose(composition => composition.Section(section =>
{
    section.Trim = PaperSizes.A5;
    section.Margins = Sides.All(15.Millimetres());
    section.DefaultType = TypeStyle.Default.WithTypeface("Noto Sans").WithPointSize(12);

    section.Body().Text("Hello from Rustaveli.Pdf.");
}));

document.ExportPdf("hello.pdf");

The page Hello.cs sets: one line of text on an A5 pagehello.pdf · A5 · 1 page

using Rustaveli.Pdf;

(string Item, string Quantity, string Amount)[] lines =
[
    ("Design review", "4", "1,600.00"),
    ("Implementation", "12", "7,200.00"),
    ("Support, one month", "1", "450.00"),
];

Ink blue = Ink.Hex("#1565C0");

Document invoice = Document.Compose(composition => composition.Section(section =>
{
    section.Trim = PaperSizes.A5;
    section.Margins = Sides.All(40);
    section.DefaultType = TypeStyle.Default.WithPointSize(10);

    section.Body().Stack(stack =>
    {
        stack.SpaceBetween(20);

        stack.Add().Text(text =>
        {
            text.Line("Invoice INV-0042").PointSize(22).Bold().Ink(blue);
            text.Line("Issued 29 September 2026, due in 30 days");
        });

        stack.Add().Table(table =>
        {
            table.Columns(columns =>
            {
                columns.Share();
                columns.Fixed(40);
                columns.Fixed(70);
            });

            table.HeaderRows(header =>
            {
                header.Cell().Fill(blue).Inset(6).Text(text => text.Run("Item").Bold().Ink(Ink.White));
                header.Cell().Fill(blue).Inset(6).FlushRight().Text(text => text.Run("Qty").Bold().Ink(Ink.White));
                header.Cell().Fill(blue).Inset(6).FlushRight().Text(text => text.Run("Amount").Bold().Ink(Ink.White));
            });

            foreach ((string item, string quantity, string amount) in lines)
            {
                table.Cell().StrokeBottom(0.5f).Inset(6).Text(item);
                table.Cell().StrokeBottom(0.5f).Inset(6).FlushRight().Text(quantity);
                table.Cell().StrokeBottom(0.5f).Inset(6).FlushRight().Text(amount);
            }
        });

        stack.Add().FlushRight().Text(text => text.Run("Total due 9,250.00").PointSize(14).Bold());
    });
}));

invoice.ExportPdf("invoice.pdf");

The page Invoice.cs sets: an invoice with a blue title, a table of three lines and the total dueinvoice.pdf · A5 · 1 page

Document document = Document.Compose(composition => composition.Section(section =>
{
    section.Trim = PaperSizes.A5;
    section.Margins = Sides.All(36);

    section.RunningHead().When(page => !page.IsFirst).FlushRight().Text("Annual report, continued");

    section.Body().Stack(stack =>
    {
        SampleData sample = new SampleData(seed: 5);

        stack.SpaceBetween(10);
        stack.Add().Text(text => text.Run("Annual report").PointSize(20));
        stack.Add().KeepTogether().Text(sample.Paragraphs(2));
        stack.Add().Text(sample.Paragraphs(6));
        stack.Add().NewPage();
        stack.Add().RequireSpace(150).Text(text => text.Run("Appendix").PointSize(16));
        stack.Add().Text(sample.Paragraphs(2));
    });
}));

The second page Report.cs sets: the running head reads Annual report, continuedreport · page 2

Every example on this site is compiled and run by the test suite, and every page shown is the one it sets.

Free, for everyone
MIT-licensed, for commercial and closed-source software too, with no revenue limits, licence keys or paid tiers.

Plain .NET
No native dependencies in the core. .NET 10, and .NET Standard 2.0 for .NET Framework 4.7.2 and later.

Standards, checked
PDF/A-2 and PDF/A-3, tagged PDF/UA-1 and AES-256, each validated by veraPDF and qpdf on every change.

A live preview
dotnet watch redraws the page in your browser as you save, with an inspector that opens the code behind any frame.

Find the frame you are after

The preview's inspector lists every frame on a page, each inside the frame that drew it. Point at one to outline it on the page; in the preview, clicking it opens the line of code that made it. These are the invoice's own frames, as the inspector records them.

The invoice's page, with the chosen frame outlined
Point at a frame

Packages

The core needs nothing else. Add a package when a document calls for it.

Rustaveli.Pdf
Layout, text, images and PDF export: everything most documents need.

Rustaveli.Pdf.Raster
Pages as PNG, JPEG or WebP; SVG pages and XPS; image recompression. Uses SkiaSharp.

Rustaveli.Pdf.Shaping
Arabic, Hebrew points, and Indic and South-East Asian scripts. Uses HarfBuzzSharp.

Rustaveli.Pdf.Operations
Existing PDF files: merging, stamping, attaching and protecting.

Rustaveli.Pdf.Preview
A live preview in the browser, redrawn on hot reload.

Using the library

How it works

The project