PDF.js библиотека для обработки PDF-документов на JavaScript
Обзор библиотеки PDF.js
PDF.js - это мощная библиотека на JavaScript для работы с PDF-документами прямо в браузере. Она позволяет загружать, отображать, извлекать текст, обрабатывать формы и многое другое без серверной части. Основана на Mozilla и широко используется в веб-приложениях.
Основное решение: рендеринг PDF через Canvas
Как отобразить первую страницу PDF из локального файла или URL?
// Установка: npm install pdfjs-dist
import * as pdfjsLib from 'pdfjs-dist';
const loadingTask = pdfjsLib.getDocument('example.pdf');
loadingTask.promise.then(pdf => {
pdf.getPage(1).then(page => {
const scale = 1.5;
const viewport = page.getViewport({ scale });
const canvas = document.getElementById('pdf-canvas');
canvas.width = viewport.width;
canvas.height = viewport.height;
const ctx = canvas.getContext('2d');
page.render({ canvasContext: ctx, viewport }).promise.then(() => {
console.log('PDF rendered');
});
});
});Pdf js (pdf.js библиотека)
Пояснение: getDocument возвращает объект PDF, метод getPage выбранную страницу. getViewport вычисляет размеры для заданного масштаба. Рендеринг происходит в canvas.
Типичные ошибки:
- PDF загружается из другого домена без CORS - ошибка доступа. Решение: настроить сервер или использовать прокси.
- Не обновлён размер canvas после загрузки - отображается пустой холст. Решение: проверять вызов canvas.width/height.
- Слишком крупный scale ведёт к падению памяти. Решение: ограничить масштаб (например, до 3).
Как реализовать навигацию по страницам (вперёд/назад)?
let currentPage = 1;
let pdfDoc = null;
function renderPage(pageNum) {
pdfDoc.getPage(pageNum).then(page => { /* рендеринг как выше */ });
}
document.getElementById('next').addEventListener('click', () => {
if (currentPage < pdfDoc.numPages) {
currentPage++;
renderPage(currentPage);
}
});
document.getElementById('prev').addEventListener('click', () => {
if (currentPage > 1) {
currentPage--;
renderPage(currentPage);
}
});Пояснение: numPages общее количество страниц. Переключение возможно после загрузки всего PDF.
Как извлечь текст из PDF?
pdf.getPage(pageNum).then(page => {
page.getTextContent().then(textContent => {
const text = textContent.items.map(item => item.str).join(' ');
console.log(text);
});
});Метод getTextContent возвращает массив фрагментов текста с координатами. Для простого текста достаточно собрать str.
Как использовать встроенный PDF.js Viewer (pdf_viewer.js)?
import { PDFViewer } from 'pdfjs-dist/web/pdf_viewer';
import 'pdfjs-dist/web/pdf_viewer.css';
const viewer = new PDFViewer({
container: document.getElementById('viewerContainer'),
viewer: document.getElementById('viewer'),
});
const loadingTask = pdfjsLib.getDocument('example.pdf');
loadingTask.promise.then(pdf => {
viewer.setDocument(pdf);
});Viewer поддерживает скролл, масштабирование, навигацию. Подходит для полноценного просмотрщика.
Как рендерить страницы в высоком разрешении или с масштабированием?
const scale = window.devicePixelRatio;
const viewport = page.getViewport({ scale });
canvas.width = viewport.width;
canvas.height = viewport.height;
canvas.style.width = viewport.width / scale + 'px';
canvas.style.height = viewport.height / scale + 'px';Умножаем scale на devicePixelRatio для retina-экранов. CSS-стили сжимают canvas до логических пикселей.
Как обработать ошибки при загрузке PDF?
const loadingTask = pdfjsLib.getDocument('broken.pdf');
loadingTask.promise.catch(err => {
if (err instanceof pdfjsLib.PasswordException) { /* запрос пароля */ }
else if (err instanceof pdfjsLib.InvalidPDFException) { /* неверный формат */ }
else { /* другая ошибка */ }
});PDF.js выбрасывает типизированные исключения. Можно обрабатывать отдельно.
Как работать с формами и полями ввода?
pdf.getPage(pageNum).then(page => {
const annotations = page.getAnnotations();
annotations.filter(ann => ann.subtype === 'Widget').forEach(widget => {
if (widget.fieldType === 'Tx') {
console.log('Text field:', widget.fieldName);
}
});
});getAnnotations возвращает все аннотации страницы. Widget-аннотации соответствуют полям форм. Доступны имя поля, значение, тип.
Как получить метаданные PDF (автор, название)?
pdf.getMetadata().then(meta => {
console.log(meta.info.Title);
console.log(meta.info.Author);
console.log(meta.metadata);
});Метаданные извлекаются асинхронно. Часть данных может отсутствовать.
Расширенные примеры работы с PDF.js
1. Загрузка PDF из ArrayBuffer и рендеринг
fetch('example.pdf')
.then(res => res.arrayBuffer())
.then(buffer => {
const loadingTask = pdfjsLib.getDocument({ data: buffer });
return loadingTask.promise;
})
.then(pdf => {
pdf.getPage(1).then(page => { /* рендеринг */ });
});Результат: PDF отображается из бинарных данных, без URL.
canvas с первой страницей PDF.
2. Рендеринг всех страниц в один большой canvas
async function renderAllPages(pdf) {
const canvases = [];
for (let i = 1; i <= pdf.numPages; i++) {
const page = await pdf.getPage(i);
const viewport = page.getViewport({ scale: 1 });
const canvas = document.createElement('canvas');
canvas.width = viewport.width;
canvas.height = viewport.height;
const ctx = canvas.getContext('2d');
await page.render({ canvasContext: ctx, viewport }).promise;
canvases.push(canvas);
}
// Размещение в контейнере
const container = document.getElementById('pdf-pages');
canvases.forEach(c => container.appendChild(c));
}Результат: все страницы отображены вертикально.
серия канвасов, по одному на страницу.
3. Извлечение текста с координатами и стилями
page.getTextContent().then(content => {
content.items.forEach(item => {
console.log(`Текст: ${item.str}, x: ${item.transform[4]}, y: ${item.transform[5]}, шрифт: ${item.fontName}`);
});
});Результат: в консоли список фрагментов с позицией.
Текст: Hello, x: 72, y: 720, шрифт: g_d1_f2
4. Интеграция с React (функциональный компонент)
import { useEffect, useRef } from 'react';
import * as pdfjsLib from 'pdfjs-dist';
function PDFViewer({ url }) {
const canvasRef = useRef();
useEffect(() => {
const loadingTask = pdfjsLib.getDocument(url);
loadingTask.promise.then(pdf => {
pdf.getPage(1).then(page => {
const viewport = page.getViewport({ scale: 1.5 });
const canvas = canvasRef.current;
canvas.width = viewport.width;
canvas.height = viewport.height;
const ctx = canvas.getContext('2d');
page.render({ canvasContext: ctx, viewport });
});
});
}, [url]);
return <canvas ref={canvasRef}></canvas>;
}Результат: React-компонент, отображающий первую страницу.
Рендеринг в canvas, встроенный в React.
5. Работа с PDF, защищённым паролем
const loadingTask = pdfjsLib.getDocument({ url: 'protected.pdf', password: 'userpass' });
loadingTask.promise.then(pdf => { /* рендеринг */ }).catch(err => {
if (err instanceof pdfjsLib.PasswordException) {
// запросить другой пароль
}
});Результат: PDF открывается, если пароль верен.
Страница отрисована.
6. Кастомизация рендеринга: создание текстового слоя поверх canvas
page.getTextContent().then(textContent => {
const textLayer = document.getElementById('text-layer');
textLayer.innerHTML = '';
textContent.items.forEach(item => {
const span = document.createElement('span');
span.style.left = item.transform[4] + 'px';
span.style.top = item.transform[5] + 'px';
span.textContent = item.str;
textLayer.appendChild(span);
});
});Результат: текст доступен для выделения мышью.
Прозрачный слой с элементами span, позиционированными над canvas.
7. Рендеринг в Web Worker (параллельная загрузка)
// Основной поток
const worker = new Worker('pdfWorker.js');
worker.postMessage({ url: 'example.pdf', page: 1 });
worker.onmessage = (e) => {
const { imageData } = e.data;
// отобразить imageData на canvas
};
// pdfWorker.js
importScripts('pdfjs-dist/build/pdf.worker.min.js');
self.onmessage = async (e) => {
const { url, page } = e.data;
const pdf = await pdfjsLib.getDocument(url).promise;
const pg = await pdf.getPage(page);
const viewport = pg.getViewport({ scale: 1 });
const canvas = new OffscreenCanvas(viewport.width, viewport.height);
const ctx = canvas.getContext('2d');
await pg.render({ canvasContext: ctx, viewport }).promise;
const bitmap = canvas.transferToImageBitmap();
self.postMessage({ imageData: bitmap }, [bitmap]);
};Результат: рендеринг не блокирует UI.
Быстрый рендеринг страницы.
8. Кастомный Viewer с кэшированием страниц
const cache = new Map();
function renderPageWithCache(pdf, pageNum) {
if (cache.has(pageNum)) {
// использовать кэшированный canvas
return Promise.resolve();
}
return pdf.getPage(pageNum).then(page => {
const viewport = page.getViewport({ scale: 1 });
const canvas = document.createElement('canvas');
canvas.width = viewport.width;
canvas.height = viewport.height;
const ctx = canvas.getContext('2d');
return page.render({ canvasContext: ctx, viewport }).promise.then(() => {
cache.set(pageNum, canvas);
});
});
}Результат: повторный рендеринг ускорен.
Страницы переключаются без задержки.