用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也是一样的.

后续文章再具体分析代码

相关推荐
asdzx675 小时前
使用 Python 快速提取 PDF 中的表格
python·pdf
南风微微吹8 小时前
2026英语六级作文模版万能句子PDF电子版
pdf·英语六级
又是被bug折磨的一天9 小时前
对多个pdf合同文件批量命名
pdf
南风微微吹11 小时前
2026年英语四级作文模版万能句子PDF电子版
pdf·英语四级
这是个假程序员13 小时前
PDF分色、智能PDF黑彩识别工具
pdf
夜勤月13 小时前
HarmonyOS 6.0 ArkWeb实战:PDF背景色自定义功能全解析(附完整代码+避坑指南)
华为·pdf·harmonyos
relis14 小时前
AI使用小技巧: 用zed和MinerU本地版,同时学习PDF文档的文字和图片
ai·pdf·大模型·agent
海盗123414 小时前
C#中PDF操作-QuestPDF页面设置与布局
java·pdf·c#
又是被bug折磨的一天15 小时前
发票形式是eml批量下载发票pdf
pdf
hmz85617 小时前
亲测有效,完全免费PDF转换工具,支持PDF压缩、PDF转图片、PDF删除和排序 、PDF转Word、PDF转文本、图片转PDF【附安装教程】
pdf