#
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#[unsafe(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#[unsafe(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#[unsafe(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#[unsafe(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#[unsafe(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#[unsafe(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#[unsafe(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#[unsafe(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#[unsafe(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#[unsafe(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#[unsafe(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#[unsafe(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#[unsafe(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#[unsafe(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#[unsafe(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#[unsafe(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#[unsafe(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#[unsafe(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#[unsafe(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#[unsafe(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#[unsafe(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#[unsafe(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#[unsafe(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#[unsafe(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#[unsafe(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#[unsafe(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#[unsafe(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#[unsafe(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 not a 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#[unsafe(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 not a 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#[unsafe(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#[unsafe(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 not a 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#[unsafe(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#[unsafe(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#[unsafe(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}