動態產生pdf檔案--從itextsharp到pdfsharp

手邊的某個專案原來使用itextsharp繪製表格。但因為 itextsharp新版本AGPL的要求,為免後續爭議,必須轉換改用其他解決方案,例如Pdfsharp。但在繪製表格時,itextsharp與pdfsharp有明顯的不同,iTextSharp 有內建的 PdfPTable 類別,支援多樣化的表格操作,例如跨列、跨欄、邊框樣式、對齊、顏色、甚至可以在表格中插入圖片或其他元素。對於需要高度客製化的表格,iTextSharp 很強大。PDFsharp則沒有原生表格 API。你必須透過手動計算格線位置,使用 gfx.DrawRectanglegfx.DrawString 等來「畫出」一張表格。代表需要更多低階的控制與程式碼。

當然,使用 PDFsharp 時,可以完全控制繪圖行為,包括只畫出每一列的「底線」。只要在畫表格的時候略過左右與上方的線段,只保留底部的那條線即可。

不過,有個壞消息是,PDFsharp 本身不具備內建解析 HTML 的功能,也就是說,單靠 PDFsharp 無法自動將含有 HTML tag 的文本轉換成對應格式的 PDF 內容。開發人員必須仰賴第三方的擴充套件,例如HtmlRenderer.PdfSharp ,以實現這類功能,將 HTML(包含 CSS 樣式)渲染成 PDF 的內容。基本上,它的做法是通過先將 HTML 渲染到一個圖形物件,再將該圖形物件繪製到 PDF 中,這樣就能夠模擬出 HTML 內容的顯示效果。


另外,PDFsharp 是一個基於繪圖 API 的低階工具,它不提供像文檔排版引擎那樣的自動「流式佈局」功能,也就是說,它不會自動維護目前文檔中已經繪製到哪個位置的座標。這需要你自己來管理目前的「游標位置」(例如目前的 X 與 Y 值)。當你處理動態生成 PDF 並需要換頁時,通常會建立一個頁面佈局管理器,用來計算每個元素的擺放位置以及是否需要新增頁面。基本的邏輯如下:

  1. 設定頁面邊界與有效區域
    通常你會定義固定的頁面邊界(例如上、下、左、右邊距),有效區域就是頁面的實際可用繪製空間。
    舉例來說,若頁面高度為 800,而上下邊距分別都是 50,有效高度就是 800 - 50 - 50 = 700。
  2. 追蹤當前游標(Coordinate Cursor)
    你需要保存一個 currentY(以及橫向的 currentX,但表格一般固定左側起點),這代表目前內容繪製到了頁面上的哪個位置。 





以下是chatgpt提供的第一個範例。

using PdfSharp.Drawing;

using PdfSharp.Pdf;


public class ComplexTableGenerator

{

    public void GeneratePdf(string filePath)

    {

        PdfDocument document = new PdfDocument();

        document.Info.Title = "複雜表格範例";

        PdfPage page = document.AddPage();

        XGraphics gfx = XGraphics.FromPdfPage(page);

        XFont headerFont = new XFont("Arial", 12, XFontStyle.Bold);

        XFont cellFont = new XFont("Arial", 10, XFontStyle.Regular);


        // 表格設定

        int startX = 50;

        int startY = 100;

        int rowHeight = 30;

        int[] colWidths = { 80, 150, 100, 120 };


        string[] headers = { "編號", "名稱", "數量", "備註" };

        string[,] data = {

            { "A001", "紅色物件", "12", "特別需求" },

            { "A002", "藍色物件", "30", "標準品" },

            { "A003", "綠色物件", "5",  "需要測試" }

        };


        // 繪製表頭

        int x = startX;

        for (int i = 0; i < headers.Length; i++)

        {

            gfx.DrawRectangle(XPens.Black, x, startY, colWidths[i], rowHeight);

            gfx.DrawString(headers[i], headerFont, XBrushes.Black, 

                new XRect(x, startY, colWidths[i], rowHeight),

                XStringFormats.Center);

            x += colWidths[i];

        }


        // 繪製資料列

        for (int row = 0; row < data.GetLength(0); row++)

        {

            x = startX;

            int y = startY + rowHeight * (row + 1);


            for (int col = 0; col < data.GetLength(1); col++)

            {

                gfx.DrawRectangle(XPens.Black, x, y, colWidths[col], rowHeight);

                gfx.DrawString(data[row, col], cellFont, XBrushes.Black, 

                    new XRect(x + 5, y + 5, colWidths[col] - 10, rowHeight - 10),

                    XStringFormats.TopLeft);

                x += colWidths[col];

            }

        }


        document.Save(filePath);

    }

}


以下是範例程式碼的調整重點(只畫每列底線):

// 繪製資料列底線(不畫整個矩形)

for (int row = 0; row < data.GetLength(0); row++)

{

    int y = startY + rowHeight * (row + 1);

    int x = startX;

    

    for (int col = 0; col < data.GetLength(1); col++)

    {

        int colWidth = colWidths[col];


        // 畫儲存格文字

        gfx.DrawString(data[row, col], cellFont, XBrushes.Black,

            new XRect(x + 5, y - rowHeight + 5, colWidth - 10, rowHeight - 10),

            XStringFormats.TopLeft);


        // 畫底線

        gfx.DrawLine(XPens.Black, x, y, x + colWidth, y);

        x += colWidth;

    }

}

pdfsharp建立段落與排版的指令,還有繪製表格的指令摘要:


using PdfSharp.Drawing;

using PdfSharp.Pdf;


// 建立文件與頁面

PdfDocument document = new PdfDocument();

document.Info.Title = "PDFsharp 段落與表格示範";

PdfPage page = document.AddPage();

XGraphics gfx = XGraphics.FromPdfPage(page);

            //排版 

XFont paragraphFont = new XFont("Arial", 12, XFontStyle.Regular);
XRect textRect = new XRect(50, 50, 500, 100); // (左上角 x, y, 寬, 高)
gfx.DrawString("這是一段使用 PDFsharp 排版的文字,內容自動換行(如果文字超出寬度)。", 
               paragraphFont, XBrushes.Black, textRect, XStringFormats.TopLeft);

         //複雜排版

using PdfSharp.Drawing.Layout;

XTextFormatter tf = new XTextFormatter(gfx);
XFont formatterFont = new XFont("Times New Roman", 12, XFontStyle.Regular);
XRect formatterRect = new XRect(50, 150, 500, 200);
tf.DrawString("這是一段由 XTextFormatter 處理的文字,可有效處理內容換行與排版。", 
              formatterFont, XBrushes.Black, formatterRect, XStringFormats.TopLeft);

//繪製表格

int startX = 50;

int startY = 400;

int rowHeight = 30;

int[] colWidths = { 80, 150, 100, 120 };

string[] headers = { "編號", "名稱", "數量", "備註" };

 XFont headerFont = new XFont("Arial", 12, XFontStyle.Bold);

int x = startX;

for (int i = 0; i < headers.Length; i++)

{

    // 畫矩形框(可選擇只畫部分線條)

    gfx.DrawRectangle(XPens.Black, x, startY, colWidths[i], rowHeight);

    // 畫表頭文字,置中對齊

    gfx.DrawString(headers[i], headerFont, XBrushes.Black,

        new XRect(x, startY, colWidths[i], rowHeight),

        XStringFormats.Center);

    x += colWidths[i];

}


 

 

留言

這個網誌中的熱門文章

The Disk2vhd Disaster — and How StarWind’s V2V Brought Redemption! --About Windows Server 2012 STD P2V

如何刪除Trello的卡片資訊 (deleting Trello cards)

Redis 在 C#的應用