Skip to main content

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    /// `resvg` must be compiled with SVGZ decoding support.
26    SVGZ_UNSUPPORTED,
27    /// Failed to open the provided file.
28    FILE_OPEN_FAILED,
29    /// Compressed SVG must use the GZip algorithm.
30    MALFORMED_GZIP,
31    /// We do not allow SVG with more than 1_000_000 elements for security reasons.
32    ELEMENTS_LIMIT_REACHED,
33    /// SVG doesn't have a valid size.
34    ///
35    /// Occurs when width and/or height are <= 0.
36    ///
37    /// Also occurs if width, height and viewBox are not set.
38    INVALID_SIZE,
39    /// Failed to parse an SVG data.
40    PARSING_FAILED,
41}
42
43/// @brief A rectangle representation.
44#[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/// @brief A size representation.
55#[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/// @brief A 2D transform representation.
64#[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/// @brief Creates an identity transform.
84#[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/// @brief Initializes the library log.
97///
98/// Use it if you want to see any warnings.
99///
100/// Must be called only once.
101///
102/// All warnings will be printed to the `stderr`.
103#[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
110/// @brief An SVG to #resvg_render_tree conversion options.
111///
112/// Also, contains a fonts database used during text to path conversion.
113/// The database is empty by default.
114pub struct resvg_options {
115    options: usvg::Options<'static>,
116}
117
118/// @brief Creates a new #resvg_options object.
119///
120/// Should be destroyed via #resvg_options_destroy.
121#[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/// @brief Sets a directory that will be used during relative paths resolving.
137///
138/// Expected to be the same as the directory that contains the SVG file,
139/// but can be set to any.
140///
141/// Must be UTF-8. Can be set to NULL.
142///
143/// Default: NULL
144#[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/// @brief Sets the target DPI.
154///
155/// Impact units conversion.
156///
157/// Default: 96
158#[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/// @brief Provides the content of a stylesheet that will be used when resolving CSS attributes.
164///
165/// Must be UTF-8. Can be set to NULL.
166///
167/// Default: NULL
168#[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/// @brief Sets the default font family.
178///
179/// Will be used when no `font-family` attribute is set in the SVG.
180///
181/// Must be UTF-8. NULL is not allowed.
182///
183/// Default: Times New Roman
184#[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/// @brief Sets the default font size.
190///
191/// Will be used when no `font-size` attribute is set in the SVG.
192///
193/// Default: 12
194#[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/// @brief Sets the `serif` font family.
200///
201/// Must be UTF-8. NULL is not allowed.
202///
203/// Has no effect when the `text` feature is not enabled.
204///
205/// Default: Times New Roman
206#[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/// @brief Sets the `sans-serif` font family.
218///
219/// Must be UTF-8. NULL is not allowed.
220///
221/// Has no effect when the `text` feature is not enabled.
222///
223/// Default: Arial
224#[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/// @brief Sets the `cursive` font family.
239///
240/// Must be UTF-8. NULL is not allowed.
241///
242/// Has no effect when the `text` feature is not enabled.
243///
244/// Default: Comic Sans MS
245#[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/// @brief Sets the `fantasy` font family.
257///
258/// Must be UTF-8. NULL is not allowed.
259///
260/// Has no effect when the `text` feature is not enabled.
261///
262/// Default: Papyrus on macOS, Impact on other OS'es
263#[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/// @brief Sets the `monospace` font family.
275///
276/// Must be UTF-8. NULL is not allowed.
277///
278/// Has no effect when the `text` feature is not enabled.
279///
280/// Default: Courier New
281#[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/// @brief Sets a comma-separated list of languages.
296///
297/// Will be used to resolve a `systemLanguage` conditional attribute.
298///
299/// Example: en,en-US.
300///
301/// Must be UTF-8. Can be NULL.
302///
303/// Default: en
304#[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/// @brief A shape rendering method.
325#[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/// @brief Sets the default shape rendering method.
335///
336/// Will be used when an SVG element's `shape-rendering` property is set to `auto`.
337///
338/// Default: `RESVG_SHAPE_RENDERING_GEOMETRIC_PRECISION`
339#[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/// @brief A text rendering method.
353#[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/// @brief Sets the default text rendering method.
363///
364/// Will be used when an SVG element's `text-rendering` property is set to `auto`.
365///
366/// Default: `RESVG_TEXT_RENDERING_OPTIMIZE_LEGIBILITY`
367#[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/// @brief A image rendering method.
381#[repr(C)]
382#[allow(missing_docs)]
383#[derive(Copy, Clone)]
384pub enum resvg_image_rendering {
385    OPTIMIZE_QUALITY,
386    OPTIMIZE_SPEED,
387}
388
389/// @brief Sets the default image rendering method.
390///
391/// Will be used when an SVG element's `image-rendering` property is set to `auto`.
392///
393/// Default: `RESVG_IMAGE_RENDERING_OPTIMIZE_QUALITY`
394#[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/// @brief Loads a font data into the internal fonts database.
407///
408/// Prints a warning into the log when the data is not a valid TrueType font.
409///
410/// Has no effect when the `text` feature is not enabled.
411#[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/// @brief Loads a font file into the internal fonts database.
426///
427/// Prints a warning into the log when the data is not a valid TrueType font.
428///
429/// Has no effect when the `text` feature is not enabled.
430///
431/// @return #resvg_error with RESVG_OK, RESVG_ERROR_NOT_AN_UTF8_STR or RESVG_ERROR_FILE_OPEN_FAILED
432#[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/// @brief Loads system fonts into the internal fonts database.
459///
460/// This method is very IO intensive.
461///
462/// This method should be executed only once per #resvg_options.
463///
464/// The system scanning is not perfect, so some fonts may be omitted.
465/// Please send a bug report in this case.
466///
467/// Prints warnings into the log.
468///
469/// Has no effect when the `text` feature is not enabled.
470#[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/// @brief Destroys the #resvg_options.
480#[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
488// TODO: use resvg::Tree
489/// @brief An opaque pointer to the rendering tree.
490pub struct resvg_render_tree(pub usvg::Tree);
491
492/// @brief Creates #resvg_render_tree from file.
493///
494/// .svg and .svgz files are supported.
495///
496/// See #resvg_is_image_empty for details.
497///
498/// @param file_path UTF-8 file path.
499/// @param opt Rendering options. Must not be NULL.
500/// @param tree Parsed render tree. Should be destroyed via #resvg_tree_destroy.
501/// @return #resvg_error
502#[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/// @brief Creates #resvg_render_tree from data.
539///
540/// See #resvg_is_image_empty for details.
541///
542/// @param data SVG data. Can contain SVG string or gzip compressed data. Must not be NULL.
543/// @param len Data length.
544/// @param opt Rendering options. Must not be NULL.
545/// @param tree Parsed render tree. Should be destroyed via #resvg_tree_destroy.
546/// @return #resvg_error
547#[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/// @brief Checks that tree has any nodes.
577///
578/// @param tree Render tree.
579/// @return Returns `true` if tree has no nodes.
580#[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/// @brief Returns an image size.
591///
592/// The size of an image that is required to render this SVG.
593///
594/// Note that elements outside the viewbox will be clipped. This is by design.
595/// If you want to render the whole SVG content, use #resvg_get_image_bbox instead.
596///
597/// @param tree Render tree.
598/// @return Image size.
599#[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/// @brief Returns an object bounding box.
615///
616/// This bounding box does not include objects stroke and filter regions.
617/// This is what SVG calls "absolute object bonding box".
618///
619/// If you're looking for a "complete" bounding box see #resvg_get_image_bbox
620///
621/// @param tree Render tree.
622/// @param bbox Image's object bounding box.
623/// @return `false` if an image has no elements.
624#[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/// @brief Returns an image bounding box.
651///
652/// This bounding box contains the maximum SVG dimensions.
653/// It's size can be bigger or smaller than #resvg_get_image_size
654/// Use it when you want to avoid clipping of elements that are outside the SVG viewbox.
655///
656/// @param tree Render tree.
657/// @param bbox Image's bounding box.
658/// @return `false` if an image has no elements.
659#[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    // `abs_layer_bounding_box` returns 0x0x1x1 for empty groups, so we need additional checks.
670    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/// @brief Returns `true` if a renderable node with such an ID exists.
688///
689/// @param tree Render tree.
690/// @param id Node's ID. UTF-8 string. Must not be NULL.
691/// @return `true` if a node exists.
692/// @return `false` if a node doesn't exist or ID isn't a UTF-8 string.
693/// @return `false` if a node exists, but not renderable.
694#[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/// @brief Returns node's transform by ID.
713///
714/// @param tree Render tree.
715/// @param id Node's ID. UTF-8 string. Must not be NULL.
716/// @param transform Node's transform.
717/// @return `true` if a node exists.
718/// @return `false` if a node doesn't exist or ID isn't a UTF-8 string.
719/// @return `false` if a node exists, but not renderable.
720#[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/// @brief Returns node's bounding box in canvas coordinates by ID.
760///
761/// @param tree Render tree.
762/// @param id Node's ID. Must not be NULL.
763/// @param bbox Node's bounding box.
764/// @return `false` if a node with such an ID does not exist
765/// @return `false` if ID isn't a UTF-8 string.
766/// @return `false` if ID is an empty string
767#[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/// @brief Returns node's bounding box, including stroke, in canvas coordinates by ID.
777///
778/// @param tree Render tree.
779/// @param id Node's ID. Must not be NULL.
780/// @param bbox Node's bounding box.
781/// @return `false` if a node with such an ID does not exist
782/// @return `false` if ID isn't a UTF-8 string.
783/// @return `false` if ID is an empty string
784#[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/// @brief Destroys the #resvg_render_tree.
838#[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/// @brief Renders the #resvg_render_tree onto the pixmap.
867///
868/// @param tree A render tree.
869/// @param transform A root SVG transform. Can be used to position SVG inside the `pixmap`.
870/// @param width Pixmap width.
871/// @param height Pixmap height.
872/// @param pixmap Pixmap data. Should have width*height*4 size and contain
873///               premultiplied RGBA8888 pixels.
874#[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/// @brief Renders a Node by ID onto the image.
896///
897/// @param tree A render tree.
898/// @param id Node's ID. Must not be NULL.
899/// @param transform A root SVG transform. Can be used to position SVG inside the `pixmap`.
900/// @param width Pixmap width.
901/// @param height Pixmap height.
902/// @param pixmap Pixmap data. Should have width*height*4 size and contain
903///               premultiplied RGBA8888 pixels.
904/// @return `false` when `id` is not a non-empty UTF-8 string.
905/// @return `false` when the selected `id` is not present.
906/// @return `false` when an element has a zero bbox.
907#[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
944/// A simple stderr logger.
945static 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}