Skip to content

Commit

Permalink
feat: add image_resolver to wasm32 and more tweaks
Browse files Browse the repository at this point in the history
  • Loading branch information
zimond committed May 15, 2022
1 parent 0a8328a commit 1dea149
Show file tree
Hide file tree
Showing 9 changed files with 294 additions and 144 deletions.
106 changes: 67 additions & 39 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -5,59 +5,87 @@ license = "MPL-2.0"
name = "resvg-js"
version = "1.0.0"

[lib]
crate-type = ["cdylib"]
[build-dependencies]
napi-build = "2"

[dependencies]
env_logger = "0.9.0"
fontdb = "0.9.0"
futures = "0.3.21"
infer = "0.8.0"
log = "0.4"
resvg = { version = "0.22.0", default-features = false, features = [
"filter",
"dump-svg",
] }

serde = { version = "1", features = ["derive"] }
pathfinder_geometry = "0.5.1"
png = "0.17.3"
serde_json = "1"
svgtypes = "0.8.0"
tiny-skia = "0.6.3"
thiserror = "1.0.30"
png = "0.17.3"
pathfinder_geometry = "0.5.1"
pathfinder_content = { version = "0.5.0", default-features = false }
pathfinder_simd = { version = "0.5.1", features = ["pf-no-simd"] }
futures = "0.3.21"
tiny-skia = "0.6.3"

[target.'cfg(all(not(all(target_os = "linux", target_arch = "aarch64", target_env = "musl")), not(all(target_os = "windows", target_arch = "aarch64")), not(target_arch = "wasm32")))'.dependencies]
mimalloc-rust = { version = "0.1" }
[dependencies.pathfinder_content]
default-features = false
version = "0.5.0"

[target.'cfg(target_arch = "wasm32")'.dependencies]
wasm-bindgen = "0.2.80"
js-sys = "0.3.57"
usvg = { version = "0.22.0", default-features = false, features = [
"export",
"filter",
] }
[dependencies.pathfinder_simd]
features = ["pf-no-simd"]
version = "0.5.1"

[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
napi = { version = "2.4.1", features = ["serde-json", "async"] }
napi-derive = "2.4.0"
usvg = { version = "0.22.0", default-features = false, features = [
"export",
[dependencies.resvg]
default-features = false
features = [
"filter",
"text",
] }
"dump-svg",
]
version = "0.22.0"

[build-dependencies]
napi-build = "2"
[dependencies.serde]
features = ["derive"]
version = "1"

[lib]
crate-type = ["cdylib"]

[patch.crates-io.resvg]
git = "https://github.com/zimond/resvg"
rev = "cab0b15"

[patch.crates-io.usvg]
git = "https://github.com/zimond/resvg"
rev = "cab0b15"

[profile.release]
lto = true # Enable Link Time Optimization
opt-level = 3
# Setting this to 1 may improve the performance of generated code, but may be slower to compile.
# https://doc.rust-lang.org/rustc/codegen-options/index.html#codegen-units
codegen-units = 1
lto = true
opt-level = 3

[target."cfg(all(not(all(target_os = \"linux\", target_arch = \"aarch64\", target_env = \"musl\")), not(all(target_os = \"windows\", target_arch = \"aarch64\")), not(target_arch = \"wasm32\")))".dependencies.mimalloc-rust]
version = "0.1"

[patch.crates-io]
resvg = { git = "https://github.com/zimond/resvg", rev = "cab0b15" }
usvg = { git = "https://github.com/zimond/resvg", rev = "cab0b15" }
[target."cfg(not(target_arch = \"wasm32\"))".dependencies]
napi-derive = "2.4.0"

[target."cfg(not(target_arch = \"wasm32\"))".dependencies.napi]
features = [
"serde-json",
"async",
]
version = "2.4.1"

[target."cfg(not(target_arch = \"wasm32\"))".dependencies.usvg]
default-features = false
features = [
"export",
"filter",
"text",
]
version = "0.22.0"
[target."cfg(target_arch = \"wasm32\")".dependencies]
js-sys = "0.3.57"
wasm-bindgen = "0.2.80"

[target."cfg(target_arch = \"wasm32\")".dependencies.usvg]
default-features = false
features = [
"export",
"filter",
]
version = "0.22.0"
6 changes: 4 additions & 2 deletions src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ pub enum Error {
ZeroSized,
#[error("Input must be string or Uint8Array")]
InvalidInput,
#[error("Unrecognized image buffer")]
UnrecognizedBuffer,
}

#[cfg(not(target_arch = "wasm32"))]
Expand All @@ -24,8 +26,8 @@ impl From<Error> for napi::Error {
}

#[cfg(target_arch = "wasm32")]
impl From<Error> for wasm_bindgen::JsValue {
impl From<Error> for js_sys::Error {
fn from(e: Error) -> Self {
js_sys::TypeError::new(&format!("{}", e)).into()
js_sys::Error::new(&format!("{}", e))
}
}
147 changes: 79 additions & 68 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ use pathfinder_geometry::vector::Vector2F;
use napi_derive::napi;
use options::JsOptions;
use tiny_skia::Pixmap;
use usvg::{ImageHrefResolver, ImageKind, NodeKind, OptionsRef};
use usvg::{ImageKind, NodeKind};
#[cfg(target_arch = "wasm32")]
use wasm_bindgen::{
prelude::{wasm_bindgen, JsValue},
Expand Down Expand Up @@ -99,7 +99,7 @@ impl RenderedImage {
#[cfg(target_arch = "wasm32")]
#[wasm_bindgen(js_name = asPng)]
/// Write the image data to Uint8Array
pub fn as_png(&self) -> Result<js_sys::Uint8Array, JsValue> {
pub fn as_png(&self) -> Result<js_sys::Uint8Array, js_sys::Error> {
let buffer = self.pix.encode_png().map_err(Error::from)?;
Ok(buffer.as_slice().into())
}
Expand Down Expand Up @@ -136,15 +136,7 @@ impl Resvg {
.try_init();

let mut opts = js_options.to_usvg_options();
opts.image_href_resolver = ImageHrefResolver::default();
opts.image_href_resolver.resolve_string = Box::new(move |data: &str, opts: &OptionsRef| {
if data.starts_with("https://") || data.starts_with("http://") {
Some(ImageKind::RAW(0, 0, data.as_bytes().to_vec()))
} else {
let resolver = ImageHrefResolver::default().resolve_string;
(resolver)(data, opts)
}
});
options::tweak_usvg_options(&mut opts);
let opts_ref = opts.to_ref();
// Parse the SVG string into a tree.
let tree = match svg {
Expand Down Expand Up @@ -200,69 +192,23 @@ impl Resvg {

#[napi]
pub fn images_to_resolve(&self) -> Result<Vec<String>, NapiError> {
let mut data = vec![];
for mut node in self.tree.root().descendants() {
if let NodeKind::Image(i) = &mut *node.borrow_mut() {
if let ImageKind::RAW(_, _, buffer) = &mut i.kind {
let s = String::from_utf8(buffer.clone()).map_err(Error::from)?;
data.push(s);
}
}
}
Ok(data)
Ok(self.images_to_resolve_inner()?)
}

#[napi]
pub fn resolve_image(&self, href: String, mime: String, buffer: Buffer) -> Result<(), NapiError> {
let resolver = usvg::ImageHrefResolver::default_data_resolver();
let options = self.js_options.to_usvg_options();
for mut node in self.tree.root().descendants() {
if let NodeKind::Image(i) = &mut *node.borrow_mut() {
let matched = if let ImageKind::RAW(_, _, data) = &mut i.kind {
let s = String::from_utf8(data.clone()).map_err(Error::from)?;
s == href
} else {
false
};
if matched {
let data = (resolver)(&mime, Arc::new(buffer.to_vec()), &options.to_ref());
if let Some(kind) = data {
i.kind = kind;
}
}
}
}
Ok(())
pub fn resolve_image(&self, href: String, buffer: Buffer) -> Result<(), NapiError> {
let buffer = buffer.to_vec();
Ok(self.resolve_image_inner(href, buffer)?)
}
}

#[cfg_attr(target_arch = "wasm32", wasm_bindgen)]
#[cfg_attr(not(target_arch = "wasm32"), napi)]
impl Resvg {
#[cfg(target_arch = "wasm32")]
#[wasm_bindgen(getter)]
/// Get the SVG width
pub fn width(&self) -> f64 {
self.tree.svg_node().size.width().round()
}

#[cfg(target_arch = "wasm32")]
#[wasm_bindgen(getter)]
/// Get the SVG height
pub fn height(&self) -> f64 {
self.tree.svg_node().size.height().round()
}

#[cfg(not(target_arch = "wasm32"))]
#[napi(getter)]
/// Get the SVG width
pub fn width(&self) -> f64 {
self.tree.svg_node().size.width().round()
}

#[cfg(not(target_arch = "wasm32"))]
#[napi(getter)]
/// Get the SVG height
#[napi(getter)]
pub fn height(&self) -> f64 {
self.tree.svg_node().size.height().round()
}
Expand All @@ -272,12 +218,13 @@ impl Resvg {
#[wasm_bindgen]
impl Resvg {
#[wasm_bindgen(constructor)]
pub fn new(svg: IStringOrBuffer, options: Option<String>) -> Result<Resvg, JsValue> {
pub fn new(svg: IStringOrBuffer, options: Option<String>) -> Result<Resvg, js_sys::Error> {
let js_options: JsOptions = options
.and_then(|o| serde_json::from_str(o.as_str()).ok())
.unwrap_or_default();

let opts = js_options.to_usvg_options();
let mut opts = js_options.to_usvg_options();
options::tweak_usvg_options(&mut opts);
let opts_ref = opts.to_ref();
let tree = if js_sys::Uint8Array::instanceof(&svg) {
let uintarray = js_sys::Uint8Array::unchecked_from_js_ref(&svg);
Expand All @@ -291,22 +238,33 @@ impl Resvg {
Ok(Resvg { tree, js_options })
}

#[wasm_bindgen]
/// Get the SVG width
#[wasm_bindgen(getter)]
pub fn width(&self) -> f64 {
self.tree.svg_node().size.width().round()
}

/// Get the SVG height
#[wasm_bindgen(getter)]
pub fn height(&self) -> f64 {
self.tree.svg_node().size.height().round()
}

/// Renders an SVG in Wasm
pub fn render(&self) -> Result<RenderedImage, JsValue> {
pub fn render(&self) -> Result<RenderedImage, js_sys::Error> {
Ok(self.render_inner()?)
}

#[wasm_bindgen(js_name = toString)]
/// Output usvg-simplified SVG string
#[wasm_bindgen(js_name = toString)]
pub fn to_string(&self) -> String {
self.tree.to_string(&usvg::XmlOptions::default())
}

#[wasm_bindgen(js_name = innerBBox)]
/// Calculate a maximum bounding box of all visible elements in this SVG.
///
/// Note: path bounding box are approx values.
#[wasm_bindgen(js_name = innerBBox)]
pub fn inner_bbox(&self) -> BBox {
let rect = self.tree.svg_node().view_box.rect;
let rect = points_to_rect(
Expand All @@ -333,6 +291,21 @@ impl Resvg {
height: (v.max_y().ceil() - v.min_y().floor()) as f64,
}
}

pub fn images_to_resolve(&self) -> Result<js_sys::Array, js_sys::Error> {
let images = self.images_to_resolve_inner()?;
let result = js_sys::Array::from_iter(images.into_iter().map(|s| JsValue::from(s)));
Ok(result)
}

pub fn resolve_image(
&self,
href: String,
buffer: js_sys::Uint8Array,
) -> Result<(), js_sys::Error> {
let buffer = buffer.to_vec();
Ok(self.resolve_image_inner(href, buffer)?)
}
}

impl Resvg {
Expand Down Expand Up @@ -527,6 +500,44 @@ impl Resvg {

Ok(RenderedImage { pix: pixmap })
}

fn images_to_resolve_inner(&self) -> Result<Vec<String>, Error> {
let mut data = vec![];
for mut node in self.tree.root().descendants() {
if let NodeKind::Image(i) = &mut *node.borrow_mut() {
if let ImageKind::RAW(_, _, buffer) = &mut i.kind {
let s = String::from_utf8(buffer.clone())?;
data.push(s);
}
}
}
Ok(data)
}

fn resolve_image_inner(&self, href: String, buffer: Vec<u8>) -> Result<(), Error> {
let resolver = usvg::ImageHrefResolver::default_data_resolver();
let options = self.js_options.to_usvg_options();
let mime = infer::get(&buffer)
.ok_or_else(|| Error::UnrecognizedBuffer)?
.to_string();
for mut node in self.tree.root().descendants() {
if let NodeKind::Image(i) = &mut *node.borrow_mut() {
let matched = if let ImageKind::RAW(_, _, data) = &mut i.kind {
let s = String::from_utf8(data.clone()).map_err(Error::from)?;
s == href
} else {
false
};
if matched {
let data = (resolver)(&mime, Arc::new(buffer.clone()), &options.to_ref());
if let Some(kind) = data {
i.kind = kind;
}
}
}
}
Ok(())
}
}

#[cfg(not(target_arch = "wasm32"))]
Expand Down
Loading

0 comments on commit 1dea149

Please sign in to comment.