- 桌面应用
- 文档
【免费下载链接】sumatrapdf
SumatraPDF reader
本文以 ext/patches/README.md 为主线,系统讲解 SumatraPDF 如何将内嵌(vendored)的 mupdf 从"原地改、改完就忘"的维护困局,转变为"每项改动都有一条 git diff 补丁 + 一段动机说明"的可审计流程。读完本文,你将掌握该仓库补丁集的基线版本与编号规则、40 个补丁的完整清单、git apply --3way的应用/手工合并流程、与上游1.28.2逐字节比对验证的方法,以及新增/更新补丁时必须遵守的行尾与同步纪律。
为什么需要一套补丁记录
ext/mupdf是 mupdf 的内嵌副本(vendored copy),SumatraPDF 直接在其中原地修改源码。这种做法的维护成本很高:没有任何记录能说明改了什么、为什么改,每次升级 mupdf 都意味着要从一个涉及约 1400 个文件的 diff 中重新推导出自家的改动。
ext/patches/目录就是为了解决这个问题而存在的记录层:
- 每个
.patch文件对应一个逻辑改动,采用标准的git diff格式,并附有"做了什么、为什么做"的说明; - 补丁的路径基准是
ext/mupdf,因此从该目录内使用-p1即可应用; - 完全属于 SumatraPDF 自有的整文件(例如
pkcs7-windows.[ch])不属于补丁体系,它们放在src/mupdf/(见其 README),根本不在内嵌树内。
补丁基线:mupdf 1.28.2
所有补丁都针对同一个内嵌基线:
- 上游版本:mupdf
1.28.2(tag1.28.2,commitfe374accd); - 版本记录:见 ext/versions.txt(该文件在 mupdf 一行同时注明了"我们对内嵌树的改动记录在 ext/patches/,参见其 README");
- 路径约定:补丁内的路径相对
ext/mupdf展开,应用时需在ext/mupdf目录内使用-p1。
目录的最终状态是一个严格可复现的封闭集:ext/mupdf等于字节级一致的 mupdf1.28.2加上这些补丁,除此之外别无其他。
补丁清单全览
截至当前仓库,ext/patches/下共有 38 个.patch文件。编号刻意保留空洞(缺少 0020、0028),编号一旦分配就不会复用,便于回溯历史。其中第一组是 SumatraPDF 自研补丁:
| 补丁 | 内容 |
|---|---|
0001-tools-usage-say-sumatrapdf | 用法文本显示SumatraPDF <tool>而非mutool |
0002-tools-reset-fz-optind | 支持在进程内多次调用工具 main |
0003-signatures-windows-pkcs7 | 工具改用 Windows CryptoAPI 的 pkcs7 辅助实现(src/mupdf/pkcs7-windows.[ch]),不再依赖 OpenSSL |
0004-console-io-for-gui-subsystem-exe | GUI 子系统可执行文件的 stdio 接通(#5677、#5665、#5681) |
0005-pdfinfo-to-buffer | 将mutool info输出到缓冲区,供"属性"窗口使用 |
0006-jpeg-xr-via-windows-wic | 通过 Windows WIC 编解码器解码 JPEG-XR |
0007-thread-local-secret-contexts | harfbuzz / openjpeg 的 context 走私并非线程安全,改为线程局部存储 |
0008-report-last-uncaught-throw | 未捕获的fz_throw能进入崩溃报告 |
0009-forms-cp1250-latin2-encoding | 表单字段中的中欧拉丁字符(#5404) |
0010-tounicode-from-coded-glyph-names | 从G45/g0045/C65这类字形名推导 ToUnicode(#3219) |
0011-tounicode-cp1251-cyrillic | 带 identity-Latin ToUnicode 的西里尔 Type1 字体(#5873) |
0012-pdf-external-file-streams | 指向外部文件的流通过应用回调处理 |
0013-xml-recover-from-mismatched-close-tags | 恢复处理嵌套错误的 FB2 / HTML(#5792) |
0014-html-bound-generate-boxes-recursion | 修复深层嵌套标记导致的栈溢出 |
0015-css-user-stylesheet-important-wins | 用户来源的!important优先于内联样式 |
0016-stext-search-mujs-include-path | 构建的是合并版ext/a-mujs |
0017-svg-font-attributes-on-groups | <g>的子元素继承字体族 |
0018-pdf-op-run-avoid-double-free | 结构树修复抛异常时的双重释放问题 |
0019-freetype-enable-zlib-and-brotli | 本项目 freetype 启用了 zlib/brotli,而上游精简配置没有 |
0025-webp-images | 通过 libwebp(HAVE_WEBP)解码 WebP,使 EPUB/HTML/MOBI/CBZ 能显示.webp(#3415) |
0027-webp-iccp-without-demux | 不依赖 libwebp demux,自行做 RIFF 遍历以应用 WebPICCP块 |
0030-backport-709661-subset-prefix-font-name | 匹配内建字体名时忽略ABCDEF+子集前缀(涵盖 #4655) |
0031-backport-709663-image-page-height | 重排(reflow)图片适配固定页高而非推进中的块边界(涵盖 #6007) |
0032-pdf-appearance-unrendered-annots | 为 Movie/Screen/3D/RichMedia/Watermark/PrinterMark/TrapNet/Projection 生成占位外观流(AP) |
0033-pdf-appearance-markup-movie-poster | 标记注释默认黄色高亮、无 QuadPoints 时使用/Rect、跳过 0 宽未填充的 Square/Circle、Movie 的/Poster作为 AP |
0034-backport-709678-cjk-fullwidth-punctuation | 半角/全角形式与 CJK 标点走非内嵌 CJK 路径(涵盖 #6082) |
0035-backport-709680-flow-anchor-top | HTML/EPUB 链接目标使用 flow 节点顶部而非基线(涵盖 #6095) |
0036-ocg-usage-event-on-visible | PrintState/ViewState 为 ON 时即使 OCG 在配置/OFF列表中也绘制(#6101) |
0037-backport-709648-inline-context-after-block | 块元素打断后不再继续向 inline context 追加内容(涵盖 #5943) |
0038-html-css-background-image | 块级盒上的 CSSbackground-image/-size/-position/-repeat;扫描型固定布局 EPUB 此前渲染为空白(#6131) |
0039-md-empty-buffer-nul-scan | 空 markdown:fz_md_to_html中的len-1下溢(#6143) |
0040-svg-css-class-styles | SVGclass="st0"对照<style>样式表解析;此前这类文件全部画成黑色(#2155) |
另有 11 个并非 SumatraPDF 自研、而是提前携带的上游修复(详见下节 Backports 策略):
| 补丁 | 内容 |
|---|---|
0021-backport-709471-single-line-field-box | 上游修复:单行字段内容框与/DA字号为 0 的问题 |
0022-backport-709480-bound-xml-recursion | 上游修复:XPS 元数据与 EPUB 目录的深度限制(涵盖 #5032) |
0023-backport-709574-html-metadata | 上游修复:HTML 与 FB2 的标题/作者/主题元数据(涵盖 #2254) |
0024-backport-5e5ef9e-pool-asprintf | 上游fz_pool_asprintf(0026 依赖它) |
0026-backport-709657-fb2-author | 上游 FB2 作者遍历:每个<author>的 first-name + last-name(涵盖 #2254) |
0029-backport-709660-tj-array-tc-tw | TJ数组内出现Tc/Tw后能恢复,页面其余部分仍可绘制(涵盖 #4157) |
0030-backport-709661-subset-prefix-font-name | 匹配内建字体名时忽略ABCDEF+子集标签(涵盖 #4655) |
0031-backport-709663-image-page-height | 重排图片在每一页都收缩到固定页高,而非仅第一页(#6007) |
0034-backport-709678-cjk-fullwidth-punctuation | 半角/全角形式与 CJK 标点使用 CJK 字体,而非内嵌回退字体(#6082) |
0035-backport-709680-flow-anchor-top | HTML/EPUB 链接目标使用 flow 节点顶部而非基线(#6095) |
0037-backport-709648-inline-context-after-block | 包裹块元素的嵌套<span id>不再全部跳到章节开头(#5943) |
注意0030、0031、0034、0035、0037同时出现在两张表中——它们是backport-*命名、但能力上与自研补丁重叠、最终以上游实现为准的补丁(见下节)。
Backport 策略:能拿上游的就别自己写
命名含backport-的补丁,其本质是:该修复存在于 mupdfmaster,但不在本仓库内嵌的发布版里(1.28.x系列 tag 来自在它分叉之前的维护分支),因此提前取来应用。这带来两条硬性纪律:
- 升级即删除:当内嵌的 mupdf 越过补丁名所指的那个 commit 后,必须删除该补丁——否则下次升级时,
git apply会把基线里已经包含的改动再打一遍而失败。 - 优先 backport 而非自研:只要上游已经修了同一件事,就优先采用 backport。它是不需要反复合并的代码,而且上游实现通常覆盖更多场景。
README 给出了一个非常有说服力的案例链:0022取代了 SumatraPDF 自研的 XPS 深度限制(因为它顺带保护了两个 EPUB 目录解析器);0023是 SumatraPDF 的 FB2 元数据补丁被 Artifex 上游化之后(上游提交说明中写着 "Based on a patch from Krzysztof Kowalczyk of SumatraPDF")反过来以 backport 形式并入的;0026取代了自研的 FB2 作者名辅助函数,0029取代了 TJ 数组内Tc/Tw中断修复,0030取代了基础 14 字体的子集标签剥离,0031取代了自研的 reflow 图片页高修复。
因此,写新补丁之前先查上游,每次升级再查一次——上游可能已经赶上来了。
应用补丁:一次完整的 mupdf 升级
README 给出了从上游仓库检出新版本、再把补丁集整体应用的标准流程:
git -C ~/src/mupdf worktree add /tmp/mupdf-new <new-tag> cd /tmp/mupdf-new for p in ~/src/sumatrapdf/ext/patches/0*.patch; do git apply --3way "$p" || echo "needs hand-merging: $p" done--3way让git apply在补丁不能干净落地时尝试三方合并。之后的工作流是:
- 手工合并所有标记为
needs hand-merging的补丁; - 把结果复制回
ext/mupdf——注意保留本项目的文件挑选:SumatraPDF 内嵌的是 mupdf 的子集,不是整棵树; - 更新
ext/versions.txt; - 重新生成补丁目录,使每个补丁都是基于新基线的 diff。
一个实用的经验法则:冲突有时是好消息。冲突往往意味着上游已经修了同一处。README 举了实例:旧的0019-stext-device-guard-null-line在升级到 1.28.2 时,mupdf 恰好加入了完全相同的cur_line &&防护,于是该补丁被直接删除而不是重新合并。因此解决冲突前务必先读懂冲突内容。
验证补丁:字节级回归校验
补丁体系的核心承诺是"可复现":把全部补丁应用到一个干净的基线上,必须逐字节复现ext/mupdf。验证命令:
mkdir /tmp/check git -C ~/src/mupdf -c core.autocrlf=false -c core.eol=lf archive 1.28.2 | tar -x -C /tmp/check cd /tmp/check for p in ~/src/sumatrapdf/ext/patches/*.patch; do git apply "$p" || echo "FAIL $p"; done diff -r /tmp/check ~/src/sumatrapdf/ext/mupdf # only reports files we do not vendor要点解读:
archive从 git 对象库直接导出1.28.2的树,并强制core.autocrlf=false、core.eol=lf,保证基线字节干净;diff -r是字节比较,只要它开始报告某个内嵌文件(vendored file),就说明要么漏了补丁、要么内嵌树发生了漂移;- 该检查"迄今对所有 1407 个内嵌文件全部通过"(README 原文)。注意内嵌是子集,因此 diff 会列出本项目故意不携带的上游文件,这是预期输出,不属于失败。
行尾纪律:LF 是硬约束
ext/mupdf与上游一致、全树使用 LF,补丁文件也必须是 LF-only。不要让编辑器或 checkout 把它改写成 CRLF:仓库的.gitattributes设置了* -text,所以提交的字节就是所有人拿到的字节;一旦出现 CRLF 文件,上面的字节比对会无缘无故地失败。这是一个"一次失误、全局报错"的典型陷阱,值得在 CI 或预提交检查中固化。
保持补丁集同步:改内嵌树必须同时写补丁
README 的最后一条纪律最简单也最容易被忽视:当你在ext/mupdf下改动任何东西时,必须在同一个 commit 里新增或更新这里的补丁。只存在于内嵌树里的改动,会在下一次升级时被静默丢弃——这正是当初建立这套补丁体系要消灭的问题。
补丁之外的边界:src/mupdf 自有代码
补丁体系只覆盖"对 mupdf 已有文件的修改"。完全自有的新文件不放在内嵌树里,而是放在 src/mupdf/(其 README 有完整说明),编译进mupdf工程(premake5.files.lua中的mupdf_files();ninja 侧对应cmd/deps-build-defs.ts中的mupdflib),但物理上位于内嵌树之外,因此既不会被误认为上游代码,也不需要ext/patches/条目。该目录当前包含:
mupdf_load_system_font.c—— 为 mupdf 的字体回退加载 Windows 已安装字体,并包含 plain-malloc 的 harfbuzz 分配器包装;noto_sumatra.[ch]—— 编译时替代 mupdf 的noto.c:内建字体(base 14、CJK 回退、Charis SIL、少量 Noto)不链接进二进制,而是通过fz_set_builtin_font_loader()按文件名获取,由 SumatraPDF 从fonts\的IDR_EMBEDDED_PAK资源提供(增减字体需同步修改 cmd/pack-embedded-prebuild.cmd 中的清单);pkcs7-windows.[ch]—— 在 Win32 CryptoAPI 之上实现 PDF 签名校验与签署,取代 OpenSSL(补丁0003让 mupdf 的pdfsign/murun调用它)。
代表性补丁源码剖析
补丁的价值不仅在"能打上去",更在每一条都对应真实的用户问题。下面结合仓库中的补丁文件本身(它们自带动机说明)与调用侧代码,看几个有代表性的例子。
0001:用法文本从 mutool 改名为 SumatraPDF
0001-tools-usage-say-sumatrapdf.patch 是纯字符串修改:SumatraPDF 把 mupdf 的 mutool 工具族以SumatraPDF <tool>子命令形式暴露,因此muconvert.c、mudraw.c、mugrep.c、muraster.c、mutrace.c以及pdfaudit/pdfbake/pdfclean/pdfcreate/pdfextract/pdfinfo/pdfmerge/pdfpages/pdfposter/pdfrecolor/pdfshow/pdfsign/pdftrim的 usage 文本全部从mutool xxx/mudraw/muraster改为SumatraPDF <tool>形式(muraster还删掉了版本横幅)。它印证了补丁目录与可执行形态的对应关系:见 src/sumatrapdf-tool.cpp 中gTools[]把draw/convert/run/info/clean等名字映射到从libsumatrapdf.dll导出的mudraw_main、muconvert_main、murun_main、pdfinfo_main等入口(导出清单见 src/libsumatrapdf.def)。README 也提醒:纯字符串改动,若上游重写了 usage 块,需要手工重新应用。
0004:GUI 子系统可执行文件的控制台 IO
0004-console-io-for-gui-subsystem-exe.patch 解决一个 Windows 经典难题:SumatraPDF.exe是 GUI 子系统二进制,从控制台启动SumatraPDF draw .../SumatraPDF run时没有任何可用的 stdio。补丁在mudraw.c(该翻译单元会进入 libmupdf)新增两个函数:
fz_redirect_io_to_existing_console():用AttachConsole(ATTACH_PARENT_PROCESS)挂到父控制台,但只有流未重定向时才 freopen 到CONOUT$——若 stdout/stderr 已经被重定向到文件或管道,再 freopen 会把输出丢掉(#5677);同时用_open_osfhandle把 stdin 绑到继承的句柄,否则mutool run的readline()会报 "cannot read line from stdin"(#5665);fz_console_readline():Windows 控制台上fgets(stdin)会莫名返回 EOF(#5681),改为走ReadConsoleA,murun.c的 REPL 循环与jsB_readline()都改用它。
这解释了 src/sumatrapdf-tool.cpp 头注释里的设计取舍:专门提供sumatrapdf-tool.exe这个控制台子系统程序来承载这些工具,让它们在 cmd.exe / PowerShell 下行为正常,同时因为所有实现都在libsumatrapdf.dll,可执行体本身保持极小。
0003:签名从 OpenSSL 换成 Windows CryptoAPI
0003-signatures-windows-pkcs7.patch 把murun.c与pdfsign.c中的#include "mupdf/helpers/pkcs7-openssl.h"换成#include "pkcs7-windows.h",调用点同步从pkcs7_openssl_new_verifier/pkcs7_openssl_read_pfx换成pkcs7_windows_new_verifier/pkcs7_windows_read_pfx。原因是 SumatraPDF 不链接 OpenSSL。辅助实现本身在src/mupdf/pkcs7-windows.[ch],因为src/mupdf在 mupdf 工程的 include path 上,所以补丁内按裸名#include "pkcs7-windows.h"即可;而src/下的自有代码则用#include "mupdf/pkcs7-windows.h"这种带前缀的写法(见 src/mupdf/README.md)。
0025:WebP 图片解码
0025-webp-images.patch 解决 #3415:独立的.webp文件和漫画包此前已由 EngineImages 解码,但 EPUB/MOBI/HTML 内的图片走fz_new_image_from_buffer,会显示 IMAGE 占位符。补丁(改编自 Artifex bug 697749 与 koreader 的同源补丁):
- 新增
FZ_IMAGE_WEBP枚举与source/fitz/load-webp.c(WebPGetFeatures+WebPDecodeRGB(A)Into解码进 pixmap); - 把
fz_recognize_image_format的识别长度从 8 字节扩到 12 字节,以匹配RIFF....WEBP特征; - 在 CBZ 扩展名列表加入
.webp(source/cbz/mucbz.c); - 为 HTML 的
data:image/webp;base64,内联图与 MOBI 的图片提取补充处理; - 明确省略 libwebp demux 的 EXIF/ICC——因为本项目不构建 demux 库。
它展示了补丁体系的典型形态:跨多个翻译单元的增量、附带#ifdef HAVE_WEBP的降级路径(无 libwebp 时抛FZ_ERROR_UNSUPPORTED)。
0038:CSS background-image 修复扫描型固定布局 EPUB
0038-html-css-background-image.patch 针对 #6131:CSS 引擎此前只知道background-color,而扫描转 EPUB 工具(如 Jouve LetoGen)把每页画面放在div{background-image:url(...)}里、没有<img>也没有文字,导致每页都渲染为空白。补丁为fz_css_style新增background-image/-size/-position/-repeat四个属性(含background:简写解析,见css-apply.c的add_shorthand_background),在构建 box 时像<img>一样加载图片,draw_block_box按 padding box 裁剪绘制,支持cover/contain与平铺,并用MAX_BACKGROUND_TILES 4096封顶——微图铺满大盒会意味着数百万次填充,超过上限就只画一张。注意其css-properties.h是由css-properties.gperf用 gperf 3.2.1 重新生成的(TOTAL_KEYWORDS从 81 增至 85)。
0013:XML 错配闭合标签的容错
0013-xml-recover-from-mismatched-close-tags.patch 解决 #5792:真实世界的 FB2/HTML 经常乱嵌套(<b><i>x</b></i>),原fz_parse_xml会放弃整个文档。补丁重写close_tag():遇到错配闭合标签时,向上依次关闭中间未闭合的标签直到匹配;对于找不到任何匹配开标签的闭合标签则直接忽略——即 HTML 解析器的恢复方式,这样修复</b>后残留的</i>不会弄死整个文档。原上游实现保留在#if 0块中备查。
0040:SVG 从<style>样式表解析 class
0040-svg-css-class-styles.patch 解决 #2155:SVG 解析器原本只读表现属性与内联 style 属性,而 Illustrator/Inkscape 输出把颜色全部放进<style>表、用class="st0"引用,结果每个图形都用默认 fill 渲染成纯黑。补丁在文档打开时收集每个<style>元素的.name { ... }规则,解析前把匹配声明拼接到元素的 style 属性上;内联声明排在前,因此仍然优先于样式表。超出纯 class 选择器的语法(at-rule、元素/id 选择器、组合器、伪类)一律跳过而非半支持——这是刻意的最小化实现。
小结
ext/patches/把 SumatraPDF 与 mupdf 的耦合关系变成了一个可审计、可重放、可验证的工程流程:38 个git diff格式补丁(含 11 个上游 backport)锚定在 mupdf1.28.2(commitfe374accd)之上,全部应用后必须与ext/mupdf逐字节一致(当前对 1407 个内嵌文件全部通过);升级时用git apply --3way批量应用、手工合并冲突,升级后重新生成补丁;自有新文件走src/mupdf/而非补丁;任何对内嵌树的改动都必须与补丁同 commit。这套机制既是维护手册,也是理解"SumatraPDF 从 mupdf 继承了什么、又额外补了什么"的最佳入口。
- 桌面应用
- 文档
【免费下载链接】sumatrapdf
SumatraPDF reader
相关推荐
FreeRTOS CBMC 验证补丁集详解:patches 目录的三大类补丁与自动化应用工具链
FreeRTOS CBMC 验证补丁集详解:patches 目录的三大类补丁与自动化应用工具链 FreeRTOS 仓库的 FreeRTOS/Test/CBMC/
操作系统嵌入式OS嵌入式物联网revanced-patches内存管理:优化补丁的内存使用
revanced patches内存管理:优化补丁的内存使用 还在为Android应用补丁的内存占用和性能问题烦恼吗?ReVanced Patches通过精心设
移动开发Magisk补丁管理:及时应用安全补丁的方案
Magisk补丁管理:及时应用安全补丁的方案 引言:Android安全补丁的痛点与解决方案 你是否曾遇到过这样的困境:Android系统安全补丁发布后,设备制造
移动开发系统底层
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考