14 KiB
Executable File
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>— Imagesclass="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:
- PPT-safe fonts (Corbel, Arial, SimHei, Palatino Linotype, etc. 40+) → Pass through directly
- macOS-exclusive fonts (PingFang SC, Hiragino Sans) → Mapped to cross-platform equivalents
- Web fonts (Roboto, Montserrat, Inter, etc. 40+) → Mapped to visually closest PPT-safe font
- CSS generic names (sans-serif/serif) → Corbel / Times New Roman
- 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 acceptsrgba()for transparency<span>does NOT support:margin,paddingtext-transform: uppercase / lowercase / capitalizeworks on all text elements and<span>- Rotated text:
transform: rotate(-30deg)orwriting-mode: vertical-rl / vertical-lr - Use hex colors with
#prefix in CSS; usetext-alignfor alignment hints
Shape Styles (DIV elements only)
Backgrounds, borders, and shadows only work on <div>, not on text elements.
- Background:
background-coloron<div>only - Border: uniform (
border: 2px solid #333) or partial (border-left,border-right, etc.) - Border radius:
border-radius: 8ptfor 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:
- 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 - User-provided images — local files
- 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.70–0.85 — text clearly readable
- Light mask (content): opacity 0.60–0.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 filepres(pptxgen): PptxGenJS instance with layout setoptions.tmpDir(string): Temp dir (default:process.env.TMPDIR || '/tmp')options.slide(object): Existing slide to reuseoptions.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):
- CSS gradients — must be pre-rasterized as PNG
- Backgrounds/borders/shadows on text elements (
<p>,<h1>-<h6>, etc.) - Unwrapped text directly in
<div> - Manual bullet symbols in text elements
- Font size below 11pt
Non-blocking warnings (conversion succeeds, returned in warnings):
- Element out of bounds — extends beyond slide edges
- Vertical imbalance — content clusters in top 55%
- Text overlap — text elements overlap each other
- Character density — exceeds 350 CJK / 550 Latin chars
- 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