Add pptx-plugin
This commit is contained in:
282
plugins/pptx-plugin/skills/slide-making-skill/SKILL.md
Normal file
282
plugins/pptx-plugin/skills/slide-making-skill/SKILL.md
Normal file
@@ -0,0 +1,282 @@
|
||||
---
|
||||
name: slide-making-skill
|
||||
description: "Implement single-slide PowerPoint pages with PptxGenJS. Use when writing or fixing slide JS files: dimensions, positioning, text/image/chart APIs, styling rules, and export expectations for native .pptx output."
|
||||
---
|
||||
|
||||
# PptxGenJS Slide Making Skill
|
||||
|
||||
This skill teaches how to generate native .pptx slides using PptxGenJS (JavaScript).
|
||||
|
||||
## PptxGenJS Reference
|
||||
|
||||
**Read [pptxgenjs.md](pptxgenjs.md) for the complete PptxGenJS API reference**, including:
|
||||
- Setup & basic structure
|
||||
- Text & formatting
|
||||
- Lists & bullets
|
||||
- Shapes & shadows
|
||||
- Images & icons
|
||||
- Slide backgrounds
|
||||
- Tables & charts
|
||||
|
||||
---
|
||||
|
||||
## Font Rules
|
||||
|
||||
### Font Family Standards
|
||||
|
||||
| Language | Default | Alternatives |
|
||||
|----------|---------|--------------|
|
||||
| **Chinese** | Microsoft YaHei (微软雅黑) | — |
|
||||
| **English** | Arial | Georgia, Calibri, Cambria, Trebuchet MS |
|
||||
|
||||
```javascript
|
||||
fontFace: "Microsoft YaHei" // Chinese text
|
||||
fontFace: "Arial" // English text (or approved alternative)
|
||||
```
|
||||
|
||||
Use system-safe fonts for cross-platform compatibility. Header/body can use different fonts (e.g., Georgia + Calibri).
|
||||
|
||||
### No Bold for Body Text
|
||||
|
||||
**Plain body text and caption/legend text must NOT use bold.**
|
||||
|
||||
- Body paragraphs, descriptions → normal weight
|
||||
- Captions, legends, footnotes → normal weight
|
||||
- Reserve bold for titles and headings only
|
||||
|
||||
```javascript
|
||||
// ✅ Correct
|
||||
slide.addText("Main Title", { bold: true, fontSize: 36, fontFace: "Arial" });
|
||||
slide.addText("Body text here.", { bold: false, fontSize: 14, fontFace: "Arial" });
|
||||
|
||||
// ❌ Wrong
|
||||
slide.addText("Body text here.", { bold: true, fontSize: 14 });
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Color Palette Rules (MANDATORY)
|
||||
|
||||
### Strict Palette Adherence
|
||||
|
||||
**Use ONLY the provided color palette. Do NOT create or modify colors.**
|
||||
|
||||
- All colors must come from the user-provided palette
|
||||
- Do NOT use colors outside the palette
|
||||
- Do NOT modify palette colors (brightness, saturation, mixing)
|
||||
- **Only exception**: Add transparency using the `transparency` property (0-100)
|
||||
|
||||
```javascript
|
||||
// ✅ Correct: Using palette colors
|
||||
slide.addShape(pres.shapes.RECTANGLE, { fill: { color: theme.primary } });
|
||||
slide.addText("Title", { color: theme.accent });
|
||||
|
||||
// ❌ Wrong: Colors outside palette
|
||||
slide.addShape(pres.shapes.RECTANGLE, { fill: { color: "1a1a2e" } });
|
||||
```
|
||||
|
||||
### No Gradients
|
||||
|
||||
**Gradients are prohibited. Use solid colors only.**
|
||||
|
||||
```javascript
|
||||
// ✅ Correct: Solid colors
|
||||
slide.background = { color: theme.bg };
|
||||
|
||||
// ✅ Correct: Solid + transparency for overlay
|
||||
slide.addShape(pres.shapes.RECTANGLE, { fill: { color: theme.accent, transparency: 50 } });
|
||||
```
|
||||
|
||||
### No Animations
|
||||
|
||||
**Animations and transitions are prohibited.** All slides must be static.
|
||||
|
||||
---
|
||||
|
||||
## Page Number Badge (REQUIRED)
|
||||
|
||||
All slides **except Cover Page** MUST include a page number badge in the bottom-right corner.
|
||||
|
||||
- **Position**: x: 9.3", y: 5.1"
|
||||
- Show current number only (e.g. `3` or `03`), NOT "3/12"
|
||||
- Use palette colors, keep subtle
|
||||
|
||||
### Circle Badge (Default)
|
||||
|
||||
```javascript
|
||||
slide.addShape(pres.shapes.OVAL, {
|
||||
x: 9.3, y: 5.1, w: 0.4, h: 0.4,
|
||||
fill: { color: theme.accent }
|
||||
});
|
||||
slide.addText("3", {
|
||||
x: 9.3, y: 5.1, w: 0.4, h: 0.4,
|
||||
fontSize: 12, fontFace: "Arial",
|
||||
color: "FFFFFF", bold: true,
|
||||
align: "center", valign: "middle"
|
||||
});
|
||||
```
|
||||
|
||||
### Pill Badge
|
||||
|
||||
```javascript
|
||||
slide.addShape(pres.shapes.ROUNDED_RECTANGLE, {
|
||||
x: 9.1, y: 5.15, w: 0.6, h: 0.35,
|
||||
fill: { color: theme.accent },
|
||||
rectRadius: 0.15
|
||||
});
|
||||
slide.addText("03", {
|
||||
x: 9.1, y: 5.15, w: 0.6, h: 0.35,
|
||||
fontSize: 11, fontFace: "Arial",
|
||||
color: "FFFFFF", bold: true,
|
||||
align: "center", valign: "middle"
|
||||
});
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Theme Object Contract (MANDATORY)
|
||||
|
||||
The compile script passes a theme object with these **exact keys**:
|
||||
|
||||
| Key | Purpose | Example |
|
||||
|-----|---------|---------|
|
||||
| `theme.primary` | Darkest color, titles | `"22223b"` |
|
||||
| `theme.secondary` | Dark accent, body text | `"4a4e69"` |
|
||||
| `theme.accent` | Mid-tone accent | `"9a8c98"` |
|
||||
| `theme.light` | Light accent | `"c9ada7"` |
|
||||
| `theme.bg` | Background color | `"f2e9e4"` |
|
||||
|
||||
**NEVER use other key names** like `background`, `text`, `muted`, `darkest`, `lightest`.
|
||||
|
||||
---
|
||||
|
||||
## Subagent Output Format
|
||||
|
||||
Each subagent outputs a **complete, runnable JS file**:
|
||||
|
||||
```javascript
|
||||
// slide-01.js
|
||||
const pptxgen = require("pptxgenjs");
|
||||
|
||||
const slideConfig = {
|
||||
type: 'cover',
|
||||
index: 1,
|
||||
title: 'Presentation Title'
|
||||
};
|
||||
|
||||
// ⚠️ MUST be synchronous (not async)
|
||||
function createSlide(pres, theme) {
|
||||
const slide = pres.addSlide();
|
||||
slide.background = { color: theme.bg };
|
||||
|
||||
slide.addText(slideConfig.title, {
|
||||
x: 0.5, y: 2, w: 9, h: 1.2,
|
||||
fontSize: 48, fontFace: "Arial", // English text uses Arial
|
||||
color: theme.primary, bold: true, align: "center"
|
||||
});
|
||||
|
||||
return slide;
|
||||
}
|
||||
|
||||
// Standalone preview - use slide-specific filename (slide-XX-preview.pptx)
|
||||
if (require.main === module) {
|
||||
const pres = new pptxgen();
|
||||
pres.layout = 'LAYOUT_16x9';
|
||||
const theme = {
|
||||
primary: "22223b",
|
||||
secondary: "4a4e69",
|
||||
accent: "9a8c98",
|
||||
light: "c9ada7",
|
||||
bg: "f2e9e4"
|
||||
};
|
||||
createSlide(pres, theme);
|
||||
// Replace XX with actual slide index (01, 02, etc.) to avoid conflicts
|
||||
pres.writeFile({ fileName: "slide-01-preview.pptx" });
|
||||
}
|
||||
|
||||
module.exports = { createSlide, slideConfig };
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Critical Pitfalls
|
||||
|
||||
### NEVER use async/await in createSlide()
|
||||
|
||||
```javascript
|
||||
// ❌ WRONG - compile.js won't await
|
||||
async function createSlide(pres, theme) { ... }
|
||||
|
||||
// ✅ CORRECT
|
||||
function createSlide(pres, theme) { ... }
|
||||
```
|
||||
|
||||
### NEVER use "#" with hex colors
|
||||
|
||||
```javascript
|
||||
color: "FF0000" // ✅ CORRECT
|
||||
color: "#FF0000" // ❌ CORRUPTS FILE
|
||||
```
|
||||
|
||||
### NEVER encode opacity in hex strings
|
||||
|
||||
```javascript
|
||||
shadow: { color: "00000020" } // ❌ CORRUPTS FILE
|
||||
shadow: { color: "000000", opacity: 0.12 } // ✅ CORRECT
|
||||
```
|
||||
|
||||
### Prevent text wrapping in titles
|
||||
|
||||
```javascript
|
||||
// ✅ Use fit:'shrink' for long titles
|
||||
slide.addText("Long Title Here", {
|
||||
x: 0.5, y: 2, w: 9, h: 1,
|
||||
fontSize: 48, fit: "shrink"
|
||||
});
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Quick Reference
|
||||
|
||||
- **Dimensions**: 10" × 5.625" (LAYOUT_16x9)
|
||||
- **Colors**: 6-char hex without # (e.g., `"FF0000"`)
|
||||
- **English font**: Arial (default), or approved alternatives
|
||||
- **Chinese font**: Microsoft YaHei
|
||||
- **Page badge position**: x: 9.3", y: 5.1"
|
||||
|
||||
---
|
||||
|
||||
## QA (Required)
|
||||
|
||||
**Assume there are problems. Your job is to find them.**
|
||||
|
||||
Your first render is almost never correct. Approach QA as a bug hunt, not a confirmation step. If you found zero issues on first inspection, you weren't looking hard enough.
|
||||
|
||||
### Content QA
|
||||
|
||||
```bash
|
||||
python -m markitdown slide-XX-preview.pptx
|
||||
```
|
||||
|
||||
Check for missing content, typos, wrong order.
|
||||
|
||||
**Check for leftover placeholder text:**
|
||||
|
||||
```bash
|
||||
python -m markitdown slide-XX-preview.pptx | grep -iE "xxxx|lorem|ipsum|placeholder"
|
||||
```
|
||||
|
||||
If grep returns results, fix them before declaring success.
|
||||
|
||||
### Verification Loop
|
||||
|
||||
1. Generate slide → Extract text with `python -m markitdown slide-XX-preview.pptx` → Review content
|
||||
2. **List issues found** (if none found, look again more critically)
|
||||
3. Fix issues
|
||||
4. **Re-verify** — one fix often creates another problem
|
||||
5. Repeat until verification reveals no new issues
|
||||
|
||||
**Do not declare success until you've completed at least one fix-and-verify cycle.**
|
||||
|
||||
---
|
||||
420
plugins/pptx-plugin/skills/slide-making-skill/pptxgenjs.md
Normal file
420
plugins/pptx-plugin/skills/slide-making-skill/pptxgenjs.md
Normal file
@@ -0,0 +1,420 @@
|
||||
# PptxGenJS Tutorial
|
||||
|
||||
## Setup & Basic Structure
|
||||
|
||||
```javascript
|
||||
const pptxgen = require("pptxgenjs");
|
||||
|
||||
let pres = new pptxgen();
|
||||
pres.layout = 'LAYOUT_16x9'; // or 'LAYOUT_16x10', 'LAYOUT_4x3', 'LAYOUT_WIDE'
|
||||
pres.author = 'Your Name';
|
||||
pres.title = 'Presentation Title';
|
||||
|
||||
let slide = pres.addSlide();
|
||||
slide.addText("Hello World!", { x: 0.5, y: 0.5, fontSize: 36, color: "363636" });
|
||||
|
||||
pres.writeFile({ fileName: "Presentation.pptx" });
|
||||
```
|
||||
|
||||
## Layout Dimensions
|
||||
|
||||
Slide dimensions (coordinates in inches):
|
||||
- `LAYOUT_16x9`: 10" × 5.625" (default)
|
||||
- `LAYOUT_16x10`: 10" × 6.25"
|
||||
- `LAYOUT_4x3`: 10" × 7.5"
|
||||
- `LAYOUT_WIDE`: 13.3" × 7.5"
|
||||
|
||||
---
|
||||
|
||||
## Text & Formatting
|
||||
|
||||
```javascript
|
||||
// Basic text
|
||||
slide.addText("Simple Text", {
|
||||
x: 1, y: 1, w: 8, h: 2, fontSize: 24, fontFace: "Arial",
|
||||
color: "363636", bold: true, align: "center", valign: "middle"
|
||||
});
|
||||
|
||||
// Character spacing (use charSpacing, not letterSpacing which is silently ignored)
|
||||
slide.addText("SPACED TEXT", { x: 1, y: 1, w: 8, h: 1, charSpacing: 6 });
|
||||
|
||||
// Rich text arrays
|
||||
slide.addText([
|
||||
{ text: "Bold ", options: { bold: true } },
|
||||
{ text: "Italic ", options: { italic: true } }
|
||||
], { x: 1, y: 3, w: 8, h: 1 });
|
||||
|
||||
// Multi-line text (requires breakLine: true)
|
||||
slide.addText([
|
||||
{ text: "Line 1", options: { breakLine: true } },
|
||||
{ text: "Line 2", options: { breakLine: true } },
|
||||
{ text: "Line 3" } // Last item doesn't need breakLine
|
||||
], { x: 0.5, y: 0.5, w: 8, h: 2 });
|
||||
|
||||
// Text box margin (internal padding)
|
||||
slide.addText("Title", {
|
||||
x: 0.5, y: 0.3, w: 9, h: 0.6,
|
||||
margin: 0 // Use 0 when aligning text with other elements like shapes or icons
|
||||
});
|
||||
```
|
||||
|
||||
**Tip:** Text boxes have internal margin by default. Set `margin: 0` when you need text to align precisely with shapes, lines, or icons at the same x-position.
|
||||
|
||||
---
|
||||
|
||||
## Lists & Bullets
|
||||
|
||||
```javascript
|
||||
// ✅ CORRECT: Multiple bullets
|
||||
slide.addText([
|
||||
{ text: "First item", options: { bullet: true, breakLine: true } },
|
||||
{ text: "Second item", options: { bullet: true, breakLine: true } },
|
||||
{ text: "Third item", options: { bullet: true } }
|
||||
], { x: 0.5, y: 0.5, w: 8, h: 3 });
|
||||
|
||||
// ❌ WRONG: Never use unicode bullets
|
||||
slide.addText("• First item", { ... }); // Creates double bullets
|
||||
|
||||
// Sub-items and numbered lists
|
||||
{ text: "Sub-item", options: { bullet: true, indentLevel: 1 } }
|
||||
{ text: "First", options: { bullet: { type: "number" }, breakLine: true } }
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Shapes
|
||||
|
||||
```javascript
|
||||
slide.addShape(pres.shapes.RECTANGLE, {
|
||||
x: 0.5, y: 0.8, w: 1.5, h: 3.0,
|
||||
fill: { color: "FF0000" }, line: { color: "000000", width: 2 }
|
||||
});
|
||||
|
||||
slide.addShape(pres.shapes.OVAL, { x: 4, y: 1, w: 2, h: 2, fill: { color: "0000FF" } });
|
||||
|
||||
slide.addShape(pres.shapes.LINE, {
|
||||
x: 1, y: 3, w: 5, h: 0, line: { color: "FF0000", width: 3, dashType: "dash" }
|
||||
});
|
||||
|
||||
// With transparency
|
||||
slide.addShape(pres.shapes.RECTANGLE, {
|
||||
x: 1, y: 1, w: 3, h: 2,
|
||||
fill: { color: "0088CC", transparency: 50 }
|
||||
});
|
||||
|
||||
// Rounded rectangle (rectRadius only works with ROUNDED_RECTANGLE, not RECTANGLE)
|
||||
// ⚠️ Don't pair with rectangular accent overlays — they won't cover rounded corners. Use RECTANGLE instead.
|
||||
slide.addShape(pres.shapes.ROUNDED_RECTANGLE, {
|
||||
x: 1, y: 1, w: 3, h: 2,
|
||||
fill: { color: "FFFFFF" }, rectRadius: 0.1
|
||||
});
|
||||
|
||||
// With shadow
|
||||
slide.addShape(pres.shapes.RECTANGLE, {
|
||||
x: 1, y: 1, w: 3, h: 2,
|
||||
fill: { color: "FFFFFF" },
|
||||
shadow: { type: "outer", color: "000000", blur: 6, offset: 2, angle: 135, opacity: 0.15 }
|
||||
});
|
||||
```
|
||||
|
||||
Shadow options:
|
||||
|
||||
| Property | Type | Range | Notes |
|
||||
|----------|------|-------|-------|
|
||||
| `type` | string | `"outer"`, `"inner"` | |
|
||||
| `color` | string | 6-char hex (e.g. `"000000"`) | No `#` prefix, no 8-char hex — see Common Pitfalls |
|
||||
| `blur` | number | 0-100 pt | |
|
||||
| `offset` | number | 0-200 pt | **Must be non-negative** — negative values corrupt the file |
|
||||
| `angle` | number | 0-359 degrees | Direction the shadow falls (135 = bottom-right, 270 = upward) |
|
||||
| `opacity` | number | 0.0-1.0 | Use this for transparency, never encode in color string |
|
||||
|
||||
To cast a shadow upward (e.g. on a footer bar), use `angle: 270` with a positive offset — do **not** use a negative offset.
|
||||
|
||||
**Note**: Gradient fills are not natively supported. Use a gradient image as a background instead.
|
||||
|
||||
---
|
||||
|
||||
## Images
|
||||
|
||||
### Image Sources
|
||||
|
||||
```javascript
|
||||
// From file path
|
||||
slide.addImage({ path: "images/chart.png", x: 1, y: 1, w: 5, h: 3 });
|
||||
|
||||
// From URL
|
||||
slide.addImage({ path: "https://example.com/image.jpg", x: 1, y: 1, w: 5, h: 3 });
|
||||
|
||||
// From base64 (faster, no file I/O)
|
||||
slide.addImage({ data: "image/png;base64,iVBORw0KGgo...", x: 1, y: 1, w: 5, h: 3 });
|
||||
```
|
||||
|
||||
### Image Options
|
||||
|
||||
```javascript
|
||||
slide.addImage({
|
||||
path: "image.png",
|
||||
x: 1, y: 1, w: 5, h: 3,
|
||||
rotate: 45, // 0-359 degrees
|
||||
rounding: true, // Circular crop
|
||||
transparency: 50, // 0-100
|
||||
flipH: true, // Horizontal flip
|
||||
flipV: false, // Vertical flip
|
||||
altText: "Description", // Accessibility
|
||||
hyperlink: { url: "https://example.com" }
|
||||
});
|
||||
```
|
||||
|
||||
### Image Sizing Modes
|
||||
|
||||
```javascript
|
||||
// Contain - fit inside, preserve ratio
|
||||
{ sizing: { type: 'contain', w: 4, h: 3 } }
|
||||
|
||||
// Cover - fill area, preserve ratio (may crop)
|
||||
{ sizing: { type: 'cover', w: 4, h: 3 } }
|
||||
|
||||
// Crop - cut specific portion
|
||||
{ sizing: { type: 'crop', x: 0.5, y: 0.5, w: 2, h: 2 } }
|
||||
```
|
||||
|
||||
### Calculate Dimensions (preserve aspect ratio)
|
||||
|
||||
```javascript
|
||||
const origWidth = 1978, origHeight = 923, maxHeight = 3.0;
|
||||
const calcWidth = maxHeight * (origWidth / origHeight);
|
||||
const centerX = (10 - calcWidth) / 2;
|
||||
|
||||
slide.addImage({ path: "image.png", x: centerX, y: 1.2, w: calcWidth, h: maxHeight });
|
||||
```
|
||||
|
||||
### Supported Formats
|
||||
|
||||
- **Standard**: PNG, JPG, GIF (animated GIFs work in Microsoft 365)
|
||||
- **SVG**: Works in modern PowerPoint/Microsoft 365
|
||||
|
||||
---
|
||||
|
||||
## Icons
|
||||
|
||||
Use react-icons to generate SVG icons, then rasterize to PNG for universal compatibility.
|
||||
|
||||
### Setup
|
||||
|
||||
```javascript
|
||||
const React = require("react");
|
||||
const ReactDOMServer = require("react-dom/server");
|
||||
const sharp = require("sharp");
|
||||
const { FaCheckCircle, FaChartLine } = require("react-icons/fa");
|
||||
|
||||
function renderIconSvg(IconComponent, color = "#000000", size = 256) {
|
||||
return ReactDOMServer.renderToStaticMarkup(
|
||||
React.createElement(IconComponent, { color, size: String(size) })
|
||||
);
|
||||
}
|
||||
|
||||
async function iconToBase64Png(IconComponent, color, size = 256) {
|
||||
const svg = renderIconSvg(IconComponent, color, size);
|
||||
const pngBuffer = await sharp(Buffer.from(svg)).png().toBuffer();
|
||||
return "image/png;base64," + pngBuffer.toString("base64");
|
||||
}
|
||||
```
|
||||
|
||||
### Add Icon to Slide
|
||||
|
||||
```javascript
|
||||
const iconData = await iconToBase64Png(FaCheckCircle, "#4472C4", 256);
|
||||
|
||||
slide.addImage({
|
||||
data: iconData,
|
||||
x: 1, y: 1, w: 0.5, h: 0.5 // Size in inches
|
||||
});
|
||||
```
|
||||
|
||||
**Note**: Use size 256 or higher for crisp icons. The size parameter controls the rasterization resolution, not the display size on the slide (which is set by `w` and `h` in inches).
|
||||
|
||||
### Icon Libraries
|
||||
|
||||
Install: `npm install -g react-icons react react-dom sharp`
|
||||
|
||||
Popular icon sets in react-icons:
|
||||
- `react-icons/fa` - Font Awesome
|
||||
- `react-icons/md` - Material Design
|
||||
- `react-icons/hi` - Heroicons
|
||||
- `react-icons/bi` - Bootstrap Icons
|
||||
|
||||
---
|
||||
|
||||
## Slide Backgrounds
|
||||
|
||||
```javascript
|
||||
// Solid color
|
||||
slide.background = { color: "F1F1F1" };
|
||||
|
||||
// Color with transparency
|
||||
slide.background = { color: "FF3399", transparency: 50 };
|
||||
|
||||
// Image from URL
|
||||
slide.background = { path: "https://example.com/bg.jpg" };
|
||||
|
||||
// Image from base64
|
||||
slide.background = { data: "image/png;base64,iVBORw0KGgo..." };
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Tables
|
||||
|
||||
```javascript
|
||||
slide.addTable([
|
||||
["Header 1", "Header 2"],
|
||||
["Cell 1", "Cell 2"]
|
||||
], {
|
||||
x: 1, y: 1, w: 8, h: 2,
|
||||
border: { pt: 1, color: "999999" }, fill: { color: "F1F1F1" }
|
||||
});
|
||||
|
||||
// Advanced with merged cells
|
||||
let tableData = [
|
||||
[{ text: "Header", options: { fill: { color: "6699CC" }, color: "FFFFFF", bold: true } }, "Cell"],
|
||||
[{ text: "Merged", options: { colspan: 2 } }]
|
||||
];
|
||||
slide.addTable(tableData, { x: 1, y: 3.5, w: 8, colW: [4, 4] });
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Charts
|
||||
|
||||
```javascript
|
||||
// Bar chart
|
||||
slide.addChart(pres.charts.BAR, [{
|
||||
name: "Sales", labels: ["Q1", "Q2", "Q3", "Q4"], values: [4500, 5500, 6200, 7100]
|
||||
}], {
|
||||
x: 0.5, y: 0.6, w: 6, h: 3, barDir: 'col',
|
||||
showTitle: true, title: 'Quarterly Sales'
|
||||
});
|
||||
|
||||
// Line chart
|
||||
slide.addChart(pres.charts.LINE, [{
|
||||
name: "Temp", labels: ["Jan", "Feb", "Mar"], values: [32, 35, 42]
|
||||
}], { x: 0.5, y: 4, w: 6, h: 3, lineSize: 3, lineSmooth: true });
|
||||
|
||||
// Pie chart
|
||||
slide.addChart(pres.charts.PIE, [{
|
||||
name: "Share", labels: ["A", "B", "Other"], values: [35, 45, 20]
|
||||
}], { x: 7, y: 1, w: 5, h: 4, showPercent: true });
|
||||
```
|
||||
|
||||
### Better-Looking Charts
|
||||
|
||||
Default charts look dated. Apply these options for a modern, clean appearance:
|
||||
|
||||
```javascript
|
||||
slide.addChart(pres.charts.BAR, chartData, {
|
||||
x: 0.5, y: 1, w: 9, h: 4, barDir: "col",
|
||||
|
||||
// Custom colors (match your presentation palette)
|
||||
chartColors: ["0D9488", "14B8A6", "5EEAD4"],
|
||||
|
||||
// Clean background
|
||||
chartArea: { fill: { color: "FFFFFF" }, roundedCorners: true },
|
||||
|
||||
// Muted axis labels
|
||||
catAxisLabelColor: "64748B",
|
||||
valAxisLabelColor: "64748B",
|
||||
|
||||
// Subtle grid (value axis only)
|
||||
valGridLine: { color: "E2E8F0", size: 0.5 },
|
||||
catGridLine: { style: "none" },
|
||||
|
||||
// Data labels on bars
|
||||
showValue: true,
|
||||
dataLabelPosition: "outEnd",
|
||||
dataLabelColor: "1E293B",
|
||||
|
||||
// Hide legend for single series
|
||||
showLegend: false,
|
||||
});
|
||||
```
|
||||
|
||||
**Key styling options:**
|
||||
- `chartColors: [...]` - hex colors for series/segments
|
||||
- `chartArea: { fill, border, roundedCorners }` - chart background
|
||||
- `catGridLine/valGridLine: { color, style, size }` - grid lines (`style: "none"` to hide)
|
||||
- `lineSmooth: true` - curved lines (line charts)
|
||||
- `legendPos: "r"` - legend position: "b", "t", "l", "r", "tr"
|
||||
|
||||
---
|
||||
|
||||
## Slide Masters
|
||||
|
||||
```javascript
|
||||
pres.defineSlideMaster({
|
||||
title: 'TITLE_SLIDE', background: { color: '283A5E' },
|
||||
objects: [{
|
||||
placeholder: { options: { name: 'title', type: 'title', x: 1, y: 2, w: 8, h: 2 } }
|
||||
}]
|
||||
});
|
||||
|
||||
let titleSlide = pres.addSlide({ masterName: "TITLE_SLIDE" });
|
||||
titleSlide.addText("My Title", { placeholder: "title" });
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Common Pitfalls
|
||||
|
||||
⚠️ These issues cause file corruption, visual bugs, or broken output. Avoid them.
|
||||
|
||||
1. **NEVER use "#" with hex colors** - causes file corruption
|
||||
```javascript
|
||||
color: "FF0000" // ✅ CORRECT
|
||||
color: "#FF0000" // ❌ WRONG
|
||||
```
|
||||
|
||||
2. **NEVER encode opacity in hex color strings** - 8-char colors (e.g., `"00000020"`) corrupt the file. Use the `opacity` property instead.
|
||||
```javascript
|
||||
shadow: { type: "outer", blur: 6, offset: 2, color: "00000020" } // ❌ CORRUPTS FILE
|
||||
shadow: { type: "outer", blur: 6, offset: 2, color: "000000", opacity: 0.12 } // ✅ CORRECT
|
||||
```
|
||||
|
||||
3. **Use `bullet: true`** - NEVER unicode symbols like "•" (creates double bullets)
|
||||
|
||||
4. **Use `breakLine: true`** between array items or text runs together
|
||||
|
||||
5. **Avoid `lineSpacing` with bullets** - causes excessive gaps; use `paraSpaceAfter` instead
|
||||
|
||||
6. **Each presentation needs fresh instance** - don't reuse `pptxgen()` objects
|
||||
|
||||
7. **NEVER reuse option objects across calls** - PptxGenJS mutates objects in-place (e.g. converting shadow values to EMU). Sharing one object between multiple calls corrupts the second shape.
|
||||
```javascript
|
||||
const shadow = { type: "outer", blur: 6, offset: 2, color: "000000", opacity: 0.15 };
|
||||
slide.addShape(pres.shapes.RECTANGLE, { shadow, ... }); // ❌ second call gets already-converted values
|
||||
slide.addShape(pres.shapes.RECTANGLE, { shadow, ... });
|
||||
|
||||
const makeShadow = () => ({ type: "outer", blur: 6, offset: 2, color: "000000", opacity: 0.15 });
|
||||
slide.addShape(pres.shapes.RECTANGLE, { shadow: makeShadow(), ... }); // ✅ fresh object each time
|
||||
slide.addShape(pres.shapes.RECTANGLE, { shadow: makeShadow(), ... });
|
||||
```
|
||||
|
||||
8. **Don't use `ROUNDED_RECTANGLE` with accent borders** - rectangular overlay bars won't cover rounded corners. Use `RECTANGLE` instead.
|
||||
```javascript
|
||||
// ❌ WRONG: Accent bar doesn't cover rounded corners
|
||||
slide.addShape(pres.shapes.ROUNDED_RECTANGLE, { x: 1, y: 1, w: 3, h: 1.5, fill: { color: "FFFFFF" } });
|
||||
slide.addShape(pres.shapes.RECTANGLE, { x: 1, y: 1, w: 0.08, h: 1.5, fill: { color: "0891B2" } });
|
||||
|
||||
// ✅ CORRECT: Use RECTANGLE for clean alignment
|
||||
slide.addShape(pres.shapes.RECTANGLE, { x: 1, y: 1, w: 3, h: 1.5, fill: { color: "FFFFFF" } });
|
||||
slide.addShape(pres.shapes.RECTANGLE, { x: 1, y: 1, w: 0.08, h: 1.5, fill: { color: "0891B2" } });
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Quick Reference
|
||||
|
||||
- **Shapes**: RECTANGLE, OVAL, LINE, ROUNDED_RECTANGLE
|
||||
- **Charts**: BAR, LINE, PIE, DOUGHNUT, SCATTER, BUBBLE, RADAR
|
||||
- **Layouts**: LAYOUT_16x9 (10"×5.625"), LAYOUT_16x10, LAYOUT_4x3, LAYOUT_WIDE
|
||||
- **Alignment**: "left", "center", "right"
|
||||
- **Chart data labels**: "outEnd", "inEnd", "center"
|
||||
Reference in New Issue
Block a user