namespace QWERTYkez.ExcelProcessor.Editors; /// /// Внутренняя реализация для работы с богатым текстом ячейки. /// Хранит коллекцию фрагментов . /// Минимизирует аллокации, не использует рефлексию. /// internal sealed class ExcelCellText : ICellText { private List? _runs; /// public int Count => _runs?.Count ?? 0; /// public IEnumerable GetRuns() { if (_runs is null) return []; // Возвращаем сам список, чтобы избежать копирования. // Вызывающий не должен модифицировать коллекцию. return _runs; } /// public IRun? GetRunAt(int index) { if (_runs is null || index < 0 || index >= _runs.Count) return null; return _runs[index]; } /// public bool TryGetRunAt(int index, out IRun run) { run = GetRunAt(index)!; return run != null; } /// public IRun? First() { if (_runs is null || _runs.Count == 0) return null; return _runs[0]; } /// public bool TryGetFirst(out IRun run) { run = First()!; return run != null; } /// public IRun? Last() { if (_runs is null || _runs.Count == 0) return null; return _runs[_runs.Count - 1]; } /// public bool TryGetLast(out IRun run) { run = Last()!; return run != null; } /// public bool TryRemoveRun(IRun run) { if (run is null || _runs is null) return false; return _runs.Remove(run); } /// public bool TryRemoveRun(int index) { if (_runs is null || index < 0 || index >= _runs.Count) return false; _runs.RemoveAt(index); return true; } /// public bool TryRemoveRun(int index, out IRun? removed) { removed = GetRunAt(index); if (removed is null) return false; return TryRemoveRun(index); } /// public ICellText AddBreak() { // Добавляем символ переноса строки в последний существующий Run if (_runs != null && _runs.Count > 0) { var lastRun = _runs[_runs.Count - 1]; lastRun.Text += "\n"; } else { // Если нет ни одного Run, создаём новый с символом переноса AddRun("\n", null); } return this; } /// public ICellText AddRun(string text, RunFormat? format = null) { if (string.IsNullOrEmpty(text)) return this; _runs ??= []; var run = new ExcelRun { Text = text, Format = format }; _runs.Add(run); return this; } /// public ICellText AddRunBreak(string text, RunFormat? format = null) { AddRun(text, format); AddBreak(); return this; } /// public ICellText AddSubRun(string text, RunFormat? format = null) { var subFormat = format is { } fmt ? new RunFormat { IsBold = fmt.IsBold, IsItalic = fmt.IsItalic, Underline = fmt.Underline, IsStrike = fmt.IsStrike, Color = fmt.Color, FontSize = fmt.FontSize, FontFamily = fmt.FontFamily, Vertical = VerticalTextRunAlignment.Subscript } : new RunFormat { Vertical = VerticalTextRunAlignment.Subscript }; return AddRun(text, subFormat); } /// public ICellText AddSupRun(string text, RunFormat? format = null) { var supFormat = format is { } fmt ? new RunFormat { IsBold = fmt.IsBold, IsItalic = fmt.IsItalic, Underline = fmt.Underline, IsStrike = fmt.IsStrike, Color = fmt.Color, FontSize = fmt.FontSize, FontFamily = fmt.FontFamily, Vertical = VerticalTextRunAlignment.Subscript } : new RunFormat { Vertical = VerticalTextRunAlignment.Superscript }; return AddRun(text, supFormat); } /// public bool TryInsertRun(int index, string text, RunFormat? format = null) { if (index < 0 || string.IsNullOrEmpty(text)) return false; _runs ??= []; if (index > _runs.Count) return false; var run = new ExcelRun { Text = text, Format = format }; _runs.Insert(index, run); return true; } /// public bool TryInsertRunBreak(int index, string text, RunFormat? format = null) { if (!TryInsertRun(index, text, format)) return false; // После вставленного run добавляем break на следующей позиции AddBreak(); // Сдвигаем? Просто добавляем break в конец – неверно. Break должен быть сразу после вставленного. // Но AddBreak добавляет в конец. Нужно вставить break на index+1. return TryInsertRun(index + 1, "\n", null); } /// public bool TryInsertSubRun(int index, string text, RunFormat? format = null) { var subFormat = format is { } fmt ? new RunFormat { IsBold = fmt.IsBold, IsItalic = fmt.IsItalic, Underline = fmt.Underline, IsStrike = fmt.IsStrike, Color = fmt.Color, FontSize = fmt.FontSize, FontFamily = fmt.FontFamily, Vertical = VerticalTextRunAlignment.Subscript } : new RunFormat { Vertical = VerticalTextRunAlignment.Subscript }; return TryInsertRun(index, text, subFormat); } /// public bool TryInsertSupRun(int index, string text, RunFormat? format = null) { var supFormat = format is { } fmt ? new RunFormat { IsBold = fmt.IsBold, IsItalic = fmt.IsItalic, Underline = fmt.Underline, IsStrike = fmt.IsStrike, Color = fmt.Color, FontSize = fmt.FontSize, FontFamily = fmt.FontFamily, Vertical = VerticalTextRunAlignment.Subscript } : new RunFormat { Vertical = VerticalTextRunAlignment.Superscript }; return TryInsertRun(index, text, supFormat); } /// public void ApplyFormatToAllRuns(RunFormat format) { if (_runs is null || _runs.Count == 0) return; foreach (var run in _runs) { if (run is ExcelRun xRun) { // Объединение форматов: ненулевые свойства overlay заменяют значения в base. var baseFmt = xRun.Format ?? new RunFormat(); xRun.Format = MergeRunFormat(baseFmt, format); } } } /// public void Clear() { _runs?.Clear(); _runs = null; } private static RunFormat MergeRunFormat(RunFormat baseFmt, RunFormat overlay) { return new RunFormat { IsBold = overlay.IsBold ?? baseFmt.IsBold, IsItalic = overlay.IsItalic ?? baseFmt.IsItalic, Underline = overlay.Underline ?? baseFmt.Underline, IsStrike = overlay.IsStrike ?? baseFmt.IsStrike, Color = overlay.Color ?? baseFmt.Color, FontSize = overlay.FontSize ?? baseFmt.FontSize, FontFamily = overlay.FontFamily ?? baseFmt.FontFamily, Vertical = overlay.Vertical ?? baseFmt.Vertical }; } }