1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
#[cfg(feature = "browser")]
pub mod browser;
#[cfg(feature = "node")]
pub mod node;
pub enum Renderer {
#[cfg(feature = "node")]
Node(node::Node),
#[cfg(feature = "browser")]
Browser(browser::Browser),
}
/// The output of a renderer, this is the final [MathJax](https://www.mathjax.org/) image.
pub struct Render {
/// The actual SVG source that MathJax outputs
source: String,
/// Whether the text/line color has been set
color_set: bool,
}
impl Render {
fn new(source: String) -> Self {
Render {
source,
color_set: false,
}
}
/// Sets the text/line color of the rendered image.
///
/// Will return `true` if the operation was successful.
/// This function can only be called once, subsequent calls will do nothing and return `false`.
///
/// Accepts any valid CSS [color](https://developer.mozilla.org/en-US/docs/Web/CSS/color_value) value.
pub fn set_color(&mut self, color: &str) -> bool {
if !self.color_set {
self.source = self.source.replace("currentColor", color);
self.color_set = true;
true
} else {
false
}
}
/// Returns the underlying SVG string. This is an `<svg>...</svg>` element.
pub fn as_raw(&self) -> &str {
&self.source
}
/// Converts the render into the underlying SVG string. This is an `<svg>...</svg>` element.
pub fn into_raw(self) -> String {
self.source
}
/// Converts the render into a [`resvg::Tree`].
#[cfg(feature = "image")]
pub fn into_svg(self) -> Result<resvg::Tree, resvg::usvg::Error> {
use resvg::usvg::{TreeParsing, TreeTextToPath};
let opt = resvg::usvg::Options::default();
let mut fontdb = resvg::usvg::fontdb::Database::new();
fontdb.load_system_fonts();
let mut tree = resvg::usvg::Tree::from_data(self.source.as_bytes(), &opt)?;
tree.convert_text(&fontdb);
let rtree = resvg::Tree::from_usvg(&tree);
Ok(rtree)
}
/// Converts the render into an [`image::DynamicImage`].
///
/// The `scaling_factor` parameter determines how much to scale up the output render (since the underlying SVG is a vector).
/// In general, a larger scaling factor = a higher resolution image.
/// You'll probably need some trial and error to figure out what works best for your use case, I'd recommend starting at ~`10.0`.
#[cfg(feature = "image")]
pub fn into_image(self, scaling_factor: f32) -> Result<image::DynamicImage, image::ImageError> {
let rtree = self.into_svg().map_err(|_| {
image::ImageError::Unsupported(image::error::UnsupportedError::from_format_and_kind(
image::error::ImageFormatHint::Unknown,
image::error::UnsupportedErrorKind::Format(image::error::ImageFormatHint::Unknown),
))
})?;
let pixmap_size = resvg::IntSize::from_usvg(rtree.size)
.scale_by(scaling_factor.into())
// unwrap is safe as this can only error if either width=0 or height=0, and from_usvg has a minimum value of `1`
.unwrap();
// unwrap is safe as this can only error if either pixmap_size.width=0 or pixmap_size.height=0,
// and since from_usvg must be >=1, the post-scale size must also be >=0
let mut pixmap =
resvg::tiny_skia::Pixmap::new(pixmap_size.width(), pixmap_size.height()).unwrap();
rtree.render(
resvg::tiny_skia::Transform::from_scale(scaling_factor, scaling_factor),
&mut pixmap.as_mut(),
);
image::load_from_memory_with_format(&pixmap.encode_png().unwrap(), image::ImageFormat::Png)
}
}