Primeiros passos com o PDF Oxide (Scala)
O PDF Oxide é a biblioteca de PDF mais rápida para a JVM com extração de texto embutida — 0,8ms de média e 100% de aprovação em 3.830 PDFs. O binding para Scala 3 é uma fachada fina e idiomática sobre o consolidado binding Java: não adiciona nenhum código nativo e acrescenta métodos de extensão Scala que transformam java.util.Optional[T] em Option[T] e java.util.List[T] em Seq[T]. Os handles AutoCloseable funcionam diretamente com scala.util.Using.
Instalação
Adicione a dependência ao seu build.sbt:
libraryDependencies += "fyi.oxide" % "pdf-oxide" % "0.3.69"
A fachada Scala depende do binding Java fyi.oxide:pdf-oxide, responsável pela única ponte nativa JNI. É necessário Scala 3.3 ou superior.
Guia Rápido
Crie um PDF a partir de Markdown, depois abra-o e extraia o texto de volta. O Using.resource fecha cada handle para você.
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 retorna um handle Pdf; pdf.save() o serializa em um Array[Byte]. PdfDocument.open aceita esses bytes e expõe a API do documento.
Extração de Texto
Texto Simples
Extraia texto simples de qualquer página pelo seu índice baseado em zero.
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 e HTML
Converta o documento inteiro para Markdown ou HTML em uma única chamada.
import fyi.oxide.pdf.PdfDocument
import scala.util.Using
Using.resource(PdfDocument.open(pdfBytes)): doc =>
println(doc.toMarkdown())
println(doc.toHtml())
Elementos da Página
doc.page(i) retorna um PdfPage. A fachada expõe cada extrator de elementos como um Seq Scala por meio dos métodos de extensão *Seq: wordsSeq, linesSeq, charsSeq, tablesSeq, imagesSeq e annotationsSeq. Cada TextWord carrega seu text e um 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}")
Você também pode percorrer todas as páginas como um Seq com doc.pagesSeq (cujo tamanho corresponde a 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")
}
Busca
doc.searchSeq(query) retorna um Seq[SearchMatch]. Cada correspondência expõe seu 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}"))
Metadados como Option
Metadados anuláveis do documento aparecem como Option[String] por meio de producerOption e creatorOption, para que você trate os valores ausentes do jeito 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)"))
// Os campos de formulário também voltam como um Seq:
println(s"form fields: ${doc.formFieldsSeq.size}")
Renderização
doc.render(i) rasteriza uma página e retorna os bytes da imagem codificada.
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)
Extração Automática
AutoExtractor.of(doc).extractDocument() retorna um AutoResult com o text extraído, renderizações opcionais em markdown/html e a lista de páginas que ainda precisam de OCR — tudo exposto de forma idiomática pela fachada (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}")
Edição
DocumentEditor.open abre um PDF existente para edições estruturais. Aqui limpamos os metadados e serializamos o resultado de volta para bytes.
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)
Próximos Passos
- Primeiros passos com Rust – usando o PDF Oxide a partir do Rust
- Primeiros passos com Python – usando o PDF Oxide a partir do Python
- Extração de Texto – opções e receitas detalhadas de extração
- Criação de PDF – criação avançada, criptografia e metadados
- Edição – modificando PDFs existentes, anotações e campos de formulário