PdfMini 1.2.0
dotnet add package PdfMini --version 1.2.0
NuGet\Install-Package PdfMini -Version 1.2.0
<PackageReference Include="PdfMini" Version="1.2.0" />
<PackageVersion Include="PdfMini" Version="1.2.0" />
<PackageReference Include="PdfMini" />
paket add PdfMini --version 1.2.0
#r "nuget: PdfMini, 1.2.0"
#:package PdfMini@1.2.0
#addin nuget:?package=PdfMini&version=1.2.0
#tool nuget:?package=PdfMini&version=1.2.0
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:
- Create a
PdfDocument - Add an A4 page
- Use
Composerto place content - Export to bytes / write to disk
You’ll see classes like:
PdfDocument(writer + resources)PdfPageEx/ content streamsComposer(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
PdfDocumentAddPageA4()EmbedJpegDct(name, bytes)EmbedTrueTypeUnicode(name, bytes)GetImageSize(name)DrawUnderlay,DrawOverlaydelegates
ComposerContentLeft,ContentTop,ContentWidthDrawAt(x, yTop, block, width)Spacer(height)TableSplitRowsRepeatHeader(table, headerRows)
ParagraphAdd(string)orAdd(Text)
TextText(value, font: ..., color: ...)
Table / CellSetColumnsPercent(...),SetColumnsHAlign(...)Cell.Text(...),.Box(...),.Align(...),.Span(n)
License
Permissive (MIT/BSD/etc.). No AGPL. No dual-license surprises.
| Product | Versions 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. |
-
.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.