在.NET平臺(tái)中操作生成PDF的類庫有很多如常見的有iTextSharp、PDFsharp、Aspose.PDF等,我們分享一個(gè)用于生成PDF文檔的現(xiàn)代開源.NET庫:QuestPDF,本文將介紹QuestPDF并使用它快速實(shí)現(xiàn)發(fā)票PDF文檔生成功能。
QuestPDF介紹 QuestPDF 是一個(gè)用于生成 PDF 文檔的現(xiàn)代開源 .NET 庫。QuestPDF 由簡潔易用的 C# Fluent API 提供全面的布局引擎。輕松生成 PDF 報(bào)告、發(fā)票、導(dǎo)出等。
QuestPDF它提供了一個(gè)布局引擎,在設(shè)計(jì)時(shí)考慮了完整的分頁支持。與其他庫不同,它不依賴于 HTML 到 PDF 的轉(zhuǎn)換,這在許多情況下是不可靠的。
相反,它實(shí)現(xiàn)了自己的布局引擎,該引擎經(jīng)過優(yōu)化,可以滿足所有與分頁相關(guān)的要求。
QuestPDF License
分為社區(qū)版、專業(yè)版、和企業(yè)版。
項(xiàng)目源代碼 創(chuàng)建一個(gè)控制臺(tái)應(yīng)用 創(chuàng)建一個(gè)名為 QuestPDFTest
的控制臺(tái)應(yīng)用。
安裝QuestPDF Nuget包 搜索: QuestPDF
包進(jìn)行安裝。
快速實(shí)現(xiàn)發(fā)票PDF文檔生成 創(chuàng)建InvoiceModel namespace QuestPDFTest { public class InvoiceModel { /// <summary> /// 發(fā)票號(hào)碼 /// </summary> public int InvoiceNumber { get ; set ; } /// <summary> /// 發(fā)票開具日期 /// </summary> public DateTime IssueDate { get ; set ; } /// <summary> /// 發(fā)票到期日期 /// </summary> public DateTime DueDate { get ; set ; } /// <summary> /// 賣方公司名稱 /// </summary> public string SellerCompanyName { get ; set ; } /// <summary> /// 買方公司名稱 /// </summary> public string CustomerCompanyName { get ; set ; } /// <summary> /// 訂單消費(fèi)列表 /// </summary> public List<OrderItem> OrderItems { get ; set ; } /// <summary> /// 備注 /// </summary> public string Comments { get ; set ; } } public class OrderItem { /// <summary> /// 消費(fèi)類型 /// </summary> public string Name { get ; set ; } /// <summary> /// 消費(fèi)金額 /// </summary> public decimal Price { get ; set ; } /// <summary> /// 消費(fèi)數(shù)量 /// </summary> public int Quantity { get ; set ; } } }
CreateInvoiceDetails namespace QuestPDFTest { public class CreateInvoiceDetails { private static readonly Random _random = new Random(); public enum InvoiceType { 餐飲費(fèi), 交通費(fèi), 住宿費(fèi), 日用品, 娛樂費(fèi), 醫(yī)療費(fèi), 通訊費(fèi), 教育費(fèi), 裝修費(fèi), 旅游費(fèi) } /// <summary> /// 獲取發(fā)票詳情數(shù)據(jù) /// </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 = "追逐時(shí)光者" , CustomerCompanyName = "DotNetGuide技術(shù)社區(qū)" , OrderItems = Enumerable .Range(1 , 20 ) .Select(_ => GenerateRandomOrderItemInfo()) .ToList(), Comments = "DotNetGuide技術(shù)社區(qū)是一個(gè)面向.NET開發(fā)者的開源技術(shù)社區(qū),旨在為開發(fā)者們提供全面的C#/.NET/.NET Core相關(guān)學(xué)習(xí)資料、技術(shù)分享和咨詢、項(xiàng)目推薦、招聘資訊和解決問題的平臺(tái)。在這個(gè)社區(qū)中,開發(fā)者們可以分享自己的技術(shù)文章、項(xiàng)目經(jīng)驗(yàn)、遇到的疑難技術(shù)問題以及解決方案,并且還有機(jī)會(huì)結(jié)識(shí)志同道合的開發(fā)者。我們致力于構(gòu)建一個(gè)積極向上、和諧友善的.NET技術(shù)交流平臺(tái),為廣大.NET開發(fā)者帶來更多的價(jià)值和成長機(jī)會(huì)。" }; } /// <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對(duì)象 /// </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 => { //設(shè)置頁面的邊距 page.Margin(50 ); //字體默認(rèn)大小18號(hào)字體 page.DefaultTextStyle(x => x.FontSize(18 )); //頁眉部分 page.Header().Element(BuildHeaderInfo); //內(nèi)容部分 page.Content().Element(BuildContentInfo); //頁腳部分 page.Footer().AlignCenter().Text(text => { text.CurrentPageNumber(); text.Span(" / " ); text.TotalPages(); }); }); } #region 構(gòu)建頁眉部分 void BuildHeaderInfo (IContainer container ) { container.Row(row => { row.RelativeItem().Column(column => { column.Item().Text($"發(fā)票編號(hào) #{Model.InvoiceNumber} " ).FontFamily("fangsong" ).FontSize(20 ).SemiBold().FontColor(Colors.Blue.Medium); column.Item().Text(text => { text.Span("發(fā)行日期: " ).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} " ); }); }); //在當(dāng)前行的常量項(xiàng)中插入一個(gè)圖像 row.ConstantItem(130 ).Image(LogoImage); }); } #endregion #region 構(gòu)建內(nèi)容部分 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($"總計(jì): {totalPrice} " ).FontFamily("fangsong" ).SemiBold(); if (!string .IsNullOrWhiteSpace(Model.Comments)) column.Item().PaddingTop(25 ).Element(BuildComments); }); } /// <summary> /// 創(chuàng)建表格 /// </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("消費(fèi)類型" ).Style(headerStyle).FontFamily("fangsong" ); header.Cell().AlignRight().Text("花費(fèi)金額" ).Style(headerStyle).FontFamily("fangsong" ); header.Cell().AlignRight().Text("數(shù)量" ).Style(headerStyle).FontFamily("fangsong" ); header.Cell().AlignRight().Text("總金額" ).Style(headerStyle).FontFamily("fangsong" ); //設(shè)置了表頭單元格的屬性 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 構(gòu)建頁腳部分 void BuildComments (IContainer container ) { container.ShowEntire().Background(Colors.Grey.Lighten3).Padding(10 ).Column(column => { column.Spacing(5 ); column.Item().Text("DotNetGuide技術(shù)社區(qū)介紹" ).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、請(qǐng)確保您有資格使用社區(qū)許可證,不設(shè)置的話會(huì)報(bào)異常。 Settings.License = LicenseType.Community; // 2、禁用QuestPDF庫中文本字符可用性的檢查 Settings.CheckIfAllTextGlyphsAreAvailable = false ; // 3、PDF Document 創(chuàng)建 var invoiceSourceData = CreateInvoiceDetails.GetInvoiceDetails(); var document = new CreateInvoiceDocument(invoiceSourceData); // 4、生成 PDF 文件并在默認(rèn)的查看器中顯示 document.GeneratePdfAndShow(); } } }
完整示例源代碼 https://github.com/YSGStudyHards/QuestPDFTest
示例運(yùn)行效果圖 注意問題 中文報(bào)異常 QuestPDF.Drawing.Exceptions.DocumentDrawingException:“Could not find an appropriate font fallback for glyph: U-53 D1 '發(fā)' . 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 ;
原因: 默認(rèn)情況下,使用 QuestPDF 生成 PDF 文檔時(shí),它會(huì)檢查所使用的字體是否支持文本中的所有字符,并在發(fā)現(xiàn)不能顯示的字符時(shí)輸出一條警告消息。這個(gè)選項(xiàng)可以確保文本中的所有字符都能正確地顯示在生成的 PDF 文件中。
中文亂碼問題 解決方案
假如Text("")中為漢字一定要在后面加上FontFamily("fangsong")[仿宋字體]或FontFamily("simhei")[黑體字體],否則中文無法正常顯示。
項(xiàng)目源碼地址 GitHub地址:https://github.com/QuestPDF/QuestPDF
文檔地址:https://www.questpdf.com/api-reference/