{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "pdf-utils",
  "title": "PDF Utils",
  "description": "Utility functions for PDF manipulation: extract pages, screenshots, search text, and more",
  "dependencies": [
    "react-pdf",
    "pdfjs-dist",
    "pdf-lib"
  ],
  "registryDependencies": [],
  "files": [
    {
      "path": "registry/default/blocks/pdf/pdf-utils/components/elements/pdf-utils.ts",
      "content": "/**\n * PDF Utilities using pdf-lib (isomorphic - works in browser and Node.js)\n * For text extraction and rendering, we still use pdfjs (browser-optimized)\n */\n\nimport { PDFDocument } from \"pdf-lib\";\nimport { pdfjs } from \"react-pdf\";\n\n// Configure PDF.js worker (for text extraction functions)\nif (typeof window !== \"undefined\" && !pdfjs.GlobalWorkerOptions.workerSrc) {\n  pdfjs.GlobalWorkerOptions.workerSrc = `https://unpkg.com/pdfjs-dist@${pdfjs.version}/build/pdf.worker.min.mjs`;\n}\n\nexport interface PdfPageInfo {\n  pageNumber: number;\n  width: number;\n  height: number;\n  rotation: number;\n}\n\nexport interface PdfDocumentInfo {\n  numPages: number;\n  title?: string;\n  author?: string;\n  subject?: string;\n  keywords?: string;\n  creator?: string;\n  producer?: string;\n  creationDate?: Date;\n  modificationDate?: Date;\n}\n\n/**\n * Fetch a PDF from a URL and convert it to a File object\n * @param url - URL to the PDF file\n * @param filename - Optional filename (defaults to extracted from URL or \"document.pdf\")\n * @returns File object containing the PDF data\n */\nexport async function fetchPdfAsFile(\n  url: string,\n  filename?: string,\n): Promise<File> {\n  const response = await fetch(url);\n\n  if (!response.ok) {\n    throw new Error(\n      `Failed to fetch PDF: ${response.status} ${response.statusText}`,\n    );\n  }\n\n  const blob = await response.blob();\n\n  // Extract filename from URL if not provided\n  const finalFilename =\n    filename || url.split(\"/\").pop()?.split(\"?\")[0] || \"document.pdf\";\n\n  return new File([blob], finalFilename, { type: \"application/pdf\" });\n}\n\n/**\n * Load PDF document using pdf-lib (works in browser and Node.js)\n */\nasync function loadPdfLibDocument(file: File) {\n  const arrayBuffer = await file.arrayBuffer();\n  return await PDFDocument.load(arrayBuffer);\n}\n\n/**\n * Load PDF document using pdfjs (for text extraction)\n */\nasync function loadPdfJsDocument(file: File) {\n  const arrayBuffer = await file.arrayBuffer();\n  const loadingTask = pdfjs.getDocument(arrayBuffer);\n  return await loadingTask.promise;\n}\n\n/**\n * Get information about a PDF document\n * Uses pdf-lib (works in browser and Node.js)\n * @param file - PDF File object (use fetchPdfAsFile() to convert URL to File)\n * @returns Document metadata and page count\n */\nexport async function getPdfInfo(file: File): Promise<PdfDocumentInfo> {\n  const pdfDoc = await loadPdfLibDocument(file);\n\n  return {\n    numPages: pdfDoc.getPageCount(),\n    title: pdfDoc.getTitle() || undefined,\n    author: pdfDoc.getAuthor() || undefined,\n    subject: pdfDoc.getSubject() || undefined,\n    keywords: pdfDoc.getKeywords() || undefined,\n    creator: pdfDoc.getCreator() || undefined,\n    producer: pdfDoc.getProducer() || undefined,\n    creationDate: pdfDoc.getCreationDate() || undefined,\n    modificationDate: pdfDoc.getModificationDate() || undefined,\n  };\n}\n\n/**\n * Get information about a specific page\n * Uses pdf-lib (works in browser and Node.js)\n * @param file - PDF File object (use fetchPdfAsFile() to convert URL to File)\n * @param pageNumber - Page number (1-indexed)\n * @returns Page dimensions and rotation\n */\nexport async function getPageInfo(\n  file: File,\n  pageNumber: number,\n): Promise<PdfPageInfo> {\n  const pdfDoc = await loadPdfLibDocument(file);\n  const pageCount = pdfDoc.getPageCount();\n\n  if (pageNumber < 1 || pageNumber > pageCount) {\n    throw new Error(\n      `Page number ${pageNumber} is out of range (1-${pageCount})`,\n    );\n  }\n\n  const page = pdfDoc.getPage(pageNumber - 1); // pdf-lib uses 0-indexed\n  const { width, height } = page.getSize();\n  const rotation = page.getRotation().angle;\n\n  return {\n    pageNumber,\n    width,\n    height,\n    rotation,\n  };\n}\n\n/**\n * Extract a single page as a new PDF blob\n * Uses pdf-lib (works in browser and Node.js)\n * @param file - PDF File object (use fetchPdfAsFile() to convert URL to File)\n * @param pageNumber - Page number to extract (1-indexed)\n * @returns Blob containing single-page PDF\n */\nexport async function extractPage(\n  file: File,\n  pageNumber: number,\n): Promise<Blob> {\n  const pdfDoc = await loadPdfLibDocument(file);\n  const pageCount = pdfDoc.getPageCount();\n\n  if (pageNumber < 1 || pageNumber > pageCount) {\n    throw new Error(\n      `Page number ${pageNumber} is out of range (1-${pageCount})`,\n    );\n  }\n\n  const newPdf = await PDFDocument.create();\n  const [copiedPage] = await newPdf.copyPages(pdfDoc, [pageNumber - 1]);\n  newPdf.addPage(copiedPage);\n\n  const pdfBytes = await newPdf.save();\n  // Convert Uint8Array to ArrayBuffer for Blob compatibility\n  return new Blob([pdfBytes.slice().buffer], { type: \"application/pdf\" });\n}\n\n/**\n * Extract a range of pages as a new PDF blob\n * Uses pdf-lib (works in browser and Node.js)\n * @param file - PDF File object (use fetchPdfAsFile() to convert URL to File)\n * @param startPage - First page to extract (1-indexed, inclusive)\n * @param endPage - Last page to extract (1-indexed, inclusive)\n * @returns Blob containing extracted pages\n */\nexport async function extractPageRange(\n  file: File,\n  startPage: number,\n  endPage: number,\n): Promise<Blob> {\n  const pdfDoc = await loadPdfLibDocument(file);\n  const pageCount = pdfDoc.getPageCount();\n\n  if (startPage < 1 || endPage > pageCount || startPage > endPage) {\n    throw new Error(\n      `Invalid page range ${startPage}-${endPage} (document has ${pageCount} pages)`,\n    );\n  }\n\n  const newPdf = await PDFDocument.create();\n  const pageIndices = Array.from(\n    { length: endPage - startPage + 1 },\n    (_, i) => startPage - 1 + i,\n  );\n  const copiedPages = await newPdf.copyPages(pdfDoc, pageIndices);\n\n  for (const page of copiedPages) {\n    newPdf.addPage(page);\n  }\n\n  const pdfBytes = await newPdf.save();\n  // Convert Uint8Array to ArrayBuffer for Blob compatibility\n  return new Blob([pdfBytes.slice().buffer], { type: \"application/pdf\" });\n}\n\n// ==================== PDF.js-based functions (text extraction and rendering) ====================\n\n/**\n * Get text content from a specific page\n * Uses PDF.js (optimized for browser, but works in Node.js with proper setup)\n * @param file - PDF File object (use fetchPdfAsFile() to convert URL to File)\n * @param pageNumber - Page number (1-indexed)\n * @returns Text content of the page\n */\nexport async function getPageText(\n  file: File,\n  pageNumber: number,\n): Promise<string> {\n  const pdf = await loadPdfJsDocument(file);\n\n  if (pageNumber < 1 || pageNumber > pdf.numPages) {\n    throw new Error(\n      `Page number ${pageNumber} is out of range (1-${pdf.numPages})`,\n    );\n  }\n\n  const page = await pdf.getPage(pageNumber);\n  const textContent = await page.getTextContent();\n\n  return textContent.items.map((item: any) => item.str).join(\" \");\n}\n\n/**\n * Search for text in PDF\n * Uses PDF.js for text extraction\n * @param file - PDF File object (use fetchPdfAsFile() to convert URL to File)\n * @param searchText - Text to search for\n * @param caseSensitive - Whether search should be case-sensitive (default: false)\n * @returns Array of page numbers where text was found\n */\nexport async function searchText(\n  file: File,\n  searchText: string,\n  caseSensitive = false,\n): Promise<number[]> {\n  const pdf = await loadPdfJsDocument(file);\n  const foundPages: number[] = [];\n\n  const normalizedSearch = caseSensitive\n    ? searchText\n    : searchText.toLowerCase();\n\n  for (let pageNum = 1; pageNum <= pdf.numPages; pageNum++) {\n    const page = await pdf.getPage(pageNum);\n    const textContent = await page.getTextContent();\n    const pageText = textContent.items.map((item: any) => item.str).join(\" \");\n\n    const normalizedPageText = caseSensitive\n      ? pageText\n      : pageText.toLowerCase();\n\n    if (normalizedPageText.includes(normalizedSearch)) {\n      foundPages.push(pageNum);\n    }\n  }\n\n  return foundPages;\n}\n\n/**\n * Render a PDF page as a screenshot (base64 image)\n * Uses PDF.js with Canvas API (browser only, or Node.js with canvas library)\n * @param file - PDF File object (use fetchPdfAsFile() to convert URL to File)\n * @param pageNumber - Page number (1-indexed)\n * @param scale - Scale factor (default: 2 for high DPI)\n * @returns Base64-encoded PNG image\n */\nexport async function screenshotPage(\n  file: File,\n  pageNumber: number,\n  scale = 2,\n): Promise<string> {\n  // Check if we're in a browser environment\n  if (typeof document === \"undefined\") {\n    throw new Error(\n      \"screenshotPage requires a browser environment with Canvas API. Use in browser or with a DOM implementation like happy-dom/jsdom.\",\n    );\n  }\n\n  const pdf = await loadPdfJsDocument(file);\n\n  if (pageNumber < 1 || pageNumber > pdf.numPages) {\n    throw new Error(\n      `Page number ${pageNumber} is out of range (1-${pdf.numPages})`,\n    );\n  }\n\n  const page = await pdf.getPage(pageNumber);\n  const viewport = page.getViewport({ scale });\n\n  const canvas = document.createElement(\"canvas\");\n  const context = canvas.getContext(\"2d\");\n\n  if (!context) {\n    throw new Error(\"Could not get canvas context\");\n  }\n\n  canvas.height = viewport.height;\n  canvas.width = viewport.width;\n\n  const renderContext = {\n    canvasContext: context,\n    viewport: viewport,\n  } as any;\n\n  await page.render(renderContext).promise;\n\n  return canvas.toDataURL(\"image/png\");\n}\n\n/**\n * Get all page thumbnails as base64 images\n * Uses PDF.js for rendering (browser optimized)\n * @param file - PDF File object (use fetchPdfAsFile() to convert URL to File)\n * @param scale - Scale factor for thumbnails (default: 0.5)\n * @returns Array of base64-encoded PNG images\n */\nexport async function getAllPageThumbnails(\n  file: File,\n  scale = 0.5,\n): Promise<string[]> {\n  const info = await getPdfInfo(file);\n  const thumbnails: string[] = [];\n\n  for (let i = 1; i <= info.numPages; i++) {\n    const thumbnail = await screenshotPage(file, i, scale);\n    thumbnails.push(thumbnail);\n  }\n\n  return thumbnails;\n}\n\n// Re-export types from PDF.js for compatibility\nexport type {\n  PDFDocumentProxy,\n  PDFPageProxy,\n  TextContent,\n} from \"pdfjs-dist/types/src/display/api\";\n",
      "type": "registry:component"
    }
  ],
  "docs": "Utility functions for working with PDFs: get document info, screenshot pages, extract text, search content, and generate thumbnails. Works with URLs or File objects.",
  "categories": [
    "pdf"
  ],
  "type": "registry:ui"
}