# card-resize — 카드뉴스(1080×1080) → 인스타 세로형(1080×1350) 규칙 변환 실험

**2026-10-27 · r5(덩어리 모델) · 2차 표본 = 기계번역 제외(한국어 원본 6종) · 렌더기 충실도 보강 2차 · 실렌더 확인 대기**

미리캔버스 카드뉴스 시트 JSON 을 읽어 요소를 **역할**(배경·띠·판·내용·올라탄 것)로 나누고,
결정적 규칙만으로 세로를 25% 늘린다. 크기(글자·폭)는 어디서도 바꾸지 않는다.
LLM 은 이번 판에 안 들어간다 — hybrid-aip 교훈("좌표·자산·색은 코드가, 판단만 LLM")을 따라
규칙이 못 푸는 쪽만 2단계에서 LLM 계획으로 넘길 생각이다.

**하네스 요구사항 정리는 [`SPEC.md`](./SPEC.md)** — 불변·역할·규칙·LLM 판단 범위·검수 잣대.

## 쓰는 법

```bash
export DATABRICKS_CONFIG_PROFILE=miridih-workspace     # DEFAULT 는 2026-10-27 현재 만료
python3 card-resize/pick_samples.py stats               # 모집단 · JSON 시트 비율
python3 card-resize/pick_samples.py pick --n 6 --seed v1 # 표본 → samples.json
python3 card-resize/pick_samples.py fetch               # 첫·둘째·마지막 쪽 → sheets/ + 썸네일 URL
python3 card-resize/roles.py sheets/*.json --json out   # 역할 표 (눈으로 확인)
python3 card-resize/resize.py sheets/*.json             # → out/{idx}-p{N}.1350.json + .report.json
python3 card-resize/diff_check.py                       # 바뀌면 안 되는 속성이 안 바뀌었나 (글자·색·반전·회전)
python3 card-resize/render.py --veto                    # 근사 렌더 PNG + 거부권 + out/index.html
open card-resize/out/index.html                         # 정답 썸네일 · 원본 근사 · 변환 근사 나란히
```

눈 판정은 `ab-board/index.html` 에 `out/A-원본-근사렌더.pdf`(또는 `A0-원본-미캔썸네일.pdf`)와
`out/B-변환1350-근사렌더.pdf` 를 A·B 로 올려 쪽마다 메모한다. 18쪽 순서는 둘 다 같다.

실제 미캔 렌더는 `playground/*.pagedata.json` 을 에디터 Playground(PageData 입력란, **전체 교체**
모드, DesignSizeOption 포함 ON)에 붙여넣어 본다. 세 쪽(932113-p0 · 970203-p0 · 1015660-p1)의
원본·변환 짝이 들어 있다. **아직 안 했다** — 로컬 web-2 가 없어서 사용자 몫.

## 표본

`template_type_link.template_type_id = 'card_news'` · ACTIVE · 1080×1080 · 시트 포맷 한 가지 · JSON 만
(구형 XML 은 앞 실험과 같이 대상 밖).

| 포맷 | 1080×1080 | 비율 |
|---|---|---|
| JSON | 2,624종 | 8.8% |
| XML | 26,946종 | 90.7% |

**기계번역판은 `template.translation_status = 'TRANSLATED'` 로 가른다** (언어 열로는 못 가른다 —
순수 영어·일본어 원본도 있다). JSON 카드뉴스 안 분포:

| 번역 상태 | 언어 | 종 |
|---|---|---|
| NOT_REQUIRED / NOT_TRANSLATED (원본) | ko | 1,218 |
| TRANSLATED (기계번역) | en·es·fr·de·it·nl·pt 각 195 · ja 41 | 1,406 |
| 원본 영어·일본어 | — | **0** (영어 192·일본어 18 이 있으나 전부 XML 시트) |

- **1차 표본(`runs/v1-mixed/`)**: 시드 v1, 6종 18쪽 — 그중 4종이 기계번역판(986540 ja · 1015660 en · 1015549 es · 1015507 nl).
- **2차 표본(현재 `sheets/`)**: 시드 v2 + `--no-mt`, 한국어 원본 6종 18쪽 —
  993979(학습 안내) · 978123(어린이 정보) · 1010652(투자 절세) · 917868(비즈니스 뉴스레터) · 1013977(독감 예방) · 995202(문구 홍보).

## 역할 (`roles.py`)

| 라벨 | 판정 | 18쪽 합계 |
|---|---|---|
| `cover` | 캔버스 95% 이상 덮음, 또는 **그림이면 50% 이상** (넘치는 배경 일러스트·텍스처) | 6 |
| `span` | 위·아래 변 동시에 닿음 | 0 |
| `edge-top` / `edge-bottom` | 한쪽 변에 닿거나(≤6px) 넘침 | 6 / 17 |
| `panel` | 변에 안 닿고 가로·세로 70% 이상인 도형·그룹 (여백 둔 흰 카드) | 4 |
| `free` | content 중 글자 없는 큰 그림(높이 20%↑)·좌우로 넘친 장식 — **글자를 받치고 있으면(z 위에 글자) 빼지 않는다** | 15 |
| `rider` | edge 요소 위에 올라탄 것. 그림·그룹은 낱개, **글·도형은 묶음 과반일 때만** | 23 |
| `content` | 나머지. 세로 간격 ≤ 2% 로 이어진 것끼리 **띠(band)** | 341 |

루트 노드만 본다. 그룹 자식은 부모 기준 상대 좌표라 그룹을 통째로 움직이고, 판(panel) 그룹만
안에서 같은 규칙을 다시 돌린다 (`resize_group`).

## 규칙 (`resize.py` · r5 · 덩어리 모델) — k = 1.25, Δ = 270

사용자 지적 12건(2026-10-27)의 공통 원인은 **덩어리를 모르고 요소마다 y 를 흩은 것**이었다. r5 는 덩어리가 단위다.

| 역할 | 하는 일 |
|---|---|
| cover 그림 | 가로 중심 유지 k 배 균일 확대. 캔버스 안 회전 없는 벡터 판(공책·종이)은 세로만 k 배 + **컨테이너** |
| edge-top | 높이 ≥ 20% H 인 판·사진: 위 고정 h·k (2:1 분할은 2:1 로 남는다). 그 밖(띠·장식): 그대로 |
| edge-bottom | 높이 ≥ 20% H: 판은 바닥에 붙여 h·k, **인물 그림은 k 배 확대해 발을 바닥에** (글자를 새로 가리면 배율을 줄인다). 그 밖(띠·장식): Δ 내림 |
| panel | y·k, h·k + 컨테이너 |
| 바닥 띠 위 | 아랫변이 바닥 띠 윗변에 닿은 것(책·연필꽂이)은 띠와 같이 Δ 내림 |
| 내용·장식 → **덩어리** | 2.5% H 안에 있거나 품은 것끼리 묶고, 세로로 겹치는 덩어리는 한 줄로 합친다 |
| 덩어리 안 | **강체** — 간격 그대로. 글자·글자 받침 도형(핵심)의 폭 여유만큼 덩어리째 s 배(≤ k, 옆 여백 절반 유지), **글자 크기도 s 배**. 장식 그림은 따라 커지되 캔버스 밖으로 더 나가면 안으로 당긴다 |
| 사진·표만의 덩어리 | h·k 로 채운다(crop 창·행 높이) |
| 덩어리 사이 | 남는 세로를 원래 여백·간격에 비례해 나눈다(간격은 최대 1.6배). 남으면 위·아래 여백에 원래 비율로 → 가운데 있던 덩어리는 가운데 |
| 컨테이너 | 판·공책 안에 있는 덩어리는 판 기준으로 다시 놓는다 |
| rider | 올라탄 edge 요소를 따라간다 |

## 결과 (r5, 2차 표본 한국어 원본 18쪽, 2026-10-27)

예외 없이 돌았고 `diff_check` 통과(글자 크기는 덩어리 배율만큼만 바뀜 — 색·내용·굵기 불변). 거부권 원본 18 → 변환 21,
늘어난 쪽 3 — 전부 993979 의 연필꽂이·지구본(변에 걸친 장식)이 k 배 커지며 「쪽밖 IMG」 1건씩. 원본도 잘려 있던 장식.

사용자 지적 12건 → 처리:

| 지적 | 원인 | 처리 |
|---|---|---|
| 한 덩어리인데 벌어짐(세금 계획·어린이 카드뉴스) | 요소마다 y 비례 | 덩어리 강체 |
| 크기가 작아짐 | 갤러리가 세로판을 같은 높이로 축소 표시 + 글자 크기 불변 | 갤러리 같은 폭 · 덩어리째 확대(글자 포함) |
| 동전이 가려짐 | 인물·장식이 글자 위로 | 인물은 글자 새로 가리면 축소, 장식은 밖으로 더 나가면 안으로 |
| 체크 목록 글자가 작아짐 | 표시 축소 | 같은 폭 표시 + 덩어리 확대 |
| 타이틀 중앙 정렬 안 맞음(독감·예방접종) | 렌더기가 뒤집힌 도형 안 글자까지 반전 | 글자엔 반전 안 걸음 |
| 타이틀 한 줄→두 줄 | **안 함** — 폭 여유(옆 여백 절반)만큼만 키운다. 두 줄 개행은 2단계 LLM 몫 |
| 알약 안 텍스트 중앙 | 렌더기 정렬 기본값 → 가운데 (이미 반영) | 확인 |
| 917868 p1 엉망 | 렌더기: `pre` 줄바꿈 미지원·따옴표 SVG 403 | `white-space:pre` · 403 자산은 `<img>`+점선 |
| 가운데 덩어리는 가운데에 | 위 고정/비례 | 여백 비례 배분 |
| 텍스트가 엉뚱한 곳(카드뉴스 소개) | 사진 채움 + 글자 따로 이동 | 사진·글자가 한 덩어리면 강체(사진은 안 늘림) |
| 원 안 아이콘 이탈(오늘의 목표) | 원은 네모 늘림, 아이콘은 이동 | 덩어리 강체 — 원과 아이콘이 같이 |
| 배경·꾸밈 배치 다름(오늘도 잘했어요) | 책·연필꽂이가 바닥 띠와 따로 | 바닥 띠 위에 앉은 것은 띠와 동반 |
| 딤 요소 누락(만년필) | 렌더기가 사진 틀 opacity 0.32 를 안 곱함 | 조상 투명도 곱함 |
| 상하 2:1 분할 | 머리 사진을 띠 취급 | 높이 20%↑ edge 는 판 — h·k |

렌더기 보강 2차(`tools/design/preview_pagedata.py`): 투명도(조상 곱) · 반전(글자 제외) · `pre` 줄바꿈 · 서체 로컬 캐시+인라인(한글 경로 percent-encoding, 이름 key 고정) · 403 SVG 는 `<img>`.

눈 판정(사용자): **아직**.

### 원본 ↔ 변환 전수 점검 (2026-10-27, r2 기준으로 보고 r3 로 고침)

| 본 것 | 결과 |
|---|---|
| 개행·글자 크기·색·반전·회전 | 바뀐 곳 없음 (기계 비교 + 렌더 쌍 비교) |
| 제목↔본문 간격 | r2 는 25~45px → 80~150px 로 벌어짐(14쪽). r3 는 위 고정으로 원본과 같되 바닥이 비었다. **r4 는 모든 간격·네모·표를 같이 25% 키워 원본 밀도** (사용자 요청) |
| 머리 띠에 걸친 목록(1015507 p5) | r2 는 130px 떨어짐 → r3 고정으로 붙음 |
| 그림↔글자 비율 | 변에 걸친 그림 16개가 1.07~1.25배. 유지하기로 결정 |
| 옆 여백 | r2 는 스마트폰이 캔버스 폭에 꽉 참 → r3 cap(원래 여백 절반 유지), s=1.07 |
| 회전 그림 확대(1015549 p1 곰) | r2 버그(rbb 안 따라감) → r3 수정 |
| free 그림과 띠의 겹침 관계 | 둘 다 고정이라 r3 에선 그대로 |

### 지나오며 버린 규칙 (왜)

1. **띠 사이 빈 공간에 Δ 배분** → 해변 쪽은 제목 띠가 모래밭에 닿아 빈 공간이 0, 전체가 270px 통째로 내려갔다.
2. **edge 그림을 옮기기만** → 경찰관(1015507 p0)이 270 내려가 키캡과 사이가 비었다. 띠가 아닌 그림은 k 배 확대로.
3. **rider 를 낱개 판정** → 목차 다섯 줄 중 마지막 줄만 모래밭에 걸쳐 그 줄만 따라 내려갔다(932113 p1). 글·도형은 묶음 과반 기준으로.
4. **글자가 겹치면 free 에서 제외** → 사진 **밑에** 깔린 흰 글자(디자이너 흔적, 970203 p0)가 사진 틀을 띠에 묶었다. z 가 위인 글자만 센다.
5. **띠 중심 비례 cy' = cy·k (r2)** → 띠 안은 안 늘리고 띠 사이에만 몰아넣어 제목↔본문이 느슨했다.
6. **위 고정·아래로만 (r3)** → 270px 이 바닥에 몰려 아래가 비었다(사용자: "여백 너무 많다, 원본 밀도로"). r4 로 교체.
7. **그룹 안 가는 구분선을 그림으로 확대** → 카드 밖으로 나갔다(1015549 p5). 가늘고 긴 것은 옮기기만.
8. **액자 위 글자를 y·k 로 따로 이동** → 액자는 옮기기만 하니 글자가 액자 아래로 36px 흘렀다(986540 p4). 글자 바로 밑에 깔린 것이 받침 그림이면 같은 양만 (밑에 배지 도형이 있으면 그 도형이 받침).

### 근사 렌더 — 2026-10-27 충실도 보강 뒤 남은 한계 (변환 결함이 아니다)

사용자가 "색·정렬이 안 맞는다"고 해 실물 썸네일과 견준 결과, **변환은 색·정렬을 안 바꿨고**(diff_check)
렌더기(`tools/design/preview_pagedata.py` 의 `page_body`)가 잃고 있었다. 고친 것 다섯 가지:
글자를 textTree 에서 직접(런별 색·`fontSizeScale` 곱한 크기·자간·굵기, 문단 정렬, **정렬값 없으면 가운데**,
`layout: autoHeight` 만 상자 폭에서 접음) · 회전(글자·도형·선·사진 모두 돌리기 전 상자를 CSS 로) ·
기본 도형은 항상 실루엣+적용색(그라데이션 포함) · 일러스트 colorMap 재색상 · 잘린 사진은 보이는 창만.

**2차 표본에서 또 드러난 렌더기 결함 넷 (2026-10-27, 사용자가 "요소가 다 엑박" 지적)**
1. 자산 캐시 검사가 `set(assets) < keys`(부분집합)라서 1차 캐시에 다른 키가 섞이자 거짓이 돼 **조회를 통째로 건너뛰었다** — 일러스트·사진·서체가 전부 빠짐. `keys - set(assets)` 로 고침 (`render.py`·`preview_pagedata.py` 둘 다).
2. 둥근 네모(알약·카드)를 실루엣 mask 로 그려 모서리·테두리가 사라졌다 → `corner_radius`(px)·`shape_stroke` 로 CSS 둥근 네모.
3. 패턴 배경(`PatternItem`, 녹색 줄노트)을 못 그렸다 → SVG 타일을 colorMap 재색상해 `patternCount` 만큼 반복.
4. 리소스 메타 표에 없는 키(패턴·사진 틀)는 `designresource_file.file_key` 로 보조 조회.
고친 뒤 978123(줄노트) 쪽이 실물 썸네일과 같아졌다 (스프링·핀·사진·알약·종이비행기·캐릭터).

남는 한계: 비공개 자산은 썸네일이라 흐리고 재색상이 안 된다 · 곡선 글자·게이지·차트는 자리표 · 표 칸 글자는 행 높이를 키워도 옛 자리에 그려진다(에디터는 레이아웃에서 다시 잡는다 — 실렌더로 확인) ·
그룹 상자는 점선 · 서체는 같지만 글리프 폭 미세 차이로 꼭 맞는 글상자는 접힐 수 있다(2px 여유를 둠).

## 알려진 함정

- `backgroundNodeId` 는 못 믿는다 — 배경은 면적으로 판정.
- `LineItem` 의 `headPoint/tailPoint` 는 전부 같은 기본값. 상자만 옮기면 된다.
- 벡터의 `scale` 은 bbox/resourceSize 와 안 맞는다 — 균일 확대 때 같은 배율만 곱해 둔다. 실렌더로 확인 필요.
- `ExtendableShapeItem` 늘림은 bbox 만 바꾼다(`stretchableDirection` 믿음). 실렌더로 확인 필요.

## 다음

1. **실렌더 확인** — `playground/` 다섯 쌍(2차 표본, r5): 세금 계획·세금 종류·카드뉴스 요약·오늘의 목표·만년필 2. 1차의 달력 표(970203 p5)는 `runs/v1-mixed/playground/` 에 — 행 높이만 키웠으니 칸 글자가 에디터에서 제자리로 가는지. 벡터 판의 `scale.y` 만 키운 것(1010652 p1)이 에디터에서 세로로 늘어나는지. 특히 ①늘린 ExtendableShape ②균일 확대한 Vector ③crop 연 사진(970203 p0 는 원본 1.28배 확대 + 가운데 크롭).
2. **눈 판정 18쪽** → 아래 RUNS 에 기록.
3. 2단계(LLM): 규칙이 못 하는 판단 — 긴 제목을 두 줄로 개행, 장식을 어디로 옮길지, 덩어리 묶음이 틀린 쪽 — 만 LLM 에 roles+덩어리+썸네일을 주고 plan.json 을 받는다. 1080×1920 은 그 위에.
4. 1080×1920 은 `--height 1920` 으로 돌릴 수는 있으나 규칙이 거기 맞춰 검증되진 않았다.

## RUNS

| 날짜 | 규칙 | 표본 | 거부권 악화 | 눈 판정 | 비고 |
|---|---|---|---|---|---|
| 2026-10-27 | r2 | 6종 18쪽 | 0/18 | 미정 | 1차. 중심 비례 — 제목↔본문 벌어짐 |
| 2026-10-27 | r3 | 6종 18쪽 | 1/18 | 불합격(바닥 빔) | 위 고정·아래로만 — 사용자가 여백 과다 지적 |
| 2026-10-27 | r4 | v1 6종 18쪽 (MT 4종 포함) | 0/18 | 미정 | 세로축 전체 비례 확대(네모·표·그림 같이). 실렌더 미확인 |
| 2026-10-27 | r4 | v2 한국어 원본 6종 18쪽 | 6/18 (전부 쪽밖 IMG 1, 변에 걸친 장식) | 불합격(12건 지적) | 기계번역 제외. 요소별 비례라 덩어리가 흩어짐 |
| 2026-10-27 | r5 | v2 한국어 원본 6종 18쪽 | 3/18 (993979 장식 쪽밖) | 미정 | 덩어리 모델 + 컨테이너 + 인물 글자 보호. 실렌더 미확인 |
