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

下文將分享一下這個過程。
一、基于PDFium庫
需求來了,就得想辦法實現(xiàn)誒。要預(yù)覽PDF文件,需要先完成對PDF的解析,即將PDF中各類數(shù)據(jù)提取出來,然后再實現(xiàn)對解析數(shù)據(jù)的繪制渲染到軟件層面。在C#中關(guān)于PDF解析庫有很多,我們優(yōu)先考慮的主要為用C#對PDFium封裝庫。PDFium是基于由google維護的項目 (https://github.com/chromium/pdfium) (Apache-2.0 license),維護很頻繁(本文發(fā)出前2小時Pdfium倉庫還在更新),此外他的特點是采用動態(tài)鏈接庫,C++編譯體積很小5mb,兼容性較好,接口規(guī)范而且功能強大。
二、C#現(xiàn)有Pdf預(yù)覽庫PdfiumViewer的不便
為了不造輪子,我們找到了 PdfiumViewer (https://github.com/bezzad/PdfiumViewer,Apache-2.0 license), 是一個基于Pdfium封裝后實現(xiàn)了預(yù)覽效果的C#庫。
盡管PDFium預(yù)覽效果很好,但最大的問題是,PdfiumViewer中對渲染后頁面的創(chuàng)建深度依賴了WPF框架(true),而且是與解析渲染方法組合在一起的。由于很多內(nèi)部對象大量引用了WPF元素。因此在后端調(diào)用時或用于其他如Winform、Avalonia框架時非常不方便。如果想要單純集成至Winform或后端服務(wù)器調(diào)用那么還必須帶著WPF框架過去,非常不方便,只能自定義對PdfiumViewer進行改造了。
三、Pdf預(yù)覽原理檢視及修改
Pdf的預(yù)覽主要是由解析+渲染兩部分實現(xiàn)。PdfiumViewer中,解析和底層渲染都是基于Pdfium庫的封裝調(diào)用實現(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校正
- 位圖對象管理及內(nèi)存鎖定
- 原生PDF渲染 (Pdfium中渲染準(zhǔn)備)
- 背景設(shè)置和頁面設(shè)置
- 實際PDF渲染 (渲染到位圖)
PdfiumView封裝后的方法為:
public Image Render(int page, int width, int height, float dpiX, float dpiY, PdfRotation rotate, PdfRenderFlags flags)
基于這個方法僅需要傳入頁碼(從0開始)、寬度、高度、dpi、旋轉(zhuǎn)等參數(shù),就可以得到PDF文件中指定頁的渲染后圖片了。
四、精簡處理及Avalonia頁面實現(xiàn)
為了更加通用,我們對PdfiumViewer中依賴WPF的代碼進行了刪減,主要包括書簽相關(guān)的對象,滾動面板及動態(tài)渲染等,最后留下的只有指定PDF頁渲染圖片的功能。在大部分場景中已經(jīng)夠用了,而且是支持AOT發(fā)布的,也可以作為一個獨立進程工具供其他程序調(diào)用。
為了測試效果,我們選擇在Avalonia中創(chuàng)建一個可滾動的頁面,創(chuàng)建一個虛擬模式的ItemsControl,基于Image對象用于顯示最終渲染的圖片頁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很簡單,主要是為了頁面的延遲渲染,即滾動到哪一頁,就調(diào)用上述組件的方法,實時渲染當(dāng)前頁圖片。
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;
// 頁面范圍
DisplayedData = Enumerable.Range(0, pdfDocument.PageCount).Select(o=>new PageRender(_pdfDocument, o));
}
public void Dispose()
{
_pdfDocument?.Dispose();
}
}
實現(xiàn)效果如下:

五、最后
盡管AOT啟動速度快,但我們還是更偏向與單文件的壓縮發(fā)布,因為AOT發(fā)布后,加上非托管庫,體積會比較大。而單文件壓縮將非托管后一起打包后大小僅不到26Mb,一個小巧的PDF閱讀器就誕生了,是不是很棒。
到此這篇關(guān)于C#使用PDFium實現(xiàn)預(yù)覽PDF文件的文章就介紹到這了,更多相關(guān)C#預(yù)覽PDF內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
C# SerialPort實現(xiàn)串口通訊的代碼詳解
在.NET平臺下創(chuàng)建C#串口通信程序,.NET 2.0提供了串口通信的功能,其命名空間是System.IO.Ports,這個新的框架不但可以訪問計算機上的串口,還可以和串口設(shè)備進行通信,本文給大家介紹了C# SerialPort實現(xiàn)串口通訊,需要的朋友可以參考下2024-06-06
C# 利用IRawPixels接口遍歷柵格數(shù)據(jù)
本文主要介紹了利用IRawPixels接口遍歷柵格數(shù)據(jù)。具有很好的參考價值,下面跟著小編一起來看下吧2017-02-02
C#實現(xiàn)將應(yīng)用程序設(shè)置為開機啟動的方法
這篇文章主要介紹了C#實現(xiàn)將應(yīng)用程序設(shè)置為開機啟動的方法,涉及C#針對注冊表的寫入技巧,具有一定參考借鑒價值,需要的朋友可以參考下2015-09-09
C#中在WebClient中使用post發(fā)送數(shù)據(jù)實現(xiàn)方法
這篇文章主要介紹了C#中在WebClient中使用post發(fā)送數(shù)據(jù)實現(xiàn)方法,需要的朋友可以參考下2014-08-08

