--- name: glmv-pdf-to-ppt description: Convert a PDF (research paper, report, or any document) into a polished multi-slide HTML presentation with a structured outline JSON and summary markdown. Trigger this skill when the user mentions making slides or a PPT from a PDF โ€” in Chinese or English. metadata: openclaw: emoji: "๐Ÿ“‘" homepage: https://github.com/zai-org/GLM-V/tree/main/skills/glmv-pdf-to-ppt --- # PDF โ†’ HTML PPT Skill Convert any PDF into a multi-slide HTML presentation. Pages are converted to images at DPI 120, read sequentially to understand the content, then a structured `outline.json` is saved, images are cropped locally (no cloud upload), slides are rendered one by one, and finally a `summary.md` is generated. **Scripts are in:** `{SKILL_DIR}/scripts/` ## Dependencies Python packages (install once): ```bash pip install pymupdf pillow ``` System tools: `curl` (pre-installed on macOS/Linux). ## When to Use Trigger when the user asks to make slides or a presentation from a PDF โ€” phrases like: "make a PPT from a PDF", "convert PDF to slides", "create a presentation from this paper", "ๆ นๆฎpdfๅšppt", "ๆ นๆฎ่ฎบๆ–‡ๅšๅนป็ฏ็‰‡", "ๅšPPT", "ๅšๅนป็ฏ็‰‡", "็”Ÿๆˆๆผ”็คบๆ–‡็จฟ", "ๆŠŠ่ฟ™ไธชpdf่ฝฌๆˆppt", or any similar intent in Chinese or English. ## Output Directory Convention All output goes under `{WORKSPACE}/ppt/_/`: ``` ppt/ โ””โ”€โ”€ _/ โ”œโ”€โ”€ outline.json โ† structured slide plan (SlidesPlan schema) โ”œโ”€โ”€ crops/ โ† locally-saved cropped images โ”‚ โ”œโ”€โ”€ slide3_method_crop.png โ”‚ โ””โ”€โ”€ slide5_results_crop.png โ”œโ”€โ”€ slide_01.html โ”œโ”€โ”€ slide_02.html โ”œโ”€โ”€ ... โ””โ”€โ”€ summary.md โ† final summary document ``` - `` = PDF filename without extension - `` = format `YYYYMMDD_HHMMSS` (e.g. `20240119_143022`) - Cropped images go in `crops/` subfolder - Each slide HTML references images via relative path `crops/.png` ## Input `$ARGUMENTS` is the path to the PDF file (local) or an HTTP/HTTPS URL. - If user provides a **URL**: download with curl first, then convert - If user provides a **local PDF path**: convert directly --- ## Workflow ### Phase 0 โ€” Create Output Directory Compute the output path: ```python import os, datetime pdf_stem = os.path.splitext(os.path.basename(pdf_path))[0] timestamp = datetime.datetime.now().strftime("%Y%m%d_%H%M%S") out_dir = os.path.join(workspace, "ppt", f"{pdf_stem}_{timestamp}") ``` Create it immediately: ```bash mkdir -p "/crops" ``` Record `out_dir` โ€” use it for all subsequent phases. --- ### Phase 1 โ€” Convert PDF Pages to Images (DPI 120) If the input is a URL, download it first: ```bash pdf_stem=$(basename "$ARGUMENTS" .pdf) curl -L -o "/tmp/${pdf_stem}.pdf" "$ARGUMENTS" ``` Then convert (pass either the downloaded path or the original local path): ```bash python {SKILL_DIR}/scripts/pdf_to_images.py "" --dpi 120 ``` Outputs JSON to stdout: ```json [{"page": 1, "path": "/abs/path/page_001.png"}, ...] ``` Parse and store the full `page โ†’ path` map. These local paths are used for viewing pages and as `--path` input to `crop.py`. --- ### Phase 2 โ€” Read All Pages in Order View **all page images** sequentially before planning anything. Your goal here is pure understanding โ€” absorb the full structure, content, figures, and arguments of the document. While reading, note: - What figures, charts, or tables appear on which pages - The overall arc (intro โ†’ method โ†’ results โ†’ conclusion for papers; or logical structure for other doc types) - Candidate visuals worth cropping for slides (page number + rough region) Do NOT plan or write slides yet โ€” just read and understand all pages first. --- ### Phase 3 โ€” Plan Outline & Save outline.json After reading all pages, plan 8โ€“15 slides (adapt freely for non-academic documents). | Slide | Typical purpose | |-------|----------------| | 1 | Title, authors, affiliation, venue/year | | 2 | Motivation / Problem statement | | 3 | Related Work (brief) | | 4โ€“N-2 | Method / Core contributions (one concept per slide) | | N-1 | Results & Experiments | | N | Conclusion & Future Work | For each slide that needs a visual, identify: - Which page it comes from (the local page path from Phase 1) - A description of what the visual shows and why it belongs on this slide **Save the outline as `/outline.json`** using exactly this schema: ```json { "presentation_title": "Paper Title Here", "lang": "Chinese", "total_slides": 10, "slides_plan": [ { "slide_index": 1, "title": "Slide Title", "main_content": "Key points and text content for this slide", "template_id": null, "required_crops": [ { "url": "", "visual_description": "Figure 3: architecture diagram showing encoder-decoder", "usage_reason": "Illustrates the core model structure for slide 4" } ] } ] } ``` Field notes: - `lang`: `"Chinese"` or `"English"` โ€” match the PDF language - `template_id`: always `null` - `required_crops`: empty array `[]` if this slide needs no images - `url` in each crop: the **local file path** of the source page image (from Phase 1 `path` field) โ€” this is what crop.py will open and crop from - `visual_description`: what the visual shows, including figure/table number if available - `usage_reason`: why this visual belongs on this particular slide - For images that need cropping, note the approximate region โ€” exact crop boxes are determined in Phase 4 Write `outline.json` using the Write tool to `/outline.json`. --- ### Phase 4 โ€” Crop Required Images (Grounding + Subagent) **IMPORTANT: You MUST delegate ALL cropping to a clean subagent using the Agent tool.** By this phase your context is very long (all page images + outline), which degrades visual coordinate accuracy. A fresh subagent with only the target image produces much more precise coordinates. **IMPORTANT: You MUST use the provided `{SKILL_DIR}/scripts/crop.py` script for ALL image cropping. Do NOT write your own cropping code, do NOT use PIL/Pillow directly, do NOT use any other method.** Read `outline.json`. Collect all crops needed, then launch **one subagent per source page** (or one per crop if pages differ). The subagent uses **grounding-style localization** โ€” it views the image, locates the target element, and outputs a precise bounding box in normalized 0โ€“999 coordinates. Use the Agent tool like this: ``` Agent tool call: description: "Grounding crop page N" prompt: | You are a visual grounding and cropping assistant. Your task is to precisely locate specified visual elements in a page image and crop them out. ## Grounding method Use visual grounding to locate each target: 1. Read the source image using the Read tool to view it 2. Identify the target element described below 3. Determine its bounding box as normalized coordinates in the 0โ€“999 range: - 0 = left/top edge of the image - 999 = right/bottom edge of the image - These are thousandths, NOT pixels, NOT percentages (0โ€“100) - Format: [x1, y1, x2, y2] where (x1,y1) is top-left, (x2,y2) is bottom-right - Example: [0, 0, 500, 500] = top-left quarter of the image 4. Be precise: tightly bound the target element with a small margin (~10โ€“20 units) around it. Do NOT crop too wide or too narrow. ## Source image ## Crops needed For each crop below, first do grounding (locate the element), then crop: 1. Name: "slide_" Target: "" Context: "" ## Crop command After determining the bounding box [X1, Y1, X2, Y2] for each target, run: ```bash python /scripts/crop.py \ --path "" \ --box X1 Y1 X2 Y2 \ --name "" \ --out-dir "/crops" ``` ## Verification After each crop, READ the output image to visually verify the correct region was captured. If the crop missed the target or is too wide/narrow, adjust the coordinates and re-run crop.py. ## Output Report the final results as a list: - crop_name: , file: , box: [X1, Y1, X2, Y2] ``` Replace ``, ``, ``, and crop details with actual values from your context. The crop.py script outputs JSON: `{"path": "/abs/path/slide3_method_crop.png"}` Collect results from all subagents and build the mapping: `slide_index โ†’ [crop filename, ...]` to reference in HTML. The filename will be `_crop.png`. **Launch subagents for independent pages in parallel** when possible. Wait for all to complete before proceeding. --- ### Phase 5 โ€” Measure Cropped Image Dimensions After cropping, get pixel dimensions: ```bash python3 -c " from PIL import Image; import os, json d = '/crops' sizes = {} for f in sorted(os.listdir(d)): if f.endswith('.png'): w, h = Image.open(os.path.join(d, f)).size sizes[f] = {'width': w, 'height': h, 'aspect': round(w/h, 2)} print(json.dumps(sizes, indent=2)) " ``` Use aspect ratios to pick each slide's layout: | Aspect ratio | Layout recommendation | |---|---| | **< 0.7** (tall/narrow) | `text + image` side-by-side โ€” `max-height: 600px` on image | | **0.7 โ€“ 1.3** (square-ish) | `text + image` โ€” image takes ~50% width | | **> 1.3** (wide) | Image on top or bottom, text above/below | | **> 2.0** (very wide, e.g. tables) | `full-image` โ€” spans full 1280px width, caption below | --- ### Phase 6 โ€” Generate Slides One by One For each slide, write the HTML, save it to a temp file, then call `generate_slide.py`. **Step A โ€” Write HTML** to `/tmp/slide_N.html` - All `` must use **relative paths**: `crops/_crop.png` - Do NOT use absolute paths or URLs for cropped images - Navigation is click-area based โ€” no buttons needed: - Clicking the **left half** of the slide navigates to the previous slide - Clicking the **right half** of the slide navigates to the next slide - On slide 1, left click does nothing; on the last slide, right click does nothing - Keyboard `โ†` / `โ†’` arrows also navigate - Implement with two transparent `
` overlays covering each half, positioned absolute over the slide canvas **Step B โ€” Save slide:** ```bash python {SKILL_DIR}/scripts/generate_slide.py \ --html-file /tmp/slide_N.html \ --index N \ --total \ --title "" \ --out-dir "/" ``` Repeat until all slides are saved. --- ### Phase 7 โ€” Generate summary.md Write `/summary.md` in the same language as the slides (`lang` from `outline.json`). Include: - Document title and basic info (authors, venue, year if applicable) - Brief abstract/overview (2โ€“3 sentences) - Per-slide breakdown table: slide number, title, 1โ€“2 sentence summary - Main contributions or takeaways (bullet list) - Link to `slide_01.html` to open the first slide Example structure: ```markdown # [Presentation Title] > **ๆฅๆบ / Source:** [PDF filename] | **่ฏญ่จ€ / Language:** Chinese | **ๅนป็ฏ็‰‡ๆ•ฐ / Slides:** 10 ## ๆ‘˜่ฆ [2-3 sentence overview] ## ๅนป็ฏ็‰‡ๆฆ‚่งˆ | # | ๆ ‡้ข˜ | ไธป่ฆๅ†…ๅฎน | |---|------|---------| | 1 | ๆ ‡้ข˜้กต | ... | ... ## ไธป่ฆ่ดก็Œฎ - ... ## ๐Ÿ“‚ ๆ‰“ๅผ€ๆผ”็คบๆ–‡็จฟ [โ–ถ ๅผ€ๅง‹ๆ’ญๆ”พ](slide_01.html) ``` --- ## HTML Slide Spec Each slide is a **standalone HTML file** โ€” full `โ€ฆ` with embedded CSS only. **Canvas:** fixed `1280 ร— 720 px`, `overflow: hidden` โ€” nothing scrolls. **Consistent design across all slides:** - Choose a visual style that fits the document's domain and tone โ€” no fixed palette or font required - If the user specifies a style, follow it exactly; otherwise infer from the content (e.g. a ML paper โ†’ clean modern; a historical report โ†’ editorial serif; a product pitch โ†’ bold and branded) - Same fonts, colors, and spacing system applied uniformly to every slide - Every slide shows: slide title, page counter (bottom-right corner), presentation title (subtle footer) **Navigation on each slide:** - Two transparent click areas cover the full slide height: left 50% โ†’ previous slide, right 50% โ†’ next slide - On slide 1 the left area is inert; on the last slide the right area is inert - Keyboard `โ†` / `โ†’` arrows also navigate - No visible buttons needed โ€” optionally show a subtle `โ€น` / `โ€บ` hint at the edges that fades in on hover **Layout patterns:** - `title-card` โ€” centered hero, large title, authors/venue below - `text-only` โ€” structured bullet points, max 5โ€“6 items, generous whitespace - `text + image` โ€” image right or left, text opposite - `full-image` โ€” image fills canvas, minimal text overlay - `grid` โ€” 2ร—2 or 3-column figures with captions **Images:** - Use relative paths: `crops/_crop.png` - Add `style="object-fit: contain; max-width: 100%; max-height: 100%;"` - Add captions below in small italic text **Do NOT:** - Use external JS frameworks or icon CDNs - Use placeholder/stock images โ€” only the cropped PDFs - Generate generic purple-gradient-on-white slides - Let content overflow the 720px height --- ## Quality Checklist - [ ] Output directory named `_/` - [ ] `outline.json` saved with valid SlidesPlan schema - [ ] All crops saved to `crops/` (local only, no cloud upload) - [ ] Each slide fits within 1280ร—720, nothing overflows - [ ] Consistent theme across all slides - [ ] Crop images referenced via relative path `crops/_crop.png` - [ ] Slide number and presentation title visible on every slide - [ ] Left/right click-area navigation works, keyboard arrows work - [ ] `summary.md` written in the correct language, links to `slide_01.html` --- ## Language Match the PDF language. Chinese PDF โ†’ Chinese slides and summary. English โ†’ English. No mixing.