use std::io::Cursor;
use wasm_bindgen::prelude::*;
use crate::model::{Book, Format, TocEntry};
#[wasm_bindgen(start)]
pub fn init() {
#[cfg(feature = "wasm")]
console_error_panic_hook::set_once();
}
fn parse_format(name: &str) -> Result<Format, JsValue> {
match name.to_ascii_lowercase().as_str() {
"epub" => Ok(Format::Epub),
"azw3" => Ok(Format::Azw3),
"mobi" | "azw" => Ok(Format::Mobi),
"kfx" => Ok(Format::Kfx),
"markdown" | "md" => Ok(Format::Markdown),
_ => Err(JsValue::from_str(&format!("unknown format: {name}"))),
}
}
fn js_err(e: impl std::fmt::Display) -> JsValue {
JsValue::from_str(&e.to_string())
}
#[wasm_bindgen]
pub fn convert(data: &[u8], from: &str, to: &str) -> Result<Vec<u8>, JsValue> {
let from = parse_format(from)?;
let to = parse_format(to)?;
if !from.can_import() {
return Err(JsValue::from_str(&format!(
"format not supported as input: {from:?}"
)));
}
if !to.can_export() {
return Err(JsValue::from_str(&format!(
"format not supported as output: {to:?}"
)));
}
let book = Book::from_bytes(data, from).map_err(js_err)?;
let mut output = Cursor::new(Vec::new());
book.export(to, &mut output).map_err(js_err)?;
Ok(output.into_inner())
}
#[wasm_bindgen]
pub fn book_info(data: &[u8], from: &str) -> Result<JsValue, JsValue> {
let from = parse_format(from)?;
if !from.can_import() {
return Err(JsValue::from_str(&format!(
"format not supported as input: {from:?}"
)));
}
let book = Book::from_bytes(data, from).map_err(js_err)?;
let meta = book.metadata();
fn count_toc(entries: &[TocEntry]) -> usize {
entries.iter().map(|e| 1 + count_toc(&e.children)).sum()
}
fn json_string(s: &str) -> String {
let mut out = String::with_capacity(s.len() + 2);
out.push('"');
for c in s.chars() {
match c {
'"' => out.push_str("\\\""),
'\\' => out.push_str("\\\\"),
'\n' => out.push_str("\\n"),
'\r' => out.push_str("\\r"),
'\t' => out.push_str("\\t"),
c if (c as u32) < 0x20 => {
out.push_str(&format!("\\u{:04x}", c as u32));
}
c => out.push(c),
}
}
out.push('"');
out
}
let authors: Vec<String> = meta.authors.iter().map(|a| json_string(a)).collect();
let json = format!(
"{{\"title\":{},\"authors\":[{}],\"language\":{},\"chapters\":{},\"toc_entries\":{}}}",
json_string(&meta.title),
authors.join(","),
json_string(&meta.language),
book.spine().len(),
count_toc(book.toc()),
);
Ok(JsValue::from_str(&json))
}