LOGO OA教程 ERP教程 模切知识交流 PMS教程 CRM教程 开发文档 其他文档  
 
网站管理员

【C#】.NET 使用QuestPDF高效地生成PDF文档

admin
2024年5月27日 16:26 本文热度 841

前言

在.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 { getset; }  
  
        /// <summary>  
        /// 发票开具日期  
        /// </summary>  
        public DateTime IssueDate { getset; }  
  
        /// <summary>  
        /// 发票到期日期  
        /// </summary>  
        public DateTime DueDate { getset; }  
  
        /// <summary>  
        /// 卖方公司名称  
        /// </summary>  
        public string SellerCompanyName { getset; }  
  
        /// <summary>  
        /// 买方公司名称  
        /// </summary>  
        public string CustomerCompanyName { getset; }  
  
        /// <summary>  
        /// 订单消费列表  
        /// </summary>  
        public List<OrderItem> OrderItems { getset; }  
  
        /// <summary>  
        /// 备注  
        /// </summary>  
        public string Comments { getset; }  
    }  
  
    public class OrderItem  
    {  
        /// <summary>  
        /// 消费类型  
        /// </summary>  
        public string Name { getset; }  
  
        /// <summary>  
        /// 消费金额  
        /// </summary>  
        public decimal Price { getset; }  
  
        /// <summary>  
        /// 消费数量  
        /// </summary>  
        public int Quantity { getset; }  
    }  
}  

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(120)  
                .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() * 1002),  
                Quantity = _random.Next(110)  
            };  
        }  
    }  
}  

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")[黑体字体],否则中文无法正常显示。

项目源码地址

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

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

- EOF -


该文章在 2024/5/27 16:26:26 编辑过
关键字查询
相关文章
正在查询...
点晴ERP是一款针对中小制造业的专业生产管理软件系统,系统成熟度和易用性得到了国内大量中小企业的青睐。
点晴PMS码头管理系统主要针对港口码头集装箱与散货日常运作、调度、堆场、车队、财务费用、相关报表等业务管理,结合码头的业务特点,围绕调度、堆场作业而开发的。集技术的先进性、管理的有效性于一体,是物流码头及其他港口类企业的高效ERP管理信息系统。
点晴WMS仓储管理系统提供了货物产品管理,销售管理,采购管理,仓储管理,仓库管理,保质期管理,货位管理,库位管理,生产管理,WMS管理系统,标签打印,条形码,二维码管理,批号管理软件。
点晴免费OA是一款软件和通用服务都免费,不限功能、不限时间、不限用户的免费OA协同办公管理系统。
Copyright 2010-2024 ClickSun All Rights Reserved