mod block_elements;
mod box_drawing;
mod box_drawing_data;
mod geometric_shapes;
mod snapping;
pub(super) mod types;
pub use block_elements::get_geometric_block;
pub use box_drawing::get_box_drawing_geometry;
pub use geometric_shapes::get_geometric_shape_rect;
pub use snapping::{SnapGlyphParams, snap_glyph_to_cell};
pub use types::{BlockCharType, BoxDrawingGeometry, GeometricBlock, PixelRect, ranges};
pub fn classify_char(ch: char) -> BlockCharType {
let code = ch as u32;
if (ranges::BOX_DRAWING_START..=ranges::BOX_DRAWING_END).contains(&code) {
return BlockCharType::BoxDrawing;
}
if (ranges::BLOCK_ELEMENTS_START..=ranges::BLOCK_ELEMENTS_END).contains(&code) {
return classify_block_element(ch);
}
if (ranges::GEOMETRIC_SHAPES_START..=ranges::GEOMETRIC_SHAPES_END).contains(&code) {
return BlockCharType::Geometric;
}
if (ranges::POWERLINE_START..=ranges::POWERLINE_END).contains(&code) {
return BlockCharType::Powerline;
}
if (ranges::BRAILLE_START..=ranges::BRAILLE_END).contains(&code) {
return BlockCharType::Braille;
}
if (ranges::MISC_SYMBOLS_START..=ranges::MISC_SYMBOLS_END).contains(&code) {
return BlockCharType::Symbol;
}
if (ranges::DINGBATS_START..=ranges::DINGBATS_END).contains(&code) {
return BlockCharType::Symbol;
}
BlockCharType::None
}
fn classify_block_element(ch: char) -> BlockCharType {
match ch {
'\u{2591}' | '\u{2592}' | '\u{2593}' => BlockCharType::Shade,
'\u{2588}' => BlockCharType::SolidBlock,
'\u{2580}'..='\u{2590}' | '\u{2594}'..='\u{259F}' => BlockCharType::PartialBlock,
_ => BlockCharType::PartialBlock,
}
}
pub fn should_snap_to_boundaries(char_type: BlockCharType) -> bool {
matches!(
char_type,
BlockCharType::BoxDrawing
| BlockCharType::SolidBlock
| BlockCharType::PartialBlock
| BlockCharType::Geometric
| BlockCharType::Powerline
| BlockCharType::Symbol
)
}
pub fn should_render_geometrically(char_type: BlockCharType) -> bool {
matches!(
char_type,
BlockCharType::SolidBlock
| BlockCharType::PartialBlock
| BlockCharType::BoxDrawing
| BlockCharType::Geometric
)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_classify_box_drawing() {
assert_eq!(classify_char('─'), BlockCharType::BoxDrawing);
assert_eq!(classify_char('━'), BlockCharType::BoxDrawing);
assert_eq!(classify_char('═'), BlockCharType::BoxDrawing);
assert_eq!(classify_char('│'), BlockCharType::BoxDrawing);
assert_eq!(classify_char('┃'), BlockCharType::BoxDrawing);
assert_eq!(classify_char('║'), BlockCharType::BoxDrawing);
assert_eq!(classify_char('┌'), BlockCharType::BoxDrawing);
assert_eq!(classify_char('┐'), BlockCharType::BoxDrawing);
assert_eq!(classify_char('└'), BlockCharType::BoxDrawing);
assert_eq!(classify_char('┘'), BlockCharType::BoxDrawing);
assert_eq!(classify_char('╔'), BlockCharType::BoxDrawing);
assert_eq!(classify_char('╗'), BlockCharType::BoxDrawing);
assert_eq!(classify_char('╚'), BlockCharType::BoxDrawing);
assert_eq!(classify_char('╝'), BlockCharType::BoxDrawing);
}
#[test]
fn test_classify_block_elements() {
assert_eq!(classify_char('█'), BlockCharType::SolidBlock);
assert_eq!(classify_char('▀'), BlockCharType::PartialBlock);
assert_eq!(classify_char('▄'), BlockCharType::PartialBlock);
assert_eq!(classify_char('▌'), BlockCharType::PartialBlock);
assert_eq!(classify_char('▐'), BlockCharType::PartialBlock);
assert_eq!(classify_char('░'), BlockCharType::Shade);
assert_eq!(classify_char('▒'), BlockCharType::Shade);
assert_eq!(classify_char('▓'), BlockCharType::Shade);
assert_eq!(classify_char('▖'), BlockCharType::PartialBlock);
assert_eq!(classify_char('▗'), BlockCharType::PartialBlock);
assert_eq!(classify_char('▘'), BlockCharType::PartialBlock);
assert_eq!(classify_char('▝'), BlockCharType::PartialBlock);
}
#[test]
fn test_classify_geometric_shapes() {
assert_eq!(classify_char('■'), BlockCharType::Geometric);
assert_eq!(classify_char('□'), BlockCharType::Geometric);
assert_eq!(classify_char('▪'), BlockCharType::Geometric);
assert_eq!(classify_char('▫'), BlockCharType::Geometric);
}
#[test]
fn test_classify_regular_chars() {
assert_eq!(classify_char('a'), BlockCharType::None);
assert_eq!(classify_char('Z'), BlockCharType::None);
assert_eq!(classify_char('0'), BlockCharType::None);
assert_eq!(classify_char(' '), BlockCharType::None);
assert_eq!(classify_char('!'), BlockCharType::None);
}
#[test]
fn test_should_snap_to_boundaries() {
assert!(should_snap_to_boundaries(BlockCharType::BoxDrawing));
assert!(should_snap_to_boundaries(BlockCharType::SolidBlock));
assert!(should_snap_to_boundaries(BlockCharType::PartialBlock));
assert!(should_snap_to_boundaries(BlockCharType::Geometric));
assert!(should_snap_to_boundaries(BlockCharType::Powerline));
assert!(!should_snap_to_boundaries(BlockCharType::None));
assert!(!should_snap_to_boundaries(BlockCharType::Shade));
assert!(!should_snap_to_boundaries(BlockCharType::Braille));
}
#[test]
fn test_should_render_geometrically() {
assert!(should_render_geometrically(BlockCharType::SolidBlock));
assert!(should_render_geometrically(BlockCharType::PartialBlock));
assert!(should_render_geometrically(BlockCharType::BoxDrawing));
assert!(should_render_geometrically(BlockCharType::Geometric));
assert!(!should_render_geometrically(BlockCharType::None));
assert!(!should_render_geometrically(BlockCharType::Shade));
assert!(!should_render_geometrically(BlockCharType::Powerline));
assert!(!should_render_geometrically(BlockCharType::Braille));
}
#[test]
fn test_geometric_block_full() {
let block = get_geometric_block('█').unwrap();
assert_eq!(block.x, 0.0);
assert_eq!(block.y, 0.0);
assert_eq!(block.width, 1.0);
assert_eq!(block.height, 1.0);
}
#[test]
fn test_geometric_block_halves() {
let block = get_geometric_block('▀').unwrap();
assert_eq!(block.x, 0.0);
assert_eq!(block.y, 0.0);
assert_eq!(block.width, 1.0);
assert_eq!(block.height, 0.5);
let block = get_geometric_block('▄').unwrap();
assert_eq!(block.x, 0.0);
assert_eq!(block.y, 0.5);
assert_eq!(block.width, 1.0);
assert_eq!(block.height, 0.5);
let block = get_geometric_block('▌').unwrap();
assert_eq!(block.x, 0.0);
assert_eq!(block.y, 0.0);
assert_eq!(block.width, 0.5);
assert_eq!(block.height, 1.0);
let block = get_geometric_block('▐').unwrap();
assert_eq!(block.x, 0.5);
assert_eq!(block.y, 0.0);
assert_eq!(block.width, 0.5);
assert_eq!(block.height, 1.0);
}
#[test]
fn test_geometric_block_quadrants() {
let block = get_geometric_block('▖').unwrap();
assert_eq!(block.x, 0.0);
assert_eq!(block.y, 0.5);
assert_eq!(block.width, 0.5);
assert_eq!(block.height, 0.5);
let block = get_geometric_block('▗').unwrap();
assert_eq!(block.x, 0.5);
assert_eq!(block.y, 0.5);
assert_eq!(block.width, 0.5);
assert_eq!(block.height, 0.5);
let block = get_geometric_block('▘').unwrap();
assert_eq!(block.x, 0.0);
assert_eq!(block.y, 0.0);
assert_eq!(block.width, 0.5);
assert_eq!(block.height, 0.5);
let block = get_geometric_block('▝').unwrap();
assert_eq!(block.x, 0.5);
assert_eq!(block.y, 0.0);
assert_eq!(block.width, 0.5);
assert_eq!(block.height, 0.5);
}
#[test]
fn test_geometric_block_to_pixel_rect() {
let block = GeometricBlock::new(0.0, 0.5, 1.0, 0.5); let rect = block.to_pixel_rect(10.0, 20.0, 8.0, 16.0);
assert_eq!(rect.x, 10.0);
assert_eq!(rect.y, 28.0); assert_eq!(rect.width, 8.0);
assert_eq!(rect.height, 8.0);
}
#[test]
fn test_box_drawing_light_horizontal() {
let geo = get_box_drawing_geometry('─', 2.0).unwrap();
assert_eq!(geo.segments.len(), 1);
let seg = &geo.segments[0];
assert_eq!(seg.x, 0.0);
assert!(seg.width > 0.99); }
#[test]
fn test_box_drawing_light_corner() {
let geo = get_box_drawing_geometry('┌', 2.0).unwrap();
assert_eq!(geo.segments.len(), 2);
}
#[test]
fn test_box_drawing_double_lines() {
let geo = get_box_drawing_geometry('═', 2.0).unwrap();
assert_eq!(geo.segments.len(), 2);
}
#[test]
fn test_snap_glyph_to_cell_basic() {
let (left, top, w, h) = snap_glyph_to_cell(SnapGlyphParams {
glyph_left: 10.5,
glyph_top: 20.5,
render_w: 7.8,
render_h: 15.8,
cell_x0: 10.0,
cell_y0: 20.0,
cell_x1: 18.0,
cell_y1: 36.0,
snap_threshold: 3.0,
extension: 0.5,
});
assert!((left - 9.5).abs() < 0.01);
assert!((top - 19.5).abs() < 0.01);
assert!((left + w - 18.5).abs() < 0.01);
assert!((top + h - 36.5).abs() < 0.01);
}
#[test]
fn test_snap_glyph_no_snap_when_far() {
let (left, top, w, h) = snap_glyph_to_cell(SnapGlyphParams {
glyph_left: 12.0,
glyph_top: 24.0,
render_w: 5.0,
render_h: 10.0,
cell_x0: 10.0,
cell_y0: 20.0,
cell_x1: 20.0,
cell_y1: 40.0,
snap_threshold: 1.5,
extension: 0.5,
});
assert_eq!(left, 12.0);
assert_eq!(top, 24.0);
assert_eq!(w, 5.0);
assert_eq!(h, 10.0);
}
#[test]
fn test_snap_glyph_middle_snap() {
let (_left, top, _w, h) = snap_glyph_to_cell(SnapGlyphParams {
glyph_left: 10.0,
glyph_top: 20.0,
render_w: 8.0,
render_h: 9.8,
cell_x0: 10.0,
cell_y0: 20.0,
cell_x1: 18.0,
cell_y1: 40.0,
snap_threshold: 1.0,
extension: 0.0,
});
assert!((top + h - 30.0).abs() < 0.01);
}
#[test]
fn test_geometric_shape_rect_black_square() {
let rect = get_geometric_shape_rect('\u{25A0}', 10.0, 20.0, 8.0, 16.0).unwrap();
assert_eq!(rect.x, 10.0);
assert_eq!(rect.y, 24.0); assert_eq!(rect.width, 8.0);
assert_eq!(rect.height, 8.0);
}
#[test]
fn test_geometric_shape_rect_medium_square() {
let rect = get_geometric_shape_rect('\u{25FC}', 10.0, 20.0, 8.0, 16.0).unwrap();
let size = 8.0 * 0.75; assert_eq!(rect.x, 10.0 + (8.0 - size) / 2.0);
assert_eq!(rect.y, 20.0 + (16.0 - size) / 2.0);
assert_eq!(rect.width, size);
assert_eq!(rect.height, size);
}
#[test]
fn test_geometric_shape_rect_small_square() {
let rect = get_geometric_shape_rect('\u{25AA}', 10.0, 20.0, 8.0, 16.0).unwrap();
let size = 8.0 * 0.5; assert_eq!(rect.x, 10.0 + (8.0 - size) / 2.0);
assert_eq!(rect.y, 20.0 + (16.0 - size) / 2.0);
assert_eq!(rect.width, size);
assert_eq!(rect.height, size);
}
#[test]
fn test_geometric_shape_rect_rectangle() {
let rect = get_geometric_shape_rect('\u{25AC}', 10.0, 20.0, 8.0, 16.0).unwrap();
let h = 16.0 * 0.33;
assert_eq!(rect.x, 10.0);
assert!((rect.y - (20.0 + (16.0 - h) / 2.0)).abs() < 0.01);
assert_eq!(rect.width, 8.0);
assert!((rect.height - h).abs() < 0.01);
}
#[test]
fn test_geometric_shape_rect_vertical_rectangle() {
let rect = get_geometric_shape_rect('\u{25AE}', 10.0, 20.0, 8.0, 16.0).unwrap();
let w = 8.0 * 0.5;
assert_eq!(rect.x, 10.0 + (8.0 - w) / 2.0);
assert_eq!(rect.y, 20.0);
assert_eq!(rect.width, w);
assert_eq!(rect.height, 16.0);
}
#[test]
fn test_geometric_shape_rect_outline_returns_none() {
assert!(get_geometric_shape_rect('\u{25A1}', 0.0, 0.0, 8.0, 16.0).is_none()); assert!(get_geometric_shape_rect('\u{25AB}', 0.0, 0.0, 8.0, 16.0).is_none()); assert!(get_geometric_shape_rect('\u{25FB}', 0.0, 0.0, 8.0, 16.0).is_none()); assert!(get_geometric_shape_rect('\u{25FD}', 0.0, 0.0, 8.0, 16.0).is_none()); }
}