#![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 text;
use std::rc::Rc;
use std::sync::Arc;
pub use strict_num::{self, ApproxEqUlps, NonZeroPositiveF32, NormalizedF32, PositiveF32};
pub use svgtypes::{Align, AspectRatio};
pub use tiny_skia_path;
pub use crate::geom::*;
pub use crate::text::*;
pub type Opacity = NormalizedF32;
#[derive(Clone, Copy, Debug)]
pub struct NonZeroF32(f32);
impl NonZeroF32 {
#[inline]
pub fn new(n: f32) -> Option<Self> {
if n.approx_eq_ulps(&0.0, 4) {
None
} else {
Some(NonZeroF32(n))
}
}
#[inline]
pub fn get(&self) -> f32 {
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 id: String,
pub units: Units,
pub transform: Transform,
pub spread_method: SpreadMethod,
pub stops: Vec<Stop>,
}
#[allow(missing_docs)]
#[derive(Clone, Debug)]
pub struct LinearGradient {
pub base: BaseGradient,
pub x1: f32,
pub y1: f32,
pub x2: f32,
pub y2: f32,
}
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 base: BaseGradient,
pub cx: f32,
pub cy: f32,
pub r: PositiveF32,
pub fx: f32,
pub fy: f32,
}
impl std::ops::Deref for RadialGradient {
type Target = BaseGradient;
fn deref(&self) -> &Self::Target {
&self.base
}
}
pub type StopOffset = NormalizedF32;
#[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: NonZeroRect,
pub view_box: Option<ViewBox>,
pub root: Node,
}
pub type StrokeWidth = NonZeroPositiveF32;
#[derive(Clone, Copy, Debug)]
pub struct StrokeMiterlimit(f32);
impl StrokeMiterlimit {
#[inline]
pub fn new(n: f32) -> 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) -> f32 {
self.0
}
}
impl Default for StrokeMiterlimit {
#[inline]
fn default() -> Self {
StrokeMiterlimit::new(4.0)
}
}
impl From<f32> for StrokeMiterlimit {
#[inline]
fn from(n: f32) -> Self {
Self::new(n)
}
}
impl PartialEq for StrokeMiterlimit {
#[inline]
fn eq(&self, other: &Self) -> bool {
self.0.approx_eq_ulps(&other.0, 4)
}
}
#[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,
MiterClip,
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<f32>>,
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: NonZeroRect,
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(),
}
}
}
#[derive(Clone, Debug)]
pub struct Group {
pub id: String,
pub transform: Transform,
pub abs_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>>,
}
impl Default for Group {
fn default() -> Self {
Group {
id: String::new(),
transform: Transform::default(),
abs_transform: Transform::default(),
opacity: Opacity::ONE,
blend_mode: BlendMode::Normal,
isolate: false,
clip_path: None,
mask: None,
filters: Vec::new(),
}
}
}
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 visibility: Visibility,
pub fill: Option<Fill>,
pub stroke: Option<Stroke>,
pub paint_order: PaintOrder,
pub rendering_mode: ShapeRendering,
pub data: Rc<tiny_skia_path::Path>,
}
impl Path {
pub fn new(data: Rc<tiny_skia_path::Path>) -> Self {
Path {
id: String::new(),
visibility: Visibility::Visible,
fill: None,
stroke: None,
paint_order: PaintOrder::default(),
rendering_mode: ShapeRendering::default(),
data,
}
}
}
#[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 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)
}
pub fn calculate_abs_transforms(&mut self) {
calculate_abs_transform(&self.root, Transform::identity());
}
}
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;
if let NodeKind::Image(ref image) = *node.borrow() {
if let ImageKind::SVG(ref tree) = image.kind {
if has_text_nodes(&tree.root) {
has_text = true;
}
}
}
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::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() {
if text.flattened.is_none() {
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) => {
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) => {
if let Some(ref flattened) = text.flattened {
f(flattened.clone());
return;
}
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 abs_transform(&self) -> Transform;
fn append_kind(&self, kind: NodeKind) -> Node;
fn calculate_bbox(&self) -> Option<Rect>;
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())
}
fn abs_transform(&self) -> Transform {
if let NodeKind::Group(ref g) = *self.borrow() {
g.abs_transform
} else {
self.parent().map(|n| n.abs_transform()).unwrap_or_default()
}
}
#[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<Rect> {
calc_node_bbox(self, self.abs_transform()).and_then(|r| r.to_rect())
}
fn subroots<F: FnMut(Node)>(&self, mut f: F) {
node_subroots(self, &mut f)
}
}
fn calc_node_bbox(node: &Node, ts: Transform) -> Option<BBox> {
match *node.borrow() {
NodeKind::Path(ref path) => path
.data
.compute_tight_bounds()?
.transform(ts)
.map(BBox::from),
NodeKind::Image(ref img) => img.view_box.rect.transform(ts).map(BBox::from),
NodeKind::Group(_) => {
let mut bbox = BBox::default();
for child in node.children() {
let child_transform = if let NodeKind::Group(ref group) = *child.borrow() {
ts.pre_concat(group.transform)
} else {
ts
};
if let Some(c_bbox) = calc_node_bbox(&child, child_transform) {
bbox = bbox.expand(c_bbox);
}
}
if bbox.is_default() {
return None;
}
Some(bbox)
}
NodeKind::Text(ref text) => {
if let Some(bbox) = text.bounding_box {
bbox.transform(ts).map(BBox::from)
} else {
None
}
}
}
}
fn calculate_abs_transform(node: &Node, ts: Transform) {
if matches!(*node.borrow(), NodeKind::Group(_)) {
let mut abs_ts = ts;
if let NodeKind::Group(ref mut group) = *node.borrow_mut() {
group.abs_transform = ts.pre_concat(group.transform);
abs_ts = group.abs_transform;
}
for child in node.children() {
calculate_abs_transform(&child, abs_ts);
}
}
node.subroots(|root| calculate_abs_transform(&root, Transform::identity()));
}