pub const CURSOR_CHAR: char = '▌';
pub const PLACEHOLDER: char = '⬚';
pub const FRAC_BAR: char = '─';
pub const OP_BAND: char = '┈';
pub const DOUBLE_BODY: char = '═';
pub const HEAD_RIGHT: char = '>';
pub const HEAD_LEFT: char = '<';
pub const NORM: char = '‖';
pub const MID: char = '│';
pub const ARM_RISE: char = '╱'; pub const ARM_FALL: char = '╲';
pub const STEM: char = '│';
pub const OVERLINE_CORNER: char = '┌';
pub fn is_stem_glyph(c: char) -> bool {
c == STEM || crate::symbols::Radical::of_glyph(c).is_some()
}
pub const BRACE_TL: char = '╭'; pub const BRACE_TR: char = '╮'; pub const BRACE_BL: char = '╰'; pub const BRACE_BR: char = '╯';
pub const fn brace_corners(over: bool) -> (char, char) {
if over {
(BRACE_TL, BRACE_TR)
} else {
(BRACE_BL, BRACE_BR)
}
}
pub fn is_brace_corner(c: char) -> bool {
matches!(c, BRACE_TL | BRACE_TR | BRACE_BL | BRACE_BR)
}
pub const LATTICE: [[char; 3]; 3] = [['┌', '┬', '┐'], ['├', '┼', '┤'], ['└', '┴', '┘']];
pub fn lattice_char(row_kind: usize, col_kind: usize) -> char {
LATTICE[row_kind][col_kind]
}
pub fn is_lattice_glyph(c: char) -> bool {
LATTICE.iter().flatten().any(|&l| l == c)
}
pub const LATTICE_LEFT: [char; 3] = [LATTICE[0][0], LATTICE[1][0], LATTICE[2][0]];
pub const LATTICE_RIGHT: [char; 3] = [LATTICE[0][2], LATTICE[1][2], LATTICE[2][2]];
pub const LATTICE_TOP: [char; 3] = [LATTICE[0][0], LATTICE[0][1], LATTICE[0][2]];
pub const COL_MARK_TOP: char = LATTICE[0][1]; pub const COL_MARK_BOT: char = LATTICE[2][1]; pub const ROW_JUNCTION_L: char = LATTICE[1][0]; pub const ROW_JUNCTION_R: char = LATTICE[1][2]; pub const CROSSING: char = LATTICE[1][1];
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Mark {
Sel { open: bool },
BlockOpen { rank: usize },
BlockClose,
SlotGhost,
Cells { open: bool },
Lane { open: bool, cols: bool },
Frame { open: bool },
Delims { open: bool },
Gap { cols: bool },
MidArm,
Probe { index: usize },
}
const SEL_BASE: u32 = 0xE0F0;
const BLOCK_BASE: u32 = 0xE000;
pub const PROBE_BASE: u32 = 0xF000;
pub const PROBE_MAX: usize = 0x800;
impl Mark {
pub fn ch(self) -> char {
let u = match self {
Mark::Sel { open } => SEL_BASE + u32::from(!open),
Mark::BlockOpen { rank } => BLOCK_BASE + rank as u32,
Mark::BlockClose => SEL_BASE + 2,
Mark::SlotGhost => SEL_BASE + 3,
Mark::Gap { cols: true } => SEL_BASE + 4,
Mark::Lane { open, cols: true } => SEL_BASE + 5 + u32::from(!open),
Mark::Cells { open } => SEL_BASE + 7 + u32::from(!open),
Mark::Frame { open } => SEL_BASE + 9 + u32::from(!open),
Mark::Delims { open } => SEL_BASE + 11 + u32::from(!open),
Mark::Lane { open, cols: false } => SEL_BASE + 13 + u32::from(!open),
Mark::Gap { cols: false } => SEL_BASE + 15,
Mark::MidArm => SEL_BASE + 16,
Mark::Probe { index } => PROBE_BASE + index as u32,
};
char::from_u32(u).expect("marker chars are private-use scalars")
}
pub fn decode(c: char) -> Option<Mark> {
let u = c as u32;
let open = |n: u32| n.is_multiple_of(2);
match u {
_ if (SEL_BASE..SEL_BASE + 17).contains(&u) => Some(match u - SEL_BASE {
n @ (0 | 1) => Mark::Sel { open: open(n) },
2 => Mark::BlockClose,
3 => Mark::SlotGhost,
4 => Mark::Gap { cols: true },
n @ (5 | 6) => Mark::Lane {
open: open(n - 5),
cols: true,
},
n @ (7 | 8) => Mark::Cells { open: open(n - 7) },
n @ (9 | 10) => Mark::Frame { open: open(n - 9) },
n @ (11 | 12) => Mark::Delims { open: open(n - 11) },
n @ (13 | 14) => Mark::Lane {
open: open(n - 13),
cols: false,
},
15 => Mark::Gap { cols: false },
16 => Mark::MidArm,
_ => return None,
}),
_ if (BLOCK_BASE..SEL_BASE).contains(&u) => Some(Mark::BlockOpen {
rank: (u - BLOCK_BASE) as usize,
}),
_ if (PROBE_BASE..PROBE_BASE + PROBE_MAX as u32).contains(&u) => Some(Mark::Probe {
index: (u - PROBE_BASE) as usize,
}),
_ => None,
}
}
pub fn opener(self) -> Option<Mark> {
match self {
Mark::Sel { open: false } => Some(Mark::Sel { open: true }),
Mark::Cells { open: false } => Some(Mark::Cells { open: true }),
Mark::Lane { open: false, cols } => Some(Mark::Lane { open: true, cols }),
Mark::Frame { open: false } => Some(Mark::Frame { open: true }),
Mark::Delims { open: false } => Some(Mark::Delims { open: true }),
Mark::BlockClose => Some(Mark::BlockOpen { rank: 0 }),
_ => None,
}
}
}
pub fn is_display_marker(c: char) -> bool {
(0xE000..=0xF8FF).contains(&(c as u32))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn marks_round_trip_through_their_wire_chars() {
let mut all: Vec<Mark> = vec![
Mark::SlotGhost,
Mark::BlockClose,
Mark::BlockOpen { rank: 0 },
Mark::BlockOpen { rank: 0xEF },
];
for open in [true, false] {
all.push(Mark::Sel { open });
all.push(Mark::Cells { open });
all.push(Mark::Frame { open });
all.push(Mark::Delims { open });
for cols in [true, false] {
all.push(Mark::Lane { open, cols });
}
}
for cols in [true, false] {
all.push(Mark::Gap { cols });
}
all.push(Mark::MidArm);
all.extend([
Mark::Probe { index: 0 },
Mark::Probe {
index: PROBE_MAX - 1,
},
]);
let mut seen = std::collections::HashSet::new();
for m in all {
let c = m.ch();
assert!(is_display_marker(c), "{:?} escapes the marker page", m);
assert!(seen.insert(c), "{:?} shares a wire char", m);
assert_eq!(Mark::decode(c), Some(m));
}
}
}