# DomPDF 사용 가이드 > **작성일**: 2026-03-11 > **패키지**: `barryvdh/laravel-dompdf` v3.1 (DomPDF v3.1.5) > **구현 참조**: `mng/app/Services/HR/PayrollService.php` --- ## 1. DomPDF 인스턴스 규칙 ### 1.1 폰트 등록은 반드시 렌더링할 인스턴스에 `Pdf::loadView()`는 **매번 새 DomPDF 인스턴스**를 생성한다. 다른 인스턴스에 폰트를 등록해도 렌더링 인스턴스에는 적용되지 않는다. ```php // ❌ 인스턴스 불일치 — 폰트가 적용되지 않음 $dompdf = app(\Barryvdh\DomPDF\PDF::class)->getDomPDF(); // 인스턴스 A $dompdf->getFontMetrics()->registerFont(...); $pdf = Pdf::loadView('view', $data); // 인스턴스 B (폰트 없음) $pdf->output(); // ✅ 동일 인스턴스에 등록 $pdf = Pdf::loadView('view', $data); $dompdf = $pdf->getDomPDF(); // loadView가 만든 바로 그 인스턴스 $dompdf->getFontMetrics()->registerFont(...); $pdf->output(); ``` ### 1.2 등록 → 렌더링 순서 ``` Pdf::loadView() → registerFont() → saveFontFamilies() → $pdf->output() ``` `output()` 호출 시 내부에서 `render()`가 실행되므로, 그 전에 폰트 등록이 완료되어야 한다. --- ## 2. setOptions() 사용 금지 ### 2.1 문제 `->setOptions([...])` 호출 시 DomPDF 내부에서 `new Options($options)`를 실행한다. 이때 **전달한 옵션만 설정되고 나머지는 DomPDF 기본값으로 초기화**된다. ```php // ❌ chroot, font_dir 등 config/dompdf.php 설정이 모두 초기화됨 $pdf = Pdf::loadView('view', $data) ->setOptions([ 'font_dir' => storage_path('fonts'), 'enable_font_subsetting' => true, ]); // 이 시점에서 chroot = vendor/dompdf/dompdf (DomPDF 기본값) ``` ### 2.2 해결 `config/dompdf.php`에 모든 설정을 선언하고, 코드에서 `setOptions()`를 호출하지 않는다. ```php // config/dompdf.php — 여기에 모든 설정 'options' => [ 'font_dir' => storage_path('fonts'), 'font_cache' => storage_path('fonts'), 'enable_font_subsetting' => true, 'chroot' => array_filter([ realpath(base_path()), realpath(storage_path('fonts')), ]), // ... ], // ✅ 코드에서는 setOptions 없이 사용 $pdf = Pdf::loadView('view', $data)->setPaper('a4'); ``` --- ## 3. chroot와 파일 경로 ### 3.1 chroot 검증 원리 DomPDF의 `validateLocalUri()`는 폰트 파일 접근 시 다음을 검사한다: ``` realpath(파일 경로)가 realpath(chroot) 하위인가? ``` **symlink는 realpath()로 해소**되므로, symlink 경로가 chroot 밖을 가리키면 차단된다. ### 3.2 릴리스 기반 배포 환경 ``` mng/current → releases/20260311_134148/ (배포마다 변경) releases/XXXXX/storage/fonts → ../../shared/storage/fonts/ (symlink) ``` - `storage_path('fonts')` → `/home/.../releases/XXXXX/storage/fonts` (symlink 경로) - `realpath()` → `/home/.../shared/storage/fonts` (실제 경로) - `base_path()` → `/home/.../releases/XXXXX/` (릴리스 경로) **shared 경로는 릴리스 경로 하위가 아니므로** chroot에 별도 등록이 필요하다. ### 3.3 chroot 설정 ```php // config/dompdf.php 'chroot' => array_filter([ realpath(base_path()), // 릴리스 내부 파일 허용 realpath(storage_path('fonts')), // shared 폰트 디렉토리 허용 ]), ``` `array_filter()`는 `realpath()`가 `false`를 반환할 경우(경로 미존재) 제거하기 위함이다. --- ## 4. 폰트 파일 경로 선택 ### 4.1 resource_path() vs storage_path() | 항목 | `resource_path()` | `storage_path()` | |------|-------------------|-------------------| | 릴리스 변경 시 | 경로 변경됨 | symlink → shared (불변) | | .ufm 캐시 | 매 배포마다 재생성 | 유지됨 | | installed-fonts.json | 경로 불일치로 무효화 | 안정적 | | Git 포함 | O (원본 보관용) | X (.gitignore) | **결론**: 폰트 등록 시 `storage_path()` 사용. 원본 TTF는 `resources/fonts/`에 Git으로 관리하고, 최초 실행 시 `storage/fonts/`로 복사한다. ### 4.2 폰트 복사 패턴 ```php $fontDir = storage_path('fonts'); $dst = $fontDir.'/Pretendard-Regular.ttf'; // 최초 1회만 복사 (이후 shared에 유지) if (! file_exists($dst)) { $src = resource_path('fonts/Pretendard-Regular.ttf'); if (! file_exists($src)) { return; } copy($src, $dst); } ``` --- ## 5. 외부 폰트 및 금지 사항 ### 5.1 구글 폰트 금지 ``` ❌ @import url('https://fonts.googleapis.com/...'); ❌ ❌ @font-face src: url('https://...'); ``` DomPDF는 웹 브라우저가 아니다. 외부 폰트 다운로드는 네트워크 의존성, 방화벽 차단, 성능 저하를 유발한다. ### 5.2 isRemoteEnabled 금지 ```php // ❌ 보안 위험 + 외부 의존성 ->setOptions(['isRemoteEnabled' => true]) ``` ### 5.3 font-weight 800 이상 사용 금지 Pretendard는 `normal`(400)과 `bold`(700)만 DomPDF에 등록되어 있다. `font-weight: 800` 이상을 지정하면 DomPDF가 매칭되는 폰트를 찾지 못해 해당 텍스트의 한글이 `?`로 깨진다. ```css /* ❌ DomPDF에서 한글 깨짐 — 800 weight에 매칭되는 폰트 없음 */ h1 { font-weight: 800; } /* ✅ bold(700)까지만 사용 */ h1 { font-weight: bold; } ``` ### 5.4 시스템 전용 폰트 단독 사용 금지 DomPDF는 OS 시스템 폰트를 자동 인식하지 않는다. `registerFont()`로 등록된 폰트만 사용 가능하다. ```css /* ❌ DomPDF가 인식 못함 → 한글 ??? */ body { font-family: 'Malgun Gothic', sans-serif; } /* ✅ DomPDF에 등록된 폰트 사용 */ body { font-family: 'Pretendard', 'Malgun Gothic', sans-serif; } ``` > `Malgun Gothic`은 브라우저에서 HTML을 직접 볼 때의 fallback 용도로만 기재한다. --- ## 6. PDF 경량화 설정 ### 6.1 폰트 서브셋팅 (필수) `config/dompdf.php`에서 `enable_font_subsetting`을 `true`로 설정한다. PDF에 실제 사용된 문자의 글리프만 포함하여 용량을 대폭 줄인다. ```php // config/dompdf.php 'options' => [ 'enable_font_subsetting' => true, // ✅ 필수 — 사용 글자만 임베딩 'enable_javascript' => false, // ✅ 권장 — PDF 내 JS 불필요 // ... ], ``` | 설정 | 변경 전 | 변경 후 | 효과 | |------|---------|---------|------| | `enable_font_subsetting` | `false` | `true` | 폰트 전체(~2-5MB) → 사용 글자만(수십KB) | | `enable_javascript` | `true` | `false` | PDF 내 JS 코드 제거 | > 한글 폰트는 11,172개의 완성형 글자를 포함하지만, 급여명세서에 사용되는 글자는 100~200자 수준이다. 서브셋팅으로 99% 이상의 불필요한 글리프를 제거한다. --- ## 7. 표준 PDF 생성 패턴 ### 7.1 전체 코드 ```php $pdf = Pdf::loadView('emails.payslip', ['payslipData' => $data]) ->setPaper('a4'); $this->registerKoreanFont($pdf); // 동일 인스턴스에 등록 $pdfContent = $pdf->output(); ``` ### 7.2 registerKoreanFont 구현 ```php private function registerKoreanFont(\Barryvdh\DomPDF\PDF $pdf): void { $fontDir = storage_path('fonts'); $normalDst = $fontDir.'/Pretendard-Regular.ttf'; $boldDst = $fontDir.'/Pretendard-Bold.ttf'; // resources → storage 복사 (최초 1회, 이후 shared에 유지) if (! file_exists($normalDst)) { $src = resource_path('fonts/Pretendard-Regular.ttf'); if (! file_exists($src)) { return; } if (! is_dir($fontDir)) { mkdir($fontDir, 0755, true); } copy($src, $normalDst); } if (! file_exists($boldDst)) { $src = resource_path('fonts/Pretendard-Bold.ttf'); if (file_exists($src)) { copy($src, $boldDst); } } $dompdf = $pdf->getDomPDF(); $fm = $dompdf->getFontMetrics(); $fm->registerFont( ['family' => 'pretendard', 'style' => 'normal', 'weight' => 'normal'], $normalDst ); if (file_exists($boldDst)) { $fm->registerFont( ['family' => 'pretendard', 'style' => 'normal', 'weight' => 'bold'], $boldDst ); } $fm->saveFontFamilies(); } ``` --- ## 8. 폰트 캐시 구조 ``` storage/fonts/ ← shared 디렉토리 (배포 간 유지) ├── installed-fonts.json ← DomPDF 폰트 레지스트리 ├── Pretendard-Regular.ttf ← resources/에서 복사된 원본 ├── Pretendard-Bold.ttf ├── pretendard_normal_*.ufm ← DomPDF 메트릭 캐시 (자동 생성) ├── pretendard_normal_*.ttf ← DomPDF 서브셋 (자동 생성) └── pretendard_bold_*.* ``` > `storage/fonts/`는 `.gitignore`에 포함되어 있다. 각 환경에서 첫 PDF 생성 시 자동으로 생성된다. --- ## 9. 체크리스트 ### PDF 뷰 작성 시 - [ ] 외부 폰트 URL 미포함 (`@import`, ``) - [ ] `font-family`에 `Pretendard` 포함 (DomPDF 한글 지원) - [ ] `font-family` fallback에 `Malgun Gothic`, `sans-serif` 포함 (브라우저용) - [ ] `font-weight`는 `normal`/`bold`만 사용 (800 이상 금지) ### PDF 생성 코드 작성 시 - [ ] `setOptions()` 미사용 (config/dompdf.php에 선언) - [ ] `Pdf::loadView()` 후 **동일 인스턴스**에 폰트 등록 - [ ] 폰트 경로는 `storage_path()` 사용 (`resource_path()` 아님) - [ ] `isRemoteEnabled` 미사용 ### 배포 환경 확인 - [ ] `config/dompdf.php` chroot에 `realpath(storage_path('fonts'))` 포함 - [ ] `storage/fonts/` 디렉토리 쓰기 권한 확인 - [ ] 폰트 TTF 파일이 `resources/fonts/`에 Git 관리됨 - [ ] `enable_font_subsetting` → `true` --- ## 관련 문서 - DomPDF 설정: `mng/config/dompdf.php` - 구현 참조: `mng/app/Services/HR/PayrollService.php` — `registerKoreanFont()` - 폰트 원본: `mng/resources/fonts/` — Pretendard TTF (Git 관리) - 서버 운영: `dev/deploys/ops-manual/README.md` --- **최종 업데이트**: 2026-03-11