【免费下载链接】bookokrat
A terminal EPUB / PDF Book Reader (+djvu)
Bookokrat 是一个用 Rust 编写的终端 EPUB / PDF 阅读器,它的EPUB 渲染管线把每个章节的 HTML 依次转换为Markdown AST,最终渲染成终端里的Span(文字片段)上屏。这条链路解耦了"解析"与"展示",是理解 Bookokrat 代码架构的关键。本文带你从源码角度走完全程:原始 HTML 如何一步步变成你看到的一行行终端文字。
📖 项目概览:Bookokrat 是什么
Bookokrat 是一个专注速度、平滑导航与 Vim 风格工作流的终端读书应用,支持 EPUB、PDF、DJVU 三种格式。其阅读区采用"左侧书库/目录 + 右侧正文"的分栏布局,并支持搜索、书签、内联批注、图片渲染、主题切换与禅模式(Zen Mode)。
对新手来说,最需要先建立一个认知:EPUB 章节本质上是 HTML 文件。Bookokrat 没有直接"画"这些 HTML,而是把它们翻译成一套结构化的中间表示(Markdown AST),再由不同的消费者按各自需要输出。下面就来拆解这条管线。
🔄 渲染管线总览:四步链路
整条 EPUB 渲染管线可以概括为四步,核心模块路径如下:
EPUB 章节原始 HTML │ html5ever 解析为 DOM ▼ Markdown AST (Document / Block / Inline) ├─► [分支 A] MarkdownRenderer → Markdown 字符串 (extract 导出 / 标题提取) └─► [分支 B] MarkdownTextReader → ratatui Span (TUI 正文上屏)| 阶段 | 作用 | 核心模块 |
|---|---|---|
| 入口 | 打开 EPUB、逐章读取原始 HTML | src/epub_analysis.rs |
| 解析 | HTML → Markdown AST | src/parsing/html_to_markdown.rs |
| AST 定义 | Document / Block / Inline 数据结构 | src/markdown.rs |
| 分支 A | AST → Markdown 字符串 | src/parsing/markdown_renderer.rs |
| 分支 B | AST → 终端 Span | src/widget/text_reader/rendering.rs |
解析模块的入口在 src/parsing/mod.rs,它只导出三个子模块:html_to_markdown、markdown_renderer与toc_parser(目录解析)。
① 入口:从 EPUB 章节到原始 HTML
打开一本 EPUB 时,analyze_epub() 会一次性遍历全书所有章节,完成"单遍分析":
- 通过
epub库的EpubDoc定位当前章节,用doc.get_current_str()取出该章的原始 HTML; - 用 extract_chapter_title() 从 HTML 里提取章节标题(优先
h1/h2/h3,其次<title>); - 把 HTML 交给
HtmlToMarkdownConverter::convert()转成 AST,并顺带生成用于全文搜索的SearchLine列表、锚点映射等衍生数据。
这样做的好处是:解析只做一次,搜索、进度统计、锚点跳转全部复用同一份 AST,避免重复解析。
② HTML → Markdown AST(管线核心)
这一步由 HtmlToMarkdownConverter 完成,是整个管线最重的部分。
1. 预处理:修复"脏" HTML
很多 EPUB 源自 XHTML,会写出<p/>、<title/>这类自闭合标签,而 html5ever 按 HTML5 解析会把它们误判。因此在解析前先跑一遍 fix_html_for_parser(),把非空元素的自闭合标签改写成成对标签,并修复被换行打断的结束标签(如</\nmrow>)。
2. 解析成 DOM 并遍历
预处理后的 HTML 交给html5ever::parse_document()得到一棵 DOM 树。随后 convert() 从根节点开始递归遍历,visit_element() 按标签名分发处理:
| HTML 标签 | 转换结果(Block 类型) |
|---|---|
h1~h6 | Heading { level, content } |
p/div/span | Paragraph(保留 id 作为锚点) |
pre | CodeBlock { language, content } |
blockquote | Quote |
ul/ol | List(区分有序/无序、嵌套) |
table | Table(先做布局表检测) |
dl | DefinitionList |
hr | ThematicBreak |
math | MathML → ASCII 代码块 |
img | 内联Image(占位,供图片加载) |
3. 结构化的 Block / Inline
AST 的骨架定义在 src/markdown.rs 中:Block 枚举 描述块级元素(标题、段落、代码块、引用、列表、表格等),每个块携带一个id(来自 HTML 的id属性,用于锚点跳转)。
块内部是Text,它由一串 TextOrInline 组成——既可是带样式的 TextNode(样式枚举 Style:Code/Emphasis/Strong/Strikethrough),也可是 Inline 元素(链接、图片、锚点、换行)。
4. 两个值得一提的"聪明"设计
- 布局表 vs 数据表:不少书用
<table>来做页面排版(对齐、留白),而非展示数据。is_layout_table() 用一组启发式规则(单格表、空单元格 >50%、含占位图、每行内容格 ≤1 等)识别布局表,把它"拍平"成普通段落,避免终端里出现一堆无意义的表格线。 - MathML → ASCII:内嵌数学公式无法在纯终端里渲染,mathml_to_ascii() 把它转成 ASCII/Unicode 近似表达;上下标
<sub>/<sup>也优先转成 Unicode 下/上标,失败则回退为_{…}/^{…}。
下图就是这条 HTML→AST 管线跑完后的真实上屏效果:章节标题、内嵌图片、表格、彩色超链接与脚注引用都被完整还原。
③ 分支 A:Markdown AST → Markdown 字符串
MarkdownRenderer 负责把 AST 反向渲染成规范的 Markdown 文本。它的 render() 逐块遍历,输出#标题、**加粗**、_斜体_、`代码`、~~删除线~~、带列宽对齐的表格、缩进列表与代码围栏。
这段能力有两个消费方:
extract导出命令:src/extract.rs 把整本书的每章渲染成.md文件并导出,图片引用会被改写成本地相对路径,方便你把 EPUB 转成纯 Markdown 资料库;- 标题/摘要提取:在批注、锚点迁移等场景里,用
render_text()从 AST 快速取出一段可读文本。
它的设计哲学是"只做 AST→字符串,不做任何清理"——清理逻辑早已在前面的转换阶段完成,保持渲染器简单纯粹。
④ 分支 B:Markdown AST → 终端 Span(TUI 上屏)
这是读者真正看到的支路。MarkdownTextReader::render_document_to_lines() 遍历Document.blocks,把每个Block渲染成 ratatui 的Line与Span<'static>(带样式、颜色的文字片段),产出RenderedContent。
几个关键机制:
- 主题配色:所有
Span的颜色取自 Base16Palette,换主题只需换一套调色板,AST 本身不变; - 前缀框架 PrefixFrame:列表符号、引用
>、续行缩进统一由 PrefixFrame 管理,按"首行 / 续行 / 每行"分别拼接,保证嵌套列表与引用缩进整齐; - 上下文区分:RenderContext 区分
TopLevel与InsideContainer,让表格单元格、列表项内的段落用不同间距规则渲染; - 交互信息追踪:渲染的同时收集锚点位置、可点击链接与图片占位,供点击跳转、图片加载、文字选区等功能复用。
下面是禅模式(Zen Mode)下的最终上屏效果:单栏、无边框,标题、斜体强调、彩色脚注引用与插图都由这套 Span 渲染管线绘制。
⚡ 为什么用 Markdown AST 做中间层
这条管线最大的价值在于"一次解析,多处消费":
- 解耦:解析逻辑只关心"HTML 长什么样",展示逻辑只关心"AST 长什么样",二者可独立演进与测试;
- 复用:同一份 AST 同时服务 TUI 上屏、全文搜索、目录跳转、批注定位、
extract导出,避免重复解析; - 可测:AST 是纯数据结构,便于用快照测试断言,降低终端 UI 的测试成本。
🔧 关键源码速查表
| 你要看什么 | 文件 | 关键入口 |
|---|---|---|
| 全书单遍分析 | src/epub_analysis.rs | analyze_epub() |
| HTML→AST 总转换 | src/parsing/html_to_markdown.rs | convert() |
| 脏 HTML 预处理 | src/parsing/html_to_markdown.rs | fix_html_for_parser() |
| 标签分发遍历 | src/parsing/html_to_markdown.rs | visit_element() |
| Block / Inline 结构 | src/markdown.rs | Block/Inline |
| AST→Markdown 字符串 | src/parsing/markdown_renderer.rs | render() |
| AST→终端 Span | src/widget/text_reader/rendering.rs | render_document_to_lines() |
| MathML→ASCII | src/components/mathml_renderer.rs | mathml_to_ascii() |
结语
Bookokrat 的 EPUB 渲染管线是一条清晰的单向链:原始 HTML →(html5ever)DOM → Markdown AST →(字符串 / 终端 Span)。它用 Markdown AST 作为中枢,既让终端里的阅读体验(标题、表格、链接、公式、图片)尽量还原,又让搜索、导出、批注等功能共享同一份解析结果。想动手改造阅读器的渲染细节,从 src/parsing/html_to_markdown.rs 与 src/widget/text_reader/rendering.rs 这两个文件入手,是最快的路径。
【免费下载链接】bookokrat
A terminal EPUB / PDF Book Reader (+djvu)
相关推荐
如何在电脑上玩起 Switch 游戏:yuzu 模拟器从安装到调参的完整流程
如何在电脑上玩起 Switch 游戏:yuzu 模拟器从安装到调参的完整流程 周末想把《旷野之息》从吃灰状态拉回桌面,Switch 模拟器 yuzu 是最主流的
虚拟化图形学逆向工程Mistune渲染器完全指南:从HTML到Markdown再到RST的全面转换
Mistune渲染器完全指南:从HTML到Markdown再到RST的全面转换 Mistune是一个快速且功能强大的Python Markdown解析器,它提供
开发工具微信公众号为什么没法直接订阅?用 wewe-rss 十分钟把第一篇公众号文章推进阅读器
微信公众号为什么没法直接订阅?用 wewe rss 十分钟把第一篇公众号文章推进阅读器 wewe rss 是一个私有化部署的微信公众号转 RSS 订阅源工具:它
后端前端
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考