use self::guillotine::GuillotineBin;
use self::maxrects::MaxRectsBin;
use crate::dimension::Dimension;
use crate::rectangle::Rectangle;
use std::error::Error;
use std::fmt::{Display, Formatter};
use std::slice::Iter;
pub mod guillotine;
pub mod maxrects;
#[derive(Copy, Clone, Debug, PartialEq)]
pub enum BinType {
MaxRects,
Guillotine,
}
pub trait BinPacker: Display {
fn width(&self) -> i32;
fn height(&self) -> i32;
fn clear(&mut self) {
self.clear_with(4);
}
fn clear_with(&mut self, capacity: usize);
fn grow(&mut self, dw: u32, dh: u32);
fn shrink(&mut self, binary: bool);
fn insert(&mut self, dim: &Dimension) -> Option<Rectangle>;
fn insert_list(&mut self, nodes: &[Dimension]) -> (Vec<Rectangle>, Vec<Dimension>);
fn occupancy(&self) -> f32;
fn as_slice(&self) -> &[Rectangle];
fn is_empty(&self) -> bool;
fn len(&self) -> usize;
fn iter(&self) -> Iter<'_, Rectangle>;
fn find_by_id(&self, id: isize) -> Option<Rectangle>;
fn visualize(&self) -> String;
}
#[derive(Debug, PartialEq)]
pub enum BinError {
ItemTooBig,
ItemTooSmall,
Unspecified,
}
impl Display for BinError {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
let s = match self {
Self::ItemTooBig => "item is too big for the bin",
Self::ItemTooSmall => "item with no space cannot be placed into the bin",
_ => "unspecified error",
};
f.write_str(s)
}
}
impl Error for BinError {}
pub fn bin_new(bin_type: BinType, width: i32, height: i32) -> Box<dyn BinPacker> {
match bin_type {
BinType::MaxRects => Box::new(MaxRectsBin::new(width, height)),
BinType::Guillotine => Box::new(GuillotineBin::new(width, height)),
}
}
pub fn bin_with_capacity(
bin_type: BinType,
width: i32,
height: i32,
capacity: usize,
) -> Box<dyn BinPacker> {
match bin_type {
BinType::MaxRects => Box::new(MaxRectsBin::with_capacity(width, height, capacity)),
BinType::Guillotine => Box::new(GuillotineBin::with_capacity(width, height, capacity)),
}
}
pub fn pack_bins(
bin_type: BinType,
nodes: &[Dimension],
bin_width: i32,
bin_height: i32,
optimized: bool,
) -> Result<Vec<Box<dyn BinPacker>>, BinError> {
if optimized {
pack_bins_list(bin_type, nodes, bin_width, bin_height)
} else {
pack_bins_single(bin_type, nodes, bin_width, bin_height)
}
}
fn pack_bins_list(
bin_type: BinType,
nodes: &[Dimension],
bin_width: i32,
bin_height: i32,
) -> Result<Vec<Box<dyn BinPacker>>, BinError> {
let mut bins = Vec::new();
if nodes.is_empty() || bin_width == 0 || bin_height == 0 {
return Ok(bins);
}
let mut bin = bin_new(bin_type, bin_width, bin_height);
let (inserted, mut rejected) = bin.insert_list(nodes);
if inserted.is_empty() && !rejected.is_empty() {
rejected.clear();
}
if !inserted.is_empty() {
bins.push(bin);
}
let mut nodes_left = rejected;
while !nodes_left.is_empty() {
let mut bin = bin_new(bin_type, bin_width, bin_height);
let (inserted, mut rejected) = bin.insert_list(&nodes_left);
if inserted.is_empty() && !rejected.is_empty() {
let result = rejected
.iter()
.map(|r| {
if r.width_total() == 0 || r.height_total() == 0 {
BinError::ItemTooSmall
} else if r.width_total() > bin_width || r.height_total() > bin_height {
BinError::ItemTooBig
} else {
BinError::Unspecified
}
})
.next();
if let Some(result) = result {
return Err(result);
} else {
eprintln!("pack_bins(): Could not insert remaining items");
rejected.clear();
}
}
if !inserted.is_empty() {
bins.push(bin);
}
nodes_left.clear();
nodes_left.append(&mut rejected);
}
Ok(bins)
}
fn pack_bins_single(
bin_type: BinType,
nodes: &[Dimension],
bin_width: i32,
bin_height: i32,
) -> Result<Vec<Box<dyn BinPacker>>, BinError> {
let mut bins = Vec::new();
if nodes.is_empty() || bin_width == 0 || bin_height == 0 {
return Ok(bins);
}
for node in nodes {
if node.is_empty() {
return Err(BinError::ItemTooSmall);
} else if node.width_total() > bin_width || node.height_total() > bin_height {
return Err(BinError::ItemTooBig);
}
let mut inserted = false;
for bin in &mut bins {
if bin.insert(node).is_some() {
inserted = true;
break;
}
}
if !inserted {
bins.push(bin_new(bin_type, bin_width, bin_height));
if let Some(bin) = bins.last_mut() {
bin.insert(node).expect("Object should fit into the bin");
}
}
}
Ok(bins)
}
fn visualize_bin(width: i32, height: i32, rects: &Vec<Rectangle>) -> Option<String> {
if width > 0 && height > 0 && rects.len() <= 62 {
let size = (width * height) as usize;
let mut grid = vec![0u32; size];
for y in 0..height {
for x in 0..width {
for (i, r) in rects.iter().enumerate() {
if r.y() <= y && r.y() + r.height() > y && r.x() <= x && r.x() + r.width() > x {
let pos = (y * width + x) as usize;
grid[pos] = (i + 1) as u32;
}
}
}
}
let mut output = String::with_capacity(grid.len() + height as usize);
for y in 0..height {
let pos = (y * width) as usize;
let line: String = grid[pos..(pos + width as usize)]
.iter()
.map(|v| match v {
0 => '.',
1..=10 => char::from_digit(v - 1, 10).unwrap_or('?'),
11..=36 => char::from_u32('a' as u32 + v - 11).unwrap_or('?'),
37..=62 => char::from_u32('A' as u32 + v - 37).unwrap_or('?'),
_ => '?',
})
.collect();
output.push_str(&line);
output.push('\n');
}
output.pop(); Some(output)
} else {
None
}
}
#[cfg(test)]
mod tests;