Skip to content

Commit 25c7883

Browse files
committed
The toplevel python files for building the python interface
0 parents  commit 25c7883

File tree

5 files changed

+283
-0
lines changed

5 files changed

+283
-0
lines changed

.cargo/config.toml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
# first run "rustup target add i686-pc-windows-msvc" to download needed files
2+
[build]
3+
target = "i686-pc-windows-msvc"
4+
5+
# switch this and python version in cargo.toml to build 64 bit python
6+
# target = "x86_64-pc-windows-msvc"
7+

.vscode/settings.json

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
{
2+
"cSpell.words": [
3+
"libmathcat",
4+
"Placemarker",
5+
"pyfunction",
6+
"pymodule",
7+
"SSML"
8+
]
9+
}

Cargo.toml

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
[package]
2+
name = "MathCatForPython"
3+
version = "0.1.0"
4+
authors = ["Neil Soiffer <soiffer@alum.mit.edu>"]
5+
edition = "2018"
6+
7+
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
8+
9+
# [dependencies]
10+
11+
[lib]
12+
name = "libmathcat_py"
13+
crate-type = ["cdylib"]
14+
15+
[dependencies.MathCAT]
16+
path = "../MathCAT/"
17+
18+
[dependencies.pyo3]
19+
version = "0.15.1"
20+
features = ["extension-module", "abi3"]
21+
22+
# 32 bit target stable-i686-pc-windows-msvc
23+
# if changing to building 32 bit python version, change .cargo/config.toml
24+
# along with changing env variable PYO3_PYTHON

mc.toml

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
[package]
2+
name = "MathCAT"
3+
version = "0.1.0"
4+
authors = ["Neil Soiffer <soiffer@alum.mit.edu>"]
5+
edition = "2018"
6+
7+
8+
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
9+
10+
[dependencies]
11+
sxd-document = "0.3.2"
12+
sxd-xpath = "0.4.2"
13+
#sxd-xpath = {path = "../sxd-xpath-0.4.2"}
14+
yaml-rust = "0.4"
15+
lazy_static = "1.4.0"
16+
strum = "0.20"
17+
strum_macros = "0.20"
18+
structopt = "*"
19+
error-chain = "*"
20+
regex = "1.5"
21+
dirs = "3.0"
22+
bitflags = "1.2.1"
23+
phf = { version = "0.8.0", features = ["macros"] }
24+
internment = "0.5.4"
25+
26+
[build-dependencies]
27+
bitflags = "1.2.1"
28+
phf = { version = "0.8.0", features = ["macros"] }
29+
30+
# slog = "2.7"
31+
# sloggers = "1.0.1"
32+
# slog-term = "2.8"
33+
# slog-async = "2.6"
34+
# slog-envlogger = "0.4.2"
35+
# slog-stdlog = "4.1.0"
36+
# log = "0.4.14"
37+
38+
# intercom = "0.3.0"
39+
# com = "0.4.0"
40+
# windows = "0.3.1"
41+
# bindings = { package = "windows_bindings", path = "bindings" }
42+
43+
# [build-dependencies]
44+
# windows = "0.3.1"
45+
46+
[[bin]]
47+
name = "mathcat"
48+
path = "src/main.rs"
49+
50+
[lib]
51+
name = "libmathcat"
52+
crate-type = ["rlib", "cdylib"]
53+
54+
[dependencies.pyo3]
55+
version = "0.13.2"
56+
features = ["extension-module", "abi3"]
57+
58+
# 32 bit target stable-i686-pc-windows-msvc
59+
# if changing to building 32 bit python version, change .cargo/config.toml
60+
# along with changing env variable PYO3_PYTHON

src/lib.rs

Lines changed: 183 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,183 @@
1+
//! When calling from python, the general ordering is:
2+
//! 1. whatever preferences the AT needs to set, it is done with calls to [`SetPreference`].
3+
//! 2. the MathML is sent over via [`SetMathML`].
4+
//! 3. AT calls to get the speech [`GetSpokenText`] and calls [`GetBraille`] to get the (Unicode) braille.
5+
//!
6+
//! Navigation can be done via calls to either:
7+
//! * [`DoNavigateKeyPress`] (takes key events as input)
8+
//! * [`DoNavigateCommand`] (takes the commands the key events internally map to)
9+
//! Both return a string to speak.
10+
//! To highlight the node on is on, 'id's are used. If they weren't already present,
11+
//! [`SetMathML`] returns a string representing MathML that contains 'id's for any node that doesn't already
12+
//! have an 'id' set. You can get the current node with
13+
//! * [`GetNavigationMathMLId`]
14+
//! * [`GetNavigationMathML`] -- returns a string representing the MathML for the selected node
15+
//! Note: a second integer is returned. This is the offset in characters for a leaf node.
16+
//! This is needed when navigating by character for multi-symbol leaf nodes such as "sin" and "1234"
17+
//!
18+
//! It is also possible to find out what preferences are currently set by calling [`GetPreference`]
19+
//!
20+
//! AT can pass key strokes to allow a user to navigate the MathML by calling [`DoNavigateKeyPress`]; the speech is returned.
21+
//! To get the MathML associated with the current navigation node, call [`GetNavigationMathML`].
22+
//!
23+
24+
// for Python interfaces --#[...] doesn't help on name mangled python function names
25+
#![allow(non_snake_case)]
26+
#![allow(clippy::needless_return)]
27+
28+
use libmathcat::interface::StringOrFloat;
29+
use pyo3::prelude::*;
30+
use pyo3::wrap_pyfunction;
31+
use pyo3::exceptions::PyOSError;
32+
33+
/// The error type returned from MathCAT is from an error package, so we can't implement From on it.
34+
/// Instead, we write a wrapper function that deals with the error conversion
35+
fn convert_error<T>(result: Result<T, libmathcat::errors::Error>) -> PyResult<T> {
36+
return match result {
37+
Ok(answer) => Ok(answer),
38+
Err(e) => {
39+
Err( PyOSError::new_err(e.to_string()) )
40+
},
41+
};
42+
}
43+
44+
#[pyfunction]
45+
/// The absolute path location of the MathCAT Rules dir.
46+
/// IMPORTANT: This should be the first call to MathCAT
47+
pub fn SetRulesDir(_py: Python, rules_dir_location: String) -> PyResult<()> {
48+
return convert_error( libmathcat::interface::SetRulesDir(rules_dir_location) );
49+
}
50+
51+
#[pyfunction]
52+
/// The MathML to be spoken, brailled, or navigated.
53+
///
54+
/// This will override any previous MathML that was set.
55+
/// Returns: the MathML that was set, annotated with 'id' values on each node (if none were present)
56+
/// The 'id' values can be used during navigation for highlighting the current node
57+
pub fn SetMathML(_py: Python, mathml_str: String) -> PyResult<String> {
58+
return convert_error( libmathcat::interface::SetMathML(mathml_str) );
59+
}
60+
#[pyfunction]
61+
/// Get the spoken text of the MathML that was set.
62+
/// The speech takes into account any AT or user preferences.
63+
pub fn GetSpokenText(_py: Python) -> PyResult<String> {
64+
return convert_error( libmathcat::interface::GetSpokenText() );
65+
}
66+
67+
#[pyfunction]
68+
/// Set an API preference. The preference name should be a known preference name.
69+
/// The value should either be a string or a number (depending upon the preference being set)
70+
///
71+
/// This function can be called multiple times to set different values.
72+
/// The values are persistent but can be overwritten by setting a preference with the same name and a different value.
73+
pub fn SetPreference(_py: Python, name: String, value: String) -> PyResult<()> {
74+
let as_float = value.parse::<f64>();
75+
let str_or_float = match as_float {
76+
Ok(f) => StringOrFloat::AsFloat(f),
77+
Err(_) => StringOrFloat::AsString(value),
78+
};
79+
return convert_error( libmathcat::interface::SetPreference(name, str_or_float) );
80+
}
81+
82+
#[pyfunction]
83+
/// Set an API preference. The preference name should be a known preference name.
84+
/// The value should either be a string or a number (depending upon the preference being set)
85+
///
86+
/// This function can be called multiple times to set different values.
87+
/// The values are persistent but can be overwritten by setting a preference with the same name and a different value.
88+
pub fn GetPreference(_py: Python, name: String) -> PyResult<String> {
89+
return match libmathcat::interface::GetPreference(name) {
90+
Some(value) => Ok(value),
91+
None => Err( PyOSError::new_err("Unknown preference name") ),
92+
}
93+
}
94+
95+
#[pyfunction]
96+
#[allow(unused_variables)]
97+
/// Get the braille associated with the MathML node with a given id (MathML set by `SetMathML`]).
98+
/// An empty string can be used to return the braille associated with the entire expression.
99+
///
100+
/// The braille returned depends upon the preference for braille output.
101+
pub fn GetBraille(_py: Python, nav_node_id: String) -> PyResult<String> {
102+
return convert_error( libmathcat::interface::GetBraille(nav_node_id) );
103+
}
104+
105+
#[pyfunction]
106+
/// Given a key code along with the modifier keys, the current node is moved accordingly (or value reported in some cases).
107+
///
108+
/// The spoken text for the new current node is returned.
109+
pub fn DoNavigateKeyPress(_py: Python, key: usize, shift_key: bool, control_key: bool, alt_key: bool, meta_key: bool) -> PyResult<String> {
110+
return convert_error( libmathcat::interface::DoNavigateKeyPress(key, shift_key, control_key, alt_key, meta_key) );
111+
}
112+
113+
#[pyfunction]
114+
/// Given a command, the current node is moved accordingly (or value reported in some cases).
115+
///
116+
/// The spoken text for the new current node is returned.
117+
///
118+
/// The list of legal commands are:
119+
/// "MovePrevious", "MoveNext", "MoveStart", "MoveEnd", "MoveLineStart", "MoveLineEnd",
120+
/// "MoveCellPrevious", "MoveCellNext", "MoveCellUp", "MoveCellDown", "MoveColumnStart", "MoveColumnEnd",
121+
/// "ZoomIn", "ZoomOut", "ZoomOutAll", "ZoomInAll",
122+
/// "MoveLastLocation",
123+
/// "ReadPrevious", "ReadNext", "ReadCurrent", "ReadCellCurrent", "ReadStart", "ReadEnd", "ReadLineStart", "ReadLineEnd",
124+
/// "DescribePrevious", "DescribeNext", "DescribeCurrent",
125+
/// "WhereAmI", "WhereAmIAll",
126+
/// "ToggleZoomLockUp", "ToggleZoomLockDown", "ToggleSpeakMode",
127+
/// "Exit",
128+
/// "MoveTo0","MoveTo1","MoveTo2","MoveTo3","MoveTo4","MoveTo5","MoveTo6","MoveTo7","MoveTo8","MoveTo9",
129+
/// "Read0","Read1","Read2","Read3","Read4","Read5","Read6","Read7","Read8","Read9",
130+
/// "Describe0","Describe1","Describe2","Describe3","Describe4","Describe5","Describe6","Describe7","Describe8","Describe9",
131+
/// "SetPlacemarker0","SetPlacemarker1","SetPlacemarker2","SetPlacemarker3","SetPlacemarker4","SetPlacemarker5","SetPlacemarker6","SetPlacemarker7","SetPlacemarker8","SetPlacemarker9",
132+
pub fn DoNavigateCommand(_py: Python, command: String) -> PyResult<String> {
133+
return convert_error( libmathcat::interface::DoNavigateCommand(command) );
134+
}
135+
136+
#[pyfunction]
137+
/// Return the MathML associated with the current (navigation) node.
138+
pub fn GetNavigationMathMLId(_py: Python) -> PyResult<(String, usize)> {
139+
return convert_error( libmathcat::interface::GetNavigationMathMLId() );
140+
}
141+
142+
#[pyfunction]
143+
/// Return the MathML associated with the current (navigation) node.
144+
pub fn GetNavigationMathML(_py: Python) -> PyResult<(String, usize)> {
145+
return convert_error( libmathcat::interface::GetNavigationMathML() );
146+
}
147+
148+
#[pymodule]
149+
fn libmathcat(_py: Python, m: &PyModule) -> PyResult<()> {
150+
m.add_function(wrap_pyfunction!(SetRulesDir, m)?)?;
151+
m.add_function(wrap_pyfunction!(SetMathML, m)?)?;
152+
m.add_function(wrap_pyfunction!(GetSpokenText, m)?)?;
153+
m.add_function(wrap_pyfunction!(SetPreference, m)?)?;
154+
m.add_function(wrap_pyfunction!(GetPreference, m)?)?;
155+
m.add_function(wrap_pyfunction!(GetBraille, m)?)?;
156+
m.add_function(wrap_pyfunction!(DoNavigateKeyPress, m)?)?;
157+
m.add_function(wrap_pyfunction!(DoNavigateCommand, m)?)?;
158+
m.add_function(wrap_pyfunction!(GetNavigationMathMLId, m)?)?;
159+
m.add_function(wrap_pyfunction!(GetNavigationMathML, m)?)?;
160+
161+
return Ok( () );
162+
}
163+
164+
#[cfg(test)]
165+
mod py_tests {
166+
use super::*;
167+
168+
#[test]
169+
fn test_setting() {
170+
// this isn't a real test
171+
pyo3::prepare_freethreaded_python();
172+
let mathml_str = "<math><mo>(</mo><mrow><mn>451</mn><mo>,</mo><mn>231</mn></mrow><mo>)</mo></math>";
173+
match convert_error( libmathcat::interface::SetMathML(mathml_str.to_string()) ) {
174+
Ok(_mathml_with_ids) => println!("MathML is set w/o error"),
175+
Err(e) => println!("Error is {}", e.to_string()),
176+
}
177+
// still alive?
178+
match convert_error( libmathcat::interface::SetMathML(mathml_str.to_string()) ) {
179+
Ok(_mathml_with_ids) => panic!("MathML is set 2nd time w/o error"),
180+
Err(e) => panic!("Error remains {}", e.to_string()),
181+
}
182+
}
183+
}

0 commit comments

Comments
 (0)