Dynamic Layout System

Contents API의 동적 배치 시스템 가이드입니다. 템플릿 요소들을 페이지에 자동으로 배치하며 컬럼, 페이지, 면(left/right)을 자동으로 관리합니다.

개요

동적 배치 시스템은 PhotobookAPI의 핵심 기능으로, page_layout_state 테이블 기반의 상태 추적을 통해 컬럼, 페이지, 면(left/right)을 자동으로 관리하며, 텍스트의 동적 높이 계산 및 분할 기능을 지원합니다.

특징설명
자동 배치컬럼, 면, 페이지를 자동으로 선택하여 요소 배치
컬럼 시스템1~3컬럼 레이아웃 지원, 슬롯 기반 배치
텍스트 분할splittable 속성으로 긴 텍스트 자동 분할
동적 높이isDynamic 속성으로 텍스트 높이 실시간 계산
breakBefore 제어배치 위치를 세밀하게 제어
LanesX 범위 기반 독립 Y 흐름 (레인별 독립 배치)
shiftUpOnHide조건부 요소 숨김 시 레이아웃 자동 재배치
DynamicDeltaisDynamic 요소 높이 변화에 따른 앵커 Y 보정

페이지 구조

양면 인쇄 구조

포토북은 양면 인쇄 구조로 되어 있으며, 각 페이지는 왼쪽(left)과 오른쪽(right) 두 면으로 구성됩니다.

페이지 번호 규칙 — PUR 제본 방식 (기본)

pageNumpageSide설명비고
0right표지 (Cover)앞표지와 뒷표지
1right첫 내지 (First)오른쪽부터 시작
2left두 번째 내지 왼쪽
2right두 번째 내지 오른쪽

페이지 번호 규칙 — 기타 제본 방식 (슬림앨범, 레이플랫 등)

pageNumpageSide설명비고
0right표지 (Cover)앞표지와 뒷표지
1left첫 내지왼쪽부터 시작
1right첫 내지 오른쪽
2left두 번째 내지 왼쪽
PUR 제본: 첫 내지(pageNum=1)는 오른쪽(right)부터 시작, page_type은 "first". 기타 제본: 왼쪽(left)부터 시작, page_type은 "photo". 이후 페이지는 왼쪽(left) → 오른쪽(right) 순서로 진행.

페이지 JSON 파일명 규칙

pageNum파일명설명
0cover.json표지
1000.json첫 내지
2001.json두 번째 내지
N{N-1:D3}.jsonN번째 페이지 (3자리 숫자로 포맷)
csharp
string fileName = pageNum == 0 ? "cover" : (pageNum - 1).ToString("D3");
// 저장 경로: d:\efiles\{bookid}\page\{fileName}.json

컬럼 시스템

컬럼은 페이지 한 면을 세로로 나눈 영역입니다. 템플릿의 layoutRules.flow.columns 값으로 정의됩니다. 슬롯은 0부터 시작하는 인덱스를 가집니다.

X 좌표 계산

csharp
double slotWidth = pageWidth / totalColumns;
double xOffset = currentSlotIndex * slotWidth;
if (pageSide == "right") { xOffset += pageWidth; }

// 예시 (2컬럼, pageWidth=978px):
// 왼쪽 면, 슬롯 #0: X = 0
// 왼쪽 면, 슬롯 #1: X = 489
// 오른쪽 면, 슬롯 #0: X = 978
// 오른쪽 면, 슬롯 #1: X = 1467

페이지 마진과 컬럼 간격

pageMargin 구조

json
{
  "layoutRules": {
    "margin": {
      "pageMargin": {
        "spine": 40,  // 책등 쪽 마진
        "fore": 30,   // 바깥쪽 마진
        "head": 20,   // 위쪽 마진
        "tail": 30    // 아래쪽 마진
      }
    }
  }
}

컬럼 폭 계산 (pageMargin + columnGap 적용)

csharp
// 1. 콘텐츠 전체 폭 계산
double contentWidth = pageWidth - (spine + fore);

// 2. 컬럼 폭 계산
double slotWidth = (contentWidth - columnGap * (columns - 1)) / columns;

// 예: 2컬럼, pageWidth=978, spine=40, fore=30, columnGap=20
// contentWidth = 978 - (40 + 30) = 908
// slotWidth = (908 - 20 * 1) / 2 = 444px

기존 vs 신규 레이아웃 비교

항목기존 레이아웃신규 레이아웃 (pageMargin + columnGap)
컬럼 폭pageWidth / columns(contentWidth - columnGap*(columns-1)) / columns
왼쪽 면 XslotIndex * slotWidthfore + slotIndex * (slotWidth + columnGap) + templateX
오른쪽 면 XpageWidth + slotIndex * slotWidthpageWidth + spine + slotIndex * (slotWidth + columnGap) + templateX
세로 높이fullPageHeightfullPageHeight - (head + tail)
첫 요소 YoriginalYhead + originalY
pageMargin이 정의되어 있으면 값이 모두 0이어도 신규 레이아웃이 적용됩니다. 1컬럼 템플릿에서는 columnGap이 무시됩니다.

breakBefore 파라미터

동작사용 시나리오
none (기본값)이전 콘텐츠 바로 다음에 배치연속된 콘텐츠, 텍스트 플로우
column다음 컬럼(슬롯)으로 이동 후 배치컬럼 구분, 섹션 시작
page다음 면 또는 페이지로 이동 후 배치챕터 시작, 중요 콘텐츠
bash
# none: 이어서 배치 (기본값)
POST /books/{bookUid}/contents?breakBefore=none

# column: 다음 컬럼으로 이동
POST /books/{bookUid}/contents?breakBefore=column

# page: 다음 페이지로 이동
POST /books/{bookUid}/contents?breakBefore=page

텍스트 요소 속성

isDynamic

isDynamic: true일 때 실제 텍스트 내용에 따라 TextUtil.GetHeight()로 높이 계산합니다. 가변 길이 텍스트(일기, 리뷰, 댓글 등)에 사용합니다.

splittable

splittable: true일 때 텍스트가 페이지/컬럼 경계를 넘으면 자동 분할합니다. 이진 탐색으로 분할 지점을 찾고, 단어 경계로 조정합니다.

isDynamicsplittable동작
falsefalse템플릿 height 사용, 한 덩어리 배치 (기본)
truefalse동적 height 계산, 한 덩어리 배치
falsetrue템플릿 height 사용, 분할 배치 (비권장)
truetrue동적 height 계산, 분할 배치 (권장)
json
{
  "type": "text",
  "text": "$$userContent$$",
  "isDynamic": true,
  "splittable": true,
  "fontSize": 14,
  "width": 400,
  "height": 100
}

Lanes (X-lane 기반 독립 Y 흐름)

일부 템플릿은 페이지 내에서 X 범위가 다른 영역이 독립적인 Y 흐름을 가져야 합니다.layoutRules.lanes로 정의하며, 각 레인은 독립적인 maxY를 유지합니다.

json
{
  "layoutRules": {
    "lanes": [
      { "id": "left",  "xMin": 0,   "xMax": 155  },
      { "id": "right", "xMin": 155, "xMax": 1000 }
    ]
  }
}

shiftUpOnHide (조건부 요소 숨김)

layoutRules.shiftUpOnHide: true와 요소의 visible: "$$param$$" 속성을 조합하면, 파라미터 값이 false일 때 해당 요소가 숨겨지고 아래 요소들이 위로 올라옵니다. lanes가 정의된 경우 숨겨진 요소와 같은 X-lane에 속하는 요소만 상향 이동합니다.

DynamicDelta (isDynamic 앵커 보정)

같은 템플릿 내 요소들은 TemplateBaseY 앵커로 배치됩니다.isDynamic: true 요소의 높이가 변할 때, 그 변화량을 DynamicDelta로 누적하여 후속 요소의 Y를 보정합니다.

text
최종 Y = max(anchoredY, sequentialY)
anchoredY = TemplateBaseY + OriginalY + DynamicDelta
DynamicDelta = Σ (실제높이 - 템플릿높이)  (각 isDynamic 요소별)

새 템플릿(isFirstOfTemplate)이 시작될 때마다 DynamicDelta는 0으로 리셋됩니다.

itemSpacing (템플릿 간 간격)

json
{
  "layoutRules": {
    "flow": {
      "itemSpacing": { "size": 15 }
    }
  }
}

새 템플릿의 첫 요소 배치 시, 이전 콘텐츠의 bottom Y에 itemSpacing.size만큼 간격을 추가합니다. 설정되지 않은 경우 요소의 OriginalY가 간격으로 사용됩니다.

관련 문서