news 2026/5/30 17:28:50

从零开始: C#轻松预览PDF文件-支持跨平台AOT友好

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
从零开始: C#轻松预览PDF文件-支持跨平台AOT友好

求来了,就得想办法实现诶。要预览PDF文件,需要先完成对PDF的解析,即将PDF中各类数据提取出来,然后再实现对解析数据的绘制渲染到软件层面。在C#中关于PDF解析库有很多,我们优先考虑的主要为用C#对PDFium封装库。PDFium是基于由google维护的项目 (https://github.com/chromium/pdfium) (Apache-2.0 license),维护很频繁(本文发出前2小时Pdfium仓库还在更新),此外他的特点是采用动态链接库,C++编译体积很小5mb,兼容性较好,接口规范而且功能强大。

二、C#现有Pdf预览库PdfiumViewer的不便

为了不造轮子,我们找到了 PdfiumViewer (https://github.com/bezzad/PdfiumViewer,Apache-2.0 license), 是一个基于Pdfium封装后实现了预览效果的C#库。

尽管PDFium预览效果很好,但最大的问题是,PdfiumViewer中对渲染后页面的创建深度依赖了WPF框架(true),而且是与解析渲染方法组合在一起的。由于很多内部对象大量引用了WPF元素。因此在后端调用时或用于其他如Winform、Avalonia框架时非常不方便。如果想要单纯集成至Winform或后端服务器调用那么还必须带着WPF框架过去,非常不方便,只能自定义对PdfiumViewer进行改造了。

三、Pdf预览原理检视及修改

Pdf的预览主要是由解析+渲染两部分实现。PdfiumViewer中,解析和底层渲染都是基于Pdfium库的封装调用实现的。如:,

[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封装的方法中,将额外考虑:

状态检查和DPI校正

位图对象管理及内存锁定

原生PDF渲染 (Pdfium中渲染准备)

背景设置和页面设置

实际PDF渲染 (渲染到位图)

PdfiumView封装后的方法为:

public Image Render(int page, int width, int height, float dpiX, float dpiY, PdfRotation rotate, PdfRenderFlags flags)

基于这个方法仅需要传入页码(从0开始)、宽度、高度、dpi、旋转等参数,就可以得到PDF文件中指定页的渲染后图片了。

四、精简处理及Avalonia页面实现

为了更加通用,我们对PdfiumViewer中依赖WPF的代码进行了删减,主要包括书签相关的对象,滚动面板及动态渲染等,最后留下的只有指定PDF页渲染图片的功能。在大部分场景中已经够用了,而且是支持AOT发布的,也可以作为一个独立进程工具供其他程序调用。

为了测试效果,我们选择在Avalonia中创建一个可滚动的页面,创建一个虚拟模式的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很简单,主要是为了页面的延迟渲染,即滚动到哪一页,就调用上述组件的方法,实时渲染当前页图片。

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();

}

}

实现效果如下:

pdfPreview2

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/5/28 12:30:05

【MUSIC、最大似然与克拉美-罗下界】MUSIC与ESPRIT 算法来估计到达角(AoA),并尝试推导克拉美-罗下界(CRLB)以分析其性能研究附Matlab代码

✅作者简介&#xff1a;热爱科研的Matlab仿真开发者&#xff0c;擅长数据处理、建模仿真、程序设计、完整代码获取、论文复现及科研仿真。&#x1f34e; 往期回顾关注个人主页&#xff1a;Matlab科研工作室&#x1f34a;个人信条&#xff1a;格物致知,完整Matlab代码及仿真咨询…

作者头像 李华
网站建设 2026/5/28 12:30:06

LLM 场景下的强化学习技术扫盲

强化学习基础&#xff1a;行业黑话想象你正在和一个刚训练好的语言模型聊天。你问&#xff1a;“今天过得怎么样&#xff1f;”模型可能回&#xff1a;“还行。” 也可能回&#xff1a;“我是个 AI&#xff0c;没有感情。”人类觉得前者更自然、更友好——这就是偏好反馈。强化…

作者头像 李华
网站建设 2026/5/28 12:31:26

多智能体协同系统

多智能体协同系统的核心概念 多智能体协同系统&#xff08;Multi-Agent Systems, MAS&#xff09;通过多个自主智能体的交互实现复杂任务&#xff0c;广泛应用于机器人协作、自动驾驶、游戏AI等领域。核心特性包括分布式决策、通信协议、任务分配与冲突解决。典型应用案例 1. 无…

作者头像 李华
网站建设 2026/5/28 14:09:13

多角度关于人的本质的论述,你怎么思考?

第六章&#xff1a;多角度关于人的本质的论述人的本质&#xff0c;人和动物的区别是什么&#xff0c;此文可以参考。这个问题很深奥&#xff0c;历来人类试图回答。比如中国古代对于人&#xff0c;有善恶之分&#xff0c;但这显然不具有说服力。以下是马克思哲学关于人本质的思…

作者头像 李华
网站建设 2026/5/28 12:30:12

Flutter 实现一个容器内部元素可平移、缩放和旋转等功能(六)

Flutter 实现一个容器内部元素可平移、缩放和旋转等功能&#xff08;六&#xff09; Flutter: 3.35.6 前面有人提到在元素内部的那块判断怎么那么写的&#xff0c;看来对知识渴望的小伙伴还是有&#xff0c;这样挺好的。不至于说牢记部分知识&#xff0c;只需要大致了解一下有…

作者头像 李华
网站建设 2026/5/28 12:30:11

python作业4

a 56 b -18# 1. 按位与(&)&#xff1a;对应位都为1则为1&#xff0c;否则为0 # 56: 00111000 # -18补码: 11101110 # 按位与: 00101000 → 十进制40 bit_and a & b print(f"按位与(&): {a} & {b} {bit_and}")# 2. 按位或(|)&#xff1a;对应位有…

作者头像 李华