Skip to content
92 changes: 92 additions & 0 deletions Terminal.Gui/Text/FormattedText.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
#nullable enable
using System;
using System.Collections.Generic;
using System.Drawing;

namespace Terminal.Gui.Text;

/// <summary>
/// Represents the result of text formatting, containing formatted lines, size requirements, and metadata.
/// </summary>
public sealed class FormattedText
{
/// <summary>
/// Initializes a new instance of the <see cref="FormattedText"/> class.
/// </summary>
/// <param name="lines">The formatted text lines.</param>
/// <param name="requiredSize">The size required to display the text.</param>
/// <param name="hotKey">The HotKey found in the text, if any.</param>
/// <param name="hotKeyPosition">The position of the HotKey in the original text.</param>
public FormattedText(
IReadOnlyList<FormattedLine> lines,
Size requiredSize,
Key hotKey = default,

Check warning on line 23 in Terminal.Gui/Text/FormattedText.cs

View workflow job for this annotation

GitHub Actions / build_release

Cannot convert null literal to non-nullable reference type.

Check warning on line 23 in Terminal.Gui/Text/FormattedText.cs

View workflow job for this annotation

GitHub Actions / Parallel Unit Tests (ubuntu-latest)

Cannot convert null literal to non-nullable reference type.

Check warning on line 23 in Terminal.Gui/Text/FormattedText.cs

View workflow job for this annotation

GitHub Actions / build_and_test_debug (ubuntu-latest)

Cannot convert null literal to non-nullable reference type.

Check warning on line 23 in Terminal.Gui/Text/FormattedText.cs

View workflow job for this annotation

GitHub Actions / Parallel Unit Tests (macos-latest)

Cannot convert null literal to non-nullable reference type.

Check warning on line 23 in Terminal.Gui/Text/FormattedText.cs

View workflow job for this annotation

GitHub Actions / Non-Parallel Unit Tests (ubuntu-latest)

Cannot convert null literal to non-nullable reference type.

Check warning on line 23 in Terminal.Gui/Text/FormattedText.cs

View workflow job for this annotation

GitHub Actions / build_and_test_debug (windows-latest)

Cannot convert null literal to non-nullable reference type.

Check warning on line 23 in Terminal.Gui/Text/FormattedText.cs

View workflow job for this annotation

GitHub Actions / Non-Parallel Unit Tests (windows-latest)

Cannot convert null literal to non-nullable reference type.

Check warning on line 23 in Terminal.Gui/Text/FormattedText.cs

View workflow job for this annotation

GitHub Actions / Non-Parallel Unit Tests (macos-latest)

Cannot convert null literal to non-nullable reference type.

Check warning on line 23 in Terminal.Gui/Text/FormattedText.cs

View workflow job for this annotation

GitHub Actions / build_and_test_debug (macos-latest)

Cannot convert null literal to non-nullable reference type.
int hotKeyPosition = -1)
{
Lines = lines ?? throw new ArgumentNullException(nameof(lines));
RequiredSize = requiredSize;
HotKey = hotKey;
HotKeyPosition = hotKeyPosition;
}

/// <summary>Gets the formatted text lines.</summary>
public IReadOnlyList<FormattedLine> Lines { get; }

/// <summary>Gets the size required to display the formatted text.</summary>
public Size RequiredSize { get; }

/// <summary>Gets the HotKey found in the text, if any.</summary>
public Key HotKey { get; }

/// <summary>Gets the position of the HotKey in the original text (-1 if no HotKey).</summary>
public int HotKeyPosition { get; }

/// <summary>Gets a value indicating whether the text contains a HotKey.</summary>
public bool HasHotKey => HotKeyPosition >= 0;
}

/// <summary>
/// Represents a single formatted line of text.
/// </summary>
public sealed class FormattedLine
{
/// <summary>
/// Initializes a new instance of the <see cref="FormattedLine"/> class.
/// </summary>
/// <param name="runs">The text runs that make up this line.</param>
/// <param name="width">The display width of this line.</param>
public FormattedLine(IReadOnlyList<FormattedRun> runs, int width)
{
Runs = runs;
Width = width;
}

/// <summary>Gets the text runs that make up this line.</summary>
public IReadOnlyList<FormattedRun> Runs { get; }

/// <summary>Gets the display width of this line.</summary>
public int Width { get; }
}

/// <summary>
/// Represents a run of text with consistent formatting.
/// </summary>
public sealed class FormattedRun
{
/// <summary>
/// Initializes a new instance of the <see cref="FormattedRun"/> class.
/// </summary>
/// <param name="text">The text content of this run.</param>
/// <param name="isHotKey">Whether this run represents a HotKey.</param>
public FormattedRun(string text, bool isHotKey = false)
{
Text = text;
IsHotKey = isHotKey;
}

/// <summary>Gets the text content of this run.</summary>
public string Text { get; }

/// <summary>Gets a value indicating whether this run represents a HotKey.</summary>
public bool IsHotKey { get; }
}
52 changes: 52 additions & 0 deletions Terminal.Gui/Text/ITextFormatter.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
#nullable enable
using System.Drawing;

namespace Terminal.Gui.Text;

/// <summary>
/// Interface for text formatting. Separates formatting concerns from rendering.
/// </summary>
public interface ITextFormatter
{
/// <summary>Gets or sets the text to be formatted.</summary>
string Text { get; set; }

/// <summary>Gets or sets the size constraint for formatting.</summary>
Size? ConstrainToSize { get; set; }

/// <summary>Gets or sets the horizontal text alignment.</summary>
Alignment Alignment { get; set; }

/// <summary>Gets or sets the vertical text alignment.</summary>
Alignment VerticalAlignment { get; set; }

/// <summary>Gets or sets the text direction.</summary>
TextDirection Direction { get; set; }

/// <summary>Gets or sets whether word wrap is enabled.</summary>
bool WordWrap { get; set; }

/// <summary>Gets or sets whether multi-line text is allowed.</summary>
bool MultiLine { get; set; }

/// <summary>Gets or sets the HotKey specifier character.</summary>
Rune HotKeySpecifier { get; set; }

/// <summary>Gets or sets the tab width.</summary>
int TabWidth { get; set; }

/// <summary>Gets or sets whether trailing spaces are preserved in word-wrapped lines.</summary>
bool PreserveTrailingSpaces { get; set; }

/// <summary>
/// Formats the text and returns the formatted result.
/// </summary>
/// <returns>The formatted text result containing lines, size, and metadata.</returns>
FormattedText Format();

/// <summary>
/// Gets the size required to display the formatted text.
/// </summary>
/// <returns>The size required for the formatted text.</returns>
Size GetFormattedSize();
}
40 changes: 40 additions & 0 deletions Terminal.Gui/Text/ITextRenderer.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
#nullable enable

namespace Terminal.Gui.Text;

/// <summary>
/// Interface for rendering formatted text to the console.
/// </summary>
public interface ITextRenderer
{
/// <summary>
/// Draws the formatted text to the console driver.
/// </summary>
/// <param name="formattedText">The formatted text to draw.</param>
/// <param name="screen">The screen bounds for drawing.</param>
/// <param name="normalColor">The color for normal text.</param>
/// <param name="hotColor">The color for HotKey text.</param>
/// <param name="fillRemaining">Whether to fill remaining area with spaces.</param>
/// <param name="maximum">The maximum container bounds.</param>
/// <param name="driver">The console driver to use for drawing.</param>
void Draw(
FormattedText formattedText,
Rectangle screen,
Attribute normalColor,
Attribute hotColor,
bool fillRemaining = false,
Rectangle maximum = default,
IConsoleDriver? driver = null);

/// <summary>
/// Gets the region that would be drawn by the formatted text.
/// </summary>
/// <param name="formattedText">The formatted text.</param>
/// <param name="screen">The screen bounds.</param>
/// <param name="maximum">The maximum container bounds.</param>
/// <returns>A region representing the areas that would be drawn.</returns>
Region GetDrawRegion(
FormattedText formattedText,
Rectangle screen,
Rectangle maximum = default);
}
43 changes: 43 additions & 0 deletions Terminal.Gui/Text/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
# Text Formatting in Terminal.Gui

This directory contains text formatting and processing classes for Terminal.Gui.

## Classes

### TextFormatter

The main text formatting class that handles:
- Text alignment (horizontal and vertical)
- Text direction support (left-to-right, right-to-left, top-to-bottom, etc.)
- Word wrapping
- Multi-line text support
- HotKey processing
- Wide character (Unicode) support

**Known Issues**: The current `TextFormatter` implementation has several architectural problems that are planned to be addressed in a future rewrite:

1. **Format/Draw Coupling**: The `Draw()` method does significant formatting work, making `FormatAndGetSize()` unreliable
2. **Performance**: `Format()` is called multiple times during layout operations
3. **Complex Alignment**: Alignment logic is embedded in drawing code instead of using the `Aligner` engine
4. **Poor Extensibility**: Adding new features requires modifying the monolithic class
5. **No Interface**: Prevents multiple text formatter implementations

See [TextFormatter Rewrite Issue](https://github.com/gui-cs/Terminal.Gui/issues/3469) for details.

### Other Classes

- `TextDirection`: Enumeration for text direction support
- `StringExtensions`: Extension methods for string processing
- `RuneExtensions`: Extension methods for Unicode Rune processing
- `NerdFonts`: Support for Nerd Fonts icons

## Future Plans

A complete rewrite of `TextFormatter` is planned that will:
- Separate formatting from rendering concerns
- Provide an interface-based architecture for extensibility
- Improve performance with better caching
- Support multiple text formats (HTML, Attributed Text, etc.)
- Use the `Aligner` engine for proper alignment

This is a major architectural change planned for a future release.
Loading
Loading