PDF Oxide 快速上手(Scala)
PDF Oxide 是 JVM 上最快的 PDF 库,内置文本提取功能——在 3,830 个 PDF 上平均 0.8ms、100% 通过率。Scala 3 绑定是在成熟的 Java 绑定之上做的一层轻量、符合 Scala 习惯的封装:它不引入任何原生代码,只是叠加了一组 Scala 扩展方法,把 java.util.Optional[T] 变成 Option[T]、把 java.util.List[T] 变成 Seq[T]。AutoCloseable 句柄可以直接配合 scala.util.Using 使用。
安装
在 build.sbt 中添加依赖:
libraryDependencies += "fyi.oxide" % "pdf-oxide" % "0.3.69"
Scala 封装依赖于 fyi.oxide:pdf-oxide 这个 Java 绑定,后者负责管理唯一的 JNI 原生桥接。需要 Scala 3.3 及以上版本。
快速上手
先从 Markdown 构建一个 PDF,再打开它并把文本提取出来。Using.resource 会自动帮你关闭每个句柄。
import fyi.oxide.pdf.{Pdf, PdfDocument, producerOption}
import scala.util.Using
Using.resource(Pdf.fromMarkdown("# Hello pdf_oxide\n\nThis is a **Scala** binding.\n")): pdf =>
Using.resource(PdfDocument.open(pdf.save())): doc =>
println(s"pages: ${doc.pageCount()}")
println(s"producer: ${doc.producerOption.getOrElse("(none)")}")
println(doc.extractText(0))
Pdf.fromMarkdown 返回一个 Pdf 句柄;pdf.save() 把它序列化为 Array[Byte]。PdfDocument.open 接收这些字节并暴露文档 API。
文本提取
纯文本
通过从零开始的页码索引,从任意页面提取纯文本。
import fyi.oxide.pdf.PdfDocument
import scala.util.Using
Using.resource(PdfDocument.open(pdfBytes)): doc =>
assert(doc.isOpen)
val text = doc.extractText(0)
println(text)
Markdown 和 HTML
只需一次调用即可把整个文档转换为 Markdown 或 HTML。
import fyi.oxide.pdf.PdfDocument
import scala.util.Using
Using.resource(PdfDocument.open(pdfBytes)): doc =>
println(doc.toMarkdown())
println(doc.toHtml())
页面元素
doc.page(i) 返回一个 PdfPage。封装通过 *Seq 扩展方法把每种元素提取器暴露为 Scala 的 Seq:wordsSeq、linesSeq、charsSeq、tablesSeq、imagesSeq 和 annotationsSeq。每个 TextWord 都携带自身的 text 和一个 bbox。
import fyi.oxide.pdf.{PdfDocument, wordsSeq, linesSeq, charsSeq, tablesSeq, imagesSeq, annotationsSeq}
import scala.util.Using
Using.resource(PdfDocument.open(pdfBytes)): doc =>
val page = doc.page(0)
println(s"size: ${page.width()} x ${page.height()}")
page.wordsSeq.take(8).foreach { w =>
println(s" ${w.text} @ ${w.bbox} (w=${w.bbox.width})")
}
println(s"lines: ${page.linesSeq.size}")
println(s"chars: ${page.charsSeq.size}")
println(s"tables: ${page.tablesSeq.size}")
println(s"images: ${page.imagesSeq.size}")
println(s"annotations: ${page.annotationsSeq.size}")
你也可以用 doc.pagesSeq 把每一页当作 Seq 来遍历(它的大小与 doc.pageCount() 一致)。
import fyi.oxide.pdf.{PdfDocument, pagesSeq, wordsSeq}
import scala.util.Using
Using.resource(PdfDocument.open(pdfBytes)): doc =>
doc.pagesSeq.zipWithIndex.foreach { (page, i) =>
println(s"page $i: ${page.wordsSeq.size} words")
}
搜索
doc.searchSeq(query) 返回一个 Seq[SearchMatch]。每个匹配项都暴露其 text。
import fyi.oxide.pdf.{PdfDocument, searchSeq}
import scala.util.Using
Using.resource(PdfDocument.open(pdfBytes)): doc =>
val matches = doc.searchSeq("Hello")
println(s"${matches.size} match(es)")
matches.foreach(m => println(s" ${m.text}"))
以 Option 表示的元数据
可空的文档元数据通过 producerOption 和 creatorOption 以 Option[String] 的形式呈现,让你能以 Scala 的方式处理缺失值。
import fyi.oxide.pdf.{PdfDocument, producerOption, creatorOption}
import scala.util.Using
Using.resource(PdfDocument.open(pdfBytes)): doc =>
println(doc.producerOption.getOrElse("(unknown producer)"))
println(doc.creatorOption.getOrElse("(unknown creator)"))
// 表单字段同样以 Seq 返回:
println(s"form fields: ${doc.formFieldsSeq.size}")
渲染
doc.render(i) 把一个页面光栅化,并返回编码后的图像字节。
import fyi.oxide.pdf.PdfDocument
import scala.util.Using
Using.resource(PdfDocument.open(pdfBytes)): doc =>
val png = doc.render(0)
java.nio.file.Files.write(java.nio.file.Path.of("page-0.png"), png)
自动提取
AutoExtractor.of(doc).extractDocument() 返回一个 AutoResult,其中包含提取出的 text、可选的 markdown/html 渲染结果,以及仍需 OCR 的页面列表——这一切都通过封装以符合 Scala 习惯的方式暴露出来(markdownOption、htmlOption、pagesNeedingOcrSeq)。
import fyi.oxide.pdf.{PdfDocument, AutoExtractor, markdownOption, htmlOption, pagesNeedingOcrSeq}
import scala.util.Using
Using.resource(PdfDocument.open(pdfBytes)): doc =>
val result = AutoExtractor.of(doc).extractDocument()
println(result.text)
result.markdownOption.foreach(println)
result.htmlOption.foreach(println)
println(s"pages needing OCR: ${result.pagesNeedingOcrSeq}")
编辑
DocumentEditor.open 打开一个已有的 PDF 以进行结构性编辑。这里我们清除元数据,并把结果重新序列化为字节。
import fyi.oxide.pdf.DocumentEditor
import scala.util.Using
Using.resource(DocumentEditor.open(pdfBytes)): editor =>
assert(editor.isOpen)
editor.scrubMetadata()
val cleaned: Array[Byte] = editor.save()
java.nio.file.Files.write(java.nio.file.Path.of("scrubbed.pdf"), cleaned)
后续步骤
- Rust 快速上手 —— 在 Rust 中使用 PDF Oxide
- Python 快速上手 —— 在 Python 中使用 PDF Oxide
- 文本提取 —— 详细的提取选项与实用范例
- PDF 创建 —— 进阶创建、加密与元数据
- 编辑 —— 修改已有 PDF、注释与表单字段