| name | pdf-annotation-reader |
| description | Extract reviewer comments hidden behind underlines/highlights in a PDF. Use when a user shares a "reviewed" or "annotated" PDF (thesis feedback, contract redlines, manuscript comments, design markups) and asks what the reviewer wrote, what to fix, or where the comments are — especially when only colored marks are visible but no popup text is rendered. |
pdf-annotation-reader
When to invoke
Trigger this skill whenever the user:
- Sends a PDF with colored underlines / highlights / wavy lines and asks what they mean
- Says things like "can you see the teacher's comments / 老师的批注 / 导师批注 / reviewer feedback / redlines"
- Asks you to summarize / list / apply PDF review feedback
- Wants to convert annotations into a TODO list or apply them to a source document
Do NOT confuse with: Word .docx track-changes (those live in word/document.xml <w:ins>/<w:del>), Google Docs suggestions, or visual-only stylistic marks (squiggles from spell-check).
What the skill does
PDF reviewers add Annot objects to pages. Each annotation has:
- A markup region (the highlight/underline you can see) — exposed via
annot.vertices as quad points
- A popup comment (the text the reviewer typed) — often invisible until you hover, but lives in
annot.info["content"]
- Metadata: author, timestamps, type (
Underline, Highlight, StrikeOut, Text for sticky notes, FreeText, etc.)
This skill reads them all with PyMuPDF.
How to use
1) Quick CLI (recommended)
pip install pymupdf
python extract_annotations.py path/to/reviewed.pdf
python extract_annotations.py path/to/reviewed.pdf --markdown
python extract_annotations.py path/to/reviewed.pdf --json
Output (text mode):
===== Page 8 =====
[1] Underline by 23727
underlined: '这一组'
★ comment : '去掉这一组'
[2] Underline by 23727
underlined: '趋势'
★ comment : '发展趋势'
2) Inline Python (when you need the data inside a larger pipeline)
import fitz
doc = fitz.open("reviewed.pdf")
for page_num, page in enumerate(doc, start=1):
for a in page.annots() or []:
info = a.info
comment = info.get("content", "").strip()
if getattr(a, "vertices", None):
xs = [p[0] for p in a.vertices[:4]]
ys = [p[1] for p in a.vertices[:4]]
rect = fitz.Rect(min(xs), min(ys), max(xs), max(ys))
underlying = page.get_text("text", clip=rect).strip()
else:
underlying = page.get_text("text", clip=a.rect).strip()
if comment:
print(f"P{page_num}: '{underlying}' → '{comment}'")
Recommended workflow
- Extract annotations with the CLI/script above.
- Group by type: format fixes (quotes, spacing) vs content fixes (wording, equations).
- Cross-check the source manuscript (.tex / .docx / .md) — annotated PDFs may be from an older draft; some fixes might already be in the current source.
- Apply fixes, then rebuild the PDF and re-extract with this skill to confirm zero annotations remain (or that only "approved" ones do).
Edge cases
- No annotations returned but reviewer says they exist → ask the user to re-export the PDF with "Include comments and markup" checked in their PDF editor; some flatten-on-export workflows drop the annotation layer.
- Annotations contain only the markup, no popup text → the reviewer didn't type a comment, only highlighted. Treat the underlined text itself as the signal (typically: "fix this").
- Cyrillic / mixed-script comments mojibake'd → the annotation was stored with a non-UTF8 encoding. Try
info.get("content") as bytes and decode with gbk / cp1251 / etc.
- Stamps and ink (drawn) annotations → no extractable text. Note their presence and page location only.
- Form fields confused for annotations → filter by
a.type[1] in {"Underline","Highlight","StrikeOut","Text","FreeText","Squiggly","Caret"}.
Output formats supported
text (default, terminal-friendly)
--markdown (paste into a report or PR description)
--json (machine-readable for further automation)
Dependencies
Only pymupdf (a.k.a. fitz). No system libs, no Java, no Adobe SDK.