mod curves;
mod fill;
mod glyphs;
pub use fill::{fill_h, fill_v};
pub use glyphs::{braille_bit, H_EIGHTHS, QUADRANT_CHARS, V_EIGHTHS};
use crate::base::{Point, Rgba};
use crate::ui::{Canvas, StyledCanvas};
#[non_exhaustive]
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub enum DotMode {
Braille,
Quadrant,
}
impl DotMode {
const fn dot_rows(self) -> i32 {
match self {
DotMode::Braille => 4,
DotMode::Quadrant => 2,
}
}
}
pub struct DotCanvas {
mode: DotMode,
cells_w: i32,
cells_h: i32,
bits: Vec<u8>,
}
impl DotCanvas {
pub fn new(mode: DotMode, cells_w: i32, cells_h: i32) -> DotCanvas {
let cells_w = cells_w.max(0);
let cells_h = cells_h.max(0);
DotCanvas {
mode,
cells_w,
cells_h,
bits: vec![0; (cells_w * cells_h) as usize],
}
}
pub fn braille(cells_w: i32, cells_h: i32) -> DotCanvas {
DotCanvas::new(DotMode::Braille, cells_w, cells_h)
}
pub fn quadrant(cells_w: i32, cells_h: i32) -> DotCanvas {
DotCanvas::new(DotMode::Quadrant, cells_w, cells_h)
}
pub fn mode(&self) -> DotMode {
self.mode
}
pub fn cells_w(&self) -> i32 {
self.cells_w
}
pub fn cells_h(&self) -> i32 {
self.cells_h
}
pub fn dots_w(&self) -> i32 {
self.cells_w * 2
}
pub fn dots_h(&self) -> i32 {
self.cells_h * self.mode.dot_rows()
}
fn dot_bit(&self, col: i32, row: i32) -> u8 {
match self.mode {
DotMode::Braille => glyphs::braille_bit(col, row),
DotMode::Quadrant => 1u8 << (row * 2 + col),
}
}
fn slot(&self, x: i32, y: i32) -> Option<(usize, u8)> {
if x < 0 || y < 0 || x >= self.dots_w() || y >= self.dots_h() {
return None;
}
let rows = self.mode.dot_rows();
let idx = ((y / rows) * self.cells_w + x / 2) as usize;
Some((idx, self.dot_bit(x % 2, y % rows)))
}
pub fn set(&mut self, x: i32, y: i32) {
if let Some((idx, bit)) = self.slot(x, y) {
self.bits[idx] |= bit;
}
}
pub fn clear(&mut self, x: i32, y: i32) {
if let Some((idx, bit)) = self.slot(x, y) {
self.bits[idx] &= !bit;
}
}
pub fn get(&self, x: i32, y: i32) -> bool {
match self.slot(x, y) {
Some((idx, bit)) => self.bits[idx] & bit != 0,
None => false,
}
}
pub fn clear_all(&mut self) {
self.bits.fill(0);
}
pub fn line(&mut self, a: (i32, i32), b: (i32, i32)) {
let Some((a, b)) = self.clip_segment(a, b) else {
return;
};
let (mut x0, mut y0) = a;
let (x1, y1) = b;
let dx = (x1 - x0).abs();
let dy = -(y1 - y0).abs();
let sx = if x0 < x1 { 1 } else { -1 };
let sy = if y0 < y1 { 1 } else { -1 };
let mut err = dx + dy;
loop {
self.set(x0, y0);
if x0 == x1 && y0 == y1 {
break;
}
let e2 = 2 * err;
if e2 >= dy {
err += dy;
x0 += sx;
}
if e2 <= dx {
err += dx;
y0 += sy;
}
}
}
fn clip_segment(&self, a: (i32, i32), b: (i32, i32)) -> Option<((i32, i32), (i32, i32))> {
let (hi_x, hi_y) = (self.dots_w(), self.dots_h());
let in_box = |p: (i32, i32)| p.0 >= -1 && p.1 >= -1 && p.0 <= hi_x && p.1 <= hi_y;
if in_box(a) && in_box(b) {
return Some((a, b));
}
let (x0, y0) = (f64::from(a.0), f64::from(a.1));
let (dx, dy) = (f64::from(b.0) - x0, f64::from(b.1) - y0);
let mut t0 = 0.0f64;
let mut t1 = 1.0f64;
for (p, q) in [
(-dx, x0 + 1.0), (dx, f64::from(hi_x) - x0), (-dy, y0 + 1.0), (dy, f64::from(hi_y) - y0), ] {
if p == 0.0 {
if q < 0.0 {
return None; }
} else {
let r = q / p;
if p < 0.0 {
if r > t1 {
return None;
}
if r > t0 {
t0 = r;
}
} else {
if r < t0 {
return None;
}
if r < t1 {
t1 = r;
}
}
}
}
let at = |t: f64| ((x0 + t * dx).round() as i32, (y0 + t * dy).round() as i32);
Some((at(t0), at(t1)))
}
pub fn polyline(&mut self, points: &[(i32, i32)]) {
match points {
[] => {}
[p] => self.set(p.0, p.1),
_ => {
for w in points.windows(2) {
self.line(w[0], w[1]);
}
}
}
}
pub fn cell_char(&self, cx: i32, cy: i32) -> Option<char> {
if cx < 0 || cy < 0 || cx >= self.cells_w || cy >= self.cells_h {
return None;
}
let bits = self.bits[(cy * self.cells_w + cx) as usize];
if bits == 0 {
return None;
}
match self.mode {
DotMode::Braille => char::from_u32(0x2800 + bits as u32),
DotMode::Quadrant => Some(glyphs::QUADRANT_CHARS[bits as usize]),
}
}
pub fn blit<C: Canvas + ?Sized>(&self, canvas: &mut C, origin: Point, color: Rgba) {
for cy in 0..self.cells_h {
for cx in 0..self.cells_w {
if let Some(ch) = self.cell_char(cx, cy) {
canvas.put(
Point::new(origin.x + cx, origin.y + cy),
ch,
color,
Rgba::TRANSPARENT,
);
}
}
}
}
pub fn blit_styled<C: StyledCanvas + ?Sized>(
&self,
canvas: &mut C,
origin: Point,
style: &crate::render::Style,
) {
let mut buf = [0u8; 4];
for cy in 0..self.cells_h {
for cx in 0..self.cells_w {
if let Some(ch) = self.cell_char(cx, cy) {
let s: &str = ch.encode_utf8(&mut buf);
canvas.print_styled(Point::new(origin.x + cx, origin.y + cy), s, style);
}
}
}
}
}
#[cfg(test)]
mod tests;