use super::{cells, letter, Braille};
use crate::tree::*;
pub fn transcribe(node: &Node) -> Braille {
let mut w = Writer { out: String::new(), after_number: false, missing: Vec::new() };
w.node(node);
let text = w.out.trim_end_matches(['⠀', '\n']).to_string();
Braille { text, missing: w.missing }
}
struct Writer {
out: String,
after_number: bool,
missing: Vec<char>,
}
const AUX_OPEN: &str = "⠢";
const AUX_CLOSE: &str = "⠔";
impl Writer {
fn push(&mut self, s: &str) {
self.out.push_str(s);
self.after_number = false;
}
fn dots(&mut self, d: &str) {
self.push(&cells(d));
}
fn blank(&mut self) {
if !self.out.is_empty() && !self.out.ends_with(['⠀', '\n']) {
self.out.push('⠀');
}
self.after_number = false;
}
fn missing(&mut self, c: char) {
if !self.missing.contains(&c) {
self.missing.push(c);
}
self.push("⠿");
}
fn node(&mut self, node: &Node) {
match node {
Node::Number { value, thousands, .. } => self.number(value, thousands.is_some()),
Node::Identifier { name, variant } => self.identifier(name, *variant),
Node::Function { name, .. } => self.function_name(name),
Node::Operator { symbol, .. } | Node::FixedDelimiter { symbol, .. } => self.symbol(*symbol),
Node::LargeOperator { symbol, .. } => self.large_operator(*symbol, None, None),
Node::Row { children } => self.row(children),
Node::Fraction { num, den, rule, .. } => self.fraction(num, den, *rule),
Node::Root { radicand, index } => {
self.dots("1246");
if let Some(i) = index {
self.node(i);
}
self.dots("156");
self.argument(radicand);
}
Node::Scripts { base, sup, sub } => self.scripts(base, sup.as_deref(), sub.as_deref()),
Node::Delimited { open, close, parts, middles } => {
if let Some(o) = open {
self.symbol(*o);
}
for (i, p) in parts.iter().enumerate() {
if i > 0 {
self.symbol(middles.get(i - 1).copied().unwrap_or('|'));
}
self.node(p);
}
if let Some(c) = close {
self.symbol(*c);
}
}
Node::Table { shape, rows, .. } => self.table(*shape, rows),
Node::Accent { base, accent, below, .. } => self.mark(base, *accent, *below),
Node::Bar { base, above } => self.mark(base, '\u{0304}', !above),
Node::Brace { base, above, .. } => {
self.node(base);
self.missing(if *above { '⏞' } else { '⏟' });
}
Node::Text { parts } => {
for p in parts {
match p {
TextPart::Text(t) => self.text(t),
TextPart::Math(m) => self.node(m),
}
}
}
Node::Arrow { symbol, above, below } => {
self.symbol(*symbol);
if above.is_some() || below.is_some() {
self.missing('⟶');
}
}
Node::Stacked { base, above, below } => {
self.node(base);
let filled = |n: Option<&Node>| n.filter(|n| **n != Node::row(vec![])).cloned();
if let Some(b) = filled(below.as_deref()) {
self.dots("34 34");
self.argument(&b);
}
if let Some(a) = filled(above.as_deref()) {
self.dots("16 16");
self.argument(&a);
}
}
Node::Canceled { base, .. } => {
self.node(base);
self.missing('╱');
}
Node::Framed { base, shape: Frame::Rectangle, .. } => {
self.node(base);
self.missing('▭');
}
Node::Framed { base, .. }
| Node::Colored { base, .. }
| Node::ForcedClass { base, .. }
| Node::Style { base, .. }
| Node::Multicolumn { base, .. }
| Node::Raised { base, .. }
| Node::Scaled { base, .. }
| Node::Smashed { base, .. }
| Node::Overlapped { base, .. }
| Node::Quantity { unit: base, .. } => self.node(base),
Node::Space { .. } | Node::Phantom { .. } | Node::Rule { .. } => {}
Node::VerticalArrow { symbol, .. } => self.missing(*symbol),
Node::LongDivision { dividend, divisor } => {
self.number(dividend, false);
self.dots("256");
self.number(divisor, false);
}
Node::ColumnArithmetic { operation, terms } => {
for (i, t) in terms.iter().enumerate() {
if i > 0 {
self.symbol(*operation);
}
self.number(t, false);
}
}
}
}
fn row(&mut self, children: &[Node]) {
let mut i = 0;
while i < children.len() {
if let (Node::Scripts { base, sup, sub }, Some(next)) = (&children[i], children.get(i + 1)) {
if **base == Node::row(vec![]) {
let (letter, right_sup, right_sub) = match next {
Node::Scripts { base, sup, sub } => (base.as_ref(), sup.as_deref(), sub.as_deref()),
other => (other, None, None),
};
self.node(letter);
if let Some(s) = sub {
self.dots("6 34");
self.argument(s);
}
if let Some(s) = sup {
self.dots("4 16");
self.argument(s);
}
self.indices(right_sup, right_sub);
i += 2;
continue;
}
}
if let Node::Operator { symbol: s @ ('∠' | '△' | '□'), .. } = &children[i] {
let points: Vec<&Node> = children[i + 1..].iter().take_while(|n| matches!(n, Node::Identifier { name, .. } if name.chars().count() == 1)).collect();
if !points.is_empty() {
self.dots(match s {
'∠' => "45 25",
'△' => "6 23456",
_ => "456 13456",
});
let aux = *s == '∠' && points.len() > 1;
if aux {
self.push(AUX_OPEN);
}
for p in &points {
self.node(p);
}
if aux {
self.push(AUX_CLOSE);
}
i += 1 + points.len();
continue;
}
}
if let (Node::Number { value, thousands, .. }, Some(f @ Node::Fraction { .. })) = (&children[i], children.get(i + 1)) {
if numeric_fraction(f).is_some() && !value.contains('.') {
self.number(value, thousands.is_some());
self.node(f);
i += 2;
continue;
}
}
if let (Node::Operator { symbol: '>', .. }, Some(Node::Identifier { name, .. })) = (&children[i], children.get(i + 1)) {
if name == "o" {
self.symbol('>');
self.dots("5 135");
i += 2;
continue;
}
}
self.node(&children[i]);
i += 1;
}
}
fn number(&mut self, value: &str, thousands: bool) {
let (integer, fraction) = value.split_once('.').map_or((value, None), |(a, b)| (a, Some(b)));
let mut s = String::from("⠼");
let n = integer.chars().count();
for (i, d) in integer.chars().enumerate() {
if thousands && i > 0 && (n - i) % 3 == 0 {
s.push('⠄');
}
s.push_str(&cells(upper_digit(d)));
}
if let Some(f) = fraction {
s.push('⠂');
for d in f.chars() {
s.push_str(&cells(upper_digit(d)));
}
}
self.push(&s);
self.after_number = true;
}
fn identifier(&mut self, name: &str, variant: Variant) {
let mut chars = name.chars();
let (Some(c), None) = (chars.next(), chars.next()) else {
if matches!(variant, Variant::Italic | Variant::Upright) {
self.text(name);
} else {
for c in name.chars() {
self.letter_in_variant(c, variant);
}
}
return;
};
if variant == Variant::DoubleStruck && matches!(c, 'N' | 'Z' | 'Q' | 'R' | 'C') {
self.push(&format!("⠸{}", cells(letter(c).unwrap_or("123456"))));
return;
}
self.letter_in_variant(c, variant);
}
fn letter_in_variant(&mut self, c: char, variant: Variant) {
let variant_prefix = match variant {
Variant::Italic | Variant::Upright => "",
_ if c.is_uppercase() => "⠰",
_ => "⠠",
};
let c = if variant_prefix == "⠰" && c.is_ascii_uppercase() { c.to_ascii_lowercase() } else { c };
match self.letter_cells(c) {
Some(l) => {
let dot5 = self.after_number && variant_prefix.is_empty() && ('a'..='j').contains(&c);
let s = format!("{}{variant_prefix}{l}", if dot5 { "⠐" } else { "" });
self.push(&s);
}
None => self.missing(c),
}
}
fn letter_cells(&self, c: char) -> Option<String> {
if c.is_ascii_alphabetic() {
let l = cells(letter(c)?);
return Some(if c.is_ascii_uppercase() { format!("⠨{l}") } else { l });
}
let lower = c.to_lowercase().next()?;
let dots = match lower {
'α' => "1",
'β' => "12",
'γ' => "1245",
'δ' => "145",
'ϵ' | 'ε' => "15",
'ζ' => "1356",
'η' => "156",
'θ' | 'ϑ' => "1456",
'ι' => "24",
'κ' => "13",
'λ' => "123",
'μ' => "134",
'ν' => "1345",
'ξ' => "1346",
'ο' => "135",
'π' => "1234",
'ρ' => "1235",
'σ' | 'ς' => "234",
'τ' => "2345",
'υ' => "136",
'ϕ' | 'φ' => "124",
'χ' => "12346",
'ψ' => "13456",
'ω' => "2456",
_ => return None,
};
Some(format!("{}{}", if c != lower { "⠘" } else { "⠈" }, cells(dots)))
}
fn function_name(&mut self, name: &str) {
let written = match name {
"sin" => "sen",
"tan" => "tg",
"cot" => "cotg",
"csc" => "cossec",
"arcsin" => "arc'sen",
"arctan" => "arc'tg",
"arccot" => "arc'cotg",
"arccsc" => "arc'cossec",
"sinh" => "sh",
"cosh" => "ch",
"tanh" => "th",
"coth" => "cth",
other => other,
};
let mut s = String::new();
for c in written.chars() {
match c {
'\'' => s.push('⠄'),
c => s.push_str(&letter(c).map(cells).unwrap_or_default()),
}
}
s.push('⠄');
self.push(&s);
}
fn text(&mut self, t: &str) {
let mut digits = String::new();
for c in t.chars().filter(|c| !matches!(c, '\u{E000}'..='\u{E003}')) {
if c.is_ascii_digit() {
digits.push(c);
continue;
}
if !digits.is_empty() {
self.number(&digits, false);
digits.clear();
}
if c.is_whitespace() {
if !t.trim().is_empty() {
self.blank();
}
} else if let Some(l) = self.letter_cells(c).or_else(|| portuguese_letter(c)) {
self.push(&l);
} else {
self.symbol(c);
}
}
if !digits.is_empty() {
self.number(&digits, false);
}
}
fn symbol(&mut self, c: char) {
if self.letter_cells(c).is_some() && !c.is_ascii() {
let l = self.letter_cells(c).unwrap_or_default();
self.push(&l);
return;
}
match sign(c) {
Some(d) => {
if matches!(c, '∴' | '∵') {
self.blank();
}
self.dots(d);
if matches!(c, '|' | ',' | '∴' | '∵') {
self.blank();
}
}
None => self.missing(c),
}
}
fn argument(&mut self, n: &Node) {
if simple(n) {
self.node(n);
} else {
self.push(AUX_OPEN);
self.node(n);
self.push(AUX_CLOSE);
}
}
fn fraction(&mut self, num: &Node, den: &Node, rule: bool) {
if !rule {
self.node(num);
self.dots("25");
self.node(den);
return;
}
if let Some((n, d)) = numeric_fraction(&Node::Fraction { num: Box::new(num.clone()), den: Box::new(den.clone()), style: FractionStyle::Auto, rule }) {
let lower: String = n.chars().map(|c| cells(lower_digit(c))).collect();
let upper: String = d.chars().map(|c| cells(upper_digit(c))).collect();
self.push(&format!("⠼{lower}{upper}"));
self.after_number = true;
return;
}
self.argument(num);
self.dots("256");
self.argument(den);
}
fn scripts(&mut self, base: &Node, sup: Option<&Node>, sub: Option<&Node>) {
match base {
Node::LargeOperator { symbol, .. } => return self.large_operator(*symbol, sub, sup),
Node::Function { name, .. } if name == "lim" => {
self.function_name("lim");
if let Some(s) = sub {
self.node(s);
}
self.dots("156");
return;
}
Node::Function { name, limits: true } if name != "lim" => {
self.function_name(name);
if let Some(s) = sub {
self.dots("34 34");
self.argument(s);
}
if let Some(s) = sup {
self.dots("16 16");
self.argument(s);
}
return;
}
Node::Function { name, .. } if name == "log" && sub.is_some() && sup.is_none() => {
self.function_name("log");
if let Some(s) = sub {
self.node(s);
}
self.dots("156");
return;
}
_ => {}
}
self.node(base);
self.indices(sup, sub);
}
fn indices(&mut self, sup: Option<&Node>, sub: Option<&Node>) {
let (primes, sup) = split_primes(sup);
for _ in 0..primes {
self.dots("1256");
}
if let Some(s) = sub {
self.dots("34");
self.argument(s);
}
if let Some(s) = sup {
if matches!(s, Node::Operator { symbol: '°' | '∘', .. }) {
self.dots("356");
} else {
self.dots("16");
self.argument(&s);
}
}
}
fn large_operator(&mut self, c: char, lower: Option<&Node>, upper: Option<&Node>) {
let sign = match c {
'∑' => "45 234",
'∏' => "45 1234",
'∫' => "12346",
'∬' => "12346 12346",
'∭' => "12346 12346 12346",
'∮' => "12346 356",
'⋃' => "123456 345",
'⋂' => "123456 156",
'⋀' => "456 26",
'⋁' => "456 24",
other => {
self.missing(other);
return;
}
};
self.dots(sign);
if let Some(l) = lower {
self.node(l);
}
if let Some(u) = upper {
self.dots("25");
self.node(u);
}
self.dots("156");
}
fn mark(&mut self, base: &Node, accent: char, below: bool) {
let dots = match (accent, below) {
('\u{0304}', false) => "4 14",
('\u{0304}', true) => "6 36",
('\u{20D7}' | '→', false) => "25 2",
('←', false) => "5 25",
('\u{0303}', false) => "5 26",
('⏜', false) => "4 25",
('\u{0307}', false) => "4",
('\u{0308}', false) => "4 4",
(other, _) => {
self.node(base);
self.missing(other);
return;
}
};
self.dots(dots);
if matches!(accent, '\u{0307}' | '\u{0308}') && matches!(base, Node::Identifier { name, .. } if name.chars().all(|c| c.is_ascii_lowercase())) {
self.dots("5");
}
self.argument(base);
}
fn table(&mut self, shape: TableKind, rows: &[Vec<Node>]) {
let (open, close) = match shape {
TableKind::PMatrix => ("126", "345"),
TableKind::BMatrix => ("12356", "23456"),
TableKind::BraceMatrix => ("5 123", "456 2"),
TableKind::VMatrix => ("456", "456"),
TableKind::VVMatrix => ("456 123", "456 123"),
TableKind::Cases => ("5 123", ""),
TableKind::RCases => ("", "456 2"),
_ => ("", ""),
};
if !self.out.is_empty() && !self.out.ends_with('\n') {
self.out.push('\n');
}
for row in rows {
if !open.is_empty() {
self.dots(open);
}
for (j, cell) in row.iter().enumerate() {
if j > 0 {
self.blank();
}
self.node(cell);
}
if !close.is_empty() {
self.dots(close);
}
self.out.push('\n');
self.after_number = false;
}
}
}
fn simple(n: &Node) -> bool {
match n {
Node::Number { .. } | Node::Identifier { .. } | Node::Operator { .. } | Node::Root { .. } | Node::Function { .. } => true,
Node::Delimited { open: Some(_), close: Some(_), .. } => true,
Node::Fraction { .. } => numeric_fraction(n).is_some(),
Node::Scripts { base, sup, sub } => simple(base) && sup.as_deref().is_none_or(simple) && sub.as_deref().is_none_or(simple),
Node::Accent { base, .. } | Node::Bar { base, .. } => simple(base),
Node::Colored { base, .. } | Node::ForcedClass { base, .. } | Node::Style { base, .. } => simple(base),
Node::Row { children } => match children.as_slice() {
[one] => simple(one),
[Node::Operator { symbol: '−' | '+', .. }, one] => simple(one),
_ => false,
},
_ => false,
}
}
fn numeric_fraction(n: &Node) -> Option<(String, String)> {
match n {
Node::Fraction { num, den, rule: true, .. } => match (num.as_ref(), den.as_ref()) {
(Node::Number { value: a, .. }, Node::Number { value: b, .. }) if !a.contains('.') && !b.contains('.') => Some((a.clone(), b.clone())),
_ => None,
},
_ => None,
}
}
fn upper_digit(d: char) -> &'static str {
match d {
'1' => "1",
'2' => "12",
'3' => "14",
'4' => "145",
'5' => "15",
'6' => "124",
'7' => "1245",
'8' => "125",
'9' => "24",
_ => "245",
}
}
fn lower_digit(d: char) -> &'static str {
match d {
'1' => "2",
'2' => "23",
'3' => "25",
'4' => "256",
'5' => "26",
'6' => "235",
'7' => "2356",
'8' => "236",
'9' => "35",
_ => "356",
}
}
fn portuguese_letter(c: char) -> Option<String> {
let lower = c.to_lowercase().next()?;
let dots = match lower {
'á' => "12356",
'à' => "1246",
'â' => "16",
'ã' => "345",
'é' => "123456",
'ê' => "126",
'í' => "34",
'ó' => "346",
'ô' => "1456",
'õ' => "246",
'ú' => "23456",
'ü' => "1256",
'ç' => "12346",
_ => return None,
};
Some(format!("{}{}", if c != lower { "⠨" } else { "" }, cells(dots)))
}
fn sign(c: char) -> Option<&'static str> {
Some(match c {
'+' => "235",
'−' | '-' => "36",
'±' => "235 25 36",
'×' => "236",
'·' | '⋅' => "3",
'÷' | '/' => "256",
'!' => "45 3",
'%' => "456 356",
'‰' => "456 356 356",
'=' => "2356",
'≈' => "4 2356",
'≡' => "2356 2356",
'<' => "246",
'>' => "135",
'≤' => "246 2356",
'≥' => "135 2356",
'≪' => "246 246",
'≫' => "135 135",
'≠' => "45 2356",
'≮' => "45 246",
'≯' => "45 135",
'≰' => "45 246 2356",
'≱' => "45 135 2356",
'≢' => "45 2356 2356",
'≉' => "45 4 2356",
'∉' => "45 126 2",
'⊄' => "45 126 3",
'⊈' => "45 126 23",
'∈' => "126 2",
'∋' => "5 345",
'⊂' => "126 3",
'⊆' => "126 23",
'⊃' => "6 345",
'⊇' => "56 345",
'∪' => "456 345",
'∩' => "456 156",
'∅' => "456 245",
'∖' | '\\' => "5 3",
'°' => "356",
'≁' => "45 5 26 3",
'⇏' => "45 25 135",
'⇍' => "45 246 25",
'⇎' => "45 246 25 135",
'→' | '⟶' => "25 2",
'⇒' | '⟹' => "25 135",
'⇐' | '⟸' => "246 25",
'⇔' | '⟺' => "246 25 135",
'∞' => "3456 1256",
'…' | '⋯' => "3 3 3",
'∘' => "6 23",
'∴' => "6 16",
'∵' => "4 34",
'ℵ' => "6 1256",
'∼' => "5 26 3",
'∇' | '𝛁' => "4 12456",
'∀' => "46 3",
'∃' => "46 26",
'¬' => "6 3",
'∧' => "56 2",
'∨' => "56 3",
'∂' => "456 145",
'′' | '\'' => "1256",
'″' => "1256 1256",
'(' => "126",
')' => "345",
'[' => "12356",
']' => "23456",
'{' => "5 123",
'}' => "456 2",
'⟨' => "5 13",
'⟩' => "46 2",
'|' => "456",
'‖' => "456 123",
',' => "2",
';' => "23",
':' => "25",
'.' => "3",
_ => return None,
})
}
fn split_primes(sup: Option<&Node>) -> (usize, Option<Node>) {
let count = |n: &Node| match n {
Node::Operator { symbol: '′', .. } => 1,
Node::Operator { symbol: '″', .. } => 2,
Node::Operator { symbol: '‴', .. } => 3,
_ => 0,
};
match sup {
Some(n) if count(n) > 0 => (count(n), None),
Some(Node::Row { children }) if children.first().is_some_and(|c| count(c) > 0) => {
let k = children.iter().take_while(|c| count(c) > 0).count();
let primes = children[..k].iter().map(count).sum();
let rest = &children[k..];
(primes, if rest.is_empty() { None } else { Some(Node::row(rest.to_vec()).simplify()) })
}
other => (0, other.cloned()),
}
}