跳到主要内容

pdf 精选

Read, extract (text/tables), create, merge/split/rotate, watermark, encrypt,

数据 Codex CLIMCP 客户端 Apache-2.0 4.1 万
在 GitHub 打开

安装方式

cp -r deeptutor/skills/builtin/pdf ~/.claude/skills/pdf

把该 skill 目录放进客户端 skills 目录。不同客户端的目录可能不同,Claude Code 默认是 ~/.claude/skills/

整仓一次性获取:git clone --depth 1 https://github.com/HKUDS/DeepTutor.git

依赖与权限

  • 无需 API Key
  • 无需联网
  • 依赖外部命令:python pip pandoc tesseract make convert soffice
  • 以上为按正文与 frontmatter 自动推断的信号,仅供参考;安装前建议先阅读原文。

SKILL.md 正文

来源 HKUDS/DeepTutor@6cf793b · 许可 Apache-2.0 · 内容未修改

PDF

Work PDFs in the sandbox with preinstalled Python libs. Pick the library by task:

  • Extract text/tables/layout/word-coordinates → pdfplumber; quick raw text or page ops → pypdf.
  • Merge / split / rotate / crop / watermark / encrypt / metadata → pypdf.
  • Fill forms → pypdf (fillable AcroForm fields) or annotation overlay (flat forms).
  • Create from scratch → reportlab.

Use exec with complete Python source (language: python). Prefer creating,

reopening, and validating the PDF in one call; later calls can revise the same

relative filename. Follow the turn's User workspace instructions for

locating inputs, output boundaries, and presenting the finished file.

Preserve an explicitly requested quantity (such as 500 words) and verify the count in the output before finishing. If execution fails or the artifact is missing, diagnose stderr/root cause and change strategy; do not retry identical code or reduce the requested scope without asking.

Extract text and tables (pdfplumber)

import pdfplumber

with pdfplumber.open("in.pdf") as pdf:
    for i, page in enumerate(pdf.pages, 1):
        print(f"--- page {i} ---")
        print(page.extract_text() or "")  # layout-aware text
        for t in page.extract_tables():  # list of tables; each is list[row]
            for row in t:
                print(row)

Tables → Excel (one worksheet per table):

import pdfplumber
from openpyxl import Workbook

workbook = Workbook()
workbook.remove(workbook.active)
table_number = 0
with pdfplumber.open("in.pdf") as pdf:
    for page_number, page in enumerate(pdf.pages, 1):
        for table in page.extract_tables():
            if not table:
                continue
            table_number += 1
            sheet = workbook.create_sheet(f"p{page_number}_table{table_number}"[:31])
            for row in table:
                sheet.append([cell or "" for cell in row])
if table_number:
    workbook.save("tables.xlsx")

Messy tables: pass strategies, or crop a region with page.within_bbox((x0, top, x1, bottom)) first:

ts = {
    "vertical_strategy": "lines",
    "horizontal_strategy": "lines",
    "snap_tolerance": 3,
    "intersection_tolerance": 15,
}
page.extract_tables(ts)

For very large PDFs where you only need raw text, pypdf's page.extract_text() is lighter.

Scanned / image-only PDFs (be honest)

If extract_text() returns empty or garbage (e.g. (cid:NN) runs) the page is scanned. No OCR engine (tesseract) is installed and network is off, so you cannot recover that text. Say so plainly and stop — do not fabricate content or attempt pip install.

Merge / split / rotate / crop / metadata (pypdf)

from pypdf import PdfReader, PdfWriter

# Merge
w = PdfWriter()
for f in ["a.pdf", "b.pdf"]:
    for p in PdfReader(f).pages:
        w.add_page(p)
w.write("merged.pdf")

# Split: one file per page
r = PdfReader("in.pdf")
for i, p in enumerate(r.pages, 1):
    w = PdfWriter()
    w.add_page(p)
    w.write(f"page_{i}.pdf")

# Rotate page 0 by 90 degrees clockwise
r = PdfReader("in.pdf")
w = PdfWriter()
r.pages[0].rotate(90)
w.add_page(r.pages[0])
w.write("rotated.pdf")
  • Metadata: PdfReader("in.pdf").metadata (.title, .author, ...).
  • Crop: set page.mediabox.left/bottom/right/top (points, origin y=0 at bottom).
  • Encrypt: w = PdfWriter(clone_from=PdfReader("in.pdf")); w.encrypt("userpw", "ownerpw"); w.write("enc.pdf").
  • Decrypt: r = PdfReader("enc.pdf"); r.decrypt("pw") if r.is_encrypted, then read/copy pages.

Watermark (stamp one page over every page):

from pypdf import PdfReader, PdfWriter

wm = PdfReader("stamp.pdf").pages[0]
r = PdfReader("in.pdf")
w = PdfWriter()
for p in r.pages:
    p.merge_page(wm)
    w.add_page(p)
w.write("stamped.pdf")

Create PDFs (reportlab)

Flowing document (preferred for text/reports/tables — handles pagination):

from reportlab.lib.pagesizes import letter
from reportlab.lib.styles import getSampleStyleSheet
from reportlab.lib import colors
from reportlab.platypus import SimpleDocTemplate, Paragraph, Spacer, Table, TableStyle

styles = getSampleStyleSheet()
story = [
    Paragraph("Report Title", styles["Title"]),
    Spacer(1, 12),
    Paragraph("Body text. " * 20, styles["Normal"]),
]
data = [["Product", "Q1", "Q2"], ["Widgets", "120", "135"]]
tbl = Table(data)
tbl.setStyle(
    TableStyle(
        [
            ("BACKGROUND", (0, 0), (-1, 0), colors.grey),
            ("TEXTCOLOR", (0, 0), (-1, 0), colors.whitesmoke),
            ("GRID", (0, 0), (-1, -1), 0.5, colors.black),
        ]
    )
)
story += [Spacer(1, 12), tbl]
SimpleDocTemplate("out.pdf", pagesize=letter).build(story)

Absolute placement (labels at fixed coordinates): use canvas.Canvas("out.pdf", pagesize=letter), c.drawString(x, y, "...") (origin bottom-left, points), c.showPage() per page, c.save().

Non-Latin text (Chinese / Japanese / Korean, Cyrillic, …)

reportlab's built-in fonts (Helvetica/Times/Courier) carry zero CJK glyphs, so any 中文/日本語/한국어 renders as empty boxes (□) baked permanently into the PDF. reportlab never auto-discovers system fonts — you MUST register a font that has the glyphs and set it on every style. Whenever the document may contain non-Latin text, register a CJK font first (it also covers Latin, so it is safe to use as the only font):

import os
from reportlab.pdfbase import pdfmetrics
from reportlab.pdfbase.ttfonts import TTFont


def register_cjk_font(name="CJK"):
    # TrueType ONLY — reportlab cannot embed CFF/OpenType outlines, so a .otf
    # like Noto Sans CJK fails with "postscript outlines are not supported".
    for path in [
        "/usr/share/fonts/truetype/wqy/wqy-zenhei.ttc",  # Linux sandbox (fonts-wqy-zenhei)
        "/usr/share/fonts/truetype/wqy/wqy-microhei.ttc",
        "/System/Library/Fonts/STHeiti Light.ttc",  # macOS
        "/System/Library/Fonts/Hiragino Sans GB.ttc",
        "/System/Library/Fonts/Supplemental/Songti.ttc",
        "/System/Library/Fonts/Supplemental/Arial Unicode.ttf",
        "C:/Windows/Fonts/msyh.ttc",  # Windows
    ]:
        if os.path.exists(path):
            try:
                pdfmetrics.registerFont(TTFont(name, path, subfontIndex=0))
                return name
            except Exception:
                continue
    raise RuntimeError("No CJK-capable TrueType font found — do not emit tofu; say so.")


font = register_cjk_font()
styles = getSampleStyleSheet()
for s in styles.byName.values():  # make the CJK font the default everywhere
    s.fontName = font
# Tables don't read the stylesheet — set the font in the TableStyle too:
#   ("FONTNAME", (0, 0), (-1, -1), font)
# Canvas: c.setFont(font, size) before every drawString.

If register_cjk_font raises (no font on the host), do not ship a tofu PDF — tell the user the sandbox lacks a CJK font instead of producing garbage.

Gotcha: even with a good font, reportlab still needs markup for subscripts/superscripts. In Paragraph use Paragraph("H<sub>2</sub>O", styles["Normal"]), x<super>2</super>.

Markdown/HTML → PDF needs an external converter (soffice/pandoc) that is usually absent — command -v soffice / command -v pandoc and degrade to building the PDF directly with reportlab if neither is present.

Fill forms (pypdf)

First detect whether the PDF has real fillable (AcroForm) fields:

from pypdf import PdfReader

fields = PdfReader("form.pdf").get_fields()
print("fillable" if fields else "flat (no fields)")

Fillable — inspect field names/types, then fill and write:

from pypdf import PdfReader, PdfWriter

r = PdfReader("form.pdf")
for name, f in r.get_fields().items():
    print(name, f.get("/FT"), f.get("/_States_"))  # /Tx text, /Btn checkbox/radio, /Ch choice

w = PdfWriter(clone_from=r)
values = {"first_name": "Bart", "agree": "/Yes"}  # checkbox/radio: use its on-state, NOT True/False
for page in w.pages:
    w.update_page_form_field_values(page, values, auto_regenerate=False)
w.set_need_appearances_writer(True)  # force viewers to render the values
w.write("filled.pdf")

Checkbox/radio values are on-state strings, not booleans — read the field's /_States_ (e.g. /Yes, /On); /Off clears it.

Flat form (no fields) — overlay text with FreeText annotations at PDF coordinates. Get real coordinates from the layout with pdfplumber instead of guessing:

import pdfplumber

with pdfplumber.open("form.pdf") as pdf:
    pg = pdf.pages[0]
    for wd in pg.extract_words():  # each has x0, top, x1, bottom (TOP-left origin!)
        print(wd["text"], wd["x0"], wd["top"])
    for rc in pg.rects:  # small squares are likely checkboxes
        print("rect", rc["x0"], rc["top"], rc["x1"], rc["bottom"])

pdfplumber top is measured from the page top; pypdf rects are bottom-left, so convert: pdf_y = page_height - top. Place text just right of the matching label:

from pypdf import PdfReader, PdfWriter
from pypdf.annotations import FreeText

r = PdfReader("form.pdf")
w = PdfWriter()
w.append(r)
h = float(r.pages[0].mediabox.height)
top = 700  # pdfplumber 'top' of the label's row
w.add_annotation(
    page_number=0,
    annotation=FreeText(
        text="Smith",
        rect=(255, h - top - 14, 720, h - top),  # (x0, y0, x1, y1)
        font="Helvetica",
        font_size="10pt",
        font_color="000000",
        border_color=None,
        background_color=None,
    ),
)
w.write("filled.pdf")

Verify: re-open the output and re-read get_fields() values (fillable) or re-extract text (overlay) to confirm the values landed.

Page → image rendering (PyMuPDF)

PyMuPDF (imported as fitz, preinstalled) rasterizes pages — useful to inspect a PDF visually or to hand a page to an image-capable step. No external tools needed (poppler / pdf2image are absent; don't reach for them).

import fitz  # PyMuPDF

doc = fitz.open("in.pdf")
for i, page in enumerate(doc, 1):
    page.get_pixmap(dpi=150).save(f"page_{i}.png")  # higher dpi = sharper + larger

fitz also extracts text (page.get_text()) and can render a sub-region via page.get_pixmap(clip=fitz.Rect(x0, y0, x1, y1)). It does not OCR — a rendered scanned page is still just pixels (see Scanned PDFs above).

质量与来源

所属仓库
HKUDS/DeepTutor
仓库星标
4.1 万
最近更新
今天
采集时间
今天
commit
6cf793bd868b
内容指纹
sha256:c950efb9d180879
文件路径
deeptutor/skills/builtin/pdf/SKILL.md

相似 Skill

更多「数据」
freehire-search
Use this skill to search live software / tech / data / engineering job listings across many countries and markets (and remote) via the freehire.me aggregator's public API, or to look up a specific posting. It aggregates roles from ~50 ATS platforms into one schema, so a single skill covers many markets — but its faceted filtering (skills, category, seniority) is tuned tech-first, so scope triggers to technical roles. Trigger phrases: find a tech job, software job search, developer jobs, engineering vacancies, data/ML jobs, DevOps roles, remote developer jobs, "are there any <tech role> jobs in <place>", look up this freehire job posting.
精选 MadsLorentzen/ai-job-search 4.6 万 原文
Claude CodeCodex CLI
jobbank-search
Make sure to use this skill whenever the user mentions anything related to job searching on Akademikernes Jobbank, jobbank.dk, or looking for academic or highly educated positions in Denmark — even if they don't mention jobbank.dk explicitly. Also invoke this skill for questions about Danish job listings, graduate trainee positions, Ph.d. jobs, or finding work in specific industries or regions in Denmark. Trigger phrases include: jobbank, akademikernes jobbank, jobs denmark, academic jobs denmark, find job denmark, highly educated jobs, graduate job denmark, trainee position denmark, ph.d. position denmark, postdoc denmark, studiejob, fuldtidsjob, deltidsjob, vikariat, freelance job, praktikplads, job søgning, jobsøgning, søg job, ledige stillinger, nye jobs, it jobs denmark, engineering jobs denmark, marketing jobs denmark, finance jobs denmark, healthcare jobs denmark, remote job denmark, fjernarbejde, job københavn, job aarhus, job odense, nyuddannede job, job til nyuddannede, international job denmark, jobbank søgning, find stilling, data scientist job, software developer job, projektleder stilling, konsulent job, data analyse job.
精选 MadsLorentzen/ai-job-search 4.6 万 原文
Claude CodeCodex CLI
jobdanmark-search
Make sure to use this skill whenever the user mentions anything related to Danish job listings, job search in Denmark, finding work in Denmark, or job vacancies on Jobdanmark — even if they don't explicitly mention jobdanmark.dk. Also invoke this skill for questions about specific Danish job categories, municipalities, job types, or salaries in a job-search context. Trigger phrases include: danish jobs, jobs in denmark, find job denmark, job search denmark, danish job listings, jobdanmark, job opslag, find job, jobsøgning, ledige stillinger, stillingsopslag, job i Danmark, fuldtidsjob, deltidsjob, studiejob, praktikplads, elev, fleksjob, IT job denmark, sygeplejersker job, håndværker job, ingeniør job, pædagog job, kontor job, leder job, salg job, hotel job, kirke job, job aarhus, job københavn, job odense, job aalborg, job sjælland, job jylland, job fyn, jobkategorier denmark, ledige job, ansøgningsfrist, søg job, job opslaget, jobopslag, danish vacancies, work in denmark, employment denmark, job denmark, jobs near me denmark, apprentice denmark, internship denmark, part-time denmark, full-time denmark.
精选 MadsLorentzen/ai-job-search 4.6 万 原文
Claude CodeCodex CLI
jobindex-search
Make sure to use this skill whenever the user wants to search for jobs in Denmark, find Danish job listings, look up a specific job posting, or asks anything about the Danish job market — even if they don't mention jobindex.dk explicitly. Invoke this skill for questions about open positions, job vacancies, hiring in Denmark, job opportunities in Danish cities or sectors, or when the user wants to find work in Denmark. Also trigger for phrases like "find me a job", "are there any jobs for X in Copenhagen", or "what jobs are available in Aarhus" when the context is Denmark. Trigger phrases include: jobindex, jobsøgning, job i Danmark, ledige stillinger, job opslag, find job, stillingopslag, jobannonce, job vacancy denmark, danish jobs, jobs in denmark, job search denmark, work in denmark, find work denmark, IT jobs denmark, engineer jobs denmark, developer jobs copenhagen, marketing jobs aarhus, jobs aarhus, jobs copenhagen, jobs odense, jobs aalborg, job openings denmark, hiring denmark, job listings denmark, python jobs denmark, grafisk designer job, data engineer job, softwareudvikler job, full stack developer job danmark.
精选 MadsLorentzen/ai-job-search 4.6 万 原文
Claude CodeCodex CLI
jobnet-search
Make sure to use this skill whenever the user mentions anything related to Danish job searching, job listings, job vacancies, employment opportunities in Denmark, or the Danish government job portal — even if they don't mention jobnet.dk explicitly. Also invoke this skill for questions about specific job titles, occupations, employers, or regions in a Danish employment context. This skill covers the official Danish public job portal operated by STAR (Styrelsen for Arbejdsmarked og Rekruttering). Trigger phrases include: danish jobs, danish job search, jobnet, jobnet.dk, find job denmark, danish employment, job i danmark, job på jobnet, offentlige job, stillinger i det offentlige, public sector jobs denmark, government jobs denmark, STAR jobs, job ledige stillinger, ledig stilling, søg job, job opslag, job vacancy denmark, stillingopslag, jobopslag, sygepleje job, ingeniør job, lærer job, pædagog job, it-job denmark, jobs in copenhagen, jobs in aarhus, jobs in odense, deltidsjob, fuldtidsjob, fastansættelse, tidsbegrænset ansættelse, fleksjob, sygeplejerske job, social worker job denmark, occupation search denmark, esco occupation, job deadline, ansøgningsfrist, søg efter job, full time job denmark, part time job denmark.
精选 MadsLorentzen/ai-job-search 4.6 万 原文
Claude CodeCodex CLI
linkedin-search
Use this skill whenever the user wants to search for jobs in any location or market, find job listings, or look up a specific job posting — in any country, city, or remotely. Invoke for open positions, vacancies, and hiring across any sector or role (software, data, design, marketing, finance, legal, operations, etc.). The location is always supplied explicitly by the user. Trigger phrases: find a job, job search, search for jobs, job openings, vacancies, hiring, positions open, remote jobs, "are there any X jobs in <place>", look up this job posting.
精选 MadsLorentzen/ai-job-search 4.6 万 原文
Claude CodeCodex CLI