Claude
Skill
pdf
Extract text from PDF documents
Virus-scanned
Reviewed automatically before listing.
Download
axoviq-ai-synthadoc-synthadoc_skills_pdf-8dee0ee.zip · 3 KB
Install
skills CLI
npx skills add https://github.com/axoviq-ai/synthadoc/tree/main/synthadoc/skills/pdf
Claude Code
claude plugin marketplace add https://llmmart.ai/marketplace.json && claude plugin install axoviq-ai-synthadoc@llmmart
Git
git clone https://github.com/axoviq-ai/synthadoc.git
The skills CLI installs just this skill, for any of its supported agents. Claude Code installs the whole axoviq-ai/synthadoc collection as a plugin from our marketplace. Git is the plain clone.
Skill manifest
PDF Skill
Extracts text from PDF files using pypdf as the primary parser, with
pdfminer.six as a fallback for CJK fonts that pypdf cannot decode
(detected when pypdf yields fewer than 50 characters per page on average).
Setup
pip install pypdf pdfminer.six
Standalone usage
import asyncio
from synthadoc.skills.pdf.scripts.main import PdfSkill
skill = PdfSkill()
async def main():
result = await skill.extract("/path/to/paper.pdf")
print(result.text) # extracted text from all pages
print(result.metadata) # {"pages": N, "cjk_fallback": bool, ...}
asyncio.run(main())
When this skill is used
- Source path ends with
.pdf - User intent contains:
pdf,research paper
Scripts
scripts/main.py—PdfSkillclass
References
references/cjk-notes.md— notes on CJK font handling
Files (synthadoc)
-
references
-
cjk-notes.md 399 B
# CJK Font Handling Notes pypdf cannot decode PDF files that use fonts without embedded ToUnicode CMaps — common in documents typeset with Chinese, Japanese, or Korean character sets. In this case pypdf returns near-empty text (< 50 chars/page on average). pdfminer.six uses its own CMap tables and handles these fonts correctly. The threshold `_MIN_CHARS_PER_PAGE = 50` was chosen empirically.
-
-
scripts
-
main.py 3.6 KB
# SPDX-License-Identifier: AGPL-3.0-or-later # Copyright (C) 2026 Paul Chen / axoviq.com import asyncio import logging import pypdf from synthadoc.skills.base import BaseSkill, ExtractedContent, SkillMeta logger = logging.getLogger(__name__) # pypdf logs benign structural warnings (e.g. incorrect startxref pointer) at WARNING # level for many real-world PDFs. Suppress them so they don't pollute the console. logging.getLogger("pypdf").setLevel(logging.ERROR) # If pypdf extracts fewer than this many characters per page on average, # the PDF likely uses CJK fonts whose ToUnicode CMaps pypdf cannot decode. # In that case we fall back to pdfminer.six which has better CJK support. _MIN_CHARS_PER_PAGE = 50 def _build_pagemap(page_texts: list[str]) -> dict[int, int]: """Return {first_line_of_page: pdf_page_number} for each non-empty page. Line numbers are 1-based and reference the concatenated extracted text. Empty pages are skipped and do not advance the line counter. """ pagemap: dict[int, int] = {} current_line = 1 for page_num, text in enumerate(page_texts, start=1): if text: pagemap[current_line] = page_num current_line += text.count("\n") + 1 return pagemap class PdfSkill(BaseSkill): meta = SkillMeta(name="pdf", description="Extract text from PDF files", extensions=[".pdf"]) async def extract(self, source: str) -> ExtractedContent: # pypdf and pdfminer are synchronous CPU-bound libraries; run them in a # thread pool so they do not block the asyncio event loop and starve # other coroutines (e.g. HTTP handlers, jobs list) while processing # large PDFs. text, num_pages, pagemap = await asyncio.to_thread(self._extract_pypdf, source) # Low yield → likely CJK fonts that pypdf cannot decode; try pdfminer fallback if num_pages > 0 and len(text.strip()) < num_pages * _MIN_CHARS_PER_PAGE: logger.debug( "pypdf yielded %d chars for %d page(s) in %s — trying pdfminer fallback", len(text.strip()), num_pages, source, ) fallback = await asyncio.to_thread(self._extract_pdfminer, source) if len(fallback.strip()) > len(text.strip()): text = fallback pagemap = {1: 1} # pdfminer has no page-level info return ExtractedContent(text=text, source_path=source, metadata={"pages": num_pages, "page_boundaries": pagemap}) def _extract_pypdf(self, source: str) -> tuple[str, int, dict[int, int]]: try: parts = [] page_texts: list[str] = [] with open(source, "rb") as f: reader = pypdf.PdfReader(f) num_pages = len(reader.pages) for page in reader.pages: t = page.extract_text() page_texts.append(t or "") if t: parts.append(t) pagemap = _build_pagemap(page_texts) return "\n".join(parts), num_pages, pagemap except (FileNotFoundError, IsADirectoryError): raise except Exception as exc: raise ValueError( f"Cannot read '{source}' as a PDF file: {exc}. " "Ensure the file is a valid PDF document." ) from exc def _extract_pdfminer(self, source: str) -> str: try: from pdfminer.high_level import extract_text return extract_text(source) or "" except Exception as exc: logger.debug("pdfminer fallback failed for %s: %s", source, exc) return "" -
__init__.py 0 B
-
-
requirements.txt 19 B
pypdf pdfminer.six -
SKILL.md 1.1 KB
--- name: pdf version: "1.0" description: Extract text from PDF documents entry: script: scripts/main.py class: PdfSkill triggers: extensions: - ".pdf" intents: - "pdf" - "research paper" requires: - pypdf - pdfminer.six author: axoviq.com license: AGPL-3.0-or-later --- # PDF Skill Extracts text from PDF files using `pypdf` as the primary parser, with `pdfminer.six` as a fallback for CJK fonts that pypdf cannot decode (detected when pypdf yields fewer than 50 characters per page on average). ## Setup ```bash pip install pypdf pdfminer.six ``` ## Standalone usage ```python import asyncio from synthadoc.skills.pdf.scripts.main import PdfSkill skill = PdfSkill() async def main(): result = await skill.extract("/path/to/paper.pdf") print(result.text) # extracted text from all pages print(result.metadata) # {"pages": N, "cjk_fallback": bool, ...} asyncio.run(main()) ``` ## When this skill is used - Source path ends with `.pdf` - User intent contains: `pdf`, `research paper` ## Scripts - `scripts/main.py` — `PdfSkill` class ## References - `references/cjk-notes.md` — notes on CJK font handling
Comments (0)
Sign in to join the conversation.
Reviews (0)
No reviews yet.
No comments yet.