突發奇想閃過念頭,想把 PDF 投影片逐頁轉成圖檔,索性當成 .NET 練習題伸展暖身,說做就做。

.NET PDF 元件選擇不少,但好用的多是商用軟體,開源專案如 iTextSharpQuestPDF 新版也改成商業授權,而轉存圖檔不同於編譯修改文件,訴求重點不同,順便趁機認識不同的程式庫。

這回我選擇的是 Google 的開源 PDF 引擎 - PDFium (Google 與 Foxit 共同開源,採 BSD 3-Clause 授權參考),其最大賣點為 Chrome 就是靠它顯示 PDF 文件! 沒人敢懷疑 Chrome/Edge 顯示 PDF 的效能或正確性(PDF 文件沒法用 Chrome 看,大家會覺得 PDF 文件有問題 XD),用 PDFium 完全不用擔心跑不動大檔或排版錯亂,若不幸遇上,請儘速通知 PDF 文件作者自我檢討 😛

PDFium 是原生程式庫,直接用 .NET 呼叫太累,一般會借助第三方 .NET Wrapper 程式庫,生活比較輕鬆愉快。我找到兩個較多人推的 PDFium Wrapper:

  • PDFiumSharp
    版本偏舊,適合 .NET Framework/Windows 平台,已六年未更新
  • PDFiumCore
    仍持續更新,可支援 .NET 6 及跨 Linux 平台

想用在 .NET 6+,選 PDFiumCore 就對了。

PDFium 是個渲染引擎,核心功能是將 PDF 文件繪製成畫面,轉存圖檔正是它的拿手好戲,PDFiumCore 專案也有現成的頁面轉存圖檔範例,轉檔過程有點小複雜,需動用到矩陣運算:

不過,我只是單純 PDF 頁面轉圖檔,程式不用怎麼改,拿香跟著拜就對了。程式範例如下:

using System.Drawing;
using System.Drawing.Imaging;
using PDFiumCore;

var scale = 1;
fpdfview.FPDF_InitLibrary();

var path = "blogger-story.pdf";
ExportImages(path, scale);

static void ExportImages(string pdfPath, float scale)
{

    var imgFolder = Path.Combine("images", Path.GetFileNameWithoutExtension(pdfPath));
    Directory.CreateDirectory(imgFolder);
    // 載入PDF
    var document = fpdfview.FPDF_LoadDocument(pdfPath, null);
    // 取得總頁數
    var pageCount = fpdfview.FPDF_GetPageCount(document);
    for (var pageIndex = 0; pageIndex < pageCount; pageIndex++)
    {
        // 逐頁載入
        var page = fpdfview.FPDF_LoadPage(document, pageIndex);
        
        // 取得尺寸,依比例縮放
        var size = new FS_SIZEF_();
        fpdfview.FPDF_GetPageSizeByIndexF(document, 0, size);

        var width = (int)Math.Round(size.Width * scale);
        var height = (int)Math.Round(size.Height * scale);

        var bitmap = fpdfview.FPDFBitmapCreateEx(
            width,
            height,
            (int)FPDFBitmapFormat.BGRA,
            IntPtr.Zero,
            0);
        
        // 填入白色背景
        fpdfview.FPDFBitmapFillRect(bitmap, 0, 0, width, height, (uint)Color.White.ToArgb());
        // |          | a b 0 |
        // | matrix = | c d 0 |
        // |          | e f 1 |
        using var matrix = new FS_MATRIX_();
        using var clipping = new FS_RECTF_();

        matrix.A = scale;
        matrix.B = 0;
        matrix.C = 0;
        matrix.D = scale;
        matrix.E = 0;
        matrix.F = 0;

        clipping.Left = 0;
        clipping.Right = width;
        clipping.Bottom = 0;
        clipping.Top = height;

        fpdfview.FPDF_RenderPageBitmapWithMatrix(bitmap, page, matrix, clipping, (int)RenderFlags.RenderAnnotations);
        
        //REF: https://stackoverflow.com/a/67744257/288936
        var bitmapImage = new Bitmap(
            width,
            height,
            fpdfview.FPDFBitmapGetStride(bitmap),
            PixelFormat.Format32bppArgb,
            fpdfview.FPDFBitmapGetBuffer(bitmap));

        bitmapImage.Save(
            Path.Combine(imgFolder, $"{pageIndex:000}.png"),
            ImageFormat.Png
        );
    }
}

簡單拼裝一下。插電,開機,輕鬆秒殺~

Example of how to export PDF to images with PDFiumCore library.


Comments

Be the first to post a comment

Post a comment