BrailleGrid

Struct BrailleGrid 

Source
pub struct BrailleGrid { /* private fields */ }
Expand description

High-resolution grid using Braille characters

Extracted from crabmusic - Battle-tested rendering engine.

Each terminal cell contains a 2×4 dot pattern (8 dots total), giving us high-resolution graphics in any terminal that supports Unicode braille.

§Architecture (Preserved from crabmusic)

  • patterns: Vec<u8> - Flat array, each u8 is a bitfield (8 bits = 8 dots)
  • Dot coordinates: (dot_x, dot_y) in pixel space (width2 × height4)
  • Cell coordinates: (cell_x, cell_y) in terminal space (width × height)

§Dot Indexing (Unicode Braille Standard)

Braille cell (8 dots):
1 4    (Dot1=0x01, Dot4=0x08)
2 5    (Dot2=0x02, Dot5=0x10)
3 6    (Dot3=0x04, Dot6=0x20)
7 8    (Dot7=0x40, Dot8=0x80)

§Example

use dotmax::BrailleGrid;

let mut grid = BrailleGrid::new(40, 20).unwrap();
// Grid is 40×20 cells = 80×80 dot resolution
grid.set_dot(0, 0); // Top-left dot
grid.set_dot(1, 0); // Top-right dot of first cell

Implementations§

Source§

impl BrailleGrid

Source

pub fn new(width: usize, height: usize) -> Result<Self, DotmaxError>

Create a new Braille grid

Extracted from crabmusic::BrailleGrid::new() with added validation.

§Arguments
  • width - Width in terminal cells (must be > 0 and <= MAX_GRID_WIDTH)
  • height - Height in terminal cells (must be > 0 and <= MAX_GRID_HEIGHT)
§Returns
  • Ok(BrailleGrid) if dimensions are valid
  • Err(DotmaxError::InvalidDimensions) if width/height is 0 or exceeds max
§Errors

Returns InvalidDimensions if width or height is 0 or exceeds max allowed dimensions.

§Crabmusic Change

Original crabmusic code never validated dimensions. Dotmax adds validation for security (NFR-S2).

Source

pub const fn width(&self) -> usize

Get width in terminal cells

Extracted from crabmusic (lines 104-106)

Source

pub const fn height(&self) -> usize

Get height in terminal cells

Extracted from crabmusic (lines 108-111)

Source

pub const fn dot_width(&self) -> usize

Get width in dots (2× terminal width)

Extracted from crabmusic (lines 113-116)

Source

pub const fn dot_height(&self) -> usize

Get height in dots (4× terminal height)

Extracted from crabmusic (lines 118-121)

Source

pub const fn dimensions(&self) -> (usize, usize)

Get the dimensions of the grid (dotmax addition for AC #7)

NEW - Not in crabmusic. Added to satisfy AC #7 requirement.

§Returns

A tuple of (width, height) in terminal cells

Source

pub fn clear(&mut self)

Clear all dots

Extracted from crabmusic (lines 124-127) with minor adaptation

Source

pub fn set_dot(&mut self, dot_x: usize, dot_y: usize) -> Result<(), DotmaxError>

Set a single dot at the specified position

Extracted from crabmusic (lines 144-172) with added error handling.

CRITICAL: This uses PIXEL coordinates (dot_x, dot_y), not cell coordinates. The grid is width2 × height4 dots.

§Arguments
  • dot_x - X position in dots (0 to width*2-1)
  • dot_y - Y position in dots (0 to height*4-1)
§Crabmusic Change

Original crabmusic silently ignored out-of-bounds coordinates. Dotmax returns an error for explicit bounds checking (zero panics policy).

§Errors

Returns OutOfBounds if dot coordinates exceed grid dimensions.

Source

pub fn get_dot( &self, x: usize, y: usize, dot_index: u8, ) -> Result<bool, DotmaxError>

Get an individual dot value

NEW - Not in crabmusic. Added to match AC #4 requirement.

§Arguments
  • dot_x - X position in dots (0 to width*2-1)
  • dot_y - Y position in dots (0 to height*4-1)
  • dot_index - Dot position 0-7 in the cell
§Returns
  • Ok(bool) - The dot value (true = enabled, false = disabled)
  • Err(DotmaxError::OutOfBounds) if coordinates exceed grid dimensions
  • Err(DotmaxError::InvalidDotIndex) if dot_index > 7
§Errors

Returns OutOfBounds if dot coordinates exceed grid dimensions, or InvalidDotIndex if dot index > 7.

Source

pub fn clear_region( &mut self, x: usize, y: usize, width: usize, height: usize, ) -> Result<(), DotmaxError>

Clear a rectangular region of the grid

NEW - Not in crabmusic. Added to satisfy AC #6 requirement.

§Arguments
  • x - Starting column in cells (0-indexed)
  • y - Starting row in cells (0-indexed)
  • width - Width of region to clear in cells
  • height - Height of region to clear in cells
§Returns
  • Ok(()) if region was cleared successfully
  • Err(DotmaxError::OutOfBounds) if region extends beyond grid bounds
§Errors

Returns OutOfBounds if the specified region extends beyond grid dimensions.

Source

pub fn get_char(&self, cell_x: usize, cell_y: usize) -> char

Get the Braille character at a cell position

Extracted from crabmusic (lines 338-347)

§Arguments
  • cell_x - X position in cells
  • cell_y - Y position in cells
§Returns

Character at the cell position:

  • If a text character is set (Story 4.4 density rendering), returns that character
  • Otherwise, returns braille character representing the dot pattern
Source

pub fn get_color(&self, cell_x: usize, cell_y: usize) -> Option<Color>

Get the color at a cell position

Extracted from crabmusic (lines 350-357)

Source

pub fn is_empty(&self, cell_x: usize, cell_y: usize) -> bool

Check if a cell has any dots set

Extracted from crabmusic (lines 360-368)

Source

pub fn get_raw_patterns(&self) -> &[u8]

Get raw access to the pattern buffer for serialization.

Story 6.4 - Enables efficient animation frame serialization.

Returns a slice of the internal pattern buffer, where each byte represents one braille cell’s dot pattern (8 bits = 8 dots).

§Returns

A byte slice of length width * height containing dot patterns.

§Examples
use dotmax::BrailleGrid;

let mut grid = BrailleGrid::new(10, 5).unwrap();
grid.set_dot(0, 0).unwrap(); // Set top-left dot

let patterns = grid.get_raw_patterns();
assert_eq!(patterns.len(), 50); // 10 * 5 cells
assert_eq!(patterns[0], 0b0000_0001); // First cell has dot 1 set
Source

pub fn set_raw_patterns(&mut self, data: &[u8])

Set raw pattern buffer from serialized data.

Story 6.4 - Enables efficient animation frame deserialization.

Copies data into the internal pattern buffer. The data slice length must match width * height. If the length doesn’t match, excess data is truncated or missing data is left unchanged.

§Arguments
  • data - Raw pattern bytes to copy into the grid
§Examples
use dotmax::BrailleGrid;

let mut grid = BrailleGrid::new(10, 5).unwrap();
let mut patterns = vec![0u8; 50];
patterns[0] = 0b0000_0001; // Set dot 1 in first cell

grid.set_raw_patterns(&patterns);
assert!(grid.get_char(0, 0) == '⠁'); // First cell shows dot 1
Source

pub fn to_unicode_grid(&self) -> Vec<Vec<char>>

Convert entire grid to 2D array of Unicode braille characters

Story 2.2 - Batch conversion for rendering pipeline.

This method converts the entire grid from dot patterns to Unicode braille characters, producing a 2D array that matches the grid dimensions.

Uses the proven dots_to_char() function extracted from crabmusic (lines 53-56) which applies the Unicode Braille standard formula: U+2800 + bitfield

§Returns

A 2D vector of Unicode braille characters, where result[y][x] corresponds to cell (x, y) in the grid.

§Examples
use dotmax::BrailleGrid;

let mut grid = BrailleGrid::new(5, 5).unwrap();
grid.set_dot(0, 0).unwrap(); // Top-left dot of cell (0,0)

let chars = grid.to_unicode_grid();
assert_eq!(chars.len(), 5); // 5 rows
assert_eq!(chars[0].len(), 5); // 5 columns
assert_eq!(chars[0][0], '⠁'); // Cell (0,0) has dot 1 set
§Performance

Time complexity: O(width × height) - processes each cell once Allocates: Vec<Vec<char>> with dimensions matching grid size

Source

pub fn cell_to_braille_char( &self, x: usize, y: usize, ) -> Result<char, DotmaxError>

Convert single cell at (x, y) to Unicode braille character

Story 2.2 - Single-cell conversion with bounds validation.

Returns the Unicode braille character for a specific cell, or an error if coordinates are out of bounds.

§Arguments
  • x - X position in cells (0 to width-1)
  • y - Y position in cells (0 to height-1)
§Returns
  • Ok(char) - Unicode braille character (U+2800 to U+28FF)
  • Err(DotmaxError::OutOfBounds) - If coordinates exceed grid dimensions
§Examples
use dotmax::BrailleGrid;

let mut grid = BrailleGrid::new(10, 10).unwrap();
grid.set_dot(2, 4).unwrap(); // Set a dot in cell (1,1)

let ch = grid.cell_to_braille_char(1, 1).unwrap();
assert!(ch >= '\u{2800}' && ch <= '\u{28FF}'); // Valid braille range
§Errors

Returns OutOfBounds if x >= width or y >= height.

Source

pub fn resize( &mut self, new_width: usize, new_height: usize, ) -> Result<(), DotmaxError>

Resize the grid to new dimensions

NEW for Story 2.5 - Not in crabmusic. Enables terminal resize handling.

§Arguments
  • new_width - New width in braille cells
  • new_height - New height in braille cells
§Behavior
  • Grow: New cells initialized to empty (pattern=0, color=None)
  • Shrink: Existing dots outside new bounds are truncated
  • Preserve: Dots within overlap region are preserved
  • Colors: Color buffer resizes in sync with patterns
§Errors

Returns DotmaxError::InvalidDimensions if:

  • new_width or new_height is 0
  • new_width or new_height exceeds MAX_GRID_WIDTH/MAX_GRID_HEIGHT (10,000)
§Examples
use dotmax::BrailleGrid;

let mut grid = BrailleGrid::new(10, 10)?;
grid.set_dot(0, 0)?; // Set top-left dot

// Resize to larger dimensions
grid.resize(20, 20)?;
assert_eq!(grid.dimensions(), (20, 20));

// Resize to smaller dimensions
grid.resize(5, 5)?;
assert_eq!(grid.dimensions(), (5, 5));
Source

pub fn enable_color_support(&mut self)

Enable color support by allocating color buffer

Story 2.6 - Allocates per-cell color storage.

Note: In the current implementation, the color buffer is always allocated during BrailleGrid::new(), so this method is a no-op for compatibility with the AC specification. It ensures the color buffer exists.

§Examples
use dotmax::BrailleGrid;

let mut grid = BrailleGrid::new(10, 10).unwrap();
grid.enable_color_support(); // Ensures color support is enabled
Source

pub fn set_cell_color( &mut self, x: usize, y: usize, color: Color, ) -> Result<(), DotmaxError>

Assign RGB color to cell at (x, y)

Story 2.6 - Per-cell color assignment with bounds validation.

Sets the color for a specific cell. The color will be applied when rendering via TerminalRenderer.

§Arguments
  • x - X position in cells (0 to width-1)
  • y - Y position in cells (0 to height-1)
  • color - RGB color to assign
§Returns
  • Ok(()) if color was assigned successfully
  • Err(DotmaxError::OutOfBounds) if coordinates exceed grid dimensions
§Examples
use dotmax::{BrailleGrid, Color};

let mut grid = BrailleGrid::new(10, 10).unwrap();
grid.enable_color_support();

// Set cell (5, 5) to red
grid.set_cell_color(5, 5, Color::rgb(255, 0, 0)).unwrap();

// Verify color was set
assert_eq!(grid.get_color(5, 5), Some(Color::rgb(255, 0, 0)));
§Errors

Returns OutOfBounds if x >= width or y >= height.

Source

pub fn clear_colors(&mut self)

Reset all colors to None (monochrome)

Story 2.6 - Clear color buffer without deallocating.

Resets all cell colors to None while keeping the color buffer allocated. This is useful for switching back to monochrome rendering without disabling color support entirely.

§Examples
use dotmax::{BrailleGrid, Color};

let mut grid = BrailleGrid::new(10, 10).unwrap();
grid.enable_color_support();

// Set some colors
grid.set_cell_color(5, 5, Color::rgb(255, 0, 0)).unwrap();
grid.set_cell_color(7, 7, Color::rgb(0, 255, 0)).unwrap();

// Clear all colors
grid.clear_colors();

// All colors are now None
assert_eq!(grid.get_color(5, 5), None);
assert_eq!(grid.get_color(7, 7), None);
Source

pub fn set_char( &mut self, x: usize, y: usize, character: char, ) -> Result<(), DotmaxError>

Set a text character at a cell position (Story 4.4)

Story 4.4 - Character density-based rendering support.

Sets a text character at the specified cell position. When a character is set, it overrides the braille dot pattern for that cell during rendering. This enables ASCII-art style density rendering as an alternative to binary braille dots.

§Arguments
  • x - X position in cells (0-indexed)
  • y - Y position in cells (0-indexed)
  • character - The character to set at this position
§Returns
  • Ok(()) if character was set successfully
  • Err(DotmaxError::OutOfBounds) if coordinates exceed grid dimensions
§Examples
use dotmax::BrailleGrid;

let mut grid = BrailleGrid::new(10, 10).unwrap();

// Set character at cell (5, 5)
grid.set_char(5, 5, '@').unwrap();

// Verify character was set
assert_eq!(grid.get_char(5, 5), '@');
§Errors

Returns OutOfBounds if x >= width or y >= height.

Source

pub fn clear_characters(&mut self)

Clear all text characters (Story 4.4)

Story 4.4 - Reset character buffer for density rendering.

Resets all cell characters to None, restoring default braille dot rendering mode. This is useful for switching back from density rendering to braille dots without creating a new grid.

§Examples
use dotmax::BrailleGrid;

let mut grid = BrailleGrid::new(10, 10).unwrap();

// Set some characters
grid.set_char(5, 5, '@').unwrap();
grid.set_char(7, 7, '#').unwrap();

// Clear all characters
grid.clear_characters();

// All characters are now None (braille mode restored)
assert_eq!(grid.get_char(5, 5), '⠀'); // Empty braille pattern
Source

pub fn apply_color_scheme( &mut self, intensities: &[f32], scheme: &ColorScheme, ) -> Result<(), DotmaxError>

Apply a color scheme to a flattened intensity buffer.

Story 5.5 - Convenience method for colorizing intensity data.

Maps a 1D intensity buffer to colors using the provided color scheme, populating the grid’s color buffer. The intensity buffer must be in row-major order and match the grid dimensions (width × height).

This method directly modifies the grid’s internal color buffer for maximum performance, avoiding intermediate allocations.

§Arguments
  • intensities - Flattened 1D intensity buffer (row-major order)
  • scheme - Color scheme for intensity-to-color mapping
§Returns
  • Ok(()) if colors were applied successfully
  • Err(DotmaxError::BufferSizeMismatch) if buffer length doesn’t match grid size
§Intensity Handling
  • Values in range 0.0-1.0 are mapped normally
  • Values outside range are clamped (consistent with ColorScheme::sample)
  • NaN values are treated as 0.0
  • Infinity values are clamped to 0.0 or 1.0
§Examples
use dotmax::{BrailleGrid, ColorScheme};

let mut grid = BrailleGrid::new(4, 3).unwrap();

// Create intensity buffer (4×3 = 12 cells)
let intensities: Vec<f32> = (0..12)
    .map(|i| i as f32 / 11.0)
    .collect();

// Apply heat map scheme
let scheme = ColorScheme::heat_map();
grid.apply_color_scheme(&intensities, &scheme).unwrap();

// First cell is black (intensity 0.0)
let c0 = grid.get_color(0, 0).unwrap();
assert_eq!(c0.r, 0);

// Last cell is white (intensity 1.0)
let c11 = grid.get_color(3, 2).unwrap();
assert_eq!(c11.r, 255);
§Integration with Epic 3 Image Pipeline
use dotmax::{BrailleGrid, ColorScheme};
§Performance

Target: <10ms for 80×24 grid (1,920 cells)

§Errors

Returns DotmaxError::BufferSizeMismatch if intensities.len() != width × height.

Source§

impl BrailleGrid

Source

pub fn render_density( &mut self, intensity_buffer: &[f32], density_set: &DensitySet, ) -> Result<(), DotmaxError>

Render intensity buffer using character density mapping

Renders a buffer of intensity values [0.0, 1.0] onto the grid using character density mapping. Each intensity value is mapped to a character via the provided density set, then rendered at the corresponding grid cell position.

§Arguments
  • intensity_buffer: Row-major array of f32 intensity values [0.0, 1.0]
    • Length must equal grid.width() * grid.height()
    • Row-major order: [row0_col0, row0_col1, ..., row1_col0, ...]
    • 0.0 = darkest (sparse character), 1.0 = brightest (dense character)
  • density_set: Character mapping for intensity → character conversion
§Errors

Returns DotmaxError::BufferSizeMismatch if intensity_buffer.len() != grid.width() * grid.height().

§Performance
  • Complexity: O(n) where n = width × height cells
  • Expected: ~1μs per cell = ~2ms for 80×24 terminal
  • Target: <10ms for full terminal rendering
§Examples
use dotmax::{BrailleGrid, density::DensitySet};

// Create grid and generate horizontal gradient
let mut grid = BrailleGrid::new(10, 5).unwrap();
let intensities: Vec<f32> = (0..50)
    .map(|i| (i % 10) as f32 / 9.0)  // Gradient per row
    .collect();

// Render using ASCII density set
let density = DensitySet::ascii();
grid.render_density(&intensities, &density).unwrap();
§Error Handling
use dotmax::{BrailleGrid, density::DensitySet};

let mut grid = BrailleGrid::new(10, 5).unwrap();
let wrong_size = vec![0.5_f32; 30]; // Wrong size (expected 50)

let density = DensitySet::simple();
let result = grid.render_density(&wrong_size, &density);
assert!(result.is_err()); // BufferSizeMismatch error

Trait Implementations§

Source§

impl Clone for BrailleGrid

Source§

fn clone(&self) -> BrailleGrid

Returns a duplicate of the value. Read more
1.0.0 · Source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
Source§

impl Debug for BrailleGrid

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> CloneToUninit for T
where T: Clone,

Source§

unsafe fn clone_to_uninit(&self, dest: *mut u8)

🔬This is a nightly-only experimental API. (clone_to_uninit)
Performs copy-assignment from self to dest. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T> Instrument for T

Source§

fn instrument(self, span: Span) -> Instrumented<Self>

Instruments this type with the provided Span, returning an Instrumented wrapper. Read more
Source§

fn in_current_span(self) -> Instrumented<Self>

Instruments this type with the current Span, returning an Instrumented wrapper. Read more
Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T> IntoEither for T

Source§

fn into_either(self, into_left: bool) -> Either<Self, Self>

Converts self into a Left variant of Either<Self, Self> if into_left is true. Converts self into a Right variant of Either<Self, Self> otherwise. Read more
Source§

fn into_either_with<F>(self, into_left: F) -> Either<Self, Self>
where F: FnOnce(&Self) -> bool,

Converts self into a Left variant of Either<Self, Self> if into_left(&self) returns true. Converts self into a Right variant of Either<Self, Self> otherwise. Read more
Source§

impl<T> ToOwned for T
where T: Clone,

Source§

type Owned = T

The resulting type after obtaining ownership.
Source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
Source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
Source§

impl<T> WithSubscriber for T

Source§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self>
where S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a WithDispatch wrapper. Read more
Source§

fn with_current_subscriber(self) -> WithDispatch<Self>

Attaches the current default Subscriber to this type, returning a WithDispatch wrapper. Read more