diff --git a/fonts/KaTeX_Main-Italic.ttf b/fonts/KaTeX_Main-Italic.ttf new file mode 100644 index 0000000..0e9b0f3 Binary files /dev/null and b/fonts/KaTeX_Main-Italic.ttf differ diff --git a/src/consts.rs b/src/consts.rs index 5386d66..548ca3d 100644 --- a/src/consts.rs +++ b/src/consts.rs @@ -1,9 +1,11 @@ - pub static LINE_WIDTH: f32 = 0.02; // Global scale -pub static MIN_LOCAL_SCALE: f32 = 0.1; // Global scale +// pub static MIN_LOCAL_SCALE: f32 = 0.1; // Global scale +pub static TEXT_OFFSET: f32 = 0.; // Local scale +pub static TEXT_X_PADDING: f32 = 0.0; // Local scale +pub static OPERATOR_X_PADDING: f32 = 0.1; // Local scale pub static FRACTION_SCALE: f32 = 0.8; // Local scale pub static FRACTION_PADDING: f32 = 0.2; // global scale -pub static SUPERSCRIPT_SCALE: f32 = 0.6; // local scale +pub static SUPERSCRIPT_SCALE: f32 = 0.8; // local scale pub static SUPERSCRIPT_Y_OFFSET: f32 = 0.4; // local scale \ No newline at end of file diff --git a/src/element/element_parser.rs b/src/element/element_parser.rs index 9a627b9..1c5dc2d 100644 --- a/src/element/element_parser.rs +++ b/src/element/element_parser.rs @@ -39,36 +39,37 @@ impl KElement { } => { if !super_script.is_empty() && !sub_script.is_empty() { root.push(KElement::SuperSub { - inner: Rc::new(KElement::Text(text.clone())), + inner: Rc::new(Self::parse_text(text)), upper: Some(Rc::new(Self::parse_object(super_script)?)), lower: Some(Rc::new(Self::parse_object(sub_script)?)), }); } else if !super_script.is_empty() { root.push(KElement::SuperSub { - inner: Rc::new(KElement::Text(text.clone())), + inner: Rc::new(Self::parse_text(text)), upper: Some(Rc::new(Self::parse_object(super_script)?)), lower: None }); } else if !sub_script.is_empty() { root.push(KElement::SuperSub { - inner: Rc::new(KElement::Text(text.clone())), + inner: Rc::new(Self::parse_text(text)), upper: None, lower: Some(Rc::new(Self::parse_object(super_script)?)) }); } else { - root.push(KElement::Text(text.clone())); + root.push(Self::parse_text(text)); } }, text_parser::ParsedObject::Operator { text } => { - root.push(KElement::Text(text.clone())); + root.push(KElement::from_symbol(text)?); }, text_parser::ParsedObject::Parenthesis { - inner, - parenthesis_type, - super_script, - sub_script + // inner, + // parenthesis_type, + // super_script, + // sub_script + .. } => { }, } @@ -77,5 +78,15 @@ impl KElement { return Ok(KElement::LinearGroup(root)); } + + pub fn parse_text(str: &str) -> KElement { + if let Ok(num) = str.parse::() { + KElement::Decimal(num) + } else if let Ok(num) = str.parse::() { + KElement::Integer(num) + } else { + KElement::Text(str.to_string()) + } + } } \ No newline at end of file diff --git a/src/element/functions.rs b/src/element/functions.rs index e7f5dbe..2ec5884 100644 --- a/src/element/functions.rs +++ b/src/element/functions.rs @@ -10,11 +10,11 @@ fn assert_args(n: usize, start: usize, end: usize, err: &str) -> Result<(), Stri } } -fn derive_symbol(symbol_str: &str, args: &Vec>) -> Result { - assert_args(args.len(), 0, 0, "Symbol cannot take in any args!")?; +// fn derive_symbol(symbol_str: &str, args: &Vec>) -> Result { +// assert_args(args.len(), 0, 0, "Symbol cannot take in any args!")?; - Ok(KElement::Text(symbol_str.to_string())) -} +// Ok(KElement::Text(symbol_str.to_string())) +// } impl KElement { pub fn from_function(name: &str, args: &Vec>) -> Result { @@ -27,7 +27,7 @@ impl KElement { lower: Rc::new(Self::parse_object(&args[1])?) }) } - "pm" => derive_symbol("±", args), + "pm" => Ok(KElement::PlusMinus), _ => Err(format!("Invalid function: \\{}", name)) } } diff --git a/src/element/mod.rs b/src/element/mod.rs index bddfab1..8b93d74 100644 --- a/src/element/mod.rs +++ b/src/element/mod.rs @@ -4,6 +4,7 @@ mod rasterizer; mod text_parser; mod element_parser; mod functions; +mod symbols; pub enum KElement { LinearGroup(Vec), @@ -19,4 +20,9 @@ pub enum KElement { upper: Option>, lower: Option> }, + + Plus, + Minus, + Equals, + PlusMinus, } \ No newline at end of file diff --git a/src/element/rasterizer.rs b/src/element/rasterizer.rs index 649b936..d27cd07 100644 --- a/src/element/rasterizer.rs +++ b/src/element/rasterizer.rs @@ -44,13 +44,13 @@ impl KElement { bitmap } KElement::Integer(i) => { - render_text_block(&mut globals.layout, &i.to_string(), current_scale) + Self::render_text_block(&mut globals.layout, &i.to_string(), 0, current_scale, TEXT_X_PADDING) }, KElement::Decimal(i) => { - render_text_block(&mut globals.layout, &i.to_string(), current_scale) + Self::render_text_block(&mut globals.layout, &i.to_string(), 0, current_scale, TEXT_X_PADDING) }, KElement::Text(str) => { - render_text_block(&mut globals.layout, &str, current_scale) + Self::render_text_block(&mut globals.layout, &str, 1, current_scale, TEXT_X_PADDING) }, KElement::Fraction{upper,lower} => { let padding = (FRACTION_PADDING * current_scale) as usize; @@ -110,21 +110,38 @@ impl KElement { unreachable!() } } + _ => self.rasterize_symbol(globals, current_scale), } } pub fn get_bounds(&self, globals: &mut RusTeX, current_scale: f32) -> (usize, usize, usize) { match self { KElement::LinearGroup(elems) => { - bounds_of_linear_group(elems, globals, current_scale) + let (mut totalx, mut mintop, mut maxbottom): (usize, usize, usize) = (0,0,0); + for elem in elems { + let (width, height, centery) = elem.get_bounds(globals, current_scale); + totalx += width; + let top = centery; + let bottom = height - centery; + + + mintop = mintop.max(top); + maxbottom = maxbottom.max(bottom); + } + + ( + totalx, + maxbottom + mintop, + mintop + ) } KElement::Integer(i) => { - measure_text_bounds(&mut globals.layout, &i.to_string(), current_scale) + Self::measure_text_bounds(&mut globals.layout, &i.to_string(), 0, current_scale, TEXT_X_PADDING, TEXT_OFFSET) }, KElement::Decimal(i) => { - measure_text_bounds(&mut globals.layout, &i.to_string(), current_scale) + Self::measure_text_bounds(&mut globals.layout, &i.to_string(), 0, current_scale, TEXT_X_PADDING, TEXT_OFFSET) }, KElement::Text(str) => { - measure_text_bounds(&mut globals.layout, &str, current_scale) + Self::measure_text_bounds(&mut globals.layout, &str, 1, current_scale, TEXT_X_PADDING, TEXT_OFFSET) }, KElement::Fraction{upper,lower} => { let (ax,ay, _) = upper.get_bounds(globals, current_scale * FRACTION_SCALE); @@ -153,85 +170,59 @@ impl KElement { } } + _ => Self::get_symbol_bounds(&self, globals, current_scale), } } } -fn bounds_of_linear_group(elems: &Vec, globals: &mut RusTeX, current_scale: f32) -> (usize, usize, usize) { - // let common_centery = elems[0].get_bounds(globals, current_scale); +impl KElement { + pub fn measure_text_bounds(layout: &mut Layout, text: &str, font_index: usize, scale:f32, x_padding: f32, center_offset:f32) -> (usize, usize, usize) { + layout.clear(); + layout.append(&FONTS, &TextStyle::new(text, scale, font_index)); - let (mut totalx, mut mintop, mut maxbottom): (usize, usize, usize) = (0,0,0); - for elem in elems { - let (width, height, centery) = elem.get_bounds(globals, current_scale); - totalx += width; - let top = centery; - let bottom = height - centery; + let (mut width, mut height): (usize, usize) = (0,0); + for glyph in layout.glyphs() { + width = width.max(glyph.x as usize + glyph.width); + height = height.max(glyph.y as usize + glyph.height); + } + ( + width + 2*(scale*x_padding) as usize, + height, + height/2 + ((center_offset)*scale) as usize + ) - mintop = mintop.max(top); - maxbottom = maxbottom.max(bottom); } - ( - totalx, - maxbottom + mintop, - mintop - ) + pub fn render_text_block(layout: &mut Layout, text: &str, font_index: usize, scale:f32, x_padding: f32) -> Bitmap { + layout.clear(); + + layout.append(&FONTS, &TextStyle::new(text, scale, font_index)); + + let (mut width, mut height): (usize, usize) = (0,0); + for glyph in layout.glyphs() { + width = width.max(glyph.x as usize + glyph.width); + height = height.max(glyph.y as usize + glyph.height); + } + + let mut new_bitmap = Bitmap::new(width + 2*(scale*x_padding) as usize, height); + + for glyph in layout.glyphs() { + + let font = &FONTS[glyph.font_index]; + let (_, char_bitmap) = font.rasterize_config(glyph.key); + + new_bitmap.overlay( + &Bitmap::from_data( + char_bitmap, + glyph.width, + glyph.height + ), + glyph.x as usize + (scale*x_padding) as usize, + glyph.y as usize); + } + + new_bitmap + } } - -// impl KSymbol { -// // pub fn get_max_bounds(&self) -> (f32, f32) { -// // match self { -// // KSymbol::Text { x, y, ..} => (*x,*y), -// // _ => (0.,0.) -// // } -// // } -// pub fn rasterize(&self, globals.layout: &mut Layout, bitmap: &mut Bitmap) { -// match self { -// KSymbol::Text { data, x, y, scale } => { -// let new_bitmap = render_text_block(globals.layout, data, *scale); -// bitmap.overlay(&new_bitmap, *x as usize, *y as usize); -// }, -// KSymbol::None => {}, -// } -// } -// } - -fn measure_text_bounds(layout: &mut Layout, text: &str, scale:f32) -> (usize, usize, usize) { - layout.clear(); - layout.append(&FONTS, &TextStyle::new(text, scale, 0)); - - let (mut width, mut height): (usize, usize) = (0,0); - for glyph in layout.glyphs() { - width = width.max(glyph.x as usize + glyph.width); - height = height.max(glyph.y as usize + glyph.height); - } - - (width, height, height/2) - -} - -fn render_text_block(layout: &mut Layout, text: &str, scale:f32) -> Bitmap { - layout.clear(); - - layout.append(&FONTS, &TextStyle::new(text, scale, 0)); - - let (mut width, mut height): (usize, usize) = (0,0); - for glyph in layout.glyphs() { - width = width.max(glyph.x as usize + glyph.width); - height = height.max(glyph.y as usize + glyph.height); - } - - let mut new_bitmap = Bitmap::new(width, height); - - for glyph in layout.glyphs() { - - let font = &FONTS[glyph.font_index]; - let (_, char_bitmap) = font.rasterize_config(glyph.key); - - new_bitmap.overlay(&Bitmap::from_data(char_bitmap, glyph.width, glyph.height), glyph.x as usize, glyph.y as usize); - } - - new_bitmap -} \ No newline at end of file diff --git a/src/element/symbols.rs b/src/element/symbols.rs new file mode 100644 index 0000000..2773b8f --- /dev/null +++ b/src/element/symbols.rs @@ -0,0 +1,46 @@ +use crate::{bitmap::Bitmap, consts::OPERATOR_X_PADDING, element::KElement, RusTeX}; + +pub const PLUS: &'static str = "+"; +pub const MINUS: &'static str = "-"; +pub const EQUALS: &'static str = "="; +pub const PLUS_MINUS: &'static str = "±"; + +pub const PLUS_YOFFSET: f32 = 0.15; +pub const MINUS_YOFFSET: f32 = 0.15; +pub const EQUALS_YOFFSET: f32 = 0.27; +pub const PLUSMMINUS_YOFFSET: f32 = 0.15; + +impl KElement { + pub fn from_symbol(symbol: &str) -> Result { + match symbol { + "+" => Ok(KElement::Plus), + "-" => Ok(KElement::Minus), + "=" => Ok(KElement::Equals), + "±" => Ok(KElement::PlusMinus), + _ => unreachable!("Unimplemented symbol: {}", symbol), + // _ => Ok(KElement::Text(symbol.to_string())) + + // MINUS => KElement::Minus + } + } + + pub fn rasterize_symbol(&self, globals: &mut RusTeX, current_scale: f32) -> Bitmap { + match self { + KElement::Plus => Self::render_text_block(&mut globals.layout, PLUS, 0, current_scale, OPERATOR_X_PADDING), + KElement::Minus => Self::render_text_block(&mut globals.layout, MINUS, 0, current_scale, OPERATOR_X_PADDING), + KElement::Equals => Self::render_text_block(&mut globals.layout, EQUALS, 0, current_scale, OPERATOR_X_PADDING), + KElement::PlusMinus => Self::render_text_block(&mut globals.layout, PLUS_MINUS, 0, current_scale, OPERATOR_X_PADDING), + _ => unreachable!() + } + } + + pub fn get_symbol_bounds(&self, globals: &mut RusTeX, current_scale: f32) -> (usize, usize, usize) { + match self { + KElement::Plus => Self::measure_text_bounds(&mut globals.layout, PLUS, 0, current_scale, OPERATOR_X_PADDING, PLUS_YOFFSET), + KElement::Minus => Self::measure_text_bounds(&mut globals.layout, MINUS, 0, current_scale, OPERATOR_X_PADDING, MINUS_YOFFSET), + KElement::Equals => Self::measure_text_bounds(&mut globals.layout, EQUALS, 0, current_scale, OPERATOR_X_PADDING, EQUALS_YOFFSET), + KElement::PlusMinus => Self::measure_text_bounds(&mut globals.layout, PLUS_MINUS, 0, current_scale, OPERATOR_X_PADDING, PLUSMMINUS_YOFFSET), + _ => unreachable!() + } + } +} \ No newline at end of file diff --git a/src/element/text_parser.rs b/src/element/text_parser.rs index 60fc99c..a231bba 100644 --- a/src/element/text_parser.rs +++ b/src/element/text_parser.rs @@ -1,3 +1,162 @@ +// use regex::{Captures, Regex}; +// use lazy_static::lazy_static; +// use std::collections::VecDeque; + +// lazy_static! { +// static ref FUNCTION_REGEX: Regex = Regex::new(r"\\([a-zA-Z]+)(\s*(?:\{[^}]*\}|[^_^{}\s\\])*)\s*(?:_(\{[^}]*\}|[^_^{}\s\\]))?\s*(?:\^(\{[^}]*\}|[^_^{}\s\\]))?").unwrap(); +// static ref TEXT_REGEX: Regex = Regex::new(r"^\{\}<>()\[\]]+").unwrap(); +// } + +// #[derive(Debug, Clone, PartialEq)] +// pub enum PerenthesisType { +// Round, // () +// Square, // [] +// // Curly, // {} +// // Angle, // <> +// } + +// #[derive(Debug, Clone, PartialEq)] +// pub enum ParsedObject { +// Func { +// name: String, +// content: Vec>, +// super_script: Vec, +// sub_script: Vec, +// }, +// Var { +// text: String, +// super_script: Vec, +// sub_script: Vec, +// }, +// Operator { +// text: String, +// }, +// Parenthesis { +// inner: Vec, +// parenthesis_type: PerenthesisType, +// super_script: Vec, +// sub_script: Vec, +// }, +// } + +// pub fn parse(input: &str) -> Result, String> { +// if input.is_empty() { +// return Ok(Vec::new()) +// } + +// let mut i = 0; + +// let mut objects = Vec::new(); + +// for i in 0..input.len() { +// // let mut functions = FUNCTION_REGEX.is_match_at(); +// // FUNCTION_REGEX.(&mut functions, input); +// // for i in 0..functions.len() { +// // println!("{:?}", functions.get(i)); +// // } + +// if TEXT_REGEX.is_match_at(input, i) { +// println!("TEXT!") +// } else if FUNCTION_REGEX.is_match_at(input, i) { +// println!("FUNCTION!") +// } else { +// break; +// } +// } + + + +// // loop { + +// // } + +// // for captures in FUNCTION_REGEX.captures_iter(input) { +// // objects.push(parse_function(&captures)?); +// // } + +// // println!("{:?}", objects); + +// // let capture_locations = FUNCTION_REGEX.captures_read(locs, haystack); + +// // loop { + +// // } + + +// Ok(objects) +// } + +// fn parse_function(captures: &Captures) -> Result<(usize, ParsedObject), String> { +// let name = captures.get(1).unwrap().as_str(); +// let args_str = captures.get(2).map_or("", |m| m.as_str()); +// let sub = captures.get(3).map(|m| m.as_str()); +// let super_script = captures.get(4).map(|m| m.as_str()); + +// let len = captures.iter().map(|c| c.iter().len()).sum::(); + +// // Parse arguments +// let mut args = Vec::new(); +// let args_clean = args_str.trim(); +// if !args_clean.is_empty() { +// let mut i = 0; +// let chars: Vec = args_clean.chars().collect(); +// while i < chars.len() { +// if chars[i] == '{' { +// let mut brace_content = String::new(); +// i += 1; +// while i < chars.len() && chars[i] != '}' { +// brace_content.push(chars[i]); +// i += 1; +// } +// args.push(brace_content); +// i += 1; +// } else if chars[i] != ' ' { +// args.push(chars[i].to_string()); +// i += 1; +// } else { +// i += 1; +// } +// } +// } + +// // Clean up sub/super (remove braces if present) +// let sub_clean = sub.map(|s| if s.starts_with('{') && s.ends_with('}') { +// &s[1..s.len()-1] +// } else { s }); +// let super_clean = super_script.map(|s| if s.starts_with('{') && s.ends_with('}') { +// &s[1..s.len()-1] +// } else { s }); + +// Ok( +// ( +// len, +// ParsedObject::Func { +// name: name.to_string(), +// content: parse_string_arr(&args)?, +// super_script: if let Some(super_clean) = super_clean { +// parse(super_clean)? +// } else { +// Vec::new() +// }, +// sub_script: if let Some(sub_clean) = sub_clean { +// parse(sub_clean)? +// } else { +// Vec::new() +// }, +// } +// )) +// } + +// fn parse_string_arr(args: &Vec) -> Result>, String> { +// let mut result_vec = Vec::new(); +// for arg in args { +// result_vec.push(parse(arg)?); +// } +// Ok(result_vec) +// } + + + use regex::Regex; use lazy_static::lazy_static; use std::collections::VecDeque; diff --git a/src/fonts.rs b/src/fonts.rs index 4c2a1ae..cfb41ac 100644 --- a/src/fonts.rs +++ b/src/fonts.rs @@ -2,9 +2,11 @@ use fontdue::Font; use lazy_static::lazy_static; pub static KaTeX_Main_Regular: &'static [u8] = include_bytes!("../fonts/KaTeX_Main-Regular.ttf"); +pub static KaTeX_Main_Italic: &'static [u8] = include_bytes!("../fonts/KaTeX_Main-Italic.ttf"); lazy_static! { pub static ref FONTS: Vec = vec![ Font::from_bytes(KaTeX_Main_Regular, fontdue::FontSettings::default()).unwrap(), + Font::from_bytes(KaTeX_Main_Italic, fontdue::FontSettings::default()).unwrap(), ]; } \ No newline at end of file diff --git a/src/main.rs b/src/main.rs index 435bc03..50d618f 100644 --- a/src/main.rs +++ b/src/main.rs @@ -5,6 +5,8 @@ mod element; mod bitmap; mod consts; +use std::time::Instant; + use fontdue::{layout::{CoordinateSystem, Layout, LayoutSettings}}; use crate::{bitmap::Bitmap, element::{KElement}}; @@ -16,15 +18,30 @@ fn main() -> Result<(), std::fmt::Error> { fn parse_test() -> Result<(), std::fmt::Error> { let tex_input = &std::env::args().nth(1).unwrap(); + let mut start = Instant::now(); + match KElement::parse(tex_input) { Ok(result) => { + println!("Parse time: {:?}", start.elapsed()); + + start = Instant::now(); let mut rustex = RusTeX::new(TeXSettings { scale: 100. }); - rustex.rasterize(result).print(); + + let bitmap = rustex.rasterize(result); + + println!("Raster time: {:?}", start.elapsed()); + start = Instant::now(); + + bitmap.print(); + + println!("Display time: {:?}", start.elapsed()); } Err(e) => println!("Error: {}", e), } + Ok(()) + }