背景
MinerU 解决了什么
MinerU 是一个开源的文档提取工具,能把论文、报告、合同、扫描件等 PDF / 图片,解析成结构化的、可编辑的产物。它把一份文档"拆"成:
full.md—— 按语义块排版的 Markdown(标题、段落、表格、公式、图片占位…)- 源文件 —— 原始 PDF 或切分后的页面图片
layout.json/middle.json—— 版面与中间结果,记录了每个语义块在源文件里的物理坐标(bbox)
也就是说,MinerU 已经帮你做好了"哪段文字来自源文件哪一页哪块区域"的对应关系。
来由:为什么需要一个 Viewer
MinerU 产出的只是一个 zip 压缩包。这个 zip 是"数据",不是"界面"。要把它"用起来",下游团队通常要自己写:
- 一个 PDF / 图片预览外壳(翻页、缩放、滚动)
- 一个 Markdown 渲染区
- 一套"左栏点一段 → 右栏跳到对应区域并高亮"的联动逻辑
- 一套"业务想给某个块打高亮"(问答引用、审查规则命中、标注)的注入机制
每一家用一遍,重复造一遍轮子;而且块级溯源(block-level traceability)这件事,做了才知道坑有多深:一对多映射、图片块、表格块、坐标单位换算……
MinerU Viewer 就是为补上这个断层而生的:它消费 MinerU 的 zip 产物,直接提供一个可嵌入的、带块级溯源的文档查看器组件,让任意前端应用像嵌入一个 <img> 一样嵌入"文档溯源能力"。
WARNING
当前仅支持 PDF 与图片两种源文件。 MinerU 在 PDF 与图片这两种模式下才会返回符合预期的结构与版面坐标(bbox);其他格式(如纯文本、Office 文档)暂不保证 blockId 映射准确。
本项目适配的 MinerU 版本为 3.4.0(对应内置 v340 适配器)。适配新版本只需在 adapters/ 注册新实现,下游零改动。
本项目的定位
MinerU Viewer 把上面这一切封装成一个组件,让任意前端应用都能像嵌入一个 <img> 一样,嵌入一个"带块级溯源的文档查看器"。
核心契约是一个稳定的句柄 —— blockId:
- 它来自源文件(PDF/图片)上的一个"物理块",带 bbox(PDF 点数坐标
[x0, y0, x2, y1]),写在NormalizedDocument.pages[].blocks[].id - Markdown 渲染块通过映射持有
1..N个blockId(一对多,已验证) - 所有高亮、编辑、反馈、业务引用,最终都落到
blockId上
设计目标:UI 逻辑完全独立于宿主框架。同一套能力,提供 Vue 与 React 两种封装;每个包又都支持
npm引入与免构建script引入两种形态。
架构抽象
MinerU Viewer 在逻辑上分为若干彼此解耦的层,从上到下依次为:
| 层 | 职责 | 关键产出 |
|---|---|---|
| 接入层 MineruViewer | 统一入口。props(zip / version / highlights)+ emits(ready / block-click / highlight-activate / error)+ 命令式 API(setHighlights / addHighlight / focusBlock / loadZip)。Vue 用 defineComponent + expose,React 用 forwardRef + useImperativeHandle | 一个可挂载的查看器实例 |
| 视图层 MarkdownPane / SourcePane | 左栏渲染 full.md(marked → 真实表格),右栏用 pdf.js 渲染 PDF/图片并提供翻页、缩放、缩略图钉住(pin)、QQ 式自动收纳 | 两块可视区域 |
| 联动核心 blockId | 左→右:点击 MD 块滚动到源块并居中;右→左:点击源块高亮对应 MD 块。一切以 blockId 为锚点 | 双向溯源 |
| 适配层 adapters / v340 | getAdapter(version) 自动探测;zip → NormalizedDocument(meta + markdown + pages[].blocks[].bbox) | 结构化文档对象 |
| 加载层 loadZip(JSZip) | 解析 zip 为 File / ArrayBuffer / url,抽取 md / 源文件 / json | 喂给适配层 |
| 外部依赖 | pdfjs-dist(渲染 + worker)、marked(MD→HTML)、jszip(解压)。npm 形态由宿主解析;免构建形态由 vendor 提供 | —— |
设计要点:
- 版本适配对上层透明:当前内置
v340适配器;未来适配新版本只需在adapters/注册一个新实现,下游零改动。 blockId驱动双向联动:左→右(点击 MD 块滚动到源块并居中)、右→左(点击源块高亮对应 MD 块)。- 业务高亮并存:
addHighlight多组共存、以最新组为锚点;setHighlights互斥替换。
架构图
┌─────────────────────────────────────────────┐
│ MineruViewer │
│ props: zip / version / highlights / ... │
│ emits: ready / block-click / highlight-activate / error ...
│ imperative: setHighlights / addHighlight / focusBlock / ...
├───────────────┬─────────────────────────────┤
│ MarkdownPane │ SourcePane │
│ (左栏 MD 预览) │ (右栏 PDF/图片 + 缩略图) │
│ blockId ↔ token 映射 │ canvas 命中点 → blockId │
├───────────────┴─────────────────────────────┤
│ loadZip(JSZip) → adapters/v340 │
│ zip → NormalizedDocument (pages[].blocks[].bbox)
└─────────────────────────────────────────────┘
↑ pdfjs-dist (PDF 渲染 + worker)
↑ marked (MD → HTML,含真实表格)它不做什么
- 不解析文档 —— 解析是 MinerU 的事,本组件只消费其产物。
- 不带后端 —— 纯前端,zip 可以是
File/ArrayBuffer/url字符串。 - 不绑定存储 —— 高亮、标注结果由业务侧自己持久化,组件只负责渲染与联动。
- 不保证非 PDF / 非图片源文件的
blockId准确性 —— 见上方来由中的约束说明。