box

Combinators for creating and manipulating two-dimensional rectangular text layouts

art
combinators
moon add CAIMEOX/box@0.1.3
Download zip
Author
Version
0.1.3
License
Apache-2.0
Last updated
3 months ago
Downloads
35
README

#Box

This library provides a set of combinators for creating and manipulating two-dimensional text layouts, inspired by Box combinators. It's perfect for creating ASCII art, diagrams, charts, and other visual representations in text.

#Core Concepts

  • Box: A rectangular area containing text data
  • Combinators: Functions that combine boxes in various ways
  • Alignment: Control how boxes are positioned relative to each other

#Basic Usage

#Creating Boxes

// Create a single character box
let star = singleton('*')

// Create filled rectangles
let rect = fill('█', 3, 5) // 3 rows, 5 columns of █

// Create empty space
let gap = space(2, 4) // 2 rows, 4 columns of spaces

#Combining Boxes

// Place boxes side by side
let horizontal = box1.beside(box2)

// Stack boxes vertically
let vertical = box1.above(box2)

// Combine multiple boxes
let combined = hconcat([box1, box2, box3]) // horizontal
let stacked = vconcat([box1, box2, box3]) // vertical

#Examples

#Sierpinski Triangle

Create fractal triangles:

fn sierpinski(n) {
guard n > 0 else { singleton('*') }
let s = sierpinski(n - 1)
s.above([s, singleton(' '), s] |> hconcat())
}

sierpinski(4)

Output:

* * * * * * * * * * * * * * * * * * * * * * * * * * *

#Diamond Pattern

Create diamond shapes:

fn diamond(size : Int) -> Box {
let lines = []
// Top half including middle
for i = 0; i <= size; i = i + 1 {
let spaces = size - i
let stars = 2 * i + 1
let line = space(1, spaces)
.beside(fill('*', 1, stars))
.beside(space(1, spaces))
lines.push(line)
}
// Bottom half
for i = size - 1; i >= 0; i = i - 1 {
let spaces = size - i
let stars = 2 * i + 1
let line = space(1, spaces)
.beside(fill('*', 1, stars))
.beside(space(1, spaces))
lines.push(line)
}
vconcat(lines)
}

Sample output:

* *** ***** ******* ********** ******* ***** *** *

#Bar Chart

Create visual data representations:

fn bar_chart(values : Array[Int]) -> Box {
let max_val = values.fold(init=0, fn(a, b) { if b > a { b } else { a } })
let bars = values.map(fn(v) {
let height = v * 10 / max_val + 1
fill('█', height, 3).above(
v.to_string().iter().map(singleton).to_array() |> hconcat(),
)
})
hconcat(bars, align=Bottom)
}

#Spiral Patterns

Create nested rectangular spirals:

fn spiral(n : Int) -> Box {
if n <= 0 {
singleton('+')
} else {
let s = spiral(n - 1)
let (h, w) = s.dimensions()
let vbar = fill('|', h, 1)
grid([
[
singleton('|').beside(singleton(' ')).beside(singleton('+')),
fill('-', 1, w),
singleton('+'),
],
[vbar, singleton(' '), s, singleton(' '), vbar],
[singleton('+'), fill('-', 1, w + 2), singleton('+')],
])
}
}

Output:

| +-------------------------------------+ | | +---------------------------------+ | | | | +-----------------------------+ | | | | | | +-------------------------+ | | | | | | | | +---------------------+ | | | | | | | | | | +-----------------+ | | | | | | | | | | | | +-------------+ | | | | | | | | | | | | | | +---------+ | | | | | | | | | | | | | | | | +-----+ | | | | | | | | | | | | | | | | | | +-+ | | | | | | | | | | | | | | | | | | | + | | | | | | | | | | | | | | | | | | | +---+ | | | | | | | | | | | | | | | | | +-------+ | | | | | | | | | | | | | | | +-----------+ | | | | | | | | | | | | | +---------------+ | | | | | | | | | | | +-------------------+ | | | | | | | | | +-----------------------+ | | | | | | | +---------------------------+ | | | | | +-------------------------------+ | | | +-----------------------------------+ | +---------------------------------------+

#Binary Tree

Create tree structures:

fn binary_tree(depth : Int) -> Box {
if depth <= 0 {
singleton('*')
} else {
let left = binary_tree(depth - 1)
let right = binary_tree(depth - 1)
let root = singleton('*')
let branches = [left, space(1, 3), right] |> hconcat()
root.above(branches)
}
}

Output:

* * * * * * * * * * * * * * *

#API Reference

#Core Types

  • Box: The main type representing a rectangular text area
  • Vertical: Alignment enum (Top, Center, Bottom)
  • Horizontal: Alignment enum (Left, Center, Right)

#Creation Functions

  • singleton(c): Create a 1x1 box with character c
  • fill(c, h, w): Create an h×w box filled with character c
  • space(h, w): Create an h×w box filled with spaces
  • empty(): Create an empty box

#Combination Functions

  • beside(other, align~): Place boxes horizontally
  • above(other, align~): Stack boxes vertically
  • hconcat(boxes, align~): Combine array of boxes horizontally
  • vconcat(boxes, align~): Combine array of boxes vertically
  • grid(matrix): Arrange boxes in a 2D grid

#Utility Functions

  • dimensions(): Get (height, width) of a box
  • height(): Get height of a box
  • width(): Get width of a box
  • widen(w, align~): Expand box width
  • heighten(h, align~): Expand box height
  • framed(): Add a border around a box

#Advanced Examples

The library can create complex patterns including:

  • Cantor Set: Mathematical fractal dust patterns
  • Julia Set: Complex number fractals
  • Tree Fractals: Recursive branching structures
  • Square Spirals: Nested square patterns with Unicode box-drawing characters

See the test cases in examples.mbt for complete implementations of these patterns.

#
Box

type Box

A Box represents a rectangular area of text data. Each box has a consistent width across all rows.
impl Show for Box

#
Box::above

fn Box::above(self : Box, b : Box, align? : Horizontal) -> Box

Stack two boxes vertically.

Parameters

  • b: The box to place below
  • align: Horizontal alignment (default: Center)

Example

let top = fill('T', 1, 5)

let bottom = fill('B', 2, 3)

let _stacked = top.above(bottom, align=Left)

#
Box::beside

fn Box::beside(self : Box, r : Box, align? : Vertical) -> Box

Place two boxes side by side horizontally.

Parameters

  • r: The box to place to the right
  • align: Vertical alignment (default: Center)

Example

let left = fill('L', 2, 3)

let right = fill('R', 3, 2)

let _combined = left.beside(right, align=Top)

#
Box::dimensions

fn Box::dimensions(self : Box) -> (Int, Int)

Get both dimensions of a box as (height, width).

Example

let box = fill('*', 3, 5)
let _ = box.dimensions() // returns (3, 5)

#
Box::framed

fn Box::framed(self : Box) -> Box

Add a simple ASCII frame around a box using '+', '-', and '|' characters.

Example

let content = fill('*', 2, 4)

let _framed = content.framed()
// Creates:
// +----+
// |****|
// |****|
// +----+

#
Box::height

fn Box::height(self : Box) -> Int

Get the height (number of rows) of a box.

Example

let box = fill('*', 3, 5)
let _ = box.height() // returns 3

#
Box::heighten

fn Box::heighten(self : Box, h : Int, align? : Vertical) -> Box

Expand a box to the specified height by adding padding.

Parameters

  • h: Target height
  • align: Vertical alignment of original content (default: Center)

Example

let box = fill('*', 2, 3)

let _heightened = box.heighten(5, align=Top)
// Adds 3 rows of spaces below

#
Box::overlay

fn Box::overlay(self : Box, overlay : Box, dx? : Int, dy? : Int, transparent? : Char) -> Box

Overlay another box on top of this box, treating a character as transparent. The overlay can be shifted by horizontal (dx) and vertical (dy) offsets.

Parameters

  • overlay: Box to draw over the base box
  • dx: Horizontal offset relative to the base box (default: 0)
  • dy: Vertical offset relative to the base box (default: 0)
  • transparent: Character on the overlay that keeps the underlying content (default: space)

Example

let base = fill('.', 3, 5)

let marker = grid([
[singleton(' '), singleton('#')],
[singleton('#'), singleton('#')],
])

let _combined = base.overlay(marker, dx=1, dy=1)
// Result:
// .....
// ..#..
// .##..

#
Box::widen

fn Box::widen(self : Box, w : Int, align? : Horizontal) -> Box

Expand a box to the specified width by adding padding.

Parameters

  • w: Target width
  • align: Horizontal alignment of original content (default: Center)

Example

let box = fill('*', 2, 3)

let _widened = box.widen(7, align=Left)
// Adds 4 spaces to the right

#
Box::width

fn Box::width(self : Box) -> Int

Get the width (number of columns) of a box. Returns 0 for empty boxes.

Example

let box = fill('*', 3, 5)
let _ = box.width() // returns 5

#
Horizontal

pub(all) enum Horizontal {
Left
Center
Right
}

Horizontal alignment options for box positioning.

#
Vertical

pub(all) enum Vertical {
Top
Center
Bottom
}

Vertical alignment options for box positioning.

#
empty

fn empty() -> Box

Create an empty box with zero dimensions. Useful as an identity element for box combinations.

#
fill

fn fill(c : Char, h : Int, w : Int) -> Box

Create a box filled with the specified character.

Parameters

  • c: The character to fill the box with
  • h: Height (number of rows)
  • w: Width (number of columns)

Example

let _star = fill('*', 3, 5)
// Creates:
// *****
// *****
// *****

#
grid

fn grid(g : Array[Array[Box]]) -> Box

Arrange boxes in a 2D grid layout. Each sub-array represents a row of boxes.

Parameters

  • g: 2D array where g[i][j] is the box at row i, column j

Example

let corner = singleton('+')

let h_bar = fill('-', 1, 3)

let v_bar = fill('|', 1, 1)

let center = fill(' ', 1, 3)

let _frame = grid([
[corner, h_bar, corner],
[v_bar, center, v_bar],
[corner, h_bar, corner],
])

#
hconcat

fn hconcat(boxes : Array[Box], align? : Vertical) -> Box

Combine an array of boxes horizontally.

Parameters

  • boxes: Array of boxes to combine
  • align: Vertical alignment (default: Center)

Example

let boxes = [fill('A', 2, 1), fill('B', 3, 1), fill('C', 1, 1)]

let _combined = hconcat(boxes, align=Bottom)

#
singleton

fn singleton(c : Char) -> Box

Create a 1×1 box containing a single character.

Example

let _star = singleton('*')
// Creates a single '*'

#
space

fn space(h : Int, w : Int) -> Box

Create a box filled with spaces.

Parameters

  • h: Height (number of rows)
  • w: Width (number of columns)

Example

let _gap = space(2, 4)
// Creates 2 rows of 4 spaces each

#
vconcat

fn vconcat(boxes : Array[Box], align? : Horizontal) -> Box

Combine an array of boxes vertically.

Parameters

  • boxes: Array of boxes to combine
  • align: Horizontal alignment (default: Center)

Example

let boxes = [fill('A', 1, 3), fill('B', 1, 5), fill('C', 1, 2)]

let _stacked = vconcat(boxes, align=Left)

Source Files