# 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"
