C#/.Net 不要再使用Aspose和iTextSharp啦!QuestPDF操作產生PDF更快更高效!

C#/.Net 不要再使用Aspose和iTextSharp啦!QuestPDF操作產生PDF更快更高效!

它提供了一個佈局引擎,設計時考慮到了完整的分頁支援以及靈活性要求!比市面上常見的Aspose和iTextSharp好用太多了!

最後更新 2022/4/23 下午1:55
黑哥聊dotNet
預計閱讀 7 分鐘
分類
.NET
標籤
.NET C# PDF 產生PDF Aspose

1. 關於 QuestPDF

QuestPDF 是一個開源的工具庫,可以在 .NET 或 .NET Core 中產生 PDF 文件

它提供了一個佈局引擎,設計時考慮到了完整的分頁支援以及靈活性要求!比市面上常見的 Aspose 和 iTextSharp 好用太多了!

2. 安裝

Install-Package QuestPDF

3. 範例

3.1 簡單範例

產生 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(200, 100));
            });

        page.Footer()
            .AlignCenter()
            .Text(x =>
            {
                x.Span("Page ");
                x.CurrentPageNumber();
            });
    });
})
.GeneratePdf("hello.pdf");

上面程式碼產生的 PDF 結果:

3.2 範本產生

使用範本產生一共涉及三個應用層的工作:

  1. 文件 Model(一組描述 PDF 文件內容的類別)
  2. 資料源(將領域實體對應到文件模型的層級)
  3. 範本(描述如何視覺化資訊並將其轉換為 PDF 檔案的表示層)

比如我們設計一個基本的發票資訊,要設計一個購物清單、一個賣家買家的地址以及發票編號等等,我們設計了以下 3 個 Model 類別:

public class InvoiceModel
{
    public int InvoiceNumber { get; set; }
    public DateTime IssueDate { get; set; }
    public DateTime DueDate { get; set; }

    public Address SellerAddress { get; set; }
    public Address CustomerAddress { get; set; }

    public List<OrderItem> Items { get; set; }
    public string Comments { get; set; }
}

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

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

Model 定義好之後我們就定義一些假資料來填充 PDF:

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

    public static InvoiceModel GetInvoiceDetails()
    {
        var items = Enumerable
            .Range(1, 8)
            .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() * 100, 2),
            Quantity = Random.Next(1, 10)
        };
    }

    private static Address GenerateRandomAddress()
    {
        return new Address
        {
            CompanyName = "測試商店",
            Street = "測試街道",
            City = "測試城市",
            State = "測試狀態",
            Email = "測試郵件",
            Phone = "測試電話"
        };
    }
}

然後搭建我們的範本腳手架。我們要使用範本腳手架,就要從定義一個實作 IDocument 介面的新類別開始。

該介面包含兩個方法:

  1. DocumentMetadata GetMetadata();
  2. 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 的頁面總是包含三個元素:頁首、頁尾、內容。查看一下我們產生的文件:

到目前為止,我們已經搭建了一個非常簡單的頁面,其中每個部分都有不同的顏色或大小。

接下來我們來填充它的頁首。把資料源整理好之後,就可以呼叫 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);

4. 總結

當然還有很多好玩的功能,今天就給大家講個概念,讓大家對這個東西有個印象,後面我會繼續輸出該庫的相關功能。如果你們對該庫感興趣,可以持續關注我!微信公眾號【黑哥聊 dotNet】

繼續探索

延伸閱讀

更多文章
同分類 / 同標籤 2022/7/16

C#將PDF檔案轉換為圖片

今日一位同事跟我說,你取得的PDF檔案有點不符合我們現有軟體流程,你能不能將我們的PDF檔案轉換成圖片啊!

繼續閱讀