아카이브 · 당시 기준

2026-09-13 원본에서 평가 계약·구현·코퍼스가 달라진 것을 확인해 당시 기록으로 보관했습니다. 본문과 자동 동기화 표는 보관 당시 내용입니다.

현재 기준과 원본 보기 → · 보관 목록

← 미리디 아카이브 / 내용 대치 후 요소 재배치

내용시각화post-replace-relayoutv0.7 · 2026-08-30 기록코드 확인 2026-08-30

재배치 알고리즘
파이프라인 v0.7

내용 대치 후의 넘침과 겹침을 줄이는 과정. 먼저 크기와 간격을 조정하고, 실제 글자 영역으로 다시 확인한 뒤 필요한 경우에만 폰트를 줄인다.

연산자 8개내부 등록 후보 17개 = 단독 8 + 조합 9화면 선택지 2개기준 커밋 7b4354f62

이 문서의 기준

v0.6의 구성과 설명을 바탕으로 현재 구현을 다시 확인했다. 브랜치는 feat/post-replace-relayout이며, 인용 파일은 기준 커밋과 작업트리가 일치한다. 코드 링크는 고정 커밋의 GitHub 주소다.

도해는 본문에 이미 적힌 내용을 다시 그린 것이다. 새 측정이나 새 주장을 담지 않았고, 근거 링크는 각 절의 원래 자리에 그대로 둔다.

기존 문서·코드 주석과 실행 코드가 다르면 실행 코드를 우선했다. 과거 측정값은 최신 성능으로 재사용하지 않으며, 최근 평가도 기록된 측정 범위와 한계를 함께 표시한다.

§0

핵심 요약

연산자는 여전히 8개

method 1~8을 단독으로 등록한 8개와 합성 후보 9개를 합쳐 17개다. 새 연산자가 추가된 것이 아니라 3+1+2+5+4+7+8 조합이 추가됐다.

이동 기준은 실제 글자 영역

placementBoxMap이 글머리 기호를 포함한 글자 영역을 계산한다. method 7·8은 이 영역으로 유닛의 겹침 깊이와 이동 여유를 구한다.

축소 전에 먼저 실측

대표 조합의 검증 경로는 축소 전 문서를 렌더하고, method 5·7·8로 겹침을 다시 처리한다. 실측으로 통과한 문서는 method 6을 생략한다.

SUCCESS도 최대 10% 축소 허용

현재 SUCCESS의 최소 폰트 배율은 0.90이다. 신규 겹침·넘침·유휴 여백 조건도 충족해야 한다. 1.00만 허용하던 v0.6과 다르다.

근거: 내부 후보 레지스트리 · 실측 점유 영역 · 대표 조합·실측 검증 · 최종 판정

§1

전체 실행 흐름

대표 조합은 3 → 1 ↔ 2 → 5 → 4 → 7 → 8 → 6이다. 다만 예측 경로와 실측 검증 경로는 실행 방식이 다르다. 모든 단계가 매번 문서를 바꾸는 것은 아니며, 처리 대상과 검사 결과에 따라 건너뛴다.

대표 조합 3+1+2+5+4+7+8+6 — 진입점 두 개 공통 입력 rendered.json + original.json 좌표계 tight · baseTight · outlines · 원안의 소속·계층 기본 배치 3 → 1 ↔ 2 → 5 → 4 2는 최대 3회 · 1은 최대 4회 · 4는 밴드 분리만 여백 활용 7 세로 이동 → 8 가로 이동 신규 겹침 필수 · 스택 idle ≥ 0.5 · 짝 밀기 0 A · replaceLayout / run — 예측 경로 (동기) method 6 · 자체 판정이 FAIL일 때만 축소 배율 0.05 단계 · 시도마다 3+1+2+5+4 재실행 7·8이 실제로 이동했나? 아니오 · 비교 없음 6 결과를 그대로 쓴다 예 · betterOf 비교 6 결과 ↔ 4까지+6 결과 예측 결과 · replaceLayout 반환 공통 외곽선 보정은 실행하지 않는다 뷰어는 이 결과를 먼저 표시한다 B · replaceLayoutVerified — 실측 검증 (비동기) 1 · 축소 전 결과를 만든다 7·8이 이동했다면 기본 배치 결과와 betterOf 2 · 렌더 → runMethod5Verified 유닛 내부 겹침을 실측으로 정리한다 3 · 새 tight로 method 7 → 8 재시도 이동만 한다 · 이동했으면 다시 렌더 완전한 실측 판정이 SUCCESS 또는 ACCEPTABLE? 아니오 원래 입력에서 A 실행 예 · 축소 없이 반환 method 6을 건너뛴다 공통 실측 보정 fitActualTightToOutlines 외곽선 데이터가 있을 때 · 기본 10라운드
두 진입점은 같은 단계를 다르게 쓴다. A는 예측값으로 method 6까지 끝내고 동기 반환하며 공통 외곽선 보정을 거치지 않는다. B는 축소 전에 실제 렌더로 판정하고, 통과하면 method 6을 건너뛴다. 통과하지 못하면 원래 입력에서 A를 실행한다. 주황 점선은 실패했을 때 되돌아가는 경로다.

단계별 상세

공통 입력

대치 직후 문서와 원안 기준

rendered.json을 입력으로 받고 original.json의 좌표계를 맞춘다. 원안에서 텍스트 소속과 컨테이너 계층을 정해 전달한다.

추가 데이터: tight · baseTight · outlines

기본 배치 · 3 → 1 ↔ 2 → 5 → 4

크기 조정 → 유닛 내부 정리 → 유닛 간 분리

  1. method 3 — 가로 넘침을 줄인다.
  2. method 1 ↔ 2 — 세로 여백을 재분배하고 필요한 텍스트 폭을 넓힌다. 가로 단계는 최대 3회이며, 상한에 도달하면 세로 단계로 한 번 더 마무리한다.
  3. method 5 — 같은 유닛 안에서 겹친 텍스트의 폭과 세로 배치를 조정한다.
  4. method 4 — 신규 겹침으로 묶인 밴드를 분리해 유닛을 세로로 배치한다. 짝 밀기는 여기서 실행하지 않는다.
여백 활용 · 7 → 8

세로 이동 후 가로 이동

method 7은 세로 빈 구간에 함께 배치하거나 두 유닛의 위·아래 이동량을 나눠 겹침을 줄인다. method 8은 가로로 분리할 쌍을 처리한다.

method 7의 스택 재배치는 유휴 비율 ≥ 0.5, 짝 밀기는 기본 문턱 0이다. method 8은 유휴 비율 ≥ 0.5일 때 실행한다. 모두 원안 대비 신규 겹침이 있어야 한다.

A · replaceLayout / run

예측 경로

  1. 7·8의 결과에 method 6을 적용한다. 자체 판정이 FAIL인 경우에만 축소를 시도한다.
  2. 7·8이 실제로 이동했다면, 4까지의 결과에도 method 6을 적용해 비교 경로를 만든다.
  3. betterOf가 두 결과를 비교한다. 7·8이 이동하지 않았다면 비교 경로는 실행하지 않는다.

각 축소 시도에서 다시 실행하는 범위는 3+1+2+5+4다.

B · replaceLayoutVerified / runVerified

실측 검증 경로

  1. 축소 전 결과를 만든다. 7·8이 이동했다면 기본 배치 결과와 먼저 비교한다.
  2. 원안과 원안 tight가 있으면 렌더 후 runMethod5Verified로 유닛 내부를 정리한다.
  3. 새 tight로 method 7 → 8의 이동만 다시 시도한다. 이동했다면 결과를 다시 렌더한다.
  4. 완전한 실측으로 SUCCESS 또는 ACCEPTABLE이면 축소 없이 반환한다. 아니면 원래 입력에서 A 경로를 실행한다.
공통 실측 보정 · fitActualTightToOutlines

최종 글자 영역을 도형 외곽선과 다시 비교

실측 → 내부 겹침 정리 → 이동 전용 보정 → 필요 시 후보 재실행 → 위치·도형 크기 보정 → 마지막 폰트 축소 순으로 확인한다. 문서가 바뀌면 다시 렌더한다.

외곽선 데이터가 있을 때 실행한다. 반복 상한에 도달하거나 해결하지 못한 채 반환할 수도 있으므로, 반환됐다는 사실만으로 SUCCESS를 뜻하지 않는다.

대표 조합의 두 경로를 구분한 흐름도. 공통 실측 보정은 replaceLayoutVerified에서 호출하며, 동기 함수 replaceLayout에는 포함되지 않는다. 뷰어는 먼저 예측 결과를 표시한 뒤 검증 결과를 반영한다.

근거: 예측 진입점 · 실측 검증 진입점 · 1↔2 반복 · 축소 전 비교 · 대표 조합·예측 · 대표 조합·실측 검증 · 공통 외곽선 보정 · 뷰어의 검증 호출

§2

배치 단위와 판정 기준

용어의미주의할 점
유닛 · unit함께 움직이는 단위. 배경 도형과 그 안의 텍스트 등을 묶는다.같은 유닛의 글자를 통째로 옮기면 내부 겹침은 줄지 않는다. 내부는 method 5가 처리한다.
밴드 · band처리 축의 구간이 겹쳐 함께 배치되는 유닛 묶음.method 4는 splitUnitPairs로 특정 쌍이 같은 밴드로 묶이지 않게 한다.
체인 · chain같은 축의 공간을 나눠 쓰는 배치 묶음.axisChains는 해당 시점의 좌표와 분리 조건으로 밴드·체인을 계산한다.
프레임 · frame노드의 좌표·크기 또는 배치 경계로 사용하는 박스.실제 글자가 차지하는 영역과는 다를 수 있다.
소속·계층containers와 containerParents. 원안에서 텍스트 소속과 컨테이너의 부모 관계를 정한다.기본 경로는 자동 추론한다. 승인된 정답 관계를 주입하는 fixedContainers는 별도의 평가 용도다.

파이프라인 전체에서 모든 관계를 한 번만 계산하는 것은 아니다

replaceLayout이 원안에서 소속·계층을 정해 전달하고, 개별 단계가 freezeRelations로 필요한 배치 그래프를 만든다. axisChains는 이동·확장 뒤 좌표로 다시 계산할 수 있다. v0.6 흐름도의 “이후 재계산 없음”을 전체 실행의 규칙으로 읽으면 현재 코드와 맞지 않는다.

단계별 검사와 최종 판정은 구분한다

이동을 채택할 때는 단계 입력보다 다른 겹침 쌍을 키우지 않았는지, 원안 대비 신규 겹침이 감소했는지, 넘침이 늘지 않았는지 등을 검사한다. 검사 항목과 허용 오차는 단계마다 다르다. 예를 들어 method 5의 실측 경로는 측정 누락, 유닛 내부 잔여 겹침, 페이지 잘림 증가도 확인한다.

최종 판정 · verdict.ts 결과 지표와 원안 기준이 있나? requireComplete면 tight 누락·외곽선 불완전도 여기서 걸린다 아니오 UNKNOWN 지표·기준 없음 UNSOLVABLE 해답 불가 사유 명시 예 FAIL 조건 넷 중 하나라도 걸리나? 넘침 노드 > 원안 · 넘침 면적 > 원안의 110% 신규 겹침 쌍 존재 · 최소 폰트 배율 < 0.70 예 FAIL 넷 중 하나면 성립 아니오 폰트·여백 조건을 둘 다 채우나? 최소 폰트 배율 ≥ 0.90 유휴 여백 비율 ≤ 원안 · VERDICT_EPS 0.0005 예 한쪽이라도 미달 SUCCESS 넘침 0을 뜻하진 않는다 ACCEPTABLE FAIL은 아닌 나머지 최소 폰트 배율 하나만 떼어 보면 FAIL 구간 ACCEPTABLE 가능 SUCCESS 가능 0.600.70 0.901.00 가독성 하한 (기본) SUCCESS 하한 원안 유지 배율만으로 정해지지 않는다 — 겹침·넘침·여백 조건을 모두 함께 본다. 이 축은 배율이 허용하는 최대치다.
위는 판정이 정해지는 순서, 아래는 그중 폰트 배율 축만 떼어 본 것이다. v0.6에서 SUCCESS의 하한은 1.00이었다 — 현재는 0.90이라 오른쪽 초록 구간이 10% 넓어졌다. 배율이 구간 안에 들어와도 나머지 조건에서 떨어질 수 있다.
최종 라벨현재 조건
UNKNOWN결과 지표나 원안 기준이 없다. requireComplete: true이면 tight 누락이나 외곽선 판정 불완전도 UNKNOWN이다.
FAIL넘침 노드가 원안보다 많거나, 넘침 면적이 원안의 110%를 초과하거나, 신규 겹침 쌍이 있거나, 최소 폰트 배율이 가독성 하한보다 작다. 기본 하한은 0.70이다.
SUCCESSFAIL 조건이 없고, 최소 폰트 배율이 0.90 이상이며, 유휴 여백 비율이 원안 이하이다. 배율·여백 비교에는 VERDICT_EPS = 0.0005를 적용한다.
ACCEPTABLEFAIL은 아니지만 SUCCESS의 폰트 또는 여백 조건을 충족하지 못한다.
UNSOLVABLE결과 지표가 없고 규칙 안에서 해답을 만들 수 없다는 사유가 명시돼 있다. 일반적인 실행 실패와는 구분한다.

SUCCESS가 반드시 “넘침 0”이나 “폰트 크기 유지”를 뜻하지는 않는다. 근거: 배치 관계 구성 · 밴드·체인 계산 · 예측 진입점 · 이동 결과 검사 · method 5 실측 검증 · 최종 판정

§3

연산자 8개와 등록 후보 17개

왜 17개인가?

단독 연산자 8개 + 합성 후보 9개 = 내부 등록 후보 17개다. 합성 후보는 기존 method들을 조합한 실행 경로다. method 9가 생긴 것이 아니다. 화면에서는 이 목록을 모두 노출하지 않고 Adaptive Relayout과 tidyLayout 두 항목만 보여 준다.

연산자 8개 method 1 … 8 + 합성 후보 9개 기존 method 의 조합 등록 후보 17개 내부 레지스트리 화면 선택지 2개 Adaptive Relayout · tidyLayout method 9 가 생긴 것이 아니다 — 조합이 하나 늘었다 열 = 실행 순서 312 547 86 단독 8 — 연산자 하나만 등록 단독 · method 1 단독 · method 2 단독 · method 3 단독 · method 4 단독 · method 5 단독 · method 6 단독 · method 7 단독 · method 8 합성 9 — 기존 method 를 이어 붙인 실행 경로 1+2 세로 조정 ↔ 가로 확장 3+1+2 가로 넘침 먼저 3+1+2+4 유닛 간 밴드 분리 추가 3+1+2+5+4 내부 정리 후 분리 · method 6의 재실행 범위 3+1+2+5+4+7+8 v0.7 추가 — 축소 없이 여백만 3+1+2+5+4+6 기본 배치 후 축소 3+1+2+5+4+7+6 세로 여백 후 축소 3+1+2+5+4+8+6 가로 여백 후 축소 3+1+2+5+4+7+8+6 대표 조합 — Adaptive Relayout 3 가로 넘침 · 1↔2 세로·가로 재분배 · 5 유닛 내부 · 4 밴드 분리 · 7·8 여백 이동 · 6 폰트 축소 열을 실행 순서로 세웠으므로 각 행은 왼쪽에서 오른쪽으로 그대로 읽으면 된다. tidyLayout 은 이 17개에 들어가지 않는다.
17 = 단독 8 + 합성 9. 열을 번호순이 아니라 실행 순서(3 · 1 · 2 · 5 · 4 · 7 · 8 · 6)로 세웠기 때문에, 각 행의 점을 왼쪽부터 이으면 그 후보가 실제로 도는 순서가 된다. 라벤더로 강조한 두 행이 v0.7에서 중요한 후보 — 위쪽은 축소 없이 여백만 쓰는 새 조합, 아래쪽이 화면의 Adaptive Relayout에 연결된 대표 조합이다.

8개 연산자의 역할

연산자처리 대상주요 동작현재 구현에서 볼 점
method 1세로 공간 부족컨테이너 높이와 세로 여백을 조정하고 체인을 배치한다.가로 폭을 유지한 채 세로 문제를 먼저 푼다.
method 2넘침·좁은 열·전달받은 충돌 텍스트 등이웃과 도형 경계 안에서 폭을 넓혀 줄바꿈을 줄인다.폭이 바뀌면 기존 tight를 그대로 사용할 수 없다. 검증 경로에서는 재렌더한다.
method 3가로 넘침도형 확장과 텍스트 폭 조정을 검토한다.원안의 컨테이너 관계와 외곽선 한계를 함께 사용한다.
method 4유닛 간 신규 겹침밴드를 분리하고 여백 재분배·placeRow로 세로 배치한다.짝 밀기 제거. 밴드 분리만 담당한다.
method 5같은 유닛 안의 텍스트 겹침폭 확장, 세로 재배치, 필요한 공간 확보를 시도한다.실측 경로는 먼저 현재 폭으로 재배치하고, 실패하면 폭을 넓혀 다시 측정·배치한다.
method 6앞 단계에서 해결하지 못한 결함대상 텍스트의 폰트를 단계적으로 줄이고 배치를 다시 계산한다.기본 상대 하한 0.70과 페이지 높이 기반 하한 중 더 엄격한 값을 적용한다.
method 7유닛 간 세로 분리 대상세로 빈 구간에 스택으로 배치하거나 위·아래 이동량을 나눠 겹침을 줄인다.스택과 짝 밀기의 여백 문턱이 다르다. 실측 점유 영역을 사용한다.
method 8유닛 간 가로 분리 대상좌우 이동 여유를 계산해 한쪽 또는 양쪽 유닛을 이동한다.세로 위치와 크기를 유지한다. 실측 점유 영역을 사용한다.

등록된 합성 후보 9개

조합의미
1+2세로 조정과 가로 확장의 반복
3+1+2가로 넘침 조정을 먼저 수행
3+1+2+4유닛 간 밴드 분리 추가
3+1+2+5+4유닛 내부 정리 후 유닛 간 분리
3+1+2+5+4+7+8v0.6 이후 추가. 폰트 축소 없이 세로·가로 여백을 활용하는 조합
3+1+2+5+4+6기본 배치 후 폰트 축소
3+1+2+5+4+7+6세로 여백 활용 후 폰트 축소
3+1+2+5+4+8+6가로 여백 활용 후 폰트 축소
3+1+2+5+4+7+8+6현재 Adaptive Relayout에 연결된 대표 조합

표에서는 반복되는 method 접두어를 생략했다. 실제 등록 키는 method3+method1+… 형식이다. tidyLayout은 이 17개에 포함되지 않는다. 근거: 내부 후보 레지스트리 · 화면 선택지 · method 1 · method 2 · method 3 · method 4 · method 5 실측 검증 · method 6

§4

method 7·8은 겹침 방향을 나눠 처리한다

두 연산자는 겹친 유닛의 실측 점유 영역에서 가로 겹침 길이 ix, 세로 겹침 길이 iy, 한 열에 포함되는지 여부를 계산한다. 같은 유닛 또는 부모·자식 관계인 유닛은 이 분리 대상에서 제외한다.

penetrationOf(a, b)현재 동일한 수식이 두 파일에 있음
columnContained = ix >= min(a.width, b.width) - 1 세로 분리 후보: iy > 0 && (iy <= ix || columnContained) 가로 분리 후보: ix > 0 && iy > ix && !columnContained
① iy ≤ ix — 납작한 겹침 a b ix iy ↕ 위아래로 민다 method 7 · 세로 분리 iy > 0 이고 iy ≤ ix 위아래로 벌릴 자리를 찾는다 ② iy > ix — 길쭉한 겹침 a b ix iy ↔ 좌우로 민다 method 8 · 가로 분리 ix > 0, iy > ix, 한 열 아님 좌우 이동 여유를 계산한다 ③ 한 열에 포함 — columnContained a b ix iy ↕ 위아래로 민다 method 7 · 세로 분리 ix ≥ min(a.width, b.width) − 1 iy > ix 여도 세로로 되돌린다 겹침 영역(주황 점선)이 납작하면 위아래로, 길쭉하면 좌우로 민다. 단 한 열에 갇힌 쌍은 좌우로 뺄 자리가 없으므로 다시 세로다.
방향은 겹침 영역의 모양이 정한다. 같은 유닛이거나 부모·자식 관계인 쌍은 애초에 분리 대상에서 빠진다. 세 조건은 같은 좌표에서 서로 겹치지 않지만, 7이 먼저 움직인 뒤 8이 다시 판별하므로 실행 시점마다 대상이 달라진다.

method 7 · 세로

  1. 같은 방향으로 분리할 유닛 쌍을 연결 성분으로 묶는다.
  2. 스택 조건과 여백 문턱을 충족하면 verticalCorridor 안에서 placeRow로 함께 배치한다.
  3. 스택 배치가 불가능하면 위·아래 여유에 맞춰 균형 이동 → 한쪽 이동 → 부분 이동 후보를 만든다.
  4. 검사 조건을 통과하는 이동을 채택한다. 짝 밀기는 클러스터마다 최대 20회 시도한다.

method 8 · 가로

  1. 유휴 여백 비율이 기본 문턱 0.5 이상인지 확인한다.
  2. 현재 좌표에서 가로 분리 대상과 좌우 이동 여유를 계산한다.
  3. 한쪽 또는 양쪽 이동 후보를 만들고 검사 조건을 통과한 결과를 채택한다.
  4. 실패한 쌍은 같은 실행에서 반복 시도하지 않는다. 전체 반복 상한은 20회다.

방향 구분이 모든 겹침의 해결을 보장하지는 않는다

동일한 좌표에서 세로·가로 조건은 겹치지 않는다. 하지만 7이 이동한 뒤 8이 다시 판별하므로 처리 대상은 실행 시점마다 달라진다. 여백 부족, 측정 누락, 부모·자식 관계, 결과 검사 때문에 이동하지 않는 쌍도 있다. v0.6의 “한 쌍을 정확히 한쪽이 처리한다”는 표현은 분류와 실제 해결을 구분해 읽어야 한다.

근거: method 7 방향 판별 · method 8 방향 판별 · method 7 · method 8

§5

v0.6 이후 달라진 점

항목v0.6의 설명현재 코드
등록 후보단독 8 + 합성 8 = 16개축소 없는 3+1+2+5+4+7+8을 추가해 17개. 연산자는 8개 그대로다.
세로 짝 밀기method 4의 대칭 이동과 method 7의 여유 기반 이동이 중복됨method 7로 통합. method 4에는 밴드 분리만 남았다.
method 7 발동 조건후보 전체에 유휴 비율 ≥ 0.5 적용스택은 idleThreshold = 0.5, 짝 밀기는 nudgeThreshold = 0. 원안과 신규 겹침 조건은 유지한다.
겹침 상수OVERLAP_EPS가 100과 1의 서로 다른 의미로 쓰임겹침 최소 면적 OVERLAP_MIN_AREA = 100과 이동 단계의 증가 허용 오차 OVERLAP_GROWTH_EPS = 1로 이름을 구분했다.
이동 계산 영역실측 tight를 지표·게이트에 전달하는 흐름 중심placementBoxMap을 추가해 대상 선택·겹침 깊이·스택 높이·장애물 여유도 실제 글자 영역으로 계산한다.
유닛 내부 겹침주로 프레임 기반 예측으로 처리runMethod5Verified에서 실측으로 재배치한다. 폭을 바꾸면 다시 렌더하고, 유닛 내부가 모두 해결됐는지 확인한다.
축소 전 확인대표 조합의 마지막에 method 6 실행검증 경로에서 축소 전 실제 렌더 판정을 추가했다. 이미 통과한 결과는 불필요한 축소를 피한다.
외곽선 보정 후 겹침method 6 내부 재호출에서 7·8이 빠지는 구조 지적그 재호출 범위는 여전히 3+1+2+5+4다. 별도로 공통 검증 루프가 새 측정값을 받아 refineMeasured로 7·8을 재실행한다.
SUCCESS 폰트 기준최소 폰트 배율 = 1.000.90 이상. FAIL의 기본 하한 0.70과는 별개다.
화면의 후보 선택전체 후보 목록과 선택기 설계를 함께 설명화면은 Adaptive Relayout / tidyLayout으로 단순화했다. 실험 후보는 내부 레지스트리에 유지한다.

아직 남아 있는 중복과 한계

penetrationOf와 COLUMN_EPS는 여전히 method 7·8에 각각 정의돼 있다. 공통 함수로 추출된 것은 아니다. method 6의 동기 경로도 계속 프레임 대용 측정값을 사용한다. 따라서 예측 경로 전체가 실측 기반으로 바뀌었다고 설명하면 안 된다.

근거: 내부 후보 레지스트리 · method 4 · method 7 · 겹침 최소 면적 · 실측 점유 영역 · method 5 실측 검증 · 대표 조합·실측 검증 · 공통 외곽선 보정 · 최종 판정 · 화면 선택지

§6

반복 상한과 실행 비용

v0.6의 1,177건 실행 빈도와 문서 크기는 과거 코퍼스의 측정값이다. 현재는 실제 렌더와 재측정이 추가됐으므로 당시 비율로 현재 병목을 단정할 수 없다. 여기서는 실행 코드에서 확인한 반복 범위를 정리한다.

구간현재 반복 범위비용을 읽는 방법
method 1 ↔ 2method 2 최대 3회, method 1 최대 4회3번의 가로 조정 뒤에도 변화가 있으면 세로 단계로 한 번 더 마무리한다. 단순히 “전체 최대 3회”라고 쓰면 부정확하다.
method 7스택 배치 시도 후 클러스터별 짝 밀기 최대 20회이동 시도마다 점유 영역과 겹침을 다시 계산한다. 20회는 문서 전체의 상한이 아니다.
method 8가로 분리 반복 최대 20회각 반복에서 남은 쌍을 재탐색하고, 한 쌍에 여러 이동 후보를 검사할 수 있다.
method 6배율을 0.05씩 낮춘다. 기본 상대 하한 0.70이면 최대 6개 배율 단계노드별 페이지 높이 하한이 더 엄격하면 더 일찍 끝난다. 각 시도에서 기본 조합 3+1+2+5+4을 다시 실행한다. 설정을 바꾸면 횟수도 달라진다.
betterOf두 문서의 지표를 비교함수 자체가 method 6을 실행하지는 않는다. 호출자가 비교할 두 결과를 만드는 데 추가 비용이 든다.
공통 외곽선 보정기본 최대 10라운드 + 반복 종료 후 최종 실측내부 method 5 검증에도 렌더 호출이 있으므로 “렌더 최대 11회”로 해석하면 안 된다.
비용이 곱해지는 자리 — 안쪽 상자는 바깥 상자가 한 번 돌 때마다 다시 돈다 공통 외곽선 보정 · 기본 10라운드 후보 재실행 · 같은 문서엔 1회 method 6 · 배율 0.05 단계 (기본 하한 0.70 → 최대 6단계) 기본 조합 3+1+2+5+4 재실행 1 ↔ 2 · method 2 최대 3회 · method 1 최대 4회 여기 method 7·8이 없다 method 6 이 다시 도는 범위는 3+1+2+5+4 그대로다. 축소로 좌표가 바뀌어도 여백 이동은 없다. 7·8은 바깥에서 다시 돈다 공통 루프의 refineMeasured 가 새 tight 로 7 → 8 을 재실행한다. method 7 클러스터당 최대 20회 method 8 전체 최대 20회 각 라운드에는 렌더 호출이 더해진다. 후보는 입력을 바꾸지 않고 새 문서를 반환하므로 시도마다 structuredClone 사본이 생긴다.
횟수 자체보다 어디에 어디가 들어 있는지가 비용을 정한다. 가장 안쪽 1 ↔ 2는 바깥 네 겹이 한 번 돌 때마다 다시 돈다. 반대로 method 7·8은 method 6 안에 들어 있지 않다 — 축소가 끝난 뒤 공통 루프에서 따로 재실행된다.

계산량

leaf 수를 L이라 할 때 겹침 쌍 비교는 최대 O(L²) 규모다. placeRow는 정렬을 포함해 O(k log k)이며, 여백 재분배는 반복 하한 보정 비용이 있다. 전체 비용에는 이 계산들의 호출 횟수와 외곽선 처리·렌더 시간이 더해진다.

메모리와 복제

후보는 입력을 직접 바꾸지 않고 새 문서를 반환한다. 이동·확장·축소 시도마다 structuredClone 사본을 만들고, 두 경로 비교에서는 결과 문서들을 동시에 보관한다. 실제 메모리 사용량은 JSON 크기만으로 환산할 수 없다.

현재 실행 시간·메모리는 이 문서 갱신 과정에서 새로 벤치마크하지 않았다. 근거: 1↔2 반복 · method 7 · method 8 · method 6 · 두 경로 비교 · 공통 외곽선 보정 · 1차원 배치 · 여백 재분배

§7

두 경로 비교와 전체 후보 선택

betterOf는 받은 두 문서를 비교하는 함수다. 등록 후보 17개를 모두 실행해 최선을 고르는 선택기는 아니다. 대표 조합에서는 여백 활용 단계를 거친 결과와 이를 생략한 결과를 비교하는 데 사용한다.

betterOf — 사전순 비교 차이가 나면 그 줄에서 끝 · 동점이면 한 칸 내려간다 1 · 신규 겹침 쌍 적을수록 ↓ 2 · 신규 겹침 면적 적을수록 ↓ 3 · 넘침 노드 적을수록 ↓ 4 · 넘침 면적 적을수록 ↓ 5 · 최소 폰트 배율 클수록 ↑ 6 · 정렬선 유지 비율 클수록 ↑ 여섯 지표가 모두 같으면 incumbent(기준 경로)를 유지한다. 예시 — 앞 지표가 이기면 뒤 지표는 진다 후보 X · 신규 겹침 0쌍 · 최소 폰트 0.75 후보 Y · 신규 겹침 1쌍 · 최소 폰트 1.00 → 1번 지표에서 갈리므로 X를 고른다 폰트가 25% 작아져도 5번까지 내려가지 않는다 비교하지 않는 것 판정 라벨 · 유휴 여백 비율 · 총 이동 거리 “모든 지표가 나빠지지 않는다”, “항상 더 좋은 라벨을 고른다”, “디자인 품질을 보장한다”는 뜻이 아니다. 등록 후보 17개를 전부 돌려 최선을 찾는 선택기도 아니다.
계단이 오른쪽으로 밀리는 만큼이 “앞이 동점일 때만”이라는 조건이다. 대표 조합에서는 여백 활용(7·8)을 거친 결과와 그것을 생략한 결과, 이 두 문서만 비교한다.
구분현재 동작
단계별 검사한 번의 이동·확장 결과를 채택할지 결정한다. 다른 쌍의 겹침 증가 등을 검사한다.
betterOf두 경로의 결과를 여섯 지표로 비교한다. 판정 라벨, 유휴 여백, 총 이동 거리는 직접 비교하지 않는다.
뷰어의 Adaptive Relayout현재 대표 조합 한 개를 연결한 표시 이름이다. 케이스마다 전체 후보를 자동 탐색한다는 뜻은 아니다.
전체 후보 선택기 설계여러 후보의 결과를 비교하는 별도 설계다. 현재 METHODS와 진입점에는 이를 실행하는 선택 후보가 등록돼 있지 않다.

비교 결과의 보장 범위를 넓혀 해석하지 않는다

여섯 지표를 사전순으로 비교하므로 앞 지표가 개선되면 뒤 지표가 악화돼도 도전 경로를 고를 수 있다. “모든 지표가 나빠지지 않는다”, “항상 더 좋은 판정 라벨을 선택한다”, “전체 디자인 품질을 보장한다”는 의미는 아니다.

근거: 두 경로 비교 · 축소 전 비교 · 대표 조합·예측 · 화면 선택지 · 내부 후보 레지스트리

§8

이동 가능한 여백 찾기

idleMarginRatio는 페이지 전체의 유휴 여백 비율이다. 실제 이동에는 “얼마나 비었는가”뿐 아니라 어느 방향으로 얼마나 움직일 수 있는가가 필요하다. idleSpace.ts가 다음 세 가지를 제공한다.

obstaclesFor

이동을 막는 노드의 점유 영역을 수집한다. 자기 유닛, 장식, 페이지 배경 등 제외 조건을 적용한다. method 7·8에서는 텍스트의 실제 영역이 반영된 지도를 넘긴다.

directionalSlack

상자 하나가 장애물과 페이지 경계에 닿기 전까지 이동할 수 있는 위·아래·왼쪽·오른쪽 거리를 구한다. 이 거리로 이동 후보를 만든다.

verticalCorridor

클러스터가 사용할 수 있는 연속된 세로 빈 구간을 찾는다. method 7은 이 구간에 유닛들을 placeRow로 배치할 수 있는지 확인한다.

directionalSlack — 한 상자의 네 방향 여유 페이지 장애물 유닛 위 여유 아래 여유 왼쪽 여유 오른쪽 장애물과 페이지 경계에 닿기 전까지의 거리다. obstaclesFor 가 자기 유닛 · 장식 · 페이지 배경을 빼고 모은다. verticalCorridor — 클러스터가 들어갈 빈 구간 연속된 세로 빈 구간 placeRow 로 함께 배치 클러스터가 통째로 들어가는 구간만 후보다. 못 들어가면 균형 이동 → 한쪽 이동 → 부분 이동 순으로 넘어간다.
idleMarginRatio는 페이지 전체가 얼마나 비었는가만 말한다. 실제로 움직이려면 어느 방향으로 얼마나가 필요해서 이 두 함수가 따로 있다. 문턱(0.5 · 0)을 넘어도 여기서 여유가 안 나오거나 결과 검사에 걸리면 문서는 그대로다.

현재의 여백 문턱

단계공통 조건여백 조건
method 7 · 스택 배치원안 존재 + 신규 겹침 존재 + 이동 가능한 점유 영역idleMarginRatio ≥ idleThreshold · 기본 0.5
method 7 · 짝 밀기위와 동일idleMarginRatio ≥ nudgeThreshold · 기본 0
method 8 · 가로 이동원안 존재 + 신규 겹침 존재 + 가로 분리 대상idleMarginRatio ≥ idleThreshold · 기본 0.5

이 문턱은 여백을 그 비율까지 줄이라는 목표가 아니다. 특정 이동 방법을 시도할 조건이다. 문턱을 통과해도 실제 이동 여유가 없거나 결과 검사에 실패하면 문서는 유지된다.

근거: 장애물 조회 · 방향별 이동 여유 · 세로 빈 구간 · method 7 · method 8

§9

실측과 외곽선 보정

좌표를 쓰는 박스와 이동 여유를 재는 박스를 분리한다

데이터쓰임유효성
absBoxMap실제 노드 프레임. 부모·자식 관계와 좌표 쓰기의 기준배치 그래프를 만들거나 translateNode로 좌표를 반영할 때 사용한다.
tight + visualBoxMap렌더러가 측정한 글자 영역에 글머리 기호 등을 반영프레임의 빈 공간과 실제 글자 영역을 구분한다.
placementBoxMap텍스트는 visual box, 나머지는 프레임으로 구성한 이동 계산용 지도텍스트를 측정하지 못했다면 null을 반환해 이동을 보류한다. 빈 텍스트는 점유 영역에서 제외한다.
placementUnitBox유닛 전체가 차지하는 영역배경 도형의 범위를 유지하면서, 도형 밖으로 나온 실제 글자도 포함한다.
carryTightBoxes기존 실측값을 변경된 문서에 승계노드 프레임의 폭·높이가 허용 오차 안에서 같은 값만 승계한다. 텍스트 내용이나 폰트 변경을 별도로 검사하는 함수는 아니므로 새 측정을 대신하지 않는다.
frameBoxesAsTight실측 없이 프레임으로 계산하는 예측용 대체값method 6의 동기 경로 등에 남아 있다. 실제 측정 결과로 표시하면 안 된다.
같은 유닛 · 서로 다른 세 상자 ③ 배경 도형 ① ② 도형 아래끝 글자가 여기서 넘친다 텍스트를 측정하지 못하면 placementBoxMap 은 null 이다 프레임이나 빈 공간으로 임의 대체하지 않는다 — 새 측정값이 생긴 뒤에 이동을 다시 시도한다. 쓰임이 갈리는 자리 ① absBoxMap 노드 프레임. 부모·자식 관계와 좌표 쓰기의 기준 ② tight + visualBoxMap 렌더러가 잰 글자 영역에 글머리 기호를 반영한 값 ③ placementUnitBox 도형 범위를 지키면서 도형 밖 글자까지 포함한다 placementBoxMap 텍스트는 ②, 나머지는 ①로 짠 이동 계산용 지도 carryTightBoxes 폭·높이가 허용 오차 안에서 같을 때만 승계한다 frameBoxesAsTight 실측 없이 프레임으로 대체. method 6 동기 경로에 남아 있다
좌표를 쓰는 상자와 이동 여유를 재는 상자가 다르다. 프레임(①)은 글자보다 넓고, 글자(②)는 도형 밖으로 나가기도 한다. method 7·8은 ②가 반영된 지도로 겹침 깊이와 여유를 구하고, 유닛을 통째로 옮길 때는 ③을 쓴다.

“실측을 주지 않음”과 “제공된 실측이 불완전함”은 다르다

method 7·8에 judge를 주지 않으면 프레임 대용 경로로 실행할 수 있다. 반면 실측을 전달했는데 크기 변경 등으로 승계가 끊기거나 측정이 누락되면, placementBoxMap은 그 글자를 빈 공간이나 프레임으로 임의 대체하지 않는다. 새 측정값이 생긴 뒤 이동을 재시도한다.

공통 검증 루프의 실제 순서

fitActualTightToOutlines — 공통 검증 루프 1 렌더 · 비교 판정 통과 시 반환 2 유닛 내부 method5Verified 3 유닛 간 이동 refineMeasured 4 후보 재실행 같은 문서엔 1회 5 위치·도형 보정 기하 보정 제한 6 폰트 축소 0.05 단위 7 반복 종료 최종 실측 데이터가 완전하고 판정을 통과하면 즉시 반환 문서가 바뀌면 다시 렌더 — 기본 10라운드 해결하지 못해도 여기서 끝낸다 반환됐다는 사실만으로 SUCCESS 를 뜻하지 않는다. report.resolved 는 외곽선 문제와 측정 누락에 관한 값이다.
단계마다 문서가 바뀌면 다시 렌더한다. 그래서 “렌더 최대 11회”처럼 라운드 수로 렌더 횟수를 환산할 수 없다 — 2번 안에도 렌더 호출이 있다. 외곽선 데이터가 없으면 이 루프는 보정 없이 그대로 반환한다.
  1. 현재 문서를 렌더한다. 실제 글자 영역과 컨테이너 외곽선을 비교한다. 필요한 데이터가 완전하고 최종 판정을 통과하면 즉시 반환한다.
  2. 유닛 내부를 다시 확인한다. runMethod5Verified가 글자 높이와 줄바꿈을 실측해 겹침을 줄인다. 변경됐다면 다음 라운드에서 다시 측정한다.
  3. 유닛 간 이동을 다시 시도한다. 대표 조합의 refineMeasured가 새 tight로 method 7·8을 실행한다. 크기는 바꾸지 않으며, 이동한 결과도 다음 라운드에서 재렌더한다.
  4. 필요하면 후보를 다시 실행한다. 같은 문서에는 한 번만 실행하고, 외곽선 보정이나 축소로 문서가 달라지면 다시 허용한다.
  5. 외곽선 안으로 위치를 보정하거나 도형을 키운다. 같은 위치에서 무한히 확장하지 않도록 기하 보정 단계를 제한한다.
  6. 해결되지 않은 텍스트만 줄인다. 0.05 단위로 줄이되 원안 대비 하한을 확인한다. 폰트가 바뀌면 다시 렌더한다.
  7. 해결되지 않아도 반복을 끝낸다. 기본 10라운드 후 최종 실측을 남긴다. 외곽선 문제 해결 여부와 전체 판정은 서로 다른 값이다.

외곽선 데이터가 없으면 공통 루프는 보정 없이 반환한다. fitActualTightToOutlines의 report.resolved는 외곽선 문제와 측정 누락에 관한 값이며 전체 겹침·가독성·여백 통과를 보장하지 않는다. 근거: 실측 점유 영역 · 유닛 점유 영역 · 실측값 승계 · method 5 실측 검증 · 새 실측 후 이동 보정 · 공통 외곽선 보정

§10

최근 검증 기록

저장소의 2026-08-30 검증 보고서 기준

다음 값은 이번 문서 수정 중 새로 실행한 벤치마크가 아니다. 기준 커밋에 포함된 검증 보고서의 결과를 인용했다. 보고서는 전체 100개 중 평가 가능한 동일 88개를 비교하고, 12개는 제외했다고 명시한다. 원본 실행 JSON은 현재 작업트리에 없어 수치를 독립 재계산하지 않았다.

동일 88개 평가 범위SUCCESSACCEPTABLEFAILUNKNOWN
실측 점유 영역 적용 전671470
실측 점유 영역 적용 후701620
동일 88건 · 2026-08-30 검증 보고서 인용 SUCCESS ACCEPTABLE FAIL UNKNOWN · 전후 모두 0 적용 전 67 14 7 적용 후 70 16 2 022 446688 SUCCESS +3 · ACCEPTABLE +2 · FAIL −5 · 판정 하락 0건 · 신규 FAIL 0건 전체 100건 중 전·후 모두 평가 가능한 88건만 비교하고 12건은 제외했다. 이 문서 수정 중 새로 실행한 벤치마크가 아니다.
두 줄은 같은 88건을 같은 눈금으로 쌓은 것이라 길이가 같다. 값은 모두 막대 위에 직접 적었다. 줄어든 FAIL 5건이 실측 점유 영역으로 겹침을 없앤 사례다 — 아래 표의 다섯 케이스가 그중 일부다.

보고된 측정 시간: 2026-08-30 20:28:44~20:29:16 KST. 판정 하락 0건·신규 FAIL 0건. 이전 85건·96건·1,177건 결과와는 평가 범위 및 코드가 다르므로 직접 증감 비교하지 않는다.

실측 글자 영역으로 겹침을 줄인 5개 사례

케이스변경 전 신규 겹침변경 후판정최소 폰트 배율
56472913,167px² · 1쌍0ACCEPTABLE1.00
58363226,102px² · 1쌍0ACCEPTABLE1.00
65679929,069px² · 1쌍0SUCCESS1.00
67208192,808px² · 1쌍0ACCEPTABLE0.70
647861844,824px² · 4쌍0SUCCESS1.00

남은 문제와 검증 한계

보고서의 잔여 FAIL은 6248263의 도형 간 추가 겹침 399px²와 6690122의 도형·이미지 추가 겹침 3,633px²다. 좁은 열, 장식선 교차, 문단 간격 등 시각적 문제도 일부 남아 있다. 지표 통과를 디자인 품질 전체의 통과로 해석하지 않는다.

같은 보고서의 테스트 결과는 7,915개 통과·8개 실패다. 실패 8개는 수정 전 코드에서도 재현됐다고 기록돼 있다. 이 문서 갱신 과정에서는 재배치 구현이나 해당 테스트를 변경하지 않았다.

출처와 재현 절차: 2026-08-30 검증 보고서

§11

코드 지도

아래 링크는 모두 miricanvas-iui의 7b4354f62f79b66af80e0d05fb6cf12023431b75 커밋을 가리킨다. 작업트리의 인용 파일에 변경이 없음을 확인했으며, 줄 번호도 해당 파일에서 추출했다.

확인할 내용고정 커밋의 파일·줄 번호
내부 후보 레지스트리relayout-methods/index.ts:78
예측 진입점relayout-methods/index.ts:144
실측 검증 진입점relayout-methods/index.ts:200
화면 선택지viewer/afterSources.ts:27
실측 점유 영역relayout-core/placementBox.ts:14
유닛 점유 영역relayout-core/placementBox.ts:33
최종 판정relayout-core/verdict.ts:203
배치 관계 구성relayout-core/topology.ts:267
밴드·체인 계산relayout-core/topology.ts:581
1↔2 반복relayout-methods/method1Method2.ts:250
method 1relayout-methods/method1.ts:763
method 2relayout-methods/method2.ts:632
method 3relayout-methods/method3.ts:616
method 4relayout-methods/method4.ts:340
method 5 실측 검증relayout-methods/method5.ts:654
method 6relayout-methods/method6.ts:272
method 7relayout-methods/method7.ts:328
method 8relayout-methods/method8.ts:265
method 7 방향 판별relayout-methods/method7.ts:267
method 8 방향 판별relayout-methods/method8.ts:212
이동 결과 검사relayout-methods/method7.ts:290
두 경로 비교relayout-methods/betterOf.ts:44
축소 전 비교relayout-methods/method3Method1Method2Method5Method4Method7Method8Method6.ts:105
대표 조합·예측relayout-methods/method3Method1Method2Method5Method4Method7Method8Method6.ts:135
대표 조합·실측 검증relayout-methods/method3Method1Method2Method5Method4Method7Method8Method6.ts:233
새 실측 후 이동 보정relayout-methods/method3Method1Method2Method5Method4Method7Method8Method6.ts:200
공통 외곽선 보정relayout-methods/actualOutlineFit.ts:352
실측값 승계relayout-core/metrics.ts:245
겹침 최소 면적relayout-core/metrics.ts:128
장애물 조회relayout-core/idleSpace.ts:87
방향별 이동 여유relayout-core/idleSpace.ts:118
세로 빈 구간relayout-core/idleSpace.ts:180
1차원 배치relayout-core/placeRow.ts:112
여백 재분배relayout-core/gapAlgebra.ts:85
뷰어의 검증 호출viewer/PostReplaceRelayout.tsx:1309
2026-08-30 검증 보고서docs/visual-placement-verification-2026-08-30.md:1

과거 문서와 현재 코드를 함께 읽을 때

v0.6은 2026-08-28의 구현·측정 기록이다. v0.7은 여기에 후보 수, 단계별 여백 조건, 실측 점유 영역, 축소 전 검증, 공통 외곽선 보정, 현재 판정 규칙을 반영했다. 이전 문서의 코드 주소나 측정값을 현재 코드에 그대로 적용하지 않는다.