// ============================================================
// 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);
}
}
}