查看原文
其他

.NET 使用 QuestPDF 操作生成 PDF 更快更高效

DotNet 2023-02-16

The following article is from 黑哥聊dotNet Author 黑哥聊dotNet

QuestPDF


QuestPDF是一个开源的工具库,可以在.NET或者.Net Core中生成pdf文档

它提供了一个布局引擎,设计时考虑到了完整的分页支持以及灵活性要求!

比市面上常见的Aspose和iTextSharp好用太多了!

GitHub地址

安装

Install-Package QuestPDF

例子


简单例子


生成Pdf文档一共分为三部分,Header(页眉),Content(内容),Footer(页脚)

Document.Create(container =>  
{  
    container.Page(page =>  
    {  
        page.Size(PageSizes.A4);  
        page.Margin(2, Unit.Centimetre);  
        page.Background(Colors.White);  
        page.DefaultTextStyle(x => x.FontSize(20));  
          
        page.Header()  
            .Text("Hello PDF!")  
            .SemiBold().FontSize(36).FontColor(Colors.Blue.Medium);  
          
        page.Content()  
            .PaddingVertical(1, Unit.Centimetre)  
            .Column(x =>  
            {  
                x.Spacing(20);  
                  
                x.Item().Text(Placeholders.LoremIpsum());  
                x.Item().Image(Placeholders.Image(200100));  
            });  
          
        page.Footer()  
            .AlignCenter()  
            .Text(x =>  
            {  
                x.Span("Page ");  
                x.CurrentPageNumber();  
            });  
    });  
})  
.GeneratePdf("hello.pdf");  
模板生成

使用模板生成一共设计三个应用层的工作:

  • 文档Model(一组描述 PDF 文档内容的类)
  • 数据源(将域实体映射到文档模型的层)
  • 模板(描述如何可视化信息并将其转换为 PDF 文件的表示层)

比如我们设计一个基本的发票信息 要设计一个购物清单,一个卖家买家的地址,以及发票编号等等 我们设计这样的3个Model类

public class InvoiceModel  
{  
    public int InvoiceNumber { getset; }  
    public DateTime IssueDate { getset; }  
    public DateTime DueDate { getset; }  

    public Address SellerAddress { getset; }  
    public Address CustomerAddress { getset; }  

    public List<OrderItem> Items { getset; }  
    public string Comments { getset; }  
}  

public class OrderItem  
{  
    public string Name { getset; }  
    public decimal Price { getset; }  
    public int Quantity { getset; }  
}  

public class Address  
{  
    public string CompanyName { getset; }  
    public string Street { getset; }  
    public string City { getset; }  
    public string State { getset; }  
    public object Email { getset; }  
    public string Phone { getset; }  
}  

Model定义好了之后我们就定义一些假数据来填充pdf

public static class InvoiceDocumentDataSource  
{  
    private static Random Random = new Random();  

    public static InvoiceModel GetInvoiceDetails()  
    {  
        var items = Enumerable  
            .Range(18)  
            .Select(i => GenerateRandomOrderItem())  
            .ToList();  

        return new InvoiceModel  
        {  
            InvoiceNumber = Random.Next(1_000, 10_000),  
            IssueDate = DateTime.Now,  
            DueDate = DateTime.Now + TimeSpan.FromDays(14),  

            SellerAddress = GenerateRandomAddress(),  
            CustomerAddress = GenerateRandomAddress(),  

            Items = items,  
            Comments ="测试备注"  
        };  
    }  

    private static OrderItem GenerateRandomOrderItem()  
    {  
        return new OrderItem  
        {  
            Name = "商品",  
            Price = (decimal)Math.Round(Random.NextDouble() * 1002),  
            Quantity = Random.Next(110)  
        };  
    }  

    private static Address GenerateRandomAddress()  
    {  
        return new Address  
        {  
            CompanyName = "测试商店",  
            Street = "测试街道",  
            City = "测试城市",  
            State = "测试状态",  
            Email = "测试邮件",  
            Phone = "测试电话"  
        };  
    }  
}  

然后搭建我们的模板脚手架 我们要使用模板脚手架,就要定义一个实现IDocument接口的新类开始。该接口包含两个方法

  • DocumentMetadata GetMetadata();

  • void Compose(IDocumentContainer container);

第一个是模板文档的一些基础信息 第二个是模板的容器 基于这些原则我们设计一个模板层类

public class InvoiceDocument : IDocument  
    {  
        public InvoiceModel Model { get; }  
  
        public InvoiceDocument(InvoiceModel model)  
        {  
            Model = model;  
        }  
  
        public DocumentMetadata GetMetadata() => DocumentMetadata.Default;  
  
        public void Compose(IDocumentContainer container)  
        {  
            container  
                .Page(page =>  
                {  
                    page.PageColor(Colors.Red.Lighten1);  
                    page.Size(PageSizes.A4);  
                    page.Margin(10);//外边距  
  
        
                    page.Header().Height(100).Background(Colors.LightBlue.Lighten1);  
                    page.Content().Background(Colors.Grey.Lighten3);  
                    page.Footer().Height(50).Background(Colors.Grey.Lighten1);  
                });  
        }  
}  

pdf的page页面总是有三个元素:页眉,页脚,内容。查看一下我们生成的文档

到目前为止,我们已经搭建了一个非常简单的页面,其中每个部分都有不同的颜色或大小

接下来我们来填充他的页眉,我们把数据源整理好了之后,就可以调用Element方法填充

public void Compose(IDocumentContainer container)  
{  
    container  
        .Page(page =>  
        {  
            page.PageColor(Colors.Red.Lighten1);  
            page.Size(PageSizes.A4);  
            page.Margin(10);//外边距  


            page.Header().Height(100).Background(Colors.LightBlue.Lighten1).Element(ComposeHeader);  
            page.Content().Background(Colors.Grey.Lighten3);  
            page.Footer().Height(50).Background(Colors.Grey.Lighten1);  
        });  
}  


void ComposeHeader(IContainer container)  
{  
    var titleStyle = TextStyle.Default.FontSize(20).SemiBold().FontColor(Colors.Blue.Medium);  

    container.Row(row =>  
    {  
        row.RelativeItem().Column(column =>  
        {  
            column.Item().Text($"发票 #{Model.InvoiceNumber}").FontFamily("simhei").Style(titleStyle);  

            column.Item().Text(text =>  
            {  
                text.Span("发行日期: ").SemiBold().FontFamily("simhei");  
                text.Span($"{Model.IssueDate:d}").FontFamily("simhei");  
            });  

            column.Item().Text(text =>  
            {  
                text.Span("支付日期: ").FontFamily("simhei").SemiBold();  
                text.Span($"{Model.DueDate:d}").FontFamily("simhei");  
            });  

        })  
        ;  

    });  
}  

最后我们来实现内容,

public void Compose(IDocumentContainer container)  
{  
    container  
        .Page(page =>  
        {  
            page.PageColor(Colors.Red.Lighten1);  
            page.Size(PageSizes.A4);  
            page.Margin(10);//外边距  


            page.Header().Height(100).Background(Colors.LightBlue.Lighten1).Element(ComposeHeader);  
            page.Content().Background(Colors.Grey.Lighten3).Element(ComposeContent);  
            page.Footer().Height(50).Background(Colors.Grey.Lighten1);  
        });  
}  


void ComposeHeader(IContainer container)  
{  
    var titleStyle = TextStyle.Default.FontSize(20).SemiBold().FontColor(Colors.Blue.Medium);  

    container.Row(row =>  
    {  
        row.RelativeItem().Column(column =>  
        {  
            column.Item().Text($"发票 #{Model.InvoiceNumber}").FontFamily("simhei").Style(titleStyle);  

            column.Item().Text(text =>  
            {  
                text.Span("发行日期: ").SemiBold().FontFamily("simhei");  
                text.Span($"{Model.IssueDate:d}").FontFamily("simhei");  
            });  

            column.Item().Text(text =>  
            {  
                text.Span("支付日期: ").FontFamily("simhei").SemiBold();  
                text.Span($"{Model.DueDate:d}").FontFamily("simhei");  
            });  

        })  
        ;  

    });  
}  

void ComposeContent(IContainer container)  
{  
    container.Table(table =>  
    {  
        // step 1  
        table.ColumnsDefinition(columns =>  
        {  
            columns.ConstantColumn(25);  
            columns.RelativeColumn(3);  
            columns.RelativeColumn();  
            columns.RelativeColumn();  
            columns.RelativeColumn();  
        });  

        // step 2  
        table.Header(header =>  
        {  
            header.Cell().Text("#").FontFamily("simhei");  
            header.Cell().Text("商品").FontFamily("simhei");  
            header.Cell().AlignRight().Text("价格").FontFamily("simhei");  
            header.Cell().AlignRight().Text("数量").FontFamily("simhei");  
            header.Cell().AlignRight().Text("总价").FontFamily("simhei");  

            header.Cell().ColumnSpan(5)  
                .PaddingVertical(5).BorderBottom(1).BorderColor(Colors.Black);  
        });  

        // step 3  
        foreach (var item in Model.Items)  
        {  
            table.Cell().Element(CellStyle).Text(Model.Items.IndexOf(item) + 1).FontFamily("simhei");  
            table.Cell().Element(CellStyle).Text(item.Name).FontFamily("simhei");  
            table.Cell().Element(CellStyle).AlignRight().Text($"{item.Price}$").FontFamily("simhei");  
            table.Cell().Element(CellStyle).AlignRight().Text(item.Quantity).FontFamily("simhei");  
            table.Cell().Element(CellStyle).AlignRight().Text($"{item.Price * item.Quantity}$").FontFamily("simhei");  

            static IContainer CellStyle(IContainer container)  
            {  
                return container.BorderBottom(1).BorderColor(Colors.Grey.Lighten2).PaddingVertical(5);  
            }  
        }  
    });  

}  

在这些准备工作做完了之后我们就可以生成Pdf文档了

var filePath = "invoice.pdf";  
  
var model = InvoiceDocumentDataSource.GetInvoiceDetails();  
var document = new InvoiceDocument(model);  
document.GeneratePdf(filePath);  

当然还有很多好玩的功能,先给大家讲个概念,让大家对这个东西有个印象,后面我会继续输出该库的相关功能。

- EOF -

推荐阅读  点击标题可跳转
.NET 性能优化-使用SourceGenerator-Logger记录日志.NET 6.0 + Blazor 开发一个访客管理系统SQL Server、MySQL主从搭建,EF Core读写分离代码实现


看完本文有收获?请转发分享给更多人

推荐关注「DotNet」,提升.Net技能 

点赞和在看就是最大的支持❤️

您可能也对以下帖子感兴趣

文章有问题?点此查看未经处理的缓存