lib.rs - source
Description: Source of the Rust file `crates/c-api/lib.rs`.

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}