pub(crate) fn draws(ch: char) -> bool {
matches!(ch as u32, 0x2500..=0x259F | 0xE0B0..=0xE0BF)
}
pub(crate) fn raster(ch: char, w: u32, h: u32) -> Vec<u8> {
let mut m = Mask::new(w, h);
let cp = ch as u32;
match cp {
0x2500..=0x254B | 0x2574..=0x257F => lines(&mut m, cp),
0x254C..=0x254F => dashes(&mut m, cp),
0x2550..=0x256C => doubles(&mut m, cp),
0x256D..=0x2570 => arc(&mut m, cp),
0x2571..=0x2573 => diagonal(&mut m, cp),
0x2580..=0x259F => block(&mut m, cp),
0xE0B0..=0xE0B3 => powerline(&mut m, cp),
0xE0B4..=0xE0B7 => half_circle(&mut m, cp),
0xE0B8..=0xE0BF => wedge(&mut m, cp),
_ => {}
}
m.a
}
struct Mask {
w: u32,
h: u32,
a: Vec<u8>,
}
struct Pen {
light: u32,
heavy: u32,
}
impl Mask {
fn new(w: u32, h: u32) -> Self {
Self {
w,
h,
a: vec![0; (w * h) as usize],
}
}
fn pen(&self) -> Pen {
let light = ((self.w as f32 / 8.0).round() as u32).max(1);
Pen {
light,
heavy: light * 3,
}
}
fn col(&self, t: u32) -> u32 {
self.w.saturating_sub(t) / 2
}
fn row(&self, t: u32) -> u32 {
self.h.saturating_sub(t) / 2
}
fn rect(&mut self, x0: u32, y0: u32, x1: u32, y1: u32) {
self.rect_alpha(x0, y0, x1, y1, 255);
}
fn rect_alpha(&mut self, x0: u32, y0: u32, x1: u32, y1: u32, alpha: u8) {
let (x1, y1) = (x1.min(self.w), y1.min(self.h));
for y in y0..y1 {
for x in x0..x1 {
let i = (y * self.w + x) as usize;
self.a[i] = self.a[i].max(alpha);
}
}
}
fn shape(&mut self, inside: impl Fn(f32, f32) -> bool) {
const N: u32 = 4;
for y in 0..self.h {
for x in 0..self.w {
let mut hits = 0;
for sy in 0..N {
for sx in 0..N {
let px = x as f32 + (sx as f32 + 0.5) / N as f32;
let py = y as f32 + (sy as f32 + 0.5) / N as f32;
if inside(px, py) {
hits += 1;
}
}
}
if hits > 0 {
let i = (y * self.w + x) as usize;
let a = (hits * 255 / (N * N)) as u8;
self.a[i] = self.a[i].max(a);
}
}
}
}
}
#[derive(Clone, Copy, PartialEq, Eq)]
enum Arm {
None,
Light,
Heavy,
}
impl Arm {
fn from(c: u8) -> Self {
match c {
b'l' | b's' => Arm::Light,
b'h' | b'd' => Arm::Heavy,
_ => Arm::None,
}
}
}
#[derive(Clone, Copy)]
struct Arms {
up: Arm,
down: Arm,
left: Arm,
right: Arm,
}
impl Arms {
fn parse(s: &[u8]) -> Self {
Self {
up: Arm::from(s[0]),
down: Arm::from(s[1]),
left: Arm::from(s[2]),
right: Arm::from(s[3]),
}
}
}
const LINES: &[u8; 76 * 4] = b"\
..ll..hhll..hh..\
..ll..hhll..hh..\
..ll..hhll..hh..\
.l.l.l.h.h.l.h.h\
.ll..lh..hl..hh.\
l..ll..hh..lh..h\
l.l.l.h.h.l.h.h.\
ll.lll.hhl.llh.lhh.lhl.hlh.hhh.h\
lll.llh.hll.lhl.hhl.hlh.lhh.hhh.\
.lll.lhl.llh.lhh.hll.hhl.hlh.hhh\
l.lll.hll.lhl.hhh.llh.hlh.lhh.hh\
llllllhllllhllhhhlll\
lhllhhllhlhlhllhlhhllhlhhlhhlhhhhhhlhhlhhhhh";
const STUBS: &[u8; 12 * 4] = b"..l.l......l.l....h.h......h.h....lhlh....hlhl..";
const DOUBLES: &[u8; 29 * 4] = b"\
..dddd...s.d.d.s.d.d.sd..ds..dd.\
s..dd..sd..ds.d.d.s.d.d.\
ss.ddd.sdd.dssd.dds.ddd.\
.sdd.dss.ddds.ddd.ssd.dd\
ssddddssdddd";
fn arms_of(cp: u32) -> Arms {
let (table, i): (&[u8], usize) = match cp {
0x2500..=0x254B => (LINES, (cp - 0x2500) as usize),
0x2574..=0x257F => (STUBS, (cp - 0x2574) as usize),
0x2550..=0x256C => (DOUBLES, (cp - 0x2550) as usize),
_ => (b"....", 0),
};
Arms::parse(&table[i * 4..i * 4 + 4])
}
fn width(pen: &Pen, arm: Arm) -> u32 {
match arm {
Arm::None => 0,
Arm::Light => pen.light,
Arm::Heavy => pen.heavy,
}
}
fn lines(m: &mut Mask, cp: u32) {
let pen = m.pen();
let arms = arms_of(cp);
if (0x2504..=0x250B).contains(&cp) {
let n = if cp < 0x2508 { 3 } else { 4 };
let vertical = cp & 2 != 0;
let heavy = cp & 1 != 0;
let t = if heavy { pen.heavy } else { pen.light };
return dashed(m, n, vertical, t);
}
let (wu, wd, wl, wr) = (
width(&pen, arms.up),
width(&pen, arms.down),
width(&pen, arms.left),
width(&pen, arms.right),
);
let wv = wu.max(wd);
let wh = wl.max(wr);
if wu > 0 {
let band = wh.max(wu);
m.rect(m.col(wu), 0, m.col(wu) + wu, m.row(band) + band);
}
if wd > 0 {
let band = wh.max(wd);
m.rect(m.col(wd), m.row(band), m.col(wd) + wd, m.h);
}
if wl > 0 {
let band = wv.max(wl);
m.rect(0, m.row(wl), m.col(band) + band, m.row(wl) + wl);
}
if wr > 0 {
let band = wv.max(wr);
m.rect(m.col(band), m.row(wr), m.w, m.row(wr) + wr);
}
}
fn dashed(m: &mut Mask, n: u32, vertical: bool, t: u32) {
let len = if vertical { m.h } else { m.w };
let gap = ((len as f32 / n as f32 * 0.3).round() as u32).max(1);
let left = gap / 2;
let span = len.saturating_sub(gap);
let dash = (span.saturating_sub((n - 1) * gap)) as f32 / n as f32;
for i in 0..n {
let at = left as f32 + i as f32 * (dash + gap as f32);
let s = at.round() as u32;
let e = ((at + dash).round() as u32).max(s + 1).min(len);
if vertical {
m.rect(m.col(t), s, m.col(t) + t, e);
} else {
m.rect(s, m.row(t), e, m.row(t) + t);
}
}
}
fn dashes(m: &mut Mask, cp: u32) {
let pen = m.pen();
let vertical = cp & 2 != 0;
let heavy = cp & 1 != 0;
dashed(m, 2, vertical, if heavy { pen.heavy } else { pen.light });
}
fn doubles(m: &mut Mask, cp: u32) {
let t = m.pen().light;
let arms = arms_of(cp);
let gap = t;
let (xa, ya) = (m.col(2 * t + gap), m.row(2 * t + gap));
let (xb, yb) = (xa + t + gap, ya + t + gap);
let (xc, yc) = (m.col(t), m.row(t));
let (w, h) = (m.w, m.h);
let single_end = |opp: Arm, near_side: Arm, far_side: Arm, near: u32, far: u32, centre: u32| {
if opp != Arm::None {
centre
} else if near_side == Arm::Heavy && far_side == Arm::Heavy {
near
} else if near_side == Arm::Heavy || far_side == Arm::Heavy {
far
} else {
centre
}
};
let rail_end = |same: Arm, other: Arm, opp: Arm, near: u32, far: u32, centre: u32| {
if same == Arm::Heavy {
near
} else if other == Arm::Heavy && opp == Arm::None {
far
} else {
centre
}
};
match arms.up {
Arm::None => {}
Arm::Light => {
let e = single_end(arms.down, arms.left, arms.right, ya, yb, yc);
m.rect(xc, 0, xc + t, e + t);
}
Arm::Heavy => {
let e = rail_end(arms.left, arms.right, arms.down, ya, yb, yc);
m.rect(xa, 0, xa + t, e + t);
let e = rail_end(arms.right, arms.left, arms.down, ya, yb, yc);
m.rect(xb, 0, xb + t, e + t);
}
}
match arms.down {
Arm::None => {}
Arm::Light => {
let e = single_end(arms.up, arms.left, arms.right, yb, ya, yc);
m.rect(xc, e, xc + t, h);
}
Arm::Heavy => {
let e = rail_end(arms.left, arms.right, arms.up, yb, ya, yc);
m.rect(xa, e, xa + t, h);
let e = rail_end(arms.right, arms.left, arms.up, yb, ya, yc);
m.rect(xb, e, xb + t, h);
}
}
match arms.left {
Arm::None => {}
Arm::Light => {
let e = single_end(arms.right, arms.up, arms.down, xa, xb, xc);
m.rect(0, yc, e + t, yc + t);
}
Arm::Heavy => {
let e = rail_end(arms.up, arms.down, arms.right, xa, xb, xc);
m.rect(0, ya, e + t, ya + t);
let e = rail_end(arms.down, arms.up, arms.right, xa, xb, xc);
m.rect(0, yb, e + t, yb + t);
}
}
match arms.right {
Arm::None => {}
Arm::Light => {
let e = single_end(arms.left, arms.up, arms.down, xb, xa, xc);
m.rect(e, yc, w, yc + t);
}
Arm::Heavy => {
let e = rail_end(arms.up, arms.down, arms.left, xb, xa, xc);
m.rect(e, ya, w, ya + t);
let e = rail_end(arms.down, arms.up, arms.left, xb, xa, xc);
m.rect(e, yb, w, yb + t);
}
}
}
fn arc(m: &mut Mask, cp: u32) {
let t = m.pen().light;
let (cx, cy) = (
m.col(t) as f32 + t as f32 / 2.0,
m.row(t) as f32 + t as f32 / 2.0,
);
let r = m.w.min(m.h) as f32 / 2.0;
let (sx, sy) = match cp {
0x256D => (1.0, 1.0), 0x256E => (-1.0, 1.0), 0x256F => (-1.0, -1.0), _ => (1.0, -1.0), };
let (col, row) = (m.col(t), m.row(t));
let ay = cy + sy * r;
let ax = cx + sx * r;
if sy > 0.0 {
m.rect(col, ay.floor() as u32, col + t, m.h);
} else {
m.rect(col, 0, col + t, ay.ceil().max(0.0) as u32);
}
if sx > 0.0 {
m.rect(ax.floor() as u32, row, m.w, row + t);
} else {
m.rect(0, row, ax.ceil().max(0.0) as u32, row + t);
}
let half = t as f32 / 2.0;
m.shape(|x, y| {
let (dx, dy) = (x - ax, y - ay);
let inward = dx * sx <= 0.0 && dy * sy <= 0.0;
inward && ((dx * dx + dy * dy).sqrt() - r).abs() <= half
});
}
fn seg_dist(px: f32, py: f32, x0: f32, y0: f32, x1: f32, y1: f32) -> f32 {
let (dx, dy) = (x1 - x0, y1 - y0);
let len2 = dx * dx + dy * dy;
let u = if len2 == 0.0 {
0.0
} else {
(((px - x0) * dx + (py - y0) * dy) / len2).clamp(0.0, 1.0)
};
let (qx, qy) = (x0 + u * dx, y0 + u * dy);
((px - qx).powi(2) + (py - qy).powi(2)).sqrt()
}
fn diagonal(m: &mut Mask, cp: u32) {
let half = m.pen().light as f32 / 2.0;
let (w, h) = (m.w as f32, m.h as f32);
if cp != 0x2572 {
m.shape(|x, y| seg_dist(x, y, w, 0.0, 0.0, h) <= half);
}
if cp != 0x2571 {
m.shape(|x, y| seg_dist(x, y, 0.0, 0.0, w, h) <= half);
}
}
fn block(m: &mut Mask, cp: u32) {
let (w, h) = (m.w, m.h);
let eighth_x = |n: u32| (w as f32 * n as f32 / 8.0).round() as u32;
let eighth_y = |n: u32| (h as f32 * n as f32 / 8.0).round() as u32;
let (hx, hy) = (eighth_x(4), eighth_y(4));
match cp {
0x2580 => m.rect(0, 0, w, hy),
0x2581..=0x2588 => m.rect(0, eighth_y(8 - (cp - 0x2580)), w, h),
0x2589..=0x258F => m.rect(0, 0, eighth_x(8 - (cp - 0x2588)), h),
0x2590 => m.rect(hx, 0, w, h),
0x2591 => m.rect_alpha(0, 0, w, h, 64),
0x2592 => m.rect_alpha(0, 0, w, h, 128),
0x2593 => m.rect_alpha(0, 0, w, h, 192),
0x2594 => m.rect(0, 0, w, eighth_y(1).max(1)),
0x2595 => m.rect(w - eighth_x(1).max(1), 0, w, h),
0x2596..=0x259F => {
const Q: [u8; 10] = [
0b0010, 0b0001, 0b1000, 0b1011, 0b1001, 0b1110, 0b1101, 0b0100, 0b0110, 0b0111, ];
let q = Q[(cp - 0x2596) as usize];
if q & 0b1000 != 0 {
m.rect(0, 0, hx, hy);
}
if q & 0b0100 != 0 {
m.rect(hx, 0, w, hy);
}
if q & 0b0010 != 0 {
m.rect(0, hy, hx, h);
}
if q & 0b0001 != 0 {
m.rect(hx, hy, w, h);
}
}
_ => {}
}
}
fn powerline(m: &mut Mask, cp: u32) {
let half = m.pen().light as f32 / 2.0;
let (w, h) = (m.w as f32, m.h as f32);
let mid = h / 2.0;
match cp {
0xE0B0 => m.shape(|x, y| x <= w * (1.0 - (y - mid).abs() / mid)),
0xE0B1 => m.shape(|x, y| {
seg_dist(x, y, 0.0, 0.0, w, mid) <= half || seg_dist(x, y, w, mid, 0.0, h) <= half
}),
0xE0B2 => m.shape(|x, y| x >= w * ((y - mid).abs() / mid)),
_ => m.shape(|x, y| {
seg_dist(x, y, w, 0.0, 0.0, mid) <= half || seg_dist(x, y, 0.0, mid, w, h) <= half
}),
}
}
fn half_circle(m: &mut Mask, cp: u32) {
let t = m.pen().light as f32;
let (w, h) = (m.w as f32, m.h as f32);
let mid = h / 2.0;
let cx = if cp <= 0xE0B5 { 0.0 } else { w };
let within = |x: f32, y: f32, rx: f32, ry: f32| {
let (dx, dy) = ((x - cx) / rx, (y - mid) / ry);
dx * dx + dy * dy <= 1.0
};
if cp == 0xE0B4 || cp == 0xE0B6 {
m.shape(|x, y| within(x, y, w, mid));
} else {
m.shape(|x, y| within(x, y, w, mid) && !within(x, y, w - t, mid - t));
}
}
fn wedge(m: &mut Mask, cp: u32) {
let half = m.pen().light as f32 / 2.0;
let (w, h) = (m.w as f32, m.h as f32);
let back = matches!(cp, 0xE0B8 | 0xE0B9 | 0xE0BE | 0xE0BF);
if cp % 2 == 1 {
if back {
m.shape(|x, y| seg_dist(x, y, 0.0, 0.0, w, h) <= half);
} else {
m.shape(|x, y| seg_dist(x, y, 0.0, h, w, 0.0) <= half);
}
return;
}
match cp {
0xE0B8 => m.shape(|x, y| y * w >= x * h),
0xE0BA => m.shape(|x, y| y * w >= (w - x) * h),
0xE0BC => m.shape(|x, y| y * w <= (w - x) * h),
_ => m.shape(|x, y| y * w <= x * h),
}
}
#[cfg(test)]
mod tests {
use super::*;
fn at(a: &[u8], w: u32, x: u32, y: u32) -> u8 {
a[(y * w + x) as usize]
}
#[test]
fn the_tables_parse() {
for cp in 0x2500..=0x254B {
let _ = arms_of(cp);
}
for b in LINES.iter().chain(STUBS.iter()) {
assert!(matches!(b, b'.' | b'l' | b'h'), "{}", *b as char);
}
for b in DOUBLES.iter() {
assert!(matches!(b, b'.' | b's' | b'd'), "{}", *b as char);
}
let a = arms_of(0x250C); assert!(
a.up == Arm::None
&& a.down == Arm::Light
&& a.left == Arm::None
&& a.right == Arm::Light
);
let a = arms_of(0x254B); assert!(
[a.up, a.down, a.left, a.right]
.iter()
.all(|&x| x == Arm::Heavy)
);
let a = arms_of(0x2542); assert!(
a.up == Arm::Heavy
&& a.down == Arm::Heavy
&& a.left == Arm::Light
&& a.right == Arm::Light
);
let a = arms_of(0x257F); assert!(a.up == Arm::Heavy && a.down == Arm::Light && a.left == Arm::None);
let a = arms_of(0x2552); assert!(a.down == Arm::Light && a.right == Arm::Heavy && a.up == Arm::None);
let a = arms_of(0x256B); assert!(
a.up == Arm::Heavy
&& a.down == Arm::Heavy
&& a.left == Arm::Light
&& a.right == Arm::Light
);
}
#[test]
fn lines_run_edge_to_edge_on_shared_pixels() {
let (w, h) = (7, 20);
let v = raster('│', w, h);
let col = (0..w).find(|&x| at(&v, w, x, 0) == 255).expect("a stroke");
for y in 0..h {
assert_eq!(at(&v, w, col, y), 255, "row {y}");
}
let hz = raster('─', w, h);
let row = (0..h).find(|&y| at(&hz, w, 0, y) == 255).expect("a stroke");
for x in 0..w {
assert_eq!(at(&hz, w, x, row), 255, "col {x}");
}
let corner = raster('┌', w, h);
assert_eq!(
at(&corner, w, col, h - 1),
255,
"the corner's stem is on │'s column"
);
assert_eq!(
at(&corner, w, w - 1, row),
255,
"the corner's arm is on ─'s row"
);
assert_eq!(at(&corner, w, col, 0), 0, "and nothing above the corner");
let hv = raster('┃', w, h);
assert_eq!(at(&hv, w, col, 0), 255);
assert!((0..w).filter(|&x| at(&hv, w, x, 0) == 255).count() > 1);
}
#[test]
fn blocks_tile() {
let (w, h) = (7, 20);
let upper = raster('▀', w, h);
let lower = raster('▄', w, h);
for y in 0..h {
assert_eq!(
at(&upper, w, 0, y) as u32 + at(&lower, w, 0, y) as u32,
255,
"row {y}: ▀ over ▄ is the cell"
);
}
let right = raster('▐', w, h);
let left = raster('▌', w, h);
for x in 0..w {
assert_eq!(at(&right, w, x, 0) as u32 + at(&left, w, x, 0) as u32, 255);
}
assert!(raster('█', w, h).iter().all(|&a| a == 255));
assert!(raster('▒', w, h).iter().all(|&a| a == 128));
let ul = raster('▘', w, h);
assert_eq!(at(&ul, w, 0, 0), 255);
assert_eq!(at(&ul, w, w - 1, h - 1), 0);
}
#[test]
fn doubles_leave_the_gap_and_close_the_corners() {
let (w, h) = (9, 20);
let v = raster('║', w, h);
let cols: Vec<u32> = (0..w).filter(|&x| at(&v, w, x, 0) == 255).collect();
assert_eq!(cols.len(), 2, "{cols:?}");
assert_eq!(cols[1] - cols[0], 2, "a light stroke apart: {cols:?}");
let c = raster('╔', w, h);
let hz = raster('═', w, h);
let rows: Vec<u32> = (0..h).filter(|&y| at(&hz, w, 0, y) == 255).collect();
assert_eq!(rows.len(), 2);
assert_eq!(at(&c, w, cols[0], rows[0]), 255, "outer corner");
assert_eq!(at(&c, w, cols[1], rows[1]), 255, "inner corner");
assert_eq!(
at(&c, w, cols[1], rows[0]),
255,
"the outer rail runs over the inner"
);
assert_eq!(at(&c, w, cols[0], h - 1), 255);
assert_eq!(at(&c, w, w - 1, rows[1]), 255);
assert_eq!(at(&c, w, cols[0], 0), 0, "nothing above the outer corner");
let x = raster('╬', w, h);
assert_eq!(at(&x, w, cols[0] + 1, rows[0] + 1), 0, "the hole");
assert_eq!(at(&x, w, cols[0], 0), 255);
assert_eq!(at(&x, w, 0, rows[1]), 255);
let t = raster('╪', w, h);
let mid = (0..w)
.find(|&x| at(&raster('│', w, h), w, x, 0) == 255)
.unwrap();
for y in 0..h {
assert_eq!(at(&t, w, mid, y), 255, "row {y}");
}
}
#[test]
fn arcs_join_their_stubs_and_the_rest_draws_something() {
let (w, h) = (8, 20);
let a = raster('╭', w, h);
let col = (0..w)
.find(|&x| at(&raster('│', w, h), w, x, 0) == 255)
.unwrap();
let row = (0..h)
.find(|&y| at(&raster('─', w, h), w, 0, y) == 255)
.unwrap();
assert_eq!(at(&a, w, col, h - 1), 255, "the stem reaches the bottom");
assert_eq!(at(&a, w, w - 1, row), 255, "the arm reaches the right edge");
assert_eq!(at(&a, w, 0, 0), 0);
for cp in (0x2500..=0x259Fu32).chain(0xE0B0..=0xE0BF) {
let ch = char::from_u32(cp).unwrap();
assert!(draws(ch));
let m = raster(ch, w, h);
assert!(m.iter().any(|&a| a > 0), "U+{cp:04X} drew nothing");
}
assert!(!draws('a') && !draws('é') && !draws('😀'));
}
#[test]
fn half_circles_and_wedges_meet_the_run_beside_them() {
let (w, h) = (8, 20);
let right = raster('\u{E0B4}', w, h);
let left = raster('\u{E0B6}', w, h);
for y in 2..h - 2 {
assert_eq!(at(&right, w, 0, y), 255, "E0B4 flat side, row {y}");
assert_eq!(at(&left, w, w - 1, y), 255, "E0B6 flat side, row {y}");
}
assert_eq!(
at(&right, w, w - 1, h / 2),
255,
"E0B4 reaches the far edge"
);
assert_eq!(at(&right, w, w - 1, 0), 0, "and rounds the corners off");
assert_eq!(at(&left, w, 0, 0), 0);
for y in 0..h {
for x in 0..w {
assert_eq!(
at(&right, w, x, y),
at(&left, w, w - 1 - x, y),
"E0B6 mirrors E0B4 at ({x}, {y})"
);
}
}
let arc = raster('\u{E0B5}', w, h);
assert_eq!(at(&arc, w, 0, h / 2), 0, "the arc is hollow");
assert_eq!(at(&arc, w, w - 1, h / 2), 255, "and is drawn at its apex");
let ll = raster('\u{E0B8}', w, h);
assert_eq!(at(&ll, w, 0, h - 1), 255, "◣ fills the lower left");
assert_eq!(at(&ll, w, w - 1, 0), 0, "and not the upper right");
let ur = raster('\u{E0BE}', w, h);
assert_eq!(at(&ur, w, w - 1, 0), 255, "◥ fills the upper right");
assert_eq!(at(&ur, w, 0, h - 1), 0);
for y in 0..h {
for x in 0..w {
let (a, b) = (at(&ll, w, x, y) as u32, at(&ur, w, x, y) as u32);
assert!(
a + b >= 250 && a + b <= 260,
"◣ and ◥ tile at ({x}, {y}): {a} + {b}"
);
}
}
let slash = raster('\u{E0BB}', w, h);
assert!(at(&slash, w, 0, h - 1) > 128, "╱ from the lower left");
assert_eq!(at(&slash, w, 0, 0), 0);
}
#[test]
fn dashes_split_their_gap_at_the_edges() {
let (w, h) = (16, 20);
let d = raster('┄', w, h);
let row = (0..h)
.find(|&y| d[(y * w) as usize..((y + 1) * w) as usize].contains(&255))
.unwrap();
let line: Vec<bool> = (0..w).map(|x| at(&d, w, x, row) == 255).collect();
assert!(
!line[0] && !line[w as usize - 1],
"gaps at both edges: {line:?}"
);
assert_eq!(
line.windows(2).filter(|p| !p[0] && p[1]).count(),
3,
"three dashes: {line:?}"
);
}
}