mirror of
https://github.com/Astatin3/RusTeX.git
synced 2026-06-09 00:28:01 -06:00
Start working on parsing
This commit is contained in:
Generated
+45
@@ -2,6 +2,15 @@
|
|||||||
# It is not intended for manual editing.
|
# It is not intended for manual editing.
|
||||||
version = 4
|
version = 4
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "aho-corasick"
|
||||||
|
version = "1.1.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916"
|
||||||
|
dependencies = [
|
||||||
|
"memchr",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "allocator-api2"
|
name = "allocator-api2"
|
||||||
version = "0.2.21"
|
version = "0.2.21"
|
||||||
@@ -53,6 +62,41 @@ version = "1.5.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe"
|
checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "memchr"
|
||||||
|
version = "2.7.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "32a282da65faaf38286cf3be983213fcf1d2e2a58700e808f83f4ea9a4804bc0"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "regex"
|
||||||
|
version = "1.11.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "23d7fd106d8c02486a8d64e778353d1cffe08ce79ac2e82f540c86d0facf6912"
|
||||||
|
dependencies = [
|
||||||
|
"aho-corasick",
|
||||||
|
"memchr",
|
||||||
|
"regex-automata",
|
||||||
|
"regex-syntax",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "regex-automata"
|
||||||
|
version = "0.4.10"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "6b9458fa0bfeeac22b5ca447c63aaf45f28439a709ccd244698632f9aa6394d6"
|
||||||
|
dependencies = [
|
||||||
|
"aho-corasick",
|
||||||
|
"memchr",
|
||||||
|
"regex-syntax",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "regex-syntax"
|
||||||
|
version = "0.8.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "caf4aa5b0f434c91fe5c7f1ecb6a5ece2130b02ad2a590589dda5146df959001"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rustex"
|
name = "rustex"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
@@ -60,6 +104,7 @@ dependencies = [
|
|||||||
"fontdue",
|
"fontdue",
|
||||||
"icy_sixel",
|
"icy_sixel",
|
||||||
"lazy_static",
|
"lazy_static",
|
||||||
|
"regex",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|||||||
@@ -7,3 +7,4 @@ edition = "2024"
|
|||||||
fontdue = "0.9.3"
|
fontdue = "0.9.3"
|
||||||
icy_sixel = "0.1.3"
|
icy_sixel = "0.1.3"
|
||||||
lazy_static = "1.5.0"
|
lazy_static = "1.5.0"
|
||||||
|
regex = "1.11.2"
|
||||||
|
|||||||
+2
-2
@@ -101,8 +101,8 @@ impl Bitmap {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Unit vector perpendicular to the line
|
// Unit vector perpendicular to the line
|
||||||
let perp_x = -dy / length;
|
// let perp_x = -dy / length;
|
||||||
let perp_y = dx / length;
|
// let perp_y = dx / length;
|
||||||
|
|
||||||
// Half thickness for calculations
|
// Half thickness for calculations
|
||||||
let half_thickness = thickness * 0.5;
|
let half_thickness = thickness * 0.5;
|
||||||
|
|||||||
@@ -0,0 +1,81 @@
|
|||||||
|
use std::rc::Rc;
|
||||||
|
|
||||||
|
use crate::element::{text_parser::{self, ParsedObject}, KElement};
|
||||||
|
|
||||||
|
impl KElement {
|
||||||
|
pub fn parse(input: &str) -> Result<KElement, String> {
|
||||||
|
let elems = text_parser::parse(input)?;
|
||||||
|
Self::parse_object(&elems)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn parse_object(elems: &Vec<ParsedObject>) -> Result<KElement, String> {
|
||||||
|
|
||||||
|
let mut root = Vec::new();
|
||||||
|
|
||||||
|
for elem in elems {
|
||||||
|
match elem {
|
||||||
|
text_parser::ParsedObject::Func {
|
||||||
|
name,
|
||||||
|
content,
|
||||||
|
super_script,
|
||||||
|
sub_script
|
||||||
|
} => {
|
||||||
|
|
||||||
|
if !super_script.is_empty() && !sub_script.is_empty() {
|
||||||
|
// root.push(KElement::SuperSub { inner: (), upper: (), lower: () }
|
||||||
|
} else if !super_script.is_empty() {
|
||||||
|
// root.push(KElement::SuperSub { inner: (), upper: (), lower: () }
|
||||||
|
} else if !sub_script.is_empty() {
|
||||||
|
// root.push(KElement::SuperSub { inner: (), upper: (), lower: () }
|
||||||
|
} else {
|
||||||
|
|
||||||
|
root.push(Self::from_function(name, content)?);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
text_parser::ParsedObject::Var {
|
||||||
|
text,
|
||||||
|
super_script,
|
||||||
|
sub_script
|
||||||
|
} => {
|
||||||
|
if !super_script.is_empty() && !sub_script.is_empty() {
|
||||||
|
root.push(KElement::SuperSub {
|
||||||
|
inner: Rc::new(KElement::Text(text.clone())),
|
||||||
|
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())),
|
||||||
|
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())),
|
||||||
|
upper: None,
|
||||||
|
lower: Some(Rc::new(Self::parse_object(super_script)?))
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
root.push(KElement::Text(text.clone()));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
text_parser::ParsedObject::Operator {
|
||||||
|
text
|
||||||
|
} => {
|
||||||
|
root.push(KElement::Text(text.clone()));
|
||||||
|
},
|
||||||
|
text_parser::ParsedObject::Parenthesis {
|
||||||
|
inner,
|
||||||
|
parenthesis_type,
|
||||||
|
super_script,
|
||||||
|
sub_script
|
||||||
|
} => {
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
return Ok(KElement::LinearGroup(root));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
use std::rc::Rc;
|
||||||
|
|
||||||
|
use crate::element::{text_parser::ParsedObject, KElement};
|
||||||
|
|
||||||
|
fn assert_args(n: usize, start: usize, end: usize, err: &str) -> Result<(), String> {
|
||||||
|
if start <= n && n <= end {
|
||||||
|
Ok(())
|
||||||
|
} else {
|
||||||
|
Err(format!("{err}, {n} != [{start}, {end}]"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
impl KElement {
|
||||||
|
pub fn from_function(name: &str, args: &Vec<Vec<ParsedObject>>) -> Result<KElement, String> {
|
||||||
|
match name {
|
||||||
|
"frac" => {
|
||||||
|
assert_args(args.len(), 2, 2, "A fraction must have 2 arguments!")?;
|
||||||
|
|
||||||
|
Ok(KElement::Fraction {
|
||||||
|
upper: Rc::new(Self::parse_object(&args[0])?),
|
||||||
|
lower: Rc::new(Self::parse_object(&args[1])?)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
"pm" => {
|
||||||
|
assert_args(args.len(), 0, 0, "Symbol cannot take in any args!")?;
|
||||||
|
|
||||||
|
Ok(KElement::Text("±".to_string()))
|
||||||
|
}
|
||||||
|
_ => Err(format!("Invalid function: \\{}", name))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
use std::rc::Rc;
|
||||||
|
|
||||||
|
mod rasterizer;
|
||||||
|
mod text_parser;
|
||||||
|
mod element_parser;
|
||||||
|
mod functions;
|
||||||
|
|
||||||
|
pub enum KElement {
|
||||||
|
LinearGroup(Vec<KElement>),
|
||||||
|
Integer(i64),
|
||||||
|
Decimal(f64),
|
||||||
|
Text(String),
|
||||||
|
Fraction {
|
||||||
|
upper: Rc<KElement>,
|
||||||
|
lower: Rc<KElement>
|
||||||
|
},
|
||||||
|
SuperSub{
|
||||||
|
inner: Rc<KElement>,
|
||||||
|
upper: Option<Rc<KElement>>,
|
||||||
|
lower: Option<Rc<KElement>>
|
||||||
|
},
|
||||||
|
}
|
||||||
@@ -1,19 +1,8 @@
|
|||||||
use std::rc::Rc;
|
|
||||||
|
|
||||||
use fontdue::layout::{Layout, TextStyle};
|
use fontdue::layout::{Layout, TextStyle};
|
||||||
|
|
||||||
use crate::{bitmap::Bitmap, fonts::FONTS, RusTeX, consts::*};
|
use crate::{bitmap::Bitmap, consts::*, element::KElement, fonts::FONTS, RusTeX};
|
||||||
|
|
||||||
|
|
||||||
pub enum KElement {
|
|
||||||
LinearGroup(Vec<KElement>),
|
|
||||||
Integer(i64),
|
|
||||||
Decimal(f64),
|
|
||||||
Text(String),
|
|
||||||
Fraction(Rc<KElement>, Rc<KElement>),
|
|
||||||
Superscript(Rc<KElement>, Rc<KElement>),
|
|
||||||
}
|
|
||||||
|
|
||||||
impl KElement {
|
impl KElement {
|
||||||
pub fn rasterize(&self, globals: &mut RusTeX, current_scale: f32) -> Bitmap {
|
pub fn rasterize(&self, globals: &mut RusTeX, current_scale: f32) -> Bitmap {
|
||||||
match self {
|
match self {
|
||||||
@@ -48,10 +37,10 @@ impl KElement {
|
|||||||
KElement::Text(str) => {
|
KElement::Text(str) => {
|
||||||
render_text_block(&mut globals.layout, &str, current_scale)
|
render_text_block(&mut globals.layout, &str, current_scale)
|
||||||
},
|
},
|
||||||
KElement::Fraction(a,b) => {
|
KElement::Fraction{upper,lower} => {
|
||||||
let padding = (FRACTION_PADDING * globals.settings.scale) as usize;
|
let padding = (FRACTION_PADDING * current_scale) as usize;
|
||||||
let (ax,ay) = a.get_bounds(globals, current_scale * FRACTION_SCALE);
|
let (ax,ay) = upper.get_bounds(globals, current_scale * FRACTION_SCALE);
|
||||||
let (bx,by) = b.get_bounds(globals, current_scale * FRACTION_SCALE);
|
let (bx,by) = lower.get_bounds(globals, current_scale * FRACTION_SCALE);
|
||||||
|
|
||||||
let (width, height) = (
|
let (width, height) = (
|
||||||
ax.max(bx) + padding*2,
|
ax.max(bx) + padding*2,
|
||||||
@@ -60,8 +49,8 @@ impl KElement {
|
|||||||
|
|
||||||
let mut bitmap = Bitmap::new(width, height);
|
let mut bitmap = Bitmap::new(width, height);
|
||||||
|
|
||||||
let bitmap_a = &mut a.rasterize(globals, current_scale * FRACTION_SCALE);
|
let bitmap_a = &mut upper.rasterize(globals, current_scale * FRACTION_SCALE);
|
||||||
let bitmap_b = &mut b.rasterize(globals, current_scale * FRACTION_SCALE);
|
let bitmap_b = &mut lower.rasterize(globals, current_scale * FRACTION_SCALE);
|
||||||
|
|
||||||
if bitmap_a.width > bitmap_b.width {
|
if bitmap_a.width > bitmap_b.width {
|
||||||
bitmap.overlay(&bitmap_a, padding, 0);
|
bitmap.overlay(&bitmap_a, padding, 0);
|
||||||
@@ -80,21 +69,31 @@ impl KElement {
|
|||||||
// symbols
|
// symbols
|
||||||
|
|
||||||
}
|
}
|
||||||
KElement::Superscript(a, b) => {
|
KElement::SuperSub{inner, upper, lower} => {
|
||||||
let (ax, ay) = a.get_bounds(globals, current_scale);
|
let (ax, ay) = inner.get_bounds(globals, current_scale);
|
||||||
let (bx, by) = b.get_bounds(globals, current_scale * SUPERSCRIPT_SCALE);
|
if upper.is_some() && lower.is_some() {
|
||||||
let yoffset = (by as f32*SUPERSCRIPT_Y_OFFSET) as usize;
|
todo!();
|
||||||
|
} else if upper.is_some() {
|
||||||
|
let upper = upper.as_ref().unwrap();
|
||||||
|
let (bx, by) = upper.get_bounds(globals, current_scale * SUPERSCRIPT_SCALE);
|
||||||
|
let yoffset = (by as f32*SUPERSCRIPT_Y_OFFSET) as usize;
|
||||||
|
|
||||||
let (width, height) = (
|
let (width, height) = (
|
||||||
ax+bx,
|
ax+bx,
|
||||||
ay + yoffset
|
ay + yoffset
|
||||||
);
|
);
|
||||||
let mut bitmap = Bitmap::new(width, height);
|
let mut bitmap = Bitmap::new(width, height);
|
||||||
|
|
||||||
bitmap.overlay(&a.rasterize(globals, current_scale), 0, yoffset);
|
bitmap.overlay(&inner.rasterize(globals, current_scale), 0, yoffset);
|
||||||
bitmap.overlay(&b.rasterize(globals, current_scale * SUPERSCRIPT_SCALE), ax, 0);
|
bitmap.overlay(&upper.rasterize(globals, current_scale * SUPERSCRIPT_SCALE), ax, 0);
|
||||||
|
|
||||||
bitmap
|
bitmap
|
||||||
|
|
||||||
|
} else if lower.is_some() {
|
||||||
|
todo!();
|
||||||
|
} else {
|
||||||
|
unreachable!()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -118,21 +117,30 @@ impl KElement {
|
|||||||
KElement::Text(str) => {
|
KElement::Text(str) => {
|
||||||
measure_text_bounds(&mut globals.layout, &str, current_scale)
|
measure_text_bounds(&mut globals.layout, &str, current_scale)
|
||||||
},
|
},
|
||||||
KElement::Fraction(a,b) => {
|
KElement::Fraction{upper,lower} => {
|
||||||
let (ax,ay) = a.get_bounds(globals, current_scale * FRACTION_SCALE);
|
let (ax,ay) = upper.get_bounds(globals, current_scale * FRACTION_SCALE);
|
||||||
let (bx,by) = b.get_bounds(globals, current_scale * FRACTION_SCALE);
|
let (bx,by) = lower.get_bounds(globals, current_scale * FRACTION_SCALE);
|
||||||
(
|
(
|
||||||
(ax.max(bx)) + 2*(FRACTION_PADDING * globals.settings.scale) as usize,
|
(ax.max(bx)) + 2*(FRACTION_PADDING * current_scale) as usize,
|
||||||
ay+by + (FRACTION_PADDING * globals.settings.scale) as usize
|
ay+by + (FRACTION_PADDING * current_scale) as usize
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
KElement::Superscript(a, b) => {
|
KElement::SuperSub{inner, upper, lower} => {
|
||||||
let (ax, ay) = a.get_bounds(globals, current_scale);
|
let (ax, ay) = inner.get_bounds(globals, current_scale);
|
||||||
let (bx, by) = b.get_bounds(globals, current_scale * SUPERSCRIPT_SCALE);
|
if upper.is_some() && lower.is_some() {
|
||||||
(
|
todo!();
|
||||||
ax+bx,
|
} else if upper.is_some() {
|
||||||
ay + (by as f32*SUPERSCRIPT_Y_OFFSET) as usize
|
let (bx, by) = upper.as_ref().unwrap().get_bounds(globals, current_scale * SUPERSCRIPT_SCALE);
|
||||||
)
|
(
|
||||||
|
ax+bx,
|
||||||
|
ay + (by as f32*SUPERSCRIPT_Y_OFFSET) as usize
|
||||||
|
)
|
||||||
|
} else if lower.is_some() {
|
||||||
|
todo!();
|
||||||
|
} else {
|
||||||
|
unreachable!()
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,307 @@
|
|||||||
|
use regex::Regex;
|
||||||
|
use lazy_static::lazy_static;
|
||||||
|
use std::collections::VecDeque;
|
||||||
|
|
||||||
|
lazy_static! {
|
||||||
|
static ref FUNCTION_REGEX: Regex = Regex::new(r"\\[a-zA-Z]+").unwrap();
|
||||||
|
static ref NUMBER_REGEX: Regex = Regex::new(r"\d+(?:\.\d+)?").unwrap();
|
||||||
|
static ref LETTER_REGEX: Regex = Regex::new(r"[a-zA-Z]").unwrap();
|
||||||
|
static ref OPERATOR_REGEX: Regex = Regex::new(r"[+\-=*/±]").unwrap();
|
||||||
|
static ref WHITESPACE_REGEX: Regex = Regex::new(r"\s+").unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, PartialEq)]
|
||||||
|
pub enum PerenthesisType {
|
||||||
|
Round, // ()
|
||||||
|
Square, // []
|
||||||
|
// Curly, // {}
|
||||||
|
// Angle, // <>
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, PartialEq)]
|
||||||
|
pub enum ParsedObject {
|
||||||
|
Func {
|
||||||
|
name: String,
|
||||||
|
content: Vec<Vec<ParsedObject>>,
|
||||||
|
super_script: Vec<ParsedObject>,
|
||||||
|
sub_script: Vec<ParsedObject>,
|
||||||
|
},
|
||||||
|
Var {
|
||||||
|
text: String,
|
||||||
|
super_script: Vec<ParsedObject>,
|
||||||
|
sub_script: Vec<ParsedObject>,
|
||||||
|
},
|
||||||
|
Operator {
|
||||||
|
text: String,
|
||||||
|
},
|
||||||
|
Parenthesis {
|
||||||
|
inner: Vec<ParsedObject>,
|
||||||
|
parenthesis_type: PerenthesisType,
|
||||||
|
super_script: Vec<ParsedObject>,
|
||||||
|
sub_script: Vec<ParsedObject>,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, PartialEq)]
|
||||||
|
enum Token {
|
||||||
|
Function(String),
|
||||||
|
Number(String),
|
||||||
|
Letter(String),
|
||||||
|
Operator(String),
|
||||||
|
LeftParen(PerenthesisType),
|
||||||
|
RightParen(PerenthesisType),
|
||||||
|
LeftBrace,
|
||||||
|
RightBrace,
|
||||||
|
Superscript,
|
||||||
|
Subscript,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn parse(input: &str) -> Result<Vec<ParsedObject>, String> {
|
||||||
|
let tokens = tokenize_with_regex(input)?;
|
||||||
|
let mut token_queue = VecDeque::from(tokens);
|
||||||
|
parse_tokens(&mut token_queue)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn tokenize_with_regex(input: &str) -> Result<Vec<Token>, String> {
|
||||||
|
let mut tokens = Vec::new();
|
||||||
|
let mut pos = 0;
|
||||||
|
let input_chars: Vec<char> = input.chars().collect();
|
||||||
|
|
||||||
|
while pos < input_chars.len() {
|
||||||
|
let remaining: String = input_chars[pos..].iter().collect();
|
||||||
|
|
||||||
|
// Skip whitespace
|
||||||
|
if let Some(mat) = WHITESPACE_REGEX.find(&remaining) {
|
||||||
|
if mat.start() == 0 {
|
||||||
|
pos += mat.len();
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check for TeX functions
|
||||||
|
if let Some(mat) = FUNCTION_REGEX.find(&remaining) {
|
||||||
|
if mat.start() == 0 {
|
||||||
|
let func_name = mat.as_str()[1..].to_string(); // Remove the \
|
||||||
|
tokens.push(Token::Function(func_name));
|
||||||
|
pos += mat.len();
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check for numbers (including decimals)
|
||||||
|
if let Some(mat) = NUMBER_REGEX.find(&remaining) {
|
||||||
|
if mat.start() == 0 {
|
||||||
|
tokens.push(Token::Number(mat.as_str().to_string()));
|
||||||
|
pos += mat.len();
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check for single letters
|
||||||
|
if let Some(mat) = LETTER_REGEX.find(&remaining) {
|
||||||
|
if mat.start() == 0 {
|
||||||
|
tokens.push(Token::Letter(mat.as_str().to_string()));
|
||||||
|
pos += mat.len();
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check for operators
|
||||||
|
if let Some(mat) = OPERATOR_REGEX.find(&remaining) {
|
||||||
|
if mat.start() == 0 {
|
||||||
|
tokens.push(Token::Operator(mat.as_str().to_string()));
|
||||||
|
pos += mat.len();
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle special characters
|
||||||
|
match input_chars[pos] {
|
||||||
|
'(' => tokens.push(Token::LeftParen(PerenthesisType::Round)),
|
||||||
|
')' => tokens.push(Token::RightParen(PerenthesisType::Round)),
|
||||||
|
'[' => tokens.push(Token::LeftParen(PerenthesisType::Square)),
|
||||||
|
']' => tokens.push(Token::RightParen(PerenthesisType::Square)),
|
||||||
|
'{' => tokens.push(Token::LeftBrace),
|
||||||
|
'}' => tokens.push(Token::RightBrace),
|
||||||
|
'^' => tokens.push(Token::Superscript),
|
||||||
|
'_' => tokens.push(Token::Subscript),
|
||||||
|
_ => return Err(format!("Unexpected character: '{}'", input_chars[pos])),
|
||||||
|
}
|
||||||
|
|
||||||
|
pos += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(tokens)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_tokens(tokens: &mut VecDeque<Token>) -> Result<Vec<ParsedObject>, String> {
|
||||||
|
let mut result = Vec::new();
|
||||||
|
|
||||||
|
while let Some(token) = tokens.pop_front() {
|
||||||
|
match token {
|
||||||
|
Token::Function(name) => {
|
||||||
|
let func = parse_function_generic(name, tokens)?;
|
||||||
|
result.push(func);
|
||||||
|
}
|
||||||
|
Token::Number(text) | Token::Letter(text) => {
|
||||||
|
let var = parse_variable(text, tokens)?;
|
||||||
|
result.push(var);
|
||||||
|
}
|
||||||
|
Token::Operator(op) => {
|
||||||
|
result.push(ParsedObject::Operator { text: op });
|
||||||
|
}
|
||||||
|
Token::LeftParen(paren_type) => {
|
||||||
|
let paren = parse_parenthesis(paren_type, tokens)?;
|
||||||
|
result.push(paren);
|
||||||
|
}
|
||||||
|
Token::RightParen(_) | Token::RightBrace => {
|
||||||
|
// Put it back - should be handled by parent context
|
||||||
|
tokens.push_front(token);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
tokens.push_front(token);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(result)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_function_generic(name: String, tokens: &mut VecDeque<Token>) -> Result<ParsedObject, String> {
|
||||||
|
let mut content = Vec::new();
|
||||||
|
|
||||||
|
// Parse all braced content that follows this function
|
||||||
|
while let Some(Token::LeftBrace) = tokens.front() {
|
||||||
|
let braced_content = parse_braced_content(tokens)?;
|
||||||
|
content.push(braced_content);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse potential superscript and subscript
|
||||||
|
let (super_script, sub_script) = parse_scripts(tokens)?;
|
||||||
|
|
||||||
|
Ok(ParsedObject::Func {
|
||||||
|
name,
|
||||||
|
content,
|
||||||
|
super_script,
|
||||||
|
sub_script,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_variable(text: String, tokens: &mut VecDeque<Token>) -> Result<ParsedObject, String> {
|
||||||
|
let (super_script, sub_script) = parse_scripts(tokens)?;
|
||||||
|
|
||||||
|
Ok(ParsedObject::Var {
|
||||||
|
text,
|
||||||
|
super_script,
|
||||||
|
sub_script,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_parenthesis(paren_type: PerenthesisType, tokens: &mut VecDeque<Token>) -> Result<ParsedObject, String> {
|
||||||
|
let mut inner = Vec::new();
|
||||||
|
|
||||||
|
// Parse until we find the matching closing parenthesis
|
||||||
|
while !tokens.is_empty() {
|
||||||
|
if let Some(Token::RightParen(closing_type)) = tokens.front() {
|
||||||
|
if *closing_type == paren_type {
|
||||||
|
tokens.pop_front(); // consume the closing paren
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let parsed = parse_tokens(tokens)?;
|
||||||
|
inner.extend(parsed);
|
||||||
|
|
||||||
|
if tokens.is_empty() {
|
||||||
|
return Err("Unmatched opening parenthesis".to_string());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let (super_script, sub_script) = parse_scripts(tokens)?;
|
||||||
|
|
||||||
|
Ok(ParsedObject::Parenthesis {
|
||||||
|
inner,
|
||||||
|
parenthesis_type: paren_type,
|
||||||
|
super_script,
|
||||||
|
sub_script,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_braced_content(tokens: &mut VecDeque<Token>) -> Result<Vec<ParsedObject>, String> {
|
||||||
|
if let Some(Token::LeftBrace) = tokens.pop_front() {
|
||||||
|
let mut content = Vec::new();
|
||||||
|
|
||||||
|
while !tokens.is_empty() {
|
||||||
|
if let Some(Token::RightBrace) = tokens.front() {
|
||||||
|
tokens.pop_front(); // consume the closing brace
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
let parsed = parse_tokens(tokens)?;
|
||||||
|
content.extend(parsed);
|
||||||
|
|
||||||
|
if tokens.is_empty() {
|
||||||
|
return Err("Unmatched opening brace".to_string());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(content)
|
||||||
|
} else {
|
||||||
|
Err("Expected opening brace".to_string())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_scripts(tokens: &mut VecDeque<Token>) -> Result<(Vec<ParsedObject>, Vec<ParsedObject>), String> {
|
||||||
|
let mut super_script = Vec::new();
|
||||||
|
let mut sub_script = Vec::new();
|
||||||
|
|
||||||
|
// Parse superscript and subscript (can appear in any order)
|
||||||
|
while let Some(token) = tokens.front() {
|
||||||
|
match token {
|
||||||
|
Token::Superscript => {
|
||||||
|
tokens.pop_front(); // consume ^
|
||||||
|
super_script = parse_script_content(tokens)?;
|
||||||
|
}
|
||||||
|
Token::Subscript => {
|
||||||
|
tokens.pop_front(); // consume _
|
||||||
|
sub_script = parse_script_content(tokens)?;
|
||||||
|
}
|
||||||
|
_ => break,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok((super_script, sub_script))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_script_content(tokens: &mut VecDeque<Token>) -> Result<Vec<ParsedObject>, String> {
|
||||||
|
if let Some(token) = tokens.front() {
|
||||||
|
match token {
|
||||||
|
Token::LeftBrace => {
|
||||||
|
// Multi-character script content in braces
|
||||||
|
parse_braced_content(tokens)
|
||||||
|
}
|
||||||
|
Token::Number(text) | Token::Letter(text) => {
|
||||||
|
// Single character/number script content
|
||||||
|
let text = text.clone();
|
||||||
|
tokens.pop_front();
|
||||||
|
Ok(vec![ParsedObject::Var {
|
||||||
|
text,
|
||||||
|
super_script: vec![],
|
||||||
|
sub_script: vec![],
|
||||||
|
}])
|
||||||
|
}
|
||||||
|
Token::Function(name) => {
|
||||||
|
// Function in script
|
||||||
|
let name = name.clone();
|
||||||
|
tokens.pop_front();
|
||||||
|
let func = parse_function_generic(name, tokens)?;
|
||||||
|
Ok(vec![func])
|
||||||
|
}
|
||||||
|
_ => Err("Invalid script content".to_string()),
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Err("Expected script content".to_string())
|
||||||
|
}
|
||||||
|
}
|
||||||
+114
-53
@@ -1,7 +1,7 @@
|
|||||||
#[allow(non_upper_case_globals)]
|
#[allow(non_upper_case_globals)]
|
||||||
|
|
||||||
mod fonts;
|
mod fonts;
|
||||||
mod parser;
|
mod element;
|
||||||
mod bitmap;
|
mod bitmap;
|
||||||
mod consts;
|
mod consts;
|
||||||
|
|
||||||
@@ -9,70 +9,131 @@ use std::{rc::Rc, time::Instant};
|
|||||||
|
|
||||||
use fontdue::{layout::{CoordinateSystem, Layout, LayoutSettings}};
|
use fontdue::{layout::{CoordinateSystem, Layout, LayoutSettings}};
|
||||||
|
|
||||||
use crate::{bitmap::Bitmap, parser::{KElement}};
|
use crate::{bitmap::Bitmap, element::{KElement}};
|
||||||
|
|
||||||
fn main() -> Result<(), std::fmt::Error> {
|
fn main() -> Result<(), std::fmt::Error> {
|
||||||
let start = Instant::now();
|
parse_test()
|
||||||
|
}
|
||||||
|
|
||||||
let mut rustex = RusTeX::new(TeXSettings { scale: 100. });
|
fn parse_test() -> Result<(), std::fmt::Error> {
|
||||||
|
// Test the quadratic formula
|
||||||
|
// let tex_input = r"x=\frac{-b\pm\sqrt{b^2 - 4ac}}{2a}";
|
||||||
|
let tex_input = &std::env::args().nth(1).unwrap();
|
||||||
|
|
||||||
let element = KElement::LinearGroup(vec![
|
match KElement::parse(tex_input) {
|
||||||
KElement::Fraction(
|
Ok(result) => {
|
||||||
Rc::new(KElement::LinearGroup(vec![
|
let mut rustex = RusTeX::new(TeXSettings { scale: 100. });
|
||||||
KElement::Integer(123),
|
rustex.rasterize(result).print();
|
||||||
KElement::Text("*".to_string()),
|
}
|
||||||
KElement::Superscript(
|
Err(e) => println!("Error: {}", e),
|
||||||
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))
|
|
||||||
))
|
|
||||||
))
|
|
||||||
))
|
|
||||||
))
|
|
||||||
))
|
|
||||||
))
|
|
||||||
)),
|
|
||||||
),
|
|
||||||
]);
|
|
||||||
|
|
||||||
// rustex.add_text(&TextStyle::new("testi12345ng! e^23", 100.0, 0));
|
// println!("Parsing: {}", tex_input);
|
||||||
let bitmap = rustex.rasterize(element);
|
|
||||||
|
|
||||||
println!("Rasterizing time: {:?}", start.elapsed());
|
// match parser::parse(tex_input) {
|
||||||
|
// Ok(result) => {
|
||||||
|
// println!("Parsed result:");
|
||||||
|
// for (i, obj) in result.iter().enumerate() {
|
||||||
|
// println!(" [{}]: {:#?}", i, obj);
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// Err(e) => {
|
||||||
|
// println!("Error parsing TeX: {}", e);
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
bitmap.print();
|
// // Test some other examples
|
||||||
|
// let examples = vec![
|
||||||
|
// "a^2",
|
||||||
|
// r"\frac{12.34a+b}{2}",
|
||||||
|
// r"\sqrt{x}",
|
||||||
|
// "x_{a^2}^{2^a}",
|
||||||
|
// "(a+b)^2",
|
||||||
|
// ];
|
||||||
|
|
||||||
// print_bitmap(&bitmap, width, height);
|
// for example in examples {
|
||||||
|
// println!("\n--- Parsing: {} ---", example);
|
||||||
|
// match parser.parse(example) {
|
||||||
|
// Ok(result) => {
|
||||||
|
// for obj in result {
|
||||||
|
// println!("{:#?}", obj);
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// Err(e) => println!("Error: {}", e),
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// fn raster_test() -> Result<(), std::fmt::Error> {
|
||||||
|
// let start = Instant::now();
|
||||||
|
|
||||||
|
// let mut rustex = RusTeX::new(TeXSettings { scale: 100. });
|
||||||
|
|
||||||
|
// let element =
|
||||||
|
|
||||||
|
// KElement::Superscript(Rc::new(
|
||||||
|
// 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::Fraction(
|
||||||
|
// Rc::new(KElement::Integer(5)),
|
||||||
|
// 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::Decimal(12.34)));
|
||||||
|
|
||||||
|
// // 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 {
|
struct RusTeX {
|
||||||
pub settings: TeXSettings,
|
pub settings: TeXSettings,
|
||||||
pub layout: Layout
|
pub layout: Layout
|
||||||
|
|||||||
Reference in New Issue
Block a user