/* ============================================================================= * B06_Section/logic/B06_Section_Label_Tiers.ts * 종단 측점 · 구조물 이름표 비켜 그리기(PLAN 407-4) — 가까운 측점(1.6 m 등)끼리 이름이 겹치면 위 단으로 * 올림 · 이름은 지우지 않음. 그리는 곳 = `B06_Section_UI_Longitudinal`(B05 종단도 같은 함수). * B05 종단 아래 표 겹친 칸 세우기(407-5 · `crowdedThickness` · `B05_Profile_UI_Profile_Table`). * 시험 `resources/tester/test_407_4_label_tiers.py`. 화면 · 세션 없음(순수). * ========================================================================== */ /** 단 수 위 끝 — 넘으면 마지막 단에 겹쳐 둠(이름은 남김). */ export const MAX_LABEL_TIER = 3; /** 글 폭 짐작(px) — 한글 · 한자 = 글자 크기 · 그 밖 = 0.6 배. */ export const labelWidthPx = (text: string, fontPx: number): number => [...text].reduce((sum, ch) => sum + (ch.charCodeAt(0) > 0x2e80 ? fontPx : fontPx * 0.6), 0); /** 가운데 맞춤 이름표마다 단(0 = 제자리) — 왼쪽부터 겹치지 않는 가장 낮은 단 · `gapPx` = 글 사이 틈. */ export function labelTiers( items: ReadonlyArray<{ x: number; width: number }>, gapPx = 3, ): number[] { const order = items.map((_, index) => index).sort((a, b) => items[a].x - items[b].x); const rightEdge: number[] = []; // 단마다 마지막 이름표 오른쪽 끝 const tiers = items.map(() => 0); for (const index of order) { const { x, width } = items[index]; let tier = rightEdge.findIndex((edge) => edge + gapPx <= x - width / 2); if (tier < 0) tier = Math.min(rightEdge.length, MAX_LABEL_TIER); rightEdge[tier] = Math.max(rightEdge[tier] ?? -Infinity, x + width / 2); tiers[index] = tier; } return tiers; } /** 겹친 칸 세우기(407-5 · B05 종단 아래 표) — 같은 폭(`width`) 칸이 이웃과 가로로 겹치면 세운 칸 두께 * (px · 가까운 이웃까지 거리 − 1 · `minPx` ~ `maxPx`) · 안 겹치면 null(그대로) · x 가 null = 빈 칸(판정에서 뺌). */ export function crowdedThickness( xs: ReadonlyArray, width: number, maxPx: number, minPx = 6, ): Array { const order = xs .map((x, index) => ({ x, index })) .filter((item): item is { x: number; index: number } => item.x !== null) .sort((a, b) => a.x - b.x); const out: Array = xs.map(() => null); order.forEach(({ x, index }, k) => { const nearest = Math.min( k > 0 ? x - order[k - 1].x : Infinity, k < order.length - 1 ? order[k + 1].x - x : Infinity, ); if (nearest < width) out[index] = Math.max(minPx, Math.min(maxPx, Math.floor(nearest - 1))); }); return out; }