///|
fn ceil_div(n : Int, d : Int) -> Int {
  (n + d - 1) / d
}

///|
fn label_char(id : String) -> Char {
  if id.is_empty() {
    '#'
  } else {
    id.unsafe_get(0).to_int().unsafe_to_char()
  }
}

///|
/// Render a coarse ASCII preview of a layout. Each output cell represents
/// `cell_width` by `cell_height` layout units.
pub fn to_ascii_preview(
  layout : Layout,
  cell_width? : Int = 1,
  cell_height? : Int = 1,
) -> Result[String, PackError] {
  if cell_width <= 0 || cell_height <= 0 {
    return Err(InvalidCellSize(width=cell_width, height=cell_height))
  }
  match validate_layout(layout) {
    Err(err) => return Err(err)
    Ok(_) => ()
  }
  let cols = ceil_div(layout.bin.width, cell_width)
  let rows = ceil_div(layout.bin.height, cell_height)
  let cells = Array::make(rows * cols, '.')
  for placed in layout.placements {
    let mark = label_char(placed.id)
    let start_col = placed.x / cell_width
    let end_col = (placed.x + placed.width - 1) / cell_width
    let start_row = placed.y / cell_height
    let end_row = (placed.y + placed.height - 1) / cell_height
    for row in start_row..<(end_row + 1) {
      for col in start_col..<(end_col + 1) {
        if row >= 0 && row < rows && col >= 0 && col < cols {
          cells[row * cols + col] = mark
        }
      }
    }
  }
  let out = StringBuilder()
  for row in 0..