该页面可以包含自动翻译的文本。

C# PDF 生成快速入门指南

了解如何在 .NET 项目中生成 PDF 文件,例如报告、发票和收据。 使用 C# 或 VB.NET,通过组合结构元素轻松 创建 PDF 文档。 这些元素包括页眉、页脚、容器、表格、段落、图像等。 API 自动将内容分成页面。

在 C# 和 VB.NET 中创建 PDF 文档 一文中描述了生成 PDF 文件的其他一些方法。

PDF生成库简介

我们提供布局 API 作为 Docotic.Pdf 库的免费附加组件。 该库和 Layout 附加组件均可在 NuGet 和我们的网 站上获取。 要尝试没有评估模式限制的库,请在库页面上获取免费的限时许 可证密钥。

Docotic.Pdf 库 9.3.17105-dev 布局附加组件 9.3.17105-dev
回归测试 14,681 通过 NuGet 总下载量 4,234,061

安装

从 NuGet 安装 BitMiracle.Docotic.Pdf.Layout 包。 这是安装 Layout 插件最简单、最方便的方法。

作为替代方案,您可以在我们的网站上下载包含库二进制文件的 ZIP。 要使用 Layout API,请 添加对 ZIP包中的以下 DLL 的引用:

  • BitMiracle.Docotic.Pdf.dll
  • Layout add-on/BitMiracle.Docotic.Pdf.Layout.dll

该方法

入口点是PdfDocumentBuilder类。 要开始生成 PDF,请调用该类的Generate方法。 该方法需要 Action<Document>类型的委托作为其参数之一。 库希望您在委托中布局文档内容。

PdfDocumentBuilder.Create().Generate("output.pdf", (Document doc) =>
{
    // 在这里构建文档内容
});

给定一个Document对象,委托应该定义布局。 完整的布局由较小的构建块组成。 页面、页眉和页脚、容器、 文本块都是此类块的示例。

许多方法调用链接在一起。 链中的调用顺序很重要。 这是一个示例,展示了如何在几个链接调用中设置页面内 容。

PdfDocumentBuilder.Create()
    .Generate("output.pdf", doc => doc.Pages(pages =>
    {
        pages.Content().Padding(24).AlignCenter().Text("Some text");
    }));

Intellisense 将帮助您发现 API。 自己尝试一下 API。 我希望您发现它易于使用。

你好世界!

让我们开发一个简单的应用程序来显示正在运行的常见构建块。 该应用程序将使用上述方法。

我将 完整的应用程序代码 放入我们的 Docotic.Pdf 示例代码存储库中。 此步骤的代码位 于示例应用程序的“HelloWorld”类中。

public void CreatePdf()
{
    PdfDocumentBuilder.Create().Generate("hello.pdf", doc => doc.Pages(page =>
    {
        page.Content().Text("Hello, world!");
    }));
}

代码不多,但是这里发生了什么?

短代码的长解释

该代码创建文档生成器的实例,要求生成器生成hello.pdf。 构建器将文档内容布局的工作委托给我的代码。

在委托的代码中,我要求文档构建一些页面。 现在,文档委托了我的代码布置页面内容的工作。

在页面的委托中,我访问页面主要内容的布局容器。 我通过调用Content方法来做到这一点。 对Text的链式 调用将著名的文本范围添加到页面内容中。

现在是时候让构建器根据提供的布局生成包含一些页面的文档了。 构建器创建包含我添加到主要内容的布局容 器中的数据所需的确切页面数。 在这种情况下一页就够了吗? 好的。

为未来的更新做好准备

我将向代码添加更多功能。 为了方便进一步开发应用,我将代码改成了这样:

public void CreatePdf()
{
    PdfDocumentBuilder.Create().Generate("hello.pdf", BuildDocument);
}

static void BuildDocument(Document doc)
{
    doc.Pages(BuildPages);
}

static void BuildPages(PageLayout pages)
{
    pages.Content().Text("Hello, world!");
}

它可能看起来没有必要,但将代码拆分为方法至少使其更具可读性。

此步骤的示例代码位于示例应用程序HelloWorld2类中。

请查看以下文章以获取有关文档、页面和容器的更多信息。

字体和颜色

默认字体很好,但我将展示如何使用另一种字体。 主要思想是使用字体创建文本样式。 然后,如果需要,对字 体应用一些可选属性,最后在文本范围上使用样式。

您可以使用操作系统中安装的字体集合中的字体,或者从文件或流加载字体。

我建议通过设置文档的版式来覆盖预定义的样式。 但这不是必需的,您可以直接使用文本样式。

此步骤的示例代码位于示例应用程序HelloWorld3类中。 以下是与上一步相比发生变 化的部分。

static void BuildDocument(Document doc)
{
    doc.Typography(t =>
    {
        t.Document = doc.TextStyleWithFont(SystemFont.Family("Calibri"));
    });
    doc.Pages(BuildPages);
}

static void BuildPages(PageLayout pages)
{
    pages.Size(PdfPaperSize.A6);
    pages.Margin(10);

    BuildTextContent(pages.Content());
}

static void BuildTextContent(LayoutContainer content)
{
    var colorAccented = new PdfRgbColor(56, 194, 10);
    var styleAccented = TextStyle.Parent.FontColor(colorAccented);

    content.Text(t =>
    {
        t.Span("Hello, ").Style(styleAccented);
        t.Span("world!").Style(styleAccented.Underline());
    });
}

如您所见,我更新了BuildDocumentBuildPages的代码。 我还添加了一个名为BuildTextContent的新方 法。

BuildDocument中,我从名为“Calibri”的系统字体创建了一个文本样式。 然后我将此文本样式设置为文档的 默认文本样式。

BuildPages方法现在包含用于设置所有页面的大小和边距的代码。 此外,该方法调用BuildTextContent,将 页面主要内容的容器作为参数传递。

我现在构建主要内容的方式已经不同了。 我使用两个文本范围并对每个范围应用不同的文本样式。 实际上,两 个跨度都使用强调色,但第二个跨度也有下划线。

该代码生成具有自定义文本字体和颜色的PDF。 如果您的结果包含试用消息警告,请在 Docotic.Pdf 库页面上获取免费的限时许可证。

许多现实世界的 PDF 文档(例如报告或发票)都包含页眉和页脚。 让我展示如何向 PDF 添加页眉和页脚。

此步骤的示例代码位于示例应用程序HelloWorld4类中。 以下是与上一步相比发生变 化的部分。

static void BuildPages(PageLayout pages)
{
    pages.Size(PdfPaperSize.A6);
    pages.Margin(10);

    BuildPagesHeader(pages.Header());
    BuildPagesFooter(pages.Footer());

    BuildTextContent(pages.Content());
}

static void BuildPagesHeader(LayoutContainer header)
{
    header.TextStyle(TextStyle.Parent.FontSize(8))
        .AlignRight()
        .Text(t =>
        {
            t.Line($"Created by: {Environment.UserName}");
            t.Line($"Date: {DateTime.Now}");
        });
}

static void BuildPagesFooter(LayoutContainer footer)
{
    footer.Height(20)
        .Background(new PdfGrayColor(95))
        .AlignCenter()
        .Text(t => t.CurrentPageNumber());
}

我更改了BuildPages方法以使用标头容器作为参数来调用BuildPagesHeader。 该方法还调用 BuildPagesFooter,将页脚容器传递给它。

生成的带有页眉和页脚的 PDF 看起来 绝对更像是专业的 PDF文档。

BuildPagesHeader 方法为文本设置较小的字体大小。 我使用两行作为标题内容:第一行包含当前用户的名 称,第二行包含当前日期。 文本右对齐。

请注意,我没有指定标题的任何明确大小。 它将占据整个页面宽度减去左右边距。 高度将取决于文本行的高 度。

页脚类似,只是它明确指定了其高度。 并且应用了背景颜色。 有趣的是,要将当前页码放入 PDF 页脚中,我可 以使用 CurrentPageNumber 方法。 所有计算 都在库内进行。

图片

正如他们所说,使用一张图片可以节省很多文字。 在报价或收据中,您可能会使用您公司的徽标或其他一些重要 图像。 对于这个示例代码,我将只使用一个漂亮的代码。

该文件位于我们的网站上,您可以查看生成的 带有美丽图像的 PDF 的外观。

要使用图像,您需要首先将其添加到文档中。 该库可以从文件或流中读取图像。 我更改了BuildDocument方法 来展示如何将图像添加到文档中。

此步骤的示例代码位于示例应用程序HelloWorld5类中。 以下是与上一步相比发生变 化的部分

static void BuildDocument(Document doc)
{
    doc.Typography(t =>
    {
        t.Document = doc.TextStyleWithFont(SystemFont.Family("Calibri"));
    });

    var imageFile = new FileInfo("red-flowers-at-butterfly-world.jpg");
    var image = doc.Image(imageFile);

    doc.Pages(pages => {
        BuildPages(pages);

        pages.Content().Column(c =>
        {
            BuildTextContent(c.Item());
            BuildImageContent(c.Item(), image);
        });
    });
}

static void BuildPages(PageLayout pages)
{
    pages.Size(PdfPaperSize.A6);
    pages.Margin(10);

    BuildPagesHeader(pages.Header());
    BuildPagesFooter(pages.Footer());
}

static void BuildImageContent(LayoutContainer content, Image image)
{
    content.AlignCenter()
        .PaddingVertical(20)
        .Image(image);
}

如您所见,BuildDocument方法有更多变化。 之前,代码使用Text方法将一些文本设置为页面的主要内容。 这仍然发生在 BuildTextContent 方法中。 但现在我还想要内容中的图像。

为了在页面的主要内容中同时包含文本和图像,我需要一个容器。 我使用Column容器依次垂直添加文本和图 像。 列容器的Item方法提供了一个子容器。 我使用对Item方法的一次调用来获取文本的容器,并使用另一 次调用来获取图像的容器。

如您所见,我没有对 BuildTextContent 进行任何更改。 但是,当然,我必须从BuildPages方法中删除对 BuildTextContent的调用。

BuildImageContent 在三行中做了重要的工作。 它在图像的顶部和底部添加了一些填充。 它还使图像在页面 上居中。

列表

什么是清单? 让我们将其视为一组写在另一个下面的编号项目。 这个想法提出了一种实现列表的方法。

此步骤的示例代码位于示例应用程序HelloWorld6类中。 以下是与上一步相比发生变 化的部分。

static void BuildDocument(Document doc)
{
    ....

    doc.Pages(pages => {
        BuildPages(pages);

        pages.Content().Column(c =>
        {
            BuildTextContent(c.Item());
            BuildImageContent(c.Item(), image);
            BuildListContent(c.Item());
        });
    });
}

static void BuildListContent(LayoutContainer content)
{
    var dayNames = DateTimeFormatInfo.InvariantInfo.DayNames;
    var dayNamesSpain = DateTimeFormatInfo.GetInstance(new CultureInfo("es-ES")).DayNames;

    content.Column(column =>
    {
        for (int i = 0; i < dayNames.Length; i++)
        {
            column.Item().Row(row =>
            {
                row.Spacing(5);
                row.AutoItem().Text($"{i + 1}.");
                row.RelativeItem().Text(t => {
                    t.Line(dayNames[i]);
                    t.Line($"In Spain they call it {dayNamesSpain[i]}");
                });
            });
        }
    });
}

BuildDocument 中唯一的变化是新的方法调用。 我在BuildImageContent调用之后添加了对 BuildListContent的调用。

BuildListContent 将列表创建为一列行。 每行有两个项目。 第一个(左)用于项目编号。 另一个(右)用 于项目文本。 该代码显式设置行中项目之间的间距。

为了排列项目,Row容器需要事先知道或计算每个项目的大小。 我在本例中使用AutoItemRelativeItem 方法。 结果,行容器将计算包含第一项所需的宽度。 然后容器将使用第二个项目的剩余可用宽度。

添加列表后,生成的 PDF 内容不再适合一页。 Layout API 自动添加第二页并将列表放在上面。 您可以在多页 PDF文档中看到API在新页面上重复了页眉和页脚。

表格

许多 PDF 文档都包含表格。 这并不奇怪,因为表格增强了数据的清晰度和组织性。 让我展示如何使用 Layout API 在 PDF 中创建表格。

此步骤的示例代码位于示例应用程序HelloWorld7类中。 以下是与上一步相比发生变 化的部分。

static void BuildDocument(Document doc)
{
    ....

    doc.Pages(pages => {
        BuildPages(pages);

        pages.Content().Column(c =>
        {
            BuildTextContent(c.Item());
            BuildImageContent(c.Item(), image);
            BuildListContent(c.Item());
            BuildTableContent(c.Item());
        });
    });
}

static void BuildTableContent(LayoutContainer content)
{
    var color = new PdfGrayColor(75);

    content.PaddingTop(20).Table(t =>
    {
        t.Columns(c =>
        {
            c.RelativeColumn(4);
            c.RelativeColumn(1);
            c.RelativeColumn(4);
        });

        t.Header(h =>
        {
            h.Cell().Background(color).Text("Month in 2024");
            h.Cell().Background(color).Text("Days");
            h.Cell().Background(color).Text("First Day");
        });

        for (int i = 0; i < 12; i++)
        {
            var stats = GetMonthStats(2024, i);

            t.Cell().Text(stats.Item1);
            t.Cell().Text(stats.Item2);
            t.Cell().Text(stats.Item3);
        }
    });
}

static (string, string, string) GetMonthStats(int year, int monthIndex)
{
    return (
        DateTimeFormatInfo.InvariantInfo.MonthNames[monthIndex],
        DateTime.DaysInMonth(year, monthIndex + 1).ToString(),
        new DateTime(year, monthIndex + 1, 1).DayOfWeek.ToString()
    );
}

BuildTableContent的调用是BuildDocument中的唯一更改。

BuildTableContent中,我创建了一个简单的表格。 该表显示了有关 2024 年月份的简单信息。首先,我定 义了具有相对宽度的三列。 最左边和最右边的列将比中间的列宽 4 倍。

该代码还定义了表的标题。 它通过添加标题单元格并为每个单元格指定文本和背景颜色来实现这一点。 当表格 无法容纳在一页上时,标题会在表格占用的每一页上重复。 您可以在生成的 带表格的 PDF 文 档 中看到这一点。 在文档中,表格从第二页开 始,一直到第三页。

我正在使用简单的循环来组成行。 循环中的代码首先检索有关每个月的信息。 然后它添加三个单元格组成一行 信息。

示例代码

上面,我介绍了 C# 中 PDF 生成的一些比较流行的功能。 我建议您阅读以下文章继续发现 API。 另请尝试 GitHub 上的 Docotic.Pdf.Samples 存储库中的示例代码。Layout API 的示例代码位于存储库的Layout 文件 夹中。

您可以使用示例代码作为代码游乐场。 这样您就可以尝试一些想法,而不必每次都从头开始。

相同的示例代码位于 ZIP 包的Samples文件夹中。 有两个解决方案文件。 SamplesCSharp适用于使用 C# 语 言的示例项目。 SamplesVB.NET适用于 VB.NET 版本。