From cb0173f8be3888a538aa78bf35c3726364fddc5c Mon Sep 17 00:00:00 2001 From: Michael Mikovsky <77305074+Astatin3@users.noreply.github.com> Date: Fri, 19 Sep 2025 10:47:08 -0600 Subject: [PATCH] Start making element system --- src/bitmap.rs | 65 ++++++++++++++++++++ src/main.rs | 129 +++++++++++++++------------------------ src/parser.rs | 166 ++++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 280 insertions(+), 80 deletions(-) create mode 100644 src/bitmap.rs create mode 100644 src/parser.rs diff --git a/src/bitmap.rs b/src/bitmap.rs new file mode 100644 index 0000000..b7f50bb --- /dev/null +++ b/src/bitmap.rs @@ -0,0 +1,65 @@ +use icy_sixel::{sixel_string, DiffusionMethod, MethodForLargest, MethodForRep, PixelFormat, Quality}; + +pub struct Bitmap { + pub data: Vec, + pub width: usize, + pub height: usize, +} + +impl Bitmap { + pub fn new(width: usize, height: usize) -> Self { + Self { + data: vec![0; width*height], + width, + height + } + } + + pub fn from_data(data: Vec, width: usize, height: usize) -> Self { + assert!(data.len() == width * height); + Self { + data, + width, + height + } + } + + pub fn overlay(&mut self, other: &Bitmap, xoffset: usize, yoffset:usize) { + for y in 0..other.height { + for x in 0..other.width { + self.data[(y+yoffset as usize)*self.width + (x+xoffset as usize)] = other.data[y*other.width + x]; + } + } + } + + /// Prints a 1 byte per pixel greyscale bitmap to Sixel format in console + pub fn print(&self) { + let mut bitmap_rgb888 = vec![0; self.width*self.height*3]; + + for y in 0..self.height { + for x in 0..self.width { + let index = y*self.width + x; + + let pixel = self.data[index]; + + bitmap_rgb888[index*3] = pixel; + bitmap_rgb888[index*3 + 1] = pixel; + bitmap_rgb888[index*3 + 2] = pixel; + } + } + + let sixel_data = sixel_string( + &bitmap_rgb888, + self.width as i32, + self.height as i32, + PixelFormat::RGB888, + DiffusionMethod::None, + MethodForLargest::Auto, + MethodForRep::Auto, + Quality::AUTO, + ).unwrap(); + + println!("{}", sixel_data); + + } +} \ No newline at end of file diff --git a/src/main.rs b/src/main.rs index 589c912..97a9c33 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,105 +1,74 @@ #[allow(non_upper_case_globals)] mod fonts; +mod parser; +mod bitmap; -use fontdue::{layout::{CoordinateSystem, Layout, LayoutSettings, TextStyle}}; -use icy_sixel::{ - DiffusionMethod, MethodForLargest, MethodForRep, PixelFormat, Quality, sixel_string, -}; +use std::{rc::Rc, time::Instant}; + +use fontdue::{layout::{CoordinateSystem, Layout, LayoutSettings}}; + +use crate::{bitmap::Bitmap, parser::{KElement}}; fn main() -> Result<(), std::fmt::Error> { - let mut rustex = RusTeX::new(); - rustex.add_text(&TextStyle::new("testi12345ng! e^23", 35.0, 0)); - let (bitmap, (width, height)) = rustex.rasterize(); - print_bitmap(&bitmap, width, height); + let start = Instant::now(); + + let mut rustex = RusTeX::new(TeXSettings { scale: 100. }); + + let element = KElement::LinearGroup(vec![ + KElement::Fraction( + Rc::new(KElement::Integer(123)), + Rc::new(KElement::Integer(12)) + ), + // KElement::Integer(12), + // KElement::Integer(12), + KElement::Decimal(12.34), + KElement::Fraction( + Rc::new(KElement::Fraction(Rc::new(KElement::Integer(123)),Rc::new(KElement::Integer(12)))), + Rc::new(KElement::Fraction(Rc::new(KElement::Integer(123)),Rc::new(KElement::Fraction(Rc::new(KElement::Integer(123)),Rc::new(KElement::Fraction(Rc::new(KElement::Integer(123)),Rc::new(KElement::Fraction(Rc::new(KElement::Integer(123)),Rc::new(KElement::Fraction(Rc::new(KElement::Integer(123)),Rc::new(KElement::Fraction(Rc::new(KElement::Integer(123)),Rc::new(KElement::Fraction(Rc::new(KElement::Integer(123)),Rc::new(KElement::Decimal(1234.5678)))))))))))))))), + ), + ]); + + // rustex.add_text(&TextStyle::new("testi12345ng! e^23", 100.0, 0)); + let bitmap = rustex.rasterize(element); + + println!("Rasterizing time: {:?}", start.elapsed()); + + bitmap.print(); + + // print_bitmap(&bitmap, width, height); + Ok(()) } struct RusTeX { - layout: Layout, + settings: TeXSettings, + layout: Layout +} + +struct TeXSettings { + scale: f32, } impl RusTeX { - pub fn new() -> Self { + pub fn new(settings: TeXSettings) -> Self { let mut layout = Layout::new(CoordinateSystem::PositiveYDown); - // By default, layout is initialized with the default layout settings. This call is redundant, but - // demonstrates setting the value with your custom settings. + layout.reset(&LayoutSettings { ..LayoutSettings::default() }); Self { + settings, layout, } } - pub fn add_text(&mut self, text_style: &TextStyle) { - self.layout.append(&fonts::FONTS, text_style); + // pub fn add_text_style(&mut self, text_style: &TextStyle) { + // // self.layout.append(&fonts::FONTS, text_style); + // } + + pub fn rasterize(&mut self, root_element: KElement) -> Bitmap { + root_element.rasterize(&mut self.layout, self.settings.scale) } - - pub fn rasterize(&mut self) -> (Vec, (usize, usize)) { - let (mut maxx, mut maxy): (usize, usize) = (0,0); - for glyph in self.layout.glyphs() { - maxx = maxx.max(glyph.x as usize + glyph.width); - maxy = maxy.max(glyph.y as usize + glyph.height); - } - - let mut bitmap: Vec = vec![0; maxx*maxy]; - - for glyph in self.layout.glyphs() { - - let font = &fonts::FONTS[glyph.font_index]; - let (metrics, char_bitmap) = font.rasterize_config(glyph.key); - - - - for y in 0..metrics.height { - for x in 0..metrics.width { - let pixel = char_bitmap[y*glyph.width + x]; - - // let index = (x+glyph.x as usize)*maxy + (y+glyph.y as usize); - let index = (y+glyph.y as usize)*maxx + (x+glyph.x as usize); - - bitmap[index] = pixel; - } - } - } - - (bitmap, (maxx, maxy)) - } -} - -/// Prints a 1 byte per pixel greyscale bitmap to Sixel format in console -fn print_bitmap(bitmap: &Vec, width: usize, height: usize) { - let mut bitmap_rgb888 = vec![0; width*height*3]; - - - for y in 0..height { - for x in 0..width { - let index = y*width + x; - - let pixel = bitmap[index]; - - bitmap_rgb888[index*3] = pixel; - bitmap_rgb888[index*3 + 1] = pixel; - bitmap_rgb888[index*3 + 2] = pixel; - } - } - - - - - let sixel_data = sixel_string( - &bitmap_rgb888, - width as i32, - height as i32, - PixelFormat::RGB888, - DiffusionMethod::None, - MethodForLargest::Auto, - MethodForRep::Auto, - Quality::AUTO, - ).unwrap(); - - println!("{}", sixel_data); - } \ No newline at end of file diff --git a/src/parser.rs b/src/parser.rs new file mode 100644 index 0000000..19c4cbc --- /dev/null +++ b/src/parser.rs @@ -0,0 +1,166 @@ +use std::rc::Rc; + +use fontdue::layout::{Layout, TextStyle}; + +use crate::{bitmap::Bitmap, fonts::FONTS}; + +pub enum KElement { + LinearGroup(Vec), + Integer(i64), + Decimal(f64), + Fraction(Rc, Rc), +} + +pub static FRACTION_SCALE: f32 = 1.; +// pub static FRACTION_PADDING: usize = 10; + +// pub enum KSymbol { +// Text { +// data: String, +// x: f32, +// y: f32, +// scale: f32 +// }, +// None, +// } + +impl KElement { + pub fn rasterize(&self, layout: &mut Layout, scale: f32) -> Bitmap { + match self { + KElement::LinearGroup(elems) => { + let (mut totalx, mut maxy) = (0,0); + let mut positions = Vec::new(); + for elem in elems { + let (x,y) = elem.get_bounds(layout, scale); + positions.push((totalx,y)); + totalx += x; + maxy = maxy.max(y); + } + + let mut bitmap = Bitmap::new(totalx, maxy); + + for i in 0..elems.len() { + let elem = &elems[i]; + let pos = positions[i]; + let new_bitmap = elem.rasterize(layout, scale); + // println!("{:?} {:?} {:?}", (bitmap.width, bitmap.height), pos, (new_bitmap.width, new_bitmap.height)); + bitmap.overlay(&new_bitmap, pos.0, (maxy-pos.1)/2); + } + + bitmap + } + KElement::Integer(i) => { + render_text_block(layout, &i.to_string(), scale) + }, + KElement::Decimal(i) => { + render_text_block(layout, &i.to_string(), scale) + }, + KElement::Fraction(a,b) => { + let (ax,ay) = a.get_bounds(layout, scale * FRACTION_SCALE); + let (bx,by) = b.get_bounds(layout, scale * FRACTION_SCALE); + let (width, height) = (ax.max(bx), ay+by); + + let mut bitmap = Bitmap::new(width, height); + + let bitmap_a = &mut a.rasterize(layout, scale * FRACTION_SCALE); + let bitmap_b = &mut b.rasterize(layout, scale * FRACTION_SCALE); + + if bitmap_a.width > bitmap_b.width { + bitmap.overlay(&bitmap_a, 0, 0); + bitmap.overlay(&bitmap_b, (bitmap_a.width-bitmap_b.width)/2, ay); + } else { + bitmap.overlay(&bitmap_a, (bitmap_b.width-bitmap_a.width)/2, 0); + bitmap.overlay(&bitmap_b, 0, ay); + } + + bitmap + + // symbols.append(); + // symbols.append(&mut b.to_text_style(x, midy, scale / 2.)); + // symbols + + } + } + } + pub fn get_bounds(&self, layout: &mut Layout, scale: f32) -> (usize, usize) { + match self { + KElement::LinearGroup(elems) => { + let (mut totalx, mut maxy) = (0,0); + for elem in elems { + let (x,y) = elem.get_bounds(layout, scale); + totalx += x; + maxy = maxy.max(y); + } + (totalx, maxy) + } + KElement::Integer(i) => { + measure_text_bounds(layout, &i.to_string(), scale) + }, + KElement::Decimal(i) => { + measure_text_bounds(layout, &i.to_string(), scale) + }, + KElement::Fraction(a,b) => { + let (ax,ay) = a.get_bounds(layout, scale * FRACTION_SCALE); + let (bx,by) = b.get_bounds(layout, scale * FRACTION_SCALE); + (ax.max(bx), ay+by) + }, + + } + } +} + +// impl KSymbol { +// // pub fn get_max_bounds(&self) -> (f32, f32) { +// // match self { +// // KSymbol::Text { x, y, ..} => (*x,*y), +// // _ => (0.,0.) +// // } +// // } +// pub fn rasterize(&self, layout: &mut Layout, bitmap: &mut Bitmap) { +// match self { +// KSymbol::Text { data, x, y, scale } => { +// let new_bitmap = render_text_block(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) { + 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) + +} + +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