usvgr 0.45.0

An SVG simplification library.
Documentation
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.

mod clippath;
mod converter;
mod filter;
mod image;
mod marker;
mod mask;
mod options;
mod paint_server;
mod shapes;
mod style;
mod switch;
mod units;
mod use_node;

/// FFRAMES: make svgree publcic
pub mod svgtree;
#[cfg(feature = "text")]
mod text;

pub use image::PreloadedImageData;
pub use options::Options;
pub(crate) use svgtree::{AId, EId};

pub use self::converter::Cache;

/// List of all errors.
#[derive(Debug)]
pub enum Error {
    /// Only UTF-8 content are supported.
    NotAnUtf8Str,

    /// Compressed SVG must use the GZip algorithm.
    MalformedGZip,

    /// We do not allow SVG with more than 1_000_000 elements for security reasons.
    ElementsLimitReached,

    /// SVG doesn't have a valid size.
    ///
    /// Occurs when width and/or height are <= 0.
    ///
    /// Also occurs if width, height and viewBox are not set.
    InvalidSize,

    /// Failed to parse an SVG data.
    ParsingFailed(roxmltree::Error),
}

impl From<roxmltree::Error> for Error {
    fn from(e: roxmltree::Error) -> Self {
        Error::ParsingFailed(e)
    }
}

impl std::fmt::Display for Error {
    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
        match *self {
            Error::NotAnUtf8Str => {
                write!(f, "provided data has not an UTF-8 encoding")
            }
            Error::MalformedGZip => {
                write!(f, "provided data has a malformed GZip content")
            }
            Error::ElementsLimitReached => {
                write!(f, "the maximum number of SVG elements has been reached")
            }
            Error::InvalidSize => {
                write!(f, "SVG has an invalid size")
            }
            Error::ParsingFailed(ref e) => {
                write!(f, "SVG data parsing failed cause {}", e)
            }
        }
    }
}

impl std::error::Error for Error {}

trait OptionLog {
    fn log_none<F: FnOnce()>(self, f: F) -> Self;
}

impl<T> OptionLog for Option<T> {
    #[inline]
    fn log_none<F: FnOnce()>(self, f: F) -> Self {
        self.or_else(|| {
            f();
            None
        })
    }
}

impl crate::Tree {
    /// Parses `Tree` from an SVG data.
    ///
    /// Can contain an SVG string or a gzip compressed data.
    pub fn from_data(
        data: &[u8],
        opt: &Options,
        #[cfg(feature = "text")] fontdb: &fontdb::Database,
    ) -> Result<Self, Error> {
        if data.starts_with(&[0x1f, 0x8b]) {
            let data = decompress_svgz(data)?;
            let text = std::str::from_utf8(&data).map_err(|_| Error::NotAnUtf8Str)?;
            Self::from_str(
                text,
                opt,
                #[cfg(feature = "text")]
                fontdb,
            )
        } else {
            let text = std::str::from_utf8(data).map_err(|_| Error::NotAnUtf8Str)?;
            Self::from_str(
                text,
                opt,
                #[cfg(feature = "text")]
                fontdb,
            )
        }
    }

    /// Parses `Tree` from an SVG string.
    pub fn from_str(
        text: &str,
        opt: &Options,
        #[cfg(feature = "text")] fontdb: &fontdb::Database,
    ) -> Result<Self, Error> {
        let xml_opt = roxmltree::ParsingOptions {
            allow_dtd: true,
            ..Default::default()
        };

        let doc =
            roxmltree::Document::parse_with_options(text, xml_opt).map_err(Error::ParsingFailed)?;

        Self::from_xmltree(
            &doc,
            opt,
            #[cfg(feature = "text")]
            fontdb,
        )
    }

    /// Parses `Tree` from `roxmltree::Document` with a cache.
    pub fn from_xmltree_with_cache(
        doc: &roxmltree::Document,
        opt: &Options,
        cache: &mut Cache,
        #[cfg(feature = "text")] fontdb: &fontdb::Database,
    ) -> Result<Self, Error> {
        let doc = svgtree::Document::parse_tree(doc)?;
        self::converter::convert_doc(
            &doc,
            opt,
            cache,
            #[cfg(feature = "text")]
            fontdb,
        )
    }

    /// Parses `Tree` from `roxmltree::Document`.
    pub fn from_xmltree(
        doc: &roxmltree::Document,
        opt: &Options,
        #[cfg(feature = "text")] fontdb: &fontdb::Database,
    ) -> Result<Self, Error> {
        let doc = svgtree::Document::parse_tree(doc)?;
        self::converter::convert_doc(
            &doc,
            opt,
            &mut Cache::default(),
            #[cfg(feature = "text")]
            fontdb,
        )
    }

    /// Parses `Tree` from `svgtree::NestedSvgDocument`.
    pub fn from_nested_svgtree(
        doc: &svgtree::NestedSvgDocument,
        opt: &Options,
        #[cfg(feature = "text")] fontdb: &fontdb::Database,
    ) -> Result<Self, Error> {
        let doc = svgtree::Document::try_from(doc)?;
        Self::from_svgtree(
            doc,
            opt,
            &mut Cache::default(),
            #[cfg(feature = "text")]
            fontdb,
        )
    }

    /// Parses `Tree` from `svgtree::NestedSvgDocument` with a cache.
    pub fn from_nested_svgtree_with_cache(
        doc: &svgtree::NestedSvgDocument,
        opt: &Options,
        cache: &mut Cache,
        #[cfg(feature = "text")] fontdb: &fontdb::Database,
    ) -> Result<Self, Error> {
        let doc = svgtree::Document::try_from(doc)?;
        Self::from_svgtree(
            doc,
            opt,
            cache,
            #[cfg(feature = "text")]
            fontdb,
        )
    }

    /// Parses `Tree` from the `svgtree::Document`.
    ///
    /// An empty `Tree` will be returned on any error.
    pub fn from_svgtree(
        doc: svgtree::Document,
        opt: &Options,
        cache: &mut Cache,
        #[cfg(feature = "text")] fontdb: &fontdb::Database,
    ) -> Result<Self, Error> {
        self::converter::convert_doc(
            &doc,
            opt,
            cache,
            #[cfg(feature = "text")]
            fontdb,
        )
    }
}

/// Decompresses an SVGZ file.
pub fn decompress_svgz(data: &[u8]) -> Result<Vec<u8>, Error> {
    use std::io::Read;

    let mut decoder = flate2::read::GzDecoder::new(data);
    let mut decoded = Vec::with_capacity(data.len() * 2);
    decoder
        .read_to_end(&mut decoded)
        .map_err(|_| Error::MalformedGZip)?;
    Ok(decoded)
}

#[inline]
pub(crate) fn f32_bound(min: f32, val: f32, max: f32) -> f32 {
    debug_assert!(min.is_finite());
    debug_assert!(val.is_finite());
    debug_assert!(max.is_finite());

    if val > max {
        max
    } else if val < min {
        min
    } else {
        val
    }
}