html-to-pptx

内容来源:SKILL.md(标准 Skill 格式) · 原始地址 · 查看安装指南

原始内容


name: html-to-pptx description: Convert an existing HTML slide deck into an editable .pptx that fully preserves layout, typography, colors, assets, and visual style. Use when the user gives you an HTML file containing
slides (or CSS-styled slide markup) and asks for a PowerPoint version that can still be edited — not a deck of screenshots. The output uses native text boxes, shapes, and images laid over pre-baked gradient backgrounds. Handles the CSS↔PPTX unit conversions (px→inches, CSS letter-spacing→charSpacing), gradient-fill limitations (bakes to PNGs), photo-to-panel transitions, Chinese typography, and the Windows/macOS PPTX→PDF→image verification loop.

HTML → PPTX (editable)

Convert an HTML slide deck to an editable .pptx while preserving as much of the original design as possible. Output must be editable native PowerPoint content — text boxes, shapes, images — not rasterized slide images.

This skill is for the case where the user already has a finished HTML deck (typically one <section class="slide"> per slide at a fixed canvas size like 1920×1080) and wants a PowerPoint they can hand-edit.


When to reach for this vs. other approaches

Situation Use
User has HTML and wants editable .pptx This skill
User wants a .pptx from scratch / plain content pptx skill (pptxgenjs / python-pptx)
User explicitly wants pixel-perfect screenshots Headless-browser → image-per-slide; then embed as slide backgrounds
User wants to edit an existing .pptx pptx skill editing workflow (unpack / pack XML)

If the user says "完全还原" / "pixel-perfect" / "must look identical", clarify first: editable native shapes approximate — screenshots are exact but uneditable. They usually want editable.


Tooling

Node / npm (global):

npm install -g pptxgenjs sharp

Python:

pip install pillow pymupdf markitdown[pptx]

If pptxgenjs is installed globally but Node can't find it in the project directory, run with NODE_PATH set to the global modules path:

NODE_PATH="$(npm root -g)" node build/build.js

For HTML → PPTX → PDF on Windows use the PowerPoint COM script in templates/to_pdf.template.ps1. On macOS/Linux use LibreOffice: soffice --headless --convert-to pdf deck.pptx.

For PDF → images prefer PyMuPDF (fitz) on Windows — pdftoppm can fail silently on non-ASCII or spaced paths.


The workflow

  1. Read the HTML. Enumerate distinct slide archetypes (cover, chapter, person, grid, content, close, etc.). Extract design tokens: colors from :root CSS vars, font families, the canvas size (usually 1920×1080).

  2. Generate gradient backgrounds. pptxgenjs has no gradient fill. Use Pillow to bake one PNG per distinct background variant (see templates/gen_bg.template.py). Put scatter/stars, dune silhouettes, and vignettes into the same PNG — anything too decorative to be worth recreating as native shapes.

  3. Generate photo-transition overlays if the deck has photos that blend into content panels (see templates/gen_fade.template.py). Always prefer a single gradient PNG over stacked translucent rectangles — stacks show visible bands.

  4. Write build.js with pptxgenjs. Structure:

    • Custom slide layout sized to match the HTML canvas exactly (e.g. width: 13.333, height: 7.5 for 1920×1080).
    • A C color map and F font map from the CSS tokens.
    • Unit helpers (px, pt, ls) — see reference.md.
    • Shared chrome helpers: brandLogo, footer, cornerRow, etc. — one per recurring element.
    • One function per slide archetype. Call them in order.
    • pres.writeFile({ fileName: ... }).
  5. Export & verify. PPTX → PDF → per-slide JPG → look at each slide. If you have a lot of slides (> 6), delegate the visual inspection to a subagent — you'll miss problems on your own output.

  6. Fix & re-export. Build → verify → fix → re-verify until a full pass reveals no new issues. Kill POWERPNT.EXE between Windows exports to release the file handle.


The rules that matter (read reference.md for details)

R1 · Coordinate math

For a 1920×1080 HTML canvas mapped to a 13.333×7.5″ slide:

  • IN_PER_PX = 13.333 / 19201 CSS px = 1/144 in for x/y/w/h
  • 1 CSS px = 0.5 pt for font sizes (this is the key conversion — a 280px headline is 140pt)

R2 · CSS letter-spacing → pptxgenjs charSpacing ⚠️ HIGH-RISK

pptxgenjs charSpacing is points, absolute. It is not em, not a ratio, not 100ths of a point.

const ls = (em, fontPt) => em * fontPt;   // CSS .4em @ 12pt → 4.8 (pt)
s.addText("AGENDA", { fontSize: 44, charSpacing: ls(0.1, 44) /* = 4.4 */ });

Getting this wrong is the single biggest failure mode of HTML→PPTX conversions. A value of 20 at a 12pt font produces ~1.7em of tracking — every Latin label wraps character-by-character, every Chinese title spreads into a banner row. Always compute from em × fontPt.

R3 · Gradients are baked images

pptxgenjs doesn't do gradient fills. Pre-render gradient backgrounds as PNGs in Pillow. For gradient text fills (-webkit-background-clip: text), pick a solid color near the midpoint of the gradient — PowerPoint gradient text via XML injection is possible but rarely worth it.

R4 · Photo → panel transition

One gradient PNG overlay, not stacked rectangles:

// ✅ Smooth
s.addImage({ path: "photo_fade.png",  x: edge - 280, y: 0, w: 340, h: slideH });
// ❌ Bands
rect(s, { x: edge-260, ..., fill: { color:"3A1F14", transparency:70 }});
rect(s, { x: edge-130, ..., fill: { color:"3A1F14", transparency:45 }});
rect(s, { x: edge- 30, ..., fill: { color:"3A1F14", transparency:15 }});

R5 · Footer-over-photo legibility

If the original design floats a footer over a photo, photo brightness varies and footers will become invisible on bright ones. Add a bottom-scrim gradient PNG (transparent at top → opaque at bottom) over the photo area behind the footer.

R6 · Outlined / stroked text

CSS -webkit-text-stroke has no pptxgenjs equivalent. Approximate with solid fill at 70–85% transparency for a ghostly outline feel. Works well for giant chapter numerals.

R7 · Rich-text for inline emphasis

s.addText([
  { text: "首战沙海,", options: {} },
  { text: "传承安泰荣光", options: { color: C.gold_bright, bold: true } },
  { text: "。", options: {} },
], { x, y, w, h, fontFace: F.serif, fontSize: 17, ... });

Each run carries its own color, bold, italic, breakLine.

R8 · Common pptxgenjs pitfalls

  • Colors: "FF0000", never "#FF0000" — leading # corrupts the file
  • Don't encode alpha in hex ("00000020" corrupts); use transparency (0–100) or opacity (0–1)
  • Don't reuse option objects between calls (pptxgenjs mutates) — build fresh with a factory function
  • breakLine: true between array items; don't rely on "\n" alone
  • Each pptxgen() instance writes one file — don't reuse

Starter templates

The templates/ folder has copy-and-adapt starters. Read a template, don't blindly copy — paths and the color/font maps always need project-specific edits.

File Purpose
templates/build.template.js pptxgenjs skeleton with layout, helpers (px, pt, ls), shared chrome stubs
templates/gen_bg.template.py Pillow gradient-background generator (desert/ink/cream variants)
templates/gen_fade.template.py Horizontal + vertical fade PNG overlays for photo blends
templates/to_pdf.template.ps1 Windows: PowerPoint COM PPTX → PDF
templates/pdf2img.template.py PyMuPDF PDF → per-slide JPG for visual QA

Quick sanity checklist before declaring success

  • Custom slide layout matches the HTML canvas aspect ratio exactly
  • All charSpacing values come from ls(em, fontPt) — not hardcoded integers
  • Gradient backgrounds are baked PNGs, not attempted as native fills
  • Photo fades use a single gradient PNG, not stacked rects
  • Footer text is legible on every slide (add scrim if floating over photos)
  • python -m markitdown output.pptx shows the text content intact and in order
  • PDF export opens successfully; no mojibake in Chinese
  • Re-opening the .pptx in PowerPoint, every text element is individually editable (click and type — not a flat image)