Files
mantle-ai-trader/skills/ppt/html2pptx.md
2026-06-06 05:21:10 +00:00

14 KiB
Executable File
Raw Blame History

HTML to PowerPoint Guide

Convert HTML slides into PowerPoint presentations using the html2pptx.js library with accurate positioning.


Creating HTML Slides

Each HTML slide must include the correct body dimensions:

Layout Dimensions

  • 16:9 (default): width: 720pt; height: 405pt
  • 4:3: width: 720pt; height: 540pt
  • 16:10: width: 720pt; height: 450pt

CRITICAL — Prevent Overflow:

  • <body> must set exact dimensions via inline style: width: 720pt; height: 405pt; margin: 0; padding: 0; overflow: hidden;
  • All content must fit within these boundaries. If content overflows, split into multiple slides
  • Maintain at least 36pt bottom margin inside the body for safe spacing

Fill the slide: Content should occupy the available space well. Avoid designs where text clusters in the top third with the bottom two-thirds empty. Use generous font sizes, spacing, and visual elements to fill the canvas.

Supported Elements

  • <p>, <h1>-<h6> — Styled text
  • <ul>, <ol> — Lists (never use manual bullet symbols like bullet, -, *)
  • <b>, <strong>, <i>, <em>, <u> — Inline formatting
  • <span> — Inline formatting with CSS styles
  • <br> — Line breaks
  • <div> with bg/border — Becomes shapes
  • <img> — Images
  • class="placeholder" — Reserves space for charts (returns { id, x, y, w, h })

Key Text Rules

All text must be inside <p>, <h1>-<h6>, <ul>, or <ol> tags:

  • <div><p>Text here</p></div>
  • <div>Text here</div>Silently ignored in PowerPoint
  • <span>Text here</span>Silently ignored in PowerPoint

*Never use manual bullet symbols (bullet, -, , etc.) — use <ul> or <ol> instead.

v3 Smart Font Mapping (pass-through + safe fallback):

html2pptx.js v3 no longer maps all fonts to the same one. Strategy:

  1. PPT-safe fonts (Corbel, Arial, SimHei, Palatino Linotype, etc. 40+) → Pass through directly
  2. macOS-exclusive fonts (PingFang SC, Hiragino Sans) → Mapped to cross-platform equivalents
  3. Web fonts (Roboto, Montserrat, Inter, etc. 40+) → Mapped to visually closest PPT-safe font
  4. CSS generic names (sans-serif/serif) → Corbel / Times New Roman
  5. Unknown fonts → CJK falls back to fontConfig.cjk; Latin passes through directly

Write PPT font names directly in HTML:

font-family: "SimHei", "Microsoft YaHei", sans-serif;  /* CJK heading */
font-family: "Microsoft YaHei", sans-serif;  /* CJK body */
font-family: "Gill Sans MT", "Century Gothic", sans-serif;  /* English heading */

fontConfig is still available (as CJK/Latin ultimate fallback, optional):

const fontConfig = { cjk: 'SimHei', latin: 'Gill Sans MT' };
const result = await html2pptx('slide.html', pptx, { fontConfig });

Styles

  • Body must use display: flex; flex-direction: column; — without it, multiple direct children stack horizontally
  • Do not use flex-wrap — multi-row layouts must use separate flex containers (html2pptx renderer may lose wrapped content)
  • <span> supports: font-weight, font-style, text-decoration, color, font-size, letter-spacing; color accepts rgba() for transparency
  • <span> does NOT support: margin, padding
  • text-transform: uppercase / lowercase / capitalize works on all text elements and <span>
  • Rotated text: transform: rotate(-30deg) or writing-mode: vertical-rl / vertical-lr
  • Use hex colors with # prefix in CSS; use text-align for alignment hints

Shape Styles (DIV elements only)

Backgrounds, borders, and shadows only work on <div>, not on text elements.

  • Background: background-color on <div> only
  • Border: uniform (border: 2px solid #333) or partial (border-left, border-right, etc.)
  • Border radius: border-radius: 8pt for rounded corners; 50% for circle; percentages relative to smaller dimension
  • Box shadow: outer shadows only — box-shadow: 2px 2px 8px rgba(0,0,0,0.3); inset shadows ignored

Typography Guidelines

Choose font sizes that create clear visual hierarchy. Refer to design-system.md for suggested ranges.

Minimum font size: Don't go below ~11pt for any text. Prefer ≥13pt for body text. Hierarchy principle: Headings should be noticeably larger and bolder than body text.

Spacing Guidelines

Use consistent spacing throughout the deck. Refer to design-system.md for suggested values. Key principles:

  • Page margins: ~48pt left/right, ~40pt top, ≥36pt bottom safe zone
  • Be generous with whitespace — but fill the slide; avoid large empty areas

Color Rules

All colors must come from the current theme's color scale. Arbitrary grays unrelated to the primary color are forbidden.

After selecting a theme from themes.md, use that theme's complete color scale.

Image Rules (MANDATORY for decks with 6+ slides)

Every deck with 6+ slides must include real photographs. Images create visual richness and professional quality.

Image sourcing priority:

  1. Unsplash (free, high quality) — use theme's Image Keywords from themes.md:
    curl -L "https://source.unsplash.com/1920x1080/?keyword1,keyword2" -o cover-bg.jpg
    
  2. User-provided images — local files
  3. Gradient fallback — if Unsplash fails, generate gradient PNG via Sharp

Image usage in HTML:

  • Page background: <body style="background-image:url('bg.jpg'); background-size:cover;">
  • Inline image: <img src="photo.jpg" style="width:296pt; height:220pt; object-fit:cover;">
  • DIV background-image is NOT supported — only body background-image works

Mask overlay for photo backgrounds:

<!-- Background photo on body -->
<body style="width:720pt; height:405pt; margin:0; padding:0; overflow:hidden;
             background-image:url('cover-bg.jpg'); background-size:cover;
             font-family:'PingFang SC','Microsoft YaHei',sans-serif;
             display:flex; flex-direction:column;">
  <!-- Semi-transparent mask layer (use theme's Mask Color from themes.md) -->
  <div style="position:absolute; top:0; left:0; width:720pt; height:405pt;
              background-color:rgba(18,32,64,0.75);"></div>
  <!-- Content layer above mask -->
  <div style="position:relative; z-index:1; flex:1; display:flex; flex-direction:column;
              justify-content:center; align-items:center;">
    <h1 style="font-size:34pt; font-weight:bold; color:#FFFFFF;">Title Here</h1>
  </div>
</body>

Mask opacity guide:

  • Dark mask (cover/divider): opacity 0.700.85 — text clearly readable
  • Light mask (content): opacity 0.600.80 — retains image visibility

Icons & Gradients

  • CRITICAL: Never use CSS gradients — pre-rasterize as PNG with Sharp
  • Icons: rasterize react-icons SVG to PNG; Gradients: rasterize SVG to PNG background
// Icon to PNG
const { FaHome } = require('react-icons/fa');
const svgString = ReactDOMServer.renderToStaticMarkup(
  React.createElement(FaHome, { color: '#4472C4', size: '256' })
);
await sharp(Buffer.from(svgString)).png().toFile('home-icon.png');
// In HTML: <img src="home-icon.png" style="width:40pt;height:40pt;">

// Gradient to PNG
const svg = `<svg xmlns="http://www.w3.org/2000/svg" width="1000" height="562.5">
  <defs><linearGradient id="g" x1="0%" y1="0%" x2="100%" y2="100%">
    <stop offset="0%" style="stop-color:#COLOR1"/>
    <stop offset="100%" style="stop-color:#COLOR2"/>
  </linearGradient></defs>
  <rect width="100%" height="100%" fill="url(#g)"/>
</svg>`;
await sharp(Buffer.from(svg)).png().toFile('gradient-bg.png');
// In HTML: <body style="background-image: url('gradient-bg.png');">

Using the html2pptx Library

Dependencies

Globally installed: pptxgenjs, playwright, sharp

Basic Usage

const pptxgen = require('pptxgenjs');
const html2pptx = require('./html2pptx');

const pptx = new pptxgen();
pptx.layout = 'LAYOUT_16x9';  // Must match HTML body dimensions

// Optional: custom font configuration (from themes.md)
const fontConfig = { cjk: 'Microsoft YaHei', latin: 'Gill Sans MT' };

const { slide, placeholders, warnings } = await html2pptx('slide1.html', pptx, { fontConfig });

// If warnings is non-empty, fix the HTML and re-run
if (warnings.length > 0) {
    console.error('Fix overflow issues before saving:', warnings);
    process.exit(1);
}

if (placeholders.length > 0) {
    slide.addChart(pptx.charts.LINE, chartData, placeholders[0]);
}

await pptx.writeFile('output.pptx');

API Reference

await html2pptx(htmlFile, pres, options)

Parameters:

  • htmlFile (string): Path to HTML file
  • pres (pptxgen): PptxGenJS instance with layout set
  • options.tmpDir (string): Temp dir (default: process.env.TMPDIR || '/tmp')
  • options.slide (object): Existing slide to reuse
  • options.fontConfig (object): Font mapping config { cjk: 'Microsoft YaHei', latin: 'Corbel' }

Returns:

{
    slide: pptxgenSlide,
    placeholders: [{ id, x, y, w, h }, ...],
    warnings: string[]   // Overflow and layout suggestions; empty if none
}

Validation

Blocking errors (conversion aborted):

  1. CSS gradients — must be pre-rasterized as PNG
  2. Backgrounds/borders/shadows on text elements (<p>, <h1>-<h6>, etc.)
  3. Unwrapped text directly in <div>
  4. Manual bullet symbols in text elements
  5. Font size below 11pt

Non-blocking warnings (conversion succeeds, returned in warnings):

  1. Element out of bounds — extends beyond slide edges
  2. Vertical imbalance — content clusters in top 55%
  3. Text overlap — text elements overlap each other
  4. Character density — exceeds 350 CJK / 550 Latin chars
  5. Body-level overflow

Blocking issues (overflow, font < 11pt) must be fixed. Non-blocking warnings are suggestions — use your judgment.

Visual Quality Check

After generating all slides, do a quick quality scan:

  • Is there visual variety across the deck? (different layouts, backgrounds, card styles)
  • Do real photographs appear? (at least on cover + 1 other slide for 6+ slide decks)
  • Is there at least one dramatic "rhythm breaker" page?
  • Does every slide have a clear visual focal point?

Working with Placeholders

const { slide, placeholders } = await html2pptx('slide.html', pptx);
slide.addChart(pptx.charts.BAR, data, placeholders[0]);

// By ID:
const chartArea = placeholders.find(p => p.id === 'chart-area');
slide.addChart(pptx.charts.LINE, data, chartArea);

Using PptxGenJS

Critical Rules

NEVER use # prefix with hex colors in PptxGenJS — causes file corruption.

  • color: "FF0000", fill: { color: "0066CC" }
  • color: "#FF0000"

Adding Images

const imgWidth = 1860, imgHeight = 1519;
const h = 3, w = h * (imgWidth / imgHeight);
slide.addImage({ path: "chart.png", x: (10 - w) / 2, y: 1.5, w, h });

Adding Shapes

slide.addShape(pptx.shapes.RECTANGLE, {
    x: 1, y: 1, w: 3, h: 2,
    fill: { color: "4472C4" },
    line: { color: "2E5DA8", width: 2 },
    rectRadius: 0.1  // rounded corners (ROUNDED_RECTANGLE only)
});

Adding Charts

Time Series Granularity: < 30 days daily | 30-365 days monthly | > 365 days yearly.

Bar Chart

slide.addChart(pptx.charts.BAR, [{
    name: "Sales 2024",
    labels: ["Q1", "Q2", "Q3", "Q4"],
    values: [4500, 5500, 6200, 7100]
}], {
    ...placeholders[0],
    barDir: 'col',
    showTitle: true, title: 'Quarterly Sales',
    showLegend: false,
    showCatAxisTitle: true, catAxisTitle: 'Quarter',
    showValAxisTitle: true, valAxisTitle: 'Sales ($000s)',
    valAxisMinVal: 0, valAxisMaxVal: 8000, valAxisMajorUnit: 2000,
    chartColors: ["4472C4"]
});

Line Chart

slide.addChart(pptx.charts.LINE, [{
    name: "Temperature",
    labels: ["Jan", "Feb", "Mar", "Apr"],
    values: [32, 35, 42, 55]
}], {
    x: 1, y: 1, w: 8, h: 4,
    lineSize: 4, lineSmooth: true,
    showCatAxisTitle: true, catAxisTitle: 'Month',
    showValAxisTitle: true, valAxisTitle: 'Temp (F)',
    valAxisMinVal: 0, valAxisMaxVal: 60, valAxisMajorUnit: 20,
    chartColors: ["4472C4"]
});

Pie Chart

slide.addChart(pptx.charts.PIE, [{
    name: "Market Share",
    labels: ["Product A", "Product B", "Other"],
    values: [35, 45, 20]
}], {
    x: 2, y: 1, w: 6, h: 4,
    showPercent: true, showLegend: true, legendPos: 'r',
    chartColors: ["4472C4", "ED7D31", "A5A5A5"]
});

Scatter Chart

slide.addChart(pptx.charts.SCATTER, [
    { name: 'X-Axis', values: [10, 15, 20, 12, 18] },
    { name: 'Series 1', values: [20, 25, 30] },
    { name: 'Series 2', values: [18, 22] }
], {
    x: 1, y: 1, w: 8, h: 4,
    lineSize: 0, lineDataSymbol: 'circle', lineDataSymbolSize: 6,
    showCatAxisTitle: true, catAxisTitle: 'X',
    showValAxisTitle: true, valAxisTitle: 'Y',
    chartColors: ["4472C4", "ED7D31"]
});

Multiple Series

slide.addChart(pptx.charts.LINE, [
    { name: "Product A", labels: ["Q1","Q2","Q3","Q4"], values: [10,20,30,40] },
    { name: "Product B", labels: ["Q1","Q2","Q3","Q4"], values: [15,25,20,35] }
], { x: 1, y: 1, w: 8, h: 4, showCatAxisTitle: true, catAxisTitle: 'Quarter',
     showValAxisTitle: true, valAxisTitle: 'Revenue ($M)' });

Chart colors: no # prefix; align with slide palette; strong contrast between adjacent series.

Adding Tables

const tableData = [
    [
        { text: "Product", options: { fill: { color: "4472C4" }, color: "FFFFFF", bold: true } },
        { text: "Revenue", options: { fill: { color: "4472C4" }, color: "FFFFFF", bold: true } },
        { text: "Growth",  options: { fill: { color: "4472C4" }, color: "FFFFFF", bold: true } }
    ],
    ["Product A", "$50M", "+15%"],
    ["Product B", "$35M", "+22%"]
];
slide.addTable(tableData, {
    x: 1, y: 1.5, w: 8, h: 2.5,
    colW: [3, 2.5, 2.5], rowH: [0.5, 0.6, 0.6],
    border: { pt: 1, color: "CCCCCC" },
    align: "center", valign: "middle", fontSize: 14
});

Table options: x, y, w, h | colW | rowH | border: { pt, color } | fill | align | valign | fontSize | autoPage