1#![allow(non_camel_case_types)]
7#![warn(missing_docs)]
8#![warn(missing_copy_implementations)]
9
10use std::ffi::CStr;
11use std::os::raw::c_char;
12use std::slice;
13
14use resvg::tiny_skia;
15use resvg::usvg;
16
17#[repr(C)]
19#[derive(Copy, Clone)]
20pub enum resvg_error {
21 OK = 0,
23 NOT_AN_UTF8_STR,
25 FILE_OPEN_FAILED,
27 MALFORMED_GZIP,
29 ELEMENTS_LIMIT_REACHED,
31 INVALID_SIZE,
37 PARSING_FAILED,
39}
40
41#[repr(C)]
43#[allow(missing_docs)]
44#[derive(Copy, Clone)]
45pub struct resvg_rect {
46 pub x: f32,
47 pub y: f32,
48 pub width: f32,
49 pub height: f32,
50}
51
52#[repr(C)]
54#[allow(missing_docs)]
55#[derive(Copy, Clone)]
56pub struct resvg_size {
57 pub width: f32,
58 pub height: f32,
59}
60
61#[repr(C)]
63#[allow(missing_docs)]
64#[derive(Copy, Clone)]
65pub struct resvg_transform {
66 pub a: f32,
67 pub b: f32,
68 pub c: f32,
69 pub d: f32,
70 pub e: f32,
71 pub f: f32,
72}
73
74impl resvg_transform {
75 #[inline]
76 fn to_tiny_skia(&self) -> tiny_skia::Transform {
77 tiny_skia::Transform::from_row(self.a, self.b, self.c, self.d, self.e, self.f)
78 }
79}
80
81#[no_mangle]
83pub extern "C" fn resvg_transform_identity() -> resvg_transform {
84 resvg_transform {
85 a: 1.0,
86 b: 0.0,
87 c: 0.0,
88 d: 1.0,
89 e: 0.0,
90 f: 0.0,
91 }
92}
93
94#[no_mangle]
102pub extern "C" fn resvg_init_log() {
103 if let Ok(()) = log::set_logger(&LOGGER) {
104 log::set_max_level(log::LevelFilter::Warn);
105 }
106}
107
108pub struct resvg_options {
113 options: usvg::Options<'static>,
114}
115
116#[no_mangle]
120pub extern "C" fn resvg_options_create() -> *mut resvg_options {
121 Box::into_raw(Box::new(resvg_options {
122 options: usvg::Options::default(),
123 }))
124}
125
126#[inline]
127fn cast_opt(opt: *mut resvg_options) -> &'static mut usvg::Options<'static> {
128 unsafe {
129 assert!(!opt.is_null());
130 &mut (*opt).options
131 }
132}
133
134#[no_mangle]
143pub extern "C" fn resvg_options_set_resources_dir(opt: *mut resvg_options, path: *const c_char) {
144 if path.is_null() {
145 cast_opt(opt).resources_dir = None;
146 } else {
147 cast_opt(opt).resources_dir = Some(cstr_to_str(path).unwrap().into());
148 }
149}
150
151#[no_mangle]
157pub extern "C" fn resvg_options_set_dpi(opt: *mut resvg_options, dpi: f32) {
158 cast_opt(opt).dpi = dpi;
159}
160
161#[no_mangle]
167pub extern "C" fn resvg_options_set_stylesheet(opt: *mut resvg_options, content: *const c_char) {
168 if content.is_null() {
169 cast_opt(opt).style_sheet = None;
170 } else {
171 cast_opt(opt).style_sheet = Some(cstr_to_str(content).unwrap().into());
172 }
173}
174
175#[no_mangle]
183pub extern "C" fn resvg_options_set_font_family(opt: *mut resvg_options, family: *const c_char) {
184 cast_opt(opt).font_family = cstr_to_str(family).unwrap().to_string();
185}
186
187#[no_mangle]
193pub extern "C" fn resvg_options_set_font_size(opt: *mut resvg_options, size: f32) {
194 cast_opt(opt).font_size = size;
195}
196
197#[no_mangle]
205#[allow(unused_variables)]
206pub extern "C" fn resvg_options_set_serif_family(opt: *mut resvg_options, family: *const c_char) {
207 #[cfg(feature = "text")]
208 {
209 cast_opt(opt)
210 .fontdb_mut()
211 .set_serif_family(cstr_to_str(family).unwrap().to_string());
212 }
213}
214
215#[no_mangle]
223#[allow(unused_variables)]
224pub extern "C" fn resvg_options_set_sans_serif_family(
225 opt: *mut resvg_options,
226 family: *const c_char,
227) {
228 #[cfg(feature = "text")]
229 {
230 cast_opt(opt)
231 .fontdb_mut()
232 .set_sans_serif_family(cstr_to_str(family).unwrap().to_string());
233 }
234}
235
236#[no_mangle]
244#[allow(unused_variables)]
245pub extern "C" fn resvg_options_set_cursive_family(opt: *mut resvg_options, family: *const c_char) {
246 #[cfg(feature = "text")]
247 {
248 cast_opt(opt)
249 .fontdb_mut()
250 .set_cursive_family(cstr_to_str(family).unwrap().to_string());
251 }
252}
253
254#[no_mangle]
262#[allow(unused_variables)]
263pub extern "C" fn resvg_options_set_fantasy_family(opt: *mut resvg_options, family: *const c_char) {
264 #[cfg(feature = "text")]
265 {
266 cast_opt(opt)
267 .fontdb_mut()
268 .set_fantasy_family(cstr_to_str(family).unwrap().to_string());
269 }
270}
271
272#[no_mangle]
280#[allow(unused_variables)]
281pub extern "C" fn resvg_options_set_monospace_family(
282 opt: *mut resvg_options,
283 family: *const c_char,
284) {
285 #[cfg(feature = "text")]
286 {
287 cast_opt(opt)
288 .fontdb_mut()
289 .set_monospace_family(cstr_to_str(family).unwrap().to_string());
290 }
291}
292
293#[no_mangle]
303pub extern "C" fn resvg_options_set_languages(opt: *mut resvg_options, languages: *const c_char) {
304 if languages.is_null() {
305 cast_opt(opt).languages = Vec::new();
306 return;
307 }
308
309 let languages_str = match cstr_to_str(languages) {
310 Some(v) => v,
311 None => return,
312 };
313
314 let mut languages = Vec::new();
315 for lang in languages_str.split(',') {
316 languages.push(lang.trim().to_string());
317 }
318
319 cast_opt(opt).languages = languages;
320}
321
322#[repr(C)]
324#[allow(missing_docs)]
325#[derive(Copy, Clone)]
326pub enum resvg_shape_rendering {
327 OPTIMIZE_SPEED,
328 CRISP_EDGES,
329 GEOMETRIC_PRECISION,
330}
331
332#[no_mangle]
338pub extern "C" fn resvg_options_set_shape_rendering_mode(
339 opt: *mut resvg_options,
340 mode: resvg_shape_rendering,
341) {
342 cast_opt(opt).shape_rendering = match mode as i32 {
343 0 => usvg::ShapeRendering::OptimizeSpeed,
344 1 => usvg::ShapeRendering::CrispEdges,
345 2 => usvg::ShapeRendering::GeometricPrecision,
346 _ => return,
347 }
348}
349
350#[repr(C)]
352#[allow(missing_docs)]
353#[derive(Copy, Clone)]
354pub enum resvg_text_rendering {
355 OPTIMIZE_SPEED,
356 OPTIMIZE_LEGIBILITY,
357 GEOMETRIC_PRECISION,
358}
359
360#[no_mangle]
366pub extern "C" fn resvg_options_set_text_rendering_mode(
367 opt: *mut resvg_options,
368 mode: resvg_text_rendering,
369) {
370 cast_opt(opt).text_rendering = match mode as i32 {
371 0 => usvg::TextRendering::OptimizeSpeed,
372 1 => usvg::TextRendering::OptimizeLegibility,
373 2 => usvg::TextRendering::GeometricPrecision,
374 _ => return,
375 }
376}
377
378#[repr(C)]
380#[allow(missing_docs)]
381#[derive(Copy, Clone)]
382pub enum resvg_image_rendering {
383 OPTIMIZE_QUALITY,
384 OPTIMIZE_SPEED,
385}
386
387#[no_mangle]
393pub extern "C" fn resvg_options_set_image_rendering_mode(
394 opt: *mut resvg_options,
395 mode: resvg_image_rendering,
396) {
397 cast_opt(opt).image_rendering = match mode as i32 {
398 0 => usvg::ImageRendering::OptimizeQuality,
399 1 => usvg::ImageRendering::OptimizeSpeed,
400 _ => return,
401 }
402}
403
404#[no_mangle]
410#[allow(unused_variables)]
411pub extern "C" fn resvg_options_load_font_data(
412 opt: *mut resvg_options,
413 data: *const c_char,
414 len: usize,
415) {
416 #[cfg(feature = "text")]
417 {
418 let data = unsafe { slice::from_raw_parts(data as *const u8, len) };
419 cast_opt(opt).fontdb_mut().load_font_data(data.to_vec())
420 }
421}
422
423#[no_mangle]
431#[allow(unused_variables)]
432pub extern "C" fn resvg_options_load_font_file(
433 opt: *mut resvg_options,
434 file_path: *const c_char,
435) -> i32 {
436 #[cfg(feature = "text")]
437 {
438 let file_path = match cstr_to_str(file_path) {
439 Some(v) => v,
440 None => return resvg_error::NOT_AN_UTF8_STR as i32,
441 };
442
443 if cast_opt(opt).fontdb_mut().load_font_file(file_path).is_ok() {
444 resvg_error::OK as i32
445 } else {
446 resvg_error::FILE_OPEN_FAILED as i32
447 }
448 }
449
450 #[cfg(not(feature = "text"))]
451 {
452 resvg_error::OK as i32
453 }
454}
455
456#[no_mangle]
469#[allow(unused_variables)]
470pub extern "C" fn resvg_options_load_system_fonts(opt: *mut resvg_options) {
471 #[cfg(feature = "text")]
472 {
473 cast_opt(opt).fontdb_mut().load_system_fonts();
474 }
475}
476
477#[no_mangle]
479pub extern "C" fn resvg_options_destroy(opt: *mut resvg_options) {
480 unsafe {
481 assert!(!opt.is_null());
482 let _ = Box::from_raw(opt);
483 };
484}
485
486pub struct resvg_render_tree(pub usvg::Tree);
489
490#[no_mangle]
501pub extern "C" fn resvg_parse_tree_from_file(
502 file_path: *const c_char,
503 opt: *const resvg_options,
504 tree: *mut *mut resvg_render_tree,
505) -> i32 {
506 let file_path = match cstr_to_str(file_path) {
507 Some(v) => v,
508 None => return resvg_error::NOT_AN_UTF8_STR as i32,
509 };
510
511 let raw_opt = unsafe {
512 assert!(!opt.is_null());
513 &*opt
514 };
515
516 let file_data = match std::fs::read(file_path) {
517 Ok(tree) => tree,
518 Err(_) => return resvg_error::FILE_OPEN_FAILED as i32,
519 };
520
521 let utree = usvg::Tree::from_data(&file_data, &raw_opt.options);
522
523 let utree = match utree {
524 Ok(tree) => tree,
525 Err(e) => return convert_error(e) as i32,
526 };
527
528 let tree_box = Box::new(resvg_render_tree(utree));
529 unsafe {
530 *tree = Box::into_raw(tree_box);
531 }
532
533 resvg_error::OK as i32
534}
535
536#[no_mangle]
546pub extern "C" fn resvg_parse_tree_from_data(
547 data: *const c_char,
548 len: usize,
549 opt: *const resvg_options,
550 tree: *mut *mut resvg_render_tree,
551) -> i32 {
552 let data = unsafe { slice::from_raw_parts(data as *const u8, len) };
553
554 let raw_opt = unsafe {
555 assert!(!opt.is_null());
556 &*opt
557 };
558
559 let utree = usvg::Tree::from_data(data, &raw_opt.options);
560
561 let utree = match utree {
562 Ok(tree) => tree,
563 Err(e) => return convert_error(e) as i32,
564 };
565
566 let tree_box = Box::new(resvg_render_tree(utree));
567 unsafe {
568 *tree = Box::into_raw(tree_box);
569 }
570
571 resvg_error::OK as i32
572}
573
574#[no_mangle]
579pub extern "C" fn resvg_is_image_empty(tree: *const resvg_render_tree) -> bool {
580 let tree = unsafe {
581 assert!(!tree.is_null());
582 &*tree
583 };
584
585 !tree.0.root().has_children()
586}
587
588#[no_mangle]
598pub extern "C" fn resvg_get_image_size(tree: *const resvg_render_tree) -> resvg_size {
599 let tree = unsafe {
600 assert!(!tree.is_null());
601 &*tree
602 };
603
604 let size = tree.0.size();
605
606 resvg_size {
607 width: size.width(),
608 height: size.height(),
609 }
610}
611
612#[no_mangle]
623pub extern "C" fn resvg_get_object_bbox(
624 tree: *const resvg_render_tree,
625 bbox: *mut resvg_rect,
626) -> bool {
627 let tree = unsafe {
628 assert!(!tree.is_null());
629 &*tree
630 };
631
632 if let Some(r) = tree.0.root().abs_bounding_box().to_non_zero_rect() {
633 unsafe {
634 *bbox = resvg_rect {
635 x: r.x(),
636 y: r.y(),
637 width: r.width(),
638 height: r.height(),
639 }
640 }
641
642 true
643 } else {
644 false
645 }
646}
647
648#[no_mangle]
658pub extern "C" fn resvg_get_image_bbox(
659 tree: *const resvg_render_tree,
660 bbox: *mut resvg_rect,
661) -> bool {
662 let tree = unsafe {
663 assert!(!tree.is_null());
664 &*tree
665 };
666
667 if tree.0.root().has_children() || !tree.0.root().filters().is_empty() {
669 let r = tree.0.root().abs_layer_bounding_box();
670 unsafe {
671 *bbox = resvg_rect {
672 x: r.x(),
673 y: r.y(),
674 width: r.width(),
675 height: r.height(),
676 }
677 }
678
679 true
680 } else {
681 false
682 }
683}
684
685#[no_mangle]
693pub extern "C" fn resvg_node_exists(tree: *const resvg_render_tree, id: *const c_char) -> bool {
694 let id = match cstr_to_str(id) {
695 Some(v) => v,
696 None => {
697 log::warn!("Provided ID is no an UTF-8 string.");
698 return false;
699 }
700 };
701
702 let tree = unsafe {
703 assert!(!tree.is_null());
704 &*tree
705 };
706
707 tree.0.node_by_id(id).is_some()
708}
709
710#[no_mangle]
719pub extern "C" fn resvg_get_node_transform(
720 tree: *const resvg_render_tree,
721 id: *const c_char,
722 transform: *mut resvg_transform,
723) -> bool {
724 let id = match cstr_to_str(id) {
725 Some(v) => v,
726 None => {
727 log::warn!("Provided ID is no an UTF-8 string.");
728 return false;
729 }
730 };
731
732 let tree = unsafe {
733 assert!(!tree.is_null());
734 &*tree
735 };
736
737 if let Some(node) = tree.0.node_by_id(id) {
738 let abs_ts = node.abs_transform();
739
740 unsafe {
741 *transform = resvg_transform {
742 a: abs_ts.sx,
743 b: abs_ts.ky,
744 c: abs_ts.kx,
745 d: abs_ts.sy,
746 e: abs_ts.tx,
747 f: abs_ts.ty,
748 }
749 }
750
751 return true;
752 }
753
754 false
755}
756
757#[no_mangle]
766pub extern "C" fn resvg_get_node_bbox(
767 tree: *const resvg_render_tree,
768 id: *const c_char,
769 bbox: *mut resvg_rect,
770) -> bool {
771 get_node_bbox(tree, id, bbox, &|node| node.abs_bounding_box())
772}
773
774#[no_mangle]
783pub extern "C" fn resvg_get_node_stroke_bbox(
784 tree: *const resvg_render_tree,
785 id: *const c_char,
786 bbox: *mut resvg_rect,
787) -> bool {
788 get_node_bbox(tree, id, bbox, &|node| node.abs_stroke_bounding_box())
789}
790
791fn get_node_bbox(
792 tree: *const resvg_render_tree,
793 id: *const c_char,
794 bbox: *mut resvg_rect,
795 f: &dyn Fn(&usvg::Node) -> usvg::Rect,
796) -> bool {
797 let id = match cstr_to_str(id) {
798 Some(v) => v,
799 None => {
800 log::warn!("Provided ID is no an UTF-8 string.");
801 return false;
802 }
803 };
804
805 if id.is_empty() {
806 log::warn!("Node ID must not be empty.");
807 return false;
808 }
809
810 let tree = unsafe {
811 assert!(!tree.is_null());
812 &*tree
813 };
814
815 match tree.0.node_by_id(id) {
816 Some(node) => {
817 let r = f(node);
818 unsafe {
819 *bbox = resvg_rect {
820 x: r.x(),
821 y: r.y(),
822 width: r.width(),
823 height: r.height(),
824 }
825 }
826 true
827 }
828 None => {
829 log::warn!("No node with '{}' ID is in the tree.", id);
830 false
831 }
832 }
833}
834
835#[no_mangle]
837pub extern "C" fn resvg_tree_destroy(tree: *mut resvg_render_tree) {
838 unsafe {
839 assert!(!tree.is_null());
840 let _ = Box::from_raw(tree);
841 };
842}
843
844fn cstr_to_str(text: *const c_char) -> Option<&'static str> {
845 let text = unsafe {
846 assert!(!text.is_null());
847 CStr::from_ptr(text)
848 };
849
850 text.to_str().ok()
851}
852
853fn convert_error(e: usvg::Error) -> resvg_error {
854 match e {
855 usvg::Error::NotAnUtf8Str => resvg_error::NOT_AN_UTF8_STR,
856 usvg::Error::MalformedGZip => resvg_error::MALFORMED_GZIP,
857 usvg::Error::ElementsLimitReached => resvg_error::ELEMENTS_LIMIT_REACHED,
858 usvg::Error::InvalidSize => resvg_error::INVALID_SIZE,
859 usvg::Error::ParsingFailed(_) => resvg_error::PARSING_FAILED,
860 }
861}
862
863#[no_mangle]
872pub extern "C" fn resvg_render(
873 tree: *const resvg_render_tree,
874 transform: resvg_transform,
875 width: u32,
876 height: u32,
877 pixmap: *mut c_char,
878) {
879 let tree = unsafe {
880 assert!(!tree.is_null());
881 &*tree
882 };
883
884 let pixmap_len = width as usize * height as usize * tiny_skia::BYTES_PER_PIXEL;
885 let pixmap: &mut [u8] =
886 unsafe { std::slice::from_raw_parts_mut(pixmap as *mut u8, pixmap_len) };
887 let mut pixmap = tiny_skia::PixmapMut::from_bytes(pixmap, width, height).unwrap();
888
889 resvg::render(&tree.0, transform.to_tiny_skia(), &mut pixmap)
890}
891
892#[no_mangle]
905pub extern "C" fn resvg_render_node(
906 tree: *const resvg_render_tree,
907 id: *const c_char,
908 transform: resvg_transform,
909 width: u32,
910 height: u32,
911 pixmap: *mut c_char,
912) -> bool {
913 let tree = unsafe {
914 assert!(!tree.is_null());
915 &*tree
916 };
917
918 let id = match cstr_to_str(id) {
919 Some(v) => v,
920 None => return false,
921 };
922
923 if id.is_empty() {
924 log::warn!("Node with an empty ID cannot be rendered.");
925 return false;
926 }
927
928 if let Some(node) = tree.0.node_by_id(id) {
929 let pixmap_len = width as usize * height as usize * tiny_skia::BYTES_PER_PIXEL;
930 let pixmap: &mut [u8] =
931 unsafe { std::slice::from_raw_parts_mut(pixmap as *mut u8, pixmap_len) };
932 let mut pixmap = tiny_skia::PixmapMut::from_bytes(pixmap, width, height).unwrap();
933
934 resvg::render_node(node, transform.to_tiny_skia(), &mut pixmap).is_some()
935 } else {
936 log::warn!("A node with '{}' ID wasn't found.", id);
937 false
938 }
939}
940
941static LOGGER: SimpleLogger = SimpleLogger;
943struct SimpleLogger;
944impl log::Log for SimpleLogger {
945 fn enabled(&self, metadata: &log::Metadata) -> bool {
946 metadata.level() <= log::LevelFilter::Warn
947 }
948
949 fn log(&self, record: &log::Record) {
950 if self.enabled(record.metadata()) {
951 let target = if record.target().len() > 0 {
952 record.target()
953 } else {
954 record.module_path().unwrap_or_default()
955 };
956
957 let line = record.line().unwrap_or(0);
958 let args = record.args();
959
960 match record.level() {
961 log::Level::Error => eprintln!("Error (in {}:{}): {}", target, line, args),
962 log::Level::Warn => eprintln!("Warning (in {}:{}): {}", target, line, args),
963 log::Level::Info => eprintln!("Info (in {}:{}): {}", target, line, args),
964 log::Level::Debug => eprintln!("Debug (in {}:{}): {}", target, line, args),
965 log::Level::Trace => eprintln!("Trace (in {}:{}): {}", target, line, args),
966 }
967 }
968 }
969
970 fn flush(&self) {}
971}