161 lines
6.3 KiB
C#
161 lines
6.3 KiB
C#
// ============================================================
|
|
// PdfDocument:PDF 文档封装(加载/页尺寸/按缩放渲染页位图)
|
|
// 坐标系:页面坐标 = PDF 点(1/72 英寸),原点左上(pdfium 渲染后
|
|
// 已翻转为屏幕方向,y 向下)。
|
|
// ============================================================
|
|
using System;
|
|
using System.Drawing;
|
|
using System.IO;
|
|
|
|
namespace 成套报价软件
|
|
{
|
|
/// <summary>已打开的 PDF 文档。非线程安全,只在 UI 线程使用。</summary>
|
|
internal sealed class PdfDocument : IDisposable
|
|
{
|
|
private IntPtr _doc = IntPtr.Zero;
|
|
private IntPtr[] _pageHandles; // 惰性加载的页句柄缓存
|
|
// ★ FPDF_LoadMemDocument 要求后备缓冲在文档关闭前保持有效且不可移动——
|
|
// 原来读入的 byte[] 是局部变量,打开后就没人引用;打开下一个 PDF 的大分配
|
|
// 触发 GC 把它搬走/回收,文档内存变垃圾 → 第二个 PDF 打不开(实测用户报障)。
|
|
// 钉住(GCHandleType.Pinned)+字段保引用,Close 时才释放。
|
|
private byte[] _bytes;
|
|
private System.Runtime.InteropServices.GCHandle _pin;
|
|
|
|
public string FilePath { get; private set; }
|
|
public int PageCount { get; private set; }
|
|
public float[] PageWidthPt { get; private set; }
|
|
public float[] PageHeightPt { get; private set; }
|
|
|
|
public bool IsOpen
|
|
{
|
|
get { return _doc != IntPtr.Zero; }
|
|
}
|
|
|
|
/// <summary>打开 PDF(读入内存后走 FPDF_LoadMemDocument,免疫路径问题)。失败抛异常。</summary>
|
|
public void Open(string path, string password)
|
|
{
|
|
Close();
|
|
if (!File.Exists(path))
|
|
throw new Exception("文件不存在: " + path);
|
|
_bytes = File.ReadAllBytes(path);
|
|
_pin = System.Runtime.InteropServices.GCHandle.Alloc(_bytes,
|
|
System.Runtime.InteropServices.GCHandleType.Pinned);
|
|
IntPtr doc = PdfiumNative.LoadDocumentFromBytes(_bytes, password);
|
|
if (doc == IntPtr.Zero)
|
|
{
|
|
FreeBuffer();
|
|
throw new Exception("PDF 打开失败:" + PdfiumNative.LastErrorText());
|
|
}
|
|
|
|
int count = PdfiumNative.GetPageCount(doc);
|
|
if (count <= 0)
|
|
{
|
|
PdfiumNative.CloseDocument(doc);
|
|
FreeBuffer();
|
|
throw new Exception("PDF 没有任何页面。");
|
|
}
|
|
|
|
_doc = doc;
|
|
FilePath = path;
|
|
PageCount = count;
|
|
_pageHandles = new IntPtr[count];
|
|
PageWidthPt = new float[count];
|
|
PageHeightPt = new float[count];
|
|
for (int i = 0; i < count; i++)
|
|
{
|
|
float w, h;
|
|
if (!PdfiumNative.GetPageSize(doc, i, out w, out h))
|
|
{
|
|
w = 595f; h = 842f; // A4 兜底
|
|
}
|
|
PageWidthPt[i] = w;
|
|
PageHeightPt[i] = h;
|
|
}
|
|
}
|
|
|
|
/// <summary>释放文档后备缓冲(钉住句柄+字节数组)。</summary>
|
|
private void FreeBuffer()
|
|
{
|
|
if (_pin.IsAllocated) _pin.Free();
|
|
_bytes = null;
|
|
}
|
|
|
|
public void Close()
|
|
{
|
|
if (_pageHandles != null)
|
|
{
|
|
for (int i = 0; i < _pageHandles.Length; i++)
|
|
PdfiumNative.ClosePage(_pageHandles[i]);
|
|
_pageHandles = null;
|
|
}
|
|
if (_doc != IntPtr.Zero)
|
|
{
|
|
PdfiumNative.CloseDocument(_doc);
|
|
_doc = IntPtr.Zero;
|
|
}
|
|
FreeBuffer();
|
|
FilePath = null;
|
|
PageCount = 0;
|
|
PageWidthPt = null;
|
|
PageHeightPt = null;
|
|
}
|
|
|
|
/// <summary>渲染指定页。scale = 像素/PDF点。失败返回 null。</summary>
|
|
public Bitmap RenderPage(int pageIndex, float scale)
|
|
{
|
|
if (!IsOpen) return null;
|
|
if (pageIndex < 0 || pageIndex >= PageCount) return null;
|
|
IntPtr page = GetPage(pageIndex);
|
|
if (page == IntPtr.Zero) return null;
|
|
|
|
int w = Math.Max(1, (int)Math.Round(PageWidthPt[pageIndex] * scale));
|
|
int h = Math.Max(1, (int)Math.Round(PageHeightPt[pageIndex] * scale));
|
|
return PdfiumNative.RenderPageToBitmap(page, w, h);
|
|
}
|
|
|
|
/// <summary>
|
|
/// 高清渲染页面局部区域(OCR 用):与屏幕显示缩放无关,
|
|
/// 用户任意缩放下框选,OCR 都拿到按 ocrScale 渲染的高清裁剪。
|
|
/// 返回位图与 rectPt 一一对应(像素/pt = ocrScale)。
|
|
/// </summary>
|
|
public Bitmap RenderRegion(int pageIndex, RectangleF rectPt, float ocrScale)
|
|
{
|
|
if (!IsOpen) return null;
|
|
if (pageIndex < 0 || pageIndex >= PageCount) return null;
|
|
IntPtr page = GetPage(pageIndex);
|
|
if (page == IntPtr.Zero) return null;
|
|
|
|
// 整页渲染尺寸(控制缩放)
|
|
int fullW = Math.Max(1, (int)Math.Round(PageWidthPt[pageIndex] * ocrScale));
|
|
int fullH = Math.Max(1, (int)Math.Round(PageHeightPt[pageIndex] * ocrScale));
|
|
// 区域矩形(裁到页面范围)
|
|
float pw = PageWidthPt[pageIndex], ph = PageHeightPt[pageIndex];
|
|
float l = Math.Max(0, rectPt.X), t = Math.Max(0, rectPt.Y);
|
|
float r = Math.Min(pw, rectPt.Right), b = Math.Min(ph, rectPt.Bottom);
|
|
if (r - l < 1 || b - t < 1) return null;
|
|
int startX = (int)Math.Floor(l * ocrScale);
|
|
int startY = (int)Math.Floor(t * ocrScale);
|
|
int endX = (int)Math.Ceiling(r * ocrScale);
|
|
int endY = (int)Math.Ceiling(b * ocrScale);
|
|
return PdfiumNative.RenderRegionToBitmap(page, startX, startY,
|
|
endX - startX, endY - startY, fullW, fullH);
|
|
}
|
|
|
|
private IntPtr GetPage(int pageIndex)
|
|
{
|
|
IntPtr page = _pageHandles[pageIndex];
|
|
if (page == IntPtr.Zero)
|
|
{
|
|
page = PdfiumNative.LoadPage(_doc, pageIndex);
|
|
_pageHandles[pageIndex] = page;
|
|
}
|
|
return page;
|
|
}
|
|
|
|
public void Dispose()
|
|
{
|
|
Close();
|
|
}
|
|
}
|
|
}
|