Files
Aislo/B06_Section/logic/B06_Section_Label_Tiers.ts
T
eomsangdonandClaude Opus 5.5 501d9ca907 fix(B05 종단 표): 가까운 측점끼리 겹치는 값 칸은 그 자리에서 세움 (407-5)
- 계획고 · 지반고 · 측점 행 = 이웃과 겹치는 칸만 세로쓰기 · 두께 = 이웃 사이 틈 − 1(6 ~ 16 px) · 겹침 없는 칸 그대로 · 지우지 않음
- 곡선 L · R 행도 같은 판정(crowdedThickness) — 1.6 m 이웃에서 16 px 칸끼리 겹치던 것 고침
- logic/B06_Section_Label_Tiers crowdedThickness 새로 · 시험 test_407_4_label_tiers 더함

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013QyNXY2TUKXCFMwAca2APG
2026-10-18 09:37:50 +09:00

56 lines
2.7 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/* =============================================================================
* 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<number | null>,
width: number,
maxPx: number,
minPx = 6,
): Array<number | null> {
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<number | null> = 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;
}