
Slides Grab Export
- 13 installs
- 1.2k repo stars
- Updated July 17, 2026
- vkehfdl1/slides-grab
Helps with ai & agent building tasks.
About
slides-grab-export is a Claude Code skill for ai & agent building. It helps solo builders move faster with AI-assisted development.
- slides-grab-export
- AI & Agent Building
- AI-coding skill
Slides Grab Export by the numbers
- 13 all-time installs (skills.sh)
- Ranked #11,409 of 16,546 AI & Agent Building skills by installs in the Skillselion catalog
- Data as of Aug 5, 2026 (Skillselion catalog sync)
npx skills add https://github.com/vkehfdl1/slides-grab --skill slides-grab-exportAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 13 |
|---|---|
| repo stars | ★ 1.2k |
| Last updated | July 17, 2026 |
| Repository | vkehfdl1/slides-grab ↗ |
What it does
Helps with ai & agent building tasks.
Files
slides-grab Export Skill (Codex)
Use this only after the user approves design output.
Goal
Convert reviewed slide HTML into PDF or per-slide PNG reliably, and into experimental / unstable PPTX/Figma outputs on a best-effort basis.
Inputs
- Approved
<slides-dir>/slide-*.html - Optional output path settings
Outputs
- Presentation artifact (
.pdf,.pngper slide, or.pptx)
Workflow
1. Confirm user approval for conversion. 2. Pick the right primary target:
- Card-news / Instagram-style decks →
slides-grab png --slides-dir <path> --slide-mode card-news --resolution 2160p(seeslides-grab-card-news). - Widescreen slide decks →
slides-grab pdf --slides-dir <path> --output <name>.pdf.
3. When per-slide raster output is needed (card news, social posts, thumbnails):
slides-grab png --slides-dir <path> --output-dir <path>/out-png --resolution 2160p- Add
--slide-mode card-newsfor 1:1 cards.
4. If the user also wants a PDF deck:
slides-grab pdf --slides-dir <path> --output <name>.pdf- Add
--slide-mode card-newswhen the deck is square.
5. If the user wants PPTX (experimental / unstable):
slides-grab convert --slides-dir <path> --output <name>.pptx
6. If the user wants Figma-importable PPTX (experimental / unstable):
slides-grab figma --slides-dir <path> --output <name>-figma.pptx
7. Report success/failure with actionable errors.
Rules
- Do not modify slide content during conversion stage unless explicitly requested.
- If conversion fails, diagnose and fix root causes in source HTML/CSS.
- Always tell the user that PPTX and Figma export are experimental / unstable and may require manual cleanup.
- Use the packaged CLI and bundled references only; do not depend on unpublished agent-specific files.
Reference
For detailed conversion behavior and tools, use:
references/export-rules.mdreferences/pptx-skill-reference.md— archived full PPTX workflow guidancereferences/html2pptx.md— archived converter usage guidereferences/ooxml.md— archived OOXML reference
slides-grab Export Reference
These are the packaged export rules for installable slides-grab skills.
Package-first commands
- PPTX export:
slides-grab convert --slides-dir <path> --output <name>.pptx - PDF export:
slides-grab pdf --slides-dir <path> --output <name>.pdf - Figma export:
slides-grab figma --slides-dir <path> --output <name>-figma.pptx
Export stage rules
- Only export after the user approves the reviewed HTML slides.
- Do not modify slide content during export unless explicitly requested.
- If export fails, fix the root cause in the source HTML/CSS or packaged runtime path.
User-facing caveats
- PPTX export is experimental / unstable.
- Figma export is experimental / unstable.
- Best-effort output may still require manual cleanup after export.
Runtime source of truth
- Export behavior must come from the packaged CLI/runtime in
bin/,scripts/, andsrc/. - Installable skills must not require scripts from inside a skill directory.
HTML to PowerPoint Guide (Experimental / Unstable)
Convert HTML slides to best-effort, experimental / unstable PowerPoint presentations using the html2pptx.js library.
Table of Contents
1. Creating HTML Slides 2. Using the html2pptx Library 3. Using PptxGenJS
---
Creating HTML Slides
Every HTML slide must include proper body dimensions:
Layout Dimensions
- 16:9 (default):
width: 720pt; height: 405pt - 4:3:
width: 720pt; height: 540pt - 16:10:
width: 720pt; height: 450pt
Supported Elements
<p>,<h1>-<h6>- Text with styling<ul>,<ol>- Lists (never use manual bullets •, -, *)<b>,<strong>- Bold text (inline formatting)<i>,<em>- Italic text (inline formatting)<u>- Underlined text (inline formatting)<span>- Inline formatting with CSS styles (bold, italic, underline, color)<br>- Line breaks<div>with bg/border - Becomes shape<img>- Imagesclass="placeholder"- Reserved space for charts (returns{ id, x, y, w, h })
Critical Text Rules
ALL text MUST be inside `<p>`, `<h1>`-`<h6>`, `<ul>`, or `<ol>` tags:
- ✅ Correct:
<div><p>Text here</p></div> - ❌ Wrong:
<div>Text here</div>- Text will NOT appear in PowerPoint - ❌ Wrong:
<span>Text</span>- Text will NOT appear in PowerPoint - Text in
<div>or<span>without a text tag will be silently ignored
*NEVER use manual bullet symbols (•, -, , etc.)** - Use <ul> or <ol> lists instead
ONLY use web-safe fonts that are universally available:
- ✅ Web-safe fonts:
Arial,Helvetica,Times New Roman,Georgia,Courier New,Verdana,Tahoma,Trebuchet MS,Impact,Comic Sans MS - ❌ Wrong:
'Segoe UI','SF Pro','Roboto', custom fonts - Might cause rendering issues
Styling
- Use
display: flexon body to prevent margin collapse from breaking overflow validation - Use
marginfor spacing (padding included in size) - Inline formatting: Use
<b>,<i>,<u>tags OR<span>with CSS styles <span>supports:font-weight: bold,font-style: italic,text-decoration: underline,color: #rrggbb<span>does NOT support:margin,padding(not supported in PowerPoint text runs)- Example:
<span style="font-weight: bold; color: #667eea;">Bold blue text</span> - Flexbox works - positions calculated from rendered layout
- Use hex colors with
#prefix in CSS - Text alignment: Use CSS
text-align(center,right, etc.) when needed as a hint to PptxGenJS for text formatting if text lengths are slightly off
Shape Styling (DIV elements only)
IMPORTANT: Backgrounds, borders, and shadows only work on `<div>` elements, NOT on text elements (`<p>`, `<h1>`-`<h6>`, `<ul>`, `<ol>`)
- Backgrounds: CSS
backgroundorbackground-coloron<div>elements only - Example:
<div style="background: #f0f0f0;">- Creates a shape with background - Borders: CSS
borderon<div>elements converts to PowerPoint shape borders - Supports uniform borders:
border: 2px solid #333333 - Supports partial borders:
border-left,border-right,border-top,border-bottom(rendered as line shapes) - Example:
<div style="border-left: 8pt solid #E76F51;"> - Border radius: CSS
border-radiuson<div>elements for rounded corners border-radius: 50%or higher creates circular shape- Percentages <50% calculated relative to shape's smaller dimension
- Supports px and pt units (e.g.,
border-radius: 8pt;,border-radius: 12px;) - Example:
<div style="border-radius: 25%;">on 100x200px box = 25% of 100px = 25px radius - Box shadows: CSS
box-shadowon<div>elements converts to PowerPoint shadows - Supports outer shadows only (inset shadows are ignored to prevent corruption)
- Example:
<div style="box-shadow: 2px 2px 8px rgba(0, 0, 0, 0.3);"> - Note: Inset/inner shadows are not supported by PowerPoint and will be skipped
Icons & Gradients
- CRITICAL: Never use CSS gradients (`linear-gradient`, `radial-gradient`) - They don't convert to PowerPoint
- ALWAYS create gradient/icon PNGs FIRST using Sharp, then reference in HTML
- For gradients: Rasterize SVG to PNG background images
- For icons: Rasterize Lucide SVG to PNG images
- All visual effects must be pre-rendered as raster images before HTML rendering
Rasterizing Icons with Sharp:
const React = require('react');
const ReactDOMServer = require('react-dom/server');
const sharp = require('sharp');
const { House } = require('lucide-react');
async function rasterizeIconPng(IconComponent, color, size = "256", filename) {
const svgString = ReactDOMServer.renderToStaticMarkup(
React.createElement(IconComponent, { color: `#${color}`, size: size, strokeWidth: 1.75 })
);
// Convert SVG to PNG using Sharp
await sharp(Buffer.from(svgString))
.png()
.toFile(filename);
return filename;
}
// Usage: Rasterize icon before using in HTML
const iconPath = await rasterizeIconPng(House, "4472c4", "256", "home-icon.png");
// Then reference in HTML: <img src="home-icon.png" style="width: 40pt; height: 40pt;">Rasterizing Gradients with Sharp:
const sharp = require('sharp');
async function createGradientBackground(filename) {
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(filename);
return filename;
}
// Usage: Create gradient background before HTML
const bgPath = await createGradientBackground("gradient-bg.png");
// Then in HTML: <body style="background-image: url('gradient-bg.png');">Example
<!DOCTYPE html>
<html>
<head>
<style>
html { background: #ffffff; }
body {
width: 720pt; height: 405pt; margin: 0; padding: 0;
background: #f5f5f5; font-family: Arial, sans-serif;
display: flex;
}
.content { margin: 30pt; padding: 40pt; background: #ffffff; border-radius: 8pt; }
h1 { color: #2d3748; font-size: 32pt; }
.box {
background: #70ad47; padding: 20pt; border: 3px solid #5a8f37;
border-radius: 12pt; box-shadow: 3px 3px 10px rgba(0, 0, 0, 0.25);
}
</style>
</head>
<body>
<div class="content">
<h1>Recipe Title</h1>
<ul>
<li><b>Item:</b> Description</li>
</ul>
<p>Text with <b>bold</b>, <i>italic</i>, <u>underline</u>.</p>
<div id="chart" class="placeholder" style="width: 350pt; height: 200pt;"></div>
<!-- Text MUST be in <p> tags -->
<div class="box">
<p>5</p>
</div>
</div>
</body>
</html>Using the html2pptx Library
Dependencies
These libraries have been globally installed and are available to use:
pptxgenjsplaywrightsharp
Basic Usage
const pptxgen = require('pptxgenjs');
const html2pptx = require('./html2pptx');
const pptx = new pptxgen();
pptx.layout = 'LAYOUT_16x9'; // Must match HTML body dimensions
const { slide, placeholders } = await html2pptx('slide1.html', pptx);
// Add chart to placeholder area
if (placeholders.length > 0) {
slide.addChart(pptx.charts.LINE, chartData, placeholders[0]);
}
await pptx.writeFile('output.pptx');API Reference
Function Signature
await html2pptx(htmlFile, pres, options)Parameters
htmlFile(string): Path to HTML file (absolute or relative)pres(pptxgen): PptxGenJS presentation instance with layout already setoptions(object, optional):tmpDir(string): Temporary directory for generated files (default:process.env.TMPDIR || '/tmp')slide(object): Existing slide to reuse (default: creates new slide)
Returns
{
slide: pptxgenSlide, // The created/updated slide
placeholders: [ // Array of placeholder positions
{ id: string, x: number, y: number, w: number, h: number },
...
]
}Validation
The library automatically validates and collects all errors before throwing:
1. HTML dimensions must match presentation layout - Reports dimension mismatches 2. Content must not overflow body - Reports overflow with exact measurements 3. CSS gradients - Reports unsupported gradient usage 4. Text element styling - Reports backgrounds/borders/shadows on text elements (only allowed on divs)
All validation errors are collected and reported together in a single error message, allowing you to fix all issues at once instead of one at a time.
Working with Placeholders
const { slide, placeholders } = await html2pptx('slide.html', pptx);
// Use first placeholder
slide.addChart(pptx.charts.BAR, data, placeholders[0]);
// Find by ID
const chartArea = placeholders.find(p => p.id === 'chart-area');
slide.addChart(pptx.charts.LINE, data, chartArea);Complete Example
const pptxgen = require('pptxgenjs');
const html2pptx = require('./html2pptx');
async function createPresentation() {
const pptx = new pptxgen();
pptx.layout = 'LAYOUT_16x9';
pptx.author = 'Your Name';
pptx.title = 'My Presentation';
// Slide 1: Title
const { slide: slide1 } = await html2pptx('<slides-dir>/title.html', pptx);
// Slide 2: Content with chart
const { slide: slide2, placeholders } = await html2pptx('<slides-dir>/data.html', pptx);
const chartData = [{
name: 'Sales',
labels: ['Q1', 'Q2', 'Q3', 'Q4'],
values: [4500, 5500, 6200, 7100]
}];
slide2.addChart(pptx.charts.BAR, chartData, {
...placeholders[0],
showTitle: true,
title: 'Quarterly Sales',
showCatAxisTitle: true,
catAxisTitle: 'Quarter',
showValAxisTitle: true,
valAxisTitle: 'Sales ($000s)'
});
// Save
await pptx.writeFile({ fileName: 'presentation.pptx' });
console.log('Presentation created successfully!');
}
createPresentation().catch(console.error);Using PptxGenJS
After converting HTML to slides with html2pptx, you'll use PptxGenJS to add dynamic content like charts, images, and additional elements.
⚠️ Critical Rules
Colors
- NEVER use `#` prefix with hex colors in PptxGenJS - causes file corruption
- ✅ Correct:
color: "FF0000",fill: { color: "0066CC" } - ❌ Wrong:
color: "#FF0000"(breaks document)
Adding Images
Always calculate aspect ratios from actual image dimensions:
// Get image dimensions: identify image.png | grep -o '[0-9]* x [0-9]*'
const imgWidth = 1860, imgHeight = 1519; // From actual file
const aspectRatio = imgWidth / imgHeight;
const h = 3; // Max height
const w = h * aspectRatio;
const x = (10 - w) / 2; // Center on 16:9 slide
slide.addImage({ path: "chart.png", x, y: 1.5, w, h });Adding Text
// Rich text with formatting
slide.addText([
{ text: "Bold ", options: { bold: true } },
{ text: "Italic ", options: { italic: true } },
{ text: "Normal" }
], {
x: 1, y: 2, w: 8, h: 1
});Adding Shapes
// Rectangle
slide.addShape(pptx.shapes.RECTANGLE, {
x: 1, y: 1, w: 3, h: 2,
fill: { color: "4472C4" },
line: { color: "000000", width: 2 }
});
// Circle
slide.addShape(pptx.shapes.OVAL, {
x: 5, y: 1, w: 2, h: 2,
fill: { color: "ED7D31" }
});
// Rounded rectangle
slide.addShape(pptx.shapes.ROUNDED_RECTANGLE, {
x: 1, y: 4, w: 3, h: 1.5,
fill: { color: "70AD47" },
rectRadius: 0.2
});Adding Charts
Required for most charts: Axis labels using catAxisTitle (category) and valAxisTitle (value).
Chart Data Format:
- Use single series with all labels for simple bar/line charts
- Each series creates a separate legend entry
- Labels array defines X-axis values
Time Series Data - Choose Correct Granularity:
- < 30 days: Use daily grouping (e.g., "10-01", "10-02") - avoid monthly aggregation that creates single-point charts
- 30-365 days: Use monthly grouping (e.g., "2024-01", "2024-02")
- > 365 days: Use yearly grouping (e.g., "2023", "2024")
- Validate: Charts with only 1 data point likely indicate incorrect aggregation for the time period
const { slide, placeholders } = await html2pptx('slide.html', pptx);
// CORRECT: Single series with all labels
slide.addChart(pptx.charts.BAR, [{
name: "Sales 2024",
labels: ["Q1", "Q2", "Q3", "Q4"],
values: [4500, 5500, 6200, 7100]
}], {
...placeholders[0], // Use placeholder position
barDir: 'col', // 'col' = vertical bars, 'bar' = horizontal
showTitle: true,
title: 'Quarterly Sales',
showLegend: false, // No legend needed for single series
// Required axis labels
showCatAxisTitle: true,
catAxisTitle: 'Quarter',
showValAxisTitle: true,
valAxisTitle: 'Sales ($000s)',
// Optional: Control scaling (adjust min based on data range for better visualization)
valAxisMaxVal: 8000,
valAxisMinVal: 0, // Use 0 for counts/amounts; for clustered data (e.g., 4500-7100), consider starting closer to min value
valAxisMajorUnit: 2000, // Control y-axis label spacing to prevent crowding
catAxisLabelRotate: 45, // Rotate labels if crowded
dataLabelPosition: 'outEnd',
dataLabelColor: '000000',
// Use single color for single-series charts
chartColors: ["4472C4"] // All bars same color
});Scatter Chart
IMPORTANT: Scatter chart data format is unusual - first series contains X-axis values, subsequent series contain Y-values:
// Prepare data
const data1 = [{ x: 10, y: 20 }, { x: 15, y: 25 }, { x: 20, y: 30 }];
const data2 = [{ x: 12, y: 18 }, { x: 18, y: 22 }];
const allXValues = [...data1.map(d => d.x), ...data2.map(d => d.x)];
slide.addChart(pptx.charts.SCATTER, [
{ name: 'X-Axis', values: allXValues }, // First series = X values
{ name: 'Series 1', values: data1.map(d => d.y) }, // Y values only
{ name: 'Series 2', values: data2.map(d => d.y) } // Y values only
], {
x: 1, y: 1, w: 8, h: 4,
lineSize: 0, // 0 = no connecting lines
lineDataSymbol: 'circle',
lineDataSymbolSize: 6,
showCatAxisTitle: true,
catAxisTitle: 'X Axis',
showValAxisTitle: true,
valAxisTitle: 'Y Axis',
chartColors: ["4472C4", "ED7D31"]
});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,
// Required axis labels
showCatAxisTitle: true,
catAxisTitle: 'Month',
showValAxisTitle: true,
valAxisTitle: 'Temperature (°F)',
// Optional: Y-axis range (set min based on data range for better visualization)
valAxisMinVal: 0, // For ranges starting at 0 (counts, percentages, etc.)
valAxisMaxVal: 60,
valAxisMajorUnit: 20, // Control y-axis label spacing to prevent crowding (e.g., 10, 20, 25)
// valAxisMinVal: 30, // PREFERRED: For data clustered in a range (e.g., 32-55 or ratings 3-5), start axis closer to min value to show variation
// Optional: Chart colors
chartColors: ["4472C4", "ED7D31", "A5A5A5"]
});Pie Chart (No Axis Labels Required)
CRITICAL: Pie charts require a single data series with all categories in the labels array and corresponding values in the values array.
slide.addChart(pptx.charts.PIE, [{
name: "Market Share",
labels: ["Product A", "Product B", "Other"], // All categories in one array
values: [35, 45, 20] // All values in one array
}], {
x: 2, y: 1, w: 6, h: 4,
showPercent: true,
showLegend: true,
legendPos: 'r', // right
chartColors: ["4472C4", "ED7D31", "A5A5A5"]
});Multiple Data 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
CRITICAL: Use hex colors without the # prefix - including # causes file corruption.
Align chart colors with your chosen design palette, ensuring sufficient contrast and distinctiveness for data visualization. Adjust colors for:
- Strong contrast between adjacent series
- Readability against slide backgrounds
- Accessibility (avoid red-green only combinations)
// Example: Ocean palette-inspired chart colors (adjusted for contrast)
const chartColors = ["16A085", "FF6B9D", "2C3E50", "F39C12", "9B59B6"];
// Single-series chart: Use one color for all bars/points
slide.addChart(pptx.charts.BAR, [{
name: "Sales",
labels: ["Q1", "Q2", "Q3", "Q4"],
values: [4500, 5500, 6200, 7100]
}], {
...placeholders[0],
chartColors: ["16A085"], // All bars same color
showLegend: false
});
// Multi-series chart: Each series gets a different color
slide.addChart(pptx.charts.LINE, [
{ name: "Product A", labels: ["Q1", "Q2", "Q3"], values: [10, 20, 30] },
{ name: "Product B", labels: ["Q1", "Q2", "Q3"], values: [15, 25, 20] }
], {
...placeholders[0],
chartColors: ["16A085", "FF6B9D"] // One color per series
});Adding Tables
Tables can be added with basic or advanced formatting:
Basic Table
slide.addTable([
["Header 1", "Header 2", "Header 3"],
["Row 1, Col 1", "Row 1, Col 2", "Row 1, Col 3"],
["Row 2, Col 1", "Row 2, Col 2", "Row 2, Col 3"]
], {
x: 0.5,
y: 1,
w: 9,
h: 3,
border: { pt: 1, color: "999999" },
fill: { color: "F1F1F1" }
});Table with Custom Formatting
const tableData = [
// Header row with custom styling
[
{ 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 } }
],
// Data rows
["Product A", "$50M", "+15%"],
["Product B", "$35M", "+22%"],
["Product C", "$28M", "+8%"]
];
slide.addTable(tableData, {
x: 1,
y: 1.5,
w: 8,
h: 3,
colW: [3, 2.5, 2.5], // Column widths
rowH: [0.5, 0.6, 0.6, 0.6], // Row heights
border: { pt: 1, color: "CCCCCC" },
align: "center",
valign: "middle",
fontSize: 14
});Table with Merged Cells
const mergedTableData = [
[
{ text: "Q1 Results", options: { colspan: 3, fill: { color: "4472C4" }, color: "FFFFFF", bold: true } }
],
["Product", "Sales", "Market Share"],
["Product A", "$25M", "35%"],
["Product B", "$18M", "25%"]
];
slide.addTable(mergedTableData, {
x: 1,
y: 1,
w: 8,
h: 2.5,
colW: [3, 2.5, 2.5],
border: { pt: 1, color: "DDDDDD" }
});Table Options
Common table options:
x, y, w, h- Position and sizecolW- Array of column widths (in inches)rowH- Array of row heights (in inches)border- Border style:{ pt: 1, color: "999999" }fill- Background color (no # prefix)align- Text alignment: "left", "center", "right"valign- Vertical alignment: "top", "middle", "bottom"fontSize- Text sizeautoPage- Auto-create new slides if content overflows
Office Open XML Technical Reference for PowerPoint
Important: Read this entire document before starting. Critical XML schema rules and formatting requirements are covered throughout. Incorrect implementation can create invalid PPTX files that PowerPoint cannot open.
Technical Guidelines
Schema Compliance
- Element ordering in `<p:txBody>`:
<a:bodyPr>,<a:lstStyle>,<a:p> - Whitespace: Add
xml:space='preserve'to<a:t>elements with leading/trailing spaces - Unicode: Escape characters in ASCII content:
"becomes“ - Images: Add to
ppt/media/, reference in slide XML, set dimensions to fit slide bounds - Relationships: Update
ppt/slides/_rels/slideN.xml.relsfor each slide's resources - Dirty attribute: Add
dirty="0"to<a:rPr>and<a:endParaRPr>elements to indicate clean state
Presentation Structure
Basic Slide Structure
<!-- ppt/slides/slide1.xml -->
<p:sld>
<p:cSld>
<p:spTree>
<p:nvGrpSpPr>...</p:nvGrpSpPr>
<p:grpSpPr>...</p:grpSpPr>
<!-- Shapes go here -->
</p:spTree>
</p:cSld>
</p:sld>Text Box / Shape with Text
<p:sp>
<p:nvSpPr>
<p:cNvPr id="2" name="Title"/>
<p:cNvSpPr>
<a:spLocks noGrp="1"/>
</p:cNvSpPr>
<p:nvPr>
<p:ph type="ctrTitle"/>
</p:nvPr>
</p:nvSpPr>
<p:spPr>
<a:xfrm>
<a:off x="838200" y="365125"/>
<a:ext cx="7772400" cy="1470025"/>
</a:xfrm>
</p:spPr>
<p:txBody>
<a:bodyPr/>
<a:lstStyle/>
<a:p>
<a:r>
<a:t>Slide Title</a:t>
</a:r>
</a:p>
</p:txBody>
</p:sp>Text Formatting
<!-- Bold -->
<a:r>
<a:rPr b="1"/>
<a:t>Bold Text</a:t>
</a:r>
<!-- Italic -->
<a:r>
<a:rPr i="1"/>
<a:t>Italic Text</a:t>
</a:r>
<!-- Underline -->
<a:r>
<a:rPr u="sng"/>
<a:t>Underlined</a:t>
</a:r>
<!-- Highlight -->
<a:r>
<a:rPr>
<a:highlight>
<a:srgbClr val="FFFF00"/>
</a:highlight>
</a:rPr>
<a:t>Highlighted Text</a:t>
</a:r>
<!-- Font and Size -->
<a:r>
<a:rPr sz="2400" typeface="Arial">
<a:solidFill>
<a:srgbClr val="FF0000"/>
</a:solidFill>
</a:rPr>
<a:t>Colored Arial 24pt</a:t>
</a:r>
<!-- Complete formatting example -->
<a:r>
<a:rPr lang="en-US" sz="1400" b="1" dirty="0">
<a:solidFill>
<a:srgbClr val="FAFAFA"/>
</a:solidFill>
</a:rPr>
<a:t>Formatted text</a:t>
</a:r>Lists
<!-- Bullet list -->
<a:p>
<a:pPr lvl="0">
<a:buChar char="•"/>
</a:pPr>
<a:r>
<a:t>First bullet point</a:t>
</a:r>
</a:p>
<!-- Numbered list -->
<a:p>
<a:pPr lvl="0">
<a:buAutoNum type="arabicPeriod"/>
</a:pPr>
<a:r>
<a:t>First numbered item</a:t>
</a:r>
</a:p>
<!-- Second level indent -->
<a:p>
<a:pPr lvl="1">
<a:buChar char="•"/>
</a:pPr>
<a:r>
<a:t>Indented bullet</a:t>
</a:r>
</a:p>Shapes
<!-- Rectangle -->
<p:sp>
<p:nvSpPr>
<p:cNvPr id="3" name="Rectangle"/>
<p:cNvSpPr/>
<p:nvPr/>
</p:nvSpPr>
<p:spPr>
<a:xfrm>
<a:off x="1000000" y="1000000"/>
<a:ext cx="3000000" cy="2000000"/>
</a:xfrm>
<a:prstGeom prst="rect">
<a:avLst/>
</a:prstGeom>
<a:solidFill>
<a:srgbClr val="FF0000"/>
</a:solidFill>
<a:ln w="25400">
<a:solidFill>
<a:srgbClr val="000000"/>
</a:solidFill>
</a:ln>
</p:spPr>
</p:sp>
<!-- Rounded Rectangle -->
<p:sp>
<p:spPr>
<a:prstGeom prst="roundRect">
<a:avLst/>
</a:prstGeom>
</p:spPr>
</p:sp>
<!-- Circle/Ellipse -->
<p:sp>
<p:spPr>
<a:prstGeom prst="ellipse">
<a:avLst/>
</a:prstGeom>
</p:spPr>
</p:sp>Images
<p:pic>
<p:nvPicPr>
<p:cNvPr id="4" name="Picture">
<a:hlinkClick r:id="" action="ppaction://media"/>
</p:cNvPr>
<p:cNvPicPr>
<a:picLocks noChangeAspect="1"/>
</p:cNvPicPr>
<p:nvPr/>
</p:nvPicPr>
<p:blipFill>
<a:blip r:embed="rId2"/>
<a:stretch>
<a:fillRect/>
</a:stretch>
</p:blipFill>
<p:spPr>
<a:xfrm>
<a:off x="1000000" y="1000000"/>
<a:ext cx="3000000" cy="2000000"/>
</a:xfrm>
<a:prstGeom prst="rect">
<a:avLst/>
</a:prstGeom>
</p:spPr>
</p:pic>Tables
<p:graphicFrame>
<p:nvGraphicFramePr>
<p:cNvPr id="5" name="Table"/>
<p:cNvGraphicFramePr>
<a:graphicFrameLocks noGrp="1"/>
</p:cNvGraphicFramePr>
<p:nvPr/>
</p:nvGraphicFramePr>
<p:xfrm>
<a:off x="1000000" y="1000000"/>
<a:ext cx="6000000" cy="2000000"/>
</p:xfrm>
<a:graphic>
<a:graphicData uri="http://schemas.openxmlformats.org/drawingml/2006/table">
<a:tbl>
<a:tblGrid>
<a:gridCol w="3000000"/>
<a:gridCol w="3000000"/>
</a:tblGrid>
<a:tr h="500000">
<a:tc>
<a:txBody>
<a:bodyPr/>
<a:lstStyle/>
<a:p>
<a:r>
<a:t>Cell 1</a:t>
</a:r>
</a:p>
</a:txBody>
</a:tc>
<a:tc>
<a:txBody>
<a:bodyPr/>
<a:lstStyle/>
<a:p>
<a:r>
<a:t>Cell 2</a:t>
</a:r>
</a:p>
</a:txBody>
</a:tc>
</a:tr>
</a:tbl>
</a:graphicData>
</a:graphic>
</p:graphicFrame>Slide Layouts
<!-- Title Slide Layout -->
<p:sp>
<p:nvSpPr>
<p:nvPr>
<p:ph type="ctrTitle"/>
</p:nvPr>
</p:nvSpPr>
<!-- Title content -->
</p:sp>
<p:sp>
<p:nvSpPr>
<p:nvPr>
<p:ph type="subTitle" idx="1"/>
</p:nvPr>
</p:nvSpPr>
<!-- Subtitle content -->
</p:sp>
<!-- Content Slide Layout -->
<p:sp>
<p:nvSpPr>
<p:nvPr>
<p:ph type="title"/>
</p:nvPr>
</p:nvSpPr>
<!-- Slide title -->
</p:sp>
<p:sp>
<p:nvSpPr>
<p:nvPr>
<p:ph type="body" idx="1"/>
</p:nvPr>
</p:nvSpPr>
<!-- Content body -->
</p:sp>File Updates
When adding content, update these files:
`ppt/_rels/presentation.xml.rels`:
<Relationship Id="rId1" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/slide" Target="slides/slide1.xml"/>
<Relationship Id="rId2" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/slideMaster" Target="slideMasters/slideMaster1.xml"/>`ppt/slides/_rels/slide1.xml.rels`:
<Relationship Id="rId1" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/slideLayout" Target="../slideLayouts/slideLayout1.xml"/>
<Relationship Id="rId2" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/image" Target="../media/image1.png"/>`[Content_Types].xml`:
<Default Extension="png" ContentType="image/png"/>
<Default Extension="jpg" ContentType="image/jpeg"/>
<Override PartName="/ppt/slides/slide1.xml" ContentType="application/vnd.openxmlformats-officedocument.presentationml.slide+xml"/>`ppt/presentation.xml`:
<p:sldIdLst>
<p:sldId id="256" r:id="rId1"/>
<p:sldId id="257" r:id="rId2"/>
</p:sldIdLst>`docProps/app.xml`: Update slide count and statistics
<Slides>2</Slides>
<Paragraphs>10</Paragraphs>
<Words>50</Words>Slide Operations
Adding a New Slide
When adding a slide to the end of the presentation:
1. Create the slide file (ppt/slides/slideN.xml) 2. Update `[Content_Types].xml`: Add Override for the new slide 3. Update `ppt/_rels/presentation.xml.rels`: Add relationship for the new slide 4. Update `ppt/presentation.xml`: Add slide ID to <p:sldIdLst> 5. Create slide relationships (ppt/slides/_rels/slideN.xml.rels) if needed 6. Update `docProps/app.xml`: Increment slide count and update statistics (if present)
Duplicating a Slide
1. Copy the source slide XML file with a new name 2. Update all IDs in the new slide to be unique 3. Follow the "Adding a New Slide" steps above 4. CRITICAL: Remove or update any notes slide references in _rels files 5. Remove references to unused media files
Reordering Slides
1. Update `ppt/presentation.xml`: Reorder <p:sldId> elements in <p:sldIdLst> 2. The order of <p:sldId> elements determines slide order 3. Keep slide IDs and relationship IDs unchanged
Example:
<!-- Original order -->
<p:sldIdLst>
<p:sldId id="256" r:id="rId2"/>
<p:sldId id="257" r:id="rId3"/>
<p:sldId id="258" r:id="rId4"/>
</p:sldIdLst>
<!-- After moving slide 3 to position 2 -->
<p:sldIdLst>
<p:sldId id="256" r:id="rId2"/>
<p:sldId id="258" r:id="rId4"/>
<p:sldId id="257" r:id="rId3"/>
</p:sldIdLst>Deleting a Slide
1. Remove from `ppt/presentation.xml`: Delete the <p:sldId> entry 2. Remove from `ppt/_rels/presentation.xml.rels`: Delete the relationship 3. Remove from `[Content_Types].xml`: Delete the Override entry 4. Delete files: Remove ppt/slides/slideN.xml and ppt/slides/_rels/slideN.xml.rels 5. Update `docProps/app.xml`: Decrement slide count and update statistics 6. Clean up unused media: Remove orphaned images from ppt/media/
Note: Don't renumber remaining slides - keep their original IDs and filenames.
Common Errors to Avoid
- Encodings: Escape unicode characters in ASCII content:
"becomes“ - Images: Add to
ppt/media/and update relationship files - Lists: Omit bullets from list headers
- IDs: Use valid hexadecimal values for UUIDs
- Themes: Check all themes in
themedirectory for colors
Validation Checklist for Template-Based Presentations
Before Packing, Always:
- Clean unused resources: Remove unreferenced media, fonts, and notes directories
- Fix Content_Types.xml: Declare ALL slides, layouts, and themes present in the package
- Fix relationship IDs:
- Remove font embed references if not using embedded fonts
- Remove broken references: Check all
_relsfiles for references to deleted resources
Common Template Duplication Pitfalls:
- Multiple slides referencing the same notes slide after duplication
- Image/media references from template slides that no longer exist
- Font embedding references when fonts aren't included
- Missing slideLayout declarations for layouts 12-25
- docProps directory may not unpack - this is optional
PPTX Skill - Experimental / Unstable PowerPoint Conversion
Converts HTML slides into experimental / unstable PowerPoint presentation files on a best-effort basis.
Feature Overview
1. New Presentation (HTML -> PPTX)
Convert HTML slide files to experimental / unstable PowerPoint output
2. Edit Existing Presentation
Modify contents of a PPTX file
3. Thumbnail Generation
Generate preview images of a presentation
Prerequisites (Stage 3)
This skill is Stage 3. It executes only when ALL of the following conditions are met:
1. Explicit user request required: Only run when the user explicitly requests PPTX conversion ("convert to PPTX", "make a PowerPoint", etc.). 2. HTML slides reviewed: All HTML slides must have been reviewed and approved by the user in viewer.html. 3. No automatic execution: Do not automatically start PPTX conversion just because slide generation is complete.
If prerequisites are not met, guide the user to review slides in <slides-dir>/viewer.html first (default: slides/viewer.html).
---
Core Workflow
HTML -> PPTX Conversion
1. Prepare HTML slides
- Verify HTML files exist in selected
--slides-dir(default:slides/) - Validate each file is 720pt x 405pt (16:9) specification
2. Run html2pptx.js (experimental / unstable)
slides-grab convert --slides-dir <path> --output presentation.pptx- Script-level alternative:
node .claude/skills/pptx-skill/scripts/html2pptx.js3. Verify results
- Check generated PPTX file carefully; expect best-effort fidelity only
- Visual verification via thumbnail
Script Usage
html2pptx.js
Convert HTML files to PPTX
import { html2pptx } from './.claude/skills/pptx-skill/scripts/html2pptx.js';
import PptxGenJS from 'pptxgenjs';
const pres = new PptxGenJS();
pres.layout = 'LAYOUT_WIDE'; // 16:9
// Convert each slide
await html2pptx('<slides-dir>/slide-01.html', pres);
await html2pptx('<slides-dir>/slide-02.html', pres);
// Save
await pres.writeFile({ fileName: 'presentation.pptx' });thumbnail.py
Generate presentation thumbnail grid
python .claude/skills/pptx-skill/scripts/thumbnail.py presentation.pptx output-thumbnailOptions:
--cols N: Number of columns (default 5, range 3-6)--outline-placeholders: Show placeholder regions
pack.py / unpack.py
PPTX file packaging/unpackaging
# Unpack
python .claude/skills/pptx-skill/ooxml/scripts/unpack.py presentation.pptx output_dir
# Pack
python .claude/skills/pptx-skill/ooxml/scripts/pack.py input_dir presentation.pptxvalidate.py
PPTX structure validation
python .claude/skills/pptx-skill/ooxml/scripts/validate.py unpacked_dir --original presentation.pptxReference Documents
- html2pptx.md - HTML to PPTX conversion detailed guide
- ooxml.md - Office Open XML technical reference
PptxGenJS Key Rules
Color Codes
// Correct - without #
{ color: 'FF0000' }
// Wrong - causes file corruption
{ color: '#FF0000' }Adding Slides
const slide = pres.addSlide();
// Add text
slide.addText('Title', {
x: 0.5,
y: 0.5,
w: 9,
h: 1,
fontSize: 36,
color: '1a1a2e',
bold: true
});
// Add image
slide.addImage({
path: 'image.png',
x: 1,
y: 2,
w: 4,
h: 3
});
// Add shape
slide.addShape(pres.ShapeType.rect, {
x: 0.5,
y: 1,
w: 3,
h: 2,
fill: { color: '1e3a5f' }
});Adding Charts
// Bar chart
slide.addChart(pres.ChartType.bar, [
{
name: 'Series 1',
labels: ['A', 'B', 'C'],
values: [10, 20, 30]
}
], {
x: 1,
y: 2,
w: 8,
h: 4
});
// Pie chart
slide.addChart(pres.ChartType.pie, [...], {...});
// Line chart
slide.addChart(pres.ChartType.line, [...], {...});Full Conversion Process
+-------------------+
| HTML Slides |
| <slides-dir>/*.html |
+---------+---------+
|
v
+-------------------+
| html2pptx.js |
| (Playwright + |
| PptxGenJS) |
+---------+---------+
|
v
+-------------------+
| PPTX File |
| presentation.pptx |
+---------+---------+
|
v
+-------------------+
| thumbnail.py |
| (Preview) |
+-------------------+Dependencies
Node.js
- pptxgenjs: PowerPoint generation
- playwright: Browser rendering
- sharp: Image processing
Python
- markitdown: Markdown conversion
- defusedxml: XML parsing
- pillow: Image processing
System
- LibreOffice: PDF/image conversion (soffice)
- Poppler: PDF to image (pdftoppm)
Important Notes
- PPTX export is experimental / unstable and may require manual cleanup after generation.
1. Color codes: No # prefix in PptxGenJS 2. Fonts: Web-safe fonts only 3. Text: Only p, h1-h6, ul, ol tags are converted 4. Gradients: Replace CSS gradients with images 5. Validation: Always verify with thumbnails after conversion