Skip to content

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"}}}

句柄类型(PdfPdfDocumentDocumentEditor)都实现了 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/producerpdf/creator 以值的形式返回文档元数据,缺失时返回 niljava.util.Optional 已为你解包)。优先使用 with-openpdf/closepdf/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)))

后续步骤