Skip to main content

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}