← 首页
快速入门

两分钟内生成您的第一个 PDF

Majorsilence.Pdf 是一个面向 .NET 8 和 10 的零依赖、自包含 PDF 库。只有一个包,无原生二进制文件——创建文档、绘制文本、形状和图像、嵌入 TrueType 字体,并保存到文件或流。


安装

一个单独的 NuGet 包——无需任何配套库:

# .NET CLI
dotnet add package Majorsilence.Pdf

# Package Manager Console
Install-Package Majorsilence.Pdf

Hello, PDF

使用 PdfDocument.Create() 创建文档,添加一页,在画布上绘制,然后保存。坐标单位为 PDF 点(1 pt = 1/72 英寸),原点位于左上角——Y 值向下递增。

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 风格——回调式与增量式

选择最适合您代码结构的方式:

回调风格(流式链)

AddPage 传入一个绘制 lambda。文档本身会被返回,以便继续链式调用。最适合一次性构建的短文档。

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");

增量风格

不带回调的 AddPage 会直接返回 PdfCanvas。适用于以命令式方式构建内容,例如在循环中。

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");

文本与 TextStyle

TextStyle 是不可变的。每个 With* 方法都会返回一个新实例——原实例保持不变。先构建一个基础样式,再从中派生变体。

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 参考

方法说明
.WithFamily(name)按名称指定标准字体。内置字体:HelveticaTimes-RomanCourierSymbolZapfDingbats。也接受在 FontRegistry 中注册的任意字体族。
.WithFontFile(path)通过绝对文件路径嵌入 TrueType / OpenType 字体。优先级高于 WithFamily
.WithSize(pts)字号,单位为 PDF 点。默认值:12。
.WithColor(color)文本前景色。默认值:PdfColor.Black
.WithBold()粗体字重。
.WithItalic()斜体样式。
.WithAlignment(a)TextAlignment.Left(默认)、CenterRight。X 为参考点。
.WithUnderline()下划线装饰。
.WithStrikethrough()删除线装饰。
.WithOverline()上划线装饰。
.WithVertical()将文本逆时针旋转 90°。
.WithRightToLeft()反转码位顺序以实现视觉上的 RTL 渲染(希伯来语、阿拉伯语)。请与 TextAlignment.Right 搭配使用,使 X 成为右侧锚点。

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)

测量文本宽度

使用 MeasureTextWidth 可以将标签相对于其他内容定位:

float w = canvas.MeasureTextWidth("Hello", body);
canvas.DrawText("World", 72 + w + 4, 72, body);

多行文本换行

DrawTextBox 会将文本自动换行以适应一个边界框。硬换行符(\n)会强制换行;单词永远不会被从中间截断。该方法返回文本溢出边界框的字符位置索引,以便您可以在第二个框中或下一页继续该段落。

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 参考

参数说明
text要渲染的字符串。\n 会在该位置强制换行。
x, y边界框左上角在 PDF 点单位下的坐标。
width, height边界框尺寸。超出 y + height 的文本会被省略。
styleTextStyle——字体、字号、颜色和对齐方式均会生效。TextAlignment 是相对于边界框宽度而言的。
返回值 int第一个未能容纳的字符的索引。如果全部文本都被容纳,则等于 text.Length

形状

ShapeStyle 独立控制填充和描边。所有形状方法都接受一个可选的 ShapeStyle;默认值为 1 pt 黑色描边、无填充。

// 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 参考

工厂方法 / 方法说明
ShapeStyle.Filled(color)纯色填充,无描边。
ShapeStyle.Stroked(color, width)仅描边,无填充。宽度默认为 1。
.WithFill(color)添加或替换填充颜色。
.WithStroke(color, width)添加或替换描边颜色和宽度。
.WithNoFill()移除填充。
.WithNoStroke()移除描边。
.Dashed()为描边设置虚线样式。
.Dotted()为描边设置点线样式。
.WithFillOpacity(alpha)填充透明度。1.0 = 不透明(默认);0.0 = 完全透明。参见不透明度
.WithStrokeOpacity(alpha)描边透明度。与 WithFillOpacity 使用相同的取值范围。

不透明度与透明度

可以在形状上独立设置填充和描边的透明度,或通过 StrokeStyle 设置整体线条的不透明度。所有不透明度值的范围为 0.0(完全透明)到 1.0(完全不透明,默认值)。

// 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));

不透明度 API 摘要

方法说明
ShapeStyle.WithFillOpacity(alpha)矩形、椭圆和多边形的填充透明度。
ShapeStyle.WithStrokeOpacity(alpha)相同形状类型的描边透明度。
StrokeStyle.WithOpacity(alpha)使用 DrawLineDrawCurve 绘制的线条的不透明度。

线条与曲线

StrokeStyle 之于线条,正如 ShapeStyle 之于填充形状。

// 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));

图像

DrawImage 接受原始 JPEG 字节,或原始 RGB24 字节(每像素 3 字节,按行主序排列)。图像的左上角会放置在 (x, y) 处,并按指定尺寸缩放。

// 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);

表格

PdfTable 用于布局一个带有表头行、可选交替行背景、自动单元格文本换行以及可配置边框的网格。将其传递给 canvas.DrawTable() 即可在指定位置渲染它。

// 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 参考

方法说明
new PdfTable(widths)各列宽度,单位为 PDF 点。元素个数决定列数。
.WithHeaderBackground(color)首行(表头行)的背景填充色。
.WithAlternateRowBackground(color)每个偶数数据行的背景填充色。
.WithBorder(color, width)网格线颜色和宽度。
.WithNoBorder()取消所有网格线。
.WithCellPadding(pts)应用于每个单元格的内边距(四周)。
.WithCellTextStyle(style)数据单元格的默认 TextStyle
.WithHeaderTextStyle(style)表头行的 TextStyle
.AddRow(col1, col2, …)添加一行。每列传入一个字符串。第一次调用生成表头行,后续调用生成数据行。较长的单元格文本会自动换行。
canvas.DrawTable(table, x, y)在指定位置渲染表格。
canvas.DrawTable(table, x, y, out float bottom)与上面相同,但同时返回最后一行下方的 Y 坐标——便于在表格下方放置内容。

链接与工具提示

在任意矩形区域上添加可点击的超链接或悬停工具提示。该区域由左上角 (x, y) 加上 widthheight 定义。

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 字体

五种内置标准字体(Helvetica、Times-Roman、Courier、Symbol、ZapfDingbats)覆盖 ASCII + Latin-1。若需要 Unicode 文本、表情符号或 CJK,请嵌入一种 TrueType 字体。

通过文件路径嵌入

最简单的方式:让 TextStyle 直接指向一个 .ttf.otf 文件。字体在每个文档中只会被读取一次并被缓存。

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——命名字体族 + 回退链

在一个或多个逻辑名称下注册字体族,定义回退链(当主字体没有相应字形时使用),并将该注册表附加到文档上。画布随后会自动对文本进行分段,将每个字符路由到第一个能够渲染它的字体。

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 期望文件名符合 FamilyName-Regular.ttfFamilyName-Bold.ttf 等模式。任何已注册字体都无法渲染的字符会被输出为 .notdef 方框——不会抛出异常。使用前可通过 registry.Contains("FamilyName") 检查某字体族是否已成功加载。

多页文档

按需多次调用 AddPage。页面会按添加顺序写出。

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()(或任意其他尺寸)即可交换宽度和高度。同一文档内的各页可以自由混用不同的方向。

保存选项

// 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 中。

元数据与 PDF 版本

元数据会出现在阅读器的文档属性中。PDF 版本控制文件头和交叉引用格式。

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");
版本说明
PdfVersion.Pdf14默认值。%PDF-1.4 文件头。传统交叉引用表。几乎所有 PDF 阅读器都支持。
PdfVersion.Pdf20ISO 32000-2。%PDF-2.0 文件头。附加到文档目录上的 XMP 元数据流。使用压缩交叉引用流而非普通 xref 表。

PDF/A 合规

WithConformance 将文档标记为 PDF/A——一种为长期存档而设计的 ISO PDF 子集。该库会自动插入所需的 XMP 元数据流以及一个嵌入的 sRGB ICC 输出意图。所有字体都必须通过 FontRegistry 嵌入;标准 Type 1 字体、加密以及透明度(对于 A-1b 级别)均不被允许。

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 级别

级别基于说明
PdfConformance.PdfA1bPDF 1.4最大程度的阅读器兼容性。无透明度。广泛用于存档。
PdfConformance.PdfA2bPDF 1.7允许透明度。推荐用于新建档案。
PdfConformance.PdfA3bPDF 1.7与 PDF/A-2b 相同,并额外支持嵌入文件附件。
B 级别(视觉可再现性)要求嵌入字体、XMP 元数据和 ICC 输出意图——这些都会由 WithConformance 自动插入。可使用 veraPDF 等 PDF/A 校验工具来确认输出的完全合规性。PDF/A 无法与密码保护或公钥加密同时使用。

从右到左文本

TextStyle 上调用 .WithRightToLeft() 可反转码位顺序,以实现视觉上的 RTL 渲染。请将其与 TextAlignment.Right 搭配使用,使 X 坐标成为右侧锚点。希伯来语无需整形引擎即可正确渲染;阿拉伯语会显示视觉反转效果(正确的连字需要借助 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");
NotoSansHebrewNotoSansArabic 已包含在随附的 Majorsilence.Drawing.Common 字体包中,会被 AddDirectory 自动识别。任何已注册字体都无法渲染的字符会显示为 .notdef 方框。

密码保护

对文档进行加密,使阅读者必须提供密码才能打开。您还可以限制阅读者对文档可执行的操作(打印、复制文本等)。

该加密使用标准安全处理程序第 4 版(Standard Security Handler Rev 4)、AES-128-CBC——这是 PDF 1.5–1.7 中定义、并被所有主流阅读器支持的算法。

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 参考

成员说明
PdfSecurity.Protect(user, owner)创建一个安全描述符。user 是打开文件所需的密码;owner 可解锁所有限制。
.WithPermissions(flags)要授予的 PdfPermissions 标志位字段。默认:不授予任何权限(只读)。
PdfPermissions.Print允许打印。
PdfPermissions.CopyText允许选择和复制文本。
doc.WithSecurity(security)在保存前将安全描述符附加到文档上。

数字签名

使用持有私钥的 X509Certificate2,嵌入一个 PKCS#7 分离式数字签名(adbe.pkcs7.detached,SHA-256)。在生产环境中,请从 X509Store 或由受信任 CA 签发的 .pfx 文件中加载证书。

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 参考

成员说明
new PdfSignatureOptions(cert)基于一个具有可导出私钥的 X509Certificate2 创建签名选项。
.WithReason(text)写入签名字典的签署原因(会出现在阅读器的签名面板中)。
.WithSignerName(name)可读的签署人姓名。
.WithLocation(location)签署的物理或逻辑位置。
doc.WithSignature(opts)将签名选项附加到文档。签名会在保存时计算并嵌入。
该签名使用 SHA-256 和 AES-128(Rev 4),得到广泛支持。请在 Adobe Acrobat 或兼容的阅读器中打开输出文件以验证签名面板。自签名证书除非被添加到阅读器的信任存储中,否则将显示为不受信任。

可见签名外观

调用 .WithAppearance() 可在页面上渲染一个可见的签名框——适用于需要可见“签署处”字段的文档。该框会显示边框、“Digitally Signed”文字以及签署人姓名。若不调用该方法,签名在密码学上依然存在,但不可见。

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");

基于证书的加密

加密一个 PDF,使其只能由特定 X.509 证书私钥的持有者打开——无需共享密码。使用 /Filter /Adobe.PubSec,V=4,AES-128。支持多个接收者证书;任何一个持有者都可以打开该文档。

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 参考

成员说明
PdfPublicKeySecurity.ForRecipients(cert, …)为一个或多个接收者证书创建一个公钥安全描述符。每个接收者的公钥都会通过 CMS EnvelopedData(RSA)加密一份文档密钥的副本。
.WithPermissions(flags)要授予的 PdfPermissions 标志位字段(与密码保护使用相同的标志)。
doc.WithPublicKeySecurity(security)将该描述符附加到文档。与 WithSecurity(密码)和 WithConformance(PDF/A)互斥。
在生产环境中请提供来自受信任 CA 的真实证书。测试时您可以使用 System.Security.Cryptography.X509Certificates.CertificateRequest 在运行时生成一个自签名证书——具体模式请参见示例项目中的示例 23。

PDF 合并

PdfMerger 将任意数量独立生成的 PDF 字节数组串联成一个文档。每个源 PDF 可以拥有不同的页面尺寸、方向和嵌入字体——这些都会在输出中被保留。

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 参考

方法说明
new PdfMerger()创建一个新的合并器。无需任何参数。
.Add(pdfBytes)将一个 PDF(以 byte[] 形式)的页面追加到合并队列中。可根据需要多次调用;页面按顺序输出。
.WithTitle(text)合并后文档元数据中的标题。
.WithAuthor(text)合并后文档元数据中的作者。
.WithSubject(text)合并后文档元数据中的主题。
.WithCreator(text)合并后文档元数据中的创建应用程序字符串。
.Merge()执行合并并以 byte[] 形式返回合并后的 PDF。输出始终为 PDF 1.4。
无论源文档使用什么版本,PdfMerger 的输出始终为 PDF 1.4。