在.NET平台中操作生成PDF的类库有很多如常见的有iTextSharp、PDFsharp、Aspose.PDF等,今天我们分享一个用于生成PDF文档的现代开源.NET库:QuestPDF,本文将介绍QuestPDF并使用它快速实现发票PDF文档生成功能。
QuestPDF 是一个用于生成 PDF 文档的现代开源 .NET 库。QuestPDF 由简洁易用的 C# Fluent API 提供全面的布局引擎。轻松生成 PDF 报告、发票、导出等。QuestPDF它提供了一个布局引擎,在设计时考虑了完整的分页支持。与其他库不同,它不依赖于 HTML 到 PDF 的转换,这在许多情况下是不可靠的。相反,它实现了自己的布局引擎,该引擎经过优化,可以满足所有与分页相关的要求。
分为社区版、专业版、和企业版。
创建一个名为QuestPDFTest
的控制台应用。
搜索:QuestPDF
包进行安装。
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;?}
????}
}
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)
????????????};
????????}
????}
}
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");
????????????});
????????}
????}
}
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();
????????}
????}
}
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
该项目已收录到C#/.NET/.NET Core优秀项目和框架精选中,关注优秀项目和框架精选能让你及时了解C#、.NET和.NET Core领域的最新动态和最佳实践,提高开发工作效率和质量。坑已挖,欢迎大家踊跃提交PR推荐或自荐(让优秀的项目和框架不被埋没🤞
)。
https://github.com/YSGStudyHards/DotNetGuide/blob/main/docs/DotNet/DotNetProjectPicks.md