Your first PDF in two minutes
Majorsilence.Pdf is a zero-dependency, self-contained PDF library for .NET 8 and 10. One package, no native binaries — create documents, draw text, shapes, and images, embed TrueType fonts, and save to a file or stream.
- Installation
- Hello, PDF
- API styles
- Text & TextStyle
- Text wrapping
- Shapes
- Opacity
- Lines & curves
- Images
- Tables
- Links & tooltips
- TrueType fonts
- Multi-page documents
- Save options
- Metadata & version
- PDF/A conformance
- Right-to-left text
- Password protection
- Digital signatures
- Public-key encryption
- PDF merge
Installation
A single NuGet package — no companion libraries required:
# .NET CLI dotnet add package Majorsilence.Pdf # Package Manager Console Install-Package Majorsilence.Pdf
Hello, PDF
Create a document with PdfDocument.Create(), add a page, draw on the canvas, and save. Coordinates are in PDF points (1 pt = 1/72 inch), with the top-left corner as the origin — Y increases downward.
using Majorsilence.Pdf; PdfDocument.Create() .AddPage(PageSizes.A4, canvas => { canvas.DrawText("Hello, PDF!", 72, 72, TextStyle.Default.WithSize(24).WithBold()); canvas.DrawLine(72, 96, 520, 96); }) .Save("hello.pdf");
API styles — callback vs incremental
Choose whichever fits your code structure:
Callback style (fluent chain)
Pass a drawing lambda to AddPage. The document is returned so you can keep chaining. Best for short documents built in one pass.
PdfDocument.Create()
.WithTitle("My Report")
.AddPage(PageSizes.A4, canvas =>
{
canvas.DrawText("Chapter 1", 72, 72,
TextStyle.Default.WithSize(18).WithBold());
})
.AddPage(PageSizes.A4, canvas =>
{
canvas.DrawText("Chapter 2", 72, 72,
TextStyle.Default.WithSize(18).WithBold());
})
.Save("report.pdf");
Incremental style
AddPage without a callback returns the PdfCanvas directly. Useful when building content imperatively, e.g. inside a loop.
var doc = PdfDocument.Create().WithTitle("My Report"); foreach (var chapter in chapters) { var canvas = doc.AddPage(PageSizes.A4); canvas.DrawText(chapter.Title, 72, 72, TextStyle.Default.WithSize(18).WithBold()); // ... draw body ... } doc.Save("report.pdf");
Text & TextStyle
TextStyle is immutable. Every With* method returns a new instance — the original is unchanged. Build a base style once, then derive variants from it.
var heading = TextStyle.Default .WithFamily("Helvetica") .WithSize(22) .WithBold() .WithColor(PdfColor.FromHex("#1A56A0")); var body = TextStyle.Default .WithFamily("Times-Roman") .WithSize(11); var mono = TextStyle.Default .WithFamily("Courier") .WithSize(10); canvas.DrawText("Invoice #1024", 72, 72, heading); canvas.DrawText("Due: July 17, 2026", 72, 106, body); // Alignment canvas.DrawText("Right-aligned", 540, 72, body.WithAlignment(TextAlignment.Right)); // Decorations canvas.DrawText("underlined", 72, 140, body.WithUnderline()); canvas.DrawText("strikethrough", 72, 160, body.WithStrikethrough()); canvas.DrawText("overline", 72, 180, body.WithOverline());
TextStyle reference
| Method | Description |
|---|---|
| .WithFamily(name) | Standard font by name. Built-in: Helvetica, Times-Roman, Courier, Symbol, ZapfDingbats. Also accepts any family registered in FontRegistry. |
| .WithFontFile(path) | Embed a TrueType / OpenType font by absolute file path. Takes precedence over WithFamily. |
| .WithSize(pts) | Font size in PDF points. Default: 12. |
| .WithColor(color) | Text foreground colour. Default: PdfColor.Black. |
| .WithBold() | Bold weight. |
| .WithItalic() | Italic style. |
| .WithAlignment(a) | TextAlignment.Left (default), Center, Right. X is the reference point. |
| .WithUnderline() | Underline decoration. |
| .WithStrikethrough() | Strikethrough decoration. |
| .WithOverline() | Overline decoration. |
| .WithVertical() | Rotate text 90° counter-clockwise. |
| .WithRightToLeft() | Reverse code-point order for visual RTL rendering (Hebrew, Arabic). Use together with TextAlignment.Right so X is the right anchor. |
PdfColor
// Named colours PdfColor.Black PdfColor.White PdfColor.Red PdfColor.Green PdfColor.Blue PdfColor.Yellow PdfColor.Orange PdfColor.Gray PdfColor.LightGray PdfColor.DarkGray // From CSS hex string PdfColor.FromHex("#1A56A0") // From RGB bytes (0–255) PdfColor.FromRgb(26, 86, 160) // Constructor form — same as FromRgb new PdfColor(26, 86, 160)
Measuring text width
Use MeasureTextWidth to position labels relative to other content:
float w = canvas.MeasureTextWidth("Hello", body); canvas.DrawText("World", 72 + w + 4, 72, body);
Multi-line text wrapping
DrawTextBox word-wraps text into a bounding box. Hard newlines (\n) force a line break; words are never split mid-word. The method returns the index into the string where text overflowed the box, so you can continue the paragraph in a second box or on the next page.
var body = TextStyle.Default.WithFamily("LiberationSans").WithSize(11); string text = "Lorem ipsum dolor sit amet, consectetur adipiscing elit. " + "Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.\n\n" + "Second paragraph starts here."; // Render into a 400 × 120 pt box; returns index of first character that did not fit int overflow = canvas.DrawTextBox(text, x: 72, y: 72, width: 400, height: 120, body); if (overflow < text.Length) { // Continue overflowed text in a second box below canvas.DrawTextBox(text.Substring(overflow), x: 72, y: 210, width: 400, height: 120, body); } // Centre- and right-aligned boxes canvas.DrawTextBox("Centred text\nSecond line", 72, 350, 300, 80, body.WithAlignment(TextAlignment.Center)); canvas.DrawTextBox("Right-aligned\nAll lines snap right", 72, 450, 300, 80, body.WithAlignment(TextAlignment.Right));
DrawTextBox reference
| Parameter | Description |
|---|---|
| text | The string to render. \n forces a line break at that position. |
| x, y | Top-left corner of the bounding box in PDF points. |
| width, height | Box dimensions. Text that would extend below y + height is omitted. |
| style | TextStyle — font, size, colour, and alignment are all respected. TextAlignment is relative to the box width. |
| returns int | Index of the first character that did not fit. Equal to text.Length if all text fitted. |
Shapes
ShapeStyle controls fill and stroke independently. All shape methods accept an optional ShapeStyle; the default is a 1 pt black stroke with no fill.
// Rectangles canvas.DrawRectangle(50, 50, 200, 80, ShapeStyle.Filled(PdfColor.LightGray)); canvas.DrawRectangle(50, 150, 200, 80, ShapeStyle.Stroked(PdfColor.Black, width: 2)); canvas.DrawRectangle(50, 250, 200, 80, ShapeStyle.Filled(PdfColor.FromHex("#E8F4FF")) .WithStroke(PdfColor.Blue, width: 1)); canvas.DrawRectangle(50, 350, 200, 80, ShapeStyle.Stroked(PdfColor.DarkGray).Dashed()); // Ellipses (same ShapeStyle options) canvas.DrawEllipse(300, 50, 150, 80, ShapeStyle.Filled(PdfColor.Blue)); // Polygons var triangle = new List<(float x, float y)> { (300, 200), (400, 350), (200, 350) }; canvas.DrawPolygon(triangle, ShapeStyle.Filled(PdfColor.Orange) .WithStroke(PdfColor.DarkGray));
ShapeStyle reference
| Factory / method | Description |
|---|---|
| ShapeStyle.Filled(color) | Solid fill, no stroke. |
| ShapeStyle.Stroked(color, width) | Stroke only, no fill. Width defaults to 1. |
| .WithFill(color) | Add or replace fill colour. |
| .WithStroke(color, width) | Add or replace stroke colour and width. |
| .WithNoFill() | Remove fill. |
| .WithNoStroke() | Remove stroke. |
| .Dashed() | Dashed line style for the stroke. |
| .Dotted() | Dotted line style for the stroke. |
| .WithFillOpacity(alpha) | Fill transparency. 1.0 = opaque (default); 0.0 = fully transparent. See Opacity. |
| .WithStrokeOpacity(alpha) | Stroke transparency. Same scale as WithFillOpacity. |
Opacity & transparency
Set fill and stroke transparency independently on shapes, or set overall line opacity via StrokeStyle. All opacity values range from 0.0 (fully transparent) to 1.0 (fully opaque, the default).
// Fill opacity ramp — five blue squares from opaque to 20 % opacity for (int i = 0; i < 5; i++) canvas.DrawRectangle(72 + i * 50, 100, 40, 40, ShapeStyle.Filled(PdfColor.Blue).WithFillOpacity(1f - i * 0.2f)); // Stroke opacity canvas.DrawRectangle(72, 170, 200, 40, ShapeStyle.Stroked(PdfColor.Red, 3f).WithStrokeOpacity(0.4f)); // Overlapping semi-transparent shapes — backgrounds show through canvas.DrawRectangle(80, 240, 150, 100, ShapeStyle.Filled(PdfColor.Red)); canvas.DrawRectangle(155, 265, 150, 100, ShapeStyle.Filled(PdfColor.Blue).WithFillOpacity(0.5f)); canvas.DrawEllipse(115, 300, 150, 80, ShapeStyle.Filled(PdfColor.Green).WithFillOpacity(0.4f)); // Line opacity via StrokeStyle canvas.DrawLine(72, 400, 450, 400, StrokeStyle.Default.WithWidth(4).WithColor(PdfColor.DarkGray).WithOpacity(0.5f));
Opacity API summary
| Method | Description |
|---|---|
| ShapeStyle.WithFillOpacity(alpha) | Fill transparency for rectangles, ellipses, and polygons. |
| ShapeStyle.WithStrokeOpacity(alpha) | Stroke transparency for the same shape types. |
| StrokeStyle.WithOpacity(alpha) | Opacity for lines drawn with DrawLine and DrawCurve. |
Lines & curves
StrokeStyle is to lines what ShapeStyle is to filled shapes.
// Straight lines canvas.DrawLine(72, 100, 520, 100); // 1 pt solid black canvas.DrawLine(72, 130, 520, 130, StrokeStyle.Default.WithWidth(2).WithColor(PdfColor.Blue)); canvas.DrawLine(72, 160, 520, 160, StrokeStyle.Default.WithWidth(1).Dashed()); // Smooth curve through control points (Catmull-Rom spline) var pts = new List<(float, float)> { (72, 300), (150, 260), (250, 320), (350, 270), (450, 310), (520, 280) }; canvas.DrawCurve(pts, StrokeStyle.Default.WithWidth(2).WithColor(PdfColor.Red));
Images
DrawImage accepts either raw JPEG bytes or raw RGB24 bytes (3 bytes per pixel, row-major). The image is placed with its top-left corner at (x, y) and scaled to the specified dimensions.
// JPEG from disk byte[] jpeg = File.ReadAllBytes("logo.jpg"); canvas.DrawImage(jpeg, pixelWidth: 400, pixelHeight: 200, isJpeg: true, x: 72, y: 72, width: 200, height: 100); // Raw RGB24 (e.g. a gradient generated in code) const int W = 200, H = 150; var rgb = new byte[W * H * 3]; for (int row = 0; row < H; row++) for (int col = 0; col < W; col++) { int i = (row * W + col) * 3; rgb[i] = (byte)(col * 255 / W); // R rgb[i + 1] = (byte)(row * 255 / H); // G rgb[i + 2] = 128; // B } canvas.DrawImage(rgb, W, H, isJpeg: false, x: 72, y: 72, width: 200, height: 150);
Tables
PdfTable lays out a grid with a header row, optional alternating-row background, automatic cell text wrapping, and configurable borders. Pass it to canvas.DrawTable() to render it at a given position.
// Column widths in PDF points var table = new PdfTable(new float[] { 180, 80, 90, 90 }) .WithHeaderBackground(new PdfColor(26, 86, 160)) .WithAlternateRowBackground(new PdfColor(240, 245, 252)) .WithBorder(new PdfColor(200, 200, 200), 0.5f) .WithCellPadding(5f) .WithCellTextStyle(TextStyle.Default.WithFamily("LiberationSans").WithSize(10)) .WithHeaderTextStyle( TextStyle.Default.WithFamily("LiberationSans").WithSize(10) .WithBold().WithColor(PdfColor.White)); // First AddRow call becomes the header row table.AddRow("Product", "Qty", "Unit Price", "Total"); table.AddRow("PDF Library Pro", "3", "$400.00", "$1 200.00"); table.AddRow("Report Designer", "1", "$250.00", "$250.00"); table.AddRow("Support (12 mo.)", "1", "$500.00", "$500.00"); // tableBottom receives the Y coordinate just below the last rendered row canvas.DrawTable(table, x: 72, y: 72, out float tableBottom); // Border-less "report" style var report = new PdfTable(new float[] { 200, 100, 100 }) .WithNoBorder() .WithHeaderBackground(new PdfColor(245, 245, 245)) .WithCellPadding(4f) .WithCellTextStyle(TextStyle.Default.WithFamily("LiberationSans").WithSize(10)) .WithHeaderTextStyle( TextStyle.Default.WithFamily("LiberationSans").WithSize(10).WithBold()); report.AddRow("Category", "Revenue", "Growth"); report.AddRow("North America", "$1.24M", "+12%"); report.AddRow("Europe", "$0.89M", "+8%"); canvas.DrawTable(report, 72, 72 + tableBottom + 20);
PdfTable reference
| Method | Description |
|---|---|
| new PdfTable(widths) | Column widths in PDF points. The number of elements sets the column count. |
| .WithHeaderBackground(color) | Background fill for the first (header) row. |
| .WithAlternateRowBackground(color) | Background fill for every even data row. |
| .WithBorder(color, width) | Grid line colour and width. |
| .WithNoBorder() | Suppress all grid lines. |
| .WithCellPadding(pts) | Inner padding (all sides) applied to each cell. |
| .WithCellTextStyle(style) | Default TextStyle for data cells. |
| .WithHeaderTextStyle(style) | TextStyle for the header row. |
| .AddRow(col1, col2, …) | Add a row. Pass one string per column. The first call produces the header row; subsequent calls produce data rows. Long cell text wraps automatically. |
| canvas.DrawTable(table, x, y) | Render the table at the given position. |
| canvas.DrawTable(table, x, y, out float bottom) | Same, but also returns the Y coordinate just below the last row — useful for placing content below the table. |
Links & tooltips
Add clickable hyperlinks or hover tooltips over any rectangular area. The area is defined as top-left (x, y) plus width and height.
var linkStyle = TextStyle.Default .WithColor(PdfColor.Blue).WithUnderline(); canvas.DrawText("Visit majorsilence.com", 72, 110, linkStyle); canvas.AddLink(72, 96, width: 220, height: 18, uri: "https://majorsilence.com"); // Tooltip (shows in supporting PDF viewers on hover) canvas.DrawRectangle(72, 140, 200, 40, ShapeStyle.Filled(PdfColor.LightGray).WithStroke(PdfColor.Gray)); canvas.DrawText("Hover for info", 82, 165, TextStyle.Default); canvas.AddTooltip(72, 140, 200, 40, tooltip: "This text appears on hover.");
TrueType fonts
The five built-in standard fonts (Helvetica, Times-Roman, Courier, Symbol, ZapfDingbats) cover ASCII + Latin-1. For Unicode text, emoji, or CJK, embed a TrueType font.
Embed by file path
The simplest option: point TextStyle directly at a .ttf or .otf file. The font is read once per document and cached.
var custom = TextStyle.Default .WithFontFile("/usr/share/fonts/truetype/liberation/LiberationSans-Regular.ttf") .WithSize(14); canvas.DrawText("café résumé naïve", 72, 72, custom); canvas.DrawText("Bold variant", 72, 96, custom.WithBold());
FontRegistry — named families + fallback chain
Register one or more families under logical names, define the fallback chain (used when the primary font has no glyph), and attach the registry to the document. The canvas then segments text automatically, routing each character to the first font that can render it.
var fonts = new FontRegistry() // Register individual variants .AddFamily("LiberationSans", regular: "Fonts/LiberationSans-Regular.ttf", bold: "Fonts/LiberationSans-Bold.ttf", italic: "Fonts/LiberationSans-Italic.ttf", boldItalic: "Fonts/LiberationSans-BoldItalic.ttf") // Or scan a directory — detects variants from filenames .AddDirectory("Fonts/") // NotoSans fills in any glyph LiberationSans is missing .AddFallback("NotoSans"); PdfDocument.Create() .WithFontRegistry(fonts) .AddPage(PageSizes.A4, canvas => { var style = TextStyle.Default .WithFamily("LiberationSans").WithSize(14); canvas.DrawText("Hello κόσμε мир", 72, 72, style); canvas.DrawText("Bold text", 72, 96, style.WithBold()); canvas.DrawText("Italic text", 72, 118, style.WithItalic()); }) .Save("output.pdf");
AddDirectory expects filenames in the pattern FamilyName-Regular.ttf, FamilyName-Bold.ttf, etc. Characters that no registered font can render are emitted as .notdef boxes — no exception is thrown. Use registry.Contains("FamilyName") to check whether a family was successfully loaded before using it.
Multi-page documents
Call AddPage as many times as needed. Pages are written in the order added.
var doc = PdfDocument.Create() .WithTitle("Report") .WithAuthor("Majorsilence") .WithFontRegistry(fonts); string[] chapters = { "Introduction", "Methods", "Results" }; var title = TextStyle.Default.WithSize(24).WithBold(); var footer = TextStyle.Default.WithSize(9).WithColor(PdfColor.Gray); for (int i = 0; i < chapters.Length; i++) { int pageNum = i + 1; doc.AddPage(PageSizes.A4, canvas => { canvas.DrawText(chapters[i], 72, 72, title); canvas.DrawText( $"Page {pageNum} of {chapters.Length}", PageSizes.A4.Width / 2, PageSizes.A4.Height - 30, footer.WithAlignment(TextAlignment.Center)); }); } doc.Save("report.pdf");
PageSizes.Letter.Landscape() (or any size) to swap width and height. Mix orientations freely across pages in the same document.
Save options
// Write to a file doc.Save("report.pdf"); // Write to any Stream (e.g. an ASP.NET response body) doc.Save(Response.Body); // Get bytes — useful for returning from an API endpoint byte[] bytes = doc.ToBytes(); return File(bytes, "application/pdf", "report.pdf");
Task.Run if you need to call from async code without blocking the thread pool.
Metadata & PDF version
Metadata appears in the viewer's document properties. The PDF version controls the file header and cross-reference format.
PdfDocument.Create()
.WithTitle("Invoice #INV-2026-0042")
.WithAuthor("Majorsilence Corp")
.WithSubject("Sales invoice")
.WithCreator("MyApp 1.0")
// PDF 1.4 (default) — broadest reader compatibility
.WithVersion(PdfVersion.Pdf14)
// PDF 2.0 — adds XMP metadata stream + compressed xref
// .WithVersion(PdfVersion.Pdf20)
.AddPage(PageSizes.Letter, canvas => { /* ... */ })
.Save("invoice.pdf");
| Version | Notes |
|---|---|
| PdfVersion.Pdf14 | Default. %PDF-1.4 header. Traditional cross-reference table. Supported by virtually every PDF viewer. |
| PdfVersion.Pdf20 | ISO 32000-2. %PDF-2.0 header. XMP metadata stream attached to the document catalog. Compressed cross-reference stream instead of a plain xref table. |
PDF/A conformance
WithConformance marks a document as PDF/A — an ISO subset of PDF designed for long-term archival. The library automatically inserts the required XMP metadata stream and an embedded sRGB ICC output intent. All fonts must be embedded via FontRegistry; standard Type 1 fonts, encryption, and transparency (for level A-1b) are not permitted.
var fonts = new FontRegistry().AddDirectory("Fonts/").AddFallback("NotoSans"); PdfDocument.Create() .WithConformance(PdfConformance.PdfA2b) // sets PDF 1.7 header automatically .WithFontRegistry(fonts) // embedded fonts are required .WithTitle("Archived Document") .WithAuthor("Majorsilence") .WithSubject("Archival demonstration") .AddPage(PageSizes.A4, canvas => { canvas.DrawText("Archival content", 72, 72, TextStyle.Default.WithFamily("LiberationSans").WithSize(14)); }) .Save("archive.pdf");
PdfConformance levels
| Level | Based on | Notes |
|---|---|---|
| PdfConformance.PdfA1b | PDF 1.4 | Maximum reader compatibility. No transparency. Widely used for archival. |
| PdfConformance.PdfA2b | PDF 1.7 | Allows transparency. Recommended for new archives. |
| PdfConformance.PdfA3b | PDF 1.7 | Same as PDF/A-2b plus supports embedded file attachments. |
WithConformance. Use a PDF/A validator such as veraPDF to confirm full conformance of your output. PDF/A cannot be combined with password protection or public-key encryption.
Right-to-left text
Call .WithRightToLeft() on a TextStyle to reverse code-point order for visual RTL rendering. Pair it with TextAlignment.Right so the X coordinate is the right anchor. Hebrew renders correctly without a shaping engine; Arabic shows visual reversal (correct ligatures require an external shaper like HarfBuzz).
using Majorsilence.Pdf; // Build a registry with Hebrew and Arabic script fonts var reg = new FontRegistry() .AddDirectory("Fonts/") // picks up NotoSansHebrew, NotoSansArabic, etc. .AddFallback("NotoSans") .AddFallback("NotoSansHebrew") .AddFallback("NotoSansArabic"); // Check whether the optional script font actually loaded string hebrewFamily = reg.Contains("NotoSansHebrew") ? "NotoSansHebrew" : "NotoSans"; float pageW = PageSizes.A4.Width; float rightEdge = pageW - 72; // right margin anchor var rtl = TextStyle.Default .WithFamily(hebrewFamily) .WithSize(16) .WithRightToLeft() .WithAlignment(TextAlignment.Right); PdfDocument.Create() .WithFontRegistry(reg) .AddPage(PageSizes.A4, canvas => { canvas.DrawText("שלום עולם", rightEdge, 72, rtl.WithSize(24).WithBold()); canvas.DrawText("ספר, ישראל, שלום, אהבה", rightEdge, 110, rtl); // Mix LTR and RTL on the same line var ltr = TextStyle.Default.WithFamily("LiberationSans").WithSize(14); canvas.DrawText("Hello, World!", 72, 160, ltr); canvas.DrawText("!שלום, עולם", rightEdge, 160, rtl.WithSize(14)); }) .Save("rtl.pdf");
NotoSansHebrew and NotoSansArabic are included in the bundled Majorsilence.Drawing.Common fonts and are picked up automatically by AddDirectory. Characters that no registered font can render appear as .notdef boxes.
Password protection
Encrypt a document so that readers must supply a password to open it. You can also restrict what the reader is allowed to do with the document (print, copy text, etc.).
The encryption uses the Standard Security Handler Rev 4, AES-128-CBC — the algorithm defined in PDF 1.5–1.7 and supported by every major viewer.
using Majorsilence.Pdf; using Majorsilence.Pdf.Security; var security = PdfSecurity .Protect(userPassword: "open123", ownerPassword: "owner456") .WithPermissions(PdfPermissions.Print | PdfPermissions.CopyText); PdfDocument.Create() .WithSecurity(security) .WithTitle("Confidential Report") .AddPage(PageSizes.A4, canvas => { canvas.DrawText("Confidential document", 72, 72, TextStyle.Default.WithSize(18).WithBold()); }) .Save("protected.pdf");
PdfSecurity & PdfPermissions reference
| Member | Description |
|---|---|
| PdfSecurity.Protect(user, owner) | Create a security descriptor. user is the password needed to open the file; owner unlocks all restrictions. |
| .WithPermissions(flags) | Bitfield of PdfPermissions flags to grant. Default: no permissions (read-only). |
| PdfPermissions.Print | Allow printing. |
| PdfPermissions.CopyText | Allow text selection and copying. |
| doc.WithSecurity(security) | Attach the security descriptor to the document before saving. |
Digital signatures
Embed a PKCS#7 detached digital signature (adbe.pkcs7.detached, SHA-256) using an X509Certificate2 that holds a private key. In production, load the certificate from X509Store or a .pfx file issued by a trusted CA.
using Majorsilence.Pdf; using Majorsilence.Pdf.Security; using System.Security.Cryptography.X509Certificates; // Load a certificate with a private key (from file, store, HSM, etc.) X509Certificate2 cert = X509CertificateLoader.LoadPkcs12( File.ReadAllBytes("signer.pfx"), password: "pfxpassword", X509KeyStorageFlags.Exportable); var sigOpts = new PdfSignatureOptions(cert) .WithReason("Document approval") .WithSignerName("Jane Smith") .WithLocation("Toronto, ON"); PdfDocument.Create() .WithSignature(sigOpts) .WithTitle("Signed Report") .AddPage(PageSizes.A4, canvas => { canvas.DrawText("Digitally signed document", 72, 72, TextStyle.Default.WithSize(18).WithBold()); }) .Save("signed.pdf");
PdfSignatureOptions reference
| Member | Description |
|---|---|
| new PdfSignatureOptions(cert) | Create signature options from an X509Certificate2 with an exportable private key. |
| .WithReason(text) | Signing reason written into the signature dictionary (appears in viewer's signature panel). |
| .WithSignerName(name) | Human-readable signer name. |
| .WithLocation(location) | Physical or logical location of signing. |
| doc.WithSignature(opts) | Attach the signature options to the document. The signature is computed and embedded at save time. |
Visible signature appearance
Call .WithAppearance() to render a visible signature box on the page — useful for documents that require a visible "signed here" field. The box shows a border, "Digitally Signed", and the signer name. Without it the signature is cryptographically present but invisible.
var sigOpts = new PdfSignatureOptions(cert) .WithReason("Document approved") .WithSignerName("Jane Smith") .WithLocation("Toronto, ON") .WithAppearance(x: 72, y: 690, width: 220, height: 60); PdfDocument.Create() .WithSignature(sigOpts) .AddPage(PageSizes.A4, canvas => { /* ... content ... */ }) .Save("signed.pdf");
Certificate-based encryption
Encrypt a PDF so only holders of a specific X.509 certificate's private key can open it — no shared password needed. Uses /Filter /Adobe.PubSec, V=4, AES-128. Multiple recipient certificates are supported; any one holder can open the document.
using Majorsilence.Pdf; using Majorsilence.Pdf.Security; using System.Security.Cryptography.X509Certificates; // Load the recipient's public certificate (no private key needed at encryption time) X509Certificate2 cert = new X509Certificate2("recipient.cer"); var security = PdfPublicKeySecurity.ForRecipients(cert) .WithPermissions(PdfPermissions.Print | PdfPermissions.CopyText); PdfDocument.Create() .WithPublicKeySecurity(security) .WithTitle("Confidential Report") .AddPage(PageSizes.A4, canvas => { canvas.DrawText("Only the certificate holder can open this.", 72, 72, TextStyle.Default.WithSize(14)); }) .Save("encrypted.pdf"); // Multiple recipients — any one of them can open the document var multi = PdfPublicKeySecurity.ForRecipients(cert1, cert2, cert3);
PdfPublicKeySecurity reference
| Member | Description |
|---|---|
| PdfPublicKeySecurity.ForRecipients(cert, …) | Create a public-key security descriptor for one or more recipient certificates. Each recipient's public key encrypts a copy of the document key via CMS EnvelopedData (RSA). |
| .WithPermissions(flags) | Bitfield of PdfPermissions flags to grant (same flags as password protection). |
| doc.WithPublicKeySecurity(security) | Attach the descriptor to the document. Mutually exclusive with WithSecurity (password) and WithConformance (PDF/A). |
System.Security.Cryptography.X509Certificates.CertificateRequest — see example 23 in the example project for the pattern.
PDF merge
PdfMerger concatenates any number of independently-produced PDF byte arrays into a single document. Each source PDF can have different page sizes, orientations, and embedded fonts — they are all preserved in the output.
using Majorsilence.Pdf; // Produce two source documents as byte arrays byte[] coverPage = PdfDocument.Create() .WithTitle("Quarterly Report") .AddPage(PageSizes.A4, canvas => { canvas.DrawRectangle(0, 0, PageSizes.A4.Width, PageSizes.A4.Height, ShapeStyle.Filled(new PdfColor(30, 80, 160))); canvas.DrawText("Quarterly Report — Q2 2026", 72, 280, TextStyle.Default.WithSize(36).WithBold().WithColor(PdfColor.White)); }) .ToBytes(); byte[] contentPages = PdfDocument.Create() .AddPage(PageSizes.A4, canvas => { canvas.DrawText("Financial Summary", 72, 72, TextStyle.Default.WithSize(22).WithBold()); canvas.DrawText("Net Income: $1,500,000", 72, 120, TextStyle.Default.WithSize(12)); }) .ToBytes(); // Merge into a single document byte[] merged = new PdfMerger() .Add(coverPage) .Add(contentPages) .WithTitle("Quarterly Report Q2 2026") .WithAuthor("Example Corp") .WithSubject("Financial Summary") .WithCreator("MyApp 2.0") .Merge(); File.WriteAllBytes("merged.pdf", merged);
PdfMerger reference
| Method | Description |
|---|---|
| new PdfMerger() | Create a new merger. No arguments required. |
| .Add(pdfBytes) | Append the pages of a PDF (as a byte[]) to the merge queue. Call as many times as needed; pages are output in order. |
| .WithTitle(text) | Title for the merged document's metadata. |
| .WithAuthor(text) | Author for the merged document's metadata. |
| .WithSubject(text) | Subject for the merged document's metadata. |
| .WithCreator(text) | Creator application string for the merged document's metadata. |
| .Merge() | Execute the merge and return the combined PDF as a byte[]. The output is always PDF 1.4. |
PdfMerger output is always PDF 1.4 regardless of the version of the source documents.