1use std::sync::Once;
2use v8;
3
4#[derive(Debug)]
6pub enum Error {
7 JsInitError(String),
8 JsExecError(String),
9 JsValueError(String),
10}
11
12#[derive(Default)]
13pub struct Opts {
14 display_mode: Option<bool>,
15}
16
17impl Opts {
18 pub fn new() -> Self {
19 Default::default()
20 }
21
22 pub fn display_mode(mut self, value: bool) -> Self {
23 self.display_mode = Some(value);
24 self
25 }
26}
27
28static INIT: Once = Once::new();
29
30fn initialize_v8() {
31 INIT.call_once(|| {
32 let platform = v8::new_default_platform(0, false).make_shared();
33 v8::V8::initialize_platform(platform);
34 v8::V8::initialize();
35 });
36}
37
38trait IntoV8String {
40 fn to_v8_string<'s>(&self, scope: &mut v8::HandleScope<'s>) -> v8::Local<'s, v8::String>;
41}
42
43impl<T: AsRef<str>> IntoV8String for T {
44 fn to_v8_string<'s>(&self, scope: &mut v8::HandleScope<'s>) -> v8::Local<'s, v8::String> {
45 v8::String::new(scope, self.as_ref()).unwrap()
48 }
49}
50
51pub fn inject_katex<'a>(
52 context: v8::Local<'a, v8::Context>,
53 scope: &mut v8::ContextScope<'a, v8::HandleScope<'_>>,
54) -> Result<v8::Local<'a, v8::Object>, Error> {
55 let katex_src = include_str!("../vendor/katex.min.js").to_v8_string(scope);
58
59 v8::Script::compile(scope, katex_src, None)
60 .and_then(|script| script.run(scope))
61 .ok_or_else(|| Error::JsInitError("Failed to compile KaTeX script".to_string()))?;
62
63 let global = context.global(scope);
64 let katex_string = "katex".to_v8_string(scope);
65
66 let katex = global
67 .get(scope, katex_string.into())
68 .ok_or_else(|| Error::JsValueError("Failed to get KaTeX object".to_string()))?;
69 let katex_obj = v8::Local::<v8::Object>::try_from(katex);
70
71 match katex_obj {
72 Ok(obj) => Ok(obj),
73 Err(e) => Err(Error::JsValueError(format!(
74 "Failed to acquire KaTeX object: {}",
75 e
76 ))),
77 }
78}
79
80pub fn render(input: &str, opts: &Opts) -> Result<String, Error> {
81 initialize_v8();
82
83 let isolate = &mut v8::Isolate::new(Default::default());
84 let handle_scope = &mut v8::HandleScope::new(isolate);
85 let context = v8::Context::new(handle_scope, Default::default());
86 let scope = &mut v8::ContextScope::new(handle_scope, context);
87
88 let katex = inject_katex(context, scope)?;
89
90 let input = v8::String::new(scope, input).unwrap();
91 let opts_obj = v8::Object::new(scope);
92 if let Some(display_mode) = opts.display_mode {
93 let key = "displayMode".to_v8_string(scope);
94 let value = v8::Boolean::new(scope, display_mode);
95 opts_obj.set(scope, key.into(), value.into()).unwrap();
96 }
97
98 let render_to_string = "renderToString".to_v8_string(scope);
99
100 let render_func =
101 v8::Local::<v8::Function>::try_from(katex.get(scope, render_to_string.into()).unwrap())
102 .unwrap();
103
104 let args = &[input.into(), opts_obj.into()];
105 let result = render_func.call(scope, katex.into(), args).unwrap();
106
107 let result = result.to_string(scope).unwrap();
108 Ok(result.to_rust_string_lossy(scope))
109}