diff --git a/dev/standards/pdf-font-policy.md b/dev/standards/pdf-font-policy.md
index 4c705f4..e3b5182 100644
--- a/dev/standards/pdf-font-policy.md
+++ b/dev/standards/pdf-font-policy.md
@@ -9,7 +9,7 @@
### 1.1 목적
-DomPDF로 PDF를 생성할 때 폰트 관련 문제(권한 오류, 외부 의존성, 배포 시 재발)를 방지하기 위한 정책이다.
+DomPDF로 PDF를 생성할 때 폰트 관련 문제(권한 오류, 외부 의존성, 배포 시 재발, 한글 깨짐)를 방지하기 위한 정책이다.
### 1.2 배경 — 운영서버 장애 사례
@@ -25,8 +25,10 @@ Failed to open stream: Permission denied
```
❌ PDF 뷰에서 구글 폰트(@import, ) 사용 금지
❌ DomPDF의 isRemoteEnabled 옵션 사용 금지
-✅ 시스템 기본 폰트만 사용
-✅ 폰트가 필요하면 로컬 설치 후 DomPDF에 등록
+❌ font-family에 'Malgun Gothic' 등 시스템 전용 폰트 단독 사용 금지 (DomPDF가 인식 못함)
+✅ NanumGothic을 DomPDF에 등록하여 사용
+✅ 폰트 등록은 storage/fonts/에 캐시 (vendor/ 아님)
+✅ 자동 등록 패턴(ensureKoreanFont) 적용
```
---
@@ -38,7 +40,7 @@ Failed to open stream: Permission denied
```
❌ @import url('https://fonts.googleapis.com/...');
❌
-❌ font-face src: url('https://...');
+❌ @font-face src: url('https://...');
```
### 2.2 isRemoteEnabled 옵션 금지
@@ -47,10 +49,15 @@ Failed to open stream: Permission denied
// ❌ 금지 — 외부 리소스 다운로드를 활성화하면 안 됨
$pdf = Pdf::loadView('view', $data)
->setOptions(['isRemoteEnabled' => true]);
+```
-// ✅ 올바른 사용
-$pdf = Pdf::loadView('view', $data)
- ->setPaper('a4');
+### 2.3 DomPDF 미등록 폰트 사용 금지
+
+DomPDF는 시스템 폰트를 자동으로 인식하지 않는다. `Malgun Gothic`, `Apple SD Gothic Neo` 등을 `font-family`에 지정해도 DomPDF가 찾지 못해 한글이 `???`로 깨진다.
+
+```css
+/* ❌ DomPDF에서 한글 깨짐 */
+body { font-family: 'Malgun Gothic', sans-serif; }
```
---
@@ -59,7 +66,7 @@ $pdf = Pdf::loadView('view', $data)
### 3.1 배포 시 권한 문제 재발
-`composer install`이 `vendor/`를 새로 생성하면 폰트 캐시 디렉토리 권한이 초기화된다. Jenkinsfile에 매번 권한 설정을 추가해야 하는데, `vendor/` 내부 특정 경로에 권한을 거는 것은 안티패턴이다.
+`composer install`이 `vendor/`를 새로 생성하면 폰트 캐시 디렉토리 권한이 초기화된다. `vendor/` 내부 특정 경로에 권한을 거는 것은 안티패턴이다.
```
배포 → composer install → vendor/ 재생성 → 폰트 캐시 권한 초기화 → PDF 생성 실패
@@ -71,45 +78,114 @@ PDF를 생성할 때마다 `fonts.googleapis.com`에 요청한다. 외부 서버
### 3.3 PDF에서 웹폰트 불필요
-DomPDF는 웹 브라우저가 아니다. 구글 폰트를 다운로드 → 파싱 → 캐싱하는 과정이 불필요한 오버헤드다. 시스템 기본 폰트로 충분하다.
+DomPDF는 웹 브라우저가 아니다. 구글 폰트를 다운로드 → 파싱 → 캐싱하는 과정이 불필요한 오버헤드다.
---
## 4. 올바른 폰트 사용법
-### 4.1 시스템 기본 폰트 사용 (권장)
+### 4.1 표준 한글 폰트: NanumGothic
+
+SAM 프로젝트의 PDF 한글 폰트는 **NanumGothic**을 사용한다. 로컬(Docker)과 서버(Bare-metal) 모두 `fonts-nanum` 패키지가 설치되어 있다.
+
+| 환경 | 폰트 경로 | 설치 방법 |
+|------|----------|----------|
+| 로컬 (Docker) | `/usr/share/fonts/truetype/nanum/` | Dockerfile에 포함 |
+| 개발/운영 서버 | `/usr/share/fonts/truetype/nanum/` | `sudo apt install fonts-nanum` |
+
+### 4.2 Blade 뷰 font-family 지정
```css
-/* PDF 뷰(Blade)에서 사용할 font-family */
+/* ✅ 올바른 PDF 뷰 폰트 지정 */
body {
- font-family: 'Malgun Gothic', 'Apple SD Gothic Neo', sans-serif;
+ font-family: 'NanumGothic', 'Malgun Gothic', sans-serif;
}
```
-### 4.2 PDF 생성 코드 패턴
+> `NanumGothic`이 DomPDF에서 사용되고, `Malgun Gothic`은 브라우저에서 HTML을 직접 볼 때의 fallback이다.
+
+### 4.3 자동 폰트 등록 패턴 (ensureKoreanFont)
+
+PDF를 생성하는 서비스에 다음 패턴을 적용한다. 최초 1회만 등록하고 이후는 캐시에서 로드한다.
+
+```php
+/**
+ * DomPDF에 한글(NanumGothic) 폰트 등록 (최초 1회만 실행)
+ */
+private function ensureKoreanFont(): void
+{
+ $fontDir = storage_path('fonts');
+ $installedFile = $fontDir.'/installed-fonts.json';
+
+ // 이미 등록되어 있으면 스킵
+ if (file_exists($installedFile)) {
+ $installed = json_decode(file_get_contents($installedFile), true) ?: [];
+ if (isset($installed['nanumgothic'])) {
+ return;
+ }
+ }
+
+ // 시스템에 설치된 NanumGothic 찾기
+ $fontPaths = [
+ 'normal' => '/usr/share/fonts/truetype/nanum/NanumGothic.ttf',
+ 'bold' => '/usr/share/fonts/truetype/nanum/NanumGothicBold.ttf',
+ ];
+
+ if (! file_exists($fontPaths['normal'])) {
+ return;
+ }
+
+ if (! is_dir($fontDir)) {
+ mkdir($fontDir, 0755, true);
+ }
+
+ // storage/fonts/에 복사 후 DomPDF에 등록
+ foreach ($fontPaths as $weight => $src) {
+ $dst = $fontDir.'/'.basename($src);
+ if (! file_exists($dst)) {
+ copy($src, $dst);
+ }
+ }
+
+ $dompdf = app(\Barryvdh\DomPDF\PDF::class)->getDomPDF();
+ $fm = $dompdf->getFontMetrics();
+ $fm->registerFont(
+ ['family' => 'nanumgothic', 'style' => 'normal', 'weight' => 'normal'],
+ $fontDir.'/NanumGothic.ttf'
+ );
+ $fm->registerFont(
+ ['family' => 'nanumgothic', 'style' => 'normal', 'weight' => 'bold'],
+ $fontDir.'/NanumGothicBold.ttf'
+ );
+ $fm->saveFontFamilies();
+}
+```
+
+### 4.4 PDF 생성 코드 패턴
```php
// ✅ 올바른 PDF 생성 패턴
-$pdf = Pdf::loadView('emails.payslip', ['data' => $data])
+$this->ensureKoreanFont();
+
+$pdf = Pdf::loadView('emails.payslip', ['payslipData' => $payslipData])
->setPaper('a4');
$pdfContent = $pdf->output();
```
-### 4.3 한글 폰트가 필요한 경우 — 로컬 설치
+### 4.5 폰트 캐시 구조
-시스템 기본 폰트로 부족하면 서버에 폰트를 직접 설치하고 DomPDF에 등록한다.
-
-```bash
-# 1. 서버에 폰트 설치 (Level 2 — 사용자 확인 후 실행)
-sudo apt install fonts-noto-cjk
-
-# 2. DomPDF 폰트 등록 (php artisan 명령)
-php artisan dompdf:font "Noto Sans KR" \
- /usr/share/fonts/opentype/noto/NotoSansCJK-Regular.ttc
+```
+storage/fonts/
+├── installed-fonts.json ← DomPDF 폰트 레지스트리
+├── NanumGothic.ttf ← 시스템에서 복사된 원본
+├── NanumGothicBold.ttf
+├── nanumgothic_normal_*.ufm ← DomPDF가 생성한 메트릭 캐시
+├── nanumgothic_normal_*.ttf ← DomPDF가 생성한 서브셋
+└── nanumgothic_bold_*.*
```
-> 로컬 설치된 폰트는 `vendor/` 재생성과 무관하게 유지된다.
+> `storage/fonts/`는 `.gitignore`에 포함되어 있다. 각 환경에서 첫 PDF 생성 시 자동으로 생성된다.
---
@@ -117,23 +193,22 @@ php artisan dompdf:font "Noto Sans KR" \
구글 폰트를 사용하는 기존 PDF 뷰를 발견하면 다음 절차로 수정한다.
-### 5.1 Blade 뷰에서 제거
+### 5.1 Blade 뷰 수정
```html
```
-### 5.2 서비스/컨트롤러에서 제거
+### 5.2 서비스/컨트롤러 수정
```php
// ❌ 수정 전
@@ -142,15 +217,23 @@ $pdf = Pdf::loadView('emails.payslip', ['payslipData' => $payslipData])
->setPaper('a4');
// ✅ 수정 후
+$this->ensureKoreanFont();
$pdf = Pdf::loadView('emails.payslip', ['payslipData' => $payslipData])
->setPaper('a4');
```
+### 5.3 적용 사례
+
+| 파일 | 수정 내용 |
+|------|----------|
+| `resources/views/emails/payslip.blade.php` | `@import` 구글 폰트 삭제, `font-family` NanumGothic 적용 |
+| `app/Services/HR/PayrollService.php` | `isRemoteEnabled` 제거, `ensureKoreanFont()` 추가 |
+
---
## 6. 긴급 복구 — 운영서버 권한 오류 발생 시
-이미 구글 폰트를 사용하는 코드가 배포되어 권한 오류가 발생한 경우의 즉시 조치이다. 근본 수정(구글 폰트 제거)을 반드시 병행한다.
+이미 구글 폰트를 사용하는 코드가 배포되어 권한 오류가 발생한 경우의 즉시 조치이다. 근본 수정(구글 폰트 제거 + ensureKoreanFont 적용)을 반드시 병행한다.
```bash
# Level 2 작업 — 사용자 확인 후 실행
@@ -159,7 +242,7 @@ sudo chown -R www-data:webservice /home/webservice/mng/current/vendor/dompdf/dom
sudo chmod -R 775 /home/webservice/mng/current/vendor/dompdf/dompdf/lib/fonts/
```
-> 이 조치는 임시이다. 재배포 시 `vendor/`가 새로 생성되면 다시 발생한다.
+> 이 조치는 임시이다. 재배포 시 `vendor/`가 새로 생성되면 다시 발생한다. 반드시 `ensureKoreanFont` 패턴을 적용해야 근본 해결된다.
---
@@ -169,25 +252,34 @@ sudo chmod -R 775 /home/webservice/mng/current/vendor/dompdf/dompdf/lib/fonts/
- [ ] `@import url('https://fonts.googleapis.com/...')` 미포함
- [ ] `` 미포함
-- [ ] `font-family`에 구글 폰트명 미포함
-- [ ] 시스템 기본 폰트 사용 (`Malgun Gothic`, `sans-serif`)
+- [ ] `font-family`에 `NanumGothic` 포함 (DomPDF 한글 지원)
+- [ ] `font-family` fallback에 `Malgun Gothic`, `sans-serif` 포함 (브라우저용)
### PDF 생성 코드 작성 시
- [ ] `isRemoteEnabled` 옵션 미사용
+- [ ] `ensureKoreanFont()` 호출 후 PDF 생성
- [ ] `Pdf::loadView()->setPaper()` 패턴 사용
### 코드 리뷰 시
- [ ] PDF 관련 Blade 뷰에 외부 폰트 URL 없음
- [ ] DomPDF 옵션에 `isRemoteEnabled` 없음
+- [ ] 한글 PDF에 `ensureKoreanFont()` 호출 있음
+
+### 서버 환경 확인
+
+- [ ] `fonts-nanum` 패키지 설치 확인 (`fc-list :lang=ko | grep Nanum`)
+- [ ] `storage/fonts/` 디렉토리 쓰기 권한 확인
+- [ ] 미설치 시: `sudo apt install fonts-nanum`
---
## 관련 문서
- 서버 운영 매뉴얼: `dev/deploys/ops-manual/README.md`
-- DomPDF 패키지: `barryvdh/laravel-dompdf`
+- DomPDF 패키지: `barryvdh/laravel-dompdf` v3.1
+- 구현 참조: `mng/app/Services/HR/PayrollService.php` — `ensureKoreanFont()`
---