---
name: pptx-skill
description: HTML 슬라이드를 PowerPoint(PPTX) 파일로 변환합니다. PPTX 생성, PPT 변환, 프레젠테이션 만들기, 슬라이드를 파워포인트로 요청 시 사용합니다.
allowed-tools: Read, Write, Edit, Glob, Grep, Bash
---

# PPTX Skill - PowerPoint 프레젠테이션 생성

PowerPoint 프레젠테이션 파일(.pptx)을 생성하는 스킬입니다.
두 가지 방법을 제공하며, **방법 A (Direct PptxGenJS)를 우선 사용**합니다.

## 방법 선택 가이드

| | 방법 A: Direct PptxGenJS (권장) | 방법 B: HTML → PPTX |
|---|---|---|
| **의존성** | Node.js + pptxgenjs만 필요 | Playwright + Chromium 브라우저 필요 |
| **원리** | JS 코드로 도형/텍스트 직접 배치 | HTML을 브라우저 렌더링 후 변환 |
| **PPT 편집** | 생성 후 텍스트/도형 직접 수정 가능 | 이미지 기반이라 수정 어려움 |
| **한글 폰트** | PPT 뷰어 폰트 사용 (깨짐 없음) | 시스템 폰트 필요 |
| **환경 제약** | 없음 | Chromium 시스템 라이브러리 필요 |
| **적합한 경우** | 대부분의 프레젠테이션 | 복잡한 CSS 레이아웃이 필요할 때 |

---

## 방법 A: Direct PptxGenJS (권장)

PptxGenJS API를 직접 호출하여 네이티브 PPTX 요소를 생성합니다.
브라우저 없이 Node.js만으로 동작하며, 생성된 PPT에서 텍스트/도형 편집이 가능합니다.

### 사용법

#### 1단계: .cjs 변환 스크립트 작성

**주의**: 프로젝트에 `"type": "module"`이 설정된 경우 `.cjs` 확장자를 사용해야 합니다.

```javascript
// convert.cjs
const path = require('path');
module.paths.unshift(path.join(require('os').homedir(), '.claude/skills/pptx-skill/scripts/node_modules'));

const PptxGenJS = require('pptxgenjs');

async function main() {
    const pres = new PptxGenJS();

    // 커스텀 레이아웃 정의 (16:9 = 10" x 5.625")
    pres.defineLayout({ name: 'CUSTOM_16x9', width: 10, height: 5.625 });
    pres.layout = 'CUSTOM_16x9';

    // --- 슬라이드 1 ---
    const slide1 = pres.addSlide();
    slide1.background = { fill: '1a1a2e' };

    slide1.addText('제목', {
        x: 1, y: 2, w: 8, h: 1,
        fontSize: 32, bold: true, color: 'FFFFFF',
        align: 'center', fontFace: 'Arial'
    });

    // --- 저장 ---
    await pres.writeFile({ fileName: 'output.pptx' });
    console.log('PPTX created!');
}

main().catch(console.error);
```

#### 2단계: 실행

```bash
node convert.cjs
```

#### 3단계: 결과 확인 (선택)

```bash
python ~/.claude/skills/pptx-skill/scripts/thumbnail.py output.pptx thumbnail-grid
```

### 핵심 규칙

#### 색상 코드 (매우 중요)
PptxGenJS에서 색상 코드는 반드시 `#` 없이 사용해야 합니다.

```javascript
// ✅ 올바른 사용
{ color: 'FF0000' }
{ fill: { color: '1e3a5f' } }

// ❌ 잘못된 사용 - 파일 손상 유발
{ color: '#FF0000' }
```

#### 좌표 체계
모든 위치/크기는 **인치(inch)** 단위입니다.

```
슬라이드 크기: width=10, height=5.625 (16:9)
좌표 원점: 좌측 상단 (0, 0)

x: 좌측에서의 거리
y: 상단에서의 거리
w: 너비
h: 높이
```

### PptxGenJS API 빠른 참조

#### 슬라이드 배경
```javascript
slide.background = { fill: '1a1a2e' };               // 단색
slide.background = { path: 'background.png' };        // 이미지
```

#### 텍스트 추가
```javascript
// 단순 텍스트
slide.addText('제목', {
    x: 0.5, y: 0.5, w: 9, h: 1,
    fontSize: 36, bold: true, color: '1a1a2e',
    align: 'center',       // left, center, right
    valign: 'middle',      // top, middle, bottom
    fontFace: 'Arial'
});

// 복합 텍스트 (여러 스타일 혼합)
slide.addText([
    { text: '굵은 텍스트', options: { bold: true, color: 'FF0000', fontSize: 14 } },
    { text: ' 일반 텍스트', options: { color: '333333', fontSize: 14 } }
], { x: 1, y: 2, w: 8, h: 0.5, fontFace: 'Arial' });

// 줄바꿈
slide.addText([
    { text: '첫째 줄', options: { fontSize: 14, bold: true, color: 'FFFFFF' } },
    { text: '\n둘째 줄', options: { fontSize: 9, color: 'CCCCCC' } }
], { x: 1, y: 2, w: 4, h: 0.7, align: 'center', valign: 'middle', fontFace: 'Arial' });
```

#### 도형 추가
```javascript
// 사각형
slide.addShape(pres.ShapeType.rect, {
    x: 0.5, y: 1, w: 3, h: 2,
    fill: { color: '1e3a5f' },
    line: { color: '333333', width: 1 }
});

// 둥근 사각형
slide.addShape(pres.ShapeType.roundRect, {
    x: 1, y: 1, w: 4, h: 0.5,
    rectRadius: 0.1,
    fill: { color: 'F0FAF0' },
    line: { color: 'C8E6C9', width: 0.5 }
});

// 원/타원
slide.addShape(pres.ShapeType.ellipse, {
    x: 2, y: 2, w: 0.5, h: 0.5,
    fill: { color: '4CAF50' }
});
```

#### 이미지 추가
```javascript
slide.addImage({
    path: 'image.png',    // 파일 경로
    x: 1, y: 2, w: 4, h: 3
});

slide.addImage({
    data: 'base64...',    // Base64 인코딩
    x: 1, y: 2, w: 4, h: 3
});
```

#### 차트 추가
```javascript
slide.addChart(pres.ChartType.bar, [{
    name: '시리즈 1',
    labels: ['A', 'B', 'C'],
    values: [10, 20, 30]
}], { x: 1, y: 2, w: 8, h: 4 });

slide.addChart(pres.ChartType.pie, [...], {...});
slide.addChart(pres.ChartType.line, [...], {...});
```

### 디자인 패턴 (자주 사용하는 구성요소)

#### 헤더 바 + 제목
```javascript
slide.addShape(pres.ShapeType.rect, {
    x: 0.5, y: 0.35, w: 0.06, h: 0.35,
    fill: { color: '4CAF50' }
});
slide.addText('페이지 제목', {
    x: 0.7, y: 0.3, w: 5, h: 0.45,
    fontSize: 18, bold: true, color: '1a1a2e', fontFace: 'Arial'
});
```

#### 배지/태그
```javascript
slide.addShape(pres.ShapeType.roundRect, {
    x: 5, y: 0.35, w: 1, h: 0.3,
    rectRadius: 0.04,
    fill: { color: 'E8F5E9' }
});
slide.addText('총 28%', {
    x: 5, y: 0.35, w: 1, h: 0.3,
    fontSize: 8, bold: true, color: '2E7D32',
    align: 'center', fontFace: 'Arial'
});
```

#### 카드 (배경 + 제목 + 항목)
```javascript
// 카드 배경
slide.addShape(pres.ShapeType.roundRect, {
    x: 0.5, y: 1.5, w: 4, h: 3,
    rectRadius: 0.15,
    fill: { color: 'F0FAF0' },
    line: { color: '4CAF50', width: 1.5 }
});
// 카드 내부 항목 (흰색 행)
slide.addShape(pres.ShapeType.roundRect, {
    x: 0.7, y: 2.3, w: 3.6, h: 0.4,
    rectRadius: 0.06,
    fill: { color: 'FFFFFF' }
});
slide.addText('항목명', { x: 0.8, y: 2.3, w: 2, h: 0.4, fontSize: 10, color: '333333', fontFace: 'Arial' });
slide.addText('값', { x: 2.8, y: 2.3, w: 1.4, h: 0.4, fontSize: 14, bold: true, color: '4CAF50', align: 'right', fontFace: 'Arial' });
```

#### 넘버 서클
```javascript
slide.addShape(pres.ShapeType.ellipse, {
    x: 1, y: 1, w: 0.45, h: 0.45,
    fill: { color: '2196F3' }
});
slide.addText('1', {
    x: 1, y: 1, w: 0.45, h: 0.45,
    fontSize: 14, bold: true, color: 'FFFFFF',
    align: 'center', valign: 'middle', fontFace: 'Arial'
});
```

#### 비율 바 (프로그레스 형태)
```javascript
// 큰 비율
slide.addShape(pres.ShapeType.roundRect, {
    x: 0.5, y: 1, w: 6.4, h: 0.7,
    rectRadius: 0.1, fill: { color: '4CAF50' }
});
slide.addText([
    { text: '20%', options: { fontSize: 20, bold: true, color: 'FFFFFF' } },
    { text: '\n판매자 수당', options: { fontSize: 9, color: 'DDFFDD' } }
], { x: 0.5, y: 1, w: 6.4, h: 0.7, align: 'center', valign: 'middle', fontFace: 'Arial' });

// 작은 비율
slide.addShape(pres.ShapeType.roundRect, {
    x: 7, y: 1, w: 1.6, h: 0.7,
    rectRadius: 0.1, fill: { color: '2196F3' }
});
```

#### 반복 카드 생성 (데이터 기반)
```javascript
const cards = [
    { title: '항목 A', color: '4CAF50', bg: 'F0FAF0', border: 'C8E6C9', items: [['라벨1', '값1'], ['라벨2', '값2']] },
    { title: '항목 B', color: '2196F3', bg: 'E3F2FD', border: 'BBDEFB', items: [['라벨1', '값1'], ['라벨2', '값2']] },
];
const cardW = 2.85;
cards.forEach((card, i) => {
    const x = 0.5 + i * (cardW + 0.2);
    const y = 1.9;
    // 카드 배경
    slide.addShape(pres.ShapeType.roundRect, { x, y, w: cardW, h: 3, rectRadius: 0.1, fill: { color: card.bg }, line: { color: card.border, width: 0.5 } });
    // 카드 제목
    slide.addText(card.title, { x: x + 0.2, y: y + 0.1, w: cardW - 0.4, h: 0.3, fontSize: 11, bold: true, color: card.color, fontFace: 'Arial' });
    // 항목 반복
    card.items.forEach((item, j) => {
        const iy = y + 0.5 + j * 0.7;
        slide.addShape(pres.ShapeType.roundRect, { x: x + 0.1, y: iy, w: cardW - 0.2, h: 0.55, rectRadius: 0.05, fill: { color: 'FFFFFF' } });
        slide.addText(item[0], { x: x + 0.2, y: iy + 0.03, w: cardW - 0.4, h: 0.2, fontSize: 8, color: '999999', fontFace: 'Arial' });
        slide.addText(item[1], { x: x + 0.2, y: iy + 0.25, w: cardW - 0.4, h: 0.2, fontSize: 10, bold: true, color: '333333', fontFace: 'Arial' });
    });
});
```

#### 대외비 스탬프
```javascript
function addConfidentialBadge(slide) {
    slide.addShape(pres.ShapeType.roundRect, { x: 8.3, y: 0.15, w: 1.4, h: 0.35, rectRadius: 0.04, fill: { color: 'D32F2F' } });
    slide.addText('CONFIDENTIAL', { x: 8.3, y: 0.12, w: 1.4, h: 0.22, fontSize: 7, bold: true, color: 'FFFFFF', align: 'center', fontFace: 'Arial' });
    slide.addText('대 외 비', { x: 8.3, y: 0.28, w: 1.4, h: 0.22, fontSize: 8, bold: true, color: 'FFCDD2', align: 'center', fontFace: 'Arial' });
}
// 각 슬라이드에 적용
addConfidentialBadge(slide1);
```

#### 어두운 배경 테이블
```javascript
// 테이블 컨테이너
slide.addShape(pres.ShapeType.roundRect, { x: 5, y: 2.7, w: 4.5, h: 2.5, rectRadius: 0.1, fill: { color: '1a1a2e' } });
// 헤더
slide.addText('구분', { x: 5.2, y: 3.0, w: 1.5, h: 0.25, fontSize: 8, color: '888888', fontFace: 'Arial' });
slide.addText('값 A', { x: 6.7, y: 3.0, w: 1.2, h: 0.25, fontSize: 8, bold: true, color: '4CAF50', align: 'center', fontFace: 'Arial' });
// 구분선
slide.addShape(pres.ShapeType.rect, { x: 5.2, y: 3.25, w: 4, h: 0.01, fill: { color: '333344' } });
// 데이터 행
slide.addText('항목', { x: 5.2, y: 3.35, w: 1.5, h: 0.3, fontSize: 9, color: 'CCCCCC', fontFace: 'Arial' });
slide.addText('20%', { x: 6.7, y: 3.35, w: 1.2, h: 0.3, fontSize: 10, bold: true, color: '4CAF50', align: 'center', fontFace: 'Arial' });
```

### 슬라이드 크기 참조

| 비율 | 크기 (pt) | 크기 (inch) | defineLayout |
|------|-----------|-------------|--------------|
| 16:9 | 720 x 405 | 10 x 5.625 | `{ width: 10, height: 5.625 }` |
| 4:3 | 720 x 540 | 10 x 7.5 | `{ width: 10, height: 7.5 }` |
| 16:10 | 720 x 450 | 10 x 6.25 | `{ width: 10, height: 6.25 }` |

**중요**: `LAYOUT_WIDE`는 13.3" x 7.5"이므로 사용하지 마세요. 반드시 커스텀 레이아웃을 정의하세요.

---

## 방법 B: HTML → PPTX 변환 (Playwright 필요)

HTML 슬라이드를 Playwright 브라우저로 렌더링하여 PPTX로 변환합니다.
복잡한 CSS 레이아웃(flexbox, grid, 그라데이션)이 필요한 경우에 사용합니다.

**전제 조건**: Playwright + Chromium 브라우저 + 시스템 라이브러리가 설치되어 있어야 합니다.
```bash
cd ~/.claude/skills/pptx-skill/scripts && npm install playwright pptxgenjs sharp
npx playwright install chromium
npx playwright install-deps chromium  # 시스템 라이브러리 (sudo 필요)
```

### 사용법

#### 1단계: HTML 슬라이드 준비

```bash
ls slides/*.html
```

각 HTML 파일 규격:
- 크기: 720pt x 405pt (16:9 비율)
- 인코딩: UTF-8
- 폰트: 웹 안전 폰트 사용

#### 2단계: 변환 스크립트 작성 및 실행

```javascript
const path = require('path');
module.paths.unshift(path.join(require('os').homedir(), '.claude/skills/pptx-skill/scripts/node_modules'));

const PptxGenJS = require('pptxgenjs');
const html2pptx = require(path.join(require('os').homedir(), '.claude/skills/pptx-skill/scripts/html2pptx.js'));

const pres = new PptxGenJS();
pres.defineLayout({ name: 'CUSTOM_16x9', width: 10, height: 5.625 });
pres.layout = 'CUSTOM_16x9';

await html2pptx('slides/slide-01.html', pres);
await html2pptx('slides/slide-02.html', pres);

await pres.writeFile({ fileName: 'presentation.pptx' });
```

#### 3단계: 결과 확인

```bash
python ~/.claude/skills/pptx-skill/scripts/thumbnail.py output.pptx thumbnail-grid
```

### HTML 작성 규칙 (방법 B 전용)

1. **텍스트 요소에 border/background 금지** - div로 감싸기
2. **불릿 기호(-, *, ., .)로 시작하는 텍스트 금지** - ul/ol 사용
3. **모든 텍스트는 p/h1-h6 태그로 감싸기**
4. **table 태그 사용 금지** - div + flexbox로 대체
5. **HTML body 크기와 PptxGenJS 레이아웃 일치 필수**
6. **빈 값 표시 시 하이픈(-) 금지** - 빈 문자열 또는 N/A 사용

---

## 공통 유틸리티

### thumbnail.py - 미리보기 생성

```bash
python ~/.claude/skills/pptx-skill/scripts/thumbnail.py presentation.pptx output-thumbnail
```

옵션:
- `--cols N`: 열 수 (기본 5, 범위 3-6)
- `--outline-placeholders`: 플레이스홀더 영역 표시

### pack.py / unpack.py - PPTX XML 편집

```bash
# PPTX 언패킹 (XML로 분해)
python ~/.claude/skills/pptx-skill/ooxml/scripts/unpack.py presentation.pptx output_dir

# PPTX 패킹 (XML을 PPTX로 조립)
python ~/.claude/skills/pptx-skill/ooxml/scripts/pack.py input_dir presentation.pptx

# 구조 검증
python ~/.claude/skills/pptx-skill/ooxml/scripts/validate.py unpacked_dir --original presentation.pptx
```

## 필수 의존성

### Node.js 패키지 (방법 A/B 공통)
```bash
cd ~/.claude/skills/pptx-skill/scripts && npm install pptxgenjs
```

### 추가 패키지 (방법 B만)
```bash
cd ~/.claude/skills/pptx-skill/scripts && npm install playwright sharp
```

### Python 패키지 (thumbnail/ooxml 사용 시)
```bash
pip install pillow defusedxml
```

## 추가 문서

- [html2pptx.md](html2pptx.md) - HTML to PPTX 변환 상세 가이드 (방법 B)
- [ooxml.md](ooxml.md) - Office Open XML 기술 참조
