# PDF 딸깍 — 개발 문서 (dev.md)

> 이 문서가 **단일 진실 출처 (Single Source of Truth)** — PDF 딸깍 앱에 대한 모든 결정·구조·계획은 여기에 모인다.
> 새 세션이 이 앱을 만질 때 가장 먼저 읽는다.

| 항목 | 값 |
|---|---|
| **공식 이름 (사용자 표시)** | PDF 딸깍 |
| **폴더·코드명** | `pdftools` |
| **상위 브랜드** | 딸깍 개발실 — GDI-Apps |
| **현재 상태** | 🎉 **Live v1.0.0 — 정식 출시 (카드 24/24 완성)** — Phase 1~5 누적 완료. **GDI-Apps 전체 nodejs (Express) 백엔드 인프라** (`/api/unlock` muhammara + `/api/translate-deepl` + `/api/health`). PDF 번역 Marker 모드 (include_metadata=true → json_data BlockType 트리 + polygon/bbox → Text/TableCell/FALLBACK_BLOCK_TYPES 등 → drawWrappedText `\n` 보존). **PDF 뷰어 hub = v1.0.0 MVP** (단순 미리보기 + 외부 도구 링크). ⚠ **v2.0 풀 리팩토링 예정** — 사용자 의도 = "뷰어 안에서 23개 도구 inline 작동 + TextLayer/검색/이미지 복사" (PATCHNOTE 박제). Cloudflare Worker v3.4.0. 코어 모듈 **11개** + 도구 **24개**. shared/api-keys.js v1.5.1. **다음 작업 (v2.0)**: 23개 도구 패널 모듈화 + 공유 PDF 인스턴스 + TextLayer = 수일~수주 |
| **목표 v1.0 범위** | **Phase 1~5 누적 완료 시점에 v1.0.0 정식 출시 + 랜딩 카드 활성** — 21개 도구 + 뷰어 hub (이미지 추출 등 통합) + AI 도구 (2026-05-23 사용자 결정 → v0.3.17 이미지 추출을 뷰어 hub 안으로 이동) |
| **레퍼런스** | [pdfcandy](https://pdfcandy.com/ko/), [ilovepdf](https://www.ilovepdf.com/), [smallpdf](https://smallpdf.com/), [sejda](https://www.sejda.com/) |
| **devplan 메모** | [devplan.md §96-176](../devplan.md) (2026-05-19 작성) |

---

## 1. 프로젝트 정체성

### 1.1 무엇을 만드는가

PDF 의 **편집·조작 전용** 도구 모음 앱. 자르기·붙이기·추출·병합 같이 PDF 를 직접 만지는 도구만 모은다. 사용자가 PDF 파일 1개(또는 여러 개)를 던지면 한 번의 작업으로 결과가 나오는 "딸깍" 패턴을 모든 도구에 일관 적용.

### 1.2 무엇은 안 만드는가 (devplan 확정, 사용자 재확인 2026-05-23)

- **문서 포맷 변환 일체 X** — Word/Excel/PPT/HWP/EPUB/ODT/RTF/MOBI ↔ PDF (단방향·양방향 모두). 이유: 기성 상용 앱(pdfcandy, ilovepdf 등)도 결과 품질 들쭉날쭉, "딸깍" 안 됨. 단 **PDF→MD 만 예외** (마크다운 워크플로 시너지로 §3.3 추출 카테고리에 포함)
- **PDF/A·복구·URL→PDF** — 서버 필요·외부 자원 의존 강함, 가성비 낮음
- **암호 걸기·암호 크랙** — 푸는 기능만 제공 (정당한 권리자 자기 파일 한정), 크랙은 법적·기술적 회색지대
- **양식(Form) 만들기** — 수요 적음. 작성(fill)도 제외
- **PDF Chat** — 사용자가 일반 AI Chat 직접 쓰는 게 비용·UX 모두 우월 (2026-05-23 사용자 결정)
- **이미지→PDF·MD→PDF·TXT→PDF·SVG→PDF·CSV→PDF·빈 PDF 만들기** — "만들기" 카테고리 일괄 제외 (2026-05-23 사용자 결정). 단 이미지→PDF 는 [§9 결정 보류](#9-결정-보류) 에 부활 검토 항목으로 보존
- **HTML→PDF** — 브라우저 인쇄 PDF 대비 차별점 없음 (jsPDF + html2canvas 한계). 헤드리스 브라우저 도입 시 부활 가능

### 1.3 핵심 미덕 — 다른 GDI-Apps 공통

- **정적 호스팅 유지** — AISpace lite (256MB/1GB/static) 안에서 동작. 거의 모든 도구가 클라이언트 only ([§5 기술 스택](#5-기술-스택))
- **한국어 친화** — 한국 행정문서 관례 반영 (한글 폰트 임베드, 페이지번호 포맷 "1쪽", 워터마크 한글 등)
- **딸깍 디자인 원칙** — 자동 추천·최소 개입·직관 UI ([CLAUDE.md §6](../../CLAUDE.md))
- **공용 인프라** — 다른 GDI-Apps 의 PDF 처리 백엔드로도 동작 ([§10 시너지](#10-시너지))

---

## 2. 핵심 결정사항 (2026-05-23 사용자 확정)

devplan 메모에 더해 이 세션에서 도구별로 명확하게 결정된 사항. 누가 이 문서를 다시 읽어도 같은 결론에 도달하도록 박아둔다.

| # | 결정 | 근거 |
|---|---|---|
| 1 | **공식 이름 = PDF 딸깍 / 폴더명 = `pdftools`** | devplan §140 + 2026-05-23 폴더명 확정 |
| 2 | **앱 구조 = 카드 그리드 + 도구별 페이지 분리** | pdfcandy 패턴. URL 공유 가능, 코드 격리, 도구별 독립 유지보수 |
| 3 | **뷰어 = 다른 도구 호출 hub** | 사용자: "상용 PDF 뷰어를 대체하고 싶다. 뷰어 속에서 다른 기능 바로 호출 가능하게" |
| 4 | **PDF 번역 = 이 앱 내부에 포함** | devplan 은 별도 "문서번역 딸깍" 으로 분리 안 했으나, 다른 앱 개발 부담 줄이기 위해 이 앱에 흡수 (2026-05-23) |
| 5 | **OCR = 외부 API + 사용자 본인 키 입력** | 후보: 한컴 OCR (1페이지 2원, 한국어 우수) / Google Vision (무료 한도, 정확도 우수). 둘 다 지원, 사용자가 선택 |
| 6 | **공용 API 키 매니저 재활용** | `../shared/api-keys.js` (image-editor 가 v1.7.22 부터 사용 중) 그대로 활용. OCR·AI 요약·번역 모두 같은 키 저장소 |
| 7 | **압축 = 이미지 다운샘플만** | Ghostscript 급 진짜 압축 X. 클라이언트 캔버스 JPEG 재인코딩으로 30~50% 절감 목표 |
| 8 | **HTML→PDF 제외** | 브라우저 "인쇄 → PDF로 저장" 과 결과물 동급. 차별점 X (사용자 확인 2026-05-23) |

---

## 3. 도구 카탈로그 (최종 6 카테고리, 21개 — v0.3.17 이미지 추출 → 뷰어 안 통합)

devplan 의 잡다한 9 카테고리 → **공통 처리 모듈 기준으로 6 카테고리로 재정리**. 같은 카테고리 안 도구는 공통 인프라가 강하게 겹쳐서 같이 만들면 효율 높음.

### 3.1 페이지 작업 (Page Operations) — 8개

PDF 페이지를 자르고 붙이고 옮기는 모든 작업. 모두 `pdf-lib` 단일 의존성, 페이지 썸네일 UI 공유.

| 도구 | 무슨 일 | 라이브러리 | 난이도 |
|---|---|---|---|
| 병합 (Merge) | 여러 PDF → 1개 | pdf-lib | ★1 |
| 분할 (Split) | 1개 PDF → 여러 개 (페이지별·범위별·N장씩) | pdf-lib | ★1 |
| 회전 (Rotate) | 90/180/270° (전체·선택) | pdf-lib | ★1 |
| 페이지 삭제 (Remove pages) | 특정 페이지 빼기 | pdf-lib | ★1 |
| 페이지 재정렬 (Reorder) | 드래그로 순서 변경 | pdf-lib + Sortable.js | ★2 |
| 페이지 추출 (Extract pages) | 특정 페이지만 빼서 새 PDF | pdf-lib | ★1 |
| **페이지 크기 조정** (Resize) | A4·Letter 등으로 통일, 콘텐츠 스케일 유지 | pdf-lib | ★3 |
| 자르기 (Crop) | 페이지 여백 자르기, 드래그 영역 지정 | pdf-lib | ★2 |

### 3.2 콘텐츠 편집 (Content Editing) — 6개

PDF 위에 무언가 얹는 도구. 모두 **PDF 좌표 ↔ 화면 좌표 변환** + **한글 폰트 임베드** 공통 모듈 사용.

| 도구 | 무슨 일 | 라이브러리 | 난이도 |
|---|---|---|---|
| 워터마크 (텍스트) | "대외비" 등 글자 워터마크, 위치·투명도·회전 | pdf-lib + fontkit + 나눔 폰트 | ★2 |
| 워터마크 (이미지) | 로고·도장 PNG 워터마크 | pdf-lib | ★2 |
| 페이지 번호 | 1/10, -1-, **1쪽** 등 한국식 포맷 포함 | pdf-lib + fontkit | ★2 |
| 머리글·바닥글 | 모든 페이지 상단·하단 텍스트 (페이지번호 통합) | pdf-lib + fontkit | ★2 |
| **텍스트 추가** | 빈 폼 PDF 에 직접 글자 박기 (양식이 아닌 PDF용) | pdf-lib + fontkit + 드래그 UI | ★3 |
| **서명** | 마우스로 그리기 + 이미지 업로드 → 페이지에 떨어뜨리기 | canvas 그리기 + pdf-lib | ★3 |

### 3.3 추출·변환 (Extract & Convert) — 5개

PDF 안 내용을 밖으로 꺼내는 도구. `pdf.js` 단일 의존성. OCR·PDF→MD 는 외부 API 호출.

| 도구 | 무슨 일 | 라이브러리·API | 난이도 |
|---|---|---|---|
| 텍스트 추출 | PDF → .txt 또는 화면 표시 (텍스트 레이어 기반) | pdf.js `getTextContent` | ★2 |
| ~~이미지 추출~~ | **v0.3.17 (2026-05-24): 단독 카드 제거 → 뷰어 (§3.5) 안 기능으로 이동**. 사용자가 뷰어로 PDF 보면서 이미지 객체 클릭/추출 하는 시나리오가 자연스러움 | (뷰어 내부) | (뷰어) |
| 페이지 → 이미지 | PDF 페이지 → JPG/PNG (72/150/300 DPI 선택) | pdf.js + canvas | ★2 |
| **OCR** | 스캔 PDF / 이미지 PDF → 검색가능한 PDF + .txt | API (한컴/Google) + pdf-lib (텍스트 레이어 삽입) | ★4 |
| **PDF → MD** | PDF → 마크다운 (md2hwpx 워크플로 연결) | pdf.js (텍스트·구조 추출) + 후처리 | ★4 |

### 3.4 최적화·보안 (Optimize & Secure) — 2개

| 도구 | 무슨 일 | 라이브러리 | 난이도 |
|---|---|---|---|
| **이미지 압축** | PDF 안 이미지 다운샘플·JPEG 재인코딩으로 파일 크기 ↓ | pdf-lib + canvas | ★3 |
| **잠금 풀기** | 권한제한 자동 해제 + 열기암호 입력 해제 (시나리오 자동 판별) | qpdf-wasm 또는 pdf-lib | ★2 |

### 3.5 뷰어·비교 (Viewer & Compare) — 2개

| 도구 | 무슨 일 | 라이브러리 | 난이도 |
|---|---|---|---|
| **PDF 뷰어** ★ | 브라우저 PDF 뷰어 + **다른 도구 호출 hub** (뷰어에서 "이 PDF 병합하기" 등 한 클릭으로 도구 전환) + **이미지 추출 내장** (v0.3.17 통합) — PDF 안 image XObject 를 뷰어에서 직접 클릭/추출 (pdf.js operatorList paintImageXObject 패턴) | pdf.js + 모든 도구와 연결 | ★3 |
| PDF 비교 | 두 PDF 의 텍스트·페이지 diff (변경 부분 하이라이트) | pdf.js + diff 알고리즘 | ★4 |

### 3.6 AI 도구 (AI Tools) — 2개

API 키 매니저 + 추출 인프라 공유. 추출이 안정화된 후 위에 얹는다.

| 도구 | 무슨 일 | API | 난이도 |
|---|---|---|---|
| PDF 요약 | AI 가 PDF 본문 요약 (정책 브리핑과 목적 다름 — 일반 문서 요약) | Gemini/Claude/CLOVA + 텍스트 추출 | ★3 |
| **PDF 번역** | 본문 텍스트 번역 (레이아웃 유지 제한적) | Gemini/DeepL + 텍스트 추출 | ★4 |

---

## 4. 공통 모듈 설계 ★ (핵심)

**같은 모듈을 여러 도구가 공유**한다. Phase 1 에서 모듈 인프라를 한 번 잘 세팅하면 나머지 Phase 는 그 위에 도구 페이지만 얹는 구조. 후순위 도구도 미리 공통 모듈 설계에 반영.

### 4.1 모듈 목록 (`pdftools/shared/` 하위)

| 모듈 | 역할 | 의존 라이브러리 |
|---|---|---|
| **`pdf-core.js`** | PDF 파일 로딩·파싱 wrapper. `pdf.js` 와 `pdf-lib` 양쪽 인스턴스 관리. 한 번 로드한 PDF 를 두 라이브러리로 동시 접근 가능 | pdf.js, pdf-lib |
| **`thumbnail.js`** | 페이지 썸네일 렌더러 (pdf.js → canvas). 가상 스크롤 (페이지 100장+ PDF 대응) | pdf.js |
| **`file-input.js`** | 드래그&드롭 + 클릭 업로드 + **다중 파일 정렬 가능 리스트** | — |
| **`page-range.js`** | 페이지 범위 선택기 ("1-3, 5, 7-9" 파싱 + 썸네일 체크박스 UI 동기화) | — |
| **`coordinate.js`** | PDF 좌표 ↔ 화면 좌표 변환 (★ **Y축 뒤집힘** 처리, 회전 페이지 보정, DPI 보정) | — |
| **`font-manager.js`** | 한글 폰트 임베드 매니저. 나눔고딕·맑은고딕·한컴바탕 lazy 로딩 (필요한 도구만 다운로드) | fontkit |
| **`download.js`** | 결과 다운로드 헬퍼 (단일 PDF + zip 묶음, 파일명 자동 생성) | JSZip |
| **`canvas-render.js`** | pdf.js 페이지 → canvas → DataURL (OCR·이미지 변환·뷰어·비교 공용) | pdf.js |
| **`progress-ui.js`** | 진행률 바·취소 버튼·에러 토스트 (큰 PDF 작업 공용) | — |
| **`api-client.js`** | 외부 API 호출 wrapper (OCR·AI 요약·번역). `../../shared/api-keys.js` 와 연동 | — |
| **`pdf-meta.js`** | PDF 메타데이터 R/W (Title·Author·CreationDate 등) | pdf-lib |
| **`cdn.js`** | 외부 라이브러리 CDN URL 일괄 관리 (pdf-lib · pdf.js · fontkit · JSZip · Sortable · diff-match-patch). 버전 갱신 시 한 곳만 수정 | — |

### 4.2 도구 ↔ 모듈 의존 매트릭스

각 도구가 어떤 공통 모듈을 사용하는지. ★ = 핵심 의존, ○ = 사용, 빈 칸 = 미사용.

| 도구 | pdf-core | thumb | file-input | page-range | coord | font | download | canvas | progress | api | pdf-meta |
|---|:-:|:-:|:-:|:-:|:-:|:-:|:-:|:-:|:-:|:-:|:-:|
| 병합 | ★ |  | ★ |  |  |  | ★ |  | ○ |  |  |
| 분할 | ★ | ★ |  | ★ |  |  | ★ |  | ○ |  |  |
| 회전 | ★ | ★ |  | ★ |  |  | ★ |  |  |  |  |
| 페이지 삭제 | ★ | ★ |  | ★ |  |  | ★ |  |  |  |  |
| 페이지 재정렬 | ★ | ★ |  |  |  |  | ★ |  |  |  |  |
| 페이지 추출 | ★ | ★ |  | ★ |  |  | ★ |  |  |  |  |
| 페이지 크기 조정 | ★ |  |  | ★ |  |  | ★ |  | ○ |  |  |
| 자르기 | ★ | ★ |  | ★ | ★ |  | ★ |  |  |  |  |
| 워터마크 (텍스트) | ★ | ○ |  | ○ | ★ | ★ | ★ |  |  |  |  |
| 워터마크 (이미지) | ★ | ○ |  | ○ | ★ |  | ★ |  |  |  |  |
| 페이지 번호 | ★ |  |  | ○ | ★ | ★ | ★ |  |  |  |  |
| 머리글·바닥글 | ★ |  |  |  | ★ | ★ | ★ |  |  |  |  |
| 텍스트 추가 | ★ | ★ |  |  | ★ | ★ | ★ |  |  |  |  |
| 서명 | ★ | ★ |  |  | ★ |  | ★ |  |  |  |  |
| 텍스트 추출 | ★ |  |  | ○ |  |  | ★ |  | ○ |  |  |
| 이미지 추출 | ★ |  |  |  |  |  | ★ |  | ○ |  |  |
| 페이지 → 이미지 | ★ |  |  | ○ |  |  | ★ | ★ | ○ |  |  |
| **OCR** | ★ |  |  | ○ |  | ○ | ★ | ★ | ★ | ★ |  |
| **PDF → MD** | ★ |  |  |  |  |  | ★ |  | ★ |  |  |
| **이미지 압축** | ★ |  |  |  |  |  | ★ | ★ | ★ |  |  |
| **잠금 풀기** | ★ |  |  |  |  |  | ★ |  |  |  |  |
| **PDF 뷰어** | ★ | ★ | ★ |  | ○ |  |  | ★ |  |  | ○ |
| **PDF 비교** | ★ | ★ | ★ |  |  |  | ★ | ★ | ★ |  |  |
| PDF 요약 | ★ |  |  |  |  |  | ★ |  | ★ | ★ |  |
| PDF 번역 | ★ |  |  |  |  |  | ★ |  | ★ | ★ |  |

### 4.3 매트릭스에서 보이는 인사이트

- **`pdf-core` + `download`** 가 모든 도구의 공통 분모 → Phase 1 최우선 세팅
- **`thumbnail` + `page-range`** 가 페이지 작업 카테고리(§3.1) 의 공통 기반 → 묶음 개발 효율 ↑
- **`coordinate` + `font-manager`** 가 콘텐츠 편집 카테고리(§3.2) 의 공통 기반 → 한 번 만들면 6개 도구 즉시 가능
- **`canvas-render`** 가 추출·뷰어·비교·OCR 의 공통 기반 → 추출 카테고리 끝나면 뷰어·비교 진입 비용 ↓
- **`api-client`** + 공용 API 키 매니저 → OCR·요약·번역 일괄 처리

---

## 5. 기술 스택

### 5.1 외부 라이브러리 (모두 CDN 또는 로컬 번들)

| 라이브러리 | 용도 | 라이선스 | 비고 |
|---|---|---|---|
| **pdf-lib** | 페이지 작업·콘텐츠 편집·메타데이터·암호 처리 | MIT | 대부분의 도구가 의존. 핵심 |
| **pdf.js** (Mozilla) | 렌더링·텍스트 추출·페이지 썸네일·뷰어 | Apache 2.0 | viewer 컴포넌트도 제공 (참고 가능) |
| **fontkit** | 한글 폰트 임베드 (pdf-lib subsetting 보강) | MIT | 콘텐츠 편집 카테고리 필수 |
| **JSZip** | zip 묶음 다운로드 (분할·이미지 추출·페이지→이미지) | MIT/GPL | 이미 다른 GDI-Apps 에서 사용 중 |
| **Sortable.js** | 페이지 재정렬·다중 파일 정렬 UI | MIT | 가벼움 (~30KB) |
| **qpdf-wasm** | 권한제한 자동 해제 | Apache 2.0 | 번들 크기 ~5MB → lazy load |
| **diff-match-patch** (Google) | PDF 비교 (텍스트 diff) | Apache 2.0 | Phase 4 |

### 5.2 한글 폰트 (assets/fonts/)

| 폰트 | 용도 | 라이선스 | 크기 |
|---|---|---|---|
| 나눔고딕 | 워터마크·페이지번호·헤더푸터 기본값 | OFL | ~3MB → subset 으로 줄임 |
| 나눔스퀘어 | 보조 (제목용) | OFL | ~600KB |
| 한컴 바탕 | 행정문서 톤 (옵션) | (확인 필요) | — |

**전략**: lazy 로딩. 사용자가 한글 텍스트 추가하는 도구를 열 때만 폰트 다운로드. 기본은 가볍게.

### 5.3 외부 API (사용자 본인 키 입력 패턴)

| API | 용도 | 키 입력 | 비용 |
|---|---|---|---|
| **한컴 OCR** | OCR (한국어 최강) | 사용자 본인 키 | 1페이지 2원 |
| **Google Vision** | OCR (정확도 우수, 다국어) | 사용자 본인 키 | 1000건/월 무료, 이후 $1.50/1000건 |
| **Gemini API** | PDF 요약·번역 | 사용자 본인 키 | Free tier 넉넉 |
| **Claude API** | PDF 요약·번역 (대안) | 사용자 본인 키 | 유료 |
| **DeepL API** | PDF 번역 (전문) | 사용자 본인 키 | Free 50만자/월 |

**키 관리**: 모두 `../shared/api-keys.js` (image-editor v1.7.22+ 가 이미 사용 중인 공용 모듈) 에 위임. `filterApp` 옵션으로 PDF 딸깍 관련 키만 노출.

---

## 6. 폴더 구조

```
pdftools/
├── index.html                  # 카드 그리드 진입 (22개 도구 카드)
├── pdftools.css                # 공통 스타일 (카드·버튼·진행률 등)
├── PATCHNOTE.md                # 앱별 패치노트 (CLAUDE.md 메모리 룰 #4)
├── doc/
│   ├── dev.md                  # 이 문서 (단일 진실 출처)
│   └── manual.html             # 사용 설명서 (v1.0 출시 후 작성)
├── shared/                     # 앱 내부 공통 모듈 (§4)
│   ├── pdf-core.js             # ✅ v0.0.3
│   ├── file-input.js           # ✅ v0.0.3
│   ├── thumbnail.js            # ✅ v0.0.3
│   ├── page-range.js           # ✅ v0.0.3
│   ├── download.js             # ✅ v0.0.3
│   ├── progress-ui.js          # ✅ v0.0.3
│   ├── cdn.js                  # ✅ v0.0.3 (CDN URL 일괄 관리)
│   ├── coordinate.js           # ✅ v0.0.9 (자르기에서 첫 사용, Phase 2 콘텐츠 편집 핵심)
│   ├── font-manager.js         # ✅ v0.2.0 (한글 폰트 lazy 임베드)
│   ├── canvas-render.js        # 🚧 Phase 3 (추출·변환 진입 시)
│   ├── api-client.js           # 🚧 Phase 3 (OCR)
│   └── pdf-meta.js             # 🚧 Phase 1 (메타데이터 도구 추가 시)
├── tools/                      # 도구별 페이지 (도구 1개당 폴더 1개)
│   ├── merge/
│   │   ├── index.html
│   │   └── merge.js
│   ├── split/
│   ├── rotate/
│   ├── remove-pages/
│   ├── reorder/
│   ├── extract-pages/
│   ├── resize/
│   ├── crop/
│   ├── watermark-text/
│   ├── watermark-image/
│   ├── page-number/
│   ├── header-footer/
│   ├── add-text/
│   ├── sign/
│   ├── extract-text/
│   ├── extract-images/
│   ├── pages-to-images/
│   ├── ocr/
│   ├── pdf-to-md/
│   ├── compress/
│   ├── unlock/
│   ├── compare/
│   ├── summarize/
│   └── translate/
├── viewer/                     # PDF 뷰어 (다른 도구 호출 hub)
│   ├── index.html
│   └── viewer.js
└── assets/
    ├── fonts/
    │   ├── NanumGothic-subset.ttf
    │   └── NanumSquare-subset.ttf
    └── icons/                  # 도구별 SVG 아이콘 (카드용)
```

**라이브 URL 패턴**:
- `https://sobjil-gdi-apps.mycafe24.ai/pdftools/` — 카드 그리드
- `https://sobjil-gdi-apps.mycafe24.ai/pdftools/tools/merge/` — 병합 도구
- `https://sobjil-gdi-apps.mycafe24.ai/pdftools/viewer/?file=...` — 뷰어 (외부 PDF URL 도 fragment 로 받기 가능)

**홈 링크·아이콘·manifest** (CLAUDE.md §7 준수):
- 모든 페이지 topbar 좌측 `<a href="../../">딸깍 개발실</a>` (`tools/*/` 기준 두 단계 위, `viewer/` 는 한 단계 위 `../`)
- 아이콘·manifest 는 루트 참조 (`../../favicon.svg`)

---

## 7. 개발 Phase (공통 모듈 효율 기반)

Phase 묶음은 **공통 모듈 활용 관점**으로 정렬. 같은 모듈을 쓰는 도구는 같이 만든다. 후순위 도구도 모듈 설계 단계에서 미리 입력.

### Phase 0 — 인프라 세팅 (v0.0.1, 1~2일)

- 폴더 구조 생성 (§6)
- `pdftools.css` 공통 스타일 (카드·버튼·진행률·토스트)
- `index.html` 카드 그리드 골격 (22개 카드, "준비 중" 상태)
- 외부 라이브러리 CDN 링크 정리 (§5.1)
- 한글 폰트 subset 준비 (assets/fonts/)
- 랜딩 페이지 (`../index.html`) 의 `.tools-grid` 에 카드 추가 (CLAUDE.md §7)

> **마일스톤 정책** (2026-05-23 사용자 확정): Phase 1~5 까지는 **내부 개발 버전 (v0.1.0 ~ v0.5.0)** 으로 진행, **랜딩 카드 비공개**. Phase 5 완료 시점에 일괄 **v1.0.0 정식 출시 + 랜딩 카드 활성**. 중간 Phase 는 직접 URL 로만 접근 가능 (`/pdftools/tools/{tool-name}/`).

### Phase 1 — 코어 인프라 + 페이지 작업 (v0.1.0, 1~2주) ★

**공통 모듈 (Phase 1 에서 한 번에 세팅)**:
- `pdf-core.js` — PDF 로딩 wrapper (pdf.js + pdf-lib 동시 인스턴스)
- `file-input.js` — 드래그&드롭 + 다중 파일 정렬
- `thumbnail.js` — 페이지 썸네일 렌더러
- `page-range.js` — 페이지 범위 선택기
- `download.js` — zip 묶음 다운로드
- `progress-ui.js` — 진행률·취소·에러

**도구 (10개 — 페이지 작업 카테고리 전부 + 잠금 풀기 + 텍스트 추출)**:
1. ✅ **병합** (v0.0.5, 2026-05-23) — file-input + download + progress-ui 검증
2. ✅ **분할** (v0.0.6, 2026-05-23) — pdf-core + thumbnail + page-range + downloadZip 검증 + setupPdfJs() 신설
3. ✅ **회전** (v0.0.7, 2026-05-23) — pdf-lib page.setRotation 누적
4. ✅ **페이지 삭제** (v0.0.7, 2026-05-23) — 뒤에서부터 removePage 패턴
5. ✅ **페이지 재정렬** (v0.0.7, 2026-05-23) — thumbnail.draggable + onReorder 첫 실전
6. ✅ **페이지 추출** (v0.0.7, 2026-05-23) — 범위 ↔ 썸네일 양방향 동기화
7. ✅ **페이지 크기 조정** (v0.0.8, 2026-05-23) — pdf-lib embedPages + drawPage 첫 실전 (Phase 2 콘텐츠 편집 핵심 패턴)
8. ✅ **자르기** (v0.0.9, 2026-05-23) — **`coordinate.js` 신설** (Phase 2 핵심 인프라). drag UI + setMediaBox/setCropBox + 회전 보정
9. ✅ **잠금 풀기** (v0.1.0, 2026-05-23) — pdf-lib ignoreEncryption 패턴 (qpdf-wasm 보류). 권한 제한 PDF 자동 해제. 열기 암호 PDF 는 v0.1.x 예정
10. ✅ **텍스트 추출** (v0.0.8, 2026-05-23) — pdf.js getTextContent 첫 실전

**왜 묶음**: 페이지 작업 8개는 같은 공통 모듈을 거의 다 공유. 한 번 인프라 세팅하면 도구 추가가 ★1~★2. 일주일 만에 10개 도구 출시 가능.

### Phase 2 — 콘텐츠 편집 + 한글 폰트 (v0.2.0, +1~2주)

**공통 모듈 추가**:
- `coordinate.js` 완성 (Y축 뒤집힘·회전 보정·DPI)
- `font-manager.js` — 한글 폰트 lazy 로딩

**도구 (6개 — 콘텐츠 편집 카테고리 전부)**:
1. 워터마크 (텍스트)
2. 워터마크 (이미지)
3. 페이지 번호 (한국식 포맷 포함)
4. 머리글·바닥글
5. 텍스트 추가
6. 서명

→ Phase 1+2 합산 누적 16개 도구. **여전히 내부 버전 v0.2.0** (랜딩 미공개).

### Phase 3 — 추출·변환 + API 인프라 (v0.3.0, +2주)

**공통 모듈 추가**:
- `canvas-render.js` — pdf.js → canvas 공용
- `api-client.js` — 외부 API wrapper + `../shared/api-keys.js` 연동

**도구 (4개 — 추출 카테고리 나머지)**:
1. 이미지 추출
2. 페이지 → 이미지
3. **OCR** (한컴 + Google 동시 지원)
4. **PDF → MD**

### Phase 4 — 가공·뷰어·비교 (v0.4.0, +2주)

**도구 (3개)**:
1. **이미지 압축** — canvas-render 활용
2. **PDF 뷰어** — 다른 도구 호출 hub. **모든 도구가 안정화된 후 만드는 게 합리적** (hub 가 가리킬 곳이 있어야 의미)
3. **PDF 비교** — diff-match-patch

### Phase 5 — AI 도구 → **v1.0.0 정식 출시** (+1주) ★

API 키 매니저 + 추출 파이프라인 이미 안정화. 위에 얹기만.

**도구 (2개 — 카탈로그 마지막)**:
1. PDF 요약
2. PDF 번역

**Phase 5 완료 시점 작업 (v1.0.0 출시 준비)**:
- 22개 도구 전수 검증 (브라우저 호환성·큰 PDF 처리·메모리 누수)
- 사용자 매뉴얼 (`doc/manual.html`) 작성 — 모든 도구 1줄 소개 + 자주 묻는 질문
- **랜딩 페이지 (`../index.html`) 의 `.tools-grid` 에 PDF 딸깍 카드 활성** (CLAUDE.md §7 step 7)
- CLAUDE.md §2.4 갱신 (✅ Live v1.0.0 + 외부 표시 갱신)
- 외부·내부 버전 v1.0.0 통합 (md2hwpx·WeeklyBrief 패턴 따라)

### 누적 출시 일정

| 버전 | 시점 | 누적 도구 수 | 랜딩 노출 |
|---|---|---|---|
| **v0.1.0** | **✅ Phase 1 완료 (2026-05-23)** | **10** | X (내부 개발) |
| v0.2.0 | Phase 2 완료 (다음) | 16 | X (내부 개발) |
| v0.3.0 | Phase 3 완료 | 20 | X (내부 개발) |
| v0.4.0 | Phase 4 완료 | 22 (모든 도구 + 뷰어 hub) | X (내부 개발) |
| **v1.0.0** | **Phase 5 완료 — 정식 출시** | **22 + AI 2종** | ✅ **랜딩 카드 활성** |

---

## 8. 함정·주의 (PDF 기술 특유)

이 앱을 만질 때 새 세션이 마주칠 가능성 높은 함정들. 한 번 박혀버리면 디버깅에 며칠 깎이는 것들만.

### 8.1 좌표계 — Y축 뒤집힘 ★★★

PDF 좌표계는 **좌하단 (0,0)** 기준. HTML/canvas 는 **좌상단 (0,0)** 기준. 워터마크·텍스트 추가·서명·자르기 모두 영향. `coordinate.js` 가 단일 책임으로 변환 담당.

```
HTML/Canvas:        PDF:
(0,0) ─────→ x      ↑ y
  │                 │
  ↓ y               │
                    (0,0) ─────→ x
```

→ 사용자가 화면에서 클릭한 (cx, cy) 를 PDF 좌표 (px, py) 로 변환:
- `px = cx * (pdfWidth / canvasWidth)`
- `py = pdfHeight - cy * (pdfHeight / canvasHeight)` ← 뒤집힘 + 스케일

회전된 페이지(`/Rotate 90/180/270`)는 추가 변환 필요.

### 8.2 한글 폰트 임베드 — pdf-lib 기본 폰트는 ASCII 만 ★★★

pdf-lib 의 `StandardFonts.Helvetica` 같은 기본 폰트는 한글 글리프 X. 한글 워터마크·텍스트 추가하면 ▯▯▯ (tofu) 로 깨짐.

**해법**: `fontkit` 등록 + 나눔고딕 .ttf subset 임베드.

```js
import { PDFDocument } from 'pdf-lib';
import fontkit from '@pdf-lib/fontkit';

const pdfDoc = await PDFDocument.load(bytes);
pdfDoc.registerFontkit(fontkit);
const fontBytes = await fetch('assets/fonts/NanumGothic-subset.ttf').then(r => r.arrayBuffer());
const font = await pdfDoc.embedFont(fontBytes, { subset: true });
page.drawText('대외비', { font, ... });
```

`subset: true` 필수 — 사용한 글리프만 임베드해서 파일 크기 폭증 방지.

### 8.3 큰 PDF — 100MB+ / 1000페이지+

- pdf.js 렌더링은 메모리 ↑. 페이지 썸네일은 **가상 스크롤** 필수 (보이는 페이지만 렌더)
- pdf-lib 의 `copyPages` 도 큰 PDF 에서 느림 → 진행률 표시 + 백그라운드 처리
- 브라우저 메모리 한계 → 500MB+ PDF 는 사전 경고 + 권장 사양 안내

### 8.4 압축 — 클라이언트 한계

진짜 압축 (Ghostscript 급) 은 PDF 객체 재구성·이미지 다운샘플·폰트 subset·콘텐츠 스트림 재인코딩 종합. 클라이언트로 가능한 건 **이미지 다운샘플 + JPEG 재인코딩** 정도 (30~50% 절감 가능). 텍스트 위주 PDF 는 효과 미미. **사용자에게 "이미지 압축" 으로 정직하게 표시** (그냥 "압축" 이라고 하면 기대치 깨짐).

### 8.5 잠금 풀기 — qpdf-wasm 번들 크기

qpdf-wasm 은 ~5MB. 다른 도구 페이지에서 무조건 로드하면 부담. **lazy 로딩** — 잠금 풀기 도구 페이지에서만 import.

### 8.6 권한 — `navigator.clipboard` / 큰 파일 다운로드

- 텍스트 추출 결과 클립보드 복사 → 사용자 제스처(클릭) 없이 불가
- 큰 zip 다운로드 → 브라우저별 메모리 한계 다름. 분할 다운로드 옵션 고려

### 8.7 OCR — 좌표 매핑 + 텍스트 레이어 삽입

OCR 결과를 "검색가능한 PDF" 로 만들려면 **OCR 텍스트를 보이지 않는 레이어로 페이지 위에 박아야** 함 (pdf-lib `drawText` 에 `opacity: 0` 또는 색을 페이지와 동일하게). 좌표는 OCR API 가 주는 bbox 를 PDF 좌표로 변환. API 마다 좌표 단위·원점 다르니 어댑터 분리 필요 (한컴 vs Google).

### 8.8 PDF→MD — 구조 복원의 한계

PDF 는 본질적으로 시각 포맷. "이게 헤딩 1인지 본문인지" 정보가 PDF 안에 없음 (대부분). 휴리스틱 (폰트 크기·굵기 기반) 으로 추정해야. 표·목록·이미지 위치 보존도 불완전. **사용자에게 "구조 복원 ~70%, 후처리 필요" 안내** 필수. 이후 md2hwpx 로 넘기면 양식 맞춰 정리 가능 (워크플로 시너지).

### 8.9 PDF 비교 — 무엇을 비교할 것인가

- **텍스트 diff** = 본문 글자 비교. 구현 쉬움 (diff-match-patch)
- **시각 diff** = 페이지 이미지 픽셀 비교. 구현 어려움. v1.x 보류
- **구조 diff** = 페이지 추가·삭제·순서 변경. 중간 난이도

→ Phase 4 는 **텍스트 diff 만** 우선. 시각 diff 는 사용자 요청 시 추가.

---

## 9. 결정 보류

다음 항목은 개발 진행하면서 재논의:

- ✏ **이미지 → PDF** — 만들기 카테고리 일괄 제외로 빠졌으나, GDI 직원 결재 시나리오 (사진 합쳐 PDF) 수요 가능성. v1.x 보너스 도구로 부활 검토. 부활 시 폴더 `tools/images-to-pdf/`, jsPDF 단일 의존성, 난이도 ★2
- ✏ **HTML → PDF** — 현재 브라우저 인쇄 PDF 와 차별점 X 로 제외. 헤드리스 브라우저(Puppeteer) 옵션을 GDI-Apps 백엔드 어딘가에 두면 부활 가능 (AISpace 플랜 업그레이드 후)
- ✏ **OCR 백엔드 디폴트** — 한컴 vs Google. 둘 다 지원하되 사용자가 처음 진입했을 때 어느 쪽을 추천할지. 한국어 보고서 위주면 한컴, 다국어 혼합이면 Google. 사용자 실제 사용 데이터 보고 결정
- ✏ **AI 백엔드 (요약·번역)** — Gemini Free 가 가장 합리적이지만, 한국어 행정문서 톤은 Claude 우수. v1.3 개발 착수 시 사용 가능한 API 풀로 재평가
- ✏ **PDF 뷰어 = 다른 도구 호출 hub 의 인터랙션 설계** — "이 PDF 로 병합" 클릭 → 새 탭에서 도구 열기? 같은 탭에서 전환하되 PDF 상태 유지? Phase 4 진입 시 다른 도구들의 입력 패턴 살펴보고 결정
- ✏ **manifest·favicon 별도 둘지** — 현재 GDI-Apps 루트의 것을 그대로 참조. PDF 딸깍 전용 아이콘 필요해지면 별도 manifest

---

## 10. 시너지 — 다른 GDI-Apps 와의 관계

### 10.1 즉시 시너지 (Phase 1~2 부터 가능)

**WeeklyBrief (정책 브리핑)**:
- News_DB 가 매 10분 cron 으로 첨부 PDF 사전 추출 중. 현재 `pdf` 첨부 텍스트 추출은 GitHub Actions 의 `pdfplumber` 가 담당
- PDF 딸깍 의 `pdf.js` 기반 텍스트 추출 모듈을 **클라이언트 사이드 보조**로 활용 가능 — News_DB 가 놓친 PDF 첨부를 라이브 페이지에서 즉시 추출
- 클라이언트 측 추출은 Worker 우회 가능 → CORS 이슈 회피 (이미 News_DB 가 사전 다운로드해 raw URL 제공 중이라 OK)

### 10.2 단방향 시너지 (PDF 딸깍 → 다른 앱)

**md2hwpx (한글 문서 스타일 딸깍)**:
- **PDF → MD 도구** 결과를 그대로 md2hwpx 에 붙여넣기 → HWPX 양식 변환 워크플로 완성
- 워크플로: 정책 PDF → PDF 딸깍 (PDF→MD) → md2hwpx → HWPX 보고서

**image-editor (이미지 에디터)**:
- 페이지 → 이미지 결과를 image-editor 로 전달 → 슬라이드 이미지 수정
- 공용 `shared/api-keys.js` 매니저 이미 image-editor v1.7.22 에서 검증됨

### 10.3 미래 시너지 (devplan 아이디어들)

- **통계분석 딸깍** — 보고서 PDF 표지·페이지번호·병합으로 결과 패키징
- **문서번역 딸깍** — 별도 앱으로 분리 안 함 (2026-05-23 결정). PDF 번역은 이 앱에 포함, 다른 포맷 번역은 향후 검토
- **공문 본문 딸깍** — 결재 PDF 도장 합성 (이 앱의 워터마크-이미지 도구 활용)

### 10.4 공용 인프라 출처

- **`../shared/api-keys.js`** — image-editor v1.7.22 에서 생성, GDI-Apps 전체 공용 키 저장소. PDF 딸깍은 OCR·요약·번역 키를 여기에 위임
- **`../view.html`** — Markdown 렌더링 (CLAUDE.md 메모리 룰 #6). dev.md 링크 시 `../view.html?path=pdftools/doc/dev.md` 패턴

---

## 11. 출시 사이클 (CLAUDE.md §4 준수)

```bash
# 1. 변경 후 syntax 검증
node --check pdftools/shared/*.js pdftools/tools/*/*.js

# 2. ★ cache-busting (메모리 룰 #8)
# 각 도구 페이지의 ?v=0.X.Y 일괄 bump
# 공통 모듈은 모든 도구 페이지에서 참조하므로 모든 페이지 bump 필요

# 3. 새 브랜치에서 commit + push
git checkout -b claude/pdftools-phase1 && git add -A && git commit -m "..." && git push -u origin claude/pdftools-phase1

# 4. PR 만들기 + 머지 (워크트리 안전한 API 직접 호출)
gh pr create --title "..." --body "..."
gh api -X PUT repos/sobjil/GDI-Apps/pulls/<N>/merge -f merge_method=merge

# 5. ★ 머지 직후 원격 브랜치 즉시 삭제 (메모리 룰 #9)
gh api -X DELETE repos/sobjil/GDI-Apps/git/refs/heads/claude/pdftools-phase1

# 6. AISpace 재배포 (메모리 룰 #1)
# AISpace:external_repo(action='repo_update', repo_name='GDI-Apps', wait=true)

# 7. 라이브 확인
# - https://sobjil-gdi-apps.mycafe24.ai/pdftools/
# - 도구별 URL 도 개별 확인

# 8. PATCHNOTE.md 갱신 (메모리 룰 #4)
```

---

## 12. 딸깍 디자인 원칙 점검 (CLAUDE.md §6)

PDF 딸깍 각 도구에 대해 §6 4원칙 적용:

### 12.1 ✅ 권장 패턴 (모든 도구 공통 적용)

- **자동 추천**: 파일 던지면 즉시 미리보기. "분석 시작" 버튼 X
- **딸깍 한 번**: 도구마다 "변환 시작" 같은 단일 액션 버튼. 옵션은 기본값으로 즉시 작동 가능
- **이모지 시각 단서**: 📂 업로드 / 🔄 변환 중 / ✓ 완료 / ⚠ 주의 / 🔒 잠김
- **드래그&드롭**: 모든 도구의 파일 입력
- **한국어 100%**: 영문 enum 노출 X (예: `PORTRAIT/LANDSCAPE` → "세로/가로")
- **친절한 에러**: 손상된 PDF·암호 PDF·메모리 초과 모두 한국어로 무엇이 잘못됐는지 + 해결 방법

### 12.2 ❌ 안티 패턴 회피

- 다단계 마법사 X — 한 화면에서 모든 옵션 노출 (Progressive Disclosure 로 고급은 접어두기)
- 시작부터 복잡한 폼 X — 파일 업로드만 받고 옵션은 그 결과로 자동 노출
- 영문 enum (PORTRAIT, JUSTIFY 등) 노출 X — 한국어로 표시
- 사용자가 매뉴얼 학습해야 쓸 수 있는 UI X — UI 자체로 사용법 전달

### 12.3 도구별 특화 점검 포인트

| 도구 | 딸깍 적용 |
|---|---|
| 병합 | 파일 던지면 즉시 썸네일 + 드래그 정렬. "병합" 버튼 한 번 |
| 분할 | "범위 입력" 대신 썸네일 위에 분할선 클릭 |
| 잠금 풀기 | 시나리오 자동 판별 — 권한제한이면 즉시 해제, 열기암호면 입력 폼만 |
| OCR | 사용자가 API 선택 (한컴/Google) 후 던지면 끝. 옵션 토글 최소화 |
| 뷰어 | 옆에 다른 도구 호출 버튼들 — "이 PDF 회전", "이 PDF 워터마크" 등 한 클릭 전환 |

---

## 13. 빠른 인덱스 (다시 찾을 때)

- [§1 정체성](#1-프로젝트-정체성) — 무엇을 만들고 안 만드는가
- [§2 결정사항](#2-핵심-결정사항-2026-05-23-사용자-확정) — 사용자 확정 8개
- [§3 도구 카탈로그](#3-도구-카탈로그-최종-6-카테고리-22개) — 22개 도구 (6 카테고리)
- [§4 공통 모듈 ★](#4-공통-모듈-설계--핵심) — 11개 모듈 + 의존 매트릭스
- [§5 기술 스택](#5-기술-스택) — 라이브러리·폰트·API
- [§6 폴더 구조](#6-폴더-구조) — 디렉토리 트리
- [§7 개발 Phase](#7-개발-phase-공통-모듈-효율-기반) — Phase 0~5 + 누적 일정
- [§8 함정·주의](#8-함정주의-pdf-기술-특유) — PDF 특유 디버깅 함정 9개
- [§9 결정 보류](#9-결정-보류) — 개발하면서 재논의
- [§10 시너지](#10-시너지--다른-gdi-apps-와의-관계) — 다른 앱과 연결
- [§11 출시 사이클](#11-출시-사이클-claudemd-4-준수) — 배포 명령어
- [§12 딸깍 원칙](#12-딸깍-디자인-원칙-점검-claudemd-6) — UI/UX 점검
- [§14 v2.0 viewer hub 설계 ★](#14-v20-viewer-hub-리팩토링-설계-2026-05-25-사용자-확정) — UI A + 패널 인터페이스 + 좌측 페이지 공유 전략

---

## 14. v2.0 viewer 3-column PDF 편집기 설계 (2026-05-25 사용자 재확정)

> **인수인계 컨텍스트**: v1.0.0 = MVP (단순 미리보기 + 외부 새 탭). v2.0.0-dev1 = 좌+우 2-column + viewMode 자동 전환 + 도구마다 다운로드 (사용자 의도 부분 충족). **v2.0.0-dev2 (이 §14) = 사용자가 큰 패러다임 전환 요청** — 3-column 항상 표시 + 도구는 옵션 즉시 라이브 적용 + 다운로드는 메인 한 번 + Undo + 텍스트 선택. 즉 PDF 뷰어 + 편집기 통합. 본 §14 가 단일 진실 출처.

### 14.1 사용자 확정 사항 (2026-05-25 v2.0.0-dev1 직후)

| # | 결정 | 근거 |
|---|---|---|
| 1 | **3-column 레이아웃** (좌 썸네일 / 중 뷰어 / 우 도구창) | Adobe Acrobat 식 PDF 편집기. 좌·중은 항상 표시 — viewMode 자동 전환 X |
| 2 | **좌측 썸네일 = 선택 토글 가능, 항상 표시** | 도구가 페이지 선택 필요할 때 따로 mode 전환 X. 도구 비활성 시에도 썸네일 사용 (페이지 점프 등) |
| 3 | **우측 도구창 = 기본 도구 리스트 ↔ 활성 도구 오버레이 swap** | 도구 클릭 → 패널 오버레이. "← 도구 목록" 버튼으로 복귀 |
| 4 | **도구 패널 = 옵션 즉시 라이브 적용 (WYSIWYG)** | 옵션 클릭 = 즉시 좌·중 미리보기 반영 + currentCore 누적. "적용" 버튼 X |
| 5 | **다운로드 = 메인 toolbar 한 번** | 도구마다 다운로드 X. 모든 변경 누적 후 메인 다운로드 버튼 한 번 |
| 6 | **Undo (Ctrl+Z) 지원** | history stack. 각 라이브 적용을 push, 다운로드 전 자유롭게 되돌리기 |
| 7 | **중앙 뷰어 TextLayer 텍스트 선택** | pdf.js 공식 TextLayer mount → 드래그 선택 + Ctrl+C 복사. 메인 뷰어 기본 기능 (별도 PR X) |
| 8 | **★ 기존 23개 단독 도구 페이지 모두 유지** | (이전 결정 유지) panel 과 단독 페이지가 core.js 공유 import |

### 14.2 3-column 레이아웃

```
┌─────────────────────────────────────────────────────────────────────────┐
│  메인 toolbar:  파일명 · 페이지수 │ 줌 │ ↶ Undo  ↷ Redo │ 💾 다운로드  📂 │
├──────────────┬─────────────────────────────────┬─────────────────────────┤
│  좌측        │  중앙                            │  우측                    │
│  썸네일      │  PDF 뷰어                        │  도구창                  │
│  (220px)     │  (1fr · lazy + TextLayer)        │  (320px)                 │
│              │                                   │                          │
│  [□] 1쪽     │  ┌─────────────────────┐         │  📑 병합                 │
│  [☑] 2쪽     │  │                     │         │  ✂️ 분할                 │
│  [☑] 3쪽     │  │  큰 페이지 렌더      │         │  🔄 회전·반전 ◀ active   │
│  [□] 4쪽     │  │  + TextLayer        │         │  🗑️ 페이지 삭제          │
│  ...         │  │  (텍스트 드래그)     │         │  ...                     │
│              │  └─────────────────────┘         │  (오버레이 시 패널 swap) │
└──────────────┴─────────────────────────────────┴─────────────────────────┘
```

작은 화면 (≤1300px): 좌측 썸네일을 토글로 접거나 우측 도구창과 같은 사이드바로 합침.

### 14.3 라이브 적용 + Undo 모델

**상태**
```js
currentCore   // 메인 PDF 인스턴스 (모든 적용 누적 결과)
selectedPages // 좌측 썸네일 선택 (1-based 배열)
history       // [{ bytes: Uint8Array, label: string }] 이전 상태 stack
future        // [{ bytes, label }] Redo 용
```

**도구가 라이브 적용 (예: rotate 90°)**
1. 사용자가 좌측 썸네일에서 페이지 체크 → selectedPages 갱신 (구독한 도구 패널 onSelectionChange 콜백 호출)
2. 우측 패널의 "90°" 버튼 클릭
3. 패널: `api.applyToPdf(await applyRotation(currentCore, 'rot90', selectedPages))`
4. viewer: `history.push({ bytes: currentCore.bytes, label: '90° 회전' })` → `currentCore` 교체 → 좌측·중앙 미리보기 갱신 (전체 재렌더 또는 변경 페이지만)
5. Undo 버튼 enabled

**Undo (Ctrl+Z)**
- `future.push(currentCore.bytes)` → `currentCore = loadPDF(history.pop().bytes)` → 미리보기 갱신
- history 비면 Undo disabled

**다운로드**
- 메인 💾 버튼 → `currentCore.bytes` 다운로드 (파일명: 원본 + "-편집됨")

### 14.4 panel 모듈 표준 인터페이스 (mountPanel 시그니처) — 새 패러다임

```js
// 예: tools/rotate/panel.js
export default {
  name: '회전·반전',
  icon: '🔄',
  cat: 'page',            // page · content · extract · misc
  needsPageSelection: true, // 좌측 썸네일 선택이 필요한 도구 (없으면 옵션 버튼 disabled 표시)

  /**
   * @param {Object} ctx
   * @param {function(): PDFCore} ctx.getCore       현재 currentCore (호출 시점의 최신)
   * @param {function(): number[]} ctx.getSelectedPages  현재 선택 페이지 (1-based)
   * @param {HTMLElement} ctx.container             우측 패널 mount 영역
   * @param {Object} ctx.api
   * @param {function(Uint8Array|File, string)} ctx.api.applyToPdf   PDF 변경 적용 (label = Undo 설명)
   * @param {function(callback)} ctx.api.onSelectionChange            selectedPages 변경 구독
   * @param {function(label, pct)} ctx.api.onProgress                 공용 progress (label=null = 닫기)
   * @returns {function} cleanup  패널 unmount 시 호출
   */
  mountPanel(ctx) {
    // 옵션 UI 구성 — 옵션 클릭 = 즉시 액션
    return function cleanup() { /* ... */ };
  },
};
```

**도구 분류 (새 패러다임)**

| 도구 유형 | 동작 패턴 | 도구 |
|---|---|---|
| **즉시 적용** (옵션 = 액션) | 옵션 버튼 클릭 = 즉시 currentCore 적용 + history push | rotate, remove-pages, extract-pages, reorder, split-each, watermark, page-number, header-footer |
| **WYSIWYG 부동소수 액션** | 페이지 위 오버레이 (드래그 영역 등) → 확정 시 적용 | crop, add-text, signature, resize, watermark 위치 이동 |
| **결과 반환만** (currentCore 교체 X) | 결과를 다운로드 또는 별도 영역 표시 | extract-text, to-image, to-markdown, summary, translate, split-range/group, unlock, compare, compress-images, merge (←여러 PDF 입력) |

**결과 반환만 도구**는 `api.applyToPdf` 대신 `api.downloadResult(bytes, fileName)` 또는 패널 안 결과 뷰를 사용. currentCore 누적에 영향 X.

### 14.5 공용 로직 추출 패턴 (변경 없음 — 단독 페이지 ↔ panel 양쪽 import)

```
tools/rotate/
  index.html        ← 단독 페이지 (그대로 유지)
  rotate.js         ← 단독 페이지 entry — UI 결선 + ./core.js import
  panel.js          ← v2.0 신규 — viewer 가 import, ./core.js import
  core.js           ← v2.0 신규 — 순수 PDF 변환 로직
```

### 14.6 신규 shared 모듈

- `shared/viewer-panel.js` (v2.0.0-dev1 신규, 그대로 재활용) — `createSidebarGroup` / `createOptionGrid` / `createNotice` / `clearChildren` (`createApplyRow` 는 새 패러다임에서 사용 빈도 낮음 — keep)
- `shared/text-layer.js` (v2.0.0-dev2 신규) — pdf.js TextLayer mount helper. canvas 렌더 후 같은 페이지에 TextLayer overlay 부착 (textContent + textLayer.render). 드래그 선택 + Ctrl+C native 지원.
- `shared/history-stack.js` (v2.0.0-dev2 신규) — Undo/Redo stack helper (max size 제한, label 보존)

### 14.7 viewer 전면 개편 (v2.0.0-dev2)

`viewer.js` / `index.html` / `viewer.css` 큰 변경 — 3-column + 라이브 적용 + 메인 다운로드 + Undo + TextLayer 모두 메인 뷰어 본체에 흡수.

핵심 함수:
- `setCurrentCore(newCore, label)` — currentCore 교체 + history push + 좌·중 미리보기 갱신 + 다운로드/Undo 버튼 상태 업데이트
- `renderThumbnailsLeft()` — 좌측 썸네일 selectable 렌더 (shared/thumbnail.js 재사용)
- `renderPagesCenter()` — 중앙 lazy 페이지 렌더 + 각 페이지 캔버스 위에 TextLayer mount
- `mountToolPanel(toolId)` / `unmountToolPanel()` — 우측 사이드바 swap
- `handleUndo()` / `handleRedo()` / `handleDownload()`

### 14.8 작업 순서 (v2.0.0-dev2 부터)

1. **v2.0.0-dev2 PR (★ 이 PR)** = §14 새 설계 박제 + viewer 전면 개편 (3-column · 라이브 적용 · 메인 다운로드 · Undo · TextLayer) + rotate 새 패턴 검증
2. **v2.0.0-dev3 PR** = 페이지 작업 즉시 적용 도구 5개 (remove-pages · extract-pages · reorder · split-each · split-group)
3. **v2.0.0-dev4 PR** = WYSIWYG 도구 (watermark-text/image · page-number · header-footer)
4. **v2.0.0-dev5 PR** = 결과 반환 도구 (extract-text · to-image · to-markdown · summary · translate · compare · unlock)
5. **v2.0.0-dev6 PR** = 특수 도구 (merge · from-image · compress-images · crop · add-text · signature · resize)
6. **v2.0.0 정식 출시 PR** = PATCHNOTE / dev.md / CLAUDE.md / README / cache-bust / AISpace 재배포
