namespace SheetMe.Core.Layout;
/// 정렬 방향 — 가로 3종, 세로 3종
public enum AlignMode
{
Left,
CenterH,
Right,
Top,
CenterV,
Bottom,
}
///
/// 정렬 산술 — 항목 하나를 기준 사각형에 맞춘 새 자리를 낸다.
///
/// 왜 여기 있는가. 계산 자체는 짧지만 경계가 잔소리 같은 규칙 덩어리다 —
/// 반올림 방향, 기준보다 넓은 항목, 용지가 아래 페이지라 원점이 큰 경우.
/// 이걸 디자이너 쪽에서 검사하려면 케이스마다 실행취소 스냅샷을 하나씩 먹는데(스모크의 예산이 있다),
/// 순수 함수로 두면 단위 테스트가 공짜다.
///
/// 를 그대로 쓴다 — 새 타입을 만들 이유가 없고,
/// 스냅과 정렬이 같은 좌표 표현을 공유해야 결과가 갈라지지 않는다.
///
public static class AlignSolver
{
#region Methods
///
/// 을 에 맞춘 사각형(월드).
///
/// 기준보다 항목이 넓으면 가운데 맞춤이 음수를 낸다 — 그대로 둔다.
/// 가운데에 두려면 그래야 하고, 여기서 잘라 붙이면 "가운데 맞춤을 눌렀는데 가운데가 아니다"가 된다.
///
public static SnapRect Apply(SnapRect item, SnapRect reference, AlignMode mode) => mode switch
{
AlignMode.Left => item with { X = Round(reference.Left) },
AlignMode.Right => item with { X = Round(reference.Right - item.W) },
AlignMode.CenterH => item with { X = Round(reference.Left + (reference.W - item.W) / 2) },
AlignMode.Top => item with { Y = Round(reference.Top) },
AlignMode.Bottom => item with { Y = Round(reference.Bottom - item.H) },
AlignMode.CenterV => item with { Y = Round(reference.Top + (reference.H - item.H) / 2) },
_ => item,
};
/// 문자열 명령을 방향으로 — 모르는 값이면 null(호출부가 아무 일도 하지 않는다)
public static AlignMode? Parse(string mode) => mode switch
{
"Left" => AlignMode.Left,
"Right" => AlignMode.Right,
"CenterH" => AlignMode.CenterH,
"Top" => AlignMode.Top,
"Bottom" => AlignMode.Bottom,
"CenterV" => AlignMode.CenterV,
_ => null,
};
/// 세로 축 명령인가 — 여러 페이지에 걸친 선택에서 거부해야 할 것들
public static bool IsVertical(AlignMode mode)
=> mode is AlignMode.Top or AlignMode.CenterV or AlignMode.Bottom;
///
/// 좌표는 정수로 떨어뜨린다 — 저장 포맷(LegacyFormat)이 어차피 정수로 쓰므로
/// 여기서 정하지 않으면 "화면에선 맞았는데 다시 열면 1px 어긋남"이 된다.
///
/// 기본 ToEven 을 쓰지 않는 이유: 322.5→322, 323.5→324 로 방향이 값에 따라 뒤집힌다.
/// 폭이 제각각인 라벨 다섯 개를 가운데 맞춤하면 어떤 건 322, 어떤 건 323 에 앉는다.
///
private static double Round(double value) => Math.Round(value, MidpointRounding.AwayFromZero);
#endregion
}