#![forbid(unsafe_code)]
#![warn(missing_docs)]
#![warn(missing_debug_implementations)]
#![warn(missing_copy_implementations)]
#![allow(clippy::many_single_char_names)]
#![allow(clippy::collapsible_else_if)]
#![allow(clippy::too_many_arguments)]
#![allow(clippy::neg_cmp_op_on_partial_ord)]
#![allow(clippy::identity_op)]
#![allow(clippy::question_mark)]
#![allow(clippy::upper_case_acronyms)]
macro_rules! impl_enum_default {
($name:ident, $def_value:ident) => {
impl Default for $name {
#[inline]
fn default() -> Self {
$name::$def_value
}
}
};
}
macro_rules! impl_enum_from_str {
($name:ident, $($string:pat => $result:expr),+) => {
impl crate::svgtree::EnumFromStr for $name {
fn enum_from_str(s: &str) -> Option<Self> {
match s {
$($string => Some($result)),+,
_ => None,
}
}
}
};
}
macro_rules! impl_from_str {
($name:ident) => {
impl std::str::FromStr for $name {
type Err = &'static str;
fn from_str(s: &str) -> Result<Self, Self::Err> {
crate::svgtree::EnumFromStr::enum_from_str(s).ok_or("invalid value")
}
}
};
}
mod clippath;
mod converter;
mod error;
pub mod filter;
mod geom;
mod image;
mod marker;
mod mask;
mod options;
mod paint_server;
mod pathdata;
mod shapes;
mod style;
mod svgtree;
mod switch;
mod text;
mod units;
mod use_node;
pub mod utils;
pub use image::ImageHrefResolver;
pub use strict_num::{ApproxEq, ApproxEqUlps, NonZeroPositiveF64, NormalizedF64, PositiveF64};
pub use svgtypes::{Align, AspectRatio};
use std::rc::Rc;
pub use roxmltree;
pub use crate::clippath::*;
pub use crate::error::*;
pub use crate::geom::*;
pub use crate::image::*;
pub use crate::mask::*;
pub use crate::options::*;
pub use crate::paint_server::*;
pub use crate::pathdata::*;
pub use crate::style::*;
pub use crate::text::*;
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
})
}
}
pub trait IsDefault: Default {
fn is_default(&self) -> bool;
}
impl<T: Default + PartialEq + Copy> IsDefault for T {
#[inline]
fn is_default(&self) -> bool {
*self == Self::default()
}
}
pub type Opacity = NormalizedF64;
#[derive(Clone, Copy, Debug)]
pub struct NonZeroF64(f64);
impl NonZeroF64 {
#[inline]
pub fn new(n: f64) -> Option<Self> {
if n.is_fuzzy_zero() {
None
} else {
Some(NonZeroF64(n))
}
}
#[inline]
pub fn value(&self) -> f64 {
self.0
}
}
#[allow(missing_docs)]
#[derive(Clone, Copy, PartialEq, Debug)]
pub enum Units {
UserSpaceOnUse,
ObjectBoundingBox,
}
impl_enum_from_str!(Units,
"userSpaceOnUse" => Units::UserSpaceOnUse,
"objectBoundingBox" => Units::ObjectBoundingBox
);
#[allow(missing_docs)]
#[derive(Clone, Copy, PartialEq, Debug)]
pub enum Visibility {
Visible,
Hidden,
Collapse,
}
impl_enum_default!(Visibility, Visible);
impl_enum_from_str!(Visibility,
"visible" => Visibility::Visible,
"hidden" => Visibility::Hidden,
"collapse" => Visibility::Collapse
);
#[derive(Clone, Copy, PartialEq, Debug)]
#[allow(missing_docs)]
pub enum ShapeRendering {
OptimizeSpeed,
CrispEdges,
GeometricPrecision,
}
impl ShapeRendering {
pub fn use_shape_antialiasing(self) -> bool {
match self {
ShapeRendering::OptimizeSpeed => false,
ShapeRendering::CrispEdges => false,
ShapeRendering::GeometricPrecision => true,
}
}
}
impl_enum_default!(ShapeRendering, GeometricPrecision);
impl_enum_from_str!(ShapeRendering,
"optimizeSpeed" => ShapeRendering::OptimizeSpeed,
"crispEdges" => ShapeRendering::CrispEdges,
"geometricPrecision" => ShapeRendering::GeometricPrecision
);
impl_from_str!(ShapeRendering);
#[allow(missing_docs)]
#[derive(Clone, Copy, PartialEq, Debug)]
pub enum TextRendering {
OptimizeSpeed,
OptimizeLegibility,
GeometricPrecision,
}
impl_enum_default!(TextRendering, OptimizeLegibility);
impl_enum_from_str!(TextRendering,
"optimizeSpeed" => TextRendering::OptimizeSpeed,
"optimizeLegibility" => TextRendering::OptimizeLegibility,
"geometricPrecision" => TextRendering::GeometricPrecision
);
impl_from_str!(TextRendering);
#[allow(missing_docs)]
#[derive(Clone, Copy, PartialEq, Debug)]
pub enum ImageRendering {
OptimizeQuality,
OptimizeSpeed,
}
impl_enum_default!(ImageRendering, OptimizeQuality);
impl_enum_from_str!(ImageRendering,
"optimizeQuality" => ImageRendering::OptimizeQuality,
"optimizeSpeed" => ImageRendering::OptimizeSpeed
);
impl_from_str!(ImageRendering);
#[allow(missing_docs)]
#[derive(Clone, Copy, PartialEq, Debug)]
pub enum BlendMode {
Normal,
Multiply,
Screen,
Overlay,
Darken,
Lighten,
ColorDodge,
ColorBurn,
HardLight,
SoftLight,
Difference,
Exclusion,
Hue,
Saturation,
Color,
Luminosity,
}
impl_enum_default!(BlendMode, Normal);
impl_enum_from_str!(BlendMode,
"normal" => BlendMode::Normal,
"multiply" => BlendMode::Multiply,
"screen" => BlendMode::Screen,
"overlay" => BlendMode::Overlay,
"darken" => BlendMode::Darken,
"lighten" => BlendMode::Lighten,
"color-dodge" => BlendMode::ColorDodge,
"color-burn" => BlendMode::ColorBurn,
"hard-light" => BlendMode::HardLight,
"soft-light" => BlendMode::SoftLight,
"difference" => BlendMode::Difference,
"exclusion" => BlendMode::Exclusion,
"hue" => BlendMode::Hue,
"saturation" => BlendMode::Saturation,
"color" => BlendMode::Color,
"luminosity" => BlendMode::Luminosity
);
#[allow(missing_docs)]
#[derive(Clone, Debug)]
pub enum NodeKind {
Group(Group),
Path(Path),
Image(Image),
Text(Text),
}
impl NodeKind {
pub fn id(&self) -> &str {
match self {
NodeKind::Group(ref e) => e.id.as_str(),
NodeKind::Path(ref e) => e.id.as_str(),
NodeKind::Image(ref e) => e.id.as_str(),
NodeKind::Text(ref e) => e.id.as_str(),
}
}
pub fn transform(&self) -> Transform {
match self {
NodeKind::Group(ref e) => e.transform,
NodeKind::Path(ref e) => e.transform,
NodeKind::Image(ref e) => e.transform,
NodeKind::Text(ref e) => e.transform,
}
}
}
#[derive(Clone, Copy, PartialEq, Debug)]
#[allow(missing_docs)]
pub enum PaintOrder {
FillAndStroke,
StrokeAndFill,
}
impl Default for PaintOrder {
fn default() -> Self {
Self::FillAndStroke
}
}
#[derive(Clone, Debug)]
pub struct Path {
pub id: String,
pub transform: Transform,
pub visibility: Visibility,
pub fill: Option<Fill>,
pub stroke: Option<Stroke>,
pub paint_order: PaintOrder,
pub rendering_mode: ShapeRendering,
pub text_bbox: Option<Rect>,
pub data: Rc<PathData>,
}
impl Default for Path {
fn default() -> Self {
Path {
id: String::new(),
transform: Transform::default(),
visibility: Visibility::Visible,
fill: None,
stroke: None,
paint_order: PaintOrder::default(),
rendering_mode: ShapeRendering::default(),
text_bbox: None,
data: Rc::new(PathData::default()),
}
}
}
#[derive(Clone, Copy, Debug)]
#[allow(missing_docs)]
pub struct EnableBackground(pub Option<Rect>);
#[derive(Clone, Debug)]
pub struct Group {
pub id: String,
pub transform: Transform,
pub opacity: Opacity,
pub blend_mode: BlendMode,
pub isolate: bool,
pub clip_path: Option<Rc<ClipPath>>,
pub mask: Option<Rc<Mask>>,
pub filters: Vec<Rc<filter::Filter>>,
pub filter_fill: Option<Paint>,
pub filter_stroke: Option<Paint>,
pub enable_background: Option<EnableBackground>,
}
impl Default for Group {
fn default() -> Self {
Group {
id: String::new(),
transform: Transform::default(),
opacity: Opacity::ONE,
blend_mode: BlendMode::Normal,
isolate: false,
clip_path: None,
mask: None,
filters: Vec::new(),
filter_fill: None,
filter_stroke: None,
enable_background: None,
}
}
}
pub type Node = rctree::Node<NodeKind>;
#[allow(missing_debug_implementations)]
#[derive(Clone)]
pub struct Tree {
pub size: Size,
pub view_box: ViewBox,
pub root: Node,
}
impl Tree {
pub fn from_data(data: &[u8], opt: &Options) -> 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)
} else {
let text = std::str::from_utf8(data).map_err(|_| Error::NotAnUtf8Str)?;
Self::from_str(text, opt)
}
}
pub fn from_str(text: &str, opt: &Options) -> Result<Self, Error> {
let mut xml_opt = roxmltree::ParsingOptions::default();
xml_opt.allow_dtd = true;
let doc =
roxmltree::Document::parse_with_options(text, xml_opt).map_err(Error::ParsingFailed)?;
Self::from_xmltree(&doc, opt)
}
pub fn from_xmltree(doc: &roxmltree::Document, opt: &Options) -> Result<Self, Error> {
let doc = svgtree::Document::parse(doc)?;
Self::from_svgtree(doc, opt)
}
fn from_svgtree(doc: svgtree::Document, opt: &Options) -> Result<Self, Error> {
crate::converter::convert_doc(&doc, opt)
}
pub fn node_by_id(&self, id: &str) -> Option<Node> {
if id.is_empty() {
return None;
}
self.root.descendants().find(|node| &*node.id() == id)
}
pub fn ungroup_groups(root: Node, keep_named_groups: bool) {
converter::ungroup_groups(root, keep_named_groups);
}
}
pub trait NodeExt {
fn id(&self) -> std::cell::Ref<str>;
fn transform(&self) -> Transform;
fn abs_transform(&self) -> Transform;
fn append_kind(&self, kind: NodeKind) -> Node;
fn calculate_bbox(&self) -> Option<PathBbox>;
fn filter_background_start_node(&self, filter: &filter::Filter) -> Option<Node>;
}
impl NodeExt for Node {
#[inline]
fn id(&self) -> std::cell::Ref<str> {
std::cell::Ref::map(self.borrow(), |v| v.id())
}
#[inline]
fn transform(&self) -> Transform {
self.borrow().transform()
}
fn abs_transform(&self) -> Transform {
let mut ts_list = Vec::new();
for p in self.ancestors() {
ts_list.push(p.transform());
}
let mut abs_ts = Transform::default();
for ts in ts_list.iter().rev() {
abs_ts.append(ts);
}
abs_ts
}
#[inline]
fn append_kind(&self, kind: NodeKind) -> Node {
let new_node = Node::new(kind);
self.append(new_node.clone());
new_node
}
#[inline]
fn calculate_bbox(&self) -> Option<PathBbox> {
calc_node_bbox(self, self.abs_transform())
}
fn filter_background_start_node(&self, filter: &filter::Filter) -> Option<Node> {
fn has_enable_background(node: &Node) -> bool {
if let NodeKind::Group(ref g) = *node.borrow() {
g.enable_background.is_some()
} else {
false
}
}
if !filter
.primitives
.iter()
.any(|c| c.kind.has_input(&filter::Input::BackgroundImage))
&& !filter
.primitives
.iter()
.any(|c| c.kind.has_input(&filter::Input::BackgroundAlpha))
{
return None;
}
self.ancestors()
.skip(1)
.find(|node| has_enable_background(node))
}
}
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)
}
fn calc_node_bbox(node: &Node, ts: Transform) -> Option<PathBbox> {
match *node.borrow() {
NodeKind::Path(ref path) => path.data.bbox_with_transform(ts, path.stroke.as_ref()),
NodeKind::Image(ref img) => {
let path = PathData::from_rect(img.view_box.rect);
path.bbox_with_transform(ts, None)
}
NodeKind::Group(_) => {
let mut bbox = PathBbox::new_bbox();
for child in node.children() {
let mut child_transform = ts.clone();
child_transform.append(&child.transform());
if let Some(c_bbox) = calc_node_bbox(&child, child_transform) {
bbox = bbox.expand(c_bbox);
}
}
if bbox.fuzzy_eq(&PathBbox::new_bbox()) {
return None;
}
Some(bbox)
}
NodeKind::Text(_) => None,
}
}