use ratatui::style::{Color, Style};
use ratatui::text::{Line, Span};
const ORANGE: Color = Color::Rgb(0xc2, 0x5a, 0x27);
const GRAY: Color = Color::Rgb(0x5c, 0x63, 0x70);
const GLYPH: [(u8, u8, u8, u8, Color); 5] = [
(7, 2, 2, 12, ORANGE),
(11, 2, 2, 5, GRAY),
(9, 5, 2, 2, GRAY),
(3, 7, 2, 5, GRAY),
(5, 10, 2, 2, GRAY),
];
const INK_X: (u8, u8) = (3, 13);
const INK_Y: (u8, u8) = (2, 14);
pub const LOGO_COLS: u16 = (INK_X.1 - INK_X.0) as u16;
pub const LOGO_ROWS: u16 = (INK_Y.1 - INK_Y.0) as u16 / 2;
#[rustfmt::skip]
const WORDMARK: [(char, [&str; WORDMARK_PX_ROWS]); 8] = [
('m', ["........", "........", "########", "########", "##.##.##", "##.##.##", "##.##.##", "##.##.##"]),
('i', ["##", "##", "..", "##", "##", "##", "##", "##" ]),
('n', ["......", "......", "######", "######", "##..##", "##..##", "##..##", "##..##" ]),
('d', ["....##", "....##", "######", "######", "##..##", "##..##", "######", "######" ]),
('f', ["..####", "..##..", "######", "######", "..##..", "..##..", "..##..", "..##.." ]),
('o', ["......", "......", "######", "######", "##..##", "##..##", "######", "######" ]),
('r', [".....", ".....", "#####", "#####", "##...", "##...", "##...", "##..." ]),
('k', ["##....", "##....", "##.###", "##.###", "####..", "####..", "##.###", "##.###" ]),
];
const WORDMARK_PX_ROWS: usize = 8;
const WORDMARK_TRACKING: u16 = 1;
const MIND_LETTERS: usize = 4;
pub const WORDMARK_ROWS: u16 = WORDMARK_PX_ROWS as u16 / 2;
pub const WORDMARK_COLS: u16 = wordmark_cols();
const LOCKUP_GAP: u16 = 3;
const WORDMARK_TOP_ROW: u16 = 1;
pub const LOCKUP_COLS: u16 = LOGO_COLS + LOCKUP_GAP + WORDMARK_COLS;
pub const LOCKUP_ROWS: u16 = LOGO_ROWS;
const _: () = assert!(WORDMARK_TOP_ROW + WORDMARK_ROWS <= LOCKUP_ROWS);
const fn wordmark_cols() -> u16 {
let mut total = 0;
let mut i = 0;
while i < WORDMARK.len() {
if i > 0 {
total += WORDMARK_TRACKING;
}
total += WORDMARK[i].1[0].len() as u16;
i += 1;
}
total
}
fn pixel(x: u8, y: u8) -> Option<Color> {
GLYPH
.iter()
.find(|(gx, gy, gw, gh, _)| x >= *gx && x < gx + gw && y >= *gy && y < gy + gh)
.map(|(_, _, _, _, color)| *color)
}
pub fn logo_lines() -> Vec<Line<'static>> {
(0..LOGO_ROWS)
.map(|row| {
let top_y = INK_Y.0 + (row as u8) * 2;
let spans = (INK_X.0..INK_X.1)
.map(|x| match (pixel(x, top_y), pixel(x, top_y + 1)) {
(Some(t), Some(b)) if t == b => Span::styled("█", Style::new().fg(t)),
(Some(t), Some(b)) => Span::styled("▀", Style::new().fg(t).bg(b)),
(Some(t), None) => Span::styled("▀", Style::new().fg(t)),
(None, Some(b)) => Span::styled("▄", Style::new().fg(b)),
(None, None) => Span::raw(" "),
})
.collect::<Vec<_>>();
Line::from(spans)
})
.collect()
}
fn wordmark_columns(mind: Color) -> Vec<(Color, [bool; WORDMARK_PX_ROWS])> {
let mut cols = Vec::with_capacity(WORDMARK_COLS as usize);
for (i, (_, rows)) in WORDMARK.iter().enumerate() {
if i > 0 {
cols.push((mind, [false; WORDMARK_PX_ROWS]));
}
let color = if i < MIND_LETTERS { mind } else { ORANGE };
for x in 0..rows[0].len() {
let mut px = [false; WORDMARK_PX_ROWS];
for (y, row) in rows.iter().enumerate() {
px[y] = row.as_bytes()[x] == b'#';
}
cols.push((color, px));
}
}
cols
}
pub fn wordmark_lines(mind: Color) -> Vec<Line<'static>> {
let cols = wordmark_columns(mind);
(0..WORDMARK_ROWS)
.map(|row| {
let (top, bottom) = (row as usize * 2, row as usize * 2 + 1);
let spans = cols
.iter()
.map(|(color, px)| match (px[top], px[bottom]) {
(true, true) => Span::styled("█", Style::new().fg(*color)),
(true, false) => Span::styled("▀", Style::new().fg(*color)),
(false, true) => Span::styled("▄", Style::new().fg(*color)),
(false, false) => Span::raw(" "),
})
.collect::<Vec<_>>();
Line::from(spans)
})
.collect()
}
pub fn lockup_lines(mind: Color) -> Vec<Line<'static>> {
let word = wordmark_lines(mind);
logo_lines()
.into_iter()
.enumerate()
.map(|(row, logo)| {
let mut spans = logo.spans;
let word_row = (row as u16)
.checked_sub(WORDMARK_TOP_ROW)
.and_then(|i| word.get(i as usize));
if let Some(word_row) = word_row {
spans.push(Span::raw(" ".repeat(LOCKUP_GAP as usize)));
spans.extend(word_row.spans.iter().cloned());
}
Line::from(spans)
})
.collect()
}
#[cfg(test)]
mod tests {
use super::*;
fn parse_rects(svg: &str) -> Vec<(u8, u8, u8, u8, String)> {
fn attr(tag: &str, name: &str) -> String {
let key = format!("{name}=\"");
let start = tag.find(&key).expect("attribute") + key.len();
tag[start..].split('"').next().unwrap().to_string()
}
svg.split("<rect")
.skip(1)
.map(|tag| {
let tag = tag.split('>').next().unwrap();
(
attr(tag, "x").parse().unwrap(),
attr(tag, "y").parse().unwrap(),
attr(tag, "width").parse().unwrap(),
attr(tag, "height").parse().unwrap(),
attr(tag, "fill"),
)
})
.collect()
}
fn hex(color: Color) -> String {
match color {
Color::Rgb(r, g, b) => format!("#{r:02x}{g:02x}{b:02x}"),
other => panic!("expected Rgb, got {other:?}"),
}
}
#[test]
fn glyph_matches_asset_svg() {
let path = concat!(
env!("CARGO_MANIFEST_DIR"),
"/assets/mindfork-icon-transparent.svg"
);
let svg = std::fs::read_to_string(path).expect("the icon is in place");
let from_svg = parse_rects(&svg);
let from_code: Vec<_> = GLYPH
.iter()
.map(|(x, y, w, h, c)| (*x, *y, *w, *h, hex(*c)))
.collect();
assert_eq!(
from_svg, from_code,
"GLYPH diverged from assets/mindfork-icon-transparent.svg — \
update the table or the asset"
);
}
#[test]
fn ink_bounds_match_svg_viewbox() {
let path = concat!(
env!("CARGO_MANIFEST_DIR"),
"/assets/mindfork-icon-transparent.svg"
);
let svg = std::fs::read_to_string(path).expect("the icon is in place");
assert!(
svg.contains("viewBox=\"0 0 16 16\""),
"the grid isn't 16×16"
);
let rects = parse_rects(&svg);
let (min_x, max_x) = (
rects.iter().map(|r| r.0).min().unwrap(),
rects.iter().map(|r| r.0 + r.2).max().unwrap(),
);
let (min_y, max_y) = (
rects.iter().map(|r| r.1).min().unwrap(),
rects.iter().map(|r| r.1 + r.3).max().unwrap(),
);
assert_eq!((min_x, max_x), INK_X);
assert_eq!((min_y, max_y), INK_Y);
assert_eq!((max_y - min_y) % 2, 0);
}
#[test]
fn lines_have_expected_shape() {
let lines = logo_lines();
assert_eq!(lines.len(), LOGO_ROWS as usize);
assert_eq!(LOGO_ROWS, 6);
assert_eq!(LOGO_COLS, 10);
for line in &lines {
assert_eq!(line.spans.len(), LOGO_COLS as usize);
for span in &line.spans {
assert_eq!(span.content.chars().count(), 1);
}
}
}
#[test]
fn orange_trunk_present_in_every_row() {
for (i, line) in logo_lines().iter().enumerate() {
let has_orange = line
.spans
.iter()
.any(|s| s.style.fg == Some(ORANGE) || s.style.bg == Some(ORANGE));
assert!(has_orange, "row {i} has no stem");
}
}
#[test]
fn uses_only_wgl4_block_glyphs() {
for line in logo_lines().into_iter().chain(lockup_lines(Color::White)) {
for span in line.spans {
let ch = span.content.chars().next().unwrap();
assert!(
matches!(ch, '█' | '▀' | '▄' | ' '),
"unexpected glyph {ch:?}"
);
}
}
}
#[test]
fn wordmark_font_is_well_formed() {
let word: String = WORDMARK.iter().map(|(c, _)| *c).collect();
assert_eq!(word, "mindfork");
for (ch, rows) in WORDMARK {
let w = rows[0].len();
assert!(w > 0, "letter {ch:?} has zero width");
for row in rows {
assert_eq!(row.len(), w, "letter {ch:?} has rows of differing width");
assert!(
row.bytes().all(|b| b == b'#' || b == b'.' || b == b' '),
"letter {ch:?} has a stray character in its matrix"
);
}
assert!(
rows.iter().any(|r| r.contains('#')),
"letter {ch:?} is empty"
);
}
}
#[test]
fn wordmark_has_expected_size_and_split() {
const MIND: Color = Color::Rgb(0xe4, 0xe4, 0xe7);
let lines = wordmark_lines(MIND);
assert_eq!(lines.len(), WORDMARK_ROWS as usize);
assert_eq!(WORDMARK_ROWS, 4);
for line in &lines {
assert_eq!(line.spans.len(), WORDMARK_COLS as usize);
}
let letters: u16 = WORDMARK.iter().map(|(_, r)| r[0].len() as u16).sum();
assert_eq!(
WORDMARK_COLS,
letters + WORDMARK_TRACKING * (WORDMARK.len() as u16 - 1)
);
let mind_cols: usize = WORDMARK
.iter()
.take(MIND_LETTERS)
.map(|(_, r)| r[0].len() + WORDMARK_TRACKING as usize)
.sum();
let row = &lines[1]; assert!(
row.spans[..mind_cols]
.iter()
.all(|s| s.style.fg != Some(ORANGE)),
"\"mind\" must not be the accent color"
);
assert!(
row.spans[mind_cols..]
.iter()
.any(|s| s.style.fg == Some(ORANGE)),
"\"fork\" is drawn with the fixed brand accent"
);
}
#[test]
fn lockup_places_wordmark_right_of_glyph() {
let logo = logo_lines();
let lockup = lockup_lines(Color::White);
assert_eq!(lockup.len(), LOCKUP_ROWS as usize);
for (row, (with_word, glyph)) in lockup.iter().zip(&logo).enumerate() {
assert_eq!(
with_word.spans[..LOGO_COLS as usize],
glyph.spans[..],
"row {row}: the glyph changed"
);
let width: usize = with_word
.spans
.iter()
.map(|s| s.content.chars().count())
.sum();
let has_word =
(WORDMARK_TOP_ROW..WORDMARK_TOP_ROW + WORDMARK_ROWS).contains(&(row as u16));
assert_eq!(
has_word,
width > LOGO_COLS as usize,
"row {row}: the word is where it wasn't expected (or vice versa)"
);
}
assert_eq!(LOCKUP_COLS, LOGO_COLS + LOCKUP_GAP + WORDMARK_COLS);
}
}