[ASP.net MVC] 將HTML轉成PDF檔案,使用iTextSharp套件的XMLWorkerHelper (附上解決顯示中文問題)

[ASP.net MVC] 將HTML轉成PDF檔案,使用iTextSharp套件的XMLWorkerHelper (附上解決顯示中文問題)

前言

最近專案又遇到需要製作PDF文件的需求,這次還要額外控制PDF檔案的其他功能,我的直覺馬上聯想到使用iTextSharp來實現

一般使用iTextSharp套件要塞資料到PDF檔案裡的話,大概要先把Phrase、PdfPTable、PdfPCell……等等幾個物件叫出來,然後加入至Document物件

※請參考其他網友文章:使用ASP .NET (C#) 產生PDF檔的好幫手—iTextSharp library (上)使用ASP .NET (C#) 產生PDF檔的好幫手—iTextSharp library (下)

但由於專案要求有 [網頁預覽+下載PDF(把預覽的網頁畫面轉出到PDF檔)功能]

因為已經先有Html網頁畫面了,我比較懶,所以在網路上尋求有沒有把Html網頁轉PDF檔的iTextSharp實現辦法

雖然過程中有考量用:[ASP.net MVC] 在Web專案上使用Pechkin套件將網頁轉成PDF檔

但Pechkin沒辦法進一步控制PDF檔案的詳細功能,它只能僅僅印出來而已

所以整個需求大致是:利用iTextSharp套件將網頁檔(或HTML文字)轉成PDF檔

 

而iTextSharp一般在網路上比較常見,都是使用內建的HtmlWorker物件來實現

※HtmlWorker已有中文字顯示問題的解決辦法,請參考其他網友文章:使用iTextSharp 5將html檔轉成PDF檔

但本文使用的是XMLWorkerHelper物件來Parse網頁Html文字

因為比起HtmlWorker物件,XMLWorkerHelper類別支援更多CSS和Html標籤(例如<hr/>),但缺點預設不支援中文字

不過中文字顯示問題,已經在下文的Sample Code中解決了

 

實作

先為Web專案透過NuGet加入以下參考

image

image

以下是要輸出成PDF檔案的範例靜態網頁內容,還沒套程式,因為套程式塞資料到畫面不是本文的重點

此網頁Html該注意的重點都在說明註解裡


 
      <!--設定width為100%以剛好符合PDF文件的寬度-->
      <!--如果width為固定寬度的話,C#那邊可能要改寫成Document doc = new Document(PageSize.A3)-->
      <!--此table已調整好高度剛好符合PDF A4一頁的高度了-->
      <table style='width:100%;'  >
        <tr >
            <td colspan='2' style='color:#808080;'>
               <h2>[廠商名稱]</h2>
            </td>
        </tr>
        <tr style='font-size:13px;'>
         <td style='width:600px;color:#808080;font-size:16px;'>Taiwan Limited<br />4F,144 Changchun Rd., Taipei 104, Taiwan</td>
         <td style='width:80%;color:#808080;font-size:16px;text-align:right;'>[頁碼]/[總頁數]</td>
         </tr>
       
        <tr>
            <td  colspan='2'  style='text-align:center; height:90px;'><h1>XXX借用函證書</h1></td>
        </tr>
        <tr>
            <td  colspan='2'  style='text-align:right;font-size:20px;'> 日期:民國[函證民國日期] </td>
        </tr>
        <tr>
            <td  colspan='2'  style='height:10px;font-size:20px;' > 敬啟者:[經銷商全銜] </td>
        </tr>
        <tr>
            <td  colspan='2'  style='height:30px;font-size:20px;'  > XXX 借用人:[員工姓名] </td>
        </tr>
         <tr>
            <td  colspan='2'  style='height:50px;line-height:27px;font-size:20px;vertical-align:bottom;' > [廠商名稱]將於&nbsp;[存貨盤點民國日期]&nbsp;進行例行性存貨盤點,懇請協助清點敝公司借予&nbsp;&nbsp;貴公司之存貨品名及數量是否與下列明細相符,並蓋章回傳以茲證明。 </td>
        </tr>
        <tr>
            <td  colspan='2'  style='height:40px;font-size:20px;' > 敬祝&nbsp;&nbsp;商祺 </td>
        </tr>
        <tr>
            <td  colspan='2'  style='height:30px;line-height:20px;font-size:20px;' > 此致&nbsp;&nbsp;[廠商名稱] </td>
        </tr>
        <tr>
            <td  colspan='2'  style='font-size:20px;line-height:20px;'  > 以下為截至 民國[函證民國日期] 為止,貴公司借用之存貨明細:  </td>
        </tr>
        <tr>
            <td  colspan='2'  style='height:280px;vertical-align:top;font-size:20px;'  > 
                <table  style='border:1px solid #000000; width:100%;' cellpadding='0' cellspacing='0'>
                    <tr>
                        <td style='border:1px solid #000000;text-align:center;font-size:20px;'>No</td>
                        <td style='border:1px solid #000000;text-align:center;font-size:20px;'>品號</td>
                        <td style='border:1px solid #000000;text-align:center;font-size:20px;'>品名</td>
                        <td style='border:1px solid #000000;text-align:center;font-size:20px;'>數量</td>
                    </tr>
                </table>

            </td>
        </tr>
        <tr>
            <td  colspan='2'  style='font-size:20px;' > 確認無誤,特此證明。  </td>
        </tr>
        <tr>
            <td  colspan='2'  style='height:200px; '      > 
                <div style='float:right;'>
                <table style='border:1px solid #000000; ' cellpadding='0' cellspacing='0'>
                   <tr>
                       <td style='border:1px solid #000000;width:180px;height:180px;text-align:right; vertical-align:bottom;padding-bottom:10px;font-size:16px;'>經銷商公司章</td>
                       <td style='border:1px solid #000000;width:180px;height:180px;text-align:right;vertical-align:bottom;padding-bottom:10px;font-size:16px;'>借用人簽章</td>
                   </tr>
               </table>
                </div>
            </td>
        </tr>
        <tr>
            <td  colspan='2'  style='text-align:right;font-size:20px;'  > 民國[函證民國日期] </td>
        </tr>
        <tr style='font-size:13px;height:50px;'>
            <td  colspan='2'  style='color:#808080;font-size:16px; vertical-align:bottom;'   > 經銷商代號: 出借者工號: </td>
        </tr>
    </table>
    

該Html靜態網頁的執行內容

image

※補充說明,以上最外層的table容器寬度如果是固定寬度而非100%寬,Document物件又設為PageSize.A4的話,輸出成PDF很可能會變這樣↓

image

↑這種情況雖然調參數Document doc=new Document(PageSize.A3);即可解決,但比較通用的辦法還是把最外層的容器寬度設為100%以符合PDF各種A4 、A3大小的寬度

 

接下來在專案中加入一個自訂類別UnicodeFontFactory.cs,待會輸出PDF時要使用此類別來解決XMLWorkerHelper不支援中文字問題

image

UnicodeFontFactory.cs代碼完整內容


using iTextSharp.text;
using iTextSharp.text.pdf;
using iTextSharp.tool.xml;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Web;

 
    
    public class UnicodeFontFactory : FontFactoryImp 
    {
         
        private static readonly string arialFontPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.Fonts),
            "arialuni.ttf");//arial unicode MS是完整的unicode字型。
        private static readonly string 標楷體Path = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.Fonts),
          "KAIU.TTF");//標楷體
        

        public override Font GetFont(string fontname, string encoding, bool embedded, float size, int style, BaseColor color,
            bool cached)
        {
            //可用Arial或標楷體,自己選一個
            BaseFont baseFont = BaseFont.CreateFont(標楷體Path, BaseFont.IDENTITY_H, BaseFont.EMBEDDED);
            return new Font(baseFont, size, style, color);
        }
     

    }
 

而在HomeController裡,Html轉PDF的完整範例代碼


using iTextSharp.text;
using iTextSharp.text.pdf;
using iTextSharp.tool.xml;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net;
using System.Text;
using System.Web;
using System.Web.Mvc;
 

namespace MvcApplicationPDF.Controllers
{
    public class HomeController : Controller
    {
         
        /// <summary>
        /// 執行此Url,下載PDF檔案
        /// </summary>
        /// <returns></returns>
        public ActionResult DownloadPdf()
        {
            WebClient wc = new WebClient();
            //從網址下載Html字串
            string htmlText = wc.DownloadString("http://localhost:3056/Preview.html");
            byte[] pdfFile = this.ConvertHtmlTextToPDF(htmlText);

            return File(pdfFile, "application/pdf", "範例PDF檔.pdf");
        }
        /// <summary>
        /// 將Html文字 輸出到PDF檔裡
        /// </summary>
        /// <param name="htmlText"></param>
        /// <returns></returns>
        public  byte[] ConvertHtmlTextToPDF(string htmlText)
        {
            if (string.IsNullOrEmpty(htmlText))
            {
                return null;
            }
            //避免當htmlText無任何html tag標籤的純文字時,轉PDF時會掛掉,所以一律加上<p>標籤
            htmlText = "<p>" + htmlText + "</p>";

            MemoryStream outputStream = new MemoryStream();//要把PDF寫到哪個串流
            byte[] data = Encoding.UTF8.GetBytes(htmlText);//字串轉成byte[]
            MemoryStream msInput = new MemoryStream(data);
            Document doc = new Document();//要寫PDF的文件,建構子沒填的話預設直式A4
            PdfWriter writer = PdfWriter.GetInstance(doc, outputStream);
            //指定文件預設開檔時的縮放為100%
            PdfDestination pdfDest = new PdfDestination(PdfDestination.XYZ, 0, doc.PageSize.Height, 1f);
            //開啟Document文件 
            doc.Open();
            //使用XMLWorkerHelper把Html parse到PDF檔裡
            XMLWorkerHelper.GetInstance().ParseXHtml(writer, doc, msInput, null, Encoding.UTF8, new UnicodeFontFactory());
            //將pdfDest設定的資料寫到PDF檔
            PdfAction action = PdfAction.GotoLocalPage(1, pdfDest, writer);
            writer.SetOpenAction(action);
            doc.Close();
            msInput.Close();
            outputStream.Close();
            //回傳PDF檔案 
            return outputStream.ToArray();

        }

    }
}

執行結果:

image

 

 

結語

將Html轉成PDF,使用Pechkin或iTextSharp各有優缺點

畫面呈現當然是Pechkin比較忠於原始網頁(因為採用Webkit引擎),如果沒要進一步控制PDF檔案功能的話,可使用Pechkin

如果要進一步控制PDF功能而且畫面只是單純的白紙黑字,就可以改用iTextSharp套件

 

 

參考文章

Display Unicode characters in converting Html to Pdf

How to set a default zoom percent to “100%” using iTextSharp 4.0.2?