mirror of
https://github.com/Astatin3/RusTeX.git
synced 2026-06-08 16:18:09 -06:00
Add exponentials
This commit is contained in:
+231
@@ -63,3 +63,234 @@ impl Bitmap {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
impl Bitmap {
|
||||
/// Sets a pixel value with bounds checking
|
||||
fn set_pixel(&mut self, x: usize, y: usize, value: u8) {
|
||||
if x < self.width && y < self.height {
|
||||
self.data[y * self.width + x] = value;
|
||||
}
|
||||
}
|
||||
|
||||
/// Gets a pixel value with bounds checking
|
||||
fn get_pixel(&self, x: usize, y: usize) -> u8 {
|
||||
if x < self.width && y < self.height {
|
||||
self.data[y * self.width + x]
|
||||
} else {
|
||||
0
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// Draws an antialiased line with arbitrary thickness
|
||||
pub fn draw_line(&mut self, x0: usize, y0: usize, x1: usize, y1: usize, thickness: f32, color: u8) {
|
||||
let x0 = x0 as f32;
|
||||
let y0 = y0 as f32;
|
||||
let x1 = x1 as f32;
|
||||
let y1 = y1 as f32;
|
||||
|
||||
let dx = x1 - x0;
|
||||
let dy = y1 - y0;
|
||||
let length = (dx * dx + dy * dy).sqrt();
|
||||
|
||||
if length < 0.001 {
|
||||
// Handle degenerate case of zero-length line
|
||||
// self.draw_thick_point(x0 as usize, y0 as usize, thickness, color);
|
||||
return;
|
||||
}
|
||||
|
||||
// Unit vector perpendicular to the line
|
||||
let perp_x = -dy / length;
|
||||
let perp_y = dx / length;
|
||||
|
||||
// Half thickness for calculations
|
||||
let half_thickness = thickness * 0.5;
|
||||
|
||||
// Calculate bounding box with some padding for antialiasing
|
||||
let padding = (thickness * 0.5 + 1.0).ceil() as i32;
|
||||
let min_x = ((x0.min(x1) - padding as f32).floor() as i32).max(0) as usize;
|
||||
let max_x = ((x0.max(x1) + padding as f32).ceil() as i32).min(self.width as i32 - 1) as usize;
|
||||
let min_y = ((y0.min(y1) - padding as f32).floor() as i32).max(0) as usize;
|
||||
let max_y = ((y0.max(y1) + padding as f32).ceil() as i32).min(self.height as i32 - 1) as usize;
|
||||
|
||||
// For each pixel in the bounding box, calculate distance to line
|
||||
for y in min_y..=max_y {
|
||||
for x in min_x..=max_x {
|
||||
let px = x as f32;
|
||||
let py = y as f32;
|
||||
|
||||
// Calculate distance from point to line segment
|
||||
let distance = self.point_to_line_segment_distance(px, py, x0, y0, x1, y1);
|
||||
|
||||
// Calculate alpha based on distance and thickness
|
||||
let alpha = self.calculate_alpha(distance, half_thickness);
|
||||
|
||||
if alpha > 0.0 {
|
||||
self.blend_pixel(x, y, color, alpha);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Blends a pixel with the existing value using alpha blending
|
||||
fn blend_pixel(&mut self, x: usize, y: usize, color: u8, alpha: f32) {
|
||||
if x < self.width && y < self.height {
|
||||
let existing = self.get_pixel(x, y) as f32;
|
||||
let new_value = (existing * (1.0 - alpha) + color as f32 * alpha).round() as u8;
|
||||
self.set_pixel(x, y, new_value);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// Calculates the shortest distance from a point to a line segment
|
||||
fn point_to_line_segment_distance(&self, px: f32, py: f32, x0: f32, y0: f32, x1: f32, y1: f32) -> f32 {
|
||||
let dx = x1 - x0;
|
||||
let dy = y1 - y0;
|
||||
let length_sq = dx * dx + dy * dy;
|
||||
|
||||
if length_sq < 0.001 {
|
||||
// Line segment is actually a point
|
||||
let dpx = px - x0;
|
||||
let dpy = py - y0;
|
||||
return (dpx * dpx + dpy * dpy).sqrt();
|
||||
}
|
||||
|
||||
// Calculate parameter t for the closest point on the line segment
|
||||
let t = ((px - x0) * dx + (py - y0) * dy) / length_sq;
|
||||
let t = t.max(0.0).min(1.0); // Clamp to [0, 1] to stay on segment
|
||||
|
||||
// Find the closest point on the line segment
|
||||
let closest_x = x0 + t * dx;
|
||||
let closest_y = y0 + t * dy;
|
||||
|
||||
// Return distance to closest point
|
||||
let dpx = px - closest_x;
|
||||
let dpy = py - closest_y;
|
||||
(dpx * dpx + dpy * dpy).sqrt()
|
||||
}
|
||||
|
||||
/// Calculates alpha value based on distance from line edge
|
||||
fn calculate_alpha(&self, distance: f32, half_thickness: f32) -> f32 {
|
||||
if distance <= half_thickness - 0.5 {
|
||||
// Inside the line core - full opacity
|
||||
1.0
|
||||
} else if distance <= half_thickness + 0.5 {
|
||||
// In the antialiasing zone - linear falloff
|
||||
half_thickness + 0.5 - distance
|
||||
} else {
|
||||
// Outside the line - transparent
|
||||
0.0
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// // Draw a thick line by creating a capsule shape
|
||||
// fn draw_line(&mut self, x1: usize, y1: usize, x2: usize, y2: usize, width: usize, color: u8) {
|
||||
// let half_width = width as f32 / 2.0;
|
||||
|
||||
// // Calculate line vector and length
|
||||
// let dx = (x2 - x1) as f32;
|
||||
// let dy = (y2 - y1) as f32;
|
||||
// let length = (dx * dx + dy * dy).sqrt();
|
||||
|
||||
// // if length < 0.001 {
|
||||
// // // Degenerate case: just draw a circle
|
||||
// // self.draw_circle(x1, y1, half_width as i32, color);
|
||||
// // return;
|
||||
// // }
|
||||
|
||||
// // Normalize the line direction
|
||||
// let nx = dx / length;
|
||||
// let ny = dy / length;
|
||||
|
||||
// // Calculate perpendicular vector for line thickness
|
||||
// let px = -ny * half_width;
|
||||
// let py = nx * half_width;
|
||||
|
||||
// // Get the four corners of the rectangle
|
||||
// let corner1_x = (x1 as f32 + px) as usize;
|
||||
// let corner1_y = (y1 as f32 + py) as usize;
|
||||
// let corner2_x = (x1 as f32 - px) as usize;
|
||||
// let corner2_y = (y1 as f32 - py) as usize;
|
||||
// let corner3_x = (x2 as f32 - px) as usize;
|
||||
// let corner3_y = (y2 as f32 - py) as usize;
|
||||
// let corner4_x = (x2 as f32 + px) as usize;
|
||||
// let corner4_y = (y2 as f32 + py) as usize;
|
||||
|
||||
// // Draw the main rectangle body
|
||||
// self.fill_quadrilateral(
|
||||
// corner1_x, corner1_y, corner2_x, corner2_y, corner3_x, corner3_y, corner4_x, corner4_y,
|
||||
// color,
|
||||
// );
|
||||
|
||||
// // // Draw rounded ends
|
||||
// // let radius = (half_width) as i32;
|
||||
// // self.draw_circle(x1, y1, radius, color);
|
||||
// // self.draw_circle(x2, y2, radius, color);
|
||||
// }
|
||||
|
||||
// fn fill_quadrilateral(
|
||||
// &mut self,
|
||||
// x1: usize,
|
||||
// y1: usize,
|
||||
// x2: usize,
|
||||
// y2: usize,
|
||||
// x3: usize,
|
||||
// y3: usize,
|
||||
// x4: usize,
|
||||
// y4: usize,
|
||||
// color: u8,
|
||||
// ) {
|
||||
// // Find bounding box
|
||||
// let binding_x = [x1, x2, x3, x4];
|
||||
// let binding_y = [y1, y2, y3, y4];
|
||||
// let min_x = binding_x.iter().min().unwrap();
|
||||
// let max_x = binding_x.iter().max().unwrap();
|
||||
// let min_y = binding_y.iter().min().unwrap();
|
||||
// let max_y = binding_y.iter().max().unwrap();
|
||||
|
||||
// // For each point in bounding box, test if it's inside the quadrilateral
|
||||
// for y in *min_y..=*max_y {
|
||||
// for x in *min_x..=*max_x {
|
||||
// if self.point_in_quad(x, y, x1, y1, x2, y2, x3, y3, x4, y4) {
|
||||
// self.set_pixel(x, y, color);
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
// // Test if a point is inside a quadrilateral using cross products
|
||||
// fn point_in_quad(
|
||||
// &self,
|
||||
// px: usize,
|
||||
// py: usize,
|
||||
// x1: usize,
|
||||
// y1: usize,
|
||||
// x2: usize,
|
||||
// y2: usize,
|
||||
// x3: usize,
|
||||
// y3: usize,
|
||||
// x4: usize,
|
||||
// y4: usize,
|
||||
// ) -> bool {
|
||||
// // Test against each edge of the quadrilateral
|
||||
// let sign1 = self.cross_product(px - x1, py - y1, x2 - x1, y2 - y1);
|
||||
// let sign2 = self.cross_product(px - x2, py - y2, x3 - x2, y3 - y2);
|
||||
// let sign3 = self.cross_product(px - x3, py - y3, x4 - x3, y4 - y3);
|
||||
// let sign4 = self.cross_product(px - x4, py - y4, x1 - x4, y1 - y4);
|
||||
|
||||
// // Point is inside if all cross products have the same sign
|
||||
// (sign1 >= 0 && sign2 >= 0 && sign3 >= 0 && sign4 >= 0)
|
||||
// || (sign1 <= 0 && sign2 <= 0 && sign3 <= 0 && sign4 <= 0)
|
||||
// }
|
||||
|
||||
// // Calculate 2D cross product
|
||||
// fn cross_product(&self, ax: usize, ay: usize, bx: usize, by: usize) -> usize {
|
||||
// ax * by - ay * bx
|
||||
// }
|
||||
|
||||
// fn set_pixel(&mut self, x: usize, y: usize, color: u8) {
|
||||
// self.data[y*self.width + x] = color;
|
||||
// }
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
|
||||
pub static LINE_WIDTH: f32 = 0.02; // Global scale
|
||||
pub static MIN_LOCAL_SCALE: f32 = 0.1; // Global scale
|
||||
|
||||
pub static FRACTION_SCALE: f32 = 0.95; // Local scale
|
||||
pub static FRACTION_PADDING: f32 = 0.2; // global scale
|
||||
|
||||
pub static SUPERSCRIPT_SCALE: f32 = 0.6; // local scale
|
||||
pub static SUPERSCRIPT_Y_OFFSET: f32 = 0.4; // local scale
|
||||
+38
-5
@@ -3,6 +3,7 @@
|
||||
mod fonts;
|
||||
mod parser;
|
||||
mod bitmap;
|
||||
mod consts;
|
||||
|
||||
use std::{rc::Rc, time::Instant};
|
||||
|
||||
@@ -17,15 +18,46 @@ fn main() -> Result<(), std::fmt::Error> {
|
||||
|
||||
let element = KElement::LinearGroup(vec![
|
||||
KElement::Fraction(
|
||||
Rc::new(KElement::LinearGroup(vec![
|
||||
KElement::Integer(123),
|
||||
KElement::Text("*".to_string()),
|
||||
KElement::Superscript(
|
||||
Rc::new(KElement::Integer(123)),
|
||||
Rc::new(KElement::Integer(2))
|
||||
)
|
||||
])),
|
||||
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)))))))))))))))),
|
||||
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))
|
||||
))
|
||||
))
|
||||
))
|
||||
))
|
||||
))
|
||||
))
|
||||
)),
|
||||
),
|
||||
]);
|
||||
|
||||
@@ -42,8 +74,8 @@ fn main() -> Result<(), std::fmt::Error> {
|
||||
}
|
||||
|
||||
struct RusTeX {
|
||||
settings: TeXSettings,
|
||||
layout: Layout
|
||||
pub settings: TeXSettings,
|
||||
pub layout: Layout
|
||||
}
|
||||
|
||||
struct TeXSettings {
|
||||
@@ -69,6 +101,7 @@ impl RusTeX {
|
||||
// }
|
||||
|
||||
pub fn rasterize(&mut self, root_element: KElement) -> Bitmap {
|
||||
root_element.rasterize(&mut self.layout, self.settings.scale)
|
||||
let scale = self.settings.scale;
|
||||
root_element.rasterize(self, scale)
|
||||
}
|
||||
}
|
||||
+67
-37
@@ -2,36 +2,26 @@ use std::rc::Rc;
|
||||
|
||||
use fontdue::layout::{Layout, TextStyle};
|
||||
|
||||
use crate::{bitmap::Bitmap, fonts::FONTS};
|
||||
use crate::{bitmap::Bitmap, fonts::FONTS, RusTeX, consts::*};
|
||||
|
||||
|
||||
pub enum KElement {
|
||||
LinearGroup(Vec<KElement>),
|
||||
Integer(i64),
|
||||
Decimal(f64),
|
||||
Text(String),
|
||||
Fraction(Rc<KElement>, Rc<KElement>),
|
||||
Superscript(Rc<KElement>, Rc<KElement>),
|
||||
}
|
||||
|
||||
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 {
|
||||
pub fn rasterize(&self, globals: &mut RusTeX, current_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);
|
||||
let (x,y) = elem.get_bounds(globals, current_scale);
|
||||
positions.push((totalx,y));
|
||||
totalx += x;
|
||||
maxy = maxy.max(y);
|
||||
@@ -42,7 +32,7 @@ impl KElement {
|
||||
for i in 0..elems.len() {
|
||||
let elem = &elems[i];
|
||||
let pos = positions[i];
|
||||
let new_bitmap = elem.rasterize(layout, scale);
|
||||
let new_bitmap = elem.rasterize(globals, current_scale);
|
||||
// println!("{:?} {:?} {:?}", (bitmap.width, bitmap.height), pos, (new_bitmap.width, new_bitmap.height));
|
||||
bitmap.overlay(&new_bitmap, pos.0, (maxy-pos.1)/2);
|
||||
}
|
||||
@@ -50,29 +40,39 @@ impl KElement {
|
||||
bitmap
|
||||
}
|
||||
KElement::Integer(i) => {
|
||||
render_text_block(layout, &i.to_string(), scale)
|
||||
render_text_block(&mut globals.layout, &i.to_string(), current_scale)
|
||||
},
|
||||
KElement::Decimal(i) => {
|
||||
render_text_block(layout, &i.to_string(), scale)
|
||||
render_text_block(&mut globals.layout, &i.to_string(), current_scale)
|
||||
},
|
||||
KElement::Text(str) => {
|
||||
render_text_block(&mut globals.layout, &str, current_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 padding = (FRACTION_PADDING * globals.settings.scale) as usize;
|
||||
let (ax,ay) = a.get_bounds(globals, current_scale * FRACTION_SCALE);
|
||||
let (bx,by) = b.get_bounds(globals, current_scale * FRACTION_SCALE);
|
||||
|
||||
let (width, height) = (
|
||||
ax.max(bx) + padding*2,
|
||||
ay+by + padding
|
||||
);
|
||||
|
||||
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);
|
||||
let bitmap_a = &mut a.rasterize(globals, current_scale * FRACTION_SCALE);
|
||||
let bitmap_b = &mut b.rasterize(globals, current_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);
|
||||
bitmap.overlay(&bitmap_a, padding, 0);
|
||||
bitmap.overlay(&bitmap_b, padding+(bitmap_a.width-bitmap_b.width)/2, ay + padding);
|
||||
} else {
|
||||
bitmap.overlay(&bitmap_a, (bitmap_b.width-bitmap_a.width)/2, 0);
|
||||
bitmap.overlay(&bitmap_b, 0, ay);
|
||||
bitmap.overlay(&bitmap_a, padding+(bitmap_b.width-bitmap_a.width)/2, 0);
|
||||
bitmap.overlay(&bitmap_b, padding, ay + padding);
|
||||
}
|
||||
|
||||
bitmap.draw_line(0, ay+padding, bitmap.width, ay+padding, globals.settings.scale*LINE_WIDTH, 255);
|
||||
|
||||
bitmap
|
||||
|
||||
// symbols.append();
|
||||
@@ -80,30 +80,60 @@ impl KElement {
|
||||
// symbols
|
||||
|
||||
}
|
||||
KElement::Superscript(a, b) => {
|
||||
let (ax, ay) = a.get_bounds(globals, current_scale);
|
||||
let (bx, by) = b.get_bounds(globals, current_scale * SUPERSCRIPT_SCALE);
|
||||
let yoffset = (by as f32*SUPERSCRIPT_Y_OFFSET) as usize;
|
||||
|
||||
let (width, height) = (
|
||||
ax+bx,
|
||||
ay + yoffset
|
||||
);
|
||||
let mut bitmap = Bitmap::new(width, height);
|
||||
|
||||
bitmap.overlay(&a.rasterize(globals, current_scale), 0, yoffset);
|
||||
bitmap.overlay(&b.rasterize(globals, current_scale * SUPERSCRIPT_SCALE), ax, 0);
|
||||
|
||||
bitmap
|
||||
}
|
||||
}
|
||||
pub fn get_bounds(&self, layout: &mut Layout, scale: f32) -> (usize, usize) {
|
||||
}
|
||||
pub fn get_bounds(&self, globals: &mut RusTeX, current_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);
|
||||
let (x,y) = elem.get_bounds(globals, current_scale);
|
||||
totalx += x;
|
||||
maxy = maxy.max(y);
|
||||
}
|
||||
(totalx, maxy)
|
||||
}
|
||||
KElement::Integer(i) => {
|
||||
measure_text_bounds(layout, &i.to_string(), scale)
|
||||
measure_text_bounds(&mut globals.layout, &i.to_string(), current_scale)
|
||||
},
|
||||
KElement::Decimal(i) => {
|
||||
measure_text_bounds(layout, &i.to_string(), scale)
|
||||
measure_text_bounds(&mut globals.layout, &i.to_string(), current_scale)
|
||||
},
|
||||
KElement::Text(str) => {
|
||||
measure_text_bounds(&mut globals.layout, &str, current_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)
|
||||
let (ax,ay) = a.get_bounds(globals, current_scale * FRACTION_SCALE);
|
||||
let (bx,by) = b.get_bounds(globals, current_scale * FRACTION_SCALE);
|
||||
(
|
||||
(ax.max(bx)) + 2*(FRACTION_PADDING * globals.settings.scale) as usize,
|
||||
ay+by + (FRACTION_PADDING * globals.settings.scale) as usize
|
||||
)
|
||||
},
|
||||
KElement::Superscript(a, b) => {
|
||||
let (ax, ay) = a.get_bounds(globals, current_scale);
|
||||
let (bx, by) = b.get_bounds(globals, current_scale * SUPERSCRIPT_SCALE);
|
||||
(
|
||||
ax+bx,
|
||||
ay + (by as f32*SUPERSCRIPT_Y_OFFSET) as usize
|
||||
)
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -116,10 +146,10 @@ impl KElement {
|
||||
// // _ => (0.,0.)
|
||||
// // }
|
||||
// // }
|
||||
// pub fn rasterize(&self, layout: &mut Layout, bitmap: &mut Bitmap) {
|
||||
// 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(layout, data, *scale);
|
||||
// let new_bitmap = render_text_block(globals.layout, data, *scale);
|
||||
// bitmap.overlay(&new_bitmap, *x as usize, *y as usize);
|
||||
// },
|
||||
// KSymbol::None => {},
|
||||
|
||||
Reference in New Issue
Block a user