agentsclimarketplace

Cropfig

Skill youngeun1209/oh-my-claudecode-research/skills/cropfig

Three-step pipeline from a Keynote (.key) or PowerPoint (.pptx) deck to manuscript + outline. Func 1 exports vector PDFs per slide; func 2 crops them (vector preserved) and renders an outline-grade PNG from the same cropped artifact; func 3 copies PDFs into the LaTeX manuscript figures dir and PNGs into the outline.md figures dir, then inserts/updates image links after each result heading.From its SKILL.md

Install
npx -y skills add youngeun1209/oh-my-claudecode-research --skill cropfig

Assembled from the repository path, not quoted from the project. Check it against their README if it does not work.

SKILL.md

12.2 KB, as published. Nobody here has run it

<Purpose> Three-step pipeline that produces, from a `.key` or `.pptx` deck, both manuscript-ready vector PDFs and outline-ready raster PNGs of the figure-only content (top label and bottom caption stripped), and uploads both into the right places. Both image artifacts derive from the same cropped PDF so they cannot drift; the outline embed is idempotent (re-running replaces the link rather than duplicating). </Purpose>

<Use_When>

  • You author figures in Keynote/PowerPoint and need (a) vector PDFs to drop into a LaTeX manuscript and (b) low-resolution PNGs to embed into an outline doc that already carries caption text.
  • The /sync command needs to embed figure-only PNGs into an outline document at each result section.
  • Preparing figures for slides / social-media / a reviewer response where the caption is supplied separately. </Use_When>

<Do_Not_Use_When>

  • The caller wants the slide with its caption / label intact (the full-slide PDFs are intermediate and discarded — only cropped artifacts are saved).
  • The deck has no consistent "Figure N. Title" top label or trailing caption (the heuristic still tight-bboxes whitespace, so this is safe but wasted effort).
  • You need a different crop policy (e.g. keep top label, drop bottom only) — the band-classification heuristic is symmetric and not configurable per-side. </Do_Not_Use_When>
<Configuration> Required inputs: the deck path + (for func 3) the manuscript dir and outline file. Cropping outputs land next to the deck by default; func 3 reads from there and copies into the manuscript + outline-parent figures dirs.
VariableDefaultUsed byPurpose
DECK_FILE(required)func 1, 2, 3Path to a .key or .pptx deck. Step 1 + 2 read it directly; step 3 derives <deck_parent>/pdf/ and <deck_parent>/png/ from it.
MANUSCRIPT_DIRpaperfunc 3LaTeX manuscript root. Cropped PDFs land in <MANUSCRIPT_DIR>/figures/figureNN.pdf. Matches /omcr-setup's default.
OUTLINE_FILEoutline.mdfunc 3Outline markdown file. PNGs land in <dirname(OUTLINE_FILE)>/figures/figureNN.png; image links are inserted into the outline itself.
RESULT_PATTERN^### Result (\d+)func 3Regex with a single capture group = figure number. Used to find result headings in the outline.
CROPFIG_PROBE_DPI100func 2DPI used for the probe rasterization that drives crop-bound detection. Higher = slower but slightly tighter crops.
CROPFIG_PNG_DPI120func 2Output DPI for the outline PNGs. Raise for sharper outline review (and larger docx); lower for slimmer docx.

Output locations.

<dirname($DECK_FILE)>/
├── <deck>.key|pptx               # the deck itself
├── pdf/figureNN.pdf              # func 2 writes (manuscript-grade vector)
└── png/figureNN.png              # func 2 writes (outline-grade raster)

$MANUSCRIPT_DIR/
└── figures/figureNN.pdf          # func 3 copies here for the .tex

dirname($OUTLINE_FILE)/
├── $OUTLINE_FILE                 # func 3 inserts/updates ![Figure N](...) links
└── figures/figureNN.png          # func 3 copies here for the markdown

These defaults match /omcr-setup's conventions (Manuscript dir: paper/, Outline file: outline.md, Result pattern: ^### Result (\d+)). See wiki/Configuration.md for the CLAUDE.md Research-stack schema.

On first invocation with no DECK_FILE and no project Deck file in CLAUDE.md, ask the user for the deck path then offer to persist it to the project's CLAUDE.md. </Configuration>

<Steps>

Step 1 — Export deck to per-slide PDFs (export_deck.py)

Run the exporter with a temp staging directory:

STAGE=$(mktemp -d)
python3 "$CLAUDE_PLUGIN_ROOT"/skills/cropfig/export_deck.py "$DECK_FILE" "$STAGE"

Dispatch inside export_deck.py — each format uses its native app to avoid cross-format import drift:

  • .key → macOS Keynote via AppleScript → single deck PDF.
  • .pptx → macOS PowerPoint via AppleScript when /Applications/Microsoft PowerPoint.app is present; otherwise LibreOffice headless (soffice --convert-to pdf).

The single deck PDF is then split into figure01.pdf, figure02.pdf, … using PyMuPDF. These per-slide PDFs are vector (text and lines stay sharp at any zoom) and feed Step 2.

Guard: if export fails (missing tool, permission denied, headless env), surface the error and stop.

Step 2 — Crop + emit both artifacts (crop_figures.py)

python3 "$CLAUDE_PLUGIN_ROOT"/skills/cropfig/crop_figures.py "$STAGE"
rm -rf "$STAGE"

This writes to <dirname($DECK_FILE)>/pdf/ and <dirname($DECK_FILE)>/png/. To write elsewhere, pass positional output dirs:

python3 "$CLAUDE_PLUGIN_ROOT"/skills/cropfig/crop_figures.py "$STAGE" "$PDF_OUT" "$PNG_OUT"

For each slide PDF, the script:

  1. Renders a CROPFIG_PROBE_DPI probe and runs find_crop_bounds from crop_bounds.py (band-classification heuristic on top + bottom; tight-bbox on remaining content).
  2. Maps the pixel bounds back to PDF point coordinates and applies them as a CropBox on a copy of the slide — output stays vector.
  3. Writes the cropped PDF next to the deck under pdf/figureNN.pdf.
  4. Re-rasterizes the cropped PDF at CROPFIG_PNG_DPI → png/figureNN.png next to the deck.

Both outputs share the same crop decision because the PNG is a downsampled view of the manuscript PDF.

Step 3 — Upload to manuscript + outline (upload_figures.py)

python3 "$CLAUDE_PLUGIN_ROOT"/skills/cropfig/upload_figures.py

Reads from <deck_parent>/pdf/ and <deck_parent>/png/ (so func 2 must have run first). Does two things:

3a. Manuscript copy. For each figureNN.pdf, copies to $MANUSCRIPT_DIR/figures/figureNN.pdf. The .tex is not modified — \includegraphics{figures/figureNN} is author content.

3b. Outline embed. For each figureNN.png:

  1. Copy to <dirname($OUTLINE_FILE)>/figures/figureNN.png.
  2. Scan the outline for lines matching $RESULT_PATTERN. For each match, the captured N is the figure number; insert (or replace, if previously inserted by this script) the link ![Figure N](figures/figureNN.png) immediately after the heading (skipping blank lines).

The embed is idempotent — re-running replaces the existing link line with the same content rather than appending a new one. Result heading without a matching figure is reported as missing; figure with no matching result heading is reported as orphan.

Step 4 — Report

## Cropfig run — YYYY-MM-DD

- Deck: $DECK_FILE (N slides)
- Manuscript PDFs: <dirname($DECK_FILE)>/pdf/ (N files)
- Outline PNGs:    <dirname($DECK_FILE)>/png/ (N files, $CROPFIG_PNG_DPI DPI)
- Top label removed: A / N
- Bottom caption removed: B / N

Upload:
- $MANUSCRIPT_DIR/figures/: N PDF(s) copied
- $OUTLINE_FILE: M heading(s) updated  (missing: ..., orphan: ...)

Slides where heuristic skipped a side (still tight-bboxed):
- <filename> — <which side>

If Step 1 failed, report only that and skip the rest. Steps 2 and 3 can run independently if a prior staging dir or PDF/PNG dir already exists.

</Steps>

<Output_Contract>

PathOwnerContentUsed by
<dirname($DECK_FILE)>/pdf/figureNN.pdffunc 2cropped vector PDF (no top label, no bottom caption, tight-bbox); page CropBox setsource of truth — func 3 reads here for the manuscript copy
<dirname($DECK_FILE)>/png/figureNN.pngfunc 2rasterized view of the same cropped PDF at CROPFIG_PNG_DPIsource of truth — func 3 reads here for the outline copy
$MANUSCRIPT_DIR/figures/figureNN.pdffunc 3 (copy)identical to the source PDF\includegraphics{figures/figureNN} in the .tex
<dirname($OUTLINE_FILE)>/figures/figureNN.pngfunc 3 (copy)identical to the source PNG![Figure N](figures/figureNN.png) in the outline
$OUTLINE_FILEfunc 3 (modifies)image link inserted after each result headingoutline rendering (GitHub, VS Code preview, Pandoc → docx)

Filenames mirror across the four image locations so callers can pair figureNN.pdf with figureNN.png. The staging dir from Step 1 is ephemeral and deleted at the end of the run; no uncropped intermediate is persisted.

</Output_Contract>

<Failure_Modes>

SymptomCauseAction
ModuleNotFoundError: fitzPyMuPDF not installedpython3 -m pip install pymupdf
ModuleNotFoundError: numpy / PILbare environmentpython3 -m pip install numpy pillow
ERROR: .key export requires macOS + Keynoterunning on non-macOS with a .key deckrun on macOS, or re-save the deck as .pptx and retry
Keynote export failed: ...Keynote not installed, deck password-protected, or AppleScript permission deniedgrant System Settings → Privacy & Security → Automation → Terminal/your shell → Keynote
PowerPoint export failed: ...PowerPoint not licensed/signed in, deck password-protected, or AppleScript permission deniedgrant System Settings → Privacy & Security → Automation → Terminal/your shell → Microsoft PowerPoint. Failures are no longer silently swallowed — there is no auto-fallback when PowerPoint IS installed.
PowerPoint export failed: ... -9074Office sandbox refused to write to the staging paththe script already stages under ~/Library/Caches/cropfig/ to satisfy Office sandbox; if you see this anyway, check that ~/Library/Caches/ is writable and Office has been granted Full Disk Access if your deck is on a network volume
LibreOffice (soffice) not on PATHLinux/PowerPoint-not-installed path with no LibreOfficebrew install --cask libreoffice (mac) / apt install libreoffice (linux)
Top label or bottom caption NOT removed on a slideband thinner than MIN_LABEL_HEIGHT=30 rows, or contains a colored element (heuristic treats it as figure content)accept — tight-bbox still removes whitespace; don't globally lower the threshold to catch one slide
Cropped PDF still shows label when opened in some viewersviewer ignores CropBox and renders MediaBoxuse a viewer that honors CropBox (Preview, Acrobat, most LaTeX rasterizers do); the PNG path is unaffected
Output PNG looks blurry in docxCROPFIG_PNG_DPI too lowraise to 150 / 180 (file sizes grow proportionally)
func 3 reports headings without matching figure: #Noutline has more ### Result N headings than the deck has slides foreither add the missing slide to the deck, or trim the outline heading; func 3 will not invent a figure
func 3 reports orphan figures: figureNdeck has more slides than the outline has result headingsextend the outline with ### Result N, or accept the orphan if those slides are non-result (concept, schematic, appendix)
WARN (3b copy): PNG source dir not foundfunc 2 has not run yet, or wrote to a different locationrun func 1 + 2 first; verify <dirname($DECK_FILE)>/png/ exists

</Failure_Modes>

<Files> - `SKILL.md` — this file. - `export_deck.py` — func 1: deck → per-slide vector PDFs. Dispatches by extension: `.key` → Keynote AppleScript, `.pptx` → PowerPoint AppleScript when installed, else LibreOffice headless. Splits the resulting deck PDF via PyMuPDF. Stages under `~/Library/Caches/cropfig/` on macOS so Office sandbox can write. - `crop_figures.py` — func 2: per-slide PDF → cropped PDF (vector, manuscript) + raster PNG (outline). Both share one crop decision. - `upload_figures.py` — func 3: copies cropped PDFs to `$MANUSCRIPT_DIR/figures/`, copies cropped PNGs to `<dirname($OUTLINE_FILE)>/figures/`, inserts/updates `![Figure N](...)` links in the outline after each `$RESULT_PATTERN` match. Idempotent. - `crop_bounds.py` — pure heuristic module. `find_top_cut`, `find_bottom_cut`, `find_crop_bounds(arr)` — used by `crop_figures.py`. Tune constants at the top if your slide-deck layout changes (most likely tweak: `MIN_LABEL_HEIGHT` for shorter labels). </Files>

Keep looking

Skills are one crate of 627,801. Ordering is by how many stacks a row turns up in, so the top of any crate is what has actually been picked rather than what has the most stars.