resvg/
lib.rs

1// Copyright 2020 the Resvg Authors
2// SPDX-License-Identifier: Apache-2.0 OR MIT
3
4//! C bindings.
5
6#![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/// @brief List of possible errors.
18#[repr(C)]
19#[derive(Copy, Clone)]
20pub enum resvg_error {
21    /// Everything is ok.
22    OK = 0,
23    /// Only UTF-8 content are supported.
24    NOT_AN_UTF8_STR,
25    /// Failed to open the provided file.
26    FILE_OPEN_FAILED,
27    /// Compressed SVG must use the GZip algorithm.
28    MALFORMED_GZIP,
29    /// We do not allow SVG with more than 1_000_000 elements for security reasons.
30    ELEMENTS_LIMIT_REACHED,
31    /// SVG doesn't have a valid size.
32    ///
33    /// Occurs when width and/or height are <= 0.
34    ///
35    /// Also occurs if width, height and viewBox are not set.
36    INVALID_SIZE,
37    /// Failed to parse an SVG data.
38    PARSING_FAILED,
39}
40
41/// @brief A rectangle representation.
42#[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/// @brief A size representation.
53#[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/// @brief A 2D transform representation.
62#[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/// @brief Creates an identity transform.
82#[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/// @brief Initializes the library log.
95///
96/// Use it if you want to see any warnings.
97///
98/// Must be called only once.
99///
100/// All warnings will be printed to the `stderr`.
101#[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
108/// @brief An SVG to #resvg_render_tree conversion options.
109///
110/// Also, contains a fonts database used during text to path conversion.
111/// The database is empty by default.
112pub struct resvg_options {
113    options: usvg::Options<'static>,
114}
115
116/// @brief Creates a new #resvg_options object.
117///
118/// Should be destroyed via #resvg_options_destroy.
119#[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/// @brief Sets a directory that will be used during relative paths resolving.
135///
136/// Expected to be the same as the directory that contains the SVG file,
137/// but can be set to any.
138///
139/// Must be UTF-8. Can be set to NULL.
140///
141/// Default: NULL
142#[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/// @brief Sets the target DPI.
152///
153/// Impact units conversion.
154///
155/// Default: 96
156#[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/// @brief Provides the content of a stylesheet that will be used when resolving CSS attributes.
162///
163/// Must be UTF-8. Can be set to NULL.
164///
165/// Default: NULL
166#[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/// @brief Sets the default font family.
176///
177/// Will be used when no `font-family` attribute is set in the SVG.
178///
179/// Must be UTF-8. NULL is not allowed.
180///
181/// Default: Times New Roman
182#[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/// @brief Sets the default font size.
188///
189/// Will be used when no `font-size` attribute is set in the SVG.
190///
191/// Default: 12
192#[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/// @brief Sets the `serif` font family.
198///
199/// Must be UTF-8. NULL is not allowed.
200///
201/// Has no effect when the `text` feature is not enabled.
202///
203/// Default: Times New Roman
204#[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/// @brief Sets the `sans-serif` font family.
216///
217/// Must be UTF-8. NULL is not allowed.
218///
219/// Has no effect when the `text` feature is not enabled.
220///
221/// Default: Arial
222#[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/// @brief Sets the `cursive` font family.
237///
238/// Must be UTF-8. NULL is not allowed.
239///
240/// Has no effect when the `text` feature is not enabled.
241///
242/// Default: Comic Sans MS
243#[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/// @brief Sets the `fantasy` font family.
255///
256/// Must be UTF-8. NULL is not allowed.
257///
258/// Has no effect when the `text` feature is not enabled.
259///
260/// Default: Papyrus on macOS, Impact on other OS'es
261#[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/// @brief Sets the `monospace` font family.
273///
274/// Must be UTF-8. NULL is not allowed.
275///
276/// Has no effect when the `text` feature is not enabled.
277///
278/// Default: Courier New
279#[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/// @brief Sets a comma-separated list of languages.
294///
295/// Will be used to resolve a `systemLanguage` conditional attribute.
296///
297/// Example: en,en-US.
298///
299/// Must be UTF-8. Can be NULL.
300///
301/// Default: en
302#[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/// @brief A shape rendering method.
323#[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/// @brief Sets the default shape rendering method.
333///
334/// Will be used when an SVG element's `shape-rendering` property is set to `auto`.
335///
336/// Default: `RESVG_SHAPE_RENDERING_GEOMETRIC_PRECISION`
337#[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/// @brief A text rendering method.
351#[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/// @brief Sets the default text rendering method.
361///
362/// Will be used when an SVG element's `text-rendering` property is set to `auto`.
363///
364/// Default: `RESVG_TEXT_RENDERING_OPTIMIZE_LEGIBILITY`
365#[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/// @brief A image rendering method.
379#[repr(C)]
380#[allow(missing_docs)]
381#[derive(Copy, Clone)]
382pub enum resvg_image_rendering {
383    OPTIMIZE_QUALITY,
384    OPTIMIZE_SPEED,
385}
386
387/// @brief Sets the default image rendering method.
388///
389/// Will be used when an SVG element's `image-rendering` property is set to `auto`.
390///
391/// Default: `RESVG_IMAGE_RENDERING_OPTIMIZE_QUALITY`
392#[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/// @brief Loads a font data into the internal fonts database.
405///
406/// Prints a warning into the log when the data is not a valid TrueType font.
407///
408/// Has no effect when the `text` feature is not enabled.
409#[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/// @brief Loads a font file into the internal fonts database.
424///
425/// Prints a warning into the log when the data is not a valid TrueType font.
426///
427/// Has no effect when the `text` feature is not enabled.
428///
429/// @return #resvg_error with RESVG_OK, RESVG_ERROR_NOT_AN_UTF8_STR or RESVG_ERROR_FILE_OPEN_FAILED
430#[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/// @brief Loads system fonts into the internal fonts database.
457///
458/// This method is very IO intensive.
459///
460/// This method should be executed only once per #resvg_options.
461///
462/// The system scanning is not perfect, so some fonts may be omitted.
463/// Please send a bug report in this case.
464///
465/// Prints warnings into the log.
466///
467/// Has no effect when the `text` feature is not enabled.
468#[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/// @brief Destroys the #resvg_options.
478#[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
486// TODO: use resvg::Tree
487/// @brief An opaque pointer to the rendering tree.
488pub struct resvg_render_tree(pub usvg::Tree);
489
490/// @brief Creates #resvg_render_tree from file.
491///
492/// .svg and .svgz files are supported.
493///
494/// See #resvg_is_image_empty for details.
495///
496/// @param file_path UTF-8 file path.
497/// @param opt Rendering options. Must not be NULL.
498/// @param tree Parsed render tree. Should be destroyed via #resvg_tree_destroy.
499/// @return #resvg_error
500#[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/// @brief Creates #resvg_render_tree from data.
537///
538/// See #resvg_is_image_empty for details.
539///
540/// @param data SVG data. Can contain SVG string or gzip compressed data. Must not be NULL.
541/// @param len Data length.
542/// @param opt Rendering options. Must not be NULL.
543/// @param tree Parsed render tree. Should be destroyed via #resvg_tree_destroy.
544/// @return #resvg_error
545#[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/// @brief Checks that tree has any nodes.
575///
576/// @param tree Render tree.
577/// @return Returns `true` if tree has no nodes.
578#[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/// @brief Returns an image size.
589///
590/// The size of an image that is required to render this SVG.
591///
592/// Note that elements outside the viewbox will be clipped. This is by design.
593/// If you want to render the whole SVG content, use #resvg_get_image_bbox instead.
594///
595/// @param tree Render tree.
596/// @return Image size.
597#[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/// @brief Returns an object bounding box.
613///
614/// This bounding box does not include objects stroke and filter regions.
615/// This is what SVG calls "absolute object bonding box".
616///
617/// If you're looking for a "complete" bounding box see #resvg_get_image_bbox
618///
619/// @param tree Render tree.
620/// @param bbox Image's object bounding box.
621/// @return `false` if an image has no elements.
622#[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/// @brief Returns an image bounding box.
649///
650/// This bounding box contains the maximum SVG dimensions.
651/// It's size can be bigger or smaller than #resvg_get_image_size
652/// Use it when you want to avoid clipping of elements that are outside the SVG viewbox.
653///
654/// @param tree Render tree.
655/// @param bbox Image's bounding box.
656/// @return `false` if an image has no elements.
657#[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    // `abs_layer_bounding_box` returns 0x0x1x1 for empty groups, so we need additional checks.
668    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/// @brief Returns `true` if a renderable node with such an ID exists.
686///
687/// @param tree Render tree.
688/// @param id Node's ID. UTF-8 string. Must not be NULL.
689/// @return `true` if a node exists.
690/// @return `false` if a node doesn't exist or ID isn't a UTF-8 string.
691/// @return `false` if a node exists, but not renderable.
692#[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/// @brief Returns node's transform by ID.
711///
712/// @param tree Render tree.
713/// @param id Node's ID. UTF-8 string. Must not be NULL.
714/// @param transform Node's transform.
715/// @return `true` if a node exists.
716/// @return `false` if a node doesn't exist or ID isn't a UTF-8 string.
717/// @return `false` if a node exists, but not renderable.
718#[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/// @brief Returns node's bounding box in canvas coordinates by ID.
758///
759/// @param tree Render tree.
760/// @param id Node's ID. Must not be NULL.
761/// @param bbox Node's bounding box.
762/// @return `false` if a node with such an ID does not exist
763/// @return `false` if ID isn't a UTF-8 string.
764/// @return `false` if ID is an empty string
765#[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/// @brief Returns node's bounding box, including stroke, in canvas coordinates by ID.
775///
776/// @param tree Render tree.
777/// @param id Node's ID. Must not be NULL.
778/// @param bbox Node's bounding box.
779/// @return `false` if a node with such an ID does not exist
780/// @return `false` if ID isn't a UTF-8 string.
781/// @return `false` if ID is an empty string
782#[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/// @brief Destroys the #resvg_render_tree.
836#[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/// @brief Renders the #resvg_render_tree onto the pixmap.
864///
865/// @param tree A render tree.
866/// @param transform A root SVG transform. Can be used to position SVG inside the `pixmap`.
867/// @param width Pixmap width.
868/// @param height Pixmap height.
869/// @param pixmap Pixmap data. Should have width*height*4 size and contain
870///               premultiplied RGBA8888 pixels.
871#[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/// @brief Renders a Node by ID onto the image.
893///
894/// @param tree A render tree.
895/// @param id Node's ID. Must not be NULL.
896/// @param transform A root SVG transform. Can be used to position SVG inside the `pixmap`.
897/// @param width Pixmap width.
898/// @param height Pixmap height.
899/// @param pixmap Pixmap data. Should have width*height*4 size and contain
900///               premultiplied RGBA8888 pixels.
901/// @return `false` when `id` is not a non-empty UTF-8 string.
902/// @return `false` when the selected `id` is not present.
903/// @return `false` when an element has a zero bbox.
904#[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
941/// A simple stderr logger.
942static 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}