225 lines
6.1 KiB
TypeScript
225 lines
6.1 KiB
TypeScript
/**
|
||
* Client-side PDF utilities
|
||
* Извлечение текста, операции с PDF (split, merge, rotate)
|
||
*/
|
||
|
||
import { PDFDocument, degrees } from 'pdf-lib';
|
||
|
||
type PdfJsModule = typeof import("pdfjs-dist/legacy/build/pdf.mjs");
|
||
|
||
let pdfJsModulePromise: Promise<PdfJsModule> | null = null;
|
||
|
||
async function loadPdfJs(): Promise<PdfJsModule> {
|
||
if (typeof window === "undefined") {
|
||
throw new Error("PDF utilities are only available in the browser");
|
||
}
|
||
|
||
if (!pdfJsModulePromise) {
|
||
pdfJsModulePromise = import("pdfjs-dist/legacy/build/pdf.mjs").then((module) => {
|
||
if (!module.GlobalWorkerOptions.workerSrc) {
|
||
module.GlobalWorkerOptions.workerSrc = "/pdf.worker.min.mjs";
|
||
}
|
||
return module;
|
||
});
|
||
}
|
||
|
||
return pdfJsModulePromise;
|
||
}
|
||
|
||
export interface PDFTextExtractionProgress {
|
||
currentPage: number;
|
||
totalPages: number;
|
||
progress: number;
|
||
}
|
||
|
||
/**
|
||
* Извлекает текст из PDF файла
|
||
*
|
||
* @param file - PDF файл
|
||
* @param onProgress - Callback прогресса
|
||
* @returns Извлеченный текст
|
||
*/
|
||
export async function extractPDFText(
|
||
file: File,
|
||
onProgress?: (progress: PDFTextExtractionProgress) => void
|
||
): Promise<string> {
|
||
try {
|
||
const arrayBuffer = await file.arrayBuffer();
|
||
const pdfjsLib = await loadPdfJs();
|
||
const pdf = await pdfjsLib.getDocument({ data: arrayBuffer }).promise;
|
||
const totalPages = pdf.numPages;
|
||
|
||
let fullText = '';
|
||
|
||
for (let i = 1; i <= totalPages; i++) {
|
||
const page = await pdf.getPage(i);
|
||
const textContent = await page.getTextContent();
|
||
const pageText = textContent.items
|
||
.map((item: any) => item.str)
|
||
.join(' ');
|
||
|
||
fullText += pageText + '\n\n';
|
||
|
||
if (onProgress) {
|
||
onProgress({
|
||
currentPage: i,
|
||
totalPages,
|
||
progress: (i / totalPages) * 100
|
||
});
|
||
}
|
||
}
|
||
|
||
return fullText.trim();
|
||
} catch (error) {
|
||
console.error('[PDF] Text extraction error:', error);
|
||
throw new Error('Ошибка извлечения текста из PDF');
|
||
}
|
||
}
|
||
|
||
/**
|
||
* Генерирует thumbnail первой страницы PDF
|
||
*
|
||
* @param file - PDF файл
|
||
* @param scale - Масштаб (по умолчанию 0.5)
|
||
* @returns Data URL изображения
|
||
*/
|
||
export async function generatePDFThumbnail(
|
||
file: File,
|
||
scale: number = 0.5
|
||
): Promise<string> {
|
||
try {
|
||
const arrayBuffer = await file.arrayBuffer();
|
||
const pdfjsLib = await loadPdfJs();
|
||
const pdf = await pdfjsLib.getDocument({ data: arrayBuffer }).promise;
|
||
const page = await pdf.getPage(1);
|
||
|
||
const viewport = page.getViewport({ scale });
|
||
const canvas = document.createElement('canvas');
|
||
const context = canvas.getContext('2d')!;
|
||
|
||
canvas.width = viewport.width;
|
||
canvas.height = viewport.height;
|
||
|
||
await page.render({
|
||
canvasContext: context,
|
||
viewport,
|
||
canvas
|
||
}).promise;
|
||
|
||
return canvas.toDataURL('image/jpeg', 0.7);
|
||
} catch (error) {
|
||
console.error('[PDF] Thumbnail generation error:', error);
|
||
throw new Error('Ошибка создания превью PDF');
|
||
}
|
||
}
|
||
|
||
/**
|
||
* Разделяет PDF файл на отдельные страницы
|
||
*
|
||
* @param file - PDF файл
|
||
* @param pageNumbers - Номера страниц для извлечения (1-based)
|
||
* @returns Новый PDF как Uint8Array
|
||
*/
|
||
export async function splitPDF(
|
||
file: File,
|
||
pageNumbers: number[]
|
||
): Promise<Uint8Array> {
|
||
try {
|
||
const arrayBuffer = await file.arrayBuffer();
|
||
const pdfDoc = await PDFDocument.load(arrayBuffer);
|
||
const newPdf = await PDFDocument.create();
|
||
|
||
for (const pageNum of pageNumbers) {
|
||
const [page] = await newPdf.copyPages(pdfDoc, [pageNum - 1]);
|
||
newPdf.addPage(page);
|
||
}
|
||
|
||
return await newPdf.save();
|
||
} catch (error) {
|
||
console.error('[PDF] Split error:', error);
|
||
throw new Error('Ошибка разделения PDF');
|
||
}
|
||
}
|
||
|
||
/**
|
||
* Объединяет несколько PDF файлов
|
||
*
|
||
* @param files - Массив PDF файлов
|
||
* @returns Объединенный PDF как Uint8Array
|
||
*/
|
||
export async function mergePDFs(files: File[]): Promise<Uint8Array> {
|
||
try {
|
||
const mergedPdf = await PDFDocument.create();
|
||
|
||
for (const file of files) {
|
||
const arrayBuffer = await file.arrayBuffer();
|
||
const pdf = await PDFDocument.load(arrayBuffer);
|
||
const pages = await mergedPdf.copyPages(pdf, pdf.getPageIndices());
|
||
pages.forEach(page => mergedPdf.addPage(page));
|
||
}
|
||
|
||
return await mergedPdf.save();
|
||
} catch (error) {
|
||
console.error('[PDF] Merge error:', error);
|
||
throw new Error('Ошибка объединения PDF');
|
||
}
|
||
}
|
||
|
||
/**
|
||
* Поворачивает все страницы PDF
|
||
*
|
||
* @param file - PDF файл
|
||
* @param rotation - Угол поворота (90, 180, 270)
|
||
* @returns Повернутый PDF как Uint8Array
|
||
*/
|
||
export async function rotatePDF(
|
||
file: File,
|
||
rotation: 90 | 180 | 270
|
||
): Promise<Uint8Array> {
|
||
try {
|
||
const arrayBuffer = await file.arrayBuffer();
|
||
const pdfDoc = await PDFDocument.load(arrayBuffer);
|
||
|
||
const pages = pdfDoc.getPages();
|
||
pages.forEach(page => {
|
||
page.setRotation(degrees(rotation));
|
||
});
|
||
|
||
return await pdfDoc.save();
|
||
} catch (error) {
|
||
console.error('[PDF] Rotate error:', error);
|
||
throw new Error('Ошибка поворота PDF');
|
||
}
|
||
}
|
||
|
||
/**
|
||
* Получает информацию о PDF файле
|
||
*/
|
||
export async function getPDFInfo(file: File): Promise<{
|
||
numPages: number;
|
||
title?: string;
|
||
author?: string;
|
||
creationDate?: Date;
|
||
}> {
|
||
try {
|
||
const arrayBuffer = await file.arrayBuffer();
|
||
const pdfjsLib = await loadPdfJs();
|
||
const pdf = await pdfjsLib.getDocument({ data: arrayBuffer }).promise;
|
||
const metadata = await pdf.getMetadata();
|
||
|
||
const info = metadata.info as any;
|
||
|
||
return {
|
||
numPages: pdf.numPages,
|
||
title: info?.Title,
|
||
author: info?.Author,
|
||
creationDate: info?.CreationDate
|
||
? new Date(info.CreationDate)
|
||
: undefined
|
||
};
|
||
} catch (error) {
|
||
console.error('[PDF] Info error:', error);
|
||
throw new Error('Ошибка получения информации о PDF');
|
||
}
|
||
}
|