C#使用PDFium實(shí)現(xiàn)預(yù)覽PDF文件
本項(xiàng)目對(duì)PdfiumViewer庫(kù)進(jìn)行了改寫(xiě),對(duì)其pdf解析部分的核心功能進(jìn)行了分離和精簡(jiǎn),使其支持任意程序調(diào)用生成渲染后圖片,項(xiàng)目代碼已全部開(kāi)源 (https://github.com/LdotJdot/LumPdfiumViewerSlim)。
同時(shí)我們還給出了一個(gè)用Avalonia簡(jiǎn)單實(shí)現(xiàn)了渲染頁(yè)面的UI,改造后的庫(kù)是完全支持如Winform還有后端調(diào)用的。

下文將分享一下這個(gè)過(guò)程。
一、基于PDFium庫(kù)
需求來(lái)了,就得想辦法實(shí)現(xiàn)誒。要預(yù)覽PDF文件,需要先完成對(duì)PDF的解析,即將PDF中各類(lèi)數(shù)據(jù)提取出來(lái),然后再實(shí)現(xiàn)對(duì)解析數(shù)據(jù)的繪制渲染到軟件層面。在C#中關(guān)于PDF解析庫(kù)有很多,我們優(yōu)先考慮的主要為用C#對(duì)PDFium封裝庫(kù)。PDFium是基于由google維護(hù)的項(xiàng)目 (https://github.com/chromium/pdfium) (Apache-2.0 license),維護(hù)很頻繁(本文發(fā)出前2小時(shí)Pdfium倉(cāng)庫(kù)還在更新),此外他的特點(diǎn)是采用動(dòng)態(tài)鏈接庫(kù),C++編譯體積很小5mb,兼容性較好,接口規(guī)范而且功能強(qiáng)大。
二、C#現(xiàn)有Pdf預(yù)覽庫(kù)PdfiumViewer的不便
為了不造輪子,我們找到了 PdfiumViewer (https://github.com/bezzad/PdfiumViewer,Apache-2.0 license), 是一個(gè)基于Pdfium封裝后實(shí)現(xiàn)了預(yù)覽效果的C#庫(kù)。
盡管PDFium預(yù)覽效果很好,但最大的問(wèn)題是,PdfiumViewer中對(duì)渲染后頁(yè)面的創(chuàng)建深度依賴(lài)了WPF框架(true),而且是與解析渲染方法組合在一起的。由于很多內(nèi)部對(duì)象大量引用了WPF元素。因此在后端調(diào)用時(shí)或用于其他如Winform、Avalonia框架時(shí)非常不方便。如果想要單純集成至Winform或后端服務(wù)器調(diào)用那么還必須帶著WPF框架過(guò)去,非常不方便,只能自定義對(duì)PdfiumViewer進(jìn)行改造了。
三、Pdf預(yù)覽原理檢視及修改
Pdf的預(yù)覽主要是由解析+渲染兩部分實(shí)現(xiàn)。PdfiumViewer中,解析和底層渲染都是基于Pdfium庫(kù)的封裝調(diào)用實(shí)現(xiàn)的。如:,
[DllImport("pdfium.dll")]
public static extern IntPtr FPDFBitmap_CreateEx(int width, int height, int format, IntPtr first_scan, int stride);
[DllImport("pdfium.dll")]
public static extern void FPDFBitmap_FillRect(IntPtr bitmapHandle, int left, int top, int width, int height, uint color);
[DllImport("pdfium.dll")]
public static extern void FPDF_RenderPageBitmap(IntPtr bitmapHandle, IntPtr page, int start_x, int start_y, int size_x, int size_y, int rotate, FPDF flags);
在PdfiumView封裝的方法中,將額外考慮:
- 狀態(tài)檢查和DPI校正
- 位圖對(duì)象管理及內(nèi)存鎖定
- 原生PDF渲染 (Pdfium中渲染準(zhǔn)備)
- 背景設(shè)置和頁(yè)面設(shè)置
- 實(shí)際PDF渲染 (渲染到位圖)
PdfiumView封裝后的方法為:
public Image Render(int page, int width, int height, float dpiX, float dpiY, PdfRotation rotate, PdfRenderFlags flags)
基于這個(gè)方法僅需要傳入頁(yè)碼(從0開(kāi)始)、寬度、高度、dpi、旋轉(zhuǎn)等參數(shù),就可以得到PDF文件中指定頁(yè)的渲染后圖片了。
四、精簡(jiǎn)處理及Avalonia頁(yè)面實(shí)現(xiàn)
為了更加通用,我們對(duì)PdfiumViewer中依賴(lài)WPF的代碼進(jìn)行了刪減,主要包括書(shū)簽相關(guān)的對(duì)象,滾動(dòng)面板及動(dòng)態(tài)渲染等,最后留下的只有指定PDF頁(yè)渲染圖片的功能。在大部分場(chǎng)景中已經(jīng)夠用了,而且是支持AOT發(fā)布的,也可以作為一個(gè)獨(dú)立進(jìn)程工具供其他程序調(diào)用。
為了測(cè)試效果,我們選擇在Avalonia中創(chuàng)建一個(gè)可滾動(dòng)的頁(yè)面,創(chuàng)建一個(gè)虛擬模式的ItemsControl,基于Image對(duì)象用于顯示最終渲染的圖片頁(yè)IImage:
<ScrollViewer>
<ItemsControl x:Name="pageList"
IsTabStop="False"
Focusable="False"
IsHitTestVisible="True"
ItemsSource="{Binding RenderedPages.DisplayedData, Mode=OneWay}" Background="Transparent">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<VirtualizingStackPanel/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Image
Source="{Binding Image}"
Stretch="Uniform"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl >
</ScrollViewer>
綁定的ViewModel很簡(jiǎn)單,主要是為了頁(yè)面的延遲渲染,即滾動(dòng)到哪一頁(yè),就調(diào)用上述組件的方法,實(shí)時(shí)渲染當(dāng)前頁(yè)圖片。
public interface IPageRender
{
public int page { get; }
public IImage Image {get;}
}
public class PageRender
{
PdfDocument _pdfDocument;
public PageRender(PdfDocument _pdfDocument, int pageNumber)
{
this._pdfDocument= _pdfDocument;
this.page = pageNumber;
}
public IImage Image=>GetImage();
IImage GetImage()
{
try
{
using var image = _pdfDocument.Render(page, 800, 1200, 192, 192);
return ConvertToAvaloniaBitmap(image);
}
catch
{
return null;
}
}
private Avalonia.Media.Imaging.Bitmap ConvertToAvaloniaBitmap(System.Drawing.Image image)
{
using (var memoryStream = new MemoryStream())
{
image.Save(memoryStream,ImageFormat.Png);
memoryStream.Position = 0;
return new Avalonia.Media.Imaging.Bitmap(memoryStream);
}
}
public int page { get; }
}
public class PageViewModel: ReactiveObject,IDisposable
{
private PdfDocument _pdfDocument;
IEnumerable<PageRender> _displayedData=[];
public IEnumerable<PageRender> DisplayedData
{
get => _displayedData;
private set => this.RaiseAndSetIfChanged(ref _displayedData, value);
}
public void Load(string path)
{
_pdfDocument?.Dispose();
_pdfDocument = PdfDocument.Load(path);
Initialize(_pdfDocument);
}
private void Initialize(PdfDocument pdfDocument)
{
_pdfDocument = pdfDocument;
// 頁(yè)面范圍
DisplayedData = Enumerable.Range(0, pdfDocument.PageCount).Select(o=>new PageRender(_pdfDocument, o));
}
public void Dispose()
{
_pdfDocument?.Dispose();
}
}
實(shí)現(xiàn)效果如下:

五、最后
盡管AOT啟動(dòng)速度快,但我們還是更偏向與單文件的壓縮發(fā)布,因?yàn)锳OT發(fā)布后,加上非托管庫(kù),體積會(huì)比較大。而單文件壓縮將非托管后一起打包后大小僅不到26Mb,一個(gè)小巧的PDF閱讀器就誕生了,是不是很棒。
到此這篇關(guān)于C#使用PDFium實(shí)現(xiàn)預(yù)覽PDF文件的文章就介紹到這了,更多相關(guān)C#預(yù)覽PDF內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
C#調(diào)用DeepSeek?API的兩種實(shí)現(xiàn)方案
DeepSeek(深度求索)?最近可謂火爆的一塌糊涂,具體的介紹這里不再贅述,您可以各種搜索其信息,即使您不搜索,只要您拿起手機(jī),各種關(guān)于?DeepSeek?的新聞、資訊也會(huì)撲面而來(lái)的推送到您面前,本文給大家介紹了C#調(diào)用DeepSeek?API的兩種實(shí)現(xiàn)方案,需要的朋友可以參考下2025-02-02
C# 如何使用OpcUaHelper讀寫(xiě)OPC服務(wù)器
這篇文章給大家介紹C# 如何使用OpcUaHelper讀寫(xiě)OPC服務(wù)器,本文通過(guò)圖文實(shí)例代碼相結(jié)合給大家介紹的非常詳細(xì),需要的朋友參考下吧2023-12-12
C#實(shí)現(xiàn)給定字符串生成MD5哈希的方法
這篇文章主要介紹了C#實(shí)現(xiàn)給定字符串生成MD5哈希的方法,涉及C#操作字符串的相關(guān)技巧,需要的朋友可以參考下2015-06-06
解決unity rotate旋轉(zhuǎn)物體 限制物體旋轉(zhuǎn)角度的大坑
這篇文章主要介紹了解決unity rotate旋轉(zhuǎn)物體 限制物體旋轉(zhuǎn)角度的大坑,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2021-04-04
C#基于Miniblink控件編寫(xiě)一個(gè)簡(jiǎn)易的瀏覽器
miniblink是一款精簡(jiǎn)小巧的瀏覽器控件,基于chromium精簡(jiǎn)而成,是市面上最小巧的chromium內(nèi)核控件沒(méi)有之一,本文將結(jié)合C#和Miniblink編寫(xiě)一個(gè)簡(jiǎn)易的瀏覽器,感興趣的可以了解下2024-01-01
C#中通過(guò)Command模式實(shí)現(xiàn)Redo/Undo方案
這篇文章介紹了C#中通過(guò)Command模式實(shí)現(xiàn)Redo/Undo方案的方法,文中通過(guò)示例代碼介紹的非常詳細(xì)。對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2022-06-06

