PDF Oxide 入门(Clojure)
PDF Oxide 是最快的、内置文本提取能力的 PDF 工具包——平均耗时 0.8ms,在 3,830 个 PDF 上达到 100% 通过率。Clojure 绑定是对成熟的 fyi.oxide:pdf-oxide Java 绑定的一层地道而轻量的封装,后者负责管理唯一的 JNI 原生桥接。它不引入任何原生代码:通过互操作调用 Java 类,并返回对 Clojure 友好的值(java.util.List → 向量,java.util.Optional → 值或 nil)。
安装
在 deps.edn 中添加 Java 绑定。Clojure 命名空间(pdf_oxide.core)位于你的源码树中,对其进行封装:
{:deps {fyi.oxide/pdf-oxide {:mvn/version "0.3.69"}}}
句柄类型(Pdf、PdfDocument、DocumentEditor)都实现了 AutoCloseable,因此可用 with-open 实现确定性的资源清理。
快速上手
从 Markdown 构建一个 PDF,再把它打开并提取文本。每一步都返回普通的 Clojure 值。
(require '[pdf-oxide.core :as pdf])
(with-open [p (pdf/from-markdown "# Hello\n\nbody\n")
d (pdf/open (pdf/save p))]
(println "pages: " (pdf/page-count d))
(println "producer:" (or (pdf/producer d) "(none)"))
(println (pdf/extract-text d 0)))
打开 PDF
pdf/open 接受字节数组或文件系统路径字符串,并可为加密文档提供可选的密码。
(require '[pdf-oxide.core :as pdf])
;; 从路径打开
(with-open [d (pdf/open "research-paper.pdf")]
(println "pages:" (pdf/page-count d)))
;; 从字节打开(例如从 S3 或 HTTP 下载得到)
(with-open [d (pdf/open pdf-bytes)]
(println (pdf/extract-text d 0)))
;; 加密文档
(with-open [d (pdf/open "confidential.pdf" "secret")]
(println (pdf/extract-text d 0)))
你也可以在打开之后再进行身份验证:
(with-open [d (pdf/open "confidential.pdf")]
(when (pdf/authenticate d "secret")
(println (pdf/extract-text d 0))))
文本提取
通过从零开始的页索引,从任意页面提取纯文本。
(require '[pdf-oxide.core :as pdf])
(with-open [d (pdf/open "report.pdf")]
;; 单个页面
(println (pdf/extract-text d 0))
;; 所有页面
(doseq [i (range (pdf/page-count d))]
(println "--- Page" (inc i) "---")
(println (pdf/extract-text d i))))
页面元素
pdf/page 返回一个 PdfPage。你可以从中获取单词、行、字符、表格、图片和注释——每一种都是一个 Clojure 向量。单词、行、字符对象通过互操作暴露 .text 和 .bbox。
(require '[pdf-oxide.core :as pdf])
(with-open [d (pdf/open "paper.pdf")]
(let [pg (pdf/page d 0)]
(println "page width:" (.width pg))
;; 带有边界框的单词
(doseq [w (take 8 (pdf/words pg))]
(println " " (.text w) "@" (.bbox w)))
;; 其他元素向量
(println "lines: " (count (pdf/lines pg)))
(println "chars: " (count (pdf/chars pg)))
(println "tables: " (count (pdf/tables pg)))
(println "images: " (count (pdf/images pg)))
(println "annotations:" (count (pdf/annotations pg)))
;; 整页纯文本,或裁剪到某个区域(BBox)
(println (pdf/page-text pg))))
若要将提取限定在某个区域内,传入一个 fyi.oxide.pdf.geometry.BBox:
(import '[fyi.oxide.pdf.geometry BBox])
(with-open [d (pdf/open "paper.pdf")]
(let [pg (pdf/page d 0)]
(println (pdf/page-text pg (BBox. 0.0 0.0 1000.0 1000.0)))))
Markdown 与 HTML 转换
将整篇文档或单个页面转换为 Markdown 或 HTML。
(require '[pdf-oxide.core :as pdf])
(with-open [d (pdf/open "paper.pdf")]
;; 整篇文档
(println (pdf/to-markdown d))
(println (pdf/to-html d))
;; 单个页面(从零开始)
(println (pdf/to-markdown d 0))
(println (pdf/to-html d 0)))
如需更丰富的结构,pdf/extract-structured 会返回某个页面的结构化元素树:
(with-open [d (pdf/open "paper.pdf")]
(println (pdf/extract-structured d 0)))
搜索
pdf/search 扫描整篇文档,返回一个匹配对象的向量。每个匹配项通过互操作暴露 .text。
(require '[pdf-oxide.core :as pdf])
(with-open [d (pdf/open "manual.pdf")]
(doseq [m (pdf/search d "configuration")]
(println (.text m))))
渲染
将页面渲染为 PNG 字节数组,并可指定 DPI。
(require '[pdf-oxide.core :as pdf]
'[clojure.java.io :as io])
(with-open [d (pdf/open "paper.pdf")]
;; 默认 DPI
(io/copy (pdf/render d 0) (io/file "page-0.png"))
;; 显式指定 DPI
(io/copy (pdf/render d 0 150) (io/file "page-0@150.png")))
创建
Pdf 类型提供了一组工厂函数。pdf/save 将构建好的 Pdf 序列化为字节数组。
(require '[pdf-oxide.core :as pdf]
'[clojure.java.io :as io])
;; 从 Markdown 创建
(with-open [p (pdf/from-markdown "# Hello World\n\nThis is a PDF.")]
(io/copy (pdf/save p) (io/file "output.pdf")))
;; 从 HTML 创建
(with-open [p (pdf/from-html "<h1>Invoice</h1><p>Amount: $42</p>")]
(io/copy (pdf/save p) (io/file "invoice.pdf")))
编辑与涂黑
pdf/editor 打开一个 DocumentEditor(从字节数组或路径),用于结构性编辑。清除元数据、标记需涂黑的区域并对其执行不可逆的涂黑,然后用 pdf/editor-save 序列化。
(require '[pdf-oxide.core :as pdf]
'[clojure.java.io :as io])
(import '[fyi.oxide.pdf.geometry BBox])
(with-open [ed (pdf/editor "form.pdf")]
(pdf/scrub-metadata ed)
(pdf/add-redaction ed 0 (BBox. 10.0 10.0 50.0 20.0))
(pdf/apply-redactions ed)
(io/copy (pdf/editor-save ed) (io/file "redacted.pdf")))
元数据与生命周期
pdf/producer 和 pdf/creator 以值的形式返回文档元数据,缺失时返回 nil(java.util.Optional 已为你解包)。优先使用 with-open;pdf/close 和 pdf/open? 是用于手动管理生命周期的应急手段。
(require '[pdf-oxide.core :as pdf])
(let [d (pdf/open "paper.pdf")]
(println "open? " (pdf/open? d))
(println "producer:" (or (pdf/producer d) "(none)"))
(println "creator: " (or (pdf/creator d) "(none)"))
(pdf/close d)
(println "open? " (pdf/open? d)))