news 2026/10/5 1:51:26

MuPDF 结构化文本搜索选项(Search Options)完全指南:从选项字符串到底层匹配引擎

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
MuPDF 结构化文本搜索选项(Search Options)完全指南:从选项字符串到底层匹配引擎
  • 图形学
  • 图像处理

【免费下载链接】mupdf

mupdf mirror

项目地址:https://gitcode.com/gh_mirrors/mu/mupdf
点击查看免费下载

MuPDF 提供了一套统一的“选项字符串(option string)”机制来定制文档搜索行为,docs/reference/common/search-options.md即是对这套搜索选项的官方说明。本文将完整继承该文档中的全部选项定义,并结合仓库内source/fitz/stext-search.c的真实实现,逐项剖析每个选项的底层原理与适用场景,同时给出可在mutool grep与 C API 中直接使用的组合示例。读完本文,你将能够精准配置 MuPDF 的文本搜索行为,处理大小写、变音符、正则、断行与连字符等真实文档中的检索难题。

搜索选项的载体:Option String

搜索选项并不是通过单独的 API 参数逐个传入,而是通过一个由 key-value 对构成的选项字符串统一描述。该字符串支持三种等价语法(详见 docs/reference/common/option-strings.md):

  • 逗号分隔语法(经典形式):

    ignore-case,keep-lines
  • URL 查询字符串语法(以?开头,特殊字符用%HH转义):

    ?ignore-case=true&keep-lines=true
  • JSON 子集语法(单个 JSON 对象,值仅限布尔、数字、字符串与数字数组):

    {"ignore-case":true,"keep-lines":true}

布尔值可以用多种等价写法表达:true / yes / on / enable / 1表示开启,false / no / off / disable / 0表示关闭;一个不带值的空选项同样视为true。这三个选项(exact、ignore-case、ignore-diacritics等)彼此之间通过逻辑或组合生效。

搜索选项总览

docs/reference/common/search-options.md定义了以下全部选项,它们在 MuPDF 中作为位掩码(bitmask)被解析,每个选项对应一位标志位:

选项含义源码标志位(见 include/mupdf/fitz/structured-text.h)
exact按给定字符串原样精确匹配(默认行为)FZ_SEARCH_EXACT = 0
regexp将搜索串解释为 JS 风格正则表达式FZ_SEARCH_REGEXP = 4
ignore-case忽略搜索串与页面文本的大小写差异FZ_SEARCH_IGNORE_CASE = 1
ignore-diacritics忽略搜索串与页面文本中的变音符(如 é 与 e)差异FZ_SEARCH_IGNORE_DIACRITICS = 2
keep-lines保留行尾为\n,否则行尾被转换为空格FZ_SEARCH_KEEP_LINES = 8
keep-paragraphs保留段落结尾为\n,否则段尾被转换为空格FZ_SEARCH_KEEP_PARAGRAPHS = 16
keep-hyphens保留连字符原样;默认会合并行尾的连字符断词FZ_SEARCH_KEEP_HYPHENS = 32

需要特别说明的组合规则(原文档明确给出):同时开启keep-lines与keep-paragraphs时,行以单个\n结尾、段落以\n\n结尾——这意味着你可以用正则精确匹配跨行、跨段落的文本结构。

选项解析的源码实现

在source/fitz/stext-search.c中,选项字符串首先由fz_parse_search_options()借助通用fz_new_options()解析成键值对集合,再由fz_apply_search_options()逐个匹配并置位:

const char *fz_search_options_usage = "Search options:\n" "\texact: match exact, case sensitive pattern\n" "\tignore-case: case insensitive search\n" "\tignore-diacritics: ignore character diacritics\n" "\tregexp: interpret search pattern as regular expression\n" "\tkeep-lines: preserve line breaks so pattern can match them\n" "\tkeep-paragraphs: preserve paragraph breaks so pattern can match them\n" "\tkeep-hyphens: preserve hyphens, avoiding joining lines\n"; void fz_apply_search_options(fz_context *ctx, fz_search_options *opts, fz_options *args) { fz_search_options mask = *opts; if (fz_lookup_option_yes(ctx, args, "exact")) mask |= FZ_SEARCH_EXACT; if (fz_lookup_option_yes(ctx, args, "ignore-case")) mask |= FZ_SEARCH_IGNORE_CASE; if (fz_lookup_option_yes(ctx, args, "ignore-diacritics")) mask |= FZ_SEARCH_IGNORE_DIACRITICS; if (fz_lookup_option_yes(ctx, args, "regexp")) mask |= FZ_SEARCH_REGEXP; if (fz_lookup_option_yes(ctx, args, "keep-lines")) mask |= FZ_SEARCH_KEEP_LINES; if (fz_lookup_option_yes(ctx, args, "keep-paragraphs")) mask |= FZ_SEARCH_KEEP_PARAGRAPHS; if (fz_lookup_option_yes(ctx, args, "keep-hyphens")) mask |= FZ_SEARCH_KEEP_HYPHENS; ... }

解析完成后,fz_new_search()会根据选项位掩码调用init_transform_and_finder()(source/fitz/stext-search.c),把“选项”拆解为两条独立的执行路径:

  1. 文本变换(text transform):决定 haystack(页面文本)与 needle(搜索串)在比对前被如何归一化;
  2. 匹配器(finder):决定使用精确匹配还是正则匹配引擎。

精确匹配 vs 正则匹配:exact 与 regexp

exact:默认的逐字符匹配

FZ_SEARCH_EXACT的值为0,即不设置任何额外标志位时,搜索串被当作普通文本进行完全匹配。匹配器是simple_finder(source/fitz/stext-search.c),其核心match_exact()(source/fitz/stext-search.c)在 UTF-8 层面逐字符比对,且要求匹配结束后不能剩余“Marking, Nonspacing”类字符,避免命中一个不完整的组合字符序列。

regexp:JS 风格正则引擎

设置regexp后,匹配器切换为regexp_finder(source/fitz/stext-search.c)。搜索串在初始化阶段通过fz_regcomp()编译(带REG_NEWLINE标志,\n参与^/$锚点匹配),匹配过程由fz_regexec()驱动,并支持REG_NOTBOL(非行首)、REG_RUNAWAY等标志(source/fitz/stext-search.c)。注意正则引擎的语法遵循 MuPDF 自带的 JS 风格正则实现(见 source/fitz/regexp.c)。

一个典型的组合用法:regexp,keep-paragraphs可以让你写出跨段落的模式,例如匹配“以任意内容结尾的章节标题”。

归一化魔法:ignore-case 与 ignore-diacritics 的底层变换

这两个选项并非简单地“改大小写/删符号”,而是通过一组精心设计的 Unicode 变换标志组合实现的。init_transform_and_finder()依据选项组合选择对应的变换集(source/fitz/stext-search.c):

  • 默认(exact):FZ_TEXT_TRANSFORM__NORMAL= 兼容分解(NFKD)+ 连字符归一化 + 全角 ASCII 映射 + 组合(compose)。这意味着即使页面中同一字符存在不同的编码形式(例如组合序列e+ 重音符号 vs 预组合字符é),也能被当作同一个字符匹配。
  • ignore-case:在 NORMAL 基础上追加FZ_TEXT_TRANSFORM_UPPERCASE,即先做 Unicode 兼容分解、再统一转大写后比对,从而忽略大小写差异(source/fitz/stext-search.c)。
  • ignore-diacritics:追加FZ_TEXT_TRANSFORM_STRIP_MARKING_NONSPACING,即把“Marking, Nonspacing”(UCDN_GENERAL_CATEGORY_MN)类字符从文本中剥离后再比对(source/fitz/stext-search.c),因此ä与a、é与e视为相同。
  • ignore-case,ignore-diacritics同时开启:两套变换叠加(先剥离变音符、再统一大写),形成最宽松的匹配模式。

变换过程在do_transform()/transform_char()(source/fitz/stext-search.c)中执行,包含组合缓存(最多缓存 32 个字符)、按 Unicode 组合类(combining class)冒泡排序、连续空格压缩等细节。页面上任意字符都会经过同一套变换,因此命中结果能够通过索引数组精确映射回原始文本中的位置(fz_stext_position),供上层高亮定位使用。

断行与段落:keep-lines 与 keep-paragraphs

MuPDF 搜索的默认行为是:把换行、回车、制表符、行分隔符、段落分隔符以及不换行空格统一归一化为普通空格,并将连续多个空格压缩为单个空格(source/fitz/stext-search.c)。这保证了一个在 PDF 中因排版被拆成多行的词或短语,仍能作为连续文本被找到。

但当你希望正则表达式能够跨越行边界(例如匹配“以句号结尾的一行”),就需要保留这些分隔符:

  • keep-lines:行分隔符被保留为单个\n(源码注释明确说明“mainly for use with regexps”)。
  • keep-paragraphs:段落分隔符被保留为单个\n,但注意与keep-lines组合时,段落边界呈现为\n\n(行间\n、段间\n\n)。

对应的变换标志为FZ_TEXT_TRANSFORM_KEEP_LINES = 256与FZ_TEXT_TRANSFORM_KEEP_PARAGRAPHS = 512(source/fitz/stext-search.c)。在transform_char()中,只有设置了这些标志时\n才不会被canon()规范化为空格(source/fitz/stext-search.c)。

实战示例:匹配文档中任意跨两行的连续短语

regexp,keep-lines

模式:phrase\s+continues(\s可以命中保留的\n)。

连字符断词:keep-hyphens 的默认合并行为

纸质排版中常见“单词在行尾被连字符拆开”的情况(如hyphen-+ation)。MuPDF 的默认处理是:将行尾连字符去掉,并把断开的单词合并,从而让你能直接搜到完整的hyphenation一词。

keep-hyphens则禁用这一行为,让连字符按原样参与匹配。在底层,默认变换集里的FZ_TEXT_TRANSFORM_NORMALIZE_HYPHENS会把所有 Unicode 连字符等价物(通过fz_is_unicode_hyphen()判定,如-、‐、‑等)统一归一化为-(source/fitz/stext-search.c),而FZ_TEXT_TRANSFORM_KEEP_HYPHENS = 1024会阻止这一合并处理(source/fitz/stext-search.c)。

在真实工具中使用:mutool grep

搜索选项并非只能在 C API 中使用——命令行工具mutool grep通过-S参数直接接收搜索选项字符串。命令形式为(docs/tools/mutool-grep.md):

mutool grep [options] pattern file [ file2 ...]

常用选项速览:

参数说明
pattern file...要搜索的模式(正则或固定串)与目标文档
-F将 pattern 视为固定字符串(等价于关闭regexp)
-a忽略变音符(等价于ignore-diacritics)
-i忽略大小写(等价于ignore-case)
-S search-options直接传入本文所述的搜索选项字符串
-O stext-options结构化文本提取选项(见 docs/reference/common/stext-options.md)
-b从文档末尾向前搜索
-n/-H打印页码 / 文件名
-[ mark/-] mark自定义命中内容的前后标记
-p password加密文档密码

注意默认行为:mugrep.c中,若未指定-F,则工具会自动为options加上FZ_SEARCH_REGEXP | FZ_SEARCH_KEEP_PARAGRAPHS(source/tools/mugrep.c),也就是说 mutool grep 默认以正则模式工作、且保留段落边界。

实用命令示例:

# 跨段落正则搜索(默认行为已开启 regexp 与 keep-paragraphs) mutool grep 'chapter \d+\n\n' document.pdf # 固定字符串、忽略大小写与变音符 mutool grep -F -a -i 'naive' document.pdf # 精确组合:保留行边界以便跨行匹配 mutool grep -F -S 'keep-lines' 'state-of-the-art' document.pdf

与选项字符串等价的短参数:-i等价于-S ignore-case,-a等价于-S ignore-diacritics,二者可按需混用。解析入口见 source/tools/mugrep.c 中的fz_getopt循环与fz_parse_search_options()调用。

C API 中的使用方式

对于 C/C++ 开发者,可直接使用fz_match_stext_page()系列接口并传入选项位掩码(include/mupdf/fitz/structured-text.h):

fz_search_options options = FZ_SEARCH_IGNORE_CASE | FZ_SEARCH_IGNORE_DIACRITICS; int hit_mark[MAX_HITS]; fz_quad hit_bbox[MAX_HITS]; int n = fz_match_stext_page(ctx, page, "cafe", hit_mark, hit_bbox, MAX_HITS, options);

若希望从字符串形式解析,则调用:

fz_search_options options; fz_parse_search_options(ctx, &options, "ignore-case,regexp,keep-paragraphs");

此外,fz_match_page()、fz_match_display_list()以及带 chapter 页码的fz_match_chapter_page_number()等封装接口(source/fitz/util.c)均接受同一套fz_search_options,因此在整篇文档、单页、display list 等不同层级的搜索中,选项语义完全一致。

选项组合速查表

需求推荐组合
默认精确搜索(不设任何选项,即exact)
不区分大小写ignore-case
忽略重音/变音符(国际化检索)ignore-diacritics
宽松匹配(大小写 + 变音符都不敏感)ignore-case,ignore-diacritics
正则搜索regexp
正则 + 跨行匹配regexp,keep-lines
正则 + 跨段落匹配regexp,keep-paragraphs
跨行且跨段落(行\n、段\n\n)regexp,keep-lines,keep-paragraphs
不合并行尾连字符断词keep-hyphens

适用边界与注意事项

  • 选项字符串语法三选一即可:逗号分隔、?开头的 URL 查询串、JSON 子集,切勿混用(例如?a,b这种写法属于非法输入)。
  • exact与regexp互斥:二者是同一维度上的两种匹配模式;同时传入时以regexp生效(源码中exact的位值为 0,不会覆盖regexp位)。
  • 大小写/变音符归一化以 Unicode 标准为基础:变换基于 Unicode 分解与组合(TR15)与通用类别判定(ucdn数据,见 source/fitz/ucdn.c),对拉丁字母、西欧重音字符效果显著;对某些不适用 Unicode 归一化的书写系统需自行验证。
  • keep-*系列主要面向正则场景:源码注释明确指出保留行/段落边界“mainly for use with regexps”;若同时开启精确匹配与keep-lines,换行会保留在比对文本中,可能使原本跨行的短语因含\n而无法命中。
  • 性能提示:正则模式需要对每个页面编译并执行引擎匹配(fz_regcomp/fz_regexec),在超大文档上比精确匹配更重;可通过fz_cookie中断长时间搜索(fz_match_stext_page_with_cookie)。

总结

MuPDF 的搜索选项用一份简洁的选项字符串,覆盖了从“逐字精确匹配”到“忽略大小写与变音符的 Unicode 归一化匹配”,再到“正则跨行/跨段匹配”的完整检索需求。理解exact / regexp / ignore-case / ignore-diacritics / keep-lines / keep-paragraphs / keep-hyphens这七个选项的位掩码语义与底层变换管线(source/fitz/stext-search.c),你就能在 C API 与mutool grep中灵活组合出适合自己文档形态的检索方案。

  • 图形学
  • 图像处理

【免费下载链接】mupdf

mupdf mirror

项目地址:https://gitcode.com/gh_mirrors/mu/mupdf
点击查看免费下载

相关推荐

上一篇:MyTinySTL中的排序算法:从冒泡到内省的进化之路
下一篇:如何在Bash中高效搜索文件内容:ag与rg命令终极指南

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

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

规格驱动开发实战:用Codex从Spec到全栈应用的完整流程

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/10/5 1:44:49

终极游戏存档守护指南:用Ludusavi让游戏进度永不丢失

终极游戏存档守护指南:用Ludusavi让游戏进度永不丢失 【免费下载链接】ludusavi Backup tool for PC game saves 项目地址: https://gitcode.com/GitHub_Trending/lu/ludusavi 作为一名游戏玩家,你是否曾因电脑重装、游戏崩溃或存档损坏而失去宝贵…

作者头像 李华
网站建设 2026/10/5 1:37:53

硬件工程师成长之路:通过拆解优秀产品逆向学习电路设计

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华