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

前言

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

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

  • https://www.questpdf.com/license/

项目源代码

创建一个控制台应用

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

安装QuestPDF Nuget包

搜索:QuestPDF包进行安装。

快速实现发票PDF文档生成

创建InvoiceModel

namespace QuestPDFExercise
{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 QuestPDFExercise
{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 QuestPDFExercise
{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.Infrastructure;
using QuestPDF;
using QuestPDF.Fluent;namespace QuestPDFExercise
{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/DotNetExercises

示例运行效果图

注意运行时需要把dotnetguide.png图片放到bin\Debug\net8.0文件夹目录中,否则会读不到图片报错:

注意问题

中文报异常

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支持💖。

  • 开源地址:https://github.com/QuestPDF/QuestPDF

  • 在线文档:https://www.questpdf.com/api-reference

优秀项目和框架精选

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

  • GitHub开源地址:https://github.com/YSGStudyHards/DotNetGuide/blob/main/docs/DotNet/DotNetProjectPicks.md

  • Gitee开源地址:https://gitee.com/ysgdaydayup/DotNetGuide/blob/main/docs/DotNet/DotNetProjectPicks.md

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/457043.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

什么样的JSON编辑器才好用

简介 JSON&#xff08;JavaScript Object Notation&#xff09;是一种轻量级的数据交换格式&#xff0c;易于人阅读和编写&#xff0c;同时也便于机器解析和生成。随着互联网和应用程序的快速发展&#xff0c;JSON已经成为数据传输和存储的主要格式之一。在处理和编辑JSON数据…

Python开发日记 -- 实现bin文件的签名

目录 1.数据的不同表现形式签名值不一样&#xff1f; 2.Binascii模块简介 3.问题定位 4.问题总结 1.数据的不同表现形式签名值不一样&#xff1f; Happy Muscle试运行了一段时间&#xff0c;组内同事再一次提出了新的需求&#xff1a;需要对bin文件签名。 PS&#xff1a;服…

vue3 树型视图,利用自定义SFC来定义一个TreeItem,然后进行渲染出一个树形。

1、我们在各种项目中都会碰到树形的视图&#xff0c;所以说这个还是很重要的。 2、项目中我们一般会用现成的组件&#xff08;ant-design、element&#xff09;来处理&#xff0c;这里我们使用自定义的方法&#xff0c;提供一个data来处理&#xff0c;比如这样&#xff1a; 最…

【大数据分析与挖掘模型】matlab实现——非线性回归预测模型

一、实验目的 掌握有关非线性回归的理论知识&#xff0c;通过变量代换把本来应该用非线性回归处理的问题近似转化为线性回归问题&#xff0c;并进行分析预测。 二、实验任务 对非线性回归实例进行编码计算&#xff0c;实例如下&#xff1a; 三、实验过程 1.运行非线性回归中…

AJAX—— jQuery 发送 AJAX 请求

1、get 请求 $.get&#xff08;url&#xff0c;[ data ] , [ callback ] , [ type ]&#xff09; url &#xff1a;请求的 URL 地址 data &#xff1a;请求携带的参数 callback &#xff1a;载入成功时回调函数 type &#xff1a;设置返回内容格式&#xff08;xml&#xf…

duilib的应用 在双屏异分辨率的显示器上 运行显示不出来

背景&#xff1a;win11&#xff0c;duilib应用&#xff0c;双显示器&#xff0c;两台分辨率相同&#xff0c;分别设置不同的缩放以后&#xff0c;应用运行以后&#xff0c;程序闪一下消失或者程序还在&#xff0c;但是UI显示不出来。 原因 窗口风格设置不合理&#xff0c;所以…

什么是域名?什么是泛域名?

域名 定义 域名是互联网上用于识别和定位网站或网络服务的名称。它是由一串用点分隔的字符组成&#xff0c;例如 “baidu.com”。就像是现实生活中建筑物的地址&#xff0c;方便用户在互联网的海量信息中找到特定的网站。 结构 域名从右到左依次为顶级域名&#xff08;TLD&…

【Python爬虫系列】_031.Scrapy_模拟登陆中间件

课 程 推 荐我 的 个 人 主 页:👉👉 失心疯的个人主页 👈👈入 门 教 程 推 荐 :👉👉 Python零基础入门教程合集 👈👈虚 拟 环 境 搭 建 :👉👉 Python项目虚拟环境(超详细讲解) 👈👈PyQt5 系 列 教 程:👉👉 Python GUI(PyQt5)教程合集 👈👈…

【ArcGIS微课1000例】0125:ArcGIS矢量化无法自动完成面解决方案

文章目录 一、坐标系统问题二、正确使用自动完成面工具一、坐标系统问题 1. 数据库坐标系 arcgis矢量化的过程中,无法自动完成面,可能是因为图层要素没有坐标系造成的。双击数据库打开数据库属性,可以查看当前数据框的坐标系。 2. 图层坐标系 双击图层,打开图层属性,切…

csa练习1

1、修改当前主机名为rhcsa&#xff0c;设置当前时区为Asia/Shanghai 2、在/home/和/root目录下面创建file1文件和dir1目录 3、在/home/file1文件里面写入内容hello&#xff0c;welcome to home 4、在/root/file1文件里面写入当前的时间并写入内容this is administrator 5、在/r…

Etcd 可观测最佳实践

简介 Etcd 是一个高可用的分布式键值存储系统&#xff0c;它提供了一个可靠的、强一致性的存储服务&#xff0c;用于配置管理和服务发现。它最初由 CoreOS 开发&#xff0c;现在由 Cloud Native Computing Foundation (CNCF) 维护。Etcd 使用 Raft 算法来实现数据的一致性&…

基于GPT的智能客服落地实践

&#x1f4cd;前言 在日常生活中&#xff0c;「客服」这个角色几乎贯穿着我们生活的方方面面。比如&#xff0c;淘宝买东西时&#xff0c;需要客服帮你解答疑惑。快递丢失时&#xff0c;需要客服帮忙找回。报名参加培训课程时&#xff0c;需要客服帮忙解答更适合的课程…… 基…

fpga开发环境总结

这里使用Altera(阿尔特拉&#xff09;Cyclone IV E系列的EP4CE10F17C8开发为例&#xff0c;参考正点原子开发板手册进行总结&#xff0c;。 一&#xff0c;Quartus II介绍。 1&#xff0c;Quartus II 是 Altera 公司的综合性 FPGA 开发软件&#xff0c;可以完成从设…

软件测试人员必问的十大面试题..

在软件测试职位面试中&#xff0c;准备并回答一些常见的必问面试题非常重要。这些问题涵盖了软件测试的关键概念、技术和实践&#xff0c;帮助面试官评估你的能力和经验。理解这些问题的重要性是为了在面试中展示你的专业知识和技能&#xff0c;以及你在软件测试领域的实际应用…

使用RabbitMQ实现延迟消息的完整指南

在分布式系统中&#xff0c;消息队列通常用于解耦服务&#xff0c;RabbitMQ是一个广泛使用的消息队列服务。延迟消息&#xff08;也称为延时队列或TTL消息&#xff09;是一种常见的场景应用&#xff0c;特别适合处理某些任务在一段时间后执行的需求&#xff0c;如订单超时处理、…

零基础Java第十期:类和对象(一)

目录 一、拜访对象村 1.1. 什么是面向对象 1.2. 面向对象与面向过程 二、类定义和使用 2.1. 类的定义格式 2.2. 类的定义练习 三、类的实例化 3.1. 什么是实例化 3.2. 类和对象的说明 四、this引用 4.1. 什么是this引用 4.2. this引用的特性 一、拜访对象村 在…

使用python代码绘制好看的统计图

代码功能 上述代码使用 matplotlib 和 seaborn 生成四种不同的统计图&#xff0c;具体如下&#xff1a; 玫瑰图&#xff1a;在极坐标上绘制柱状图&#xff0c;展示不同角度的数值分布。雷达图&#xff1a;绘制多维数据的雷达图&#xff0c;用于对比不同维度的数值。热力图&am…

<项目代码>YOLOv8煤矿输送带异物识别<目标检测>

YOLOv8是一种单阶段&#xff08;one-stage&#xff09;检测算法&#xff0c;它将目标检测问题转化为一个回归问题&#xff0c;能够在一次前向传播过程中同时完成目标的分类和定位任务。相较于两阶段检测算法&#xff08;如Faster R-CNN&#xff09;&#xff0c;YOLOv8具有更高的…

java项目之基于web的智慧社区设计与实现(springboot)

风定落花生&#xff0c;歌声逐流水&#xff0c;大家好我是风歌&#xff0c;混迹在java圈的辛苦码农。今天要和大家聊的是一款基于springboot的基于web的智慧社区设计与实现。项目源码以及部署相关请联系风歌&#xff0c;文末附上联系信息 。 项目简介&#xff1a; 基于web的智…

【优先算法】双指针 --(结合例题讲解解题思路)(C++)

今日鸡汤&#xff1a; “无人负我青云志&#xff0c;我自踏雪至山巅。” -徐霞客《青云志》 释义&#xff1a;没有人能够帮助我实现我的理想&#xff0c;即使面对再大的困难&#xff0c;我也要踏着积雪&#xff0c;一步步&#xff0c;到达山巅。 目录 1.快乐数 2.盛最多的…