# PPTX Importer for CE.SDK

A TypeScript library for importing PowerPoint (PPTX) presentations into [IMG.LY's Creative Engine SDK](https://img.ly/products/creative-sdk). Converts PPTX slides into CE.SDK scenes with full support for text, shapes, images, and formatting.

## Features

### ✨ Comprehensive PPTX Support

- **Text Elements**: Full text formatting including font families, sizes, colors, bold, italic, character spacing, and line height
- **Shapes**: Rectangles, ellipses, and custom vector shapes with fills and strokes
- **Gradient Fills**: Linear and radial gradients on shapes (70% of presentations)
- **Shadow Effects**: Outer drop shadows with blur, offset, and transparency (60% of presentations)
- **Gradient Backgrounds**: Linear and radial gradients on slide backgrounds
- **Images**: Embedded images with proper positioning and dimensions
- **Groups**: Grouped elements with preserved hierarchy
- **Layout**: Accurate positioning, sizing, and rotation
- **Z-Order**: Maintains element stacking order from PowerPoint
- **Theme Colors**: Resolves PowerPoint theme color references

### 🌐 Platform-Agnostic

Works in **both browser and Node.js** environments:
- No file system dependencies
- Pure ArrayBuffer/Uint8Array handling
- Platform-neutral base64 encoding
- ESM module format

### 🎯 Production-Ready

- **TypeScript-first** with full type definitions
- **Comprehensive test suite** with golden screenshot tests
- **Visual regression testing** with golden screenshots
- **Font resolution** via Google Fonts integration
- **Error handling** with detailed warnings for unsupported features

## Installation

```bash
npm install @imgly/pptx-importer
```

### Peer Dependencies

You'll need CE.SDK in your project:

```bash
# For browser
npm install @cesdk/engine

# For Node.js
npm install @cesdk/node
```

`@imgly/pptx-importer` uses JSZip and fast-xml-parser internally to read PPTX archives. They are installed automatically as dependencies of this package.

## Quick Start

### Browser

```typescript
import CreativeEngine from '@cesdk/engine';
import { PPTXParser } from '@imgly/pptx-importer';

// Initialize CE.SDK engine
const config = {
  license: 'your-license-key',
  userId: 'your-user-id',
};
const engine = await CreativeEngine.init(config);

// Load PPTX file
const response = await fetch('presentation.pptx');
const arrayBuffer = await response.arrayBuffer();

// Parse PPTX and create CE.SDK scene (automatically parses all slides)
const parser = await PPTXParser.fromFile(engine, arrayBuffer);
const result = await parser.parse();

// Get created pages
const pages = engine.block.findByType('//ly.img.ubq/page');
console.log(`Imported ${pages.length} slides`);

// Check for any warnings
const messages = result.logger.getMessages();
const warnings = messages.filter(m => m.type === 'warning');
if (warnings.length > 0) {
  console.warn('Unsupported features:', warnings);
}

// Export first slide
const blob = await engine.block.export(pages[0], 'image/png');
```

### Node.js

```typescript
import CreativeEngine from '@cesdk/node';
import { PPTXParser } from '@imgly/pptx-importer';
import { readFileSync } from 'fs';

// Initialize CE.SDK engine
const engine = await CreativeEngine.init({
  license: process.env.CESDK_LICENSE,
});

// Load PPTX file
const fileBuffer = readFileSync('presentation.pptx');
const arrayBuffer = fileBuffer.buffer.slice(
  fileBuffer.byteOffset,
  fileBuffer.byteOffset + fileBuffer.byteLength
);

// Parse PPTX (automatically parses all slides and attaches to scene)
const parser = await PPTXParser.fromFile(engine, arrayBuffer);
const result = await parser.parse();

// Get created pages
const pages = engine.block.findByType('//ly.img.ubq/page');

// Export first slide
const blob = await engine.block.export(pages[0], 'image/png');
```

## API Reference

### `PPTXParser`

#### Static Methods

##### `PPTXParser.fromFile(engine, fileBuffer, options?)`

Creates a parser instance from a PPTX file.

**Parameters:**
- `engine: CreativeEngine` - CE.SDK engine instance
- `fileBuffer: ArrayBuffer` - PPTX file as ArrayBuffer
- `options?: Partial<PPTXParserOptions>` - Optional configuration

**Returns:** `Promise<PPTXParser>`

**Example:**
```typescript
const parser = await PPTXParser.fromFile(engine, arrayBuffer, {
  tolerancePercent: 5,
  logWarnings: true,
  strictMode: false,
});
```

#### Instance Methods

##### `parser.parse()`

Parses all slides in the PPTX file and automatically attaches them to the scene.

**Parameters:** None

**Returns:** `Promise<ParseResult>` - Contains logger with warnings and messages

**Example:**
```typescript
// Parse all slides
const result = await parser.parse();

// Get created pages
const pages = engine.block.findByType('//ly.img.ubq/page');
console.log(`Imported ${pages.length} slides`);

// Access individual pages
pages.forEach((pageId, index) => {
  console.log(`Slide ${index + 1}: block ID ${pageId}`);
});
```

> **Note:** This API matches the PSD importer pattern for consistency across IMG.LY importers.
> All slides are parsed and attached to the scene automatically. Use `engine.block.findByType()` to retrieve page block IDs.

##### `parser.getSlideCount()`

Returns the total number of slides in the PPTX file.

**Returns:** `number`

### `ParseResult`

The result returned by `parser.parse()` contains a logger with warnings and messages.

**Example:**
```typescript
const result = await parser.parse();

// Get all messages
const messages = result.logger.getMessages();

// Filter by type
const warnings = messages.filter(m => m.type === 'warning');
const errors = messages.filter(m => m.type === 'error');

warnings.forEach(w => {
  console.log(`Warning: ${w.message}`);
});
```

### `PPTXParserOptions`

Configuration options for the parser.

```typescript
interface PPTXParserOptions {
  /**
   * Tolerance percentage for numeric property validation in tests
   * @default 5
   */
  tolerancePercent?: number;

  /**
   * Apply font metrics-based vertical position correction for text blocks
   * @default true
   */
  applyFontMetricsCorrection?: boolean;

  /**
   * Auto-correct text blocks that overflow their bounds
   * @default true
   */
  autoCorrectTextOverflow?: boolean;
}
```

## Supported Features

### ✅ Fully Supported

- **Text Blocks**
  - Font family, size, color
  - Bold, italic formatting
  - Character spacing
  - Line height
  - Multiple text runs with different formatting
  - Vertical alignment

- **Shapes**
  - Rectangles (with corner radius)
  - Ellipses/circles
  - Custom vector shapes (via SVG path conversion)
  - Fill colors (solid, theme colors)
  - Stroke colors and widths

- **Images**
  - PNG, JPEG, GIF, BMP, WebP
  - Embedded images
  - Proper sizing and positioning

- **Backgrounds**
  - Solid colors (RGB, theme colors)
  - Linear and radial gradients

- **Layout**
  - Absolute positioning
  - Rotation
  - Z-order preservation
  - Groups and nested elements

### ⚠️ Partially Supported

- **Gradients**
  - ✅ Linear gradients (fully supported)
  - ✅ Radial gradients (fully supported)
  - ⚠️ Path gradients (falls back to radial with warning)
  - ⚠️ Rectangular gradients (falls back to radial with warning)

- **Shadow Effects**
  - ✅ Outer drop shadows (fully supported)
  - ⚠️ Inner shadows (converted to outer shadows with warning)

- **Colors**
  - ✅ RGB colors
  - ✅ Theme colors (with fallback)
  - ⚠️ Transparency (basic support)

- **Text**
  - ✅ Basic formatting
  - ⚠️ Bullets and numbering (basic)

### ❌ Not Supported

- **Tables** (`<a:tbl>`) - Table content is skipped; workaround: ungroup table in PowerPoint before export
- **Charts** - Chart visualizations are not imported
- **SmartArt** - Diagram graphics are not imported
- Animations and transitions
- Slide masters and layouts
- Audio and video
- Comments and notes
- Background images (solid colors and gradients ARE supported)

## Font Loading

The importer resolves fonts using Google Fonts and substitutes common proprietary fonts (Helvetica, Arial, Times New Roman, etc.) with visually-similar Google Fonts equivalents. The font catalog and fallback aliases are served as two CE.SDK asset sources from the IMG.LY CDN:

| Asset source | Contents | Size (gzipped) |
|--------------|----------|----------------|
| `ly.img.gfonts` | 1,394 Google Fonts typefaces with all variants | ~127 KB |
| `ly.img.gfonts-fallbacks` | 16 proprietary-font aliases (Helvetica→Roboto, Arial→Arimo, …) | ~3 KB |

`PPTXParser.fromFile(...)` automatically calls `addGfontsAssetLibrary(engine)` during initialization, which registers both sources via `engine.asset.addLocalAssetSourceFromJSONURI(...)`. The files are fetched from `https://staticimgly.com/imgly/gfonts/<version>/dist/` at registration time and CE.SDK caches the font files once resolved.

**No extra pnpm install is required.** The sources are internal to IMG.LY and loaded from the CDN; you do not install an additional package. An active internet connection is required during parser setup.

### Customizing font resolution

The `addGfontsAssetLibrary` helper and the default `fontResolver` function are exported from the public API so you can:

- Pre-register the gfonts asset sources yourself (for example, early in your app boot to pre-warm the CDN fetch):
  ```typescript
  import { addGfontsAssetLibrary } from '@imgly/pptx-importer';
  await addGfontsAssetLibrary(engine);
  ```
- Register your own typeface asset source with the same ID (`ly.img.gfonts` or `ly.img.gfonts-fallbacks`) *before* calling `PPTXParser.fromFile` — `addGfontsAssetLibrary` skips sources that are already registered, so your override wins.
- Reuse the default `fontResolver` as a building block inside your own logger / substitution logic:
  ```typescript
  import { fontResolver } from '@imgly/pptx-importer';
  const result = await fontResolver({ family: 'Helvetica', weight: 'bold', style: 'normal' }, engine);
  ```

## Unit System

PowerPoint uses **EMUs (English Metric Units)** for measurements:
- 1 inch = 914,400 EMUs
- 1 cm = 360,000 EMUs

CE.SDK scenes use **Pixels at 96 DPI** (standard screen resolution, matching Canva and PowerPoint). The importer automatically converts:

```typescript
// EMUs to CE.SDK pixels at 96 DPI
pixels = (emus / 914400) * 96
// Simplified: pixels = emus / 9525
```

Font sizes use **points** (72 points = 1 inch), which are DPI-independent and don't require conversion.

## Platform Compatibility

### Browser Requirements

- Modern browsers with ES2022 support
- Chrome 94+, Firefox 93+, Safari 15+, Edge 94+

### Node.js Requirements

- Node.js 22+
- Works with Bun, Deno (with Node.js compatibility)

### Build Targets

The package is built with `platform: 'neutral'` to work in any JavaScript environment.

## Troubleshooting

### Font Substitution Warnings

```
Font 'CustomFont' substituted with 'Arial' for block 42
```

The importer uses Google Fonts for font resolution via the `ly.img.gfonts` and `ly.img.gfonts-fallbacks` asset sources (see [Font Loading](#font-loading) below). If a font isn't available, it falls back to a similar font. To add custom fonts:

```typescript
// Add custom font to CE.SDK asset library before parsing
await engine.asset.addAssetToSource('my-fonts', {
  id: 'custom-font',
  meta: { name: 'Custom Font' },
  payload: {
    typeface: {
      name: 'Custom Font',
      fonts: [{ uri: 'https://example.com/font.ttf' }]
    }
  }
});
```

### Missing Theme Colors

```
Color scheme not found in theme XML
```

Some PPTX files have corrupted or missing theme data. The parser falls back to default colors. To fix, re-save the PPTX in PowerPoint.

### Visual Differences in Regression Tests

Regression renders may differ from committed baselines because of:
- Font rendering differences across platforms
- CE.SDK version updates
- Intentional parser improvements

Update baselines only after verifying via the dashboard that the diff is
intentional:

```bash
pnpm test:regression:update
```

## Changelog

See [CHANGELOG.md](./CHANGELOG.md) for release notes.

## License

ISC

## Support

- 📖 [Documentation](https://img.ly/docs/cesdk)
- 💬 [Community Forum](https://community.img.ly)
- 📧 [Contact Support](mailto:support@img.ly)
