Skip to content

Instantly share code, notes, and snippets.

@mkalte666
Created April 4, 2025 13:00
Show Gist options
  • Save mkalte666/c3cf48972876d695237731507bfcd33e to your computer and use it in GitHub Desktop.
Save mkalte666/c3cf48972876d695237731507bfcd33e to your computer and use it in GitHub Desktop.
/// A helper struct to construct math text. Just wraps a &str, intended for ui.add() and similar situations.
pub struct MathText<'a>(pub &'a str);
impl<'a> Widget for MathText<'a> {
fn ui(self, ui: &mut Ui) -> Response {
ui.label(math_text(ui, self.0))
}
}
/// Layout a math text using the style from the current ui.
pub fn math_text(ui: &Ui, text: &str) -> LayoutJob {
math_text_ctx(ui.ctx().clone(), ui.style().clone(), text)
}
/// Layout a Math text using context and style
pub fn math_text_ctx(ctx: egui::Context, style: Arc<Style>, text: &str) -> LayoutJob {
let color = style.visuals.text_color();
let normal = TextStyle::Body.resolve(&style);
let subscript = TextStyle::Small.resolve(&style);
ctx.memory_mut(|mem| {
mem.caches
.cache::<MathTextCache>()
.get((text, &normal, &subscript, color))
})
}
/// Helper cache that stores layouted MathText
type MathTextCache = FrameCache<LayoutJob, MathTextGenerator>;
/// The generator struct needed to tokenize MathText
#[derive(Default)]
struct MathTextGenerator {}
impl MathTextGenerator {
/// Layout a MathText.
fn layout(&self, text: &str, normal: &FontId, small: &FontId, color: Color32) -> LayoutJob {
let sections = parse_math(text);
let mut job = LayoutJob::default();
let normal = TextFormat::simple(normal.clone(), color);
let mut subscript = TextFormat::simple(small.clone(), color);
subscript.valign = Align::BOTTOM;
let mut superscript = TextFormat::simple(small.clone(), color);
superscript.valign = Align::TOP;
for section in sections {
match section {
MathSection::Normal(s) => {
job.append(s, 0.0, normal.clone());
}
MathSection::Subscript(s) => {
job.append(s, 0.0, subscript.clone());
}
MathSection::Superscript(s) => {
job.append(s, 0.0, superscript.clone());
}
}
}
job
}
}
/// Helper to cache layout jobs for math drawing
impl ComputerMut<(&str, &FontId, &FontId, Color32), LayoutJob> for MathTextGenerator {
fn compute(
&mut self,
(text, normal, small, color): (&str, &FontId, &FontId, Color32),
) -> LayoutJob {
self.layout(text, normal, small, color)
}
}
/// Tokenize an input string for math drawing
fn parse_math(mut text: &str) -> Vec<MathSection> {
let mut sections = vec![];
while !text.is_empty() {
if let Some(start) = text.find("_") {
let before = &text[0..start];
if !before.is_empty() {
sections.push(MathSection::Normal(before));
}
let sub_begin = start + 1;
text = &text[sub_begin..];
let end = text
.find(|f: char| f == '_' || f == '^' || f.is_whitespace())
.unwrap_or(text.len());
let section = &text[..end];
if !section.is_empty() {
sections.push(MathSection::Subscript(section));
}
text = &text[end..];
} else if let Some(start) = text.find("^") {
let before = &text[0..start];
if !before.is_empty() {
sections.push(MathSection::Normal(before));
}
let sub_begin = start + 1;
text = &text[sub_begin..];
let end = text
.find(|f: char| f == '_' || f == '^' || f.is_whitespace())
.unwrap_or(text.len());
let section = &text[..end];
if !section.is_empty() {
sections.push(MathSection::Superscript(section));
}
text = &text[end..];
} else {
sections.push(MathSection::Normal(text));
text = &text[text.len()..];
}
}
sections
}
/// A helper struct to tokenize the input string
enum MathSection<'a> {
Normal(&'a str),
Subscript(&'a str),
Superscript(&'a str),
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment