pub mod filter;
mod geom;
mod text;
use std::fmt::Display;
use std::sync::Arc;
pub use strict_num::{self, ApproxEqUlps, NonZeroPositiveF32, NormalizedF32, PositiveF32};
pub use tiny_skia_path;
pub use self::geom::*;
pub use self::text::*;
use crate::OptionLog;
pub type Opacity = NormalizedF32;
#[derive(Debug)]
pub(crate) struct NonEmptyString(String);
impl NonEmptyString {
pub(crate) fn new(string: String) -> Option<Self> {
if string.trim().is_empty() {
return None;
}
Some(NonEmptyString(string))
}
pub(crate) fn get(&self) -> &str {
&self.0
}
pub(crate) fn take(self) -> String {
self.0
}
}
#[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
}
}
#[derive(Clone, Copy, PartialEq, Debug)]
pub(crate) enum Units {
UserSpaceOnUse,
ObjectBoundingBox,
}
#[allow(missing_docs)]
#[derive(Clone, Copy, PartialEq, Debug)]
pub(crate) 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,
Smooth,
HighQuality,
CrispEdges,
Pixelated,
}
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),
"smooth" => Ok(ImageRendering::Smooth),
"high-quality" => Ok(ImageRendering::HighQuality),
"crisp-edges" => Ok(ImageRendering::CrispEdges),
"pixelated" => Ok(ImageRendering::Pixelated),
_ => 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
}
}
impl Display for BlendMode {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let blend_mode = match self {
BlendMode::Normal => "normal",
BlendMode::Multiply => "multiply",
BlendMode::Screen => "screen",
BlendMode::Overlay => "overlay",
BlendMode::Darken => "darken",
BlendMode::Lighten => "lighten",
BlendMode::ColorDodge => "color-dodge",
BlendMode::ColorBurn => "color-burn",
BlendMode::HardLight => "hard-light",
BlendMode::SoftLight => "soft-light",
BlendMode::Difference => "difference",
BlendMode::Exclusion => "exclusion",
BlendMode::Hue => "hue",
BlendMode::Saturation => "saturation",
BlendMode::Color => "color",
BlendMode::Luminosity => "luminosity",
};
write!(f, "{blend_mode}")
}
}
#[allow(missing_docs)]
#[derive(Clone, Copy, PartialEq, Debug)]
pub enum SpreadMethod {
Pad,
Reflect,
Repeat,
}
impl Default for SpreadMethod {
fn default() -> Self {
Self::Pad
}
}
#[derive(Debug)]
pub struct BaseGradient {
pub(crate) id: NonEmptyString,
pub(crate) units: Units, pub(crate) transform: Transform,
pub(crate) spread_method: SpreadMethod,
pub(crate) stops: Vec<Stop>,
}
impl BaseGradient {
pub fn id(&self) -> &str {
self.id.get()
}
pub fn transform(&self) -> Transform {
self.transform
}
pub fn spread_method(&self) -> SpreadMethod {
self.spread_method
}
pub fn stops(&self) -> &[Stop] {
&self.stops
}
}
#[derive(Debug)]
pub struct LinearGradient {
pub(crate) base: BaseGradient,
pub(crate) x1: f32,
pub(crate) y1: f32,
pub(crate) x2: f32,
pub(crate) y2: f32,
}
impl LinearGradient {
pub fn x1(&self) -> f32 {
self.x1
}
pub fn y1(&self) -> f32 {
self.y1
}
pub fn x2(&self) -> f32 {
self.x2
}
pub fn y2(&self) -> f32 {
self.y2
}
}
impl std::ops::Deref for LinearGradient {
type Target = BaseGradient;
fn deref(&self) -> &Self::Target {
&self.base
}
}
#[derive(Debug)]
pub struct RadialGradient {
pub(crate) base: BaseGradient,
pub(crate) cx: f32,
pub(crate) cy: f32,
pub(crate) r: PositiveF32,
pub(crate) fx: f32,
pub(crate) fy: f32,
pub(crate) fr: PositiveF32,
}
impl RadialGradient {
pub fn cx(&self) -> f32 {
self.cx
}
pub fn cy(&self) -> f32 {
self.cy
}
pub fn r(&self) -> PositiveF32 {
self.r
}
pub fn fx(&self) -> f32 {
self.fx
}
pub fn fy(&self) -> f32 {
self.fy
}
pub fn fr(&self) -> PositiveF32 {
self.fr
}
}
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(crate) offset: StopOffset,
pub(crate) color: Color,
pub(crate) opacity: Opacity,
}
impl Stop {
pub fn offset(&self) -> StopOffset {
self.offset
}
pub fn color(&self) -> Color {
self.color
}
pub fn opacity(&self) -> Opacity {
self.opacity
}
}
#[derive(Debug)]
pub struct Pattern {
pub(crate) id: NonEmptyString,
pub(crate) units: Units, pub(crate) content_units: Units, pub(crate) transform: Transform,
pub(crate) rect: NonZeroRect,
pub(crate) view_box: Option<ViewBox>,
pub(crate) root: Group,
}
impl Pattern {
pub fn id(&self) -> &str {
self.id.get()
}
pub fn transform(&self) -> Transform {
self.transform
}
pub fn rect(&self) -> NonZeroRect {
self.rect
}
pub fn root(&self) -> &Group {
&self.root
}
}
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
}
}
#[derive(Clone, Debug)]
pub struct Stroke {
pub(crate) paint: Paint,
pub(crate) dasharray: Option<Vec<f32>>,
pub(crate) dashoffset: f32,
pub(crate) miterlimit: StrokeMiterlimit,
pub(crate) opacity: Opacity,
pub(crate) width: StrokeWidth,
pub(crate) linecap: LineCap,
pub(crate) linejoin: LineJoin,
pub(crate) context_element: Option<ContextElement>,
}
impl Stroke {
pub fn paint(&self) -> &Paint {
&self.paint
}
pub fn dasharray(&self) -> Option<&[f32]> {
self.dasharray.as_deref()
}
pub fn dashoffset(&self) -> f32 {
self.dashoffset
}
pub fn miterlimit(&self) -> StrokeMiterlimit {
self.miterlimit
}
pub fn opacity(&self) -> Opacity {
self.opacity
}
pub fn width(&self) -> StrokeWidth {
self.width
}
pub fn linecap(&self) -> LineCap {
self.linecap
}
pub fn linejoin(&self) -> LineJoin {
self.linejoin
}
pub fn to_tiny_skia(&self) -> tiny_skia_path::Stroke {
let mut stroke = tiny_skia_path::Stroke {
width: self.width.get(),
miter_limit: self.miterlimit.get(),
line_cap: match self.linecap {
LineCap::Butt => tiny_skia_path::LineCap::Butt,
LineCap::Round => tiny_skia_path::LineCap::Round,
LineCap::Square => tiny_skia_path::LineCap::Square,
},
line_join: match self.linejoin {
LineJoin::Miter => tiny_skia_path::LineJoin::Miter,
LineJoin::MiterClip => tiny_skia_path::LineJoin::MiterClip,
LineJoin::Round => tiny_skia_path::LineJoin::Round,
LineJoin::Bevel => tiny_skia_path::LineJoin::Bevel,
},
dash: None,
};
if let Some(ref list) = self.dasharray {
stroke.dash = tiny_skia_path::StrokeDash::new(list.clone(), self.dashoffset);
}
stroke
}
}
#[allow(missing_docs)]
#[derive(Clone, Copy, PartialEq, Debug)]
pub enum FillRule {
NonZero,
EvenOdd,
}
impl Default for FillRule {
fn default() -> Self {
Self::NonZero
}
}
#[derive(Clone, Copy, Debug)]
pub(crate) enum ContextElement {
UseNode,
PathNode(Transform, Option<NonZeroRect>),
}
#[derive(Clone, Debug)]
pub struct Fill {
pub(crate) paint: Paint,
pub(crate) opacity: Opacity,
pub(crate) rule: FillRule,
pub(crate) context_element: Option<ContextElement>,
}
impl Fill {
pub fn paint(&self) -> &Paint {
&self.paint
}
pub fn opacity(&self) -> Opacity {
self.opacity
}
pub fn rule(&self) -> FillRule {
self.rule
}
}
impl Default for Fill {
fn default() -> Self {
Fill {
paint: Paint::Color(Color::black()),
opacity: Opacity::ONE,
rule: FillRule::default(),
context_element: None,
}
}
}
#[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(Arc<LinearGradient>),
RadialGradient(Arc<RadialGradient>),
Pattern(Arc<Pattern>),
}
impl PartialEq for Paint {
#[inline]
fn eq(&self, other: &Self) -> bool {
match (self, other) {
(Self::Color(lc), Self::Color(rc)) => lc == rc,
(Self::LinearGradient(lg1), Self::LinearGradient(lg2)) => Arc::ptr_eq(lg1, lg2),
(Self::RadialGradient(rg1), Self::RadialGradient(rg2)) => Arc::ptr_eq(rg1, rg2),
(Self::Pattern(p1), Self::Pattern(p2)) => Arc::ptr_eq(p1, p2),
_ => false,
}
}
}
#[derive(Debug)]
pub struct ClipPath {
pub(crate) id: NonEmptyString,
pub(crate) transform: Transform,
pub(crate) clip_path: Option<Arc<ClipPath>>,
pub(crate) root: Group,
}
impl ClipPath {
pub(crate) fn empty(id: NonEmptyString) -> Self {
ClipPath {
id,
transform: Transform::default(),
clip_path: None,
root: Group::empty(),
}
}
pub fn id(&self) -> &str {
self.id.get()
}
pub fn transform(&self) -> Transform {
self.transform
}
pub fn clip_path(&self) -> Option<&ClipPath> {
self.clip_path.as_deref()
}
pub fn root(&self) -> &Group {
&self.root
}
}
#[derive(Clone, Copy, PartialEq, Debug)]
pub enum MaskType {
Luminance,
Alpha,
}
impl Default for MaskType {
fn default() -> Self {
Self::Luminance
}
}
#[derive(Debug)]
pub struct Mask {
pub(crate) id: NonEmptyString,
pub(crate) rect: NonZeroRect,
pub(crate) kind: MaskType,
pub(crate) mask: Option<Arc<Mask>>,
pub(crate) root: Group,
}
impl Mask {
pub fn id(&self) -> &str {
self.id.get()
}
pub fn rect(&self) -> NonZeroRect {
self.rect
}
pub fn kind(&self) -> MaskType {
self.kind
}
pub fn mask(&self) -> Option<&Mask> {
self.mask.as_deref()
}
pub fn root(&self) -> &Group {
&self.root
}
}
#[allow(missing_docs)]
#[derive(Clone, Debug)]
pub enum Node {
Group(Box<Group>),
Path(Box<Path>),
Image(Box<Image>),
Text(Box<Text>),
}
impl Node {
pub fn id(&self) -> &str {
match self {
Node::Group(e) => e.id.as_str(),
Node::Path(e) => e.id.as_str(),
Node::Image(e) => e.id.as_str(),
Node::Text(e) => e.id.as_str(),
}
}
pub fn abs_transform(&self) -> Transform {
match self {
Node::Group(group) => group.abs_transform(),
Node::Path(path) => path.abs_transform(),
Node::Image(image) => image.abs_transform(),
Node::Text(text) => text.abs_transform(),
}
}
pub fn bounding_box(&self) -> Rect {
match self {
Node::Group(group) => group.bounding_box(),
Node::Path(path) => path.bounding_box(),
Node::Image(image) => image.bounding_box(),
Node::Text(text) => text.bounding_box(),
}
}
pub fn abs_bounding_box(&self) -> Rect {
match self {
Node::Group(group) => group.abs_bounding_box(),
Node::Path(path) => path.abs_bounding_box(),
Node::Image(image) => image.abs_bounding_box(),
Node::Text(text) => text.abs_bounding_box(),
}
}
pub fn stroke_bounding_box(&self) -> Rect {
match self {
Node::Group(group) => group.stroke_bounding_box(),
Node::Path(path) => path.stroke_bounding_box(),
Node::Image(image) => image.bounding_box(),
Node::Text(text) => text.stroke_bounding_box(),
}
}
pub fn abs_stroke_bounding_box(&self) -> Rect {
match self {
Node::Group(group) => group.abs_stroke_bounding_box(),
Node::Path(path) => path.abs_stroke_bounding_box(),
Node::Image(image) => image.abs_bounding_box(),
Node::Text(text) => text.abs_stroke_bounding_box(),
}
}
pub fn abs_layer_bounding_box(&self) -> Option<NonZeroRect> {
match self {
Node::Group(group) => Some(group.abs_layer_bounding_box()),
Node::Path(path) => path.abs_bounding_box().to_non_zero_rect(),
Node::Image(image) => image.abs_bounding_box().to_non_zero_rect(),
Node::Text(text) => text.abs_bounding_box().to_non_zero_rect(),
}
}
pub fn subroots<F: FnMut(&Group)>(&self, mut f: F) {
match self {
Node::Group(group) => group.subroots(&mut f),
Node::Path(path) => path.subroots(&mut f),
Node::Image(image) => image.subroots(&mut f),
Node::Text(text) => text.subroots(&mut f),
}
}
}
#[derive(Clone, Debug)]
pub struct Group {
pub(crate) id: String,
pub(crate) transform: Transform,
pub(crate) abs_transform: Transform,
pub(crate) opacity: Opacity,
pub(crate) blend_mode: BlendMode,
pub(crate) isolate: bool,
pub(crate) clip_path: Option<Arc<ClipPath>>,
pub(crate) is_context_element: bool,
pub(crate) mask: Option<Arc<Mask>>,
pub(crate) filters: Vec<Arc<filter::Filter>>,
pub(crate) bounding_box: Rect,
pub(crate) abs_bounding_box: Rect,
pub(crate) stroke_bounding_box: Rect,
pub(crate) abs_stroke_bounding_box: Rect,
pub(crate) layer_bounding_box: NonZeroRect,
pub(crate) abs_layer_bounding_box: NonZeroRect,
pub(crate) children: Vec<Node>,
}
impl Group {
pub(crate) fn empty() -> Self {
let dummy = Rect::from_xywh(0.0, 0.0, 0.0, 0.0).unwrap();
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(),
is_context_element: false,
bounding_box: dummy,
abs_bounding_box: dummy,
stroke_bounding_box: dummy,
abs_stroke_bounding_box: dummy,
layer_bounding_box: NonZeroRect::from_xywh(0.0, 0.0, 1.0, 1.0).unwrap(),
abs_layer_bounding_box: NonZeroRect::from_xywh(0.0, 0.0, 1.0, 1.0).unwrap(),
children: Vec::new(),
}
}
pub fn id(&self) -> &str {
&self.id
}
pub fn transform(&self) -> Transform {
self.transform
}
pub fn abs_transform(&self) -> Transform {
self.abs_transform
}
pub fn opacity(&self) -> Opacity {
self.opacity
}
pub fn blend_mode(&self) -> BlendMode {
self.blend_mode
}
pub fn isolate(&self) -> bool {
self.isolate
}
pub fn clip_path(&self) -> Option<&ClipPath> {
self.clip_path.as_deref()
}
pub fn mask(&self) -> Option<&Mask> {
self.mask.as_deref()
}
pub fn filters(&self) -> &[Arc<filter::Filter>] {
&self.filters
}
pub fn bounding_box(&self) -> Rect {
self.bounding_box
}
pub fn abs_bounding_box(&self) -> Rect {
self.abs_bounding_box
}
pub fn stroke_bounding_box(&self) -> Rect {
self.stroke_bounding_box
}
pub fn abs_stroke_bounding_box(&self) -> Rect {
self.abs_stroke_bounding_box
}
pub fn layer_bounding_box(&self) -> NonZeroRect {
self.layer_bounding_box
}
pub fn abs_layer_bounding_box(&self) -> NonZeroRect {
self.abs_layer_bounding_box
}
pub fn children(&self) -> &[Node] {
&self.children
}
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 }
pub fn has_children(&self) -> bool {
!self.children.is_empty()
}
pub fn filters_bounding_box(&self) -> Option<NonZeroRect> {
let mut full_region = BBox::default();
for filter in &self.filters {
full_region = full_region.expand(filter.rect);
}
full_region.to_non_zero_rect()
}
fn subroots(&self, f: &mut dyn FnMut(&Group)) {
if let Some(ref clip) = self.clip_path {
f(&clip.root);
if let Some(ref sub_clip) = clip.clip_path {
f(&sub_clip.root);
}
}
if let Some(ref mask) = self.mask {
f(&mask.root);
if let Some(ref sub_mask) = mask.mask {
f(&sub_mask.root);
}
}
for filter in &self.filters {
for primitive in &filter.primitives {
if let filter::Kind::Image(ref image) = primitive.kind {
f(image.root());
}
}
}
}
}
#[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(crate) id: String,
pub(crate) visible: bool,
pub(crate) fill: Option<Fill>,
pub(crate) stroke: Option<Stroke>,
pub(crate) paint_order: PaintOrder,
pub(crate) rendering_mode: ShapeRendering,
pub(crate) data: Arc<tiny_skia_path::Path>,
pub(crate) abs_transform: Transform,
pub(crate) bounding_box: Rect,
pub(crate) abs_bounding_box: Rect,
pub(crate) stroke_bounding_box: Rect,
pub(crate) abs_stroke_bounding_box: Rect,
}
impl Path {
pub(crate) fn new_simple(data: Arc<tiny_skia_path::Path>) -> Option<Self> {
Self::new(
String::new(),
true,
None,
None,
PaintOrder::default(),
ShapeRendering::default(),
data,
Transform::default(),
)
}
pub(crate) fn new(
id: String,
visible: bool,
fill: Option<Fill>,
stroke: Option<Stroke>,
paint_order: PaintOrder,
rendering_mode: ShapeRendering,
data: Arc<tiny_skia_path::Path>,
abs_transform: Transform,
) -> Option<Self> {
let bounding_box = data.compute_tight_bounds()?;
let stroke_bounding_box =
Path::calculate_stroke_bbox(stroke.as_ref(), &data).unwrap_or(bounding_box);
let abs_bounding_box: Rect;
let abs_stroke_bounding_box: Rect;
if abs_transform.has_skew() {
let path2 = data.as_ref().clone();
let path2 = path2.transform(abs_transform)?;
abs_bounding_box = path2.compute_tight_bounds()?;
abs_stroke_bounding_box =
Path::calculate_stroke_bbox(stroke.as_ref(), &path2).unwrap_or(abs_bounding_box);
} else {
abs_bounding_box = bounding_box.transform(abs_transform)?;
abs_stroke_bounding_box = stroke_bounding_box.transform(abs_transform)?;
}
Some(Path {
id,
visible,
fill,
stroke,
paint_order,
rendering_mode,
data,
abs_transform,
bounding_box,
abs_bounding_box,
stroke_bounding_box,
abs_stroke_bounding_box,
})
}
pub fn id(&self) -> &str {
&self.id
}
pub fn is_visible(&self) -> bool {
self.visible
}
pub fn fill(&self) -> Option<&Fill> {
self.fill.as_ref()
}
pub fn stroke(&self) -> Option<&Stroke> {
self.stroke.as_ref()
}
pub fn paint_order(&self) -> PaintOrder {
self.paint_order
}
pub fn rendering_mode(&self) -> ShapeRendering {
self.rendering_mode
}
pub fn data(&self) -> &tiny_skia_path::Path {
self.data.as_ref()
}
pub fn abs_transform(&self) -> Transform {
self.abs_transform
}
pub fn bounding_box(&self) -> Rect {
self.bounding_box
}
pub fn abs_bounding_box(&self) -> Rect {
self.abs_bounding_box
}
pub fn stroke_bounding_box(&self) -> Rect {
self.stroke_bounding_box
}
pub fn abs_stroke_bounding_box(&self) -> Rect {
self.abs_stroke_bounding_box
}
fn calculate_stroke_bbox(stroke: Option<&Stroke>, path: &tiny_skia_path::Path) -> Option<Rect> {
let mut stroke = stroke?.to_tiny_skia();
stroke.dash = None;
if let Some(stroked_path) = path.stroke(&stroke, 1.0) {
return stroked_path.compute_tight_bounds();
}
None
}
fn subroots(&self, f: &mut dyn FnMut(&Group)) {
if let Some(Paint::Pattern(patt)) = self.fill.as_ref().map(|f| &f.paint) {
f(patt.root());
}
if let Some(Paint::Pattern(patt)) = self.stroke.as_ref().map(|f| &f.paint) {
f(patt.root());
}
}
}
#[derive(Clone)]
pub enum ImageKind {
JPEG(Arc<Vec<u8>>),
PNG(Arc<Vec<u8>>),
GIF(Arc<Vec<u8>>),
WEBP(Arc<Vec<u8>>),
SVG(Tree),
}
impl ImageKind {
pub(crate) fn actual_size(&self) -> Option<Size> {
match self {
ImageKind::JPEG(data)
| ImageKind::PNG(data)
| ImageKind::GIF(data)
| ImageKind::WEBP(data) => imagesize::blob_size(data)
.ok()
.and_then(|size| Size::from_wh(size.width as f32, size.height as f32))
.log_none(|| log::warn!("Image has an invalid size. Skipped.")),
ImageKind::SVG(svg) => Some(svg.size),
}
}
}
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::WEBP(_) => f.write_str("ImageKind::WEBP(..)"),
ImageKind::SVG(_) => f.write_str("ImageKind::SVG(..)"),
}
}
}
#[derive(Clone, Debug)]
pub struct Image {
pub(crate) id: String,
pub(crate) visible: bool,
pub(crate) size: Size,
pub(crate) rendering_mode: ImageRendering,
pub(crate) kind: ImageKind,
pub(crate) abs_transform: Transform,
pub(crate) abs_bounding_box: NonZeroRect,
}
impl Image {
pub fn id(&self) -> &str {
&self.id
}
pub fn is_visible(&self) -> bool {
self.visible
}
pub fn size(&self) -> Size {
self.size
}
pub fn rendering_mode(&self) -> ImageRendering {
self.rendering_mode
}
pub fn kind(&self) -> &ImageKind {
&self.kind
}
pub fn abs_transform(&self) -> Transform {
self.abs_transform
}
pub fn bounding_box(&self) -> Rect {
self.size.to_rect(0.0, 0.0).unwrap()
}
pub fn abs_bounding_box(&self) -> Rect {
self.abs_bounding_box.to_rect()
}
fn subroots(&self, f: &mut dyn FnMut(&Group)) {
if let ImageKind::SVG(ref tree) = self.kind {
f(&tree.root);
}
}
}
#[allow(missing_debug_implementations)]
#[derive(Clone, Debug)]
pub struct Tree {
pub(crate) size: Size,
pub(crate) root: Group,
pub(crate) linear_gradients: Vec<Arc<LinearGradient>>,
pub(crate) radial_gradients: Vec<Arc<RadialGradient>>,
pub(crate) patterns: Vec<Arc<Pattern>>,
pub(crate) clip_paths: Vec<Arc<ClipPath>>,
pub(crate) masks: Vec<Arc<Mask>>,
pub(crate) filters: Vec<Arc<filter::Filter>>,
#[cfg(feature = "text")]
pub(crate) fontdb: Arc<fontdb::Database>,
}
impl Tree {
pub fn size(&self) -> Size {
self.size
}
pub fn root(&self) -> &Group {
&self.root
}
pub fn node_by_id(&self, id: &str) -> Option<&Node> {
if id.is_empty() {
return None;
}
node_by_id(&self.root, id)
}
pub fn has_text_nodes(&self) -> bool {
has_text_nodes(&self.root)
}
pub fn has_defs_nodes(&self) -> bool {
!self.linear_gradients().is_empty()
|| !self.radial_gradients().is_empty()
|| !self.patterns().is_empty()
|| !self.filters().is_empty()
|| !self.clip_paths().is_empty()
|| !self.masks().is_empty()
}
pub fn linear_gradients(&self) -> &[Arc<LinearGradient>] {
&self.linear_gradients
}
pub fn radial_gradients(&self) -> &[Arc<RadialGradient>] {
&self.radial_gradients
}
pub fn patterns(&self) -> &[Arc<Pattern>] {
&self.patterns
}
pub fn clip_paths(&self) -> &[Arc<ClipPath>] {
&self.clip_paths
}
pub fn masks(&self) -> &[Arc<Mask>] {
&self.masks
}
pub fn filters(&self) -> &[Arc<filter::Filter>] {
&self.filters
}
#[cfg(feature = "text")]
pub fn fontdb(&self) -> &Arc<fontdb::Database> {
&self.fontdb
}
pub(crate) fn collect_paint_servers(&mut self) {
loop_over_paint_servers(&self.root, &mut |paint| match paint {
Paint::Color(_) => {}
Paint::LinearGradient(lg) => {
if !self
.linear_gradients
.iter()
.any(|other| Arc::ptr_eq(lg, other))
{
self.linear_gradients.push(lg.clone());
}
}
Paint::RadialGradient(rg) => {
if !self
.radial_gradients
.iter()
.any(|other| Arc::ptr_eq(rg, other))
{
self.radial_gradients.push(rg.clone());
}
}
Paint::Pattern(patt) => {
if !self.patterns.iter().any(|other| Arc::ptr_eq(patt, other)) {
self.patterns.push(patt.clone());
}
}
});
}
}
fn node_by_id<'a>(parent: &'a Group, id: &str) -> Option<&'a Node> {
for child in &parent.children {
if child.id() == id {
return Some(child);
}
if let Node::Group(g) = child {
if let Some(n) = node_by_id(g, id) {
return Some(n);
}
}
}
None
}
fn has_text_nodes(root: &Group) -> bool {
for node in &root.children {
if let Node::Text(_) = node {
return true;
}
let mut has_text = false;
if let Node::Image(image) = node {
if let ImageKind::SVG(tree) = &image.kind {
if has_text_nodes(&tree.root) {
has_text = true;
}
}
}
node.subroots(|subroot| has_text |= has_text_nodes(subroot));
if has_text {
return true;
}
}
false
}
fn loop_over_paint_servers(parent: &Group, 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 &parent.children {
match node {
Node::Group(group) => loop_over_paint_servers(group, f),
Node::Path(path) => {
push(path.fill.as_ref().map(|f| &f.paint), f);
push(path.stroke.as_ref().map(|f| &f.paint), f);
}
Node::Image(_) => {}
Node::Text(_) => {}
}
node.subroots(|subroot| loop_over_paint_servers(subroot, f));
}
}
impl Group {
pub(crate) fn collect_clip_paths(&self, clip_paths: &mut Vec<Arc<ClipPath>>) {
for node in self.children() {
if let Node::Group(g) = node {
if let Some(clip) = &g.clip_path {
if !clip_paths.iter().any(|other| Arc::ptr_eq(clip, other)) {
clip_paths.push(clip.clone());
}
if let Some(sub_clip) = &clip.clip_path {
if !clip_paths.iter().any(|other| Arc::ptr_eq(sub_clip, other)) {
clip_paths.push(sub_clip.clone());
}
}
}
}
node.subroots(|subroot| subroot.collect_clip_paths(clip_paths));
if let Node::Group(g) = node {
g.collect_clip_paths(clip_paths);
}
}
}
pub(crate) fn collect_masks(&self, masks: &mut Vec<Arc<Mask>>) {
for node in self.children() {
if let Node::Group(g) = node {
if let Some(mask) = &g.mask {
if !masks.iter().any(|other| Arc::ptr_eq(mask, other)) {
masks.push(mask.clone());
}
if let Some(sub_mask) = &mask.mask {
if !masks.iter().any(|other| Arc::ptr_eq(sub_mask, other)) {
masks.push(sub_mask.clone());
}
}
}
}
node.subroots(|subroot| subroot.collect_masks(masks));
if let Node::Group(g) = node {
g.collect_masks(masks);
}
}
}
pub(crate) fn collect_filters(&self, filters: &mut Vec<Arc<filter::Filter>>) {
for node in self.children() {
if let Node::Group(g) = node {
for filter in g.filters() {
if !filters.iter().any(|other| Arc::ptr_eq(filter, other)) {
filters.push(filter.clone());
}
}
}
node.subroots(|subroot| subroot.collect_filters(filters));
if let Node::Group(g) = node {
g.collect_filters(filters);
}
}
}
pub(crate) fn calculate_object_bbox(&mut self) -> Option<NonZeroRect> {
let mut bbox = BBox::default();
for child in &self.children {
let mut c_bbox = child.bounding_box();
if let Node::Group(group) = child {
if let Some(r) = c_bbox.transform(group.transform) {
c_bbox = r;
}
}
bbox = bbox.expand(c_bbox);
}
bbox.to_non_zero_rect()
}
pub(crate) fn calculate_bounding_boxes(&mut self) -> Option<()> {
let mut bbox = BBox::default();
let mut abs_bbox = BBox::default();
let mut stroke_bbox = BBox::default();
let mut abs_stroke_bbox = BBox::default();
let mut layer_bbox = BBox::default();
for child in &self.children {
{
let mut c_bbox = child.bounding_box();
if let Node::Group(group) = child {
if let Some(r) = c_bbox.transform(group.transform) {
c_bbox = r;
}
}
bbox = bbox.expand(c_bbox);
}
abs_bbox = abs_bbox.expand(child.abs_bounding_box());
{
let mut c_bbox = child.stroke_bounding_box();
if let Node::Group(group) = child {
if let Some(r) = c_bbox.transform(group.transform) {
c_bbox = r;
}
}
stroke_bbox = stroke_bbox.expand(c_bbox);
}
abs_stroke_bbox = abs_stroke_bbox.expand(child.abs_stroke_bounding_box());
if let Node::Group(group) = child {
let r = group.layer_bounding_box;
if let Some(r) = r.transform(group.transform) {
layer_bbox = layer_bbox.expand(r);
}
} else {
layer_bbox = layer_bbox.expand(child.stroke_bounding_box());
}
}
if let Some(bbox) = bbox.to_rect() {
self.bounding_box = bbox;
self.abs_bounding_box = abs_bbox.to_rect()?;
self.stroke_bounding_box = stroke_bbox.to_rect()?;
self.abs_stroke_bounding_box = abs_stroke_bbox.to_rect()?;
}
if let Some(filter_bbox) = self.filters_bounding_box() {
self.layer_bounding_box = filter_bbox;
} else {
self.layer_bounding_box = layer_bbox.to_non_zero_rect()?;
}
self.abs_layer_bounding_box = self.layer_bounding_box.transform(self.abs_transform)?;
Some(())
}
}