#![forbid(unsafe_code)]
#![warn(missing_docs)]
#![warn(missing_debug_implementations)]
#![warn(missing_copy_implementations)]
#![allow(clippy::collapsible_else_if)]
#![allow(clippy::neg_cmp_op_on_partial_ord)]
#![allow(clippy::too_many_arguments)]
#![allow(clippy::derivable_impls)]
pub mod filter;
mod geom;
mod pathdata;
mod text;
pub mod utils;
use std::rc::Rc;
use std::sync::Arc;
pub use strict_num::{ApproxEq, ApproxEqUlps, NonZeroPositiveF64, NormalizedF64, PositiveF64};
pub use svgtypes::{Align, AspectRatio};
pub use crate::geom::*;
pub use crate::pathdata::*;
pub use crate::text::*;
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,
}
#[allow(missing_docs)]
#[derive(Clone, Copy, PartialEq, Debug)]
pub enum Visibility {
Visible,
Hidden,
Collapse,
}
impl Default for Visibility {
fn default() -> Self {
Self::Visible
}
}
#[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 Default for ShapeRendering {
fn default() -> Self {
Self::GeometricPrecision
}
}
impl std::str::FromStr for ShapeRendering {
type Err = &'static str;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s {
"optimizeSpeed" => Ok(ShapeRendering::OptimizeSpeed),
"crispEdges" => Ok(ShapeRendering::CrispEdges),
"geometricPrecision" => Ok(ShapeRendering::GeometricPrecision),
_ => Err("invalid"),
}
}
}
#[allow(missing_docs)]
#[derive(Clone, Copy, PartialEq, Debug)]
pub enum TextRendering {
OptimizeSpeed,
OptimizeLegibility,
GeometricPrecision,
}
impl Default for TextRendering {
fn default() -> Self {
Self::OptimizeLegibility
}
}
impl std::str::FromStr for TextRendering {
type Err = &'static str;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s {
"optimizeSpeed" => Ok(TextRendering::OptimizeSpeed),
"optimizeLegibility" => Ok(TextRendering::OptimizeLegibility),
"geometricPrecision" => Ok(TextRendering::GeometricPrecision),
_ => Err("invalid"),
}
}
}
#[allow(missing_docs)]
#[derive(Clone, Copy, PartialEq, Debug)]
pub enum ImageRendering {
OptimizeQuality,
OptimizeSpeed,
}
impl Default for ImageRendering {
fn default() -> Self {
Self::OptimizeQuality
}
}
impl std::str::FromStr for ImageRendering {
type Err = &'static str;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s {
"optimizeQuality" => Ok(ImageRendering::OptimizeQuality),
"optimizeSpeed" => Ok(ImageRendering::OptimizeSpeed),
_ => Err("invalid"),
}
}
}
#[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 Default for BlendMode {
fn default() -> Self {
Self::Normal
}
}
#[allow(missing_docs)]
#[derive(Clone, Copy, PartialEq, Debug)]
pub enum SpreadMethod {
Pad,
Reflect,
Repeat,
}
impl Default for SpreadMethod {
fn default() -> Self {
Self::Pad
}
}
#[derive(Clone, Debug)]
pub struct BaseGradient {
pub units: Units,
pub transform: Transform,
pub spread_method: SpreadMethod,
pub stops: Vec<Stop>,
}
#[allow(missing_docs)]
#[derive(Clone, Debug)]
pub struct LinearGradient {
pub id: String,
pub x1: f64,
pub y1: f64,
pub x2: f64,
pub y2: f64,
pub base: BaseGradient,
}
impl std::ops::Deref for LinearGradient {
type Target = BaseGradient;
fn deref(&self) -> &Self::Target {
&self.base
}
}
#[allow(missing_docs)]
#[derive(Clone, Debug)]
pub struct RadialGradient {
pub id: String,
pub cx: f64,
pub cy: f64,
pub r: PositiveF64,
pub fx: f64,
pub fy: f64,
pub base: BaseGradient,
}
impl std::ops::Deref for RadialGradient {
type Target = BaseGradient;
fn deref(&self) -> &Self::Target {
&self.base
}
}
pub type StopOffset = NormalizedF64;
#[derive(Clone, Copy, Debug)]
pub struct Stop {
pub offset: StopOffset,
pub color: Color,
pub opacity: Opacity,
}
#[derive(Clone, Debug)]
pub struct Pattern {
pub id: String,
pub units: Units,
pub content_units: Units,
pub transform: Transform,
pub rect: Rect,
pub view_box: Option<ViewBox>,
pub root: Node,
}
pub type StrokeWidth = NonZeroPositiveF64;
#[derive(Clone, Copy, Debug)]
pub struct StrokeMiterlimit(f64);
impl StrokeMiterlimit {
#[inline]
pub fn new(n: f64) -> Self {
debug_assert!(n.is_finite());
debug_assert!(n >= 1.0);
let n = if !(n >= 1.0) { 1.0 } else { n };
StrokeMiterlimit(n)
}
#[inline]
pub fn get(&self) -> f64 {
self.0
}
}
impl Default for StrokeMiterlimit {
#[inline]
fn default() -> Self {
StrokeMiterlimit::new(4.0)
}
}
impl From<f64> for StrokeMiterlimit {
#[inline]
fn from(n: f64) -> Self {
Self::new(n)
}
}
impl PartialEq for StrokeMiterlimit {
#[inline]
fn eq(&self, other: &Self) -> bool {
self.0.fuzzy_eq(&other.0)
}
}
#[allow(missing_docs)]
#[derive(Clone, Copy, PartialEq, Debug)]
pub enum LineCap {
Butt,
Round,
Square,
}
impl Default for LineCap {
fn default() -> Self {
Self::Butt
}
}
#[allow(missing_docs)]
#[derive(Clone, Copy, PartialEq, Debug)]
pub enum LineJoin {
Miter,
Round,
Bevel,
}
impl Default for LineJoin {
fn default() -> Self {
Self::Miter
}
}
#[allow(missing_docs)]
#[derive(Clone, Debug)]
pub struct Stroke {
pub paint: Paint,
pub dasharray: Option<Vec<f64>>,
pub dashoffset: f32, pub miterlimit: StrokeMiterlimit,
pub opacity: Opacity,
pub width: StrokeWidth,
pub linecap: LineCap,
pub linejoin: LineJoin,
}
impl Default for Stroke {
fn default() -> Self {
Stroke {
paint: Paint::Color(Color::black()),
dasharray: None,
dashoffset: 0.0,
miterlimit: StrokeMiterlimit::default(),
opacity: Opacity::ONE,
width: StrokeWidth::new(1.0).unwrap(),
linecap: LineCap::default(),
linejoin: LineJoin::default(),
}
}
}
#[allow(missing_docs)]
#[derive(Clone, Copy, PartialEq, Debug)]
pub enum FillRule {
NonZero,
EvenOdd,
}
impl Default for FillRule {
fn default() -> Self {
Self::NonZero
}
}
#[allow(missing_docs)]
#[derive(Clone, Debug)]
pub struct Fill {
pub paint: Paint,
pub opacity: Opacity,
pub rule: FillRule,
}
impl Fill {
pub fn from_paint(paint: Paint) -> Self {
Fill {
paint,
..Fill::default()
}
}
}
impl Default for Fill {
fn default() -> Self {
Fill {
paint: Paint::Color(Color::black()),
opacity: Opacity::ONE,
rule: FillRule::default(),
}
}
}
#[derive(Clone, Copy, PartialEq, Debug)]
#[allow(missing_docs)]
pub struct Color {
pub red: u8,
pub green: u8,
pub blue: u8,
}
impl Color {
#[inline]
pub fn new_rgb(red: u8, green: u8, blue: u8) -> Color {
Color { red, green, blue }
}
#[inline]
pub fn black() -> Color {
Color::new_rgb(0, 0, 0)
}
#[inline]
pub fn white() -> Color {
Color::new_rgb(255, 255, 255)
}
}
#[allow(missing_docs)]
#[derive(Clone, Debug)]
pub enum Paint {
Color(Color),
LinearGradient(Rc<LinearGradient>),
RadialGradient(Rc<RadialGradient>),
Pattern(Rc<Pattern>),
}
impl Paint {
#[inline]
pub fn units(&self) -> Option<Units> {
match self {
Self::Color(_) => None,
Self::LinearGradient(ref lg) => Some(lg.units),
Self::RadialGradient(ref rg) => Some(rg.units),
Self::Pattern(ref patt) => Some(patt.units),
}
}
}
impl PartialEq for Paint {
#[inline]
fn eq(&self, other: &Self) -> bool {
match (self, other) {
(Self::Color(lc), Self::Color(rc)) => lc == rc,
(Self::LinearGradient(ref lg1), Self::LinearGradient(ref lg2)) => Rc::ptr_eq(lg1, lg2),
(Self::RadialGradient(ref rg1), Self::RadialGradient(ref rg2)) => Rc::ptr_eq(rg1, rg2),
(Self::Pattern(ref p1), Self::Pattern(ref p2)) => Rc::ptr_eq(p1, p2),
_ => false,
}
}
}
#[derive(Clone, Debug)]
pub struct ClipPath {
pub id: String,
pub units: Units,
pub transform: Transform,
pub clip_path: Option<Rc<Self>>,
pub root: Node,
}
impl Default for ClipPath {
fn default() -> Self {
ClipPath {
id: String::new(),
units: Units::UserSpaceOnUse,
transform: Transform::default(),
clip_path: None,
root: Node::new(NodeKind::Group(Group::default())),
}
}
}
#[derive(Clone, Copy, PartialEq, Debug)]
pub enum MaskType {
Luminance,
Alpha,
}
impl Default for MaskType {
fn default() -> Self {
Self::Luminance
}
}
#[derive(Clone, Debug)]
pub struct Mask {
pub id: String,
pub units: Units,
pub content_units: Units,
pub rect: Rect,
pub kind: MaskType,
pub mask: Option<Rc<Self>>,
pub root: Node,
}
#[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, 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>,
}
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,
}
}
}
impl Group {
pub fn should_isolate(&self) -> bool {
self.isolate
|| self.opacity != Opacity::ONE
|| self.clip_path.is_some()
|| self.mask.is_some()
|| !self.filters.is_empty()
|| self.blend_mode != BlendMode::Normal }
}
#[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)]
pub enum ImageKind {
JPEG(Arc<Vec<u8>>),
PNG(Arc<Vec<u8>>),
GIF(Arc<Vec<u8>>),
SVG(Tree),
}
impl std::fmt::Debug for ImageKind {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
match self {
ImageKind::JPEG(_) => f.write_str("ImageKind::JPEG(..)"),
ImageKind::PNG(_) => f.write_str("ImageKind::PNG(..)"),
ImageKind::GIF(_) => f.write_str("ImageKind::GIF(..)"),
ImageKind::SVG(_) => f.write_str("ImageKind::SVG(..)"),
}
}
}
#[derive(Clone, Debug)]
pub struct Image {
pub id: String,
pub transform: Transform,
pub visibility: Visibility,
pub view_box: ViewBox,
pub rendering_mode: ImageRendering,
pub kind: ImageKind,
}
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 node_by_id(&self, id: &str) -> Option<Node> {
if id.is_empty() {
return None;
}
self.root.descendants().find(|node| &*node.id() == id)
}
pub fn has_text_nodes(&self) -> bool {
has_text_nodes(&self.root)
}
pub fn paint_servers<F: FnMut(&Paint)>(&self, mut f: F) {
loop_over_paint_servers(&self.root, &mut f)
}
pub fn clip_paths<F: FnMut(Rc<ClipPath>)>(&self, mut f: F) {
loop_over_clip_paths(&self.root, &mut f)
}
pub fn masks<F: FnMut(Rc<Mask>)>(&self, mut f: F) {
loop_over_masks(&self.root, &mut f)
}
pub fn filters<F: FnMut(Rc<filter::Filter>)>(&self, mut f: F) {
loop_over_filters(&self.root, &mut f)
}
}
fn has_text_nodes(root: &Node) -> bool {
for node in root.descendants() {
if let NodeKind::Text(_) = *node.borrow() {
return true;
}
let mut has_text = false;
node.subroots(|subroot| {
if has_text_nodes(&subroot) {
has_text = true;
}
});
if has_text {
return true;
}
}
false
}
fn loop_over_paint_servers(root: &Node, f: &mut dyn FnMut(&Paint)) {
fn push(paint: Option<&Paint>, f: &mut dyn FnMut(&Paint)) {
if let Some(paint) = paint {
f(paint);
}
}
for node in root.descendants() {
if let NodeKind::Group(ref group) = *node.borrow() {
push(group.filter_fill.as_ref(), f);
push(group.filter_stroke.as_ref(), f);
} else if let NodeKind::Path(ref path) = *node.borrow() {
push(path.fill.as_ref().map(|f| &f.paint), f);
push(path.stroke.as_ref().map(|f| &f.paint), f);
} else if let NodeKind::Text(ref text) = *node.borrow() {
for chunk in &text.chunks {
for span in &chunk.spans {
push(span.fill.as_ref().map(|f| &f.paint), f);
push(span.stroke.as_ref().map(|f| &f.paint), f);
if let Some(ref underline) = span.decoration.underline {
push(underline.fill.as_ref().map(|f| &f.paint), f);
push(underline.stroke.as_ref().map(|f| &f.paint), f);
}
if let Some(ref overline) = span.decoration.overline {
push(overline.fill.as_ref().map(|f| &f.paint), f);
push(overline.stroke.as_ref().map(|f| &f.paint), f);
}
if let Some(ref line_through) = span.decoration.line_through {
push(line_through.fill.as_ref().map(|f| &f.paint), f);
push(line_through.stroke.as_ref().map(|f| &f.paint), f);
}
}
}
}
node.subroots(|subroot| loop_over_paint_servers(&subroot, f));
}
}
fn loop_over_clip_paths(root: &Node, f: &mut dyn FnMut(Rc<ClipPath>)) {
for node in root.descendants() {
if let NodeKind::Group(ref g) = *node.borrow() {
if let Some(ref clip) = g.clip_path {
f(clip.clone());
if let Some(ref sub_clip) = clip.clip_path {
f(sub_clip.clone());
}
}
}
node.subroots(|subroot| loop_over_clip_paths(&subroot, f));
}
}
fn loop_over_masks(root: &Node, f: &mut dyn FnMut(Rc<Mask>)) {
for node in root.descendants() {
if let NodeKind::Group(ref g) = *node.borrow() {
if let Some(ref mask) = g.mask {
f(mask.clone());
if let Some(ref sub_mask) = mask.mask {
f(sub_mask.clone());
}
}
}
node.subroots(|subroot| loop_over_masks(&subroot, f));
}
}
fn loop_over_filters(root: &Node, f: &mut dyn FnMut(Rc<filter::Filter>)) {
for node in root.descendants() {
if let NodeKind::Group(ref g) = *node.borrow() {
for filter in &g.filters {
f(filter.clone());
}
}
node.subroots(|subroot| loop_over_filters(&subroot, f));
}
}
fn node_subroots(node: &Node, f: &mut dyn FnMut(Node)) {
let mut push_patt = |paint: Option<&Paint>| {
if let Some(Paint::Pattern(ref patt)) = paint {
f(patt.root.clone());
}
};
match *node.borrow() {
NodeKind::Group(ref g) => {
push_patt(g.filter_fill.as_ref());
push_patt(g.filter_stroke.as_ref());
if let Some(ref clip) = g.clip_path {
f(clip.root.clone());
if let Some(ref sub_clip) = clip.clip_path {
f(sub_clip.root.clone());
}
}
if let Some(ref mask) = g.mask {
f(mask.root.clone());
if let Some(ref sub_mask) = mask.mask {
f(sub_mask.root.clone());
}
}
for filter in &g.filters {
for primitive in &filter.primitives {
if let filter::Kind::Image(ref image) = primitive.kind {
if let filter::ImageKind::Use(ref use_node) = image.data {
f(use_node.clone());
}
}
}
}
}
NodeKind::Path(ref path) => {
push_patt(path.fill.as_ref().map(|f| &f.paint));
push_patt(path.stroke.as_ref().map(|f| &f.paint));
}
NodeKind::Image(_) => {}
NodeKind::Text(ref text) => {
for chunk in &text.chunks {
for span in &chunk.spans {
push_patt(span.fill.as_ref().map(|f| &f.paint));
push_patt(span.stroke.as_ref().map(|f| &f.paint));
if let Some(ref underline) = span.decoration.underline {
push_patt(underline.fill.as_ref().map(|f| &f.paint));
push_patt(underline.stroke.as_ref().map(|f| &f.paint));
}
if let Some(ref overline) = span.decoration.overline {
push_patt(overline.fill.as_ref().map(|f| &f.paint));
push_patt(overline.stroke.as_ref().map(|f| &f.paint));
}
if let Some(ref line_through) = span.decoration.line_through {
push_patt(line_through.fill.as_ref().map(|f| &f.paint));
push_patt(line_through.stroke.as_ref().map(|f| &f.paint));
}
}
}
}
}
}
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 subroots<F: FnMut(Node)>(&self, f: F);
}
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 subroots<F: FnMut(Node)>(&self, mut f: F) {
node_subroots(self, &mut f)
}
}
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;
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,
}
}