Spaces:
Runtime error
Runtime error
| """ | |
| pdfβtoolkit β Extract text, read metadata, list form fields, and merge PDFs. | |
| ================================================================================ | |
| Handles the messy reality of PDF parsing (multiβcolumn layouts, | |
| Unicode, encrypted files) so agents don't have to guess. | |
| """ | |
| import io | |
| import json | |
| import sys | |
| from pathlib import Path | |
| try: | |
| from toolstore.toolset import tool | |
| except ImportError: | |
| def tool(fn): | |
| return fn # noβop when toolstore package not installed | |
| # ββ pdf_extract ββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| def pdf_extract(*, filepath: str, first_page: int = 1, | |
| last_page: int = 0) -> dict: | |
| """Extract readable text from a PDF using layoutβaware extraction. | |
| Args: | |
| filepath: Path to the PDF file. | |
| first_page: First page to extract (1βbased, default 1). | |
| last_page: Last page to extract (0 = all pages). | |
| Returns: | |
| dict with keys: | |
| text β extracted text (pages separated by formβfeeds) | |
| pages β number of pages processed | |
| total β total pages in the document | |
| """ | |
| try: | |
| import pdfplumber | |
| except ImportError: | |
| return {"error": "pdfplumber not installed β run: pip install pdfplumber"} | |
| p = Path(filepath).expanduser().resolve() | |
| if not p.exists(): | |
| return {"error": f"File not found: {p}"} | |
| try: | |
| pdf = pdfplumber.open(p) | |
| except Exception as exc: | |
| return {"error": f"Cannot open PDF: {exc}"} | |
| total = len(pdf.pages) | |
| if last_page == 0: | |
| last_page = total | |
| first_page = max(1, first_page) | |
| last_page = min(last_page, total) | |
| texts = [] | |
| for i in range(first_page - 1, last_page): | |
| try: | |
| page_text = pdf.pages[i].extract_text() or "" | |
| texts.append(page_text) | |
| except Exception: | |
| texts.append("") | |
| pdf.close() | |
| return {"text": "\n\n".join(texts), "pages": len(texts), | |
| "total": total, "range": f"{first_page}-{last_page}"} | |
| # ββ pdf_meta βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| def pdf_meta(*, filepath: str) -> dict: | |
| """Read metadata from a PDF file without extracting content. | |
| Args: | |
| filepath: Path to the PDF file. | |
| Returns: | |
| dict with keys: | |
| filename, pages, title, author, subject, creator, | |
| producer, encrypted, file_size_bytes | |
| """ | |
| p = Path(filepath).expanduser().resolve() | |
| if not p.exists(): | |
| return {"error": f"File not found: {p}"} | |
| file_size = p.stat().st_size | |
| try: | |
| from PyPDF2 import PdfReader | |
| except ImportError: | |
| return {"error": "PyPDF2 not installed β run: pip install PyPDF2"} | |
| try: | |
| reader = PdfReader(p) | |
| except Exception as exc: | |
| return {"error": f"Cannot read PDF: {exc}"} | |
| info = reader.metadata or {} | |
| return { | |
| "filename": p.name, | |
| "pages": len(reader.pages), | |
| "title": (info.get("/Title") or "").strip() or None, | |
| "author": (info.get("/Author") or "").strip() or None, | |
| "subject": (info.get("/Subject") or "").strip() or None, | |
| "creator": (info.get("/Creator") or "").strip() or None, | |
| "producer": (info.get("/Producer") or "").strip() or None, | |
| "encrypted": reader.is_encrypted, | |
| "file_size_bytes": file_size, | |
| } | |
| # ββ pdf_merge ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| def pdf_merge(*, inputs: list, output: str) -> dict: | |
| """Merge multiple PDF files into a single output file. | |
| Args: | |
| inputs: List of file paths to merge (order is preserved). | |
| output: Path for the merged output PDF. | |
| Returns: | |
| dict with "written" (absolute path), "pages", and "sources" count. | |
| """ | |
| try: | |
| from PyPDF2 import PdfReader, PdfWriter | |
| except ImportError: | |
| return {"error": "PyPDF2 not installed β run: pip install PyPDF2"} | |
| if not inputs: | |
| return {"error": "No input files provided"} | |
| writer = PdfWriter() | |
| total_pages = 0 | |
| sources = 0 | |
| for path_str in inputs: | |
| p = Path(path_str).expanduser().resolve() | |
| if not p.exists(): | |
| return {"error": f"Input not found: {p}"} | |
| try: | |
| reader = PdfReader(p) | |
| except Exception as exc: | |
| return {"error": f"Cannot read {p.name}: {exc}"} | |
| for page in reader.pages: | |
| writer.add_page(page) | |
| total_pages += len(reader.pages) | |
| sources += 1 | |
| out = Path(output).expanduser().resolve() | |
| out.parent.mkdir(parents=True, exist_ok=True) | |
| with open(out, "wb") as f: | |
| writer.write(f) | |
| return {"written": str(out), "pages": total_pages, "sources": sources} | |
| # ββ pdf_form_fields ββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| def pdf_form_fields(*, filepath: str) -> dict: | |
| """List all fillable form fields in a PDF with their types and current values. | |
| Args: | |
| filepath: Path to the PDF file. | |
| Returns: | |
| dict with: | |
| has_fields β True if the PDF contains any form fields | |
| count β total number of fields found | |
| fields β list of dicts: {name, type, value, flags} | |
| """ | |
| try: | |
| from PyPDF2 import PdfReader | |
| except ImportError: | |
| return {"error": "PyPDF2 not installed β run: pip install PyPDF2"} | |
| p = Path(filepath).expanduser().resolve() | |
| if not p.exists(): | |
| return {"error": f"File not found: {p}"} | |
| try: | |
| reader = PdfReader(p) | |
| except Exception as exc: | |
| return {"error": f"Cannot read PDF: {exc}"} | |
| fields = [] | |
| try: | |
| raw_fields = reader.get_fields() | |
| except Exception: | |
| raw_fields = None | |
| if raw_fields: | |
| for name, info in raw_fields.items(): | |
| if info is None: | |
| continue | |
| field = { | |
| "name": name, | |
| "type": str(info.get("/FT", "unknown")).lstrip("/"), | |
| "value": str(info.get("/V", "")) if info.get("/V") else None, | |
| "flags": [], | |
| } | |
| ff = info.get("/Ff", 0) | |
| if isinstance(ff, int): | |
| if ff & 1: field["flags"].append("readOnly") | |
| if ff & 2: field["flags"].append("required") | |
| if ff & 4: field["flags"].append("noExport") | |
| if ff & 0x10000: field["flags"].append("multiLine") | |
| if ff & 0x20000: field["flags"].append("password") | |
| opts = info.get("/Opt") | |
| if opts: | |
| field["options"] = [str(o) if not isinstance(o, list) else str(o[0]) | |
| for o in opts] | |
| fields.append(field) | |
| return { | |
| "has_fields": len(fields) > 0, | |
| "count": len(fields), | |
| "fields": fields, | |
| "filename": p.name, | |
| } | |