# 문서 뷰어 아키텍처 설계

> 품질인정심사 1일차 기준/매뉴얼 문서 및 범용 문서 뷰어 구현 방안

## 1. 배경

품질인정심사 시스템의 1일차(기준/매뉴얼 심사)에서 작업표준서, 검사기준서 등 다양한 포맷의 문서를 웹에서 바로 열람해야 함. 문서 포맷은 HWP, HWPX, Excel, PDF 등 다양하며, 수정 없이 읽기 전용으로 표시하는 것이 목적.

### 적용 대상
- 품질인정심사 1일차: 스크린 작업표준서, 검사기준서 등
- 향후 확장: ERP 전반의 문서 열람 기능

---

## 2. 기술 검토 결과

### 2.1 HWP 웹 뷰어 현황

| 방식 | HWP | HWPX | PDF | Excel | 비용 | 비고 |
|------|:---:|:----:|:---:|:-----:|------|------|
| LibreOffice headless | ✅ | ✅ | - | ✅ | 무료 | H2Orestart 확장 필요 |
| 한컴 공식 API | ✅ | ✅ | ✅ | ✅ | 엔터프라이즈 | DocsConverter SDK |
| CloudConvert API | ✅ | ✅ | ✅ | ✅ | 월 250건 무료 | 클라우드 의존 |
| hwp.js (클라이언트) | △ | ❌ | ❌ | ❌ | 무료 | HWP 스펙 ~20% 구현, 사실상 폐기 |
| @ssabrojs/hwpxjs | ❌ | ✅ | ❌ | ❌ | 무료 | HWPX만 지원 |

- **웨일 브라우저**: 네이티브 한글 뷰어 내장 (브라우저 바이너리에 포함). Chrome에서 재현 불가.
- **Google Docs Viewer**: HWP 미지원.
- **순수 클라이언트 HWP 렌더링**: 신뢰할 수 있는 라이브러리 없음 (2026년 기준).

### 2.2 결론

순수 클라이언트에서 HWP를 직접 렌더링하는 것은 현실적으로 불가능. **서버사이드 PDF 변환 후 프론트에서 PDF 표시**가 유일한 프로덕션 옵션.

---

## 3. 채택 아키텍처: 서버사이드 변환 + react-pdf

### 3.1 전체 흐름

```
┌─────────────┐     ┌──────────────────┐     ┌─────────────────┐
│  프론트엔드   │     │  Laravel 백엔드    │     │  파일 스토리지    │
│  (Next.js)   │     │  (PHP)            │     │  (S3/로컬)       │
└──────┬───────┘     └────────┬──────────┘     └────────┬────────┘
       │                      │                         │
       │  1. 문서 열람 요청    │                         │
       │─────────────────────>│                         │
       │                      │  2. 원본 파일 조회       │
       │                      │────────────────────────>│
       │                      │<────────────────────────│
       │                      │                         │
       │                      │  3. PDF 변환 (캐싱)     │
       │                      │  ┌───────────────────┐  │
       │                      │  │ LibreOffice        │  │
       │                      │  │ --headless         │  │
       │                      │  │ --convert-to pdf   │  │
       │                      │  └───────────────────┘  │
       │                      │                         │
       │                      │  4. 변환된 PDF 저장     │
       │                      │────────────────────────>│
       │                      │                         │
       │  5. PDF URL 반환      │                         │
       │<─────────────────────│                         │
       │                      │                         │
       │  6. react-pdf로 표시  │                         │
       │  (DocumentViewer 모달)│                         │
```

### 3.2 백엔드 (Laravel)

#### 변환 방식 A: LibreOffice headless (무료, 자체 호스팅)

```bash
# 서버 설치
sudo apt install libreoffice

# H2Orestart 확장 설치 (HWP 필터 지원)
# https://extensions.libreoffice.org/en/extensions/show/27504

# 변환 커맨드
libreoffice --headless --infilter="Hwp2002_File" --convert-to pdf:writer_pdf_Export input.hwp
libreoffice --headless --convert-to pdf:writer_pdf_Export input.hwpx
libreoffice --headless --convert-to pdf:writer_pdf_Export input.xlsx
```

```php
// Laravel Controller 예시
class DocumentConvertController extends Controller
{
    public function convert(Request $request, string $documentId)
    {
        $document = Document::findOrFail($documentId);
        $originalPath = $document->file_path;
        $pdfPath = $this->getCachedPdfPath($document);

        // 캐시된 PDF가 있으면 바로 반환
        if (Storage::exists($pdfPath)) {
            return response()->json(['pdf_url' => Storage::url($pdfPath)]);
        }

        // LibreOffice로 변환
        $outputDir = storage_path('app/converted');
        $command = sprintf(
            'libreoffice --headless --convert-to pdf --outdir %s %s',
            escapeshellarg($outputDir),
            escapeshellarg(Storage::path($originalPath))
        );
        exec($command, $output, $returnCode);

        if ($returnCode !== 0) {
            return response()->json(['error' => '변환 실패'], 500);
        }

        // 변환된 PDF 저장 및 URL 반환
        return response()->json(['pdf_url' => Storage::url($pdfPath)]);
    }
}
```

#### 변환 방식 B: 한컴 API (유료, 높은 정확도)

```php
// 한컴 DocsConverter API 활용
class HancomConvertController extends Controller
{
    public function convert(Request $request, string $documentId)
    {
        $document = Document::findOrFail($documentId);

        // 한컴 DocsConverter SDK 호출
        // https://developer.hancom.com/docsconverter
        $result = HancomSDK::convert($document->file_path, 'pdf');

        return response()->json(['pdf_url' => $result->getPdfUrl()]);
    }
}
```

#### API 엔드포인트 설계

```
GET  /api/documents/{id}/preview    → PDF URL 반환 (변환 필요시 자동 변환)
POST /api/documents/upload          → 원본 파일 업로드
GET  /api/documents/{id}/download   → 원본 파일 다운로드
```

#### PDF 캐싱 전략

```
원본 파일 업로드 시점 또는 최초 열람 시점에 PDF 변환
  → converted/{document_id}.pdf로 캐싱
  → 원본 파일 변경 시 캐시 무효화 (파일 해시 비교)
  → 이미 PDF인 파일은 변환 없이 바로 반환
```

### 3.3 프론트엔드 (Next.js)

#### 라이브러리

```bash
npm install react-pdf
# 또는
npm install @react-pdf-viewer/core @react-pdf-viewer/default-layout
```

#### 구현 구조

```
품질인정심사 1일차
├── 점검표 항목 (좌측 패널)
├── 기준 문서화 (우측 패널)
│   └── 문서 클릭 시 → DocumentViewer 모달로 PDF 표시
│       ├── 기존 DocumentViewer 모달 재사용 (zoom/drag/print/PDF)
│       └── 내부에 react-pdf PDF 렌더러 삽입
```

```tsx
// 개념 코드 (실제 구현은 기획서 확정 후)
import { Document, Page } from 'react-pdf';

function PdfDocumentViewer({ pdfUrl }: { pdfUrl: string }) {
  const [numPages, setNumPages] = useState(0);

  return (
    <Document
      file={pdfUrl}
      onLoadSuccess={({ numPages }) => setNumPages(numPages)}
    >
      {Array.from({ length: numPages }, (_, i) => (
        <Page key={i} pageNumber={i + 1} />
      ))}
    </Document>
  );
}
```

#### 모달 통합

```
기존 DocumentViewer 모달 (2일차에서 사용 중)
├── 헤더: 문서명 + 날짜
├── 툴바: 축소/확대/맞춤/PDF/인쇄/다운로드
├── 본문:
│   ├── 기존: React 컴포넌트 렌더링 (수주서, 성적서 등)
│   └── 추가: PDF 렌더링 (업로드된 HWP/Excel/기타 문서)
```

---

## 4. 지원 포맷별 처리

| 파일 포맷 | 처리 방식 | 변환 필요 |
|-----------|----------|:---------:|
| PDF | 바로 표시 | ❌ |
| HWP | LibreOffice + H2Orestart → PDF | ✅ |
| HWPX | LibreOffice → PDF | ✅ |
| Excel (.xlsx/.xls) | LibreOffice → PDF | ✅ |
| Word (.docx/.doc) | LibreOffice → PDF | ✅ |
| PowerPoint (.pptx) | LibreOffice → PDF | ✅ |
| 이미지 (.png/.jpg) | 바로 표시 (img 태그) | ❌ |

---

## 5. 서버 환경 요구사항

### LibreOffice 방식

```yaml
# Docker 예시
FROM php:8.2-fpm
RUN apt-get update && apt-get install -y libreoffice default-jre
# H2Orestart 확장 설치
COPY h2orestart.oxt /tmp/
RUN libreoffice --headless --norestore --nofirststartwizard \
    "macro:///Tools.Install.installOxt(/tmp/h2orestart.oxt)"
```

| 항목 | 요구사항 |
|------|---------|
| LibreOffice | 7.x 이상 |
| JRE | 8 이상 (H2Orestart용) |
| 디스크 | LibreOffice ~500MB + 변환 캐시 |
| 메모리 | 변환당 ~200-500MB |
| 주의사항 | LibreOffice는 thread-safe하지 않음 → 동시 변환 시 큐 처리 필요 |

### 한컴 API 방식

| 항목 | 요구사항 |
|------|---------|
| 라이선스 | 엔터프라이즈 B2B (한컴 영업팀 문의) |
| SDK | DocsConverter SDK (서버 설치) |
| 장점 | HWP 변환 정확도 최고 |
| 단점 | 비용, 라이선스 관리 |

---

## 6. 1일차 UI 변경 사항 (요약)

### 현재 구조 (3컬럼)
```
[ 점검표 항목 ] [ 기준 문서화 ] [ 스크린 작업표준서 뷰어 ]
```

### 변경 후 구조 (2컬럼 + 모달)
```
[ 점검표 항목 ] [ 기준 문서화 ]
                      │
                      └── 문서 클릭 → DocumentViewer 모달 (PDF 표시)
```

- 우측 `Day1DocumentViewer` 패널 제거
- 기준 문서화에서 문서 클릭 시 2일차와 동일한 `DocumentViewer` 모달로 표시
- 모달 내부에서 서버 변환된 PDF를 `react-pdf`로 렌더링

---

## 7. 구현 우선순위

1. **백엔드 API**: 문서 업로드 + LibreOffice PDF 변환 + 캐싱
2. **프론트엔드**: react-pdf 통합 + DocumentViewer 모달 연동
3. **1일차 UI**: 3컬럼 → 2컬럼 레이아웃 변경 + 모달 연동
4. **선택사항**: 한컴 API 도입 (LibreOffice 변환 품질 불만족 시)

---

## 8. 참고 자료

- [LibreOffice H2Orestart 확장](https://extensions.libreoffice.org/en/extensions/show/27504) - HWP 필터 지원
- [react-pdf](https://www.npmjs.com/package/react-pdf) - PDF 렌더링
- [@react-pdf-viewer](https://react-pdf-viewer.dev/) - PDF 뷰어 (줌/검색/썸네일)
- [한컴 DocsConverter](https://developer.hancom.com/docsconverter) - 한컴 공식 변환 SDK
- [CloudConvert HWP to PDF](https://cloudconvert.com/hwp-to-pdf) - 클라우드 변환 API (월 250건 무료)
- [hwp.js GitHub](https://github.com/hahnlee/hwp.js) - 클라이언트 HWP 파서 (참고용, 프로덕션 부적합)
- [@ssabrojs/hwpxjs](https://www.npmjs.com/package/@ssabrojs/hwpxjs) - HWPX 파서 (참고용)
