.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
分为社区版、专业版、和企业版。

项目源代码

创建一个控制台应用
创建一个名为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();
}
}
}
完整示例源代码

示例运行效果图

注意问题
中文报异常
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
DotNetGuide技术社区交流群
- DotNetGuide技术社区是一个面向.NET开发者的开源技术社区,旨在为开发者们提供全面的C#/.NET/.NET Core相关学习资料、技术分享和咨询、项目推荐、招聘资讯和解决问题的平台。
- 在这个社区中,开发者们可以分享自己的技术文章、项目经验、遇到的疑难技术问题以及解决方案,并且还有机会结识志同道合的开发者。
- 我们致力于构建一个积极向上、和谐友善的.NET技术交流平台,为广大.NET开发者带来更多的价值和成长机会。
.NET使用QuestPDF高效地生成PDF文档的更多相关文章
- 利用Java动态生成 PDF 文档
利用Java动态生成 PDF 文档,则需要开源的API.首先我们先想象需求,在企业应用中,客户会提出一些复杂的需求,比如会针对具体的业务,构建比较典型的具备文档性质的内容,一般会导出PDF进行存档.那 ...
- Aspose.Words操作word生成PDF文档
Aspose.Words操作word生成PDF文档 using Aspose.Words; using System; using System.Collections.Generic; using ...
- 如何从Windows Phone 生成PDF文档
我需要从我的Windows Phone应用程序生成PDF. 遗憾的是没有标准的免费的PDF生成库在Windows Phone上运行. 我不得不自己生成PDF,通过直接写入到文件格式. 这竟然是真的很容 ...
- 使用PHP生成PDF文档
原文:使用PHP生成PDF文档 实际工作中,我们要使用PHP动态的创建PDF文档,目前有许多开源的PHP创建PDF的类库,今天我给大家来介绍一款优秀的PDF库,它就是TCPDF,TCPDF是一个用于快 ...
- DocFX生成PDF文档
使用DocFX生成PDF文档,将在线文档转换为PDF离线文档. 关于DocFX的简单介绍使用DocFX生成文档 使用docfx 命令 1.下载 https://github.com/dotnet/do ...
- qt 利用 HTML 生成PDF文档,不能显示jpg图片
利用 QPrinter 和html 生成 pdf文档 其中用html语句有显示图片的语句 但只能显示png格式的图片,不能显示jpg格式图片. 经过排查:语法,文件路径等都正确,最终在stack ov ...
- ireport图形化界面生成pdf文档
一.ireport软件安装 1.下载软件的官网 https://community.jaspersoft.com/project/ireport-designer/releases 2.安装软件 ...
- 自动把动态的jsp页面(或静态html)生成PDF文档,并且上传至服务器
置顶2017年11月06日 14:41:04 阅读数:2311 这几天,任务中有一个难点是把一个打印页面自动给生成PDF文档,并且上传至服务器,然而公司框架只有手动上传文档,打印时可以保存为PDF在本 ...
- Spring Boot集成JasperReports生成PDF文档
由于工作需要,要实现后端根据模板动态填充数据生成PDF文档,通过技术选型,使用Ireport5.6来设计模板,结合JasperReports5.6工具库来调用渲染生成PDF文档.本人文采欠缺,写作能力 ...
- 使用PHP类TCPDF生成PDF文档
转自:http://www.blhere.com/1180.html 这两天遇到一个项目中,需要php自动处理生成pdf文档.在网上找了好几个类,最后决定使用TCPDF,使用的时候真是发现这个类真是强 ...
随机推荐
- 算法训练 递归 s01串
问题描述 s01串初始为"0" 按以下方式变换 0变1,1变01 输入格式 1个整数(0~19) 输出格式 n次变换后s01串 样例输入 3 样例输出 101 数据规模和约定 0~ ...
- javascript+php 实现blob加密视频(html video)
1.mp4地址加密为blob链接在html5的video标签展示 PHP: 1 $file_path = "...mp4"; //视频文件地址 2 ob_end_clean(); ...
- [ABC262F] Erase and Rotate
Problem Statement You are given a sequence $P = (p_1,p_2,\ldots,p_N)$ that contains $1,2,\ldots,N$ e ...
- 【scikit-learn基础】--『预处理』之 标准化
数据的预处理是数据分析,或者机器学习训练前的重要步骤.通过数据预处理,可以 提高数据质量,处理数据的缺失值.异常值和重复值等问题,增加数据的准确性和可靠性 整合不同数据,数据的来源和结构可能多种多样, ...
- TypeError: 'module' object is not callable (pytorch在进行MNIST数据集预览时出现的错误)
在使用pytorch在对MNIST数据集进行预览时,出现了TypeError: 'module' object is not callable的错误: 上报错信息图如下: 从图中可以看出,报错位置为第 ...
- 江西财经大学第一届程序设计竞赛 H题- 小P的数学问题
题目链接:https://www.nowcoder.com/acm/contest/115/H 解题思路:分块打表!!! 什么是分块打表呢??? 从这道题我们知道我们要找到最多1*e9的阶乘 那循环暴 ...
- 反转数组、打印数组元素、加强(增强)for循环
package com.guoba.array; public class Demo04 { public static void main(String[] args) { int[] arr = ...
- MySQL优化:12种提升SQL执行效率的有效方法
在数据库管理和优化的世界里,MySQL作为一个流行的关系型数据库管理系统,其性能优化是任何数据密集型应用成功的关键.优化MySQL数据库不仅可以显著提高SQL查询的效率,还能确保数据的稳定性和可靠性. ...
- 【OpenVINO】 使用 OpenVINO CSharp API 部署 PaddleOCR 项目介绍
前言: 在之前的项目中,我们已经使用 OpenVINOTM CSharp API 部署 PaddleOCR 全系列模型,但随着PaddleOCRv4版本发布以及OpenVINO CSharp AP ...
- 记一次uboot编译/经验/教训
记一次uboot编译/经验/教训 - 最近学习uboot移植有关的知识,在了解原理后自己编译uboot,但是出现了好多各式各样的错误,最后换了几次系统环境之后才找到错误来源 1.准备源码与交叉编译工具 ...