diff options
author | Reizner Evgeniy <razrfalcon@gmail.com> | 2018-03-10 19:34:18 +0200 |
---|---|---|
committer | Reizner Evgeniy <razrfalcon@gmail.com> | 2018-03-10 19:34:18 +0200 |
commit | 6578eabb83c198a242b9af64f3e19710bffb6ddd (patch) | |
tree | 8c1c1aea19b2aff066b8554e152c9812ecd01bb7 /capi | |
parent | 647163277742df2403b689d1faf12a1564286aa7 (diff) |
C-API rewrite.
Diffstat (limited to 'capi')
-rw-r--r-- | capi/include/resvg.h | 102 | ||||
-rw-r--r-- | capi/src/lib.rs | 465 |
2 files changed, 509 insertions, 58 deletions
diff --git a/capi/include/resvg.h b/capi/include/resvg.h index 8bfd4ac..e01c30b 100644 --- a/capi/include/resvg.h +++ b/capi/include/resvg.h @@ -5,14 +5,43 @@ #ifndef RESVG_H #define RESVG_H +#include <stdbool.h> + #ifdef RESVG_CAIRO_BACKEND #include <cairo.h> #endif -struct resvg_render_tree; +typedef struct resvg_handle resvg_handle; typedef struct resvg_render_tree resvg_render_tree; +typedef struct resvg_color { + unsigned char r; + unsigned char g; + unsigned char b; +} resvg_color; + +typedef enum resvg_fit_to_type { + RESVG_FIT_TO_ORIGINAL, + RESVG_FIT_TO_WIDTH, + RESVG_FIT_TO_HEIGHT, + RESVG_FIT_TO_ZOOM, +} resvg_fit_to_type; + +typedef struct resvg_fit_to { + resvg_fit_to_type type; + float value; +} resvg_fit_to; + +typedef struct resvg_options { + const char *path; + double dpi; + resvg_fit_to fit_to; + bool draw_background; + resvg_color background; + bool keep_named_groups; +} resvg_options; + typedef struct resvg_rect { double x; double y; @@ -20,9 +49,31 @@ typedef struct resvg_rect { double height; } resvg_rect; +typedef struct resvg_transform { + double a; + double b; + double c; + double d; + double e; + double f; +} resvg_transform; + + +resvg_handle* resvg_init(); +void resvg_destroy(resvg_handle *handle); void resvg_init_log(); +void resvg_init_options(resvg_options *opt) +{ + opt->path = NULL; + opt->dpi = 96; + opt->fit_to.type = RESVG_FIT_TO_ORIGINAL; + opt->fit_to.value = 0; + opt->draw_background = false; + opt->keep_named_groups = false; +} + /** * @brief Creates <b>resvg_render_tree</b> from file. * @@ -34,7 +85,7 @@ void resvg_init_log(); * @return Parsed render tree. NULL on error. Should be destroyed via resvg_rtree_destroy. */ resvg_render_tree *resvg_parse_rtree_from_file(const char *file_path, - double dpi, + const resvg_options *opt, char **error); /** @@ -46,28 +97,67 @@ resvg_render_tree *resvg_parse_rtree_from_file(const char *file_path, * @return Parsed render tree. NULL on error. Should be destroyed via resvg_rtree_destroy. */ resvg_render_tree *resvg_parse_rtree_from_data(const char *text, - double dpi, + const resvg_options *opt, char **error); -void resvg_get_image_size(resvg_render_tree *rtree, +void resvg_get_image_size(const resvg_render_tree *rtree, double *width, double *height); +bool resvg_node_exists(const resvg_render_tree *rtree, + const char *id); + +bool resvg_get_node_transform(const resvg_render_tree *rtree, + const char *id, + resvg_transform *ts); + void resvg_rtree_destroy(resvg_render_tree *rtree); void resvg_error_msg_destroy(char *msg); #ifdef RESVG_CAIRO_BACKEND -void resvg_cairo_render_to_canvas(resvg_render_tree *rtree, +bool resvg_cairo_get_node_bbox(const resvg_render_tree *rtree, + const resvg_options *opt, + const char *id, + resvg_rect *bbox); + +bool resvg_cairo_render_to_image(const resvg_render_tree *rtree, + const resvg_options *opt, + const char *file_path); + +void resvg_cairo_render_to_canvas(const resvg_render_tree *rtree, + const resvg_options *opt, resvg_rect view, cairo_t *cr); + +void resvg_cairo_render_to_canvas_by_id(const resvg_render_tree *rtree, + const resvg_options *opt, + resvg_rect view, + const char *id, + void *painter); #endif #ifdef RESVG_QT_BACKEND -void resvg_qt_render_to_canvas(resvg_render_tree *rtree, +bool resvg_qt_get_node_bbox(const resvg_render_tree *rtree, + const resvg_options *opt, + const char *id, + resvg_rect *bbox); + +bool resvg_qt_render_to_image(const resvg_render_tree *rtree, + const resvg_options *opt, + const char *file_path); + +void resvg_qt_render_to_canvas(const resvg_render_tree *rtree, + const resvg_options *opt, resvg_rect view, void *painter); + +void resvg_qt_render_to_canvas_by_id(const resvg_render_tree *rtree, + const resvg_options *opt, + resvg_rect view, + const char *id, + void *painter); #endif #endif // RESVG_H diff --git a/capi/src/lib.rs b/capi/src/lib.rs index d062460..ec50e6c 100644 --- a/capi/src/lib.rs +++ b/capi/src/lib.rs @@ -2,8 +2,10 @@ // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at http://mozilla.org/MPL/2.0/. +#![allow(non_camel_case_types)] + extern crate resvg; -extern crate log; +#[macro_use] extern crate log; extern crate fern; #[cfg(feature = "cairo-backend")] @@ -12,6 +14,7 @@ extern crate glib; extern crate cairo_sys; use std::fmt; +use std::path; use std::ptr; use std::ffi::{ CStr, @@ -30,17 +33,62 @@ use resvg::cairo; use resvg::RectExt; use resvg::tree::prelude::*; + +#[repr(C)] +pub struct resvg_options { + pub path: *const c_char, + pub dpi: f64, + pub fit_to: resvg_fit_to, + pub draw_background: bool, + pub background: resvg_color, + pub keep_named_groups: bool, +} + #[repr(C)] -pub struct Rect { - x: f64, - y: f64, - width: f64, - height: f64, +pub struct resvg_color { + pub r: u8, + pub g: u8, + pub b: u8, +} + +#[repr(C)] +pub enum resvg_fit_to_type { + RESVG_FIT_TO_ORIGINAL, + RESVG_FIT_TO_WIDTH, + RESVG_FIT_TO_HEIGHT, + RESVG_FIT_TO_ZOOM, +} + +#[repr(C)] +pub struct resvg_fit_to { + kind: resvg_fit_to_type, + value: f32, +} + +#[repr(C)] +pub struct resvg_rect { + pub x: f64, + pub y: f64, + pub width: f64, + pub height: f64, +} + +#[repr(C)] +pub struct resvg_transform { + pub a: f64, + pub b: f64, + pub c: f64, + pub d: f64, + pub e: f64, + pub f: f64, } #[repr(C)] pub struct resvg_render_tree(resvg::tree::RenderTree); +#[repr(C)] +pub struct resvg_handle(resvg::InitObject); + macro_rules! on_err { ($err:expr, $msg:expr) => ({ let c_str = CString::new($msg).unwrap(); @@ -49,20 +97,18 @@ macro_rules! on_err { }) } -macro_rules! from_raw_str { - ($raw_str:expr, $err:expr, $msg:expr) => ({ - let rstr = unsafe { - assert!(!$raw_str.is_null()); - CStr::from_ptr($raw_str) - }; - - let rstr = match rstr.to_str() { - Ok(rstr) => rstr, - Err(_) => on_err!($err, $msg), - }; +#[no_mangle] +pub extern fn resvg_init() -> *mut resvg_handle { + let handle = Box::new(resvg_handle(resvg::init())); + Box::into_raw(handle) +} - rstr - }) +#[no_mangle] +pub extern fn resvg_destroy(handle: *mut resvg_handle) { + unsafe { + assert!(!handle.is_null()); + Box::from_raw(handle) + }; } #[no_mangle] @@ -92,24 +138,67 @@ fn log_format(out: fern::FormatCallback, message: &fmt::Arguments, record: &log: )) } +fn to_native_opt(opt: &resvg_options) -> resvg::Options { + let mut path: Option<path::PathBuf> = None; + + if !opt.path.is_null() { + if let Some(p) = cstr_to_str(opt.path) { + if !p.is_empty() { + path = Some(p.into()); + } + } + }; + + let fit_to = match opt.fit_to.kind { + resvg_fit_to_type::RESVG_FIT_TO_ORIGINAL => { + resvg::FitTo::Original + } + resvg_fit_to_type::RESVG_FIT_TO_WIDTH => { + assert!(opt.fit_to.value > 0.0); + resvg::FitTo::Width(opt.fit_to.value as u32) + } + resvg_fit_to_type::RESVG_FIT_TO_HEIGHT => { + assert!(opt.fit_to.value > 0.0); + resvg::FitTo::Height(opt.fit_to.value as u32) + } + resvg_fit_to_type::RESVG_FIT_TO_ZOOM => { + assert!(opt.fit_to.value > 0.0); + resvg::FitTo::Zoom(opt.fit_to.value) + } + }; + + let background = if opt.draw_background { + Some(resvg::tree::Color::new( + opt.background.r, + opt.background.g, + opt.background.b, + )) + } else { + None + }; + + resvg::Options { + path, + dpi: opt.dpi, + fit_to, + background, + keep_named_groups: opt.keep_named_groups, + } +} + #[no_mangle] pub extern fn resvg_parse_rtree_from_file( file_path: *const c_char, - dpi: f64, + opt: *const resvg_options, error: *mut *mut c_char, ) -> *mut resvg_render_tree { - let file_path = from_raw_str!( - file_path, - error, - "Error: the file path is not an UTF-8 string." - ); - - let opt = resvg::Options { - path: Some(file_path.into()), - dpi: dpi, - .. resvg::Options::default() + let file_path = match cstr_to_str(file_path) { + Some(v) => v, + None => on_err!(error, "Error: file path is not an UTF-8 string."), }; + let opt = to_native_opt(unsafe { &*opt }); + let rtree = match resvg::parse_rtree_from_file(file_path, &opt) { Ok(rtree) => rtree, Err(e) => on_err!(error, e.to_string()), @@ -122,20 +211,16 @@ pub extern fn resvg_parse_rtree_from_file( #[no_mangle] pub extern fn resvg_parse_rtree_from_data( text: *const c_char, - dpi: f64, + opt: *const resvg_options, error: *mut *mut c_char, ) -> *mut resvg_render_tree { - let text = from_raw_str!( - text, - error, - "Error: the SVG data is not an UTF-8 string." - ); - - let opt = resvg::Options { - dpi: dpi, - .. resvg::Options::default() + let text = match cstr_to_str(text) { + Some(v) => v, + None => on_err!(error, "Error: SVG data is not an UTF-8 string."), }; + let opt = to_native_opt(unsafe { &*opt }); + let rtree = match resvg::parse_rtree_from_data(text, &opt) { Ok(rtree) => rtree, Err(e) => on_err!(error, e.to_string()), @@ -161,37 +246,128 @@ pub extern fn resvg_rtree_destroy(rtree: *mut resvg_render_tree) { }; } +fn cstr_to_str(text: *const c_char) -> Option<&'static str> { + let text = unsafe { + assert!(!text.is_null()); + CStr::from_ptr(text) + }; + + text.to_str().ok() +} + +#[cfg(feature = "qt-backend")] +#[no_mangle] +pub extern fn resvg_qt_render_to_image( + rtree: *const resvg_render_tree, + opt: *const resvg_options, + file_path: *const c_char, +) -> bool { + let backend = Box::new(resvg::render_qt::Backend); + render_to_image(rtree, opt, file_path, backend) +} + +#[cfg(feature = "cairo-backend")] +#[no_mangle] +pub extern fn resvg_cairo_render_to_image( + rtree: *const resvg_render_tree, + opt: *const resvg_options, + file_path: *const c_char, +) -> bool { + let backend = Box::new(resvg::render_cairo::Backend); + render_to_image(rtree, opt, file_path, backend) +} + +fn render_to_image( + rtree: *const resvg_render_tree, + opt: *const resvg_options, + file_path: *const c_char, + backend: Box<resvg::Render>, +) -> bool { + let rtree = unsafe { + assert!(!rtree.is_null()); + &*rtree + }; + + let file_path = match cstr_to_str(file_path) { + Some(v) => v, + None => return false, + }; + + let opt = to_native_opt(unsafe { &*opt }); + + let img = backend.render_to_image(&rtree.0, &opt); + let img = match img { + Ok(img) => img, + Err(e) => { + warn!("{}", e); + return false; + } + }; + + img.save(path::Path::new(file_path)) +} + #[cfg(feature = "qt-backend")] #[no_mangle] pub extern fn resvg_qt_render_to_canvas( - rtree: *mut resvg_render_tree, - view: Rect, + rtree: *const resvg_render_tree, + opt: *const resvg_options, + view: resvg_rect, painter: *mut qt::qtc_qpainter, ) { let rtree = unsafe { assert!(!rtree.is_null()); - &mut *rtree + &*rtree }; let painter = unsafe { qt::Painter::from_raw(painter) }; let rect = resvg::Rect::from_xywh(view.x, view.y, view.width, view.height); - // TODO: to a proper options - let opt = resvg::Options::default(); + let opt = to_native_opt(unsafe { &*opt }); resvg::render_qt::render_to_canvas(&rtree.0, &opt, rect, &painter); } +#[cfg(feature = "qt-backend")] +#[no_mangle] +pub extern fn resvg_qt_render_to_canvas_by_id( + rtree: *const resvg_render_tree, + opt: *const resvg_options, + view: resvg_rect, + id: *const c_char, + painter: *mut qt::qtc_qpainter, +) { + let rtree = unsafe { + assert!(!rtree.is_null()); + &*rtree + }; + + let painter = unsafe { qt::Painter::from_raw(painter) }; + let rect = resvg::Rect::from_xywh(view.x, view.y, view.width, view.height); + + let opt = to_native_opt(unsafe { &*opt }); + + let id = match cstr_to_str(id) { + Some(v) => v, + None => return, + }; + + if let Some(node) = node_by_id(&rtree.0, id) { + resvg::render_qt::render_node_to_canvas(node, &opt, rect.to_screen_size(), &painter); + } +} + #[cfg(feature = "cairo-backend")] #[no_mangle] pub extern fn resvg_cairo_render_to_canvas( - rtree: *mut resvg_render_tree, - view: Rect, + rtree: *const resvg_render_tree, + opt: *const resvg_options, + view: resvg_rect, cr: *mut cairo_sys::cairo_t, ) { let rtree = unsafe { assert!(!rtree.is_null()); - &mut *rtree + &*rtree }; use glib::translate::FromGlibPtrNone; @@ -199,21 +375,51 @@ pub extern fn resvg_cairo_render_to_canvas( let cr = unsafe { cairo::Context::from_glib_none(cr) }; let rect = resvg::Rect::from_xywh(view.x, view.y, view.width, view.height); - // TODO: to a proper options - let opt = resvg::Options::default(); + let opt = to_native_opt(unsafe { &*opt }); resvg::render_cairo::render_to_canvas(&rtree.0, &opt, rect, &cr); } +#[cfg(feature = "cairo-backend")] +#[no_mangle] +pub extern fn resvg_cairo_render_to_canvas_by_id( + rtree: *const resvg_render_tree, + opt: *const resvg_options, + view: resvg_rect, + id: *const c_char, + cr: *mut cairo_sys::cairo_t, +) { + let rtree = unsafe { + assert!(!rtree.is_null()); + &*rtree + }; + + let id = match cstr_to_str(id) { + Some(v) => v, + None => return, + }; + + use glib::translate::FromGlibPtrNone; + + let cr = unsafe { cairo::Context::from_glib_none(cr) }; + let rect = resvg::Rect::from_xywh(view.x, view.y, view.width, view.height); + + let opt = to_native_opt(unsafe { &*opt }); + + if let Some(node) = node_by_id(&rtree.0, id) { + resvg::render_cairo::render_node_to_canvas(node, &opt, rect.to_screen_size(), &cr); + } +} + #[no_mangle] pub extern fn resvg_get_image_size( - rtree: *mut resvg_render_tree, + rtree: *const resvg_render_tree, width: *mut f64, height: *mut f64, ) { let rtree = unsafe { assert!(!rtree.is_null()); - &mut *rtree + &*rtree }; let size = rtree.0.svg_node().size; @@ -223,3 +429,158 @@ pub extern fn resvg_get_image_size( *height = size.height; } } + +#[cfg(feature = "qt-backend")] +#[no_mangle] +pub extern fn resvg_qt_get_node_bbox( + rtree: *const resvg_render_tree, + opt: *const resvg_options, + id: *const c_char, + bbox: *mut resvg_rect, +) -> bool { + let backend = Box::new(resvg::render_qt::Backend); + get_node_bbox(rtree, opt, id, bbox, backend) +} + +#[cfg(feature = "cairo-backend")] +#[no_mangle] +pub extern fn resvg_cairo_get_node_bbox( + rtree: *const resvg_render_tree, + opt: *const resvg_options, + id: *const c_char, + bbox: *mut resvg_rect, +) -> bool { + let backend = Box::new(resvg::render_cairo::Backend); + get_node_bbox(rtree, opt, id, bbox, backend) +} + +fn get_node_bbox( + rtree: *const resvg_render_tree, + opt: *const resvg_options, + id: *const c_char, + bbox: *mut resvg_rect, + backend: Box<resvg::Render>, +) -> bool { + let id = match cstr_to_str(id) { + Some(v) => v, + None => { + warn!("Provided ID is no an UTF-8 string."); + return false; + } + }; + + if id.is_empty() { + warn!("Node ID must not be empty."); + return false; + } + + let rtree = unsafe { + assert!(!rtree.is_null()); + &*rtree + }; + + + let opt = to_native_opt(unsafe { &*opt }); + + match node_by_id(&rtree.0, id) { + Some(node) => { + if let Some(r) = backend.calc_node_bbox(node, &opt) { + unsafe { + (*bbox).x = r.x(); + (*bbox).y = r.y(); + (*bbox).width = r.width(); + (*bbox).height = r.height(); + } + + true + } else { + false + } + } + None => { + warn!("No node with '{}' ID in the tree.", id); + false + } + } +} + +#[no_mangle] +pub extern fn resvg_node_exists( + rtree: *const resvg_render_tree, + id: *const c_char, +) -> bool { + let id = match cstr_to_str(id) { + Some(v) => v, + None => { + warn!("Provided ID is no an UTF-8 string."); + return false; + } + }; + + if id.is_empty() { + return false; + } + + let rtree = unsafe { + assert!(!rtree.is_null()); + &*rtree + }; + + node_by_id(&rtree.0, id).is_some() +} + +#[no_mangle] +pub extern fn resvg_get_node_transform( + rtree: *const resvg_render_tree, + id: *const c_char, + ts: *mut resvg_transform, +) -> bool { + let id = match cstr_to_str(id) { + Some(v) => v, + None => { + warn!("Provided ID is no an UTF-8 string."); + return false; + } + }; + + if id.is_empty() { + return false; + } + + let rtree = unsafe { + assert!(!rtree.is_null()); + &*rtree + }; + + if let Some(node) = node_by_id(&rtree.0, id) { + let abs_ts = resvg::utils::abs_transform(node); + + unsafe { + (*ts).a = abs_ts.a; + (*ts).b = abs_ts.b; + (*ts).c = abs_ts.c; + (*ts).d = abs_ts.d; + (*ts).e = abs_ts.e; + (*ts).f = abs_ts.f; + } + + return true; + } + + false +} + +fn node_by_id<'a>( + rtree: &'a resvg::tree::RenderTree, + id: &str +) -> Option<resvg::tree::NodeRef<'a>> { + for node in rtree.root().descendants() { + if !rtree.is_in_defs(node) { + if node.svg_id() == id { + return Some(node); + } + } + } + + None +} |