# [FIX] PDF 변환 시 이미지 누락 문제 해결

**날짜**: 2026-02-09
**수정 파일**: `src/components/document-system/viewer/DocumentViewer.tsx`
**영향 범위**: DocumentViewer를 사용하는 모든 문서 (공통 수정)

---

## 문제

중간검사성적서 등 문서에서 **도해 이미지**가 화면에는 정상 표시되지만, **PDF 다운로드 시 이미지가 누락**되는 현상.

## 원인 분석

### PDF 생성 흐름

```
1. DocumentViewer.handlePdf()
2. .print-area DOM 요소를 cloneWithInlineStyles()로 복제
3. clone.outerHTML → HTML 문자열 추출
4. /api/pdf/generate POST 전송
5. Puppeteer가 page.setContent(html) → PDF 렌더링
```

### 핵심 원인: `setContent()`의 base URL 부재

Puppeteer의 `page.setContent(html)`은 **로컬 HTML 문자열을 렌더링**하므로 base URL이 없다.

| 이미지 src 유형 | 브라우저 (화면) | Puppeteer (PDF) |
|----------------|----------------|-----------------|
| 상대 경로 `/uploads/img.jpg` | `localhost:3000` 기준으로 해석 | base URL 없음 → 로드 실패 |
| 절대 URL `https://api.example.com/img.jpg` | 정상 로드 | 서버 네트워크 환경에 따라 실패 가능 |
| Blob URL `blob:http://...` | 브라우저 메모리 참조 | Puppeteer 컨텍스트에 없음 → 로드 실패 |
| Data URL `data:image/png;base64,...` | 정상 | 정상 (HTML에 내장) |

**결론**: `<img src="...">` 가 상대/절대/blob URL이면 Puppeteer PDF에서 이미지가 빠진다.

## 해결 방법

### 접근: 이미지를 base64 data URL로 인라인 변환

PDF API로 HTML을 보내기 **전에**, 모든 `<img>` 태그의 `src`를 **base64 data URL로 변환**하여 HTML 자체에 이미지를 내장시킨다.

### 구현: `convertImagesToBase64()`

```typescript
// DocumentViewer.tsx에 추가
const convertImagesToBase64 = async (original: HTMLElement, clone: HTMLElement): Promise<void> => {
  const originalImages = Array.from(original.querySelectorAll('img'));
  const clonedImages = Array.from(clone.querySelectorAll('img'));

  await Promise.all(
    originalImages.map(async (img, index) => {
      const clonedImg = clonedImages[index];
      if (!clonedImg) return;

      const src = img.src;
      if (!src || src.startsWith('data:')) return; // 이미 data URL이면 스킵

      try {
        // 1차: fetch → blob → FileReader → dataURL
        const response = await fetch(src);
        const blob = await response.blob();
        const dataUrl = await new Promise<string>((resolve, reject) => {
          const reader = new FileReader();
          reader.onloadend = () => resolve(reader.result as string);
          reader.onerror = reject;
          reader.readAsDataURL(blob);
        });
        clonedImg.setAttribute('src', dataUrl);
      } catch {
        // 2차 fallback: canvas 방식
        try {
          if (img.complete && img.naturalWidth > 0) {
            const canvas = document.createElement('canvas');
            canvas.width = img.naturalWidth;
            canvas.height = img.naturalHeight;
            const ctx = canvas.getContext('2d');
            if (ctx) {
              ctx.drawImage(img, 0, 0);
              clonedImg.setAttribute('src', canvas.toDataURL('image/png'));
            }
          }
        } catch {
          // 변환 실패 시 원본 src 유지
        }
      }
    })
  );
};
```

### 호출 위치: `handlePdf()` 내부

```typescript
// 변경 전
const clonedElement = cloneWithInlineStyles(printAreaEl);
const html = clonedElement.outerHTML;

// 변경 후
const clonedElement = cloneWithInlineStyles(printAreaEl);
await convertImagesToBase64(printAreaEl, clonedElement);  // 이미지 인라인 변환
const html = clonedElement.outerHTML;
```

## 영향 범위 (공통 적용)

`DocumentViewer`가 모든 문서의 PDF 생성을 담당하므로, **1곳 수정으로 전체 해결**:

| 화면 | 이미지 필드 | 적용 |
|------|------------|------|
| 스크린 중간검사 | `schematicImage` (도해) | 자동 적용 |
| 슬랫 중간검사 | `schematicImage` (도해) | 자동 적용 |
| 조인트바 중간검사 | `schematicImage` (도해) | 자동 적용 |
| 절곡 중간검사 | `schematicImage` (도해) | 자동 적용 |
| 절곡 재공품 중간검사 | `schematicImage` (도해) | 자동 적용 |
| 제품검사 성적서 (품질) | `productImages` | 자동 적용 |
| 기타 DocumentViewer 문서 | 모든 `<img>` | 자동 적용 |

## 변환 전략 우선순위

```
1. data URL → 스킵 (이미 인라인)
2. fetch → blob → FileReader → dataURL (동일 출처, CORS 허용)
3. canvas drawImage → toDataURL (로컬 이미지 fallback)
4. 실패 시 원본 src 유지 (graceful degradation)
```

---

## 미해결: 도해 이미지 데이터 파이프라인 (2026-02-09 확인)

### 현재 상황

PDF 변환(파이프 끝)은 수정 완료했으나, **데이터 주입(파이프 시작)이 연결되지 않은 상태**.

```
[백엔드 설정 사이트]          [sam-api]              [프론트엔드]
 도해 이미지 업로드    →   inspection_setting?  →   inspectionSetting
 검사기준 설정               (❌ 미구현)            schematicImage
                                                        ↓
                                                   DocumentViewer
                                                        ↓
                                                   PDF (base64 변환 ✅)
```

### 백엔드 API 확인 결과

| 항목 | 상태 |
|------|------|
| `ProcessStep` 테이블에 `inspection_setting` 컬럼 | ❌ 없음 |
| `ApiProcessStep` → `inspectionSetting` 매핑 | ❌ 없음 |
| 검사 설정 위치 | Process 레벨 `DocumentTemplate`으로 최근(2/10~11) 이동 |
| 도해 이미지 URL 필드 | ❌ 백엔드에 미정의 |

### 프론트 vs 백엔드 구조 불일치

| 항목 | 프론트엔드 기대 | 백엔드 실제 |
|------|----------------|-------------|
| 위치 | `step.inspectionSetting` | `process.documentTemplate` |
| 구조 | `{ standardName, schematicImage, appearance, dimension }` | DocumentTemplate (sections, fields) |
| 이미지 | `schematicImage` 직접 필드 | 없음 |

### 프론트 누락 코드 (합치고 나서 수정 필요)

**`src/components/process-management/actions.ts`**:
- `ApiProcessStep` 인터페이스에 `inspection_setting` 필드 미정의 (line 582)
- `transformStepApiToFrontend()`에서 `inspectionSetting` 매핑 안 함 (line 599)

### 해결 방향

백엔드 머지 후 아래 중 하나로 진행:

| 옵션 | 내용 | 수정 위치 |
|------|------|----------|
| A | ProcessStep에 `inspection_setting` JSON 컬럼 추가 | 백엔드 |
| B | API에서 DocumentTemplate → InspectionSetting 변환하여 내려주기 | 백엔드 |
| C | 프론트에서 `process.documentTemplate` 직접 사용하도록 변경 | 프론트 |

**백엔드 담당자와 구조 협의 필요.**
