Convert research paper PDFs into professional HTML presentations with precise figure+legend capture.
Instead of extracting low-resolution embedded images, this tool renders each PDF page at 300 DPI, detects figure and table bounding boxes, extends the crop region to include the caption/legend, and produces a single combined image with perfect visual fidelity. Each figure and table occupies exactly one centered slide with explanatory bullets drawn from the paper's Results and Discussion sections.
- Precise figure+legend capture — renders PDF pages at 300 DPI, crops figure+caption as one high-resolution image preserving exact typography, panel labels, and annotations
- Smart caption detection — geometry-based search for
Fig,Figure,Table,Tbl.with multi-block continuation and column-safe overlap filtering - Professional themes — paper, corporate, and data-focus themes with auto navy/gold accent when Manhattan plots or red heatmaps are detected
- Standalone HTML output — single self-contained file with base64-embedded images, zero build step
- Slide navigation — keyboard controls (
← → Space), fullscreen (F), speaker notes popup (S), print to PDF (Ctrl+P) - DOI/URL/arXiv input — automatically downloads PDF from DOI, URL, or arXiv link
- Frame integrity — CSS
flex: 1; min-height: 0; object-fit: containensures figures never break slide layout - Safe table rendering — page-crop images avoid the
fitz.Table.extract()SEGFAULT known in certain PDFs
pip install pymupdf pillow# Step 1: Extract figures, tables, and text from a paper PDF
python3 scripts/extract_paper.py paper.pdf -o paper_assets.json
# Step 2: Generate a standalone HTML presentation
python3 scripts/generate_slides.py paper_assets.json -o paper_slides.html --theme paperOpen paper_slides.html in any browser. Navigate with ← → Space, press F for fullscreen, S for speaker notes, Ctrl+P for PDF export.
python3 scripts/extract_paper.py 10.1038/s41586-024-XXXXX -o paper_assets.json
python3 scripts/extract_paper.py https://arxiv.org/pdf/2401.XXXXX -o paper_assets.jsonusage: extract_paper.py [-h] [-o OUTPUT] [--dpi DPI] [--min-size MIN_SIZE]
[--max-caption MAX_CAPTION] [--fig-only]
input
positional arguments:
input PDF path, DOI, or PDF URL
options:
-o, --output Write JSON to file (default: stdout)
--dpi DPI Render resolution (default: 300, range: 200-600)
--min-size MIN_SIZE Minimum figure dimension in pixels (default: 100)
--max-caption MAX_CAPTION Maximum caption characters (default: 900)
--fig-only Skip text section extraction (faster)
Output JSON structure:
metadata— title, authors, abstractsections— Introduction, Methods, Results, Discussion, Conclusionfigures[]— combined figure+legend images (base64), captions, aspect ratios, bounding boxes, color statstables[]— rendered table+legend images (base64), captions, extracted rowsdiagnostics— extraction warnings and errors
usage: generate_slides.py [-h] -o OUTPUT [--theme {paper,corporate,data-focus}]
json_path
positional arguments:
json_path JSON file from extract_paper.py
options:
-o, --output Output HTML file (required)
--theme Presentation theme
| Theme | Style | Best for |
|---|---|---|
paper (default) |
Warm serif, off-white background | Biomedical, academic talks |
corporate |
Clean sans-serif, navy/gold accent | Industry, conferences |
data-focus |
Stats-focused palette | Data-heavy presentations |
The theme auto-selects a navy/gold accent palette when extracted figures contain a high fraction of red pixels (threshold: red_fraction ≥ 0.035), which typically indicates Manhattan plots or red heatmaps.
Traditional PDF figure extraction calls doc.extract_image(xref) to pull embedded images. This approach suffers from:
- Low resolution — many PDFs embed 72 DPI thumbnails while the print resolution is 300+ DPI
- Missing annotations — axis labels, panel markers (A/B/C), and statistical annotations are often vector overlays not captured in the embedded image
- Caption separation — captions must be separately extracted from text blocks, then re-paired with figures
Paper Slide Maker v2 takes a different approach:
┌──────────────────────────────────────────────────────────┐
│ 1. Render PDF page at 300 DPI │
│ page.get_pixmap(dpi=300) │
│ │
│ 2. Detect figure bounding boxes │
│ page.get_image_rects(xref) │
│ │
│ 3. Find caption/legend text blocks near each figure │
│ Prefix match: /^\s*(Fig|Figure|Table)/i │
│ Search below first (figures), above first (tables) │
│ Horizontal overlap check: avoids adjacent column text │
│ Multi-block merge: ≤18pt vertical gap rule │
│ │
│ 4. Extend crop region to include the legend │
│ crop_rect = figure_rect ∪ caption_rect + 8px padding │
│ │
│ 5. Crop the rendered page to the extended region │
│ → ONE image preserving figure + legend as published │
│ │
│ 6. Place image centered on the slide │
│ CSS: flex: 1; min-height: 0; object-fit: contain │
│ │
│ 7. Add explanatory bullets below the image │
│ Extracted from Results/Discussion sections using │
│ keyword overlap scoring + statistical signal detection│
└──────────────────────────────────────────────────────────┘
┌──────────────────────────────────────┐
│ Page 3 · Figure 1 [kicker] │
│ GWAS identifies 12 loci [title] │
│ │
│ ┌──────────────────────────┐ │
│ │ │ │
│ │ FIGURE + LEGEND │ │
│ │ (combined image, │ │
│ │ object-fit: contain) │ │
│ │ │ │
│ └──────────────────────────┘ │
│ │
│ • 4 loci replicated across cohorts │
│ • Top SNP rs1234 reaches p=5×10⁻⁹ │
│ • Lead variant explains 0.8% of │
│ phenotypic variance │
└──────────────────────────────────────┘
The caption/legend is visually part of the image — it is NOT duplicated as separate HTML text below. The explanatory bullets are independently derived from the paper's narrative text, not extracted from the legend.
@dataclass
class CaptionMatch:
text: str # Cleaned caption text
rect: fitz.Rect # Precise bounding rectangle for crop extension
direction: str # "below" or "above" — search direction
confidence: float # 0-100 scoring
# Search strategy:
# Figures: search BELOW first (y1 to y1+200pt), then ABOVE (y0-160pt to y0)
# Tables: search ABOVE first (table captions precede), then BELOW
#
# For each candidate text block:
# 1. Match caption prefix via CAPTION_START_RE
# 2. Verify horizontal overlap with object bbox (column-safe)
# 3. Merge continuation blocks within 18pt vertical gap
# 4. Stop at: new caption start, section heading, 900-char limit
# 5. Return CaptionMatch with merged text and union rectpaper-slide-maker/
├── SKILL.md # Codex skill definition
├── README.md # This file
├── LICENSE # MIT License
├── scripts/
│ ├── extract_paper.py # [NEW] Precise page-render + legend extraction
│ ├── generate_slides.py # HTML presentation generator with make-slide themes
│ └── extract_figures.py # [LEGACY] Embedded-image extraction (kept for reference)
├── references/
│ └── figure_layout.md # CSS layout constants and rules
└── .gitignore
- Python 3.10+
- PyMuPDF (
pymupdf) — PDF rendering and text extraction - Pillow (
pillow) — image processing - Network access only when the input is a DOI or URL (PDF download)
pip install pymupdf pillowThis project builds on the make-slide design system by Kuneosu — a universal framework for AI agents to generate standalone HTML slide decks with 10 professional themes.
- GitHub: https://github.com/Kuneosu/make-slide
- Theme Gallery: https://make-slide.vercel.app
- License: MIT
make-slide themes inspired the color palettes and CSS architecture used in this project's presentation generator. The Pretendard font is by Orioncactus.
If you use paper-slide-maker in your work, please cite:
@software{paper_slide_maker,
author = {Lee, Mihyun},
title = {Paper Slide Maker: Precise Figure+Legend Capture for PDF-to-Presentation},
year = {2026},
publisher = {GitHub},
url = {https://github.com/wmyung/paper-slide-maker}
}
@software{make_slide,
author = {Kuneosu},
title = {make-slide: Universal HTML Presentation Generator},
year = {2025},
publisher = {GitHub},
url = {https://github.com/Kuneosu/make-slide}
}- Vector-only figures: If a figure is purely vector graphics with no embedded image,
get_image_rects()does not detect it. Use the legacyextract_figures.pyor a different PDF renderer for such papers. - File size: A 20-page paper with 10 figures at 300 DPI produces ~50-100 MB JSON. Use
--dpi 200for smaller output. - Two-column layouts: Caption detection may occasionally include text from an adjacent column. The
horizontally_related()function mitigates this but is not perfect. - OCR PDFs: Scanned PDFs without a text layer produce empty captions and sections. Use PDFs with embedded text for best results.
MIT License — see LICENSE.
This project is released under MIT. The upstream make-slide is also MIT licensed.