.NET使用QuestPDF高效地生成PDF文档

发布时间:2024年01月18日

前言

在.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

文档地址:API Reference | QuestPDF

优秀项目和框架精选

该项目已收录到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技术社区微信交流群👪

文章来源:https://blog.csdn.net/qq_37237487/article/details/135664863
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。