iced_markdown_editor/extension.rs
1//! Custom rendering of fenced code blocks.
2//!
3//! A [`BlockRenderer`] draws fenced code blocks of certain languages in
4//! WYSIWYG mode instead of showing their code, e.g. formulas for `math`,
5//! diagrams for `mermaid` or a chart for `csv`. The document keeps the code
6//! block, so the Markdown does not change:
7//!
8//! * the block is drawn by the renderer while the caret is elsewhere,
9//! * clicking it or moving the caret into it shows the code for editing,
10//! * without the renderer (or when it returns `None`), it is a normal code
11//! block.
12//!
13//! Renderers draw with Iced's canvas [`Frame`], so they work on the desktop
14//! and in the browser.
15//!
16//! ```
17//! use iced_markdown_editor::extension::{
18//! BlockRenderer, Color, Frame, Point, Rectangle, RenderRequest, Size,
19//! };
20//!
21//! /// Draws ```` ```swatch ```` blocks (one `r g b` per line, 0-255) as colour swatches.
22//! struct Swatches;
23//!
24//! fn colors(code: &str) -> Vec<Color> {
25//! code.lines()
26//! .filter_map(|line| {
27//! let rgb: Vec<u8> = line.split_whitespace().filter_map(|n| n.parse().ok()).collect();
28//! (rgb.len() == 3).then(|| Color::from_rgb8(rgb[0], rgb[1], rgb[2]))
29//! })
30//! .collect()
31//! }
32//!
33//! impl BlockRenderer for Swatches {
34//! fn languages(&self) -> Vec<String> {
35//! vec!["swatch".into()]
36//! }
37//!
38//! fn measure(&self, request: &RenderRequest<'_>) -> Option<Size> {
39//! let count = colors(request.code).len();
40//! (count > 0).then(|| Size::new(count as f32 * 40.0 * request.zoom, 32.0 * request.zoom))
41//! }
42//!
43//! fn draw(&self, frame: &mut Frame, bounds: Rectangle, request: &RenderRequest<'_>) {
44//! let size = 40.0 * request.zoom;
45//! for (i, color) in colors(request.code).into_iter().enumerate() {
46//! let at = Point::new(bounds.x + i as f32 * size, bounds.y);
47//! frame.fill_rectangle(at, Size::new(size - 4.0, bounds.height), color);
48//! }
49//! }
50//! }
51//! ```
52
53use crate::style::EditorStyle;
54
55/// Types used by renderers (re-exports from `iced`).
56pub use iced::widget::canvas::Frame;
57pub use iced::{Color, Point, Rectangle, Size};
58
59/// One code block to measure or draw.
60#[derive(Debug, Clone, Copy)]
61pub struct RenderRequest<'a> {
62 /// The language: first word of the info string, as listed in
63 /// [`BlockRenderer::languages`].
64 pub language: &'a str,
65 /// The whole info string, e.g. `math display`.
66 pub info: &'a str,
67 /// The code. Lines are separated by `\n`.
68 pub code: &'a str,
69 /// Available width in pixels (already zoomed).
70 pub width: f32,
71 /// Height of a page's text area in pixels (already zoomed); infinite in
72 /// full-width mode. Taller blocks overflow the page.
73 pub max_height: f32,
74 /// Zoom factor (1.0 = 100 %). Scale fixed sizes with it.
75 pub zoom: f32,
76 /// The editor style: colours and font sizes.
77 pub style: &'a EditorStyle,
78}
79
80/// Draws fenced code blocks of some languages instead of their code.
81///
82/// [`measure`](Self::measure) is called on layout and cached per code,
83/// size and zoom; [`draw`](Self::draw) is called when the page is redrawn.
84pub trait BlockRenderer: Send + Sync + 'static {
85 /// Languages this renderer draws. They are also offered in the
86 /// toolbar's language list.
87 fn languages(&self) -> Vec<String>;
88
89 /// Size of the rendered block, at most [`RenderRequest::width`] wide.
90 /// `None` shows the code instead (e.g. for invalid input).
91 fn measure(&self, request: &RenderRequest<'_>) -> Option<Size>;
92
93 /// Draws the block into `bounds` (the size from [`measure`](Self::measure),
94 /// in document coordinates).
95 fn draw(&self, frame: &mut Frame, bounds: Rectangle, request: &RenderRequest<'_>);
96}