#nullable enable
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Linq;
using System.Text;
namespace Terminal.Gui.Text;
///
/// Standard implementation of that provides the same functionality
/// as the original TextFormatter but with proper separation of concerns.
///
public class StandardTextFormatter : ITextFormatter
{
private string _text = string.Empty;
private Size? _constrainToSize;
private Alignment _alignment = Alignment.Start;
private Alignment _verticalAlignment = Alignment.Start;
private TextDirection _direction = TextDirection.LeftRight_TopBottom;
private bool _wordWrap = true;
private bool _multiLine = true;
private Rune _hotKeySpecifier = (Rune)0xFFFF;
private int _tabWidth = 4;
private bool _preserveTrailingSpaces = false;
// Caching
private FormattedText? _cachedResult;
private int _cacheHash;
///
public string Text
{
get => _text;
set
{
if (_text != value)
{
_text = value ?? string.Empty;
InvalidateCache();
}
}
}
///
public Size? ConstrainToSize
{
get => _constrainToSize;
set
{
if (_constrainToSize != value)
{
_constrainToSize = value;
InvalidateCache();
}
}
}
///
public Alignment Alignment
{
get => _alignment;
set
{
if (_alignment != value)
{
_alignment = value;
InvalidateCache();
}
}
}
///
public Alignment VerticalAlignment
{
get => _verticalAlignment;
set
{
if (_verticalAlignment != value)
{
_verticalAlignment = value;
InvalidateCache();
}
}
}
///
public TextDirection Direction
{
get => _direction;
set
{
if (_direction != value)
{
_direction = value;
InvalidateCache();
}
}
}
///
public bool WordWrap
{
get => _wordWrap;
set
{
if (_wordWrap != value)
{
_wordWrap = value;
InvalidateCache();
}
}
}
///
public bool MultiLine
{
get => _multiLine;
set
{
if (_multiLine != value)
{
_multiLine = value;
InvalidateCache();
}
}
}
///
public Rune HotKeySpecifier
{
get => _hotKeySpecifier;
set
{
if (_hotKeySpecifier.Value != value.Value)
{
_hotKeySpecifier = value;
InvalidateCache();
}
}
}
///
public int TabWidth
{
get => _tabWidth;
set
{
if (_tabWidth != value)
{
_tabWidth = value;
InvalidateCache();
}
}
}
///
public bool PreserveTrailingSpaces
{
get => _preserveTrailingSpaces;
set
{
if (_preserveTrailingSpaces != value)
{
_preserveTrailingSpaces = value;
InvalidateCache();
}
}
}
///
public FormattedText Format()
{
// Check cache first
int currentHash = GetSettingsHash();
if (_cachedResult != null && _cacheHash == currentHash)
{
return _cachedResult;
}
// Perform formatting
var result = DoFormat();
// Update cache
_cachedResult = result;
_cacheHash = currentHash;
return result;
}
///
public Size GetFormattedSize()
{
return Format().RequiredSize;
}
private void InvalidateCache()
{
_cachedResult = null;
}
private int GetSettingsHash()
{
var hash = new HashCode();
hash.Add(_text);
hash.Add(_constrainToSize);
hash.Add(_alignment);
hash.Add(_verticalAlignment);
hash.Add(_direction);
hash.Add(_wordWrap);
hash.Add(_multiLine);
hash.Add(_hotKeySpecifier.Value);
hash.Add(_tabWidth);
hash.Add(_preserveTrailingSpaces);
return hash.ToHashCode();
}
private FormattedText DoFormat()
{
if (string.IsNullOrEmpty(_text))
{
return new FormattedText(Array.Empty(), Size.Empty);
}
// Process HotKey
var processedText = _text;
var hotKey = Key.Empty;
var hotKeyPos = -1;
if (_hotKeySpecifier.Value != 0xFFFF && TextFormatter.FindHotKey(_text, _hotKeySpecifier, out hotKeyPos, out hotKey))
{
processedText = TextFormatter.RemoveHotKeySpecifier(_text, hotKeyPos, _hotKeySpecifier);
}
// Get constraints
int width = _constrainToSize?.Width ?? int.MaxValue;
int height = _constrainToSize?.Height ?? int.MaxValue;
// Handle zero constraints
if (width == 0 || height == 0)
{
return new FormattedText(Array.Empty(), Size.Empty, hotKey, hotKeyPos);
}
// Format the text using existing TextFormatter static methods
List lines;
if (TextFormatter.IsVerticalDirection(_direction))
{
int colsWidth = TextFormatter.GetSumMaxCharWidth(processedText, 0, 1, _tabWidth);
lines = TextFormatter.Format(
processedText,
height,
_verticalAlignment == Alignment.Fill,
width > colsWidth && _wordWrap,
_preserveTrailingSpaces,
_tabWidth,
_direction,
_multiLine
);
colsWidth = TextFormatter.GetMaxColsForWidth(lines, width, _tabWidth);
if (lines.Count > colsWidth)
{
lines.RemoveRange(colsWidth, lines.Count - colsWidth);
}
}
else
{
lines = TextFormatter.Format(
processedText,
width,
_alignment == Alignment.Fill,
height > 1 && _wordWrap,
_preserveTrailingSpaces,
_tabWidth,
_direction,
_multiLine
);
if (lines.Count > height)
{
lines.RemoveRange(height, lines.Count - height);
}
}
// Convert to FormattedText structure
var formattedLines = new List();
foreach (string line in lines)
{
var runs = new List();
// For now, create simple runs - we can enhance this later for HotKey highlighting
if (!string.IsNullOrEmpty(line))
{
// Check if this line contains the HotKey
if (hotKeyPos >= 0 && hotKey != Key.Empty)
{
// Simple implementation - just mark the whole line for now
// TODO: Implement proper HotKey run detection
runs.Add(new FormattedRun(line, false));
}
else
{
runs.Add(new FormattedRun(line, false));
}
}
int lineWidth = TextFormatter.IsVerticalDirection(_direction)
? TextFormatter.GetColumnsRequiredForVerticalText(new List { line }, 0, 1, _tabWidth)
: line.GetColumns();
formattedLines.Add(new FormattedLine(runs, lineWidth));
}
// Calculate required size
Size requiredSize;
if (TextFormatter.IsVerticalDirection(_direction))
{
requiredSize = new Size(
TextFormatter.GetColumnsRequiredForVerticalText(lines, 0, lines.Count, _tabWidth),
lines.Max(line => line.Length)
);
}
else
{
requiredSize = new Size(
lines.Max(line => line.GetColumns()),
lines.Count
);
}
return new FormattedText(formattedLines, requiredSize, hotKey, hotKeyPos);
}
}