乐于分享
好东西不私藏

37页PDF全白?我们给导出引擎做了一场"换脑手术"

37页PDF全白?我们给导出引擎做了一场"换脑手术"

版本:v15-fix
手术日期:2026年6月26日
主刀医生:ally.ren 技术团队
麻醉方式:咖啡 + 红牛


一、Bug 报告:"我写了37页,导出来是37张白纸"

6月25号深夜,群里弹出一条消息:

"我写了一篇37页的公众号长文,点导出PDF,下载下来……全是空白。"

紧接着又一条:

"而且文字被切成了上下两半,上一页剩半行,下一页又半行,跟腰斩似的。"

我们盯着屏幕,陷入了沉思。

37页啊,相当于一本薄薄的小册子了。用户辛辛苦苦写了几天,我们给人家导出一叠白纸?

这要是古代,我们得被拉出去砍头。好在现在是法治社会,我们只需要——加班。


二、解剖:这引擎到底哪里坏了?

2.1 病因一:浏览器有个"身高歧视"

原来我们用的 html2pdf.js,工作方式非常"直男":

"不管文档多长,我一次性把它拍成一张超级大照片(Canvas),然后塞进PDF。"

听起来很高效对吧?问题是——浏览器对 Canvas 有身高限制,大约 16384 像素。37页文档的渲染高度,早就突破了这个"身高歧视线"。

想象一下:你让一个人把一本百科全书的内容,全部写进一张A4纸。纸不够大,字就全消失了。不是字没写,是纸装不下,直接"吞字"了。

更气人的是,它失败得悄无声息——没有报错,没有弹窗,用户看到的只有一叠白纸。就像一个厨师把菜做糊了,还一脸无辜地端上来说"请慢用"。

2.2 病因二:分页的"一刀切"太粗暴

原来的分页逻辑更离谱:按固定高度(1028像素/页)硬切。如果切割线正好落在某行文字中间——恭喜,这行字被物理腰斩,上半身在第3页,下半身在第4页。

这就好比切蛋糕,刀不管不顾地切下去,把蛋糕上的草莓切成两半。你可以吃,但体验很差。


三、手术方案:给引擎换一颗"会思考的脑子"

我们决定,不再用那个"直男"工具了。我们要自己造一个 "会看路、会绕弯、会省内存"的智能引擎

3.1 第一刀:化整为零,逐页拍照片

不再一次性拍全景,而是一页一页拍。每页只处理约 1029 像素的内容,远低于浏览器的"身高歧视线"。拍完一页,立刻把照片销毁(释放 Canvas),不给浏览器任何压力。

如果文档超过 20 页,引擎还会自动"降低画质"——把渲染精度从 2 倍降到 1.5 倍。就像一个有经验的摄影师,知道什么时候该省点胶卷。

3.2 第二刀:三级智能切割,让刀口永远落在"舒服"的地方

这是整个手术最精妙的部分。我们设计了一个三级"安全切割点"搜索系统,让分页永远落在排版最舒服的位置:

🥉 第一级:块级边界扫描("找大块头")

先扫描文档里的"大块头":段落、标题、表格、代码块、引用块、图片……

这些元素的顶部和底部,都是天然的"安全区"。就像切蛋糕时,优先在草莓和草莓之间的空白处下刀。

🥈 第二级:行级文字边界扫描("找缝隙")——核心创新!

这是整台手术的灵魂。我们用 Range.getClientRects() 精确扫描每个段落内部每一行文字的像素位置,找到行与行之间的微小缝隙。

切割点永远落在这些缝隙里,绝不切穿任何一行文字。

想象一下:你拿着一把激光刀,面前是一面文字墙。第一级扫描告诉你"这里有一整面墙",第二级扫描告诉你"墙的砖缝在这里"。你的刀,永远沿着砖缝走。

🥇 第三级:容差搜索("就近找缝")

在目标切割点上下 100 像素的范围内,搜索最近的安全切割点。优先找行级缝隙,其次找块级边界。

就像一个细心的裁缝,宁可多调整几毫米,也绝不让布料"断线"。

3.3 第三刀:缓存加速,同一面墙只扫描一次

同一段落的行级边界,只扫描一次,存入 lineCache 缓存。第二次遇到同样的段落,直接复用结果。

长文档的导出速度因此提升了数倍。 就像你第一次去一个小区,要挨家挨户敲门问路;第二次去,直接走记忆里的近道。


四、手术结果:从"全白"到"完美"

测试场景
页数
空白页
文字腰斩
结果
短文档
7 页
0
0
✅ 完美
长文档
37 页
0
0
✅ 完美
极限文档
60 页
0
0
✅ 完美

37页的长文,从全白到完美导出,每一页的文字都完整、清晰、不被切割。

那个深夜反馈问题的用户,第二天收到了我们的测试版本。他说:"终于能安心存档了。"

这句话,让我们觉得这三天的通宵,值了。


五、术后观察:还顺手发现了一个"安全隐患"

排查过程中,我们发现协作房间的 ROOM_SECRET 没设置,相当于大门没上锁,谁拿到房间 URL 都能进。

我们已经规划了 Token 动态认证 方案:

  • 前端永远不持有密钥
  • 只通过 API 获取有时效的 Token
  • 过期自动失效

核心原则:钥匙只放在服务器保险柜里,绝不揣在用户口袋里。 这是我们对安全的基本态度。


六、术后护理指南:怎么更新?

如果你正在使用 ally.ren 微信编辑器,无需任何操作,更新已自动部署。

如果你需要手动部署,执行以下命令即可:

mkdir -p /root/www.ally.ren/weixinbianji/lib/html2canvas/1.4.1
mkdir -p /root/www.ally.ren/weixinbianji/lib/jspdf/2.5.1

wget -O /root/www.ally.ren/weixinbianji/lib/html2canvas/1.4.1/html2canvas.min.js   https://cdnjs.cloudflare.com/ajax/libs/html2canvas/1.4.1/html2canvas.min.js

wget -O /root/www.ally.ren/weixinbianji/lib/jspdf/2.5.1/jspdf.umd.min.js   https://cdnjs.cloudflare.com/ajax/libs/jspdf/2.5.1/jspdf.umd.min.js

回滚提示:服务器已自动保留原文件备份:
features/features-export.js.bak
如需回滚,一条命令搞定。


七、写在最后

做工具的人,最幸福的时刻,不是代码跑通的那一刻,而是用户说"终于能用了"的那一刻。

37页空白PDF的故事,到此结束。但我们的优化,不会停止。

如果你在使用中遇到任何问题,欢迎随时反馈。每一个Bug报告,都是让我们变得更好的机会。

让写作回归纯粹,让技术隐于无形。

—— ally.ren 技术团队,2026年6月26日


📌 版本信息:v15-fix(PDF 导出优化)
📌 手术对象features/features-export.js
📌 新增器官html2canvas.min.js(195KB)、jspdf.umd.min.js(356KB)
📌 未动刀部位main.jsconfig.jsui.jsupload.jsfeatures-ocr.js 等


本文档由 ally.ren 微信编辑器技术团队整理发布,转载请注明出处。