From 5712745db140009359844e0fb5f342948ff72722 Mon Sep 17 00:00:00 2001 From: Mason Smith Date: Mon, 29 Dec 2025 22:50:12 -0800 Subject: [PATCH 1/4] add mathml2text binary --- Cargo.toml | 5 ++ src/bin/mathml2text.rs | 102 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 107 insertions(+) create mode 100644 src/bin/mathml2text.rs diff --git a/Cargo.toml b/Cargo.toml index f9afc0b4..4a93889f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -38,6 +38,7 @@ log = "0.4" env_logger = "0.11.8" cfg-if = "1.0.1" fastrand = { version = "2.3.0" } +clap = { version = "*", features = ["derive"] } [target.'cfg(target_family = "wasm")'.dependencies] zip = { version = "6.0", default-features = false, features = ["deflate"] } @@ -60,6 +61,10 @@ zip = { version = "6.0", default-features = false, features = ["bzip2"] } name = "libmathcat" crate-type = ["rlib", "cdylib"] +[[bin]] +name = "mathml2text" +path = "src/bin/mathml2text.rs" + [profile.test] debug = true opt-level = 1 # adds a few seconds to the compile, but cuts testing time by ~75% (~90 secs on 5/24) diff --git a/src/bin/mathml2text.rs b/src/bin/mathml2text.rs new file mode 100644 index 00000000..6714c95c --- /dev/null +++ b/src/bin/mathml2text.rs @@ -0,0 +1,102 @@ +// *** MathCAT doesn't normally want to build a binary *** +// *** This file is here because it is useful for trying out things *** +#![allow(clippy::needless_return)] + +use libmathcat::{errors::*, interface::*}; +use log::*; +use std::path::PathBuf; +use clap::Parser; + +// Maybe also have this speak to test the TTS generation. +// There is a rust winapi crate that mirrors the WinPAI and has "Speak(...)" in it + +// env RUST_LOG=DEBUG cargo run --features "include-zip" +fn get_rules_dir() -> String { + // for testing with zipped rules dir + // let rules_path = std::env::current_exe().unwrap().parent().unwrap().join("../../../MathCATForPython/addon/globalPlugins/MathCAT/Rules"); + let rules_path = std::env::current_exe().unwrap().parent().unwrap().join("../../Rules"); + return rules_path.as_os_str().to_str().unwrap().to_string(); +} + +#[derive(Parser)] +#[command(version, about)] +struct Options { + #[arg(short, long)] + rules_dir: Option, + + input_file: Option, + + #[arg(short, long, default_value="en")] + language: String, +} + + +fn main() -> Result<()> { + env_logger::builder() + .format_timestamp(None) + .format_module_path(false) + .format_indent(Some(2)) + .format_level(false) + .init(); + + let cli = Options::parse(); + + let expr = if let Some(f) = cli.input_file { + std::fs::read_to_string(&f).chain_err(|| format!("unable to open {}", f.to_str().unwrap_or_default()))? + } else { + r#" +(1) + "#.to_string() + }; + + if let Err(e) = set_rules_dir(get_rules_dir()) { + panic!("Error: exiting -- {}", errors_to_string(&e)); + } + debug!("Languages: {}", libmathcat::interface::get_supported_languages().join(", ")); + + #[cfg(feature = "include-zip")] + info!("***********include-zip is present**********"); + info!("Version = '{}' using Rules dir {}", get_version(), get_rules_dir()); + set_preference("Language".to_string(), cli.language)?; + + set_preference("DecimalSeparator".to_string(), "Auto".to_string()).unwrap(); + set_preference("BrailleCode".to_string(), "Nemeth".to_string()).unwrap(); + set_preference("TTS".to_string(), "None".to_string()).unwrap(); + set_preference("Verbosity".to_string(), "Verbose".to_string()).unwrap(); + set_preference("NavVerbosity".to_string(), "Verbose".to_string()).unwrap(); + set_preference("NavMode".to_string(), "Enhanced".to_string()).unwrap(); + set_preference("Impairment".to_string(), "Blindness".to_string()).unwrap(); + set_preference("SpeechOverrides_CapitalLetters".to_string(), "".to_string()).unwrap(); + set_preference("MathRate".to_string(), "80".to_string()).unwrap(); + set_preference("CapitalLetters_Beep".to_string(), "true".to_string()).unwrap(); + set_preference("IntentErrorRecovery".to_string(), "Error".to_string()).unwrap(); + + set_preference("Bookmark".to_string(), "false".to_string()).unwrap(); + set_preference("SpeechStyle".to_string(), "ClearSpeak".to_string()).unwrap(); + info!("Languages: {}", libmathcat::interface::get_supported_languages().join(", ")); + info!("Speech styles: {}", libmathcat::interface::get_supported_speech_styles("ClearSpeak".to_string()).join(", ")); + info!("BrailleCodes: {}", libmathcat::interface::get_supported_braille_codes().join(", ")); + + if let Err(e) = set_mathml(expr.to_string()) { + panic!("Error: exiting -- {}", errors_to_string(&e)); + }; + + match get_spoken_text() { + Ok(speech) => println!("{speech}"), + Err(e) => panic!("{}", errors_to_string(&e)), + } + + debug!("Speech language is {}", get_preference("Language".to_string()).unwrap()); + debug!("DecimalSeparator: {:?}", get_preference("DecimalSeparator".to_string()).unwrap()); + debug!("DecimalSeparators: {:?}, BlockSeparators: {:?}", get_preference("DecimalSeparators".to_string()).unwrap(), get_preference("BlockSeparators".to_string()).unwrap()); + debug!("SpeechStyle: {:?}", get_preference("SpeechStyle".to_string()).unwrap()); + debug!("Verbosity: {:?}", get_preference("Verbosity".to_string()).unwrap()); + + match get_braille("".to_string()) { + Ok(braille) => info!("Computed braille string:\n '{braille}'"), + Err(e) => panic!("{}", errors_to_string(&e)), + } + debug!("...using BrailleCode: {:?}", get_preference("BrailleCode".to_string()).unwrap()); + + Ok(()) +} From 6c6ad546de0a78d96f580994d6760c7a9dab691f Mon Sep 17 00:00:00 2001 From: Mason Smith Date: Tue, 30 Dec 2025 11:58:50 -0800 Subject: [PATCH 2/4] mathml2text: learn --output option choose between text, braille, and speech (not yet implemented) --- src/bin/mathml2text.rs | 50 ++++++++++++++++++++++++++++++------------ 1 file changed, 36 insertions(+), 14 deletions(-) diff --git a/src/bin/mathml2text.rs b/src/bin/mathml2text.rs index 6714c95c..fb9255c4 100644 --- a/src/bin/mathml2text.rs +++ b/src/bin/mathml2text.rs @@ -5,7 +5,7 @@ use libmathcat::{errors::*, interface::*}; use log::*; use std::path::PathBuf; -use clap::Parser; +use clap::{Parser, ValueEnum}; // Maybe also have this speak to test the TTS generation. // There is a rust winapi crate that mirrors the WinPAI and has "Speak(...)" in it @@ -18,6 +18,13 @@ fn get_rules_dir() -> String { return rules_path.as_os_str().to_str().unwrap().to_string(); } +#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, ValueEnum)] +enum OutputType { + Text, + Braille, + Speech, +} + #[derive(Parser)] #[command(version, about)] struct Options { @@ -28,6 +35,9 @@ struct Options { #[arg(short, long, default_value="en")] language: String, + + #[arg(value_enum, long, default_value="text")] + output: OutputType, } @@ -77,26 +87,38 @@ fn main() -> Result<()> { info!("Speech styles: {}", libmathcat::interface::get_supported_speech_styles("ClearSpeak".to_string()).join(", ")); info!("BrailleCodes: {}", libmathcat::interface::get_supported_braille_codes().join(", ")); - if let Err(e) = set_mathml(expr.to_string()) { - panic!("Error: exiting -- {}", errors_to_string(&e)); - }; - - match get_spoken_text() { - Ok(speech) => println!("{speech}"), - Err(e) => panic!("{}", errors_to_string(&e)), - } - debug!("Speech language is {}", get_preference("Language".to_string()).unwrap()); debug!("DecimalSeparator: {:?}", get_preference("DecimalSeparator".to_string()).unwrap()); debug!("DecimalSeparators: {:?}, BlockSeparators: {:?}", get_preference("DecimalSeparators".to_string()).unwrap(), get_preference("BlockSeparators".to_string()).unwrap()); debug!("SpeechStyle: {:?}", get_preference("SpeechStyle".to_string()).unwrap()); debug!("Verbosity: {:?}", get_preference("Verbosity".to_string()).unwrap()); - match get_braille("".to_string()) { - Ok(braille) => info!("Computed braille string:\n '{braille}'"), - Err(e) => panic!("{}", errors_to_string(&e)), + match set_mathml(expr.to_string()) { + Err(e) => { + panic!("Error: exiting -- {}", errors_to_string(&e)); + }, + Ok(fmt) => { + info!("formatted input mathml into {fmt}"); + } + } + + match cli.output { + OutputType::Text => { + match get_spoken_text() { + Ok(speech) => println!("{speech}"), + Err(e) => panic!("{}", errors_to_string(&e)), + } + }, + OutputType::Braille => { + debug!("...using BrailleCode: {:?}", get_preference("BrailleCode".to_string()).unwrap()); + match get_braille("".to_string()) { + Ok(braille) => println!("{braille}"), + Err(e) => panic!("{}", errors_to_string(&e)), + } + }, + _ => { + } } - debug!("...using BrailleCode: {:?}", get_preference("BrailleCode".to_string()).unwrap()); Ok(()) } From ba8aa569b1e4b642c605e45199548451a952ce71 Mon Sep 17 00:00:00 2001 From: Mason Smith Date: Wed, 28 Jan 2026 15:16:42 -0800 Subject: [PATCH 3/4] add speech to mathml2text --output option --- Cargo.toml | 7 +++++-- src/bin/mathml2text.rs | 14 +++++++++++++- 2 files changed, 18 insertions(+), 3 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 4a93889f..ab326a87 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,6 +14,8 @@ exclude = ["src/main.rs", "docs", "PythonScripts"] # should have "Rules/", bu [features] "include-zip" = [] "enable-logs" = ["android_logger"] +"tts" = [ "natural-tts" ] + # FIX: temporary -- need to replace error-chain package which has been deprecated [lints.rust] @@ -46,8 +48,7 @@ zip = { version = "6.0", default-features = false, features = ["deflate"] } zip = { version = "6.0", default-features = false, features = ["bzip2"] } android_logger = {version = "0.15.1", optional = true} - - +natural-tts = { version = "*", optional = true } [build-dependencies] bitflags = "2.6" @@ -64,6 +65,8 @@ crate-type = ["rlib", "cdylib"] [[bin]] name = "mathml2text" path = "src/bin/mathml2text.rs" +required-features = ["tts"] + [profile.test] debug = true diff --git a/src/bin/mathml2text.rs b/src/bin/mathml2text.rs index fb9255c4..669509cd 100644 --- a/src/bin/mathml2text.rs +++ b/src/bin/mathml2text.rs @@ -116,7 +116,19 @@ fn main() -> Result<()> { Err(e) => panic!("{}", errors_to_string(&e)), } }, - _ => { + OutputType::Speech => { + // Create the NaturalTts struct using the builder pattern. + let mut natural = natural_tts::NaturalTtsBuilder::default() + .gtts_model(natural_tts::models::gtts::GttsModel::default()) + .default_model(natural_tts::Model::Gtts) + .build().expect("failed to generate natural tts gtts model"); + + + // Start producing an output using the default_model. + let _ = natural.start(get_spoken_text().unwrap(), &PathBuf::from("output.wav")); + + // Play the audio until it finishes + natural.sleep_until_end(); } } From fe7c847219f230975db644e8d1429626015824cc Mon Sep 17 00:00:00 2001 From: Mason Smith Date: Thu, 29 Jan 2026 21:53:30 -0800 Subject: [PATCH 4/4] mathml2text: make tts optional --- Cargo.toml | 1 - src/bin/mathml2text.rs | 2 ++ 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index ab326a87..ee51d856 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -65,7 +65,6 @@ crate-type = ["rlib", "cdylib"] [[bin]] name = "mathml2text" path = "src/bin/mathml2text.rs" -required-features = ["tts"] [profile.test] diff --git a/src/bin/mathml2text.rs b/src/bin/mathml2text.rs index 669509cd..fe142e42 100644 --- a/src/bin/mathml2text.rs +++ b/src/bin/mathml2text.rs @@ -22,6 +22,7 @@ fn get_rules_dir() -> String { enum OutputType { Text, Braille, + #[cfg(feature="tts")] Speech, } @@ -116,6 +117,7 @@ fn main() -> Result<()> { Err(e) => panic!("{}", errors_to_string(&e)), } }, + #[cfg(feature="tts")] OutputType::Speech => { // Create the NaturalTts struct using the builder pattern. let mut natural = natural_tts::NaturalTtsBuilder::default()