Files
melekhin 5302edfb8f borders
2026-06-19 16:35:17 +07:00

384 lines
15 KiB
C#
Raw Permalink 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.
namespace QWERTYkez.ExcelProcessor;
/// <summary>
/// Определяет границы ячейки: верхнюю, нижнюю, левую, правую и диагональные.
/// Каждая граница может иметь стиль и цвет.
/// </summary>
public readonly struct CellBorder : IEquatable<CellBorder>
{
public static CellBorder BottomThin { get; } = new() { BottomBorder = BorderSide.BlackThin };
public static CellBorder TopThin { get; } = new() { TopBorder = BorderSide.BlackThin };
public static CellBorder LeftThin { get; } = new() { LeftBorder = BorderSide.BlackThin };
public static CellBorder RightThin { get; } = new() { RightBorder = BorderSide.BlackThin };
public static CellBorder AllThin { get; } = new()
{
BottomBorder = BorderSide.BlackThin,
TopBorder = BorderSide.BlackThin,
LeftBorder = BorderSide.BlackThin,
RightBorder = BorderSide.BlackThin
};
public static CellBorder BottomMedium { get; } = new() { BottomBorder = BorderSide.BlackMedium };
public static CellBorder TopMedium { get; } = new() { TopBorder = BorderSide.BlackMedium };
public static CellBorder LeftMedium { get; } = new() { LeftBorder = BorderSide.BlackMedium };
public static CellBorder RightMedium { get; } = new() { RightBorder = BorderSide.BlackMedium };
public static CellBorder AllMedium { get; } = new()
{
BottomBorder = BorderSide.BlackMedium,
TopBorder = BorderSide.BlackMedium,
LeftBorder = BorderSide.BlackMedium,
RightBorder = BorderSide.BlackMedium
};
public static CellBorder BottomThick { get; } = new() { BottomBorder = BorderSide.BlackThick };
public static CellBorder TopThick { get; } = new() { TopBorder = BorderSide.BlackThick };
public static CellBorder LeftThick { get; } = new() { LeftBorder = BorderSide.BlackThick };
public static CellBorder RightThick { get; } = new() { RightBorder = BorderSide.BlackThick };
public static CellBorder AllThick { get; } = new()
{
BottomBorder = BorderSide.BlackThick,
TopBorder = BorderSide.BlackThick,
LeftBorder = BorderSide.BlackThick,
RightBorder = BorderSide.BlackThick
};
/// <summary>Верхняя граница.</summary>
public BorderSide? TopBorder { get; init; }
/// <summary>Нижняя граница.</summary>
public BorderSide? BottomBorder { get; init; }
/// <summary>Левая граница.</summary>
public BorderSide? LeftBorder { get; init; }
/// <summary>Правая граница.</summary>
public BorderSide? RightBorder { get; init; }
/// <summary>Диагональная граница «из левого верхнего в правый нижний» (\\).</summary>
public BorderSide? DiagonalLeft { get; init; }
/// <summary>Диагональная граница «из левого нижнего в правый верхний» (//).</summary>
public BorderSide? DiagonalRight { get; init; }
public bool TryMerge(CellBorder other, out CellBorder result)
{
if (other.TopBorder == TopBorder &&
other.BottomBorder == BottomBorder &&
other.LeftBorder == LeftBorder &&
other.RightBorder == RightBorder &&
other.DiagonalLeft == DiagonalLeft &&
other.DiagonalRight == DiagonalRight)
{
result = default;
return false;
}
result = new CellBorder
{
TopBorder = other.TopBorder ?? TopBorder,
BottomBorder = other.BottomBorder ?? BottomBorder,
LeftBorder = other.LeftBorder ?? LeftBorder,
RightBorder = other.RightBorder ?? RightBorder,
DiagonalLeft = other.DiagonalLeft ?? DiagonalLeft,
DiagonalRight = other.DiagonalRight ?? DiagonalRight
};
return true;
}
/// <summary>Создаёт элемент Border для Open XML.</summary>
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<TopBorder>();
if (BottomBorder.HasValue)
border.BottomBorder = BottomBorder.Value.ToBorderElement<BottomBorder>();
if (LeftBorder.HasValue)
border.LeftBorder = LeftBorder.Value.ToBorderElement<LeftBorder>();
if (RightBorder.HasValue)
border.RightBorder = RightBorder.Value.ToBorderElement<RightBorder>();
// Обработка диагональных границ
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<DiagonalBorder>();
}
return border;
}
/// <summary>Создаёт CellBorder из элемента Border Open XML.</summary>
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;
}
}
}
/// <summary>Стиль и цвет границы.</summary>
public readonly struct BorderSide : IEquatable<BorderSide>
{
/// <summary>Тонкая черная линия</summary>
public static BorderSide BlackThin { get; } = new() { Color = System.Drawing.Color.Black, Style = BorderStyle.Thin };
/// <summary>Толстая черная линия</summary>
public static BorderSide BlackThick { get; } = new() { Color = System.Drawing.Color.Black, Style = BorderStyle.Thick };
/// <summary>Средняя черная линия</summary>
public static BorderSide BlackMedium { get; } = new() { Color = System.Drawing.Color.Black, Style = BorderStyle.Medium };
/// <summary>Стиль линии границы.</summary>
public BorderStyle? Style { get; init; }
/// <summary>Цвет границы.</summary>
public System.Drawing.Color? Color { get; init; }
internal T ToBorderElement<T>() 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)
{
var c = Color.Value;
element.Color = new Color { Rgb = $"{c.R:X2}{c.G:X2}{c.B:X2}" };
}
return element;
}
/// <summary>Создаёт BorderSide из элемента границы Open XML (TopBorder, BottomBorder, LeftBorder, RightBorder, DiagonalBorder).</summary>
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)
{
result = result with
{
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))
};
}
return result;
}
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;
}
}
}
/// <summary>Тип линии границы</summary>
public enum BorderStyle
{
/// <summary> Тонкая линия </summary>
Thin,
/// <summary> Средняя линия </summary>
Medium,
/// <summary> Штриховая </summary>
Dashed,
/// <summary> Точечная </summary>
Dotted,
/// <summary> Толстая линия </summary>
Thick,
/// <summary> Двойная линия </summary>
Double,
/// <summary> Волосяная (очень тонкая) </summary>
Hair,
/// <summary> Средняя штриховая </summary>
MediumDashed,
/// <summary> Штрих-пунктирная </summary>
DashDot,
/// <summary> Средняя штрих-пунктирная </summary>
MediumDashDot,
/// <summary> Штрих-пунктир-пунктир </summary>
DashDotDot,
/// <summary> Средняя штрих-пунктир-пунктир </summary>
MediumDashDotDot,
/// <summary> Наклонная штрих-пунктирная (для диагональных) </summary>
SlantDashDot,
}
/// <summary>
/// Определяет, какие границы диапазона следует применить.
/// </summary>
public enum BorderTarget
{
/// <summary>Все границы (внешние и внутренние) полная сетка.</summary>
All,
/// <summary>Только внешние границы диапазона.</summary>
Outside,
/// <summary>Только внутренние границы (между ячейками).</summary>
Inside,
/// <summary>Только верхняя граница диапазона.</summary>
Top,
/// <summary>Только нижняя граница диапазона.</summary>
Bottom,
/// <summary>Только левая граница диапазона.</summary>
Left,
/// <summary>Только правая граница диапазона.</summary>
Right
}