PdfMini 1.2.0

dotnet add package PdfMini --version 1.2.0
                    
NuGet\Install-Package PdfMini -Version 1.2.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="PdfMini" Version="1.2.0" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="PdfMini" Version="1.2.0" />
                    
Directory.Packages.props
<PackageReference Include="PdfMini" />
                    
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 PdfMini --version 1.2.0
                    
#r "nuget: PdfMini, 1.2.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 PdfMini@1.2.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=PdfMini&version=1.2.0
                    
Install as a Cake Addin
#tool nuget:?package=PdfMini&version=1.2.0
                    
Install as a Cake Tool

PdfMini 🚀

Ultra-fast, allocation-minimal PDF 1.7 writer for .NET
No rendering engine. No DOM. No bloat.

✔ netstandard2.0
✔ zero external PDF dependencies
✔ stream-first design
✔ ideal for receipts, reports, invoices

A tiny, no-BS PDF generator for .NET — built for speed, predictability, and full control.

If you’re tired of heavyweight PDF “frameworks” that spend more time thinking about layout than producing bytes, this is your antidote.


var doc = new PdfDocument();

var fontBytes = File.ReadAllBytes("arial.ttf");           // put arial.ttf next to exe
var font = doc.EmbedTrueTypeUnicode("F1", fontBytes);

var page = doc.AddPageA4();
var c = new Composer(doc, page, font, margin: 24);

c.Paragraph("Hello PDF!");

doc.Save("hello.pdf");

Why PdfMini exists

Most PDF libraries are designed to be everything for everyone:

  • sprawling object models
  • multi-pass layout engines
  • layers of abstraction
  • hidden “helpful” features (and hidden costs)

PdfMini is the opposite:

  • single-purpose
  • straight-line rendering
  • minimal allocations
  • no DOM, no reflow circus
  • you own the bytes

When PdfMini is the right choice

Library PdfMini iText QuestPDF
Low-level control ⚠️
Memory usage 🔥 tiny heavy heavy
Streaming
Receipt / POS ⚠️

Benchmark (1000 pages)

Real numbers. Same machine. Same job: generate a 1000-page PDF.

  • iText 7: 7.02s
  • PdfMini: 1.76s

✅ PdfMini is ~4.0× faster (7.02 / 1.76 ≈ 3.99)
✅ PdfMini saves 5.26 seconds on 1000 pages


Features

  • Text layout with wrapping + measurement
  • Tables with row splitting / header repeat
  • Images (PNG decode to raw RGB/Alpha)
  • EAN barcodes (EAN-13 / EAN-8/ Code39) rendered as vector bars
  • TrueType font loading + metrics
  • TrueType subsetting/remap support
  • PDF reading/import (page extraction + import flow)

This is not a “toy”. It’s a fast, deterministic PDF pipeline.


Quick start (concept)

Typical flow:

  1. Create a PdfDocument
  2. Add an A4 page
  3. Use Composer to place content
  4. Export to bytes / write to disk

You’ll see classes like:

  • PdfDocument (writer + resources)
  • PdfPageEx / content streams
  • Composer (simple layout driver)
  • Paragraph, Table, Image, BarcodeEan

Design goals

✅ Deterministic performance

No “surprises” on page 800.

✅ Minimal overhead

No giant layout engine doing interpretive dance around your content.

✅ Explicit control

If something costs time, you can see it. If it allocates, you can fix it.


What PdfMini is NOT

  • A full HTML/CSS renderer
  • A general “document model” framework
  • A feature kitchen sink

If you want a Swiss army knife, use a Swiss army knife.
If you want speed, use this.


When to use it

  • High-volume invoice/report generation
  • Batch PDFs (hundreds/thousands of pages)
  • Server-side PDF generation where latency matters
  • Systems where you want predictable CPU + memory

License

Add your license here.


Respectfully (but not gently)

Big libraries have their place — complex docs, standards, long tail features.

But for raw throughput on known layouts? PdfMini doesn’t ask permission. It just prints.

🔥

This library is built around a small set of primitives:

  • PdfDocument / Page: low-level PDF + resources (fonts/images/shadings)
  • Composer: flow layout (margins, cursor, paging, split tables)
  • Paragraph + Text: rich text fragments inside one paragraph
  • Table + Cell: grid layout, spans, per-cell box styling (borders/gradients/padding)
  • Blocks: IPdfBlock (Image, Barcode, Paragraph, Table, RotatedBlock …)

Install

dotnet add package PdfMini

Killer Sample (real-world invoice/report)

This is the kind of code people actually want: watermark underlay, page numbering overlay, themed gradients, split table with repeating header, barcodes, image cells, spanning, and rich text.


var doc = new PdfDocument();
var page = doc.AddPageA4();

string logoPath = Util.ModulePath + "media/sbla.jpg";
doc.EmbedJpegDct("ImLogo", File.ReadAllBytes(logoPath));

var (iwLogo, ihLogo) = doc.GetImageSize("ImLogo");

doc.EmbedJpegDct("WM", File.ReadAllBytes(Util.ModulePath + "media/mmh_water.jpg"));

// 🔑 FONT (required)
var fontBytes = File.ReadAllBytes(Util.ModulePath + "fonts/Helvetica.ttf");
var font = doc.EmbedTrueTypeUnicode("F1", fontBytes);

doc.DrawUnderlay = (cs, pageIndex, pageCount, size) =>
{

cs.SaveState();
cs.SetExtGStateAlpha(0.33f);  // 85% transparent

var (iw, ih) = doc.GetImageSize("WM");

float maxW = size.Width * 0.60f;
float maxH = size.Height * 0.60f;
float scale = Math.Min(maxW / iw, maxH / ih);

float w = iw * scale;
float h = ih * scale;

float x = (page.Width - w) * 0.5f;
float y = (page.Height - h) * 0.5f;

cs.DrawImage("WM", x, y, w, h);

cs.RestoreState();
};

float rightPad = 0f;
float bottomPad = 2f;

doc.DrawOverlay = (cs, pageIndex, pageCount, size) =>
{
var st = new TextStyle
{
Font = font,
FontSize = 9f,
Wrap = WrapMode.None,
Align = HAlign.Right,
AnchorY = YAnchor.Baseline,
TextColor = PdfColorRgb.Gray
};



var p = new Paragraph(st, $"Page {pageIndex + 1} / {pageCount}");

float x = 0f;
float w = size.Width - rightPad;

// baseline from bottom; height=0 => auto one-line box height
p.Draw(cs, x, bottomPad, w, 0);
};


PdfTheme.Ensure(doc);

var c = new Composer(doc, page, font, margin: Units.Mm(15));


// ---- styles ----
var headerBox = new BoxStyle().BackgroundShading(PdfTheme.SteelW_Top)  // ✅ gradient per cell
.Border(0.6f, PdfColorRgb.Gray)
.Padding(Padding.HV(4, 3));

var cellBox = new BoxStyle().Border(0.4f, PdfColorRgb.LightGray).Padding(Padding.HV(4, 3));

var headerText = c.DefaultTextStyle.Clone();
headerText.Font = font;
headerText.FontSize = 11;
headerText.LineHeightFactor = 1.1f;

var bodyText = c.DefaultTextStyle.Clone();
bodyText.Font = font;
bodyText.FontSize = 10;
bodyText.LineHeightFactor = 1.15f;

// ---- table ----
var t = new Table()
.SetColumnsPercent(30, 50, 20)
.SetColumnsHAlign(HAlign.Left, HAlign.Left, HAlign.Right);

// Header row (each header cell draws its own gradient background)
t.AddRow(
Cell.Text("Item", headerText).Box(headerBox),
Cell.Text("Description", headerText).Box(headerBox),
Cell.Text("Amount", headerText).Box(headerBox).Align(HAlign.Right)
);

// Data rows
t.AddRow(
Cell.Text("Apples", bodyText).Box(cellBox),
Cell.Text("Fresh red apples (kg)", bodyText).Box(cellBox),
Cell.Text("12.50", bodyText).Box(cellBox).Align(HAlign.Right)
);

t.AddRow(
Cell.Text("Oranges", bodyText).Box(cellBox),
Cell.Text("Juicy oranges (kg)", bodyText).Box(cellBox),
Cell.Text("9.20", bodyText).Box(cellBox).Align(HAlign.Right)
);

t.AddRow(
Cell.Text("Delivery", bodyText).Box(cellBox),
Cell.Text("Local delivery fee", bodyText).Box(cellBox),
Cell.Text("3.00", bodyText).Box(cellBox).Align(HAlign.Right)
);

t.AddRow(
Cell.Text("Apples — Fresh red apples (kg)", bodyText)
    .Box(cellBox).Align(HAlign.Center)
    .Span(2),                              // 👈 spans first 2 columns
Cell.Text("12.50", bodyText)
    .Box(cellBox)
    .Align(HAlign.Right)
);



// Row 2 — span Item + Description
t.AddRow(
Cell.Text("Oranges — Juicy oranges (kg)", bodyText)
    .Box(cellBox)
    .Span(2),                              // 👈 spans first 2 columns
Cell.Text("9.20", bodyText)
    .Box(cellBox)
    .Align(HAlign.Right)
);

var bc = new BarcodeEan(EanKind.Ean13, "520123456789", font)
{
BarHeight = Units.Mm(8),
HumanFontSize = 9,
HumanGap = 2,
Align = HAlign.Center,
TargetWidth = 180 // optional, else it fills the cell width
};

t.AddRow(new Cell(bc).Box(cellBox).Span(3)  // 👈 spans all 3 columns                
);

// Additional sample rows
t.AddRow(Cell.Text("Bananas", bodyText).Box(cellBox), Cell.Text("Ripe bananas (kg)", bodyText).Box(cellBox), Cell.Text("7.80", bodyText).Box(cellBox).Align(HAlign.Right));
t.AddRow(
Cell.Text("Grapes", bodyText).Box(cellBox),
Cell.Text("Seedless grapes (kg)", bodyText).Box(cellBox),
Cell.Text("15.00", bodyText).Box(cellBox).Align(HAlign.Right)
);

t.AddRow(
Cell.Text("Discount", bodyText).Box(cellBox).Span(2),
Cell.Text("-2.50", bodyText).Box(cellBox).Align(HAlign.Right)
);

// Yet another three sample rows
t.AddRow(
Cell.Text("Pears", bodyText).Box(cellBox),
Cell.Text("Conference pears (kg)", bodyText).Box(cellBox),
Cell.Text("6.40", bodyText).Box(cellBox).Align(HAlign.Right)
);

t.AddRow(
Cell.Text("Melons", bodyText).Box(cellBox),
Cell.Text("Cantaloupe (each)", bodyText).Box(cellBox),
Cell.Text("4.50", bodyText).Box(cellBox).Align(HAlign.Right)
);

t.AddRow(
Cell.Text("Service Charge", bodyText).Box(cellBox).Span(2),
Cell.Text("1.75", bodyText).Box(cellBox).Align(HAlign.Right)
);

// Additional sample rows (added on request)
t.AddRow(
Cell.Text("Yogurt", bodyText).Box(cellBox),
Cell.Text("Greek yogurt (4-pack)", bodyText).Box(cellBox),
Cell.Text("3.20", bodyText).Box(cellBox).Align(HAlign.Right)
);

t.AddRow(
Cell.Text("Cheese", bodyText).Box(cellBox),
Cell.Text("Aged cheddar (200g)", bodyText).Box(cellBox),
Cell.Text("5.50", bodyText).Box(cellBox).Align(HAlign.Right)
);

t.AddRow(
Cell.Text("Olives", bodyText).Box(cellBox),
Cell.Text("Mixed olives (jar)", bodyText).Box(cellBox),
Cell.Text("2.95", bodyText).Box(cellBox).Align(HAlign.Right)
);

var bodyBold = bodyText.Clone();
bodyBold.SimulateBold = true;
bodyBold.BoldStrokeWidthFactor = 0.020f; // try 0.022–0.03

t.AddRow(
Cell.Text("Honey is da best sweety tasty candy in the whole world!", bodyBold).Box(cellBox).Span(2).Align(HAlign.Center),
Cell.Text("8.00", bodyText).Box(cellBox).Align(HAlign.Right)
);



var gridBox = new BoxStyle
{
DrawBorder = true,
BorderWidth = 0.4f,
BorderColor = PdfColorRgb.LightGray,
padding = new Padding(0)
};

var imgBox = gridBox.Clone();
imgBox.padding = Padding.All(1.0f); // ~1pt safe gap from stroke

Cell ImgRow(float sizeMm = 14f)
{
float sz = Units.Mm(sizeMm);

return new Cell(new Image("ImLogo")
{
    Height = sz,
    FixedWidth = sz,
    Align = HAlign.Center,
    VAlign = VAlign.Middle
})
.VAlign(VAlign.Middle)   // 👈 THIS is the missing piece
.Align(HAlign.Center)    // (safe, explicit)
.Box(gridBox);
}

t.AddRow(ImgRow().Box(imgBox).Span(3));
//
var rp = new Paragraph(new TextStyle
{
Font = font,
FontSize = 11,
LineHeightFactor = 1.2f,
Wrap = WrapMode.Word
});

rp.Add(new Text("Hello ", color: PdfColorRgb.Blue));
rp.Add(new Text("BOLD ", font: font, color: PdfColorRgb.Red));
rp.Add(new Text("world", color: PdfColorRgb.Black));

t.AddRow(new Cell(rp).Span(3).Box(cellBox));



// ---- render ----
var pL = TextStyle.Heading2(font).SetAlign(HAlign.Left);
var pC = TextStyle.Heading2(font).SetAlign(HAlign.Center);
var pR = TextStyle.Heading2(font).SetAlign(HAlign.Right);

// remove Heading2 bottom padding so it's one tight line (optional)
pL.Box.PadB = 0;
pC.Box.PadB = 0;
pR.Box.PadB = 0;

var bL = new Paragraph(pL, "Test");
var bC = new Paragraph(pC, "Invoice Items");
var bR = new Paragraph(pR, "Page 1");

// one line height = max of the 3 (same band)
float w = c.ContentWidth;
float h = Math.Max(
bL.MeasureHeight( w),
Math.Max(bC.MeasureHeight( w), bR.MeasureHeight( w))
);

float y = c.ContentTop;

// draw all three into the SAME full-width box => true left/center/right
c.DrawAt(c.ContentLeft, y, bL, w);
c.DrawAt(c.ContentLeft, y, bC, w);
c.DrawAt(c.ContentLeft, y, bR, w);


// advance layout cursor ONCE
c.Spacer(h + Units.Mm(2));


// normal paging; header repeats automatically and keeps gradient
c.TableSplitRowsRepeatHeader(t, headerRows: 1);



doc.Save(Util.ModulePath + "hello_flow.pdf");
            


What this sample demonstrates

  • Resources: embed JPEGs and TrueType Unicode fonts
  • Underlay / Overlay: watermark + page numbering across pages
  • Composer: margins + flow + paging + table splitting
  • Per-cell theming: gradients, borders, padding
  • Table power: column percentages, alignment, row spans
  • Blocks: Image, BarcodeEan, Paragraph inside cells
  • Rich text: Paragraph.Add(new Text(...)) fragments

API sketch

  • PdfDocument
    • AddPageA4()
    • EmbedJpegDct(name, bytes)
    • EmbedTrueTypeUnicode(name, bytes)
    • GetImageSize(name)
    • DrawUnderlay, DrawOverlay delegates
  • Composer
    • ContentLeft, ContentTop, ContentWidth
    • DrawAt(x, yTop, block, width)
    • Spacer(height)
    • TableSplitRowsRepeatHeader(table, headerRows)
  • Paragraph
    • Add(string) or Add(Text)
  • Text
    • Text(value, font: ..., color: ...)
  • Table / Cell
    • SetColumnsPercent(...), SetColumnsHAlign(...)
    • Cell.Text(...), .Box(...), .Align(...), .Span(n)

License

Permissive (MIT/BSD/etc.). No AGPL. No dual-license surprises.

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 was computed.  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 was computed.  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 netcoreapp2.0 was computed.  netcoreapp2.1 was computed.  netcoreapp2.2 was computed.  netcoreapp3.0 was computed.  netcoreapp3.1 was computed. 
.NET Standard netstandard2.0 is compatible.  netstandard2.1 was computed. 
.NET Framework net461 was computed.  net462 was computed.  net463 was computed.  net47 was computed.  net471 was computed.  net472 was computed.  net48 was computed.  net481 was computed. 
MonoAndroid monoandroid was computed. 
MonoMac monomac was computed. 
MonoTouch monotouch was computed. 
Tizen tizen40 was computed.  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.
  • .NETStandard 2.0

    • No dependencies.

NuGet packages

This package is not used by any NuGet packages.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
1.2.0 127 12/27/2025

Ultra fast PDF generation library initial release.