Skip to content

PDF-библиотека для Node.js — PDF Oxide

PDF Oxide — самая быстрая PDF-библиотека для Node.js: 0,8 мс на страницу в среднем, в 5 раз быстрее PyMuPDF, в 15 раз быстрее pypdf, 100 % успеха на 3 830 PDF. Один пакет для извлечения, создания и редактирования — с определениями TypeScript в комплекте. Лицензия MIT / Apache-2.0.

Запускаете в браузере, Deno, Bun или Cloudflare Workers? Используйте WASM-сборку — тот же API, без нативных бинарников. N-API расширение с этой страницы — только для Node.js и Electron.

Установка

npm install pdf-oxide

Требования: Node.js 18 или новее. Системные зависимости и Rust-тулчейн не нужны. Готовые .node-расширения для Linux (glibc + musl) x64/arm64, macOS x64/arm64 и Windows x64/arm64 загружаются автоматически через платформенные optionalDependencies — при установке ничего не компилируется.

Открытие PDF

JavaScript

const { PdfDocument } = require("pdf-oxide");

const doc = new PdfDocument("research-paper.pdf");
console.log(`Pages: ${doc.getPageCount()}`);

const { major, minor } = doc.getVersion();
console.log(`PDF version: ${major}.${minor}`);

doc.close();

TypeScript

import { PdfDocument } from "pdf-oxide";

const doc: PdfDocument = new PdfDocument("research-paper.pdf");
const pageCount: number = doc.getPageCount();
const { major, minor }: { major: number; minor: number } = doc.getVersion();
console.log(`${pageCount} pages, PDF ${major}.${minor}`);
doc.close();

В Node.js 22+ используйте using для автоматической очистки:

{
  using doc = new PdfDocument("report.pdf");
  const text = doc.extractText(0);
} // doc.close() вызовется автоматически

API страниц

С версии v0.3.34 PdfDocument поддерживает итерацию, а doc.page(i) возвращает PdfPage с кэшированными width / height / rotation и методами извлечения, действующими на конкретной странице.

const { PdfDocument } = require("pdf-oxide");

const doc = new PdfDocument("paper.pdf");
for (const page of doc) {
  console.log(`Page ${page.index}: ${page.width}x${page.height} (rotation ${page.rotation})`);
  const md = page.markdown();
  const tables = page.tables();       // строки и ячейки с bbox
}
doc.close();

Индексация: doc.page(0), doc.page(-1) (последняя страница). Методы страницы: text(), markdown(), html(), plainText(), words(), lines(), tables(), images(), paths(), annotations(), fonts(), search(query, caseSensitive).

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

Одна страница

JavaScript

const { PdfDocument } = require("pdf-oxide");

const doc = new PdfDocument("report.pdf");
const text = doc.extractText(0);
console.log(text);
doc.close();

TypeScript

import { PdfDocument } from "pdf-oxide";

const doc: PdfDocument = new PdfDocument("report.pdf");
const text: string = doc.extractText(0);
console.log(text);
doc.close();

Все страницы

const doc = new PdfDocument("book.pdf");
const pageCount = doc.getPageCount();

for (let i = 0; i < pageCount; i++) {
  console.log(`--- Page ${i + 1} ---`);
  console.log(doc.extractText(i));
}

doc.close();

Асинхронное извлечение

У каждого синхронного метода есть пара *Async, которая работает в пуле потоков libuv. Применяйте её в HTTP-обработчиках и другом конкурентном серверном коде, чтобы извлечение не блокировало событийный цикл.

const { PdfDocument } = require("pdf-oxide");

async function extract(path) {
  const doc = new PdfDocument(path);
  try {
    return await doc.extractTextAsync(0);
  } finally {
    doc.close();
  }
}

См. руководство по async — там есть шаблоны вроде Promise.all с распределением по страницам.

Структурированное извлечение

Данные на уровне символов и спанов с позициями и метаданными шрифта:

const chars = doc.extractChars(0);
for (const ch of chars.slice(0, 10)) {
  console.log(`'${ch.char}' at (${ch.x.toFixed(1)}, ${ch.y.toFixed(1)}) ` +
              `size=${ch.fontSize.toFixed(1)} font=${ch.fontName}`);
}

const spans = doc.extractSpans(0);
for (const span of spans) {
  console.log(`"${span.text}" font=${span.fontName} size=${span.fontSize}`);
}

Извлечение по словам и строкам с настраиваемой сегментацией:

const words = doc.extractWords(0);
const lines = doc.extractTextLines(0, { wordGapThreshold: 2.5, lineGapThreshold: 1.2 });

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

JavaScript

const md = doc.toMarkdown(0, { detectHeadings: true });
console.log(md);

// Все страницы
const allMd = doc.toMarkdownAll();

TypeScript

const md: string = doc.toMarkdown(0, { detectHeadings: true });
const allMd: string = doc.toMarkdownAll();

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

const html = doc.toHtml(0);
const allHtml = doc.toHtmlAll();

Извлечение изображений

const { writeFileSync } = require("fs");

const doc = new PdfDocument("brochure.pdf");
const images = doc.extractImages(0);

for (const [i, img] of images.entries()) {
  console.log(`Image ${i}: ${img.width}x${img.height} ${img.format} (${img.data.length} bytes)`);
  writeFileSync(`image_${i}.${img.format}`, img.data);
}

doc.close();

Картинки из PDF с индексными цветами автоматически разворачиваются в RGB, включая индексные палитры 1/2/4/8 bpc с базовыми цветовыми пространствами RGB, Grayscale и CMYK.

Открытие из байтов

Откройте PDF из байтов в памяти — удобно при загрузке из S3, HTTP или базы данных:

const { PdfDocument } = require("pdf-oxide");
const { readFileSync } = require("fs");

const bytes = readFileSync("document.pdf");
const doc = PdfDocument.openFromBytes(bytes);
const text = doc.extractText(0);
doc.close();

PDF с паролем

const doc = PdfDocument.openWithPassword("confidential.pdf", "secret");
const text = doc.extractText(0);
doc.close();

Можно аутентифицироваться и после открытия:

const doc = new PdfDocument("confidential.pdf");
if (doc.authenticate("secret")) {
  console.log(doc.extractText(0));
}
doc.close();

AES-256 (V=5, R=6) поддерживается полностью — включая подписи push-button-виджетов и корректно инвалидируемые кэши объектов после поздней аутентификации.

Создание PDF

Класс Pdf предоставляет фабричные методы для создания PDF из разных исходных форматов.

Из Markdown

const { Pdf } = require("pdf-oxide");
const { writeFileSync } = require("fs");

const pdf = Pdf.fromMarkdown("# Hello World\n\nThis is a PDF.");
writeFileSync("output.pdf", pdf.toBytes());

Из HTML

const pdf = Pdf.fromHtml("<h1>Invoice</h1><p>Amount due: $42.00</p>");
writeFileSync("invoice.pdf", pdf.toBytes());

Из простого текста

const pdf = Pdf.fromText("Plain text document.\n\nSecond paragraph.");
writeFileSync("notes.pdf", pdf.toBytes());

Из изображений

const pdf = Pdf.fromImage("scan.jpg");
writeFileSync("scan.pdf", pdf.toBytes());

Поиск

const doc = new PdfDocument("manual.pdf");

// Поиск по всем страницам
const results = doc.searchAll("configuration", { caseSensitive: false });
for (const r of results) {
  console.log(`Page ${r.page}: "${r.text}" at (${r.x.toFixed(0)}, ${r.y.toFixed(0)})`);
}

// Поиск по одной странице
const pageResults = doc.searchPage(0, "configuration");
doc.close();

Для потокового поиска по большим документам используйте SearchStream:

const { PdfDocument, SearchStream, SearchManager } = require("pdf-oxide");

const doc = new PdfDocument("large.pdf");
const manager = new SearchManager(doc);
const stream = new SearchStream(manager, "invoice");

stream.on("data", (r) => console.log(`page ${r.pageIndex + 1}: ${r.text}`));
stream.on("end", () => doc.close());

Подробности — в руководстве по стримам Node.js.

Редактирование

Для метаданных, операций над страницами, аннотаций и полей форм используйте DocumentEditor:

const { DocumentEditor } = require("pdf-oxide");

const editor = DocumentEditor.open("document.pdf");

// Метаданные
editor.setTitle("Updated Title");
editor.setAuthor("Jane Doe");

// Операции со страницами
editor.rotatePage(0, 90);
editor.deletePage(5);
editor.movePage(2, 0);

// Формы
editor.setFormFieldValue("employee.name", "Jane Doe");
editor.flattenForms();

editor.save("edited.pdf");
editor.close();

OCR

Чтобы включить OCR для отсканированных страниц, активируйте feature ocr при установке:

npm install pdf-oxide --build-from-source -- --features ocr
const { PdfDocument, OcrEngine } = require("pdf-oxide");

const doc = new PdfDocument("scanned.pdf");
const ocr = new OcrEngine();

if (ocr.pageNeedsOcr(doc, 0)) {
  const text = ocr.extractText(doc, 0);
  console.log(text);
}

ocr.close();
doc.close();

Готовые рецепты смотрите в руководстве по OCR.

Потокобезопасность

PdfDocument реализует Send + Sync — один документ можно делить между воркер-потоками и читать страницы параллельно. Семейство *Async делает это автоматически через пул потоков libuv; ручные паттерны воркеров описаны в статье о конкурентности.

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

Все методы выбрасывают исключение при сбое:

const { PdfDocument } = require("pdf-oxide");

try {
  const doc = new PdfDocument("document.pdf");
  const text = doc.extractText(0);
  doc.close();
} catch (err) {
  console.error(`Extraction failed: ${err.message}`);
}

Что дальше