# Doc PDF

> 어떤 콘텐츠든 PDF 파일로 만들어 드립니다 — 스타일이 입혀진 HTML 리포트는 디자인을 그대로 보존해 변환하고, Markdown·구조화 JSON·일반 텍스트는 자동 서식으로 렌더합니다. weasyprint 단일 엔진으로 풀 CSS를 충실히 렌더하며, 번들 Noto Sans CJK 폰트를 임베딩해 한국어·일본어·중국어 글리프 깨짐을 근본 차단합니다. 다음과 같은 요청 시 반드시 이 스킬을 사용하세요: - "PDF로 만들어줘", "PDF로 생성해줘", "PDF로도 생성해줘", "PDF로 저장해줘", "PDF로 출력해줘", "PDF로 뽑아줘" - "이거 PDF로", "이 리포트 PDF로", "이 보고서 PDF로 변환해줘", "HTML을 PDF로", "HTML 리포트를 PDF로" - "PDF로 변환해줘", "문서 PDF 파일 생성", "보고서 PDF로 저장해줘", "계획서 PDF 파일" - "한글 PDF 만들어줘", "한국어 PDF", "일본어 PDF", "중국어 PDF", "다국어 PDF", "CJK PDF" - "한글 깨짐 없는 PDF", "한자 깨짐 없는 PDF", "폰트 임베딩 PDF" - "Markdown을 PDF로", "JSON 입력으로 PDF 문서 생성" PDF 생성·변환 요청 시 이 스킬의 표준 경로(아래 인라인 weasyprint 코드)를 사용하세요. 입력 감지 → HTML 빌드 → Noto Sans CJK @font-face 주입 → weasyprint 렌더를 이 문서의 인라인 코드로 직접 수행하며, 올바른 CJK 폰트 설정을 함께 적용합니다.

- Skill: `modu-ai/doc-pdf` (Agent Skill, multi-file: 4 files)
- Install (CLI): `npx skillmds@latest add modu-ai/doc-pdf`
- Raw SKILL.md: https://api.skillmd.com/api/skills/modu-ai/doc-pdf/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Web & Frontend
- Author: modu-ai (https://skillmd.com/u/modu-ai)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/modu-ai/doc-pdf

---


# PDF 생성기 (doc-pdf)

## 개요

**weasyprint 단일 엔진**으로 모든 입력을 PDF로 렌더합니다. 입력 형태와 무관하게
`입력 → HTML → weasyprint → PDF` 한 경로로 통일되어, 다음 두 시나리오를 모두 충실히 처리합니다.

1. **스타일이 입혀진 HTML 리포트** (예: `moai-officer:doc-html-report`·`moai-officer:doc-html-slide` 산출물,
   또는 사용자가 만든 디자인 HTML) → **자체 CSS를 그대로 렌더해 화면 디자인을 보존**합니다.
   2열 카드 그리드·커스텀 색·웹폰트 등 모던 CSS 레이아웃이 깨지지 않습니다.
2. **Markdown · 구조화 JSON · 일반 텍스트** → 기본 문서 서식(A4·표·제목 스타일)을 적용해 렌더합니다.

CJK(한·중·일) 글리프는 번들 **Noto Sans CJK** 폰트를 `@font-face`로 임베딩해 깨짐을 차단하며,
시스템에 `Noto Sans CJK KR`이 설치돼 있으면 fontconfig가 자동 폴백합니다.

> **왜 weasyprint 단일 엔진인가**: weasyprint는 풀 CSS(레이아웃·색·웹폰트·표)를 충실히 렌더합니다.
> "이 HTML 리포트를 디자인 그대로 PDF로"가 가장 흔한 요청이며, 이 경로가 그 요구를 정확히 만족합니다.
> 좌표 기반 저수준 렌더링(예: PyMuPDF insert_text)은 모던 CSS 레이아웃을 재현하지 못하므로 사용하지 않습니다.

## 워크플로우

```
0. [폰트 확인]  → 시스템 Noto Sans CJK KR 존재 확인, 없으면 폰트 미지정 폴백 (weasyprint fontconfig 자동 폴백)
1. [입력 감지]  → HTML(완성 문서) / Markdown / JSON / Text 자동 감지
2. [HTML 빌드]  → 완성 HTML은 그대로, 그 외는 HTML로 변환 (+ 기본 문서 CSS)
3. [폰트 주입]  → Noto Sans CJK @font-face CSS 결합 (CJK 깨짐 방지)
4. [렌더]       → weasyprint HTML.write_pdf() 로 A4 PDF 생성
5. [자체 검수]  → 산출 PDF 재확인 (페이지 수·CJK 표시·플레이스홀더 잔존)
```

핵심 실행은 아래 인라인 weasyprint 코드로 직접 수행합니다(별도 스크립트 없음). 입력 파일 확장자·내용으로
HTML / Markdown / JSON / Text를 감지해 분기하고, 완성 HTML은 자체 CSS를 보존한 채, 그 외는 기본 서식 +
Noto Sans CJK를 적용합니다.

### 0단계: 폰트 보장 (조건부 경로)

CJK 폰트는 다음 우선순위로 해결합니다 — 별도 다운로드 스크립트 없이:

1. **시스템 폰트 우선**: 시스템에 `Noto Sans CJK KR`이 설치돼 있으면 weasyprint fontconfig가 자동 폴백하므로
   `@font-face` 없이도 CJK가 안전하게 표시됩니다. 대부분의 Cowork 샌드박스·Linux/macOS가 이에 해당합니다.
2. **번들 폰트가 있으면 임베딩**: `${CLAUDE_PLUGIN_ROOT}/skills/doc-pdf/assets/fonts/`에 Noto Sans CJK
   OTF가 있으면 아래 코드의 `@font-face`가 자동으로 임베딩합니다(있으면 사용, 없으면 1번 폴백).
3. **둘 다 없을 때**: 사용자에게 시스템 `Noto Sans CJK KR` 설치를 안내합니다. 운영체제별 명령은 아래와 같으며,
   자동 대량 다운로드는 수행하지 않습니다.

   | OS | 설치 |
   |---|---|
   | Windows | [fonts.google.com/noto/specimen/Noto+Sans+KR](https://fonts.google.com/noto/specimen/Noto+Sans+KR)에서 받아 압축 해제 → 폰트 파일 전체 선택 → 우클릭 → **설치** |
   | macOS | `brew install --cask font-noto-sans-cjk-kr` |
   | Debian/Ubuntu | `apt install fonts-noto-cjk` |

### 1~4단계: weasyprint 렌더링 인라인 코드

아래 코드를 그대로 실행합니다:

```python
from weasyprint import HTML, CSS
from pathlib import Path
import os

FONT_DIR = Path(os.environ.get("CLAUDE_PLUGIN_ROOT", str(Path(__file__).parent.parent))) / "assets" / "fonts"

# 번들 Noto Sans CJK를 @font-face로 등록 (weight별)
# 경로 → URL 변환은 반드시 Path.as_uri()로 한다. 문자열 "file://" + 경로 조립은
# Windows에서 file://C:\... 형태의 잘못된 URL이 되어 폰트 로드에 실패한다.
FONT_FACE = "".join(
    f"@font-face{{font-family:'Noto Sans CJK';font-weight:{w};"
    f"src:url('{(FONT_DIR / f'NotoSansCJK-{n}.otf').as_uri()}');}}"
    for w, n in {300: "Light", 400: "Regular", 500: "Medium", 700: "Bold"}.items()
    if (FONT_DIR / f"NotoSansCJK-{n}.otf").exists()
)

# (A) 완성 스타일 HTML → 자체 CSS 보존, CJK 폰트만 보강
HTML(string=styled_html, base_url=".").write_pdf("out.pdf", stylesheets=[CSS(string=FONT_FACE)])

# (B) Markdown/JSON/Text에서 만든 HTML → 기본 문서 CSS + 폰트
HTML(string=plain_html, base_url=".").write_pdf("out.pdf", stylesheets=[CSS(string=FONT_FACE + BASE_CSS)])
```

`BASE_CSS`는 `@page { size:A4; margin:20mm }` + 본문/제목/표 스타일로, `font-family`에
`'Noto Sans CJK'`를 지정해 CJK가 항상 임베딩 폰트로 렌더되게 합니다.

> 이 스킬은 부재 스크립트(render_pdf.py·download_fonts.py) 참조를 원칙 A(프롬프트/인라인 코드 경로)로 대체했습니다 — 위 weasyprint 코드를 직접 실행하고, 폰트는 시스템 폴백을 1차 경로로 씁니다.

## 의존성

```bash
pip install weasyprint   # 핵심 렌더링 엔진 (풀 CSS → PDF)
pip install markdown     # Markdown → HTML 변환 (선택, 없으면 최소 폴백)
```

> weasyprint는 시스템 라이브러리 `cairo` / `pango` / `gdk-pixbuf`를 사용합니다. 대부분의
> Cowork 샌드박스 및 Linux/macOS 환경에 기본 포함돼 있어 `pip install weasyprint`만으로 동작합니다.
>
> **Windows에서 로컬 실행할 때는 이 라이브러리들이 기본 제공되지 않습니다.** `pip install weasyprint`가
> 성공해도 `import weasyprint` 단계에서 `cannot load library 'libpango...'`로 실패합니다. GTK 런타임을
> 먼저 설치해야 합니다 — 아래 §문제 해결의 Windows 행 참조. 설치가 어려운 환경이면 PDF 대신
> `moai-officer:doc-html-report` 산출물을 브라우저에서 "PDF로 인쇄"하는 경로를 안내하세요.

## 입력 포맷 명세

### 완성 HTML (디자인 보존)

`<html>`·`<body>`·`<!doctype html>` 신호가 있으면 **완성 문서로 간주**하여 자체 `<style>`·CSS를
그대로 렌더합니다. `moai-officer:doc-html-report`·`moai-officer:doc-html-slide` 산출물을 그대로 넘기면
화면과 동일한 디자인의 PDF가 나옵니다.

### 구조화 JSON 스펙

```json
{
  "title": "문서 제목",
  "subtitle": "부제목 (선택)",
  "author": "작성자 (선택)",
  "date": "2026-01-01 (선택)",
  "sections": [
    {
      "heading": "섹션 제목",
      "level": 2,
      "body": "본문 텍스트 (마크다운 인라인 지원)",
      "table": { "headers": ["열1", "열2"], "rows": [["a", "b"]] },
      "image": { "path": "./chart.png", "caption": "그림 1" }
    }
  ]
}
```

### Markdown 입력 예시

```markdown
# 프로젝트 보고서

## 1. 개요
본 보고서는 ...

## 2. 결과
| 항목 | 수치 |
|------|------|
| 완료율 | 95% |
```

## 사용 예시

- "이 HTML 리포트를 디자인 그대로 PDF로 만들어줘" → 완성 HTML 경로 (CSS 보존)
- "주간 보고서 Markdown을 한글 깨짐 없는 PDF로" → Markdown 경로 (기본 서식 + CJK)
- "아래 JSON 구조로 분기 보고서 PDF를 생성해줘" → JSON 경로
- "한·일·중·영 혼용 매뉴얼을 PDF로 출력해줘" → 다국어 임베딩

## 출력 형식

- **파일 형식**: `.pdf` (ISO 32000 PDF 표준)
- **페이지 크기**: A4 (210 × 297mm, 기본 여백 20mm) — 완성 HTML은 자체 `@page` 설정을 우선
- **폰트**: Noto Sans CJK (한국어·일본어·중국어 간체·번체·라틴 단일 패밀리 커버, 서브셋 임베딩)
- **인코딩**: UTF-8 완전 지원

## 자체 검수

PDF 생성 후 산출 `.pdf`를 다시 열어 **플레이스홀더 잔존·페이지 수 미달·CJK 인코딩 깨짐·디자인
누락**을 자체 점검하고, 문제가 있으면 자동 수정 후 재생성하며 최종 PASS/FAIL을 보고합니다.

## 문제 해결

| 증상 | 원인 | 해결 방법 |
|------|------|-----------|
| 한글/한자가 □□□ 또는 공백 | CJK 폰트 미해결 | 시스템 `Noto Sans CJK KR` 설치 (Windows: [Google Fonts에서 받아 우클릭 설치](https://fonts.google.com/noto/specimen/Noto+Sans+KR), macOS `brew install --cask font-noto-sans-cjk-kr`, Debian/Ubuntu `apt install fonts-noto-cjk`), 또는 `assets/fonts/`에 Noto Sans CJK OTF 배치 후 재렌더 |
| `ModuleNotFoundError: weasyprint` | weasyprint 미설치 | `pip install weasyprint` 실행 |
| `cannot load library 'libpango...'` **(Windows)** | GTK 런타임 부재 — Windows는 `pip install`만으로 해결되지 않음 | ① MSYS2 설치 후 **UCRT64 셸**에서 `pacman -S mingw-w64-ucrt-x86_64-pango` (WeasyPrint 공식 안내 기준 — MINGW64 셸의 `mingw-w64-x86_64-pango`가 아님), 그다음 `$env:WEASYPRINT_DLL_DIRECTORIES="C:\msys64\ucrt64\bin"`을 설정하고 **새 터미널**에서 재시도. DLL 경로를 지정하지 않으면 설치해도 Pango를 못 찾는다. ② 또는 [GTK for Windows Runtime 설치본](https://github.com/tschoonj/GTK-for-Windows-Runtime-Environment-Installer/releases). ③ 둘 다 어려우면 `moai-officer:doc-html-report`로 HTML을 만든 뒤 브라우저 "PDF로 인쇄"(Ctrl+P → 대상: PDF로 저장)로 대체 |
| `cannot load library 'libpango...'` (macOS/Linux) | 시스템 라이브러리 부재 | (Debian/Ubuntu) `apt install libpango-1.0-0 libpangocairo-1.0-0`, (macOS) `brew install pango` |
| HTML 디자인이 PDF에서 깨짐 | 완성 HTML이 조각으로 오인됨 | 입력에 `<html>`/`<body>` 래퍼 포함, 또는 `--in *.html` 파일로 전달 |
| 웹폰트(CDN) 미적용 | 오프라인/CDN 차단 | HTML에 폰트를 인라인하거나 시스템 폰트로 대체 (오프라인 PDF는 CDN 의존 회피 권장) |
| 표가 페이지 경계에서 분리 | CSS 미지정 | `tr { page-break-inside: avoid; }` 추가 |

## 관련 스킬 / 핸드오프

| 스킬 | 관계 | 사용 시점 |
|------|------|-----------|
| `moai-officer:doc-html-report` | before (HTML 리포트 생성 → 이 스킬로 PDF 변환) | "리포트 만들고 PDF로도" 흐름 |
| `moai-officer:doc-html-slide` | before (HTML 슬라이드 생성 → PDF 변환) | 슬라이드 덱을 PDF 배포본으로 |
| `moai-officer:doc-docx` | alternative (편집 가능한 Word 산출물) | 수신자가 편집 가능 파일 필요 시 |
| `moai-coworker:ai-slop-reviewer` | after (텍스트 산출물 AI 패턴 검수) | 텍스트 PDF 생성 전 원고 검수 |

## 기술 참조

- **weasyprint 공식 문서**: https://doc.courtbouillon.org/weasyprint/stable/
- **Noto Sans CJK GitHub**: https://github.com/notofonts/noto-cjk
- **Noto Sans CJK 릴리스 (otf 다운로드)**: https://github.com/notofonts/noto-cjk/releases
- **SIL OFL 1.1 라이선스**: https://openfontlicense.org/open-font-license-official-text/
- **PDF ISO 32000 표준**: https://opensource.adobe.com/dc-acrobat-sdk-docs/

