前言

在.NET平台中操作生成PDF的类库有很多如常见的有iTextSharp、PDFsharp、Aspose.PDF等,今天我们分享一个用于生成PDF文档的现代开源.NET库:QuestPDF,本文将介绍QuestPDF并使用它快速实现发票PDF文档生成功能。

QuestPDF介绍

QuestPDF 是一个用于生成 PDF 文档的现代开源 .NET 库。QuestPDF 由简洁易用的 C# Fluent API 提供全面的布局引擎。轻松生成 PDF 报告、发票、导出等。QuestPDF它提供了一个布局引擎,在设计时考虑了完整的分页支持。与其他库不同,它不依赖于 HTML 到 PDF 的转换,这在许多情况下是不可靠的。相反,它实现了自己的布局引擎,该引擎经过优化,可以满足所有与分页相关的要求。

QuestPDF License

分为社区版、专业版、和企业版。

项目源代码

创建一个控制台应用

创建一个名为QuestPDFTest的控制台应用。

安装QuestPDF Nuget包

搜索:QuestPDF包进行安装。

快速实现发票PDF文档生成

创建InvoiceModel

namespace QuestPDFTest
{
    public class InvoiceModel
    {

        /// <summary>
        /// 发票号码
        /// </summary>
        public int InvoiceNumber { get; set; }

        /// <summary>
        /// 发票开具日期
        /// </summary>
        public DateTime IssueDate { get; set; }

        /// <summary>
        /// 发票到期日期
        /// </summary>
        public DateTime DueDate { get; set; }

        /// <summary>
        /// 卖方公司名称
        /// </summary>
        public string SellerCompanyName { get; set; }

        /// <summary>
        /// 买方公司名称
        /// </summary>
        public string CustomerCompanyName { get; set; }

        /// <summary>
        /// 订单消费列表
        /// </summary>
        public List<OrderItem> OrderItems { get; set; }

        /// <summary>
        /// 备注
        /// </summary>
        public string Comments { get; set; }
    }

    public class OrderItem
    {
        /// <summary>
        /// 消费类型
        /// </summary>
        public string Name { get; set; }

        /// <summary>
        /// 消费金额
        /// </summary>
        public decimal Price { get; set; }

        /// <summary>
        /// 消费数量
        /// </summary>
        public int Quantity { get; set; }
    }
}

CreateInvoiceDetails

namespace QuestPDFTest
{
    public class CreateInvoiceDetails
    {
        private static readonly Random _random = new Random();

        public enum InvoiceType
        {
            餐饮费,
            交通费,
            住宿费,
            日用品,
            娱乐费,
            医疗费,
            通讯费,
            教育费,
            装修费,
            旅游费
        }

        /// <summary>
        /// 获取发票详情数据
        /// </summary>
        /// <returns></returns>
        public static InvoiceModel GetInvoiceDetails()
        {
            return new InvoiceModel
            {
                InvoiceNumber = _random.Next(1_000, 10_000),
                IssueDate = DateTime.Now,
                DueDate = DateTime.Now + TimeSpan.FromDays(14),
                SellerCompanyName = "追逐时光者",
                CustomerCompanyName = "DotNetGuide技术社区",
                OrderItems = Enumerable
                .Range(1, 20)
                .Select(_ => GenerateRandomOrderItemInfo())
                .ToList(),
                Comments = "DotNetGuide技术社区是一个面向.NET开发者的开源技术社区,旨在为开发者们提供全面的C#/.NET/.NET Core相关学习资料、技术分享和咨询、项目推荐、招聘资讯和解决问题的平台。在这个社区中,开发者们可以分享自己的技术文章、项目经验、遇到的疑难技术问题以及解决方案,并且还有机会结识志同道合的开发者。我们致力于构建一个积极向上、和谐友善的.NET技术交流平台,为广大.NET开发者带来更多的价值和成长机会。"
            };
        }

        /// <summary>
        /// 订单信息生成
        /// </summary>
        /// <returns></returns>
        private static OrderItem GenerateRandomOrderItemInfo()
        {
            var types = (InvoiceType[])Enum.GetValues(typeof(InvoiceType));
            return new OrderItem
            {
                Name = types[_random.Next(types.Length)].ToString(),
                Price = (decimal)Math.Round(_random.NextDouble() * 100, 2),
                Quantity = _random.Next(1, 10)
            };
        }
    }
}

CreateInvoiceDocument

using QuestPDF.Fluent;
using QuestPDF.Helpers;
using QuestPDF.Infrastructure;

namespace QuestPDFTest
{
    public class CreateInvoiceDocument : IDocument
    {
        /// <summary>
        /// 获取Logo的的Image对象
        /// </summary>
        public static Image LogoImage { get; } = Image.FromFile("dotnetguide.png");

        public InvoiceModel Model { get; }

        public CreateInvoiceDocument(InvoiceModel model)
        {
            Model = model;
        }

        public DocumentMetadata GetMetadata() => DocumentMetadata.Default;

        public void Compose(IDocumentContainer container)
        {
            container
                .Page(page =>
                {
                    //设置页面的边距
                    page.Margin(50);

                    //字体默认大小18号字体
                    page.DefaultTextStyle(x => x.FontSize(18));

                    //页眉部分
                    page.Header().Element(BuildHeaderInfo);

                    //内容部分
                    page.Content().Element(BuildContentInfo);

                    //页脚部分
                    page.Footer().AlignCenter().Text(text =>
                    {
                        text.CurrentPageNumber();
                        text.Span(" / ");
                        text.TotalPages();
                    });
                });
        }

        #region 构建页眉部分
        void BuildHeaderInfo(IContainer container)
        {
            container.Row(row =>
            {
                row.RelativeItem().Column(column =>
                {
                    column.Item().Text($"发票编号 #{Model.InvoiceNumber}").FontFamily("fangsong").FontSize(20).SemiBold().FontColor(Colors.Blue.Medium);

                    column.Item().Text(text =>
                    {
                        text.Span("发行日期: ").FontFamily("fangsong").FontSize(13).SemiBold();
                        text.Span($"{Model.IssueDate:d}");
                    });

                    column.Item().Text(text =>
                    {
                        text.Span("终止日期: ").FontFamily("fangsong").FontSize(13).SemiBold();
                        text.Span($"{Model.DueDate:d}");
                    });
                });

                //在当前行的常量项中插入一个图像
                row.ConstantItem(130).Image(LogoImage);
            });
        }

        #endregion

        #region 构建内容部分

        void BuildContentInfo(IContainer container)
        {
            container.PaddingVertical(40).Column(column =>
            {
                column.Spacing(20);

                column.Item().Row(row =>
                {
                    row.RelativeItem().Component(new AddressComponent("卖方公司名称", Model.SellerCompanyName));
                    row.ConstantItem(50);
                    row.RelativeItem().Component(new AddressComponent("客户公司名称", Model.CustomerCompanyName));
                });

                column.Item().Element(CreateTable);

                var totalPrice = Model.OrderItems.Sum(x => x.Price * x.Quantity);
                column.Item().PaddingRight(5).AlignRight().Text($"总计: {totalPrice}").FontFamily("fangsong").SemiBold();

                if (!string.IsNullOrWhiteSpace(Model.Comments))
                    column.Item().PaddingTop(25).Element(BuildComments);
            });
        }

        /// <summary>
        /// 创建表格
        /// </summary>
        /// <param name="container">container</param>
        void CreateTable(IContainer container)
        {
            var headerStyle = TextStyle.Default.SemiBold();

            container.Table(table =>
            {
                table.ColumnsDefinition(columns =>
                {
                    columns.ConstantColumn(25);
                    columns.RelativeColumn(3);
                    columns.RelativeColumn();
                    columns.RelativeColumn();
                    columns.RelativeColumn();
                });

                table.Header(header =>
                {
                    header.Cell().Text("#").FontFamily("fangsong");
                    header.Cell().Text("消费类型").Style(headerStyle).FontFamily("fangsong");
                    header.Cell().AlignRight().Text("花费金额").Style(headerStyle).FontFamily("fangsong");
                    header.Cell().AlignRight().Text("数量").Style(headerStyle).FontFamily("fangsong");
                    header.Cell().AlignRight().Text("总金额").Style(headerStyle).FontFamily("fangsong");
                    //设置了表头单元格的属性
                    header.Cell().ColumnSpan(5).PaddingTop(5).BorderBottom(1).BorderColor(Colors.Black);
                });

                foreach (var item in Model.OrderItems)
                {
                    var index = Model.OrderItems.IndexOf(item) + 1;

                    table.Cell().Element(CellStyle).Text($"{index}").FontFamily("fangsong");
                    table.Cell().Element(CellStyle).Text(item.Name).FontFamily("fangsong");
                    table.Cell().Element(CellStyle).AlignRight().Text($"{item.Price}").FontFamily("fangsong");
                    table.Cell().Element(CellStyle).AlignRight().Text($"{item.Quantity}").FontFamily("fangsong");
                    table.Cell().Element(CellStyle).AlignRight().Text($"{item.Price * item.Quantity}").FontFamily("fangsong");
                    static IContainer CellStyle(IContainer container) => container.BorderBottom(1).BorderColor(Colors.Grey.Lighten2).PaddingVertical(5);
                }
            });
        }

        #endregion

        #region 构建页脚部分

        void BuildComments(IContainer container)
        {
            container.ShowEntire().Background(Colors.Grey.Lighten3).Padding(10).Column(column =>
            {
                column.Spacing(5);
                column.Item().Text("DotNetGuide技术社区介绍").FontSize(14).FontFamily("fangsong").SemiBold();
                column.Item().Text(Model.Comments).FontFamily("fangsong");
            });
        }

        #endregion
    }

    public class AddressComponent : IComponent
    {
        private string Title { get; }
        private string CompanyName { get; }

        public AddressComponent(string title, string companyName)
        {
            Title = title;
            CompanyName = companyName;
        }

        public void Compose(IContainer container)
        {
            container.ShowEntire().Column(column =>
            {
                column.Spacing(2);

                column.Item().Text(Title).FontFamily("fangsong").SemiBold();
                column.Item().PaddingBottom(5).LineHorizontal(1);
                column.Item().Text(CompanyName).FontFamily("fangsong");
            });
        }
    }
}

Program

using QuestPDF;
using QuestPDF.Fluent;
using QuestPDF.Infrastructure;

namespace QuestPDFTest
{
    internal class Program
    {
        static void Main(string[] args)
        {
            // 1、请确保您有资格使用社区许可证,不设置的话会报异常。
            Settings.License = LicenseType.Community;

            // 2、禁用QuestPDF库中文本字符可用性的检查
            Settings.CheckIfAllTextGlyphsAreAvailable = false;

            // 3、PDF Document 创建
            var invoiceSourceData = CreateInvoiceDetails.GetInvoiceDetails();
            var document = new CreateInvoiceDocument(invoiceSourceData);

            // 4、生成 PDF 文件并在默认的查看器中显示
            document.GeneratePdfAndShow();
        }
    }
}

完整示例源代码

https://github.com/YSGStudyHards/QuestPDFTest

示例运行效果图

注意问题

中文报异常

QuestPDF.Drawing.Exceptions.DocumentDrawingException:“Could not find an appropriate font fallback for glyph: U-53D1 '发'. Font families available on current environment that contain this glyph: Microsoft JhengHei, Microsoft JhengHei UI, Microsoft YaHei, Microsoft YaHei UI, SimSun, NSimSun, DengXian, FangSong, KaiTi, SimHei, FZCuHeiSongS-B-GB. Possible solutions: 1) Use one of the listed fonts as the primary font in your document. 2) Configure the fallback TextStyle using the 'TextStyle.Fallback' method with one of the listed fonts. You can disable this check by setting the 'Settings.CheckIfAllTextGlyphsAreAvailable' option to 'false'. However, this may result with text glyphs being incorrectly rendered without any warning.”

加上这段代码:

// 2、禁用QuestPDF库中文本字符可用性的检查
Settings.CheckIfAllTextGlyphsAreAvailable = false;

原因:

默认情况下,使用 QuestPDF 生成 PDF 文档时,它会检查所使用的字体是否支持文本中的所有字符,并在发现不能显示的字符时输出一条警告消息。这个选项可以确保文本中的所有字符都能正确地显示在生成的 PDF 文件中。

中文乱码问题

解决方案:

假如Text("")中为汉字一定要在后面加上FontFamily("fangsong")[仿宋字体]或FontFamily("simhei")[黑体字体],否则中文无法正常显示。

项目源码地址

更多项目实用功能和特性欢迎前往项目开源地址查看,别忘了给项目一个Star支持。

GitHub地址:https://github.com/QuestPDF/QuestPDF

文档地址:https://www.questpdf.com/api-reference/

优秀项目和框架精选

该项目已收录到C#/.NET/.NET Core优秀项目和框架精选中,关注优秀项目和框架精选能让你及时了解C#、.NET和.NET Core领域的最新动态和最佳实践,提高开发工作效率和质量。坑已挖,欢迎大家踊跃提交PR推荐或自荐(让优秀的项目和框架不被埋没)。

https://github.com/YSGStudyHards/DotNetGuide/blob/main/docs/DotNet/DotNetProjectPicks.md

DotNetGuide技术社区交流群

  • DotNetGuide技术社区是一个面向.NET开发者的开源技术社区,旨在为开发者们提供全面的C#/.NET/.NET Core相关学习资料、技术分享和咨询、项目推荐、招聘资讯和解决问题的平台。
  • 在这个社区中,开发者们可以分享自己的技术文章、项目经验、遇到的疑难技术问题以及解决方案,并且还有机会结识志同道合的开发者。
  • 我们致力于构建一个积极向上、和谐友善的.NET技术交流平台,为广大.NET开发者带来更多的价值和成长机会。

欢迎加入DotNetGuide技术社区微信交流群

.NET使用QuestPDF高效地生成PDF文档的更多相关文章

  1. 利用Java动态生成 PDF 文档

    利用Java动态生成 PDF 文档,则需要开源的API.首先我们先想象需求,在企业应用中,客户会提出一些复杂的需求,比如会针对具体的业务,构建比较典型的具备文档性质的内容,一般会导出PDF进行存档.那 ...

  2. Aspose.Words操作word生成PDF文档

    Aspose.Words操作word生成PDF文档 using Aspose.Words; using System; using System.Collections.Generic; using ...

  3. 如何从Windows Phone 生成PDF文档

    我需要从我的Windows Phone应用程序生成PDF. 遗憾的是没有标准的免费的PDF生成库在Windows Phone上运行. 我不得不自己生成PDF,通过直接写入到文件格式. 这竟然是真的很容 ...

  4. 使用PHP生成PDF文档

    原文:使用PHP生成PDF文档 实际工作中,我们要使用PHP动态的创建PDF文档,目前有许多开源的PHP创建PDF的类库,今天我给大家来介绍一款优秀的PDF库,它就是TCPDF,TCPDF是一个用于快 ...

  5. DocFX生成PDF文档

    使用DocFX生成PDF文档,将在线文档转换为PDF离线文档. 关于DocFX的简单介绍使用DocFX生成文档 使用docfx 命令 1.下载 https://github.com/dotnet/do ...

  6. qt 利用 HTML 生成PDF文档,不能显示jpg图片

    利用 QPrinter 和html 生成 pdf文档 其中用html语句有显示图片的语句 但只能显示png格式的图片,不能显示jpg格式图片. 经过排查:语法,文件路径等都正确,最终在stack ov ...

  7. ireport图形化界面生成pdf文档

    一.ireport软件安装 1.下载软件的官网 https://community.jaspersoft.com/project/ireport-designer/releases 2.安装软件   ...

  8. 自动把动态的jsp页面(或静态html)生成PDF文档,并且上传至服务器

    置顶2017年11月06日 14:41:04 阅读数:2311 这几天,任务中有一个难点是把一个打印页面自动给生成PDF文档,并且上传至服务器,然而公司框架只有手动上传文档,打印时可以保存为PDF在本 ...

  9. Spring Boot集成JasperReports生成PDF文档

    由于工作需要,要实现后端根据模板动态填充数据生成PDF文档,通过技术选型,使用Ireport5.6来设计模板,结合JasperReports5.6工具库来调用渲染生成PDF文档.本人文采欠缺,写作能力 ...

  10. 使用PHP类TCPDF生成PDF文档

    转自:http://www.blhere.com/1180.html 这两天遇到一个项目中,需要php自动处理生成pdf文档.在网上找了好几个类,最后决定使用TCPDF,使用的时候真是发现这个类真是强 ...

随机推荐

  1. 放弃"Jenkins"的种种理由,期待更好赋能研发的持续交付平台

    Jenkins 很酷,但是不完美,有历史局限性造成的问题.本文仅从"如何更好给研发团队赋能的角度",剖析Jenkins, 探讨理想的持续交付平台, 不带货无广告- 不完美的Jenk ...

  2. 1. Linux 软件介绍

    重点: rpm -i -e -qi -ql -qf -qa --scripts. yum install remove info list repolist provides. 配置系统源. 搭建私有 ...

  3. Stable Diffusion扩散模型

    人像生成模型 1.模型理论基础 扩散模型(Diffusion Model): 1.1 Diffusion Model 原理 首先,Denoise Model 需要一个起始的噪声图像作为输入.这个噪声图 ...

  4. .NET开源的处理分布式事务的解决方案

    前言 在分布式系统中,由于各个系统服务之间的独立性和网络通信的不确定性,要确保跨系统的事务操作的最终一致性是一项重大的挑战.今天给大家推荐一个.NET开源的处理分布式事务的解决方案基于 .NET St ...

  5. 时间复杂度为 O(n^2) 的排序算法

    对于小规模数据,我们可以选用时间复杂度为 O(n2) 的排序算法.因为时间复杂度并不代表实际代码的执行时间,它省去了低阶.系数和常数,仅代表的增长趋势,所以在小规模数据情况下, O(n2) 的排序算法 ...

  6. [USACO2007FEB S] The Cow Lexicon S

    题目描述 Few know that the cows have their own dictionary with W (1 ≤ W ≤ 600) words, each containing no ...

  7. Go 语言区块链测试:实践指南

    引言 Go 语言在区块链开发中的应用日益增多,凭借其简洁的语法和强大的并发支持,成为开发区块链应用的热门选择.理解和实践 Go 语言的单元测试对于保证区块链应用的质量和稳定性至关重要. Go 单元测试 ...

  8. 9、For语句

    1.for循环概念 程序的流程控制结构一共有三种: 顺序结构,选择结构,循环结构. 顺序结构:代码从上向下逐行的执行 选择结构:条件满足,某些代码才会被执行.0-1次 if语句,switch语句 循环 ...

  9. 微服务项目-小Q视频网

    1.后台管理系统(更新中...) (1)讲师列表 (2)添加讲师 (3)导入课程分类 (4)课程分类列表 其他功能就不一一截图了,后台详细功能如下 2.前台会员系统(更新中...) (1)前台的技术栈 ...

  10. 不会使用 EF Core 的 Code First 模式?来看看这篇文章,手把手地教你

    EF Core Code First 是什么 Code First 是 Entity Framework Core (简称 EF Core) 的一种开发模式,它允许开发人员使用纯粹的代码来定义数据模型 ...