// ============================================================ // PdfiumNative:pdfium.dll 原生库 P/Invoke 封装 // dll 来源:pypdfium2 win32 wheel(pypdfium2_raw/pdfium.dll,x86) // 调用约定:cdecl(pypdfium2 ctypes.CDLL 已验证) // ============================================================ using System; using System.Runtime.InteropServices; namespace pdfbt { /// pdfium C API 最小封装(仅本项目用到的函数)。 internal static class PdfiumNative { public const string DllName = "pdfium.dll"; // 渲染标志:渲染注释/批注 public const int FPDF_ANNOT = 0x01; // FPDF_ERR_* 错误码(GetLastError 返回) public const int FPDF_ERR_SUCCESS = 0; // 无错误 public const int FPDF_ERR_UNKNOWN = 1; // 未知错误 public const int FPDF_ERR_FILE = 2; // 文件无法打开或读取 public const int FPDF_ERR_FORMAT = 3; // 文件不是 PDF 或已损坏 public const int FPDF_ERR_PASSWORD = 4; // 需要密码或密码错误 public const int FPDF_ERR_SECURITY = 5; // 不支持的安全方案 public const int FPDF_ERR_PAGE = 6; // 页面无法加载 private static bool _inited; private static readonly object _initLock = new object(); /// 初始化 pdfium(进程内只需一次,线程安全)。 public static void InitLibrary() { lock (_initLock) { if (_inited) return; FPDF_InitLibrary(); _inited = true; } } [DllImport(DllName)] private static extern void FPDF_InitLibrary(); [DllImport(DllName)] private static extern void FPDF_DestroyLibrary(); [DllImport(DllName, CharSet = CharSet.Ansi)] private static extern IntPtr FPDF_LoadDocument(string file_path, string password); [DllImport(DllName)] private static extern void FPDF_CloseDocument(IntPtr document); [DllImport(DllName)] private static extern uint FPDF_GetLastError(); [DllImport(DllName)] private static extern int FPDF_GetPageCount(IntPtr document); [DllImport(DllName)] private static extern IntPtr FPDF_LoadPage(IntPtr document, int page_index); [DllImport(DllName)] private static extern void FPDF_ClosePage(IntPtr page); // ★ 新版 GetPageSizeByIndexF 是 FS_SIZEF* 参数(易错);用稳定的旧版 double API [DllImport(DllName)] private static extern int FPDF_GetPageSizeByIndex(IntPtr document, int page_index, out double width, out double height); [DllImport(DllName)] private static extern IntPtr FPDFBitmap_Create(int width, int height, int alpha); [DllImport(DllName)] private static extern void FPDFBitmap_FillRect(IntPtr bitmap, int left, int top, int width, int height, uint color); [DllImport(DllName)] private static extern void FPDFBitmap_Destroy(IntPtr bitmap); [DllImport(DllName)] private static extern IntPtr FPDFBitmap_GetBuffer(IntPtr bitmap); [DllImport(DllName)] private static extern int FPDFBitmap_GetStride(IntPtr bitmap); [DllImport(DllName)] private static extern void FPDF_RenderPageBitmap(IntPtr bitmap, IntPtr page, int start_x, int start_y, int size_x, int size_y, int rotate, int flags); [DllImport(DllName)] private static extern IntPtr FPDF_LoadMemDocument(byte[] data, int size, string password); public static IntPtr LoadDocumentFromBytes(byte[] data, string password) { InitLibrary(); if (data == null || data.Length == 0) return IntPtr.Zero; return FPDF_LoadMemDocument(data, data.Length, string.IsNullOrEmpty(password) ? null : password); } // ------------------------------------------------------------ // 上层直接使用的安全包装 // ------------------------------------------------------------ /// 打开 PDF 文件。失败返回 IntPtr.Zero,可用 LastErrorText 查原因。 public static IntPtr LoadDocument(string path, string password) { InitLibrary(); if (string.IsNullOrEmpty(path)) return IntPtr.Zero; return FPDF_LoadDocument(path, string.IsNullOrEmpty(password) ? null : password); } public static void CloseDocument(IntPtr doc) { if (doc != IntPtr.Zero) FPDF_CloseDocument(doc); } public static int GetPageCount(IntPtr doc) { if (doc == IntPtr.Zero) return 0; return FPDF_GetPageCount(doc); } /// 取指定页尺寸(PDF 点,1/72 英寸)。失败返回 false。 public static bool GetPageSize(IntPtr doc, int pageIndex, out float widthPt, out float heightPt) { widthPt = 0; heightPt = 0; if (doc == IntPtr.Zero) return false; double w, h; bool ok = FPDF_GetPageSizeByIndex(doc, pageIndex, out w, out h) != 0; widthPt = (float)w; heightPt = (float)h; return ok; } public static IntPtr LoadPage(IntPtr doc, int pageIndex) { if (doc == IntPtr.Zero) return IntPtr.Zero; return FPDF_LoadPage(doc, pageIndex); } public static void ClosePage(IntPtr page) { if (page != IntPtr.Zero) FPDF_ClosePage(page); } /// 把整页渲染到 GDI+ 位图(白底不透明)。 public static System.Drawing.Bitmap RenderPageToBitmap(IntPtr page, int widthPx, int heightPx) { return RenderRegionToBitmap(page, 0, 0, widthPx, heightPx, widthPx, heightPx); } /// /// 把页面局部区域渲染到位图(白底不透明)。 /// 页面整图尺寸 = fullW×fullH 像素;只取 (startX,startY,wPx,hPx) 矩形。 /// 用于:OCR 框选区域按任意分辨率高清重渲染(与显示缩放无关)。 /// public static System.Drawing.Bitmap RenderRegionToBitmap(IntPtr page, int startX, int startY, int wPx, int hPx, int fullW, int fullH) { if (page == IntPtr.Zero || wPx <= 0 || hPx <= 0) return null; if (wPx > 16384 || hPx > 16384) return null; IntPtr bmp = FPDFBitmap_Create(wPx, hPx, 1); if (bmp == IntPtr.Zero) return null; try { // 白底不透明(0xAARRGGBB) FPDFBitmap_FillRect(bmp, 0, 0, wPx, hPx, 0xFFFFFFFF); // start_x/start_y 为负偏移:位图原点相对页面原点 FPDF_RenderPageBitmap(bmp, page, -startX, -startY, fullW, fullH, 0, FPDF_ANNOT); IntPtr buffer = FPDFBitmap_GetBuffer(bmp); int stride = FPDFBitmap_GetStride(bmp); if (buffer == IntPtr.Zero || stride <= 0) return null; // pdfium BGRA(top-down) → GDI+ Format32bppArgb(BGRA) 逐行拷贝 var result = new System.Drawing.Bitmap(wPx, hPx, System.Drawing.Imaging.PixelFormat.Format32bppArgb); var rect = new System.Drawing.Rectangle(0, 0, wPx, hPx); var data = result.LockBits(rect, System.Drawing.Imaging.ImageLockMode.WriteOnly, System.Drawing.Imaging.PixelFormat.Format32bppArgb); try { for (int y = 0; y < hPx; y++) { IntPtr src = new IntPtr(buffer.ToInt64() + (long)y * stride); IntPtr dst = new IntPtr(data.Scan0.ToInt64() + (long)y * data.Stride); NativeMethods.CopyMemory(dst, src, (uint)(wPx * 4)); } } finally { result.UnlockBits(data); } return result; } finally { FPDFBitmap_Destroy(bmp); } } public static string LastErrorText() { uint err = FPDF_GetLastError(); switch (err) { case FPDF_ERR_SUCCESS: return "成功"; case FPDF_ERR_FILE: return "文件无法打开或读取"; case FPDF_ERR_FORMAT: return "文件不是有效 PDF 或已损坏"; case FPDF_ERR_PASSWORD: return "PDF 已加密,需要密码"; case FPDF_ERR_SECURITY: return "不支持的安全方案"; case FPDF_ERR_PAGE: return "页面内容无法解析"; default: return "未知错误(" + err + ")"; } } private static class NativeMethods { [DllImport("kernel32.dll", EntryPoint = "RtlMoveMemory", SetLastError = false)] public static extern void CopyMemory(IntPtr dest, IntPtr src, uint count); } } }