Skip to content

Начало работы с PDF Oxide (Elixir)

PDF Oxide — самый быстрый способ читать и создавать PDF из Elixir: в среднем 0.8 мс на извлечение текста и 100% успешных разборов на 3830 PDF. Это NIF поверх того же ядра на Rust, выполняющий нагруженную CPU работу на dirty CPU-планировщиках (ERL_NIF_DIRTY_JOB_CPU_BOUND), поэтому он никогда не блокирует планировщик BEAM.

Хэндлы Document и Pdf — это NIF-ресурсы, которые освобождаются сборщиком мусора. Функции, способные завершиться неудачей, возвращают {:ok, value} или {:error, code}, а индексы страниц начинаются с нуля.

Установка

Добавьте pdf_oxide в зависимости в mix.exs:

def deps do
  [
    {:pdf_oxide, "~> 0.3"}
  ]
end

Затем загрузите и скомпилируйте — NIF собирается через elixir_make на основе нативной cdylib:

mix deps.get
mix compile

Быстрый старт

Соберите PDF из Markdown, сериализуйте его в байты, затем откройте и снова извлеките текст.

{:ok, pdf}   = PdfOxide.from_markdown("# Hello pdf_oxide\n\nThis is an **Elixir** binding.\n")
{:ok, bytes} = PdfOxide.to_bytes(pdf)
{:ok, doc}   = PdfOxide.open_from_bytes(bytes)

{:ok, pages} = PdfOxide.page_count(doc)
IO.puts("pages: #{pages}")

%{major: maj, minor: min} = PdfOxide.version(doc)
IO.puts("version: #{maj}.#{min}")

{:ok, text} = PdfOxide.extract_text(doc, 0)
IO.puts(text)

Открытие PDF

Откройте PDF из файла по пути или напрямую из байтов в памяти (удобно при потоковой передаче из S3, по HTTP или из базы данных):

# Из файла
{:ok, doc} = PdfOxide.open("report.pdf")

# Из байтов, уже находящихся в памяти
{:ok, doc} = PdfOxide.open_from_bytes(pdf_bytes)

# Зашифрованные документы
{:ok, doc} = PdfOxide.open_with_password("confidential.pdf", "secret")

# Получение информации
{:ok, count} = PdfOxide.page_count(doc)
encrypted? = PdfOxide.encrypted?(doc)

Закройте документ явно, когда закончите (close/1 идемпотентна), либо предоставьте это сборщику мусора:

:ok = PdfOxide.close(doc)

Извлечение текста

Извлеките простой текст из отдельной страницы по её индексу (с нуля) или сразу из всего документа:

{:ok, doc} = PdfOxide.open("book.pdf")

# Одна страница
{:ok, text} = PdfOxide.extract_text(doc, 0)

# Простой текст, одна страница
{:ok, pt} = PdfOxide.to_plain_text(doc, 0)

# Все страницы, объединённые вместе
{:ok, all} = PdfOxide.to_plain_text_all(doc)
IO.puts(all)

Конвертация в Markdown и HTML

Преобразуйте страницу — или весь документ — в Markdown или HTML:

{:ok, doc} = PdfOxide.open("paper.pdf")

{:ok, md}    = PdfOxide.to_markdown(doc, 0)
{:ok, mdall} = PdfOxide.to_markdown_all(doc)

{:ok, html}    = PdfOxide.to_html(doc, 0)
{:ok, htmlall} = PdfOxide.to_html_all(doc)

Слова и строки

extract_words/2 возвращает структурированные структуры PdfOxide.Word с ограничивающим прямоугольником и флагом bold; extract_text_lines/2 группирует их в строки.

{:ok, doc} = PdfOxide.open("paper.pdf")

{:ok, words} = PdfOxide.extract_words(doc, 0)

for w <- Enum.take(words, 10) do
  %PdfOxide.Bbox{x: x, y: y, width: width} = w.bbox
  IO.puts("#{w.text} at (#{x}, #{y}) w=#{width} bold=#{w.bold}")
end

{:ok, lines} = PdfOxide.extract_text_lines(doc, 0)

for line <- lines do
  IO.puts("#{line.word_count} words: #{line.text}")
end

Поиск

Ищите по отдельной странице или по всему документу. Четвёртый аргумент — case_sensitive (учёт регистра). Каждый результат содержит text, page и PdfOxide.Bbox.

{:ok, doc} = PdfOxide.open("manual.pdf")

# Одна страница (индекс 0), без учёта регистра
{:ok, results} = PdfOxide.search(doc, 0, "configuration", false)

for r <- results do
  %PdfOxide.Bbox{x: x, y: y} = r.bbox
  IO.puts("page #{r.page}: '#{r.text}' at (#{x}, #{y})")
end

# Все страницы
{:ok, all} = PdfOxide.search_all(doc, "configuration", false)
IO.puts("#{length(all)} matches")

Создание PDF

Фабричные функции построителя возвращают хэндл Pdf, который вы сериализуете через to_bytes/1 или записываете прямо на диск через save/2:

{:ok, pdf} = PdfOxide.from_markdown("# Hello World\n\nThis is a PDF.")
:ok = PdfOxide.save(pdf, "output.pdf")

{:ok, pdf} = PdfOxide.from_html("<h1>Invoice</h1><p>Amount: $42</p>")
{:ok, bytes} = PdfOxide.to_bytes(pdf)

{:ok, pdf} = PdfOxide.from_text("Plain text content.")
:ok = PdfOxide.save(pdf, "notes.pdf")

Рендеринг страниц в изображения

С функцией рендеринга растеризуйте страницу в PdfOxide.RenderedImage и сохраните её как PNG:

{:ok, doc} = PdfOxide.open("paper.pdf")

{:ok, img} = PdfOxide.render_page(doc, 0)
IO.puts("#{img.width}x#{img.height}, #{byte_size(img.data)} bytes")
:ok = PdfOxide.save(img, "page0.png")

# Коэффициент масштабирования или миниатюра фиксированного размера
{:ok, zoomed} = PdfOxide.render_page_zoom(doc, 0, 2.0)
{:ok, thumb}  = PdfOxide.render_page_thumbnail(doc, 0, 128)

Обработка ошибок

Функции, способные завершиться неудачей, возвращают помеченный кортеж — используйте сопоставление с образцом для чистого управления потоком выполнения:

case PdfOxide.open("/nonexistent/nope.pdf") do
  {:ok, doc} ->
    {:ok, text} = PdfOxide.extract_text(doc, 0)
    IO.puts(text)

  {:error, code} ->
    IO.puts("could not open PDF: #{inspect(code)}")
end

Дальнейшие шаги