diff options
Diffstat (limited to 'rsvg/src/pattern.rs')
-rw-r--r-- | rsvg/src/pattern.rs | 525 |
1 files changed, 525 insertions, 0 deletions
diff --git a/rsvg/src/pattern.rs b/rsvg/src/pattern.rs new file mode 100644 index 00000000..8a10317f --- /dev/null +++ b/rsvg/src/pattern.rs @@ -0,0 +1,525 @@ +//! The `pattern` element. + +use markup5ever::{expanded_name, local_name, namespace_url, ns}; + +use crate::aspect_ratio::*; +use crate::coord_units::CoordUnits; +use crate::document::{AcquiredNode, AcquiredNodes, NodeId, NodeStack}; +use crate::drawing_ctx::Viewport; +use crate::element::{set_attribute, ElementData, ElementTrait}; +use crate::error::*; +use crate::href::{is_href, set_href}; +use crate::length::*; +use crate::node::{Node, NodeBorrow, WeakNode}; +use crate::parsers::ParseValue; +use crate::rect::Rect; +use crate::session::Session; +use crate::transform::{Transform, TransformAttribute}; +use crate::unit_interval::UnitInterval; +use crate::viewbox::*; +use crate::xml::Attributes; + +coord_units!(PatternUnits, CoordUnits::ObjectBoundingBox); +coord_units!(PatternContentUnits, CoordUnits::UserSpaceOnUse); + +#[derive(Clone, Default)] +struct Common { + units: Option<PatternUnits>, + content_units: Option<PatternContentUnits>, + // This Option<Option<ViewBox>> is a bit strange. We want a field + // with value None to mean, "this field isn't resolved yet". However, + // the vbox can very well be *not* specified in the SVG file. + // In that case, the fully resolved pattern will have a .vbox=Some(None) value. + vbox: Option<Option<ViewBox>>, + preserve_aspect_ratio: Option<AspectRatio>, + transform: Option<TransformAttribute>, + x: Option<Length<Horizontal>>, + y: Option<Length<Vertical>>, + width: Option<ULength<Horizontal>>, + height: Option<ULength<Vertical>>, +} + +/// State used during the pattern resolution process +/// +/// This is the current node's pattern information, plus the fallback +/// that should be used in case that information is not complete for a +/// resolved pattern yet. +struct Unresolved { + pattern: UnresolvedPattern, + fallback: Option<NodeId>, +} + +/// Keeps track of which Pattern provided a non-empty set of children during pattern resolution +#[derive(Clone)] +enum UnresolvedChildren { + /// Points back to the original Pattern if it had no usable children + Unresolved, + + /// Points back to the original Pattern, as no pattern in the + /// chain of fallbacks had usable children. This only gets returned + /// by resolve_from_defaults(). + ResolvedEmpty, + + /// Points back to the Pattern that had usable children. + WithChildren(WeakNode), +} + +/// Keeps track of which Pattern provided a non-empty set of children during pattern resolution +#[derive(Clone)] +enum Children { + Empty, + + /// Points back to the Pattern that had usable children + WithChildren(WeakNode), +} + +/// Main structure used during pattern resolution. For unresolved +/// patterns, we store all fields as Option<T> - if None, it means +/// that the field is not specified; if Some(T), it means that the +/// field was specified. +struct UnresolvedPattern { + common: Common, + + // Point back to our corresponding node, or to the fallback node which has children. + // If the value is None, it means we are fully resolved and didn't find any children + // among the fallbacks. + children: UnresolvedChildren, +} + +#[derive(Clone)] +pub struct ResolvedPattern { + units: PatternUnits, + content_units: PatternContentUnits, + vbox: Option<ViewBox>, + preserve_aspect_ratio: AspectRatio, + transform: TransformAttribute, + x: Length<Horizontal>, + y: Length<Vertical>, + width: ULength<Horizontal>, + height: ULength<Vertical>, + opacity: UnitInterval, + + // Link to the node whose children are the pattern's resolved children. + children: Children, +} + +/// Pattern normalized to user-space units. +pub struct UserSpacePattern { + pub width: f64, + pub height: f64, + pub transform: Transform, + pub coord_transform: Transform, + pub content_transform: Transform, + pub opacity: UnitInterval, + + // This one is private so the caller has to go through fn acquire_pattern_node() + node_with_children: Node, +} + +#[derive(Default)] +pub struct Pattern { + common: Common, + fallback: Option<NodeId>, +} + +impl ElementTrait for Pattern { + fn set_attributes(&mut self, attrs: &Attributes, session: &Session) { + for (attr, value) in attrs.iter() { + match attr.expanded() { + expanded_name!("", "patternUnits") => { + set_attribute(&mut self.common.units, attr.parse(value), session) + } + expanded_name!("", "patternContentUnits") => { + set_attribute(&mut self.common.content_units, attr.parse(value), session); + } + expanded_name!("", "viewBox") => { + set_attribute(&mut self.common.vbox, attr.parse(value), session) + } + expanded_name!("", "preserveAspectRatio") => { + set_attribute( + &mut self.common.preserve_aspect_ratio, + attr.parse(value), + session, + ); + } + expanded_name!("", "patternTransform") => { + set_attribute(&mut self.common.transform, attr.parse(value), session); + } + ref a if is_href(a) => { + let mut href = None; + set_attribute( + &mut href, + NodeId::parse(value).map(Some).attribute(attr.clone()), + session, + ); + set_href(a, &mut self.fallback, href); + } + expanded_name!("", "x") => { + set_attribute(&mut self.common.x, attr.parse(value), session) + } + expanded_name!("", "y") => { + set_attribute(&mut self.common.y, attr.parse(value), session) + } + expanded_name!("", "width") => { + set_attribute(&mut self.common.width, attr.parse(value), session) + } + expanded_name!("", "height") => { + set_attribute(&mut self.common.height, attr.parse(value), session) + } + _ => (), + } + } + } +} + +impl UnresolvedPattern { + fn into_resolved(self, opacity: UnitInterval) -> ResolvedPattern { + assert!(self.is_resolved()); + + ResolvedPattern { + units: self.common.units.unwrap(), + content_units: self.common.content_units.unwrap(), + vbox: self.common.vbox.unwrap(), + preserve_aspect_ratio: self.common.preserve_aspect_ratio.unwrap(), + transform: self.common.transform.unwrap(), + x: self.common.x.unwrap(), + y: self.common.y.unwrap(), + width: self.common.width.unwrap(), + height: self.common.height.unwrap(), + opacity, + + children: self.children.to_resolved(), + } + } + + fn is_resolved(&self) -> bool { + self.common.units.is_some() + && self.common.content_units.is_some() + && self.common.vbox.is_some() + && self.common.preserve_aspect_ratio.is_some() + && self.common.transform.is_some() + && self.common.x.is_some() + && self.common.y.is_some() + && self.common.width.is_some() + && self.common.height.is_some() + && self.children.is_resolved() + } + + fn resolve_from_fallback(&self, fallback: &UnresolvedPattern) -> UnresolvedPattern { + let units = self.common.units.or(fallback.common.units); + let content_units = self.common.content_units.or(fallback.common.content_units); + let vbox = self.common.vbox.or(fallback.common.vbox); + let preserve_aspect_ratio = self + .common + .preserve_aspect_ratio + .or(fallback.common.preserve_aspect_ratio); + let transform = self.common.transform.or(fallback.common.transform); + let x = self.common.x.or(fallback.common.x); + let y = self.common.y.or(fallback.common.y); + let width = self.common.width.or(fallback.common.width); + let height = self.common.height.or(fallback.common.height); + let children = self.children.resolve_from_fallback(&fallback.children); + + UnresolvedPattern { + common: Common { + units, + content_units, + vbox, + preserve_aspect_ratio, + transform, + x, + y, + width, + height, + }, + children, + } + } + + fn resolve_from_defaults(&self) -> UnresolvedPattern { + let units = self.common.units.or_else(|| Some(PatternUnits::default())); + let content_units = self + .common + .content_units + .or_else(|| Some(PatternContentUnits::default())); + let vbox = self.common.vbox.or(Some(None)); + let preserve_aspect_ratio = self + .common + .preserve_aspect_ratio + .or_else(|| Some(AspectRatio::default())); + let transform = self + .common + .transform + .or_else(|| Some(TransformAttribute::default())); + let x = self.common.x.or_else(|| Some(Default::default())); + let y = self.common.y.or_else(|| Some(Default::default())); + let width = self.common.width.or_else(|| Some(Default::default())); + let height = self.common.height.or_else(|| Some(Default::default())); + let children = self.children.resolve_from_defaults(); + + UnresolvedPattern { + common: Common { + units, + content_units, + vbox, + preserve_aspect_ratio, + transform, + x, + y, + width, + height, + }, + children, + } + } +} + +impl UnresolvedChildren { + fn from_node(node: &Node) -> UnresolvedChildren { + let weak = node.downgrade(); + + if node.children().any(|child| child.is_element()) { + UnresolvedChildren::WithChildren(weak) + } else { + UnresolvedChildren::Unresolved + } + } + + fn is_resolved(&self) -> bool { + !matches!(*self, UnresolvedChildren::Unresolved) + } + + fn resolve_from_fallback(&self, fallback: &UnresolvedChildren) -> UnresolvedChildren { + use UnresolvedChildren::*; + + match (self, fallback) { + (&Unresolved, &Unresolved) => Unresolved, + (WithChildren(wc), _) => WithChildren(wc.clone()), + (_, WithChildren(wc)) => WithChildren(wc.clone()), + (_, _) => unreachable!(), + } + } + + fn resolve_from_defaults(&self) -> UnresolvedChildren { + use UnresolvedChildren::*; + + match *self { + Unresolved => ResolvedEmpty, + _ => (*self).clone(), + } + } + + fn to_resolved(&self) -> Children { + use UnresolvedChildren::*; + + assert!(self.is_resolved()); + + match *self { + ResolvedEmpty => Children::Empty, + WithChildren(ref wc) => Children::WithChildren(wc.clone()), + _ => unreachable!(), + } + } +} + +fn nonempty_rect(bbox: &Option<Rect>) -> Option<Rect> { + match *bbox { + None => None, + Some(r) if r.is_empty() => None, + Some(r) => Some(r), + } +} + +impl ResolvedPattern { + fn node_with_children(&self) -> Option<Node> { + match self.children { + // This means we didn't find any children among the fallbacks, + // so there is nothing to render. + Children::Empty => None, + + Children::WithChildren(ref wc) => Some(wc.upgrade().unwrap()), + } + } + + fn get_rect(&self, params: &NormalizeParams) -> Rect { + let x = self.x.to_user(params); + let y = self.y.to_user(params); + let w = self.width.to_user(params); + let h = self.height.to_user(params); + + Rect::new(x, y, x + w, y + h) + } + + pub fn to_user_space( + &self, + object_bbox: &Option<Rect>, + viewport: &Viewport, + values: &NormalizeValues, + ) -> Option<UserSpacePattern> { + let node_with_children = self.node_with_children()?; + + let view_params = viewport.with_units(self.units.0); + let params = NormalizeParams::from_values(values, &view_params); + + let rect = self.get_rect(¶ms); + + // Create the pattern coordinate system + let (width, height, coord_transform) = match self.units { + PatternUnits(CoordUnits::ObjectBoundingBox) => { + let bbrect = nonempty_rect(object_bbox)?; + ( + rect.width() * bbrect.width(), + rect.height() * bbrect.height(), + Transform::new_translate( + bbrect.x0 + rect.x0 * bbrect.width(), + bbrect.y0 + rect.y0 * bbrect.height(), + ), + ) + } + PatternUnits(CoordUnits::UserSpaceOnUse) => ( + rect.width(), + rect.height(), + Transform::new_translate(rect.x0, rect.y0), + ), + }; + + let pattern_transform = self.transform.to_transform(); + + let coord_transform = coord_transform.post_transform(&pattern_transform); + + // Create the pattern contents coordinate system + let content_transform = if let Some(vbox) = self.vbox { + // If there is a vbox, use that + let r = self + .preserve_aspect_ratio + .compute(&vbox, &Rect::from_size(width, height)); + + let sw = r.width() / vbox.width(); + let sh = r.height() / vbox.height(); + let x = r.x0 - vbox.x0 * sw; + let y = r.y0 - vbox.y0 * sh; + + Transform::new_scale(sw, sh).pre_translate(x, y) + } else { + match self.content_units { + PatternContentUnits(CoordUnits::ObjectBoundingBox) => { + let bbrect = nonempty_rect(object_bbox)?; + Transform::new_scale(bbrect.width(), bbrect.height()) + } + PatternContentUnits(CoordUnits::UserSpaceOnUse) => Transform::identity(), + } + }; + + Some(UserSpacePattern { + width, + height, + transform: pattern_transform, + coord_transform, + content_transform, + opacity: self.opacity, + node_with_children, + }) + } +} + +impl UserSpacePattern { + /// Gets the `<pattern>` node that contains the children to be drawn for the pattern's contents. + /// + /// This has to go through [AcquiredNodes] to catch circular references among + /// patterns and their children. + pub fn acquire_pattern_node( + &self, + acquired_nodes: &mut AcquiredNodes<'_>, + ) -> Result<AcquiredNode, AcquireError> { + acquired_nodes.acquire_ref(&self.node_with_children) + } +} + +impl Pattern { + fn get_unresolved(&self, node: &Node) -> Unresolved { + let pattern = UnresolvedPattern { + common: self.common.clone(), + children: UnresolvedChildren::from_node(node), + }; + + Unresolved { + pattern, + fallback: self.fallback.clone(), + } + } + + pub fn resolve( + &self, + node: &Node, + acquired_nodes: &mut AcquiredNodes<'_>, + opacity: UnitInterval, + session: &Session, + ) -> Result<ResolvedPattern, AcquireError> { + let Unresolved { + mut pattern, + mut fallback, + } = self.get_unresolved(node); + + let mut stack = NodeStack::new(); + + while !pattern.is_resolved() { + if let Some(ref node_id) = fallback { + match acquired_nodes.acquire(node_id) { + Ok(acquired) => { + let acquired_node = acquired.get(); + + if stack.contains(acquired_node) { + return Err(AcquireError::CircularReference(acquired_node.clone())); + } + + match *acquired_node.borrow_element_data() { + ElementData::Pattern(ref p) => { + let unresolved = p.get_unresolved(acquired_node); + pattern = pattern.resolve_from_fallback(&unresolved.pattern); + fallback = unresolved.fallback; + + stack.push(acquired_node); + } + _ => return Err(AcquireError::InvalidLinkType(node_id.clone())), + } + } + + Err(AcquireError::MaxReferencesExceeded) => { + return Err(AcquireError::MaxReferencesExceeded) + } + + Err(e) => { + rsvg_log!(session, "Stopping pattern resolution: {}", e); + pattern = pattern.resolve_from_defaults(); + break; + } + } + } else { + pattern = pattern.resolve_from_defaults(); + break; + } + } + + Ok(pattern.into_resolved(opacity)) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::node::NodeData; + use markup5ever::{namespace_url, ns, QualName}; + + #[test] + fn pattern_resolved_from_defaults_is_really_resolved() { + let node = Node::new(NodeData::new_element( + &Session::default(), + &QualName::new(None, ns!(svg), local_name!("pattern")), + Attributes::new(), + )); + + let unresolved = borrow_element_as!(node, Pattern).get_unresolved(&node); + let pattern = unresolved.pattern.resolve_from_defaults(); + assert!(pattern.is_resolved()); + } +} |