- 계획고 · 지반고 · 측점 행 = 이웃과 겹치는 칸만 세로쓰기 · 두께 = 이웃 사이 틈 − 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
56 lines
2.7 KiB
TypeScript
56 lines
2.7 KiB
TypeScript
/* =============================================================================
|
||
* 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;
|
||
}
|