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