Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Experimental LaTeX parser and renderer in book example #45

Draft
wants to merge 17 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 12 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,29 @@

## Unreleased

## 0.15.0 - 2024-04-02

### Added

- Replace copy icon with checkmark when clicking copy button in code blocks
([#42](https://github.com/lampsitter/egui_commonmark/pull/42) by [@zeozeozeo](https://github.com/zeozeozeo))

- Interactive tasklists with `CommonMarkViewer::show_mut`
([#40](https://github.com/lampsitter/egui_commonmark/pull/40) by [@crumblingstatue](https://github.com/crumblingstatue))

### Changed

- MSRV bumped to 1.74 due to pulldown_cmark
- Alerts are case-insensitive
- More spacing between list indicator and elements ([#46](https://github.com/lampsitter/egui_commonmark/pull/46))

### Fixed

- Lists align text when wrapping instead of wrapping at the beginning of the next
line ([#46](https://github.com/lampsitter/egui_commonmark/pull/46))
- Code blocks won't insert a newline when in lists
- In certain scenarios there was no newline after lists
- Copy button for code blocks show the correct cursor again on hover (regression
after egui 0.27)

## 0.14.0 - 2024-03-26

Expand Down
15 changes: 10 additions & 5 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
[package]
name = "egui_commonmark"
authors = ["Erlend Walstad"]
version = "0.14.0"
version = "0.15.0"
edition = "2021"
description = "Commonmark viewer for egui"
keywords = ["commonmark", "egui"]
Expand All @@ -11,7 +11,7 @@ readme = "README.md"
repository = "https://github.com/lampsitter/egui_commonmark"
documentation = "https://docs.rs/egui_commonmark"
include = ["**/*.rs", "LICENSE-MIT", "LICENSE-APACHE", "Cargo.toml"]
rust-version = "1.72" # Follow egui
rust-version = "1.74" # Required by pulldown_cmark

[dependencies]
egui_extras = "0.27"
Expand All @@ -24,10 +24,10 @@ syntect = { version = "5.0.0", optional = true, default-features = false, featur
document-features = { version = "0.2", optional = true }

comrak = { version = "0.20.0", default-features = false, optional = true }
pulldown-cmark = { version = "0.10", default-features = false, optional = true }
pulldown-cmark = { git = "https://github.com/pulldown-cmark/pulldown-cmark.git", branch = "branch_0.11", default-features = false, optional = true }

[features]
default = ["load-images", "pulldown_cmark"]
default = ["load-images", "pulldown_cmark", "math"]

pulldown_cmark = ["dep:pulldown-cmark"]
comrak = ["dep:comrak"]
Expand All @@ -44,9 +44,14 @@ svg = ["egui_extras/svg"]
## Images with urls will be downloaded and displayed
fetch = ["egui_extras/http"]

## Enable pulldown_cmark's math parser
math = []

[dev-dependencies]
eframe = { version = "0.27", default-features = false, features = ["default_fonts", "glow"] }
image = { version = "0.24", default-features = false, features = ["png"] }
image = { version = "0.25", default-features = false, features = ["png"] }
egui_extras = { version = "0.27", features = ["svg"] } # required for math rendering
mathjax_svg = "3.1" # required for LaTeX -> SVG

[package.metadata.docs.rs]
features = ["better_syntax_highlighting", "document-features"]
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ Github's markdown syntax: tables, strikethrough, tasklists and footnotes.
In Cargo.toml:

```toml
egui_commonmark = "0.14"
egui_commonmark = "0.15"
# Specify what image formats you want to use
image = { version = "0.24", default-features = false, features = ["png"] }
```
Expand Down
39 changes: 37 additions & 2 deletions examples/book.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,9 @@
//! Shows a simple way to use the crate to implement a book like view.

use eframe::egui;
use egui::ImageSource;
use egui_commonmark::*;
use std::{cell::RefCell, collections::HashMap, rc::Rc, sync::Arc};

struct Page {
name: String,
Expand All @@ -18,6 +20,18 @@ struct App {
cache: CommonMarkCache,
curr_tab: Option<usize>,
pages: Vec<Page>,
math_to_svg: Rc<RefCell<HashMap<String, Arc<[u8]>>>>,
}

fn render_math(math: &str, inline: bool) -> Arc<[u8]> {
println!("rendering math: {math}");
if inline {
mathjax_svg::convert_to_svg_inline(math).unwrap()
} else {
mathjax_svg::convert_to_svg(math).unwrap()
}
.into_bytes()
.into()
}

impl App {
Expand Down Expand Up @@ -47,12 +61,28 @@ impl App {
fn content_panel(&mut self, ui: &mut egui::Ui) {
egui::ScrollArea::vertical().show(ui, |ui| {
// Add a frame with margin to prevent the content from hugging the sidepanel
let math_to_svg = self.math_to_svg.clone();
egui::Frame::none()
.inner_margin(egui::Margin::symmetric(5.0, 0.0))
.show(ui, |ui| {
CommonMarkViewer::new("viewer")
.default_width(Some(200))
.max_image_width(Some(512))
.render_math_fn(Some(&move |ui, math, inline| {
let mut map = math_to_svg.borrow_mut();
let svg = map
.entry(math.to_string())
.or_insert_with(|| render_math(math, inline));

let uri = format!("{}.svg", egui::Id::from(math.to_string()).value());
ui.add(
egui::Image::new(ImageSource::Bytes {
uri: uri.into(),
bytes: egui::load::Bytes::Shared(svg.clone()),
})
.fit_to_original_size(1.0),
);
}))
.show(
ui,
&mut self.cache,
Expand Down Expand Up @@ -121,14 +151,19 @@ fn main() {
content: include_str!("markdown/code-blocks.md").to_owned(),
},
Page {
name: "Block Quotes ".to_owned(),
name: "Block Quotes".to_owned(),
content: include_str!("markdown/blockquotes.md").to_owned(),
},
Page {
name: "Tables ".to_owned(),
name: "Tables".to_owned(),
content: include_str!("markdown/tables.md").to_owned(),
},
Page {
name: "Math".to_owned(),
content: include_str!("markdown/math.md").to_owned(),
},
],
math_to_svg: Rc::new(RefCell::new(HashMap::new())),
})
}),
);
Expand Down
2 changes: 1 addition & 1 deletion examples/markdown/blockquotes.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ will be displayed as:
> tip alert

<!-- The trailing whitespaces are deliberate on important and warning -->
<!-- Case insensetivity --->
<!-- Case insensitivity --->
> [!imporTant]
> important alert

Expand Down
7 changes: 7 additions & 0 deletions examples/markdown/code-blocks.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,3 +20,10 @@ default. It will therefore fallback to default highlighting.
egui_commonmark = "0.10"
image = { version = "0.24", default-features = false, features = ["png"] }
```

- ```rs
let x = 3.14;
```
- Code blocks can be in lists too :)

More content...
6 changes: 6 additions & 0 deletions examples/markdown/lists.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,4 +19,10 @@
1. Lorem ipsum dolor sit amet, consectetur __adipiscing elit, sed__ do
eiusmod tempor incididunt _ut_ labore ~~et~~ dolore magna aliqua. Ut enim
ad minim veniam, quis nostrud exercitation
2. Lorem ipsum dolor sit amet, consectetur __adipiscing elit, sed__ do
eiusmod tempor incididunt _ut_ labore ~~et~~ dolore magna aliqua. Ut enim
ad minim veniam, quis nostrud exercitation
- Lorem ipsum dolor sit amet, consectetur __adipiscing elit, sed__ do
eiusmod tempor incididunt _ut_ labore ~~et~~ dolore magna aliqua. Ut enim
ad minim veniam, quis nostrud exercitation

60 changes: 60 additions & 0 deletions examples/markdown/math.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
When $a \ne 0$, there are two solutions to $(ax^2 + bx + c = 0)$ and they are
$$ x = {-b \pm \sqrt{b^2-4ac} \over 2a} $$

- Block equations:

$$ \frac{\partial \rho}{\partial t} + \nabla \cdot \vec{j} = 0 \,. \label{eq:continuity} $$

- Stokes' theorem:

$$ \begin{equation}
\int_{\partial\Omega} \omega = \int_{\Omega} \mathrm{d}\omega \,.
\label{eq:stokes}
\end{equation} $$

- Maxwell's equations:

$$
\begin{align}
\nabla \cdot \vec{E} & = \rho \nonumber \\
\nabla \cdot \vec{B} & = 0 \nonumber \\
\nabla \times \vec{E} & = - \frac{\partial \vec{B}}{\partial t} \label{eq:maxwell} \\
\nabla \times \vec{B} & = \vec{j} + \frac{\partial \vec{E}}{\partial t} \nonumber \,.
\end{align}
$$

- L'Hôpital's rule

$$
\begin{align}
\lim_{x\to 0}{\frac{e^x-1}{2x}}
\overset{\left[\frac{0}{0}\right]}{\underset{\mathrm{H}}{=}}
\lim_{x\to 0}{\frac{e^x}{2}}={\frac{1}{2}}
\end{align}
$$

- More stuff

$$
\begin{align}
z = \overbrace{
\underbrace{x}_\text{real} + i
\underbrace{y}_\text{imaginary}
}^\text{complex number}
\end{align}
$$

- Multiline subscripts: $$
\begin{align}
\prod_{\substack{
1\le i \le n \\
1\le j \le m}}
M_{i,j}
\end{align}
$$

---

## Edge cases

Cheese is $10.40 + $0.20 tax
2 changes: 1 addition & 1 deletion rust-toolchain
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
[toolchain]
channel = "1.72.0"
channel = "1.74.0"
components = ["rustfmt", "clippy"]
34 changes: 22 additions & 12 deletions src/elements.rs
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ pub(crate) fn number_point(ui: &mut Ui, number: &str) {
ui.painter().text(
rect.right_center(),
egui::Align2::RIGHT_CENTER,
format!("{number}. "),
format!("{number}."),
TextStyle::Body.resolve(ui.style()),
ui.visuals().strong_text_color(),
);
Expand Down Expand Up @@ -120,16 +120,25 @@ pub fn code_block<'t>(
let persistent_id = ui.make_persistent_id(output.response.id);
let copied_icon = ui.memory_mut(|m| *m.data.get_temp_mut_or_default::<bool>(persistent_id));

let copy_button = ui.put(
egui::Rect {
min: position,
max: position,
},
egui::Button::new(if copied_icon { "✔" } else { "🗐" })
.small()
.frame(false)
.fill(egui::Color32::TRANSPARENT),
);
let copy_button = ui
.put(
egui::Rect {
min: position,
max: position,
},
egui::Button::new(if copied_icon { "✔" } else { "🗐" })
.small()
.frame(false)
.fill(egui::Color32::TRANSPARENT),
)
// workaround for a regression after egui 0.27 where the edit cursor was shown even when
// hovering over the button. We try interact_cursor first to allow the cursor to be
// overriden
.on_hover_cursor(
ui.visuals()
.interact_cursor
.unwrap_or(egui::CursorIcon::Default),
);

// Update icon state in persistent memory
if copied_icon && !copy_button.hovered() {
Expand Down Expand Up @@ -251,6 +260,8 @@ impl List {
} else {
unreachable!();
}

ui.add_space(4.0);
}

pub fn end_level(&mut self, ui: &mut Ui) {
Expand All @@ -263,7 +274,6 @@ impl List {
}

pub(crate) fn blockquote(ui: &mut Ui, accent: egui::Color32, add_contents: impl FnOnce(&mut Ui)) {
newline(ui);
let start = ui.painter().add(egui::Shape::Noop);
let response = egui::Frame::none()
// offset the frame so that we can use the space for the horizontal line and other stuff
Expand Down
21 changes: 15 additions & 6 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -302,20 +302,29 @@ impl CommonMarkOptions {
}
}

#[derive(Debug)]
pub struct CommonMarkViewer {
/// Takes [`egui::Ui`], the math text to be rendered and whether it is inline
type RenderMathFn = dyn Fn(&mut Ui, &str, bool);

pub struct CommonMarkViewer<'a> {
source_id: Id,
options: CommonMarkOptions,
math_fn: Option<&'a RenderMathFn>,
}

impl CommonMarkViewer {
impl<'a> CommonMarkViewer<'a> {
pub fn new(source_id: impl std::hash::Hash) -> Self {
Self {
source_id: Id::new(source_id),
options: CommonMarkOptions::default(),
math_fn: None,
}
}

pub fn render_math_fn(mut self, math_fn: Option<&'a RenderMathFn>) -> Self {
self.math_fn = math_fn;
self
}

/// The amount of spaces a bullet point is indented. By default this is 4
/// spaces.
pub fn indentation_spaces(mut self, spaces: usize) -> Self {
Expand Down Expand Up @@ -413,6 +422,7 @@ impl CommonMarkViewer {
&self.options,
text,
false,
self.math_fn,
);

#[cfg(feature = "comrak")]
Expand Down Expand Up @@ -442,7 +452,7 @@ impl CommonMarkViewer {
let (response, checkmark_events) = parsers::pulldown::CommonMarkViewerInternal::new(
self.source_id,
)
.show(ui, cache, &self.options, text, false);
.show(ui, cache, &self.options, text, false, self.math_fn);

// Update source text for checkmarks that were clicked
for ev in checkmark_events {
Expand Down Expand Up @@ -478,6 +488,7 @@ impl CommonMarkViewer {
cache,
&self.options,
text,
self.math_fn,
);
}
}
Expand Down Expand Up @@ -655,8 +666,6 @@ impl FencedCodeBlock {

elements::code_block(ui, max_width, &self.content, &mut layout);
});

elements::newline(ui);
}
}

Expand Down
Loading