Files
QWERTYkez.OpenXmlProcessors/QWERTYkez.ExcelProcessor/Editors/CellBorder.cs
melekhin e373d4108a
All checks were successful
Publish NuGet packages / publish (push) Successful in 28s
many debugs
2026-06-19 15:06:40 +07:00

360 lines
14 KiB
C#
Raw 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 Bottom { get; } = new() { BottomBorder = BorderSide.BlackThin };
public static CellBorder Top { get; } = new() { TopBorder = BorderSide.BlackThin };
public static CellBorder Left { get; } = new() { LeftBorder = BorderSide.BlackThin };
public static CellBorder Right { get; } = new() { RightBorder = BorderSide.BlackThin };
public static CellBorder All { get; } = new()
{
BottomBorder = BorderSide.BlackThin,
TopBorder = BorderSide.BlackThin,
LeftBorder = BorderSide.BlackThin,
RightBorder = BorderSide.BlackThin
};
/// <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
}