비트맵은 BitmapItem, 화살표는 VectorGraphicItem, 점은 ExtendableShapeItem입니다. 렌더러의 nodeType을 새 유형으로 바꿀 이유는 없습니다.
Executive summary
먼저 확인된 사실
Before sheetJson 기준으로 두 문서 모두 루트 GroupItem 아래가 평평합니다. 별도 장식 노드는 있지만 DecorationItem이나 소유 관계는 없습니다.
role: Decoration만으로는 누구를 따라갈지 모릅니다. ownerNodeId, 양쪽 anchor, offset이 함께 있어야 합니다.
Visual Bounding Box가 화살표까지 포함하면 점유·충돌 판단은 좋아집니다. 그래도 연결 정보가 없으면 화살표 자체는 움직이지 않습니다.
컨테이너에 붙은 장식
카드 밖으로 걸친 비트맵이 95% 포함 조건을 통과하지 못해 같은 이동 단위에서 빠집니다. 별도 요소로 남은 뒤 겹침 해소에서 카드와 반대 방향으로 밀렸습니다.
Before 이미지와 노드 위치
확성기와 알람시계의 카드 포함률은 각각 약 62.4%, 67.6%입니다. 현재 비텍스트 요소 연결 기준 95%에 미달합니다.
확성기 → 카드 2, 알람시계 → 카드 3를 오른쪽·위 기준점에 연결해야 카드의 위치와 크기 변화에 함께 반응합니다.
| 쌍 | 소유 노드 저장 이동 | 장식 저장 이동 | 상대 간격 변화 | 판정 |
|---|---|---|---|---|
| 카드 2 ↔ 확성기 | −112.89px | +112.89px | +225.78px | 반대 방향 |
| 카드 3 ↔ 알람시계 | −115.34px | +115.34px | +230.69px | 반대 방향 |
Live 연결 편집기
소유 노드와 기준점을 바꾸면 제안 위치와 sheetJson 조각이 즉시 갱신됩니다. 이 페이지 안에서만 바뀝니다.
제안 sheetJson 조각
텍스트에 붙은 장식
본문이 4줄에서 1줄로 줄고 폭은 259.65px 넓어졌지만, 화살표와 노란 점은 별도 형제 노드라 1px도 움직이지 않았습니다.
Before 이미지와 노드 위치
실제 글자 영역과 내부 글머리 기호 공간만 포함합니다. 별도 VectorGraphicItem인 화살표는 박스 계산에도, 이동에도 들어오지 않습니다.
화살표 → 위쪽 텍스트, 노란 점 → 31%로 보입니다. 다만 이는 시각 증거에 따른 제안이며 sheetJson에 저장된 사실은 아닙니다.
| 노드 | Before → After | 폭 변화 | 높이 변화 | 판정 |
|---|---|---|---|---|
| 위쪽 텍스트 | y +59.64px | +259.65px | −177.40px | 4줄 → 1줄 |
| 화살표 | x 0 / y 0 | 0 | 0 | 허공에 남음 |
| 31% · 노란 점 | x 0 / y 0 | 0 | 0 | 현재 결과와 일치 가능 |
Live 연결 편집기
화살표와 점을 각각 다른 텍스트에 연결해 볼 수 있습니다. Visual Bounding Box 포함 여부도 제안 JSON에 반영됩니다.
제안 sheetJson 조각
Three box models
Visual Bounding Box에 넣으면 무엇이 달라지나
점유 영역을 넓히는 일과 실제 노드를 움직이는 일을 분리해서 설계해야 합니다.
Text frame
편집 프레임 전체입니다. 줄바꿈 전후 폭과 높이를 담지만 실제 글자나 바깥 장식의 시각 점유를 정확히 나타내지 않습니다.
현재 이동의 입력 박스Visual union
글자의 tight box와 연결된 장식 박스를 합칩니다. 충돌·여백 검증은 좋아지지만 장식 좌표를 자동으로 바꾸지는 않습니다.
판정에는 유효 · 이동은 부족Attachment transform
소유 노드의 anchor가 바뀔 때 장식의 anchor와 offset으로 새 좌표를 계산합니다. 그 결과를 Visual union에 다시 반영합니다.
권장 · 이동과 점유를 함께 해결A같은 노드, 세 개의 상자
크기가 다른 세 상자가 아니라 서로 다른 질문에 답하는 세 개의 자입니다. 프레임은 편집기가 저장한 칸, tight bounding box는 글자가 실제로 찍힌 자리, visual bounding box는 그 글자에 딸린 장식까지 덮는 자리입니다. 아래는 케이스 5628689의 두 텍스트 노드를 실측 좌표 그대로 겹쳐 그린 것입니다.
boundingBox — 문서에 저장된 칸
tight bounding box — 본문 글자의 glyph extent
visual bounding box — 판정에 쓰는 상자
글머리 기호 — 본문과 다른 layer에 그려진다
TextBoundingBoxCalculator.calculateTightBoundingBox만이 값을 정의하고, src/text-tight-box/가 브라우저·폰트를 들고 재현해 구워 둔 것입니다. 글머리 기호는 렌더러가 본문과 같은 layer에 따로 그리므로(CanvasTextDrawer의 「5. markers」) 그 상자에 들어오지 않습니다.| 상자 | 무엇을 재나 | 불릿 2-1 (기호 있음) | 환경 재설계 (기호 없음) | 쓰는 곳 |
|---|---|---|---|---|
프레임 boundingBox | 편집 프레임 — 오토핏 여백까지 포함 | 548.90 × 53.79 | 520.29 × 53.79 | 저장 · 실제 이동(translateNode) |
| tight bounding box | 본문 글자의 glyph extent. 기호 제외 | 409.19 × 32.86 left +40.44 | 165.45 × 32.75 left +1.19 | 넘침 · 겹침의 원자료 |
| visual bounding box | tight + 기호가 쓰라고 비워 둔 들여쓰기 띠 | 449.63 × 32.86 left 0 | 165.45 × 32.75 tight와 동일 | 채점 · 게이트 · 이동 후보의 간격 |
기호가 있으면 tight가 기호를 지나친 자리에서 시작합니다. 커밋된 코퍼스 실측으로 tight의 left(프레임 원점 기준) 중앙값이 기호 있는 텍스트 117건은 30.94px, 없는 텍스트 789건은 3.78px였습니다. 그 상자로 재면 「기호는 도형 밖으로 나가 있는데 넘침 0」이 나옵니다.
visualBoxMap은 왼쪽 변만 프레임 안쪽 끝까지 밀고 오른쪽·위·아래는 tight 그대로 둡니다 — 그래서 visual은 언제나 tight의 superset입니다. 넓히는 양은 MARKER_INDENT_MAX_EM = 2.5로 묶여 있습니다. 가운데·오른쪽 정렬에서 정렬 여백만큼 상자가 터지는 것을 막는 상한이고, 왼쪽 정렬(코퍼스의 96%)은 여기 걸리지 않습니다. 위 불릿은 40.44px = 1.05em이라 상한 96.06px에 닿지 않습니다.
오늘의 visual bounding box는 바깥 장식 노드를 포함하지 않습니다. 텍스트 노드 하나의 상자를 글머리 기호 몫만큼 왼쪽으로 넓힐 뿐이고, 별도 VectorGraphicItem·BitmapItem은 그 지도에 아예 들어오지 않습니다. 아래 카드 ②의 「Visual union」은 현재 구현이 아니라 제안입니다.
B장식을 그 상자에 넣으면 무슨 일이 생기나
케이스 5636021의 화살표를 여기까지 여세요에 연결했다고 보고, visual bounding box를 tight ∪ 장식으로 넓혀 본 것입니다. 좌표는 모두 저장된 Before/After에서 읽은 실측값이고, ③의 offset만 Before 배치에서 역산한 파생값입니다.
boundingBox
tight bounding box
visual union — tight ∪ 장식
장식 노드 b099744c · VectorGraphicItem 66.88 × 33.59
delta는 left 0 / top 0이었습니다. ②의 union이 위로 자란 48.68px은 「글자가 거기 있다」는 뜻이 아니라 화살표가 아직 옛 자리에 있다는 뜻입니다.| 상태 | 텍스트 tight | 화살표 | visual union | 화살표 이동 |
|---|---|---|---|---|
| ① Before | 100.37 × 221.77 | 66.88 × 33.59 | 196.86 × 221.77 폭 +96.49 | — |
| ② After · 현재 | 369.79 × 51.34 | 66.88 × 33.59 제자리 | 369.79 × 100.02 높이 +48.68 | 0px |
| ③ 연결 적용 제안 | 369.79 × 51.34 | 66.88 × 33.59 | 466.29 × 51.34 높이 그대로 | +263.75 · +58.73 |
화살표가 점유 영역으로 세어집니다. 지금은 문서 경계를 넘든 옆 카드를 밟든 지표가 0을 보고합니다. 합집합을 쓰면 Before 기준 폭이 100.37에서 196.86으로 +96.49px — 눈에 보이는 것과 표가 같아집니다.
②가 그 증거입니다. union은 위로 48.68px 자랐지만 화살표의 좌표는 그대로입니다. 상자를 넓히는 일은 점유의 서술이고, 화살표를 옮기는 일은 좌표의 변경이라 서로 다른 층입니다. 연결 정보가 없으면 두 번째는 일어나지 않습니다.
visual bounding box는 채점 전용이 아닙니다. placementBoxMap이 이 상자를 그대로 이동 후보에게 넘겨(escapeIntoHorizontalSlack · spreadIntoVerticalSlack) 간격과 장애물 여유를 재는 자로 씁니다. 안 움직일 노드를 상자에 넣는 순간 그만큼 이동량이 바뀝니다 — ②의 상자로 위쪽 여백을 재면 48.68px을 없는 여백으로 착각합니다. placementBox.ts가 「위상 동결·translateNode에는 이 지도가 아니라 실제 프레임 absBoxMap을 써야 한다」고 못 박아 둔 이유가 이것입니다. 판정 상자와 이동 상자는 끝까지 갈라 두고, 장식은 먼저 옮긴 뒤에 합쳐야 합니다 — ③의 순서입니다.
5636021에 적용할 순서
① 화살표를 위쪽 TextItem에 연결 → ② 텍스트 After box에서 화살표 위치 계산 → ③ 이동된 화살표와 글자의 tight box를 합쳐 Visual Bounding Box 생성 → ④ 합쳐진 영역으로 충돌과 문서 경계를 검증합니다. 노란 점은 31%에 별도 연결해야 합니다.
Cross-repository contract
세 저장소를 잇는 공통 계약
의미 판정은 aippt-prisonbreak, 저장과 편집 보존은 miricanvas-web-2, 재배치와 검증은 miricanvas-iui가 맡는 구조가 가장 작고 안전합니다.
aippt-prisonbreak
RLSC에서 Role.Element.Decoration을 판정하고 소유 후보를 확인합니다.
sheetJson props
기존 nodeType을 유지한 채 role과 layoutAttachment를 저장합니다.
iui + web-2
iui는 이동·점유·충돌을 검증하고, web-2는 저장·편집·복제에서 관계를 보존합니다.
유형을 새로 만들지 않는 이유
BitmapItem,VectorGraphicItem등 실제 렌더 유형을 그대로 사용할 수 있습니다.TextDecorationItem,ContainerDecorationItem은 UI 표시명이나 TypeScript 별칭으로 제공할 수 있습니다.- 이를 실제
nodeType으로 만들면 엔진 등록, 역직렬화, 편집기 도구, 복제 경로까지 영향 범위가 커집니다. Role.LayoutContainer.Decoration은 “그 컨테이너 자체가 장식”이라는 뜻이라 소유 관계를 대체하지 않습니다.
권장 최소 형태
{
"nodeType": "BitmapItem",
"props": {
"role": "Decoration",
"layoutAttachment": {
"kind": "container",
"ownerNodeId": "d63bbcb1-…",
"ownerAnchor": "top-right",
"selfAnchor": "top-right",
"offset": { "x": 65.19, "y": -51.43 },
"sizePolicy": "keep",
"includeInVisualBounds": true
}
}
}| aippt-prisonbreak | 장식 역할을 판정하고 owner 후보와 attachment 의미를 생성합니다. RLSC와 sheetJson이 별도라면 변환 단계에서 node id 대응을 보존해야 합니다. |
|---|---|
| miricanvas-web-2 | NodeProps.role에 이미 있는 저장 통로를 확장해 layoutAttachment를 직렬화·역직렬화하고, 복제·그룹 이동·편집에서 owner id를 보정합니다. |
| miricanvas-iui | attachment transform을 재배치 전에 고정하고, 소유 노드의 새 anchor에서 장식 좌표를 계산한 뒤 Visual Bounding Box와 충돌 지표를 다시 계산합니다. |
권장 명명
저장 계약은 role: Decoration + layoutAttachment.kind: text | container로 두고, 화면에는 이를 조합해 TextDecorationItem 또는 ContainerDecorationItem으로 보여주는 방식이 좋습니다. 의미는 명확해지고 기존 Item 처리 경로는 유지됩니다.
Evidence & source
확인한 파일과 데이터
- 케이스 5628689 Before:
eval-out/cases/5628689/rendered.json - 케이스 5636021 Before:
eval-out/cases/5636021/rendered.json - 저장된 After:
eval-out/runs/relayout-three-way-20260830/algorithm/after/ - 소속 추론:
src/post-replace-relayout/relayout-core/topology.ts - Visual box:
src/post-replace-relayout/relayout-core/visualBox.ts - 좌표·유형·이동 근거는 이 페이지의 두 케이스 섹션에 함께 포함
이 페이지의 Before/After 좌표는 위 JSON에서 읽은 값입니다. 케이스 5636021의 “화살표→위쪽 텍스트, 점→31%” 관계만 시각 증거에 따른 제안이며 원본 sheetJson에는 없습니다.