# Step 5: Canvas 그리기 기능 (5130 → MNG 적용)

> **프로젝트**: MNG (`sam/mng`)
> **선행 조건**: Step 3 (MNG 화면) 완료
> **참조**: 레거시 `5130/js/imageEditor.js`, `5130/js/drawingModule.js`
> **상태**: ⬜ 분석 완료, 구현 미착수

---

## 1. 레거시 Canvas 분석 결과

### 1-1. 5130 Canvas 구현체 3개

| 파일 | 크기 | 라이브러리 | 용도 | 채택 여부 |
|------|------|-----------|------|:---:|
| `5130/js/imageEditor.js` | ~511줄 | **Fabric.js 5.3.0** | 프로덕션 이미지 에디터 (모달 dialog) | ✅ **채택** |
| `5130/js/drawingModule.js` | ~966줄 | Pure Canvas 2D | 독립 모달 + 전체 UI 포함 | ❌ 중복 |
| `5130/js/drawLib.js` | ~272줄 | Pure Canvas 2D | 경량 버전 | ❌ 기능 부족 |

**채택 이유**: `imageEditor.js`가 Fabric.js 기반으로 가장 안정적이며, 오브젝트 선택/편집/삭제 등 고급 기능 지원.

### 1-2. imageEditor.js 핵심 기능

```
┌─────────────────────────────────────────────────────────┐
│ Canvas Editor (1300×800 모달 dialog)                     │
├─────────────────────────────────────────────────────────┤
│                                                         │
│  [Polyline] [Free] [Line] [Text] [Eraser] [Select]     │ ← 도구 모음
│  [Clear] [Apply]                                        │
│                                                         │
│  ┌─────────────────────────────────────────┐           │
│  │                                         │           │
│  │         Canvas (800×600)                │           │
│  │                                         │           │
│  │   · 직각 고정 모드 (0°/90°/180°/270°)   │           │
│  │   · 프리뷰 라인 (대시)                   │           │
│  │   · SVG 커서 (지우개)                    │           │
│  │                                         │           │
│  └─────────────────────────────────────────┘           │
│                                                         │
│  색상: [●검정] [●빨강] [●파랑] [●초록] [●주황] [●보라]   │
│  선굵기: [━━━] 지우개크기: [━━━]                         │
│                                                         │
└─────────────────────────────────────────────────────────┘
```

**그리기 모드**:

| 모드 | 키보드 | 동작 |
|------|--------|------|
| Polyline | (기본) | 클릭으로 점 찍기 → 선 연결, ESC로 종료 |
| Free | — | 드래그로 자유 그리기 (PencilBrush) |
| Line | L | 클릭+드래그 → 직선, 직각 고정 지원 |
| Text | — | 클릭 → IText 생성 → 인라인 편집 |
| Eraser | — | 드래그로 지우기 (SVG 원형 커서, 5~100px) |
| Select | — | 오브젝트 선택/이동/삭제 (Delete키) |

**직각 고정 알고리즘**:
```javascript
// 각도 계산 후 0°/90°/180°/270° 중 가장 가까운 방향으로 스냅
const angle = Math.atan2(dy, dx) * 180 / Math.PI;
// → horizontal or vertical 결정
```

### 1-3. 5130에서의 사용처

| 화면 | Canvas 크기 | 배경 이미지 | 용도 |
|------|------------|-----------|------|
| `bending/write_form.php` | 370×300 | ❌ 없음 | 전개도 그리기 |
| `guiderail/list.php` | 800×600 | ✅ 제품 사진 위 | 치수 표기 |
| `shutterbox/list.php` | 800×600 | ✅ 제품 사진 위 | 치수 표기 |
| `bottombar/list.php` | 800×600 | ✅ 제품 사진 위 | 치수 표기 |

---

## 2. MNG 현재 Canvas 현황

### 2-1. 기존 Canvas 사용처 (참고용)

| 화면 | 라이브러리 | 용도 | 재활용 |
|------|-----------|------|:---:|
| `esign/sign/sign.blade.php` | signature_pad 4.1.7 | 전자서명 | ❌ 다른 용도 |
| `document-templates/block-editor.blade.php` | Alpine.js DOM | 블록 드래그 | ❌ 다른 용도 |
| `rd/fire-shutter-drawing/index.blade.php` | Pure Canvas | 방화셔터 도면 | ❌ 특화 로직 |

### 2-2. MNG 기술 스택

| 항목 | 현재 | Canvas 추가 시 |
|------|------|---------------|
| JS 프레임워크 | Alpine.js 3.x | Alpine.js 유지 |
| HTMX | 1.9.10 | HTMX 유지 |
| CSS | Tailwind + DaisyUI | 유지 |
| Canvas 라이브러리 | ❌ 없음 | **Fabric.js 5.3.0 CDN 추가** |
| 아이콘 | RemixIcon | RemixIcon (또는 Bootstrap Icons 병행) |

---

## 3. 적용 대상 화면

### 3-1. 기초관리 폼 (`/bending/base/{id}/edit`)

```
현재 (1차 구현):                      추가 (2차):
┌──────────────────┐                 ┌──────────────────┐
│ [형상 이미지]     │                 │ [형상 이미지]     │
│                  │                 │                  │
│  ┌────────────┐  │                 │  ┌────────────┐  │
│  │ 미리보기    │  │                 │  │ 미리보기    │  │
│  └────────────┘  │       →         │  └────────────┘  │
│                  │                 │                  │
│  [파일 선택]      │                 │  [파일 선택]      │
│  [Ctrl+V 붙여넣기]│                 │  [Ctrl+V 붙여넣기]│
│                  │                 │  [✏️ 그리기]      │ ← Canvas 모달
│                  │                 │                  │
└──────────────────┘                 └──────────────────┘
```

### 3-2. 절곡품 폼 (`/bending/products/{id}/edit`)

```
현재:                                 추가:
┌──────────────────┐                 ┌──────────────────┐
│ [결합형태 이미지]  │                 │ [결합형태 이미지]  │
│  [파일 선택]      │       →         │  [파일 선택]      │
│                  │                 │  [✏️ 그리기]      │ ← Canvas 모달
└──────────────────┘                 └──────────────────┘
```

---

## 4. 구현 설계

### 4-1. 파일 구조

```
mng/
├── public/js/
│   └── canvas-editor.js              ← imageEditor.js 이식 (MNG 맞춤)
├── resources/views/
│   └── components/
│       └── canvas-editor.blade.php   ← 모달 Blade 컴포넌트
```

### 4-2. Fabric.js 로딩

```html
<!-- layouts/app.blade.php 또는 필요한 페이지에서만 -->
@stack('scripts')

<!-- canvas-editor 사용 페이지에서 -->
@push('scripts')
<script src="https://cdnjs.cloudflare.com/ajax/libs/fabric.js/5.3.0/fabric.min.js"></script>
<script src="{{ asset('js/canvas-editor.js') }}"></script>
@endpush
```

### 4-3. Blade 컴포넌트 설계

```html
<!-- 사용법 -->
<x-canvas-editor
    target-input="image_data"         {{-- hidden input name --}}
    :existing-image="$imageUrl"       {{-- 기존 이미지 URL --}}
    :width="800"
    :height="600"
/>

<!-- 렌더링 -->
<dialog id="canvas-editor-dialog" class="modal">
    <div class="modal-box max-w-6xl p-0">
        <!-- 도구 모음 -->
        <div class="flex gap-1 p-2 bg-base-200 border-b">
            <button class="btn btn-sm" data-mode="polyline">Polyline</button>
            <button class="btn btn-sm" data-mode="free">Free</button>
            <button class="btn btn-sm" data-mode="line">Line</button>
            <button class="btn btn-sm" data-mode="text">Text</button>
            <button class="btn btn-sm btn-warning" data-mode="eraser">Eraser</button>
            <button class="btn btn-sm" data-mode="select">Select</button>
            <div class="divider divider-horizontal mx-0"></div>
            <input type="checkbox" class="toggle toggle-sm" id="right-angle" checked>
            <label for="right-angle" class="text-sm">직각고정</label>
            <div class="flex-1"></div>
            <button class="btn btn-sm btn-error" id="canvas-clear">초기화</button>
            <button class="btn btn-sm btn-success" id="canvas-apply">적용</button>
        </div>

        <!-- Canvas -->
        <canvas id="fabric-canvas" width="800" height="600"></canvas>

        <!-- 색상/옵션 -->
        <div class="flex gap-2 p-2 bg-base-200 border-t items-center">
            <!-- 색상 팔레트 -->
            <div class="flex gap-1">
                <button class="w-6 h-6 rounded-full bg-black" data-color="#000000"></button>
                <button class="w-6 h-6 rounded-full bg-red-600" data-color="#ff0000"></button>
                <button class="w-6 h-6 rounded-full bg-blue-600" data-color="#0000ff"></button>
                <button class="w-6 h-6 rounded-full bg-green-600" data-color="#00aa00"></button>
                <button class="w-6 h-6 rounded-full bg-orange-500" data-color="#ff8800"></button>
                <button class="w-6 h-6 rounded-full bg-purple-600" data-color="#800080"></button>
            </div>
            <div class="divider divider-horizontal mx-0"></div>
            <!-- 선 굵기/지우개 크기 -->
            <label class="text-sm">선:</label>
            <input type="range" min="1" max="10" value="2" class="range range-xs w-20" id="line-width">
            <label class="text-sm">지우개:</label>
            <input type="range" min="5" max="100" value="20" class="range range-xs w-20" id="eraser-size">
        </div>
    </div>
    <form method="dialog" class="modal-backdrop"><button>close</button></form>
</dialog>
```

### 4-4. canvas-editor.js 핵심 구조

```javascript
class CanvasEditor {
    constructor(options) {
        this.canvas = null;           // Fabric.js Canvas
        this.mode = 'polyline';       // 현재 모드
        this.drawColor = '#000000';
        this.lineWidth = 2;
        this.eraserSize = 20;
        this.rightAngle = true;       // 직각 고정
        this.polyPoints = [];         // polyline 점 목록
        this.previewLine = null;      // 프리뷰 라인
        this.targetInput = options.targetInput;  // 결과 저장할 input
        this.existingImage = options.existingImage;
    }

    // 주요 메서드
    init()                  // Fabric.js 캔버스 초기화
    setMode(mode)           // 모드 변경
    loadBackgroundImage(url) // 기존 이미지를 배경으로 로드
    handlePolyline(e)       // polyline 클릭 처리
    handleLine(e)           // 직선 드래그
    handleText(e)           // 텍스트 배치
    setupEraser()           // PencilBrush 지우개 모드
    snapToRightAngle(p1,p2) // 직각 고정 계산
    clear()                 // 캔버스 초기화
    apply()                 // Base64 → hidden input, 모달 닫기
    toDataURL()             // PNG/JPEG 내보내기
}
```

### 4-5. 데이터 흐름

```
[그리기 버튼 클릭]
  → Canvas 모달 열기
  → (기존 이미지 있으면) 배경 이미지로 로드
  → 사용자 그리기 작업
  → [적용] 클릭
  → Canvas → Base64 (DataURL)
  → hidden input에 저장
  → 폼 submit 시 API 전송
  → API: Base64 디코딩 → R2 저장 → file_id 반환
```

---

## 5. 이식 시 변경 사항

### 5-1. 5130 → MNG 차이점

| 항목 | 5130 (레거시) | MNG (SAM) |
|------|-------------|-----------|
| 모달 | `<dialog>` 직접 생성 | DaisyUI `modal` 컴포넌트 |
| 아이콘 | Bootstrap Icons CDN | RemixIcon (이미 로드됨) |
| CSS | 인라인 + Bootstrap | Tailwind + DaisyUI |
| JS 구조 | 전역 함수 | Class 기반 모듈 |
| 이벤트 | jQuery | Vanilla JS + Alpine.js |
| 이미지 저장 | FormData → PHP 파일 저장 | Base64 → API → R2 |
| 배경 이미지 | 별도 로드 로직 | `/files/{id}/view` 프록시 |

### 5-2. 제거할 것 (5130 전용)

```
❌ jQuery 의존성 (MNG는 Alpine.js)
❌ Bootstrap Icons CDN (RemixIcon으로 교체)
❌ 전역 변수/함수 (Class로 캡슐화)
❌ 인라인 CSS 스타일 (Tailwind 클래스로 교체)
```

### 5-3. 추가할 것 (MNG 맞춤)

```
✅ DaisyUI 모달 통합
✅ Alpine.js x-data 바인딩 (모달 상태 관리)
✅ RemixIcon 아이콘 매핑
✅ HTMX 호환 (폼 submit 시 hx-post 지원)
✅ Base64 → API 업로드 로직
✅ 반응형 캔버스 크기 (모바일 대응 불필요 — MNG는 PC 전용)
```

---

## 6. 아이콘 매핑 (Bootstrap Icons → RemixIcon)

| 기능 | Bootstrap Icons | RemixIcon |
|------|----------------|-----------|
| Polyline | `bi-vector-pen` | `ri-pen-nib-line` |
| Free Draw | `bi-brush` | `ri-brush-line` |
| Line | `bi-slash-lg` | `ri-subtract-line` |
| Text | `bi-type` | `ri-text` |
| Eraser | `bi-eraser-fill` | `ri-eraser-line` |
| Select | `bi-cursor-text` | `ri-cursor-line` |
| Clear | (텍스트) | `ri-delete-bin-line` |
| Apply | (텍스트) | `ri-check-line` |

---

## 7. 작업 순서

```
7-1. Fabric.js CDN 추가 (@push('scripts'))
7-2. canvas-editor.js 작성 (imageEditor.js 기반 이식)
7-3. canvas-editor.blade.php 컴포넌트 생성
7-4. 기초관리 폼에 [그리기] 버튼 + 컴포넌트 통합
7-5. 절곡품 폼에 동일 적용
7-6. API 연동 (Base64 → 이미지 업로드)
7-7. 테스트 (그리기 → 저장 → 재로드 → 편집)
```

### 예상 파일 변경

| 파일 | 변경 내용 |
|------|----------|
| `public/js/canvas-editor.js` | 🆕 신규 (imageEditor.js 이식) |
| `resources/views/components/canvas-editor.blade.php` | 🆕 신규 (모달 컴포넌트) |
| `resources/views/bending/base/form.blade.php` | 수정 — [그리기] 버튼 추가 |
| `resources/views/bending/products/form.blade.php` | 수정 — [그리기] 버튼 추가 |
| `resources/views/layouts/app.blade.php` | 확인 — `@stack('scripts')` 존재 여부 |

---

## 8. 주의사항

### 아키텍처

- ✅ MNG는 **샘플 확인용** — 동일 로직을 React에도 적용 예정
- ✅ `canvas-editor.js`는 **프레임워크 무관** Class로 작성 → React 포팅 용이
- ✅ Fabric.js는 CDN으로 로드 (npm 설치 불필요 — MNG는 Vite 빌드 최소화)
- ❌ drawingModule.js (966줄) 이식 불필요 — imageEditor.js로 충분

### 기존 코드 보호

- ⚠️ 기존 이미지 업로드 로직 유지 (그리기는 **추가** 옵션)
- ⚠️ 기존 Ctrl+V 붙여넣기 유지
- ⚠️ `canvas-editor.js`는 새 파일 — 기존 JS 무변경

### 성능

- ⚠️ Fabric.js 5.3.0 = ~800KB (CDN gzip ~200KB) — 필요 페이지에서만 로드
- ⚠️ Base64 이미지 크기 → JPEG 변환 (PNG 대비 1/3~1/5 크기)

---

## 관련 문서

| 문서 | 경로 |
|------|------|
| Step 3 MNG 화면 (이미지 전략) | `step3-MNG화면.md` §7 |
| 레거시 분석 | `legacy-guiderail-analysis.md` |
| 5130 imageEditor.js | `5130/js/imageEditor.js` |
| 5130 drawingModule.js | `5130/js/drawingModule.js` |

---

**최종 업데이트**: 2026-03-18
