namespace QWERTYkez.ExcelProcessor.Editors; /// /// Определяет границы ячейки: верхнюю, нижнюю, левую, правую и диагональные. /// Каждая граница может иметь стиль и цвет. /// public readonly struct CellBorder : IEquatable { /// Верхняя граница. public BorderSide? TopBorder { get; init; } /// Нижняя граница. public BorderSide? BottomBorder { get; init; } /// Левая граница. public BorderSide? LeftBorder { get; init; } /// Правая граница. public BorderSide? RightBorder { get; init; } /// Диагональная граница «из левого верхнего в правый нижний» (\\). public BorderSide? DiagonalLeft { get; init; } /// Диагональная граница «из левого нижнего в правый верхний» (//). public BorderSide? DiagonalRight { get; init; } /// Создаёт элемент Border для Open XML. public Border? ToBorder() { bool hasAny = TopBorder.HasValue || BottomBorder.HasValue || LeftBorder.HasValue || RightBorder.HasValue || DiagonalLeft.HasValue || DiagonalRight.HasValue; if (!hasAny) return null; var border = new Border(); if (TopBorder.HasValue) border.TopBorder = TopBorder.Value.ToBorderElement(); if (BottomBorder.HasValue) border.BottomBorder = BottomBorder.Value.ToBorderElement(); if (LeftBorder.HasValue) border.LeftBorder = LeftBorder.Value.ToBorderElement(); if (RightBorder.HasValue) border.RightBorder = RightBorder.Value.ToBorderElement(); // Обработка диагональных границ if (DiagonalLeft.HasValue || DiagonalRight.HasValue) { // Диагональ \\ (из левого верхнего в правый нижний) = DiagonalDown border.DiagonalDown = DiagonalLeft.HasValue; // Диагональ // (из левого нижнего в правый верхний) = DiagonalUp border.DiagonalUp = DiagonalRight.HasValue; // Стиль и цвет берём из первой заданной диагонали (например, DiagonalLeft) var diagSide = (DiagonalLeft ?? DiagonalRight)!.Value; border.DiagonalBorder = diagSide.ToBorderElement(); } return border; } /// Создаёт CellBorder из элемента Border Open XML. public static CellBorder FromBorder(Border? border) { if (border == null) return default; var result = new CellBorder { TopBorder = BorderSide.FromBorderProperties(border.TopBorder), BottomBorder = BorderSide.FromBorderProperties(border.BottomBorder), LeftBorder = BorderSide.FromBorderProperties(border.LeftBorder), RightBorder = BorderSide.FromBorderProperties(border.RightBorder) }; // Диагональные границы if (border.DiagonalDown?.Value == true) result = result with { DiagonalLeft = BorderSide.FromBorderProperties(border.DiagonalBorder) }; if (border.DiagonalUp?.Value == true) result = result with { DiagonalRight = BorderSide.FromBorderProperties(border.DiagonalBorder) }; return result; } public override bool Equals(object? obj) => obj is CellBorder other && Equals(other); public bool Equals(CellBorder other) => this == other; public static bool operator ==(CellBorder left, CellBorder right) => Equals(left.TopBorder, right.TopBorder) && Equals(left.BottomBorder, right.BottomBorder) && Equals(left.LeftBorder, right.LeftBorder) && Equals(left.RightBorder, right.RightBorder) && Equals(left.DiagonalLeft, right.DiagonalLeft) && Equals(left.DiagonalRight, right.DiagonalRight); public static bool operator !=(CellBorder left, CellBorder right) => !(left == right); public override int GetHashCode() { unchecked { int hash = 17; hash = hash * 31 + (TopBorder?.GetHashCode() ?? 0); hash = hash * 31 + (BottomBorder?.GetHashCode() ?? 0); hash = hash * 31 + (LeftBorder?.GetHashCode() ?? 0); hash = hash * 31 + (RightBorder?.GetHashCode() ?? 0); hash = hash * 31 + (DiagonalLeft?.GetHashCode() ?? 0); hash = hash * 31 + (DiagonalRight?.GetHashCode() ?? 0); return hash; } } } /// Стиль и цвет границы. public readonly struct BorderSide : IEquatable { /// Стиль линии границы. public BorderStyle? Style { get; init; } /// Цвет границы. public ExColor? Color { get; init; } internal T ToBorderElement() where T : BorderPropertiesType, new() { var element = new T(); if (Style.HasValue) { element.Style = Style.Value switch { BorderStyle.Thin => BorderStyleValues.Thin, BorderStyle.Medium => BorderStyleValues.Medium, BorderStyle.Dashed => BorderStyleValues.Dashed, BorderStyle.Dotted => BorderStyleValues.Dotted, BorderStyle.Thick => BorderStyleValues.Thick, BorderStyle.Double => BorderStyleValues.Double, BorderStyle.Hair => BorderStyleValues.Hair, BorderStyle.MediumDashed => BorderStyleValues.MediumDashed, BorderStyle.DashDot => BorderStyleValues.DashDot, BorderStyle.MediumDashDot => BorderStyleValues.MediumDashDot, BorderStyle.DashDotDot => BorderStyleValues.DashDotDot, BorderStyle.MediumDashDotDot => BorderStyleValues.MediumDashDotDot, BorderStyle.SlantDashDot => BorderStyleValues.SlantDashDot, _ => throw new NotImplementedException(), }; } if (Color.HasValue && Color.Value.Color.HasValue) { var c = Color.Value.Color.Value; element.Color = new Color { Rgb = $"{c.R:X2}{c.G:X2}{c.B:X2}" }; } return element; } /// Создаёт BorderSide из элемента границы Open XML (TopBorder, BottomBorder, LeftBorder, RightBorder, DiagonalBorder). public static BorderSide FromBorderProperties(BorderPropertiesType? borderElement) { if (borderElement is null) return default; var result = new BorderSide(); if (borderElement.Style is not null) { result = result with { Style = MapBorderStyleFromExcel(borderElement.Style.Value) }; } if (borderElement.Color?.Rgb?.Value is { } rgb && rgb.Length >= 6) { var color = System.Drawing.Color.FromArgb( Convert.ToByte(rgb.Substring(0, 2), 16), Convert.ToByte(rgb.Substring(2, 2), 16), Convert.ToByte(rgb.Substring(4, 2), 16) ); result = result with { Color = new ExColor(color) }; } return result; } private static BorderStyle MapBorderStyleFromExcel(BorderStyleValues value) { if (value == BorderStyleValues.Thin) { return BorderStyle.Thin; } else if (value == BorderStyleValues.Medium) { return BorderStyle.Medium; } else if (value == BorderStyleValues.Dashed) { return BorderStyle.Dashed; } else if (value == BorderStyleValues.Dotted) { return BorderStyle.Dotted; } else if (value == BorderStyleValues.Thick) { return BorderStyle.Thick; } else if (value == BorderStyleValues.Double) { return BorderStyle.Double; } else if (value == BorderStyleValues.Hair) { return BorderStyle.Hair; } else if (value == BorderStyleValues.MediumDashed) { return BorderStyle.MediumDashed; } else if (value == BorderStyleValues.DashDot) { return BorderStyle.DashDot; } else if (value == BorderStyleValues.MediumDashDot) { return BorderStyle.MediumDashDot; } else if (value == BorderStyleValues.DashDotDot) { return BorderStyle.DashDotDot; } else if (value == BorderStyleValues.MediumDashDotDot) { return BorderStyle.MediumDashDotDot; } else if (value == BorderStyleValues.SlantDashDot) { return BorderStyle.SlantDashDot; } else throw new NotSupportedException($"Unsupported border style: {value}"); } public override bool Equals(object? obj) => obj is BorderSide other && Equals(other); public bool Equals(BorderSide other) => this == other; public static bool operator ==(BorderSide left, BorderSide right) => left.Style == right.Style && Equals(left.Color, right.Color); public static bool operator !=(BorderSide left, BorderSide right) => !(left == right); public override int GetHashCode() { unchecked { int hash = 17; hash = hash * 31 + Style.GetHashCode(); hash = hash * 31 + (Color?.GetHashCode() ?? 0); return hash; } } } /// Тип линии границы public enum BorderStyle { /// Тонкая линия Thin, /// Средняя линия Medium, /// Штриховая Dashed, /// Точечная Dotted, /// Толстая линия Thick, /// Двойная линия Double, /// Волосяная (очень тонкая) Hair, /// Средняя штриховая MediumDashed, /// Штрих-пунктирная DashDot, /// Средняя штрих-пунктирная MediumDashDot, /// Штрих-пунктир-пунктир DashDotDot, /// Средняя штрих-пунктир-пунктир MediumDashDotDot, /// Наклонная штрих-пунктирная (для диагональных) SlantDashDot, }