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 }