pub mod font;
pub mod input;
pub mod layout;
pub mod locale;
pub mod output;
pub mod rules;
pub mod tree;
use std::fmt;
pub use locale::Locale;
pub use rules::Applied;
pub use tree::{Class, Node};
pub const VERSION: &str = env!("CARGO_PKG_VERSION");
#[derive(Debug, Clone)]
pub struct Options {
pub locale: Locale,
pub display: bool,
pub strict: bool,
}
impl Default for Options {
fn default() -> Self {
Options { locale: Locale::default(), display: true, strict: false }
}
}
#[derive(Debug, Clone)]
pub struct Output {
pub svg: String,
pub tree: Node,
pub speech: String,
pub text: String,
pub applied: Vec<Applied>,
pub warnings: Vec<Applied>,
}
impl Output {
pub fn to_json(&self) -> String {
let rules = |list: &[Applied]| -> Vec<serde_json::Value> { list.iter().map(|a| serde_json::json!({ "rule": a.rule, "description": a.description })).collect() };
let tree: serde_json::Value = serde_json::from_str(&self.tree.to_json()).expect("the tree serializes to valid JSON");
serde_json::json!({
"svg": self.svg,
"speech": self.speech,
"text": self.text,
"tree": tree,
"applied": rules(&self.applied),
"warnings": rules(&self.warnings),
})
.to_string()
}
}
#[derive(Debug, Clone, PartialEq)]
pub enum Error {
Syntax { column: usize, message: String },
MissingGlyph { character: char },
}
impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Error::Syntax { column, message } => write!(f, "column {column}: {message}"),
Error::MissingGlyph { character } => {
write!(f, "the {} font has no '{character}' character (U+{:04X})", font::NAME, *character as u32)
}
}
}
}
impl std::error::Error for Error {}
pub fn convert(tex: &str, options: &Options) -> Result<Output, Error> {
let mut registry = rules::Registry::default();
let tree = input::latex::read_with(tex, options.locale, &mut registry, options.strict)?;
let style = if options.display { layout::Style::display() } else { layout::Style::text() };
let math_box = {
let mut ctx = layout::Context { locale: options.locale, registry: &mut registry, strict: options.strict };
layout::compose(&tree, style, &mut ctx)?
};
let speech = output::speech::speak(&tree, options.locale);
let text = output::text::linear(&tree, options.locale);
let tree_json = tree.to_json();
registry.dedup();
let svg = output::svg::generate(&output::svg::Content {
math_box: &math_box,
source: tex,
tree_json: &tree_json,
speech: &speech,
text: &text,
locale: options.locale,
warnings: ®istry.warnings,
});
Ok(Output { svg, tree, speech, text, applied: registry.applied, warnings: registry.warnings })
}
pub fn typeset(tex: &str, options: &Options) -> Result<(layout::MathBox, Vec<Applied>), Error> {
let mut registry = rules::Registry::default();
let tree = input::latex::read_with(tex, options.locale, &mut registry, options.strict)?;
let style = if options.display { layout::Style::display() } else { layout::Style::text() };
let mut ctx = layout::Context { locale: options.locale, registry: &mut registry, strict: options.strict };
let math_box = layout::compose(&tree, style, &mut ctx)?;
registry.dedup();
Ok((math_box, registry.warnings))
}
pub fn tex_to_svg(tex: &str) -> Result<String, Error> {
convert(tex, &Options::default()).map(|r| r.svg)
}