# SlideCanvas

**SlideCanvas** is a high-performance, browser-native toolkit for **viewing and editing PowerPoint (.pptx) files directly in the web browser**. It provides an enterprise-grade engine for parsing, rendering, and exporting presentations with pixel-perfect accuracy and seamless S3 integration.

---

## Key Features

- **Pixel-Perfect Rendering**: High-fidelity display of slides using a sophisticated Fabric.js-based canvas.
- **AI-Powered Editing**: Integrated Gemini AI for smart text manipulation (Shorten, Reframe, Lengthen, Grammar, Rewrite) with side-by-side controls and custom "Ask AI" input.
- **Enterprise-Grade Parsing**: Deep internal processing of XML-based PPTX structures.
- **Professional Ribbon UI**: A high-density, Microsoft Office-style toolbar layout (120px height) with optimized visibility for all controls.
- **Slash Commands (`/`)**: A fast, context-aware command menu triggered by typing `/` anywhere on the canvas or in a text box.
- **AI-Powered Image Generation**: Integrated flow for generating design assets via optional `onGenerateImage` hook, with built-in preview and replacement logic.
- **Two-Step Infographic Generation**: Advanced workflow to refine selected slide text into visual prompts before generating infographics (via `onRefineInfographicPrompt` and `onGenerateInfographic`).
- **AI Suggestion Box**: A side-by-side text refinement UI for reviewing Shorten/Reframe/Lengthen suggestions before applying them.
- **Smart Actions Group**: Prominent buttons for Present, Export (PPTX), and Delete, with a subtle "Saved" status indicator.
- **Professional Export**: High-quality `.pptx` generation with support for transparency and layout mapping.
- **S3 & Remote Support**: Built-in architecture for loading presentations via secure proxy tunnels.

---

## Installation

Install SlideCanvas via your preferred package manager:

```bash
npm install slidecanvas
# or
yarn add slidecanvas
# or
pnpm add slidecanvas
```

---

## Internal Flow Architecture

SlideCanvas operates on a sophisticated **Unidirectional Data Flow** architecture designed for reliability and scalability:

1.  **Ingestion Layer**: A multi-stage processing engine that decomposes binary `.pptx` uploads or remote URLs.
2.  **Normalization Engine**: Converts complex XML structures into a standardized, lightweight JSON presentation state.
3.  **Virtual Canvas Sync**: Bridges the presentation state to a high-performance Fabric.js canvas, handling real-time manipulation and absolute positioning.
4.  **Serialization Pipe**: Reconstructs the internal state back into standard OpenXML structures for high-fidelity export.

> [!NOTE]
> All parsing and state transformations occur within a secure, sandboxed internal context to ensure document integrity.

---

## Getting Started

### 1. Basic Setup

To build a basic editor, import the `PptEditor` component into your React/Next.js application:

```tsx
import { PptEditor } from 'slidecanvas';

export default function MyEditor() {
  return (
    <main className="h-screen">
      <PptEditor appName="My Studio" />
    </main>
  );
}
```

### 2. Loading Remote Presentations via URL

SlideCanvas makes it easy to load existing presentations directly via a URL. The component handles the fetching and parsing internally:

```tsx
import { PptEditor } from 'slidecanvas';

export default function App() {
  const pptxUrl = "https://example.com/presentations/q1-report.pptx";
  
  // Custom proxy endpoint (defaults to undefined, causing direct fetch)
  // If your app has an API route at /api/proxy, specify it here:
  return <PptEditor url={pptxUrl} proxyUrl="/api/proxy" />;
}
```

---

### 3. Handling Remote Files (S3 Integration)

SlideCanvas supports loading files from S3 or public URLs. To bypass CORS restrictions, we recommend setting up a proxy route in your `api/proxy/route.ts`:

```typescript
// Next.js API Proxy Example
import { NextRequest, NextResponse } from 'next/server';

export async function GET(request: NextRequest) {
  const { searchParams } = new URL(request.url);
  const targetUrl = searchParams.get('url');

  const response = await fetch(targetUrl!, { cache: 'no-store' });
  return new NextResponse(response.body, {
    status: 200,
    headers: { 'Content-Type': 'application/vnd.openxmlformats-officedocument.presentationml.presentation' }
  });
}
```

> [!IMPORTANT]
> To use the custom proxy above, pass its path to the `proxyUrl` prop in the `PptEditor` component (e.g., `<PptEditor proxyUrl="/api/proxy" ... />`).

### 4. Handling Large Files (Client-Side Fetch)
To bypass server payload limits (e.g., Vercel's 4.5MB limit) or CORS issues, you can implement a custom client-side fetcher using the `fetchPresentation` prop. This is ideal for downloading files directly from S3 using pre-signed URLs.

```tsx
<PptEditor
  fetchPresentation={async (url) => {
    // Logic to fetch directly from client
    // Example: Fetch pre-signed URL then download file
    const response = await fetch(url);
    if (!response.ok) throw new Error("Fetch failed");
    return await response.blob();
  }}
/>
```

### 4. Slash Commands & AI design
SlideCanvas features a powerful Slash Command system. Type `/` at any time to:
- **Insert Elements**: Instantly add Text, Images, or Shapes.
- **AI Actions**: Trigger text transformations or **AI Image Generation** directly at your cursor.

---

### 5. Custom AI Image Generation
You can integrate your own AI image provider (e.g., DALL-E, Midjourney, or a custom S3-backed service) by passing the `onGenerateImage` prop:

```tsx
<PptEditor 
  onGenerateImage={async (prompt) => {
    const response = await fetch('/api/my-ai', { body: JSON.stringify({ prompt }) });
    const data = await response.json();
    return data.s3ImageUrl; // Return a URL string
  }}
/>
```

> [!TIP]
> When an image is generated, SlideCanvas provides a professional **Preview Modal** allowing users to **Insert** as new, **Replace** a selected image (preserving dimensions), or **Discard**.

### 4. Custom AI Handlers (Ask AI & Toolbar)

SlideCanvas now supports a full suite of custom AI handlers, allowing you to bypass the internal Gemini implementation and use your own backend for every AI action.

```tsx
<PptEditor
  // ... other props
  
  // Toolbar AI Actions (Quick Actions)
  onRefineShorten={async (text) => myApi.shorten(text)}
  onRefineReframe={async (text) => myApi.reframe(text)}
  onRefineLengthen={async (text) => myApi.lengthen(text)}

  // Slash Menu "Ask AI" Actions
  onAiEdit={async (text, prompt) => myApi.customEdit(text, prompt)} // Handles the custom input field
  onAiRewrite={async (text) => myApi.rewrite(text)}
  onAiGrammar={async (text) => myApi.fixGrammar(text)}
  onAiShorten={async (text) => myApi.shorten(text)}
  onAiLengthen={async (text) => myApi.lengthen(text)}
  onAiContinue={async (text) => myApi.continueWriting(text)}
/>
```

### 5. AI Image & Infographic Generation
 (Two-Step Flow)
SlideCanvas supports a sophisticated two-step workflow for creating infographics from slide content. This is enabled via the `isTwoStepInfographicGeneration` prop.

1. **Step 1: Refinement**: The selected text is sent to `onRefineInfographicPrompt`. The user previews and edits the AI-generated visual prompt.
2. **Step 2: Generation**: The refined prompt is then sent to `onGenerateInfographic` to create the final asset.

```tsx
<PptEditor 
  isTwoStepInfographicGeneration={true}
  onRefineInfographicPrompt={async (text) => {
     // Use LLM to turn slide text into a visualization prompt
     return await myAiRefine(text); 
  }}
  onGenerateInfographic={async (prompt) => {
     // Generate the actual chart/infographic image
     return await myImageGen(prompt);
  }}
/>
```

### 7. Enabling AI Text Refinement
SlideCanvas comes battery-included with Gemini AI support for text.

```tsx
import { PptEditor } from 'slidecanvas';

export default function MyEditor() {
  const geminiKey = process.env.NEXT_PUBLIC_GEMINI_API_KEY;

  return (
    <PptEditor 
      geminiApiKey={geminiKey}
      appName="AI Design Studio" 
    />
  );
}
```

### 8. Custom AI Text Refinement Hooks
While SlideCanvas includes built-in Gemini support, you can override the logic with your own AI providers (OpenAI, Anthropic, or custom internal LLMs) using the following hooks:

```tsx
<PptEditor 
  onAiRewrite={async (text) => {
    const res = await myAiService.call('rewrite', text);
    return res.output;
  }}
  onAiGrammar={async (text) => {
    return await myAiService.call('fix-grammar', text);
  }}
  onAiShorten={async (text) => {
    return await myAiService.call('summarize', text);
  }}
  // Also supports onAiLengthen and onAiContinue
/>
```

> [!NOTE]
> When these hooks are provided, the editor will favor them over the default Gemini integration.

### 9. Toolbar Refine vs Ask AI
The editor separates "Quick Actions" (Toolbar) from "Conversational AI" (Slash Command). You can provide distinct handlers for each:

-   **Toolbar (Refine Menu)**: Uses `onRefineShorten`, `onRefineReframe`, `onRefineLengthen`.
-   **Slash Command (Ask AI)**: Uses `onAiShorten`, `onAiLengthen`, `onAiRewrite`, etc.

This allows you to use lighter/faster models for the toolbar buttons and more capable models for the conversational interface if desired.

## Advanced Usage Examples

### 1. 7-Second S3 Auto-Save (Real-Time Persistence)

To save changes back to S3 after a modifications stop for 7 seconds, use the **`PptxBlobExporter`** combined with a debounce pattern. Unlike the regular exporter, this utility returns a `Blob` directly without triggering a browser download.

```tsx
import { PptEditor, PptxBlobExporter } from 'slidecanvas';
import { useRef } from 'react';

export function S3Editor({ s3Key }: { s3Key: string }) {
  const timerRef = useRef<NodeJS.Timeout | null>(null);

  const handleAutoSave = (presentation: any) => {
    // 1. Clear previous timer if user keeps editing
    if (timerRef.current) clearTimeout(timerRef.current);

    // 2. Start a new 7-second countdown
    timerRef.current = setTimeout(async () => {
      console.log('User stopped editing. Generating Blob and saving to S3...');
      
      // 3. Generate the .pptx file binary as a Blob
      const exporter = new PptxBlobExporter();
      const blob = await exporter.exportToBlob(presentation);
      
      // 4. Upload to your backend API which talks to S3
      const formData = new FormData();
      formData.append('file', blob, 'update.pptx');
      formData.append('key', s3Key);

      await fetch('/api/save-to-s3', {
        method: 'POST',
        body: formData
          });
      
      console.log('Saved successfully!');
    }, 7000); // 7 seconds
  };

  return <PptEditor onChange={handleAutoSave} />;
}
```

#### Sample Backend (Next.js API Route)

```typescript
// api/save-to-s3/route.ts
import { S3Client, PutObjectCommand } from "@aws-sdk/client-s3";

export async function POST(req: Request) {
  const data = await req.formData();
  const file = data.get('file') as File;
  const key = data.get('key') as string;
  
  const buffer = Buffer.from(await file.arrayBuffer());
  
  const s3 = new S3Client({ region: "ap-south-1" });
  await s3.send(new PutObjectCommand({
    Bucket: "your-bucket-name",
    Key: key,
    Body: buffer,
    ContentType: "application/vnd.openxmlformats-officedocument.presentationml.presentation"
  }));

  return Response.json({ success: true });
}
```

### 2. Programmatic Presentation Creation
You can skip the parser and build a presentation state manually to generate slides on the fly:

```tsx
import { PptEditor, Presentation } from 'slidecanvas';

const myDraft: Presentation = {
  slides: [
    {
      id: 'welcome-slide',
      elements: [
        {
          id: 'title-1',
          type: 'text',
          content: 'Hello World from SlideCanvas!',
          x: 100, y: 100, width: 600, height: 100, fontSize: 48,
          color: '#3b82f6', zIndex: 1
        }
      ]
    }
  ],
  layout: { width: 12192000, height: 6858000 } // Standard 16:9 EMU
};

export default function App() {
  return <PptEditor initialPresentation={myDraft} />;
}
```

### 3. Custom Headless Processing (Node.js/Edge)
Extract text content or images without rendering the UI:

```typescript
import { PptxParser } from 'slidecanvas';

async function extractCaptions(fileBuffer: ArrayBuffer) {
  const parser = new PptxParser();
  const presentation = await parser.parse(fileBuffer);

  const allText = presentation.slides.flatMap(slide => 
    slide.elements
      .filter(el => el.type === 'text')
      .map(el => el.content)
  );
  
  return allText;
}
```

---

## API Reference

### `<PptEditor />`
### Properties

| Property | Type | Default | Description |
| :--- | :--- | :--- | :--- |
| `width` | `number \| string` | **Required** | The width of the editor container. |
| `height` | `number \| string` | **Required** | The height of the editor container. |
| `initialPresentation` | `Presentation` | `undefined` | Initial presentation data to load. |
| `url` | `string` | `undefined` | URL of a public .pptx file to load on mount. |
| `initialSource` | `'scratch' \| 'uploaded' \| 'url'` | `undefined` | Sets the initial UI source state and badge. |
| `appName` | `string` | `"SlideCanvas"` | Brand name shown in the ribbon. |
| `appBgColor` | `string` | `"#B7472A"` | Primary brand color for the UI. |
| `geminiApiKey` | `string` | `undefined` | API key for built-in AI text actions. |
| `onGenerateImage` | `(prompt: string) => Promise<string>` | `undefined` | Custom hook to handle AI image generation. |
| `isTwoStepInfographicGeneration` | `boolean` | `false` | Enables Step 1: Prompt Refinement modal for infographics. |
| `onRefineInfographicPrompt` | `(text: string) => Promise<string>` | `undefined` | Hook to transform text into a visual prompt (Step 1). |
| `onGenerateInfographic` | `(prompt: string) => Promise<string>` | `undefined` | Hook to generate the infographic image (Step 2). |
| `proxyUrl` | `string` | `undefined` | Base path for the proxy API (used for PPTX loading and image previews). |
| `showHomeOnEmpty` | `boolean` | `false` | Shows a "New / Upload" splash if no data provided. |
| `onChange` | `(pres: Presentation) => void` | `undefined` | Fired on any change to the deck. |
| `onSourceChange` | `(src, url?) => void` | `undefined` | Fired when the deck origin changes (useful for routing). |
| `onAiRewrite` | `(text: string) => Promise<string>` | `undefined` | Optional hook to override default Gemini rewrite logic. |
| `onAiGrammar` | `(text: string) => Promise<string>` | `undefined` | Optional hook to override default Gemini grammar logic. |
| `onAiShorten` | `(text: string) => Promise<string>` | `undefined` | Optional hook to override default Gemini shorten logic. |
| `onAiLengthen` | `(text: string) => Promise<string>` | `undefined` | Optional hook to override default Gemini lengthen logic. |
| `onAiContinue` | `(text: string) => Promise<string>` | `undefined` | Optional hook to override default Gemini continue-writing logic. |
| `onRefineShorten` | `(text: string) => Promise<string>` | `undefined` | Override loop for Toolbar "Shorten" button. |
| `onRefineReframe` | `(text: string) => Promise<string>` | `undefined` | Override loop for Toolbar "Reframe" button. |
| `onRefineLengthen` | `(text: string) => Promise<string>` | `undefined` | Override loop for Toolbar "Lengthen" button. |
| `fetchPresentation` | `(url: string) => Promise<ArrayBuffer \| Blob>` | `undefined` | Custom fetcher for loading presentations, bypassing the proxy. |

### Usage Examples

#### 1. Branded Full-Screen Editor
Perfect for a standalone presentation app.
```tsx
import { PptEditor } from 'slidecanvas';

export default function MyEditor() {
  return (
    <div style={{ width: '100vw', height: '100vh' }}>
      <PptEditor
        width="100%"
        height="100%"
        appName="SkyDeck"
        appBgColor="#0f172a" // Custom dark theme
        showHomeOnEmpty={true} // Show the splash screen if no PPT loaded
      />
    </div>
  );
}
```

#### 2. AI-Powered Assistant
Enable Gemini-driven text refinements and slide generation.
```tsx
<PptEditor
  width={1200}
  height={800}
  geminiApiKey={process.env.NEXT_PUBLIC_GEMINI_API_KEY}
  appName="AI Slides"
/>
```

#### 3. Deep-Linking & URL Sync (Next.js)
Synchronize the editor's source (Scratch, Uploaded, or Remote) with your browser's address bar.
```tsx
const router = useRouter();
const searchParams = useSearchParams();

<PptEditor
  width="100vw"
  height="100vh"
  url={searchParams.get('url')}
  initialSource={searchParams.get('source')} // 'scratch' | 'uploaded' | 'url'
  onSourceChange={(source, url) => {
    // Dynamically update URL as user switches between 'Create New' and 'Upload'
    const query = url ? `?url=${url}` : `?source=${source}`;
    router.push(`/editor${query}`);
  }}
/>
```

### Advanced Features

#### Visual Shape Selection
The editor includes a rich selection of 30+ SVG shapes out of the box. The shapes are fully responsive and scale accurately with the editor's dimensions.

#### Professional Ribbon UI
SlideCanvas features a sophisticated 120px high ribbon layout divided into logical groups:
- **Slides**: New slide creation and layout switching (Title, Content, Split).
- **Font**: Full-width family and size selectors with comprehensive formatting tools.
- **Drawing**: Instant access to Shapes, Text Boxes, and Image uploads.
- **AI Text**: Side-by-side buttons for intelligent content transformation.
- **Actions**: Large, accessible buttons for primary workflows like Presenting and Exporting.

#### Intelligent Routing
Use the `onSourceChange` callback to synchronize the editor state with your application's routing. This allows for deep-linking to specific presentations or maintaining "Upload" vs "New" states in the address bar.

### `PptxParser`, `PptxExporter` & `PptxBlobExporter`

For headless workflows, you can use the internal engine directly:

```typescript
import { PptxParser, PptxExporter, PptxBlobExporter } from 'slidecanvas';

// 1. Load: Parse a .pptx file into JSON
const parser = new PptxParser();
const presentation = await parser.parse(myArrayBuffer);

// 2. Export: Trigger a browser file download
const exporter = new PptxExporter();
await exporter.export(presentation);

// 3. Blob Export: Get file data as a Blob (useful for S3/API uploads)
const blobExporter = new PptxBlobExporter();
const blob = await blobExporter.exportToBlob(presentation);
```

---

## Building for Production

To build your application for production, run:

```bash
npm run build
```

The `slidecanvas` library is optimized for tree-shaking and minimizes your final bundle size by lazily loading the Fabric.js engine.

---

## License

This project is licensed under the MIT License. You are free to use, modify, and distribute this software as permitted by the license terms.
