用rust+slint编写一个pdf阅读器

目录

桌面pdf阅读器的制作原因

选择ui框架

egui

floem

gpui

slint

阅读器设计

阅读器的核心部分

文档加载和初始化流程

视图布局和显示流程

解码和渲染流程

结果处理和显示流程

[UI 交互流程](#UI 交互流程)

关键性能优化

开发过程中遇到几个问题:


桌面pdf阅读器的制作原因

之前用compose写pdf阅读器https://blog.csdn.net/archko/article/details/151873466?spm=1001.2014.3001.5501,把更早的android view取代了,顺便利用了compose的跨平台特性,写了桌面版.但遇到了几个问题.

  • 桌面版发生了内存泄露,暂不清楚是不是框架的问题,解码这些共享代码,在手机上没有出现问题.
  • 缩放无法做到之前的pdf阅读器的效果,因为布局渲染的原因,只能说接近,放大10倍后移动会有些卡.

缩放的问题还算好的,其它方面的流畅度已经比之前的强了.

mupdf解码是非常快,但要复制数据到jni,然后再传到compose,一层一层,导致最终的效果没那么好.

主要是桌面的版,内存泄露.还有就是windows的版本编译mupdf,tiff,heic太困难了,试了好久没有编译成功,mupdf成功了,但是jni调用失败.最终还是先放弃了,自己也没有windows电脑了.

用rust实现一个pdf阅读器,主要是因为看重rust的性能.

选择ui框架

rust也有一些ui框架,但能称得上框架的可能只有slint,完善的文档,支持四种风格,虽然material有点怪.其它的大概只是一个底层api的简单ui封装.

解码,页面处理等这些完成的情况下.主要是要绘制到ui上.所以ui框架的替换也容易,于是就试了几个.

egui

代码简洁,使用eframe,主要问题在于即时模式,静止的时候cpu就上来了,风扇一直转.放弃

floem

上次0.2.0还是24年11月发布的,代码有看到更新,但是似乎不太积极,还没发新版.文档一般.api还有所欠缺.grid可以用flexbox轻松搞定.中文支持没问题,ui不太美观.

在最后绘制阶段,image如何绘制为img,遇到了问题,下载源码,ai直接指出,有些api只能内部用,使用image先转png,bmp等再渲染出来,耗时无法忍受,一个页3秒了,有人提了push不要img,被拒绝了.于是放弃.

gpui

与floem一样,是在代码编辑器中产生的.没有试,因为电脑太老了,不太支持.官方文档相对要完善不少,界面也漂亮一些了.后续有可能还是尝试一下的.

slint

一年前就学习过,看过文档,运行过示例.当时觉得ide支持较差,还要学习一门新的语言,太费时了.还是收费,现在桌面端改为不收费了.

中文支持遇到了问题.在m2机器上,显示方块.只能加入默认字体,试了好多种,不能逻辑映射,只能用固定的字体,要么系统安装,要么打包.

grid就比较挫了,要提前算好行列.

slint与rust的交互有点不太直接,通过各种事件绑定.

slint不太适合那些高频交互,游戏类的,手势交互相对弱.适合一些工具类的.

其它的ui框架不太想试了.tauri试过,它基于webview,放弃.我不喜欢web.

阅读器设计

因为前面有提到,ui框架还有考虑要换,所以天然与其它的部分是分开的.尤其slint就触发了这种隔离了.所以现在分为两部分.

ui部分,slint的文档照着把一些元素像html那样声明出来就可以了.它已经处理好比如滚动,视图变化等,main.rs里监听就可以了.然后自己刷新视图.

阅读器的核心部分

我还是参考了之前compose的设计.documentview用于显示,剩下的全部view_state来管理所有的页面,它连接了解码器.

我用ai总结了一下现在的设计.

文档加载和初始化流程

  1. 用户选择文件setup_open_handler 调用 rfd::FileDialog 选择 PDF/EPUB/MOBI 等文件
  2. 打开文档PageViewState::open_document() 调用 DecodeService::load_pdf()
  3. 文档解析DecodeService::handle_task(LoadDocument) 创建 PdfDecoder,调用 PdfDecoder::open() 使用 MuPDF 库解析文档
  4. 预加载页面信息PdfDecoder::open() 遍历所有页面,预加载 PageInfo(尺寸、索引等)
  5. 生成封面缩略图 → 保存到用户数据目录的 RReader/images/
  6. 创建页面对象 → 将 PageInfo 转换为 Page 对象,存储在 PageViewState.pages
  7. 加载大纲 → 获取 PDF outline 信息

视图布局和显示流程

  1. 视图大小更新viewport_changed 回调调用 PageViewState::update_view_size()
  2. 重新计算布局 → 根据滚动方向(垂直/水平)和缩放比例计算页面位置和尺寸
  3. 更新可见页面PageViewState::update_visible_pages() 使用二分查找确定可见页面列表
  4. 预加载区域 → 包含屏幕大小的预加载区域,避免加载延迟

解码和渲染流程

  1. 提交解码任务DecodeService::render_pages() 批量提交 RenderPage 任务到解码队列
  2. 后台解码 → 解码线程循环处理任务:
    • 检查新任务(task_rx.try_recv())
    • 处理一个队列任务(task_queue.pop_front())
    • 验证任务是否还在可见页面中
    • 调用 PdfDecoder::render_page(),或其它的比如djvudecoder,tiff等解码
  3. 页面渲染
    • PdfDecoder::render_page() 计算裁剪区域和缩放
    • 使用 MuPDF 的 PixmapDevice 渲染到 RGBA 像素缓冲区
    • 调用 mupdf_to_pixels() 转换为 Rust Image 格式

结果处理和显示流程

  1. 定时器轮询 → 每100ms 检查解码结果(DecodeService::try_recv_result())
  2. 转换图像格式 → 将原始像素数据转换为 image::RgbaImage 然后转换为 slint::Image
  3. 更新缓存ImageCache::put_thumbnail() 将图像存入 LRU 缓存
  4. 获取链接信息 → 同时提取页面链接信息,存储在 PageViewState::page_links
  5. 刷新UIrefresh_view() 更新 UI 页面模型:
    • 从缓存获取图像或显示页码文本
    • 更新滚动位置、大小、缩放等状态
    • 通过 Slint 数据绑定刷新 DocumentView

UI 交互流程

  • 滚动/缩放 → 触发 update_visible_pages() → 提交新解码任务
  • 翻页jump_to_page() → 更新偏移并刷新可见页面
  • 点击链接 → 解析链接 → 可能跳转到指定页面

关键性能优化

  • 异步解码: 多线程解码,不阻塞UI
  • LRU缓存: 自动清理不常用图像
  • 预加载: 提前解码一屏不可见页面
  • 分块渲染: 大页面分块减少内存使用(未使用)
  • 去重任务: 避免重复解码同一页面

其中最难的是多线程解码,因为mupdf只支持单线程的,其实就是ui线程与解码线程两个.而mupdf不允许在线程间传递,trait不满足,这是最恶心的点.就算用ai也是搞半天不成功.

最后的效果就是,打开app后,100mb内存占用,打开文档,在300mb左右.然后回收也快,没有内存泄露.运行效果是真的非常快,整个大图片解码20ms,渲染转换也是10ms以内.大概不到30ms就完成解码到转换slint可显示的图片了.然后还有预加载,这样实际的翻页效果与pdf expert这个mac上号称最快的对比,不落下风.

开发过程中遇到几个问题:

diesel这个数据库真恶心,花了好多时间,最后放弃,官方文档也是,看着好像可以,一步一步操作下来,结果报实体类找不到.然后换了sea.

解码线程的问题,最终是靠付费的ai搞定了.

中文,靠固定宋体ttf解决了.

mupdf字体链接失败,目前无法解决,等更新.除了一些特殊的非扫描版pdf与epub/mobi这些,都还好,多数非扫描版还是没问题的.

这次的代码没有花多少时间.主要有几个原因:

功能还略少一些,比如没有分块渲染,直接整个页面渲染图片.也不处理缩略图了.因为我发现,解码到渲染是真的太快了,可以直接省略这步.10年前的老机器了,依然很快.

没有了gradle,这个是真恶心,慢,还加载一堆看似无关的依赖.

没有实现tts朗读.

没有实现文字选择识别,复制功能.

虽然功能少了,但之前的相同核心功能的实现花费是这次的几倍.与有没有ai无关.

解码,渲染,缓存,布局这些流程相当于是从原来的compose直接翻译过来的,不会有太多的坑.加上ai的辅助,很快就完成了.

rust的ui框架写代码确实太快了,比compose的那套不知道快多少倍.代码量还少的多.虽然都是号称声明式的,但compose实在太耗时了.swift也是一样的.

后续文章再具体分析代码

相关推荐
manjianghong868 小时前
如何将一本书PDF扫描件转word 并打印(免费工具)
pdf·word·pdf处理工具
zhangfeng113314 小时前
大语言模型llm学习路线电子书 PDF、开源项目、数据集、视频课程、面试题、工具镜像汇总成一张「一键下载清单」
学习·语言模型·pdf
manjianghong8616 小时前
PDF扫描件图片太大如何批量裁剪(免费工具)
pdf·pdf免费工具·pdf文件处理
YJlio16 小时前
杨利杰YJlio|博客导航目录(专栏总览 + 推荐阅读路线)
开发语言·python·pdf
꧁༺℘₨风、凌๓༻꧂16 小时前
C# WPF 项目中集成 Pdf查看器
pdf·c#·wpf
liliangcsdn1 天前
常用pdf解析提取工具的分析和示例
pdf
有趣灵魂2 天前
Java-Spingboot根据HTML模板和动态数据生成PDF文件
java·pdf·html
mfxcyh2 天前
使用html2canvas和jsPDF导出pdf文件、把pdf文件传给后端
pdf
ComPDFKit2 天前
从爱泼斯坦案文件泄露,看“涂黑≠删除”的 PDF 脱敏陷阱
pdf·脱敏·pdf redaction·标记密文·涂黑
今夕资源网2 天前
PDF与图片在线处理工具纯HTML网页源码 PDF 多功能魔方
pdf·pdf在线处理