use core::fmt::Debug;
use float_pigment_css::num_traits::Zero;
use alloc::vec::Vec;
use float_pigment_css::typing::GridAutoFlow;
use crate::{
algo::grid::grid_item::{GridItem, GridLayoutItem},
LayoutTreeNode,
};
#[derive(Clone, PartialEq)]
pub(crate) struct OccupiedBitmap {
bits: Vec<u8>,
stride: usize,
bytes_per_line: usize,
row_order: bool,
}
impl OccupiedBitmap {
fn new(stride: usize, row_order: bool, capacity: usize) -> Self {
debug_assert!(stride >= 1);
let stride: usize = stride.max(1);
let bytes_per_line = stride.div_ceil(8);
let estimated_lines = if stride > 0 {
capacity.div_ceil(stride)
} else {
0
};
let total_bytes = estimated_lines * bytes_per_line;
Self {
bits: alloc::vec![0u8; total_bytes],
stride,
bytes_per_line,
row_order,
}
}
#[inline(always)]
fn byte_and_bit(&self, row: usize, col: usize) -> (usize, usize) {
let (line, offset) = if self.row_order {
(row, col)
} else {
(col, row)
};
let byte_idx = line * self.bytes_per_line + offset / 8;
let bit_idx = offset % 8;
(byte_idx, bit_idx)
}
#[inline]
fn ensure_capacity(&mut self, byte_idx: usize) {
let required_bytes = byte_idx + 1;
if required_bytes > self.bits.len() {
self.bits.resize(required_bytes, 0u8);
}
}
#[inline]
fn set(&mut self, row: usize, col: usize) {
let (byte, bit) = self.byte_and_bit(row, col);
self.ensure_capacity(byte);
self.bits[byte] |= 1u8 << bit;
}
#[cfg(test)]
#[inline]
fn get(&self, row: usize, col: usize) -> bool {
let (byte, bit) = self.byte_and_bit(row, col);
if byte >= self.bits.len() {
return false;
}
(self.bits[byte] & (1u8 << bit)) != 0
}
fn grow_stride(&mut self, new_stride: usize, max_lines: usize) {
debug_assert!(new_stride >= 1);
debug_assert!(new_stride > self.stride, "stride can only grow");
let new_stride: usize = new_stride.max(1);
let old_bytes_per_line = self.bytes_per_line;
let new_bytes_per_line = new_stride.div_ceil(8);
let new_total_bytes = max_lines * new_bytes_per_line;
self.bits.resize(new_total_bytes, 0u8);
for line_idx in (0..max_lines).rev() {
let old_start = line_idx * old_bytes_per_line;
let new_start = line_idx * new_bytes_per_line;
for byte_idx in (0..old_bytes_per_line).rev() {
let src = old_start + byte_idx;
let dst = new_start + byte_idx;
debug_assert!(src < self.bits.len(), "src out of bounds");
debug_assert!(dst < self.bits.len(), "dst out of bounds");
self.bits[dst] = self.bits[src];
}
for byte_idx in old_bytes_per_line..new_bytes_per_line {
self.bits[new_start + byte_idx] = 0;
}
}
self.stride = new_stride;
self.bytes_per_line = new_bytes_per_line;
}
fn find_first_zero(&self, start_line_idx: usize) -> (usize, usize) {
let mut line_idx = start_line_idx;
loop {
let total_bytes = self.bits.len();
let line_start_byte_idx = line_idx * self.bytes_per_line;
let line_end_byte_idx = line_start_byte_idx + self.bytes_per_line;
let mut byte_idx = line_start_byte_idx;
let mut offset = 0usize;
while byte_idx < line_end_byte_idx {
if byte_idx >= total_bytes {
return (line_idx, offset);
}
let b = self.bits[byte_idx];
if b != 0xFF {
let bit_pos = b.trailing_ones() as usize;
let candidate = offset + bit_pos;
if candidate < self.stride {
return (line_idx, candidate);
}
break;
}
byte_idx += 1;
offset += 8;
}
line_idx += 1;
}
}
#[inline(always)]
fn stride(&self) -> usize {
self.stride
}
}
#[derive(Clone, PartialEq)]
pub(crate) struct GridMatrix<'a, T: LayoutTreeNode> {
occupied: OccupiedBitmap,
max_row: usize,
max_col: usize,
items: Vec<GridItem<'a, T>>,
explicit_row_count: usize,
explicit_column_count: usize,
flow: GridAutoFlow,
}
impl<'a, T: LayoutTreeNode> GridMatrix<'a, T> {
pub(crate) fn new(
explicit_row_count: usize,
explicit_column_count: usize,
flow: GridAutoFlow,
capacity: usize,
) -> Self {
let (row_order, stride) = match flow {
GridAutoFlow::Row | GridAutoFlow::RowDense => (true, explicit_column_count.max(1)),
GridAutoFlow::Column | GridAutoFlow::ColumnDense => (false, explicit_row_count.max(1)),
};
Self {
occupied: OccupiedBitmap::new(stride, row_order, capacity),
max_row: 0,
max_col: 0,
items: Vec::with_capacity(capacity),
explicit_row_count,
explicit_column_count,
flow,
}
}
pub(crate) fn place_item(&mut self, row: usize, column: usize, item: GridItem<'a, T>) {
let stride = if self.occupied.row_order {
column + 1
} else {
row + 1
};
if stride > self.occupied.stride() {
let max_lines = if self.occupied.row_order {
self.max_row
} else {
self.max_col
};
self.occupied.grow_stride(stride, max_lines);
}
self.occupied.set(row, column);
self.max_row = self.max_row.max(row + 1);
self.max_col = self.max_col.max(column + 1);
self.items.push(item);
}
pub(crate) fn find_first_unoccupied(&self, hint_line: &mut usize) -> (usize, usize) {
let (line, offset) = self.occupied.find_first_zero(*hint_line);
if line > *hint_line {
*hint_line = line;
}
if self.occupied.row_order {
(line, offset)
} else {
(offset, line)
}
}
pub(crate) fn items(&self) -> impl Iterator<Item = &GridItem<'a, T>> {
self.items.iter()
}
pub(crate) fn items_mut(&mut self) -> impl Iterator<Item = &mut GridItem<'a, T>> {
self.items.iter_mut()
}
#[inline(always)]
pub(crate) fn row_count(&self) -> usize {
self.max_row
}
#[inline(always)]
pub(crate) fn column_count(&self) -> usize {
self.max_col
}
#[inline(always)]
pub(crate) fn explicit_row_count(&self) -> usize {
self.explicit_row_count
}
#[inline(always)]
pub(crate) fn explicit_column_count(&self) -> usize {
self.explicit_column_count
}
#[inline(always)]
pub(crate) fn flow(&self) -> GridAutoFlow {
self.flow.clone()
}
}
impl<'a, T: LayoutTreeNode> Debug for GridMatrix<'a, T> {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
write!(
f,
"GridMatrix {{ row_count: {}, column_count: {}, explicit: {}x{}, items: {} }}",
self.row_count(),
self.column_count(),
self.explicit_row_count,
self.explicit_column_count,
self.items.len()
)
}
}
pub(crate) struct GridLayoutMatrix<'a, T: LayoutTreeNode> {
row_offsets: Vec<T::Length>,
column_offsets: Vec<T::Length>,
items: Vec<GridLayoutItem<'a, T>>,
row_count: usize,
column_count: usize,
}
impl<'a, T: LayoutTreeNode> GridLayoutMatrix<'a, T> {
pub(crate) fn new(row_count: usize, column_count: usize, capacity: usize) -> Self {
Self {
row_offsets: vec![T::Length::zero(); row_count + 1],
column_offsets: vec![T::Length::zero(); column_count + 1],
items: Vec::with_capacity(capacity),
row_count,
column_count,
}
}
#[inline(always)]
pub(crate) fn row_count(&self) -> usize {
self.row_count
}
#[inline(always)]
pub(crate) fn column_count(&self) -> usize {
self.column_count
}
pub(crate) fn add_item(&mut self, item: GridLayoutItem<'a, T>) {
self.items.push(item);
}
pub(crate) fn items(&self) -> impl Iterator<Item = &GridLayoutItem<'a, T>> {
self.items.iter()
}
pub(crate) fn items_mut(&mut self) -> impl Iterator<Item = &mut GridLayoutItem<'a, T>> {
self.items.iter_mut()
}
pub(crate) fn set_row_sizes(&mut self, sizes: &[T::Length], gap: T::Length) {
use float_pigment_css::num_traits::Zero;
let mut offset = T::Length::zero();
self.row_offsets[0] = offset;
for (i, &size) in sizes.iter().enumerate() {
offset += size;
if i < sizes.len() - 1 {
offset += gap;
}
self.row_offsets[i + 1] = offset;
}
}
pub(crate) fn set_column_sizes(&mut self, sizes: &[T::Length], gap: T::Length) {
use float_pigment_css::num_traits::Zero;
let mut offset = T::Length::zero();
self.column_offsets[0] = offset;
for (i, &size) in sizes.iter().enumerate() {
offset += size;
if i < sizes.len() - 1 {
offset += gap;
}
self.column_offsets[i + 1] = offset;
}
}
#[inline(always)]
pub(crate) fn get_row_offset(&self, row: usize) -> T::Length {
self.row_offsets[row]
}
#[inline(always)]
pub(crate) fn get_column_offset(&self, column: usize) -> T::Length {
self.column_offsets[column]
}
}
#[cfg(test)]
mod tests {
use super::OccupiedBitmap;
#[test]
fn set_get_basic_row_order() {
let mut bm = OccupiedBitmap::new(3, true, 9);
bm.set(0, 0);
bm.set(1, 2);
bm.set(2, 1);
assert!(bm.get(0, 0));
assert!(bm.get(1, 2));
assert!(bm.get(2, 1));
}
#[test]
fn get_unset_returns_false() {
let bm = OccupiedBitmap::new(4, true, 16);
for r in 0..4 {
for c in 0..4 {
assert!(!bm.get(r, c), "cell ({r},{c}) should be empty");
}
}
}
#[test]
fn set_get_column_order() {
let mut bm = OccupiedBitmap::new(3, false, 9);
bm.set(2, 0); bm.set(0, 1); assert!(bm.get(2, 0));
assert!(bm.get(0, 1));
assert!(!bm.get(0, 0));
assert!(!bm.get(2, 1));
}
#[test]
fn auto_expand_beyond_initial_capacity() {
let mut bm = OccupiedBitmap::new(2, true, 4);
bm.set(10, 0);
bm.set(10, 1);
assert!(bm.get(10, 0));
assert!(bm.get(10, 1));
assert!(!bm.get(0, 0));
}
#[test]
fn grow_stride_preserves_existing_data() {
let mut bm = OccupiedBitmap::new(2, true, 6);
bm.set(0, 0);
bm.set(0, 1);
bm.set(1, 0);
bm.set(2, 1);
bm.grow_stride(5, 3);
assert_eq!(bm.stride(), 5);
assert!(bm.get(0, 0));
assert!(bm.get(0, 1));
assert!(bm.get(1, 0));
assert!(bm.get(2, 1));
assert!(!bm.get(0, 2));
assert!(!bm.get(0, 3));
assert!(!bm.get(0, 4));
}
#[test]
fn grow_stride_allows_writing_new_columns() {
let mut bm = OccupiedBitmap::new(2, true, 4);
bm.set(0, 0);
bm.set(1, 1);
bm.grow_stride(4, 2);
bm.set(0, 3);
bm.set(1, 2);
assert!(bm.get(0, 0));
assert!(bm.get(0, 3));
assert!(bm.get(1, 1));
assert!(bm.get(1, 2));
assert!(!bm.get(0, 2));
}
#[test]
fn grow_stride_column_order() {
let mut bm = OccupiedBitmap::new(2, false, 6);
bm.set(0, 0); bm.set(1, 1); bm.grow_stride(4, 2);
assert!(bm.get(0, 0));
assert!(bm.get(1, 1));
assert!(!bm.get(2, 0));
bm.set(3, 0);
assert!(bm.get(3, 0));
}
#[test]
fn find_first_zero_empty_bitmap() {
let bm = OccupiedBitmap::new(3, true, 9);
assert_eq!(bm.find_first_zero(0), (0, 0));
}
#[test]
fn find_first_zero_partially_filled() {
let mut bm = OccupiedBitmap::new(4, true, 16);
bm.set(0, 0);
bm.set(0, 1);
assert_eq!(bm.find_first_zero(0), (0, 2));
}
#[test]
fn find_first_zero_skips_full_line() {
let mut bm = OccupiedBitmap::new(3, true, 9);
bm.set(0, 0);
bm.set(0, 1);
bm.set(0, 2);
assert_eq!(bm.find_first_zero(0), (1, 0));
}
#[test]
fn find_first_zero_with_start_line() {
let mut bm = OccupiedBitmap::new(3, true, 9);
bm.set(0, 0);
bm.set(1, 0);
bm.set(1, 1);
assert_eq!(bm.find_first_zero(1), (1, 2));
}
#[test]
fn find_first_zero_beyond_allocated() {
let bm = OccupiedBitmap::new(4, true, 4);
assert_eq!(bm.find_first_zero(5), (5, 0));
}
#[test]
fn find_first_zero_stride_gt_8() {
let mut bm = OccupiedBitmap::new(10, true, 20);
for c in 0..9 {
bm.set(0, c);
}
assert_eq!(bm.find_first_zero(0), (0, 9));
}
#[test]
fn find_first_zero_full_byte_boundary() {
let mut bm = OccupiedBitmap::new(8, true, 16);
for c in 0..8 {
bm.set(0, c);
}
assert_eq!(bm.find_first_zero(0), (1, 0));
}
#[test]
fn single_column_grid() {
let mut bm = OccupiedBitmap::new(1, true, 4);
bm.set(0, 0);
bm.set(1, 0);
assert_eq!(bm.find_first_zero(0), (2, 0));
}
#[test]
fn grow_stride_across_byte_boundary() {
let mut bm = OccupiedBitmap::new(7, true, 14);
bm.set(0, 6);
bm.set(1, 3);
bm.grow_stride(10, 2);
assert!(bm.get(0, 6));
assert!(bm.get(1, 3));
assert!(!bm.get(0, 7));
assert!(!bm.get(1, 9));
bm.set(0, 9);
assert!(bm.get(0, 9));
}
}