[librsvg: 2/6] length: make the type generic on whether it is signed




commit 38289d40b30c961d21772eb2f852c288ea7d1870
Author: Paolo Borelli <pborelli gnome org>
Date:   Sat Dec 19 21:07:57 2020 +0100

    length: make the type generic on whether it is signed
    
    We now encode in the type whether the length is unsigned (non-negative)
    or signed. This simplifies parsing of values (no need to validate
    manually with check_nonnegative).
    To reduce code churn and to help readability, the original Length type
    is renamed to CssLength and two aliases Length and ULength are added.

 src/dasharray.rs      |  10 +--
 src/drawing_ctx.rs    |   4 +-
 src/filter.rs         |  68 +++++++----------
 src/filters/bounds.rs |   8 +-
 src/filters/mod.rs    |  74 ++++++------------
 src/image.rs          |  12 +--
 src/length.rs         | 204 ++++++++++++++++++++++++++++----------------------
 src/lib.rs            |   2 +-
 src/marker.rs         |  18 ++---
 src/pattern.rs        |  19 ++---
 src/shapes.rs         |  54 ++++---------
 src/structure.rs      |  76 +++++++------------
 12 files changed, 232 insertions(+), 317 deletions(-)
---
diff --git a/src/dasharray.rs b/src/dasharray.rs
index 35080c55..9af37877 100644
--- a/src/dasharray.rs
+++ b/src/dasharray.rs
@@ -9,7 +9,7 @@ use crate::parsers::{optional_comma, Parse};
 #[derive(Debug, PartialEq, Clone)]
 pub enum Dasharray {
     None,
-    Array(Vec<Length<Both>>),
+    Array(Vec<ULength<Both>>),
 }
 
 impl Default for Dasharray {
@@ -30,11 +30,7 @@ impl Parse for Dasharray {
         let mut dasharray = Vec::new();
 
         loop {
-            let loc = parser.current_source_location();
-
-            let d = Length::<Both>::parse(parser)?
-                .check_nonnegative()
-                .map_err(|e| loc.new_custom_error(e))?;
+            let d = ULength::<Both>::parse(parser)?;
             dasharray.push(d);
 
             if parser.is_exhausted() {
@@ -55,7 +51,7 @@ mod tests {
     #[test]
     fn parses_dash_array() {
         // helper to cut down boilderplate
-        let length_parse = |s| Length::<Both>::parse_str(s).unwrap();
+        let length_parse = |s| ULength::<Both>::parse_str(s).unwrap();
 
         let expected = Dasharray::Array(vec![
             length_parse("1"),
diff --git a/src/drawing_ctx.rs b/src/drawing_ctx.rs
index b471fc7e..1c7f89f0 100644
--- a/src/drawing_ctx.rs
+++ b/src/drawing_ctx.rs
@@ -42,7 +42,7 @@ use crate::transform::Transform;
 use crate::unit_interval::UnitInterval;
 use crate::viewbox::ViewBox;
 
-/// Holds values that are required to normalize `Length` values to a current viewport.
+/// Holds values that are required to normalize `CssLength` values to a current viewport.
 ///
 /// This struct is created by calling `DrawingCtx::push_view_box()` or
 /// `DrawingCtx::get_view_params()`.
@@ -371,7 +371,7 @@ impl DrawingCtx {
 
     /// Pushes a viewport size for normalizing `Length` values.
     ///
-    /// You should pass the returned `ViewParams` to all subsequent `Length.normalize()`
+    /// You should pass the returned `ViewParams` to all subsequent `CssLength.normalize()`
     /// calls that correspond to this viewport.
     ///
     /// The viewport will stay in place, and will be the one returned by
diff --git a/src/filter.rs b/src/filter.rs
index ffa7d5a3..8938f64a 100644
--- a/src/filter.rs
+++ b/src/filter.rs
@@ -20,8 +20,8 @@ use crate::rect::Rect;
 pub struct Filter {
     x: Length<Horizontal>,
     y: Length<Vertical>,
-    width: Length<Horizontal>,
-    height: Length<Vertical>,
+    width: ULength<Horizontal>,
+    height: ULength<Vertical>,
     filterunits: CoordUnits,
     primitiveunits: CoordUnits,
 }
@@ -32,8 +32,8 @@ impl Default for Filter {
         Self {
             x: Length::<Horizontal>::parse_str("-10%").unwrap(),
             y: Length::<Vertical>::parse_str("-10%").unwrap(),
-            width: Length::<Horizontal>::parse_str("120%").unwrap(),
-            height: Length::<Vertical>::parse_str("120%").unwrap(),
+            width: ULength::<Horizontal>::parse_str("120%").unwrap(),
+            height: ULength::<Vertical>::parse_str("120%").unwrap(),
             filterunits: CoordUnits::ObjectBoundingBox,
             primitiveunits: CoordUnits::UserSpaceOnUse,
         }
@@ -86,56 +86,22 @@ impl SetAttributes for Filter {
         // With ObjectBoundingBox, only fractions and percents are allowed.
         let no_units_allowed = self.filterunits == CoordUnits::ObjectBoundingBox;
 
-        let check_units_horizontal = |length: Length<Horizontal>| {
-            if !no_units_allowed {
-                return Ok(length);
-            }
-
-            match length.unit {
-                LengthUnit::Px | LengthUnit::Percent => Ok(length),
-                _ => Err(ValueErrorKind::parse_error(
-                    "unit identifiers are not allowed with filterUnits set to objectBoundingBox",
-                )),
-            }
-        };
-
-        let check_units_vertical = |length: Length<Vertical>| {
-            if !no_units_allowed {
-                return Ok(length);
-            }
-
-            match length.unit {
-                LengthUnit::Px | LengthUnit::Percent => Ok(length),
-                _ => Err(ValueErrorKind::parse_error(
-                    "unit identifiers are not allowed with filterUnits set to objectBoundingBox",
-                )),
-            }
-        };
-
-        let check_units_horizontal_and_ensure_nonnegative = |length: Length<Horizontal>| {
-            check_units_horizontal(length).and_then(Length::<Horizontal>::check_nonnegative)
-        };
-
-        let check_units_vertical_and_ensure_nonnegative = |length: Length<Vertical>| {
-            check_units_vertical(length).and_then(Length::<Vertical>::check_nonnegative)
-        };
-
         // Parse the rest of the attributes.
         for (attr, value) in attrs.iter() {
             match attr.expanded() {
                 expanded_name!("", "x") => {
-                    self.x = attr.parse_and_validate(value, check_units_horizontal)?
+                    self.x = attr.parse_and_validate(value, |v| check_units(v, no_units_allowed))?
                 }
                 expanded_name!("", "y") => {
-                    self.y = attr.parse_and_validate(value, check_units_vertical)?
+                    self.y = attr.parse_and_validate(value, |v| check_units(v, no_units_allowed))?
                 }
                 expanded_name!("", "width") => {
-                    self.width = attr
-                        .parse_and_validate(value, check_units_horizontal_and_ensure_nonnegative)?
+                    self.width =
+                        attr.parse_and_validate(value, |v| check_units(v, no_units_allowed))?
                 }
                 expanded_name!("", "height") => {
                     self.height =
-                        attr.parse_and_validate(value, check_units_vertical_and_ensure_nonnegative)?
+                        attr.parse_and_validate(value, |v| check_units(v, no_units_allowed))?
                 }
                 expanded_name!("", "primitiveUnits") => self.primitiveunits = attr.parse(value)?,
                 _ => (),
@@ -146,6 +112,22 @@ impl SetAttributes for Filter {
     }
 }
 
+fn check_units<N: Normalize, V: Validate>(
+    length: CssLength<N, V>,
+    no_units_allowed: bool,
+) -> Result<CssLength<N, V>, ValueErrorKind> {
+    if !no_units_allowed {
+        return Ok(length);
+    }
+
+    match length.unit {
+        LengthUnit::Px | LengthUnit::Percent => Ok(length),
+        _ => Err(ValueErrorKind::parse_error(
+            "unit identifiers are not allowed with filterUnits set to objectBoundingBox",
+        )),
+    }
+}
+
 impl Draw for Filter {}
 
 #[derive(Debug, Clone, PartialEq)]
diff --git a/src/filters/bounds.rs b/src/filters/bounds.rs
index 051a175c..0f56bdcb 100644
--- a/src/filters/bounds.rs
+++ b/src/filters/bounds.rs
@@ -21,8 +21,8 @@ pub struct BoundsBuilder<'a> {
     /// Filter primitive properties.
     x: Option<Length<Horizontal>>,
     y: Option<Length<Vertical>>,
-    width: Option<Length<Horizontal>>,
-    height: Option<Length<Vertical>>,
+    width: Option<ULength<Horizontal>>,
+    height: Option<ULength<Vertical>>,
 }
 
 impl<'a> BoundsBuilder<'a> {
@@ -32,8 +32,8 @@ impl<'a> BoundsBuilder<'a> {
         ctx: &'a FilterContext,
         x: Option<Length<Horizontal>>,
         y: Option<Length<Vertical>>,
-        width: Option<Length<Horizontal>>,
-        height: Option<Length<Vertical>>,
+        width: Option<ULength<Horizontal>>,
+        height: Option<ULength<Vertical>>,
     ) -> Self {
         Self {
             ctx,
diff --git a/src/filters/mod.rs b/src/filters/mod.rs
index 50b6479c..33b23ed6 100644
--- a/src/filters/mod.rs
+++ b/src/filters/mod.rs
@@ -74,8 +74,8 @@ pub mod turbulence;
 struct Primitive {
     x: Option<Length<Horizontal>>,
     y: Option<Length<Vertical>>,
-    width: Option<Length<Horizontal>>,
-    height: Option<Length<Vertical>>,
+    width: Option<ULength<Horizontal>>,
+    height: Option<ULength<Vertical>>,
     result: Option<CustomIdent>,
 }
 
@@ -149,44 +149,11 @@ impl Primitive {
             .unwrap_or(CoordUnits::UserSpaceOnUse);
 
         // With ObjectBoundingBox, only fractions and percents are allowed.
-        let no_units_allowed = primitiveunits == CoordUnits::ObjectBoundingBox;
-
-        let check_units_horizontal = |length: Length<Horizontal>| {
-            if !no_units_allowed {
-                return Ok(length);
-            }
-
-            match length.unit {
-                LengthUnit::Px | LengthUnit::Percent => Ok(length),
-                _ => Err(FilterError::InvalidUnits),
-            }
-        };
-
-        let check_units_vertical = |length: Length<Vertical>| {
-            if !no_units_allowed {
-                return Ok(length);
-            }
-
-            match length.unit {
-                LengthUnit::Px | LengthUnit::Percent => Ok(length),
-                _ => Err(FilterError::InvalidUnits),
-            }
-        };
-
-        if let Some(x) = self.x {
-            check_units_horizontal(x)?;
-        }
-
-        if let Some(y) = self.y {
-            check_units_vertical(y)?;
-        }
-
-        if let Some(w) = self.width {
-            check_units_horizontal(w)?;
-        }
-
-        if let Some(h) = self.height {
-            check_units_vertical(h)?;
+        if primitiveunits == CoordUnits::ObjectBoundingBox {
+            check_units(self.x)?;
+            check_units(self.y)?;
+            check_units(self.width)?;
+            check_units(self.height)?;
         }
 
         Ok(BoundsBuilder::new(
@@ -199,22 +166,25 @@ impl Primitive {
     }
 }
 
+fn check_units<N: Normalize, V: Validate>(
+    length: Option<CssLength<N, V>>,
+) -> Result<(), FilterError> {
+    match length {
+        Some(l) if l.unit == LengthUnit::Px || l.unit == LengthUnit::Percent => Ok(()),
+        Some(_) => Err(FilterError::InvalidUnits),
+        None => Ok(()),
+    }
+}
+
 impl SetAttributes for Primitive {
     fn set_attributes(&mut self, attrs: &Attributes) -> ElementResult {
         for (attr, value) in attrs.iter() {
             match attr.expanded() {
-                expanded_name!("", "x") => self.x = Some(attr.parse(value)?),
-                expanded_name!("", "y") => self.y = Some(attr.parse(value)?),
-                expanded_name!("", "width") => {
-                    self.width = Some(
-                        attr.parse_and_validate(value, Length::<Horizontal>::check_nonnegative)?,
-                    )
-                }
-                expanded_name!("", "height") => {
-                    self.height =
-                        Some(attr.parse_and_validate(value, Length::<Vertical>::check_nonnegative)?)
-                }
-                expanded_name!("", "result") => self.result = Some(attr.parse(value)?),
+                expanded_name!("", "x") => self.x = attr.parse(value).map(Some)?,
+                expanded_name!("", "y") => self.y = attr.parse(value).map(Some)?,
+                expanded_name!("", "width") => self.width = attr.parse(value).map(Some)?,
+                expanded_name!("", "height") => self.height = attr.parse(value).map(Some)?,
+                expanded_name!("", "result") => self.result = attr.parse(value).map(Some)?,
                 _ => (),
             }
         }
diff --git a/src/image.rs b/src/image.rs
index a62052b2..2f72ce91 100644
--- a/src/image.rs
+++ b/src/image.rs
@@ -19,8 +19,8 @@ use crate::rect::Rect;
 pub struct Image {
     x: Length<Horizontal>,
     y: Length<Vertical>,
-    width: Length<Horizontal>,
-    height: Length<Vertical>,
+    width: ULength<Horizontal>,
+    height: ULength<Vertical>,
     aspect: AspectRatio,
     href: Option<String>,
 }
@@ -31,12 +31,8 @@ impl SetAttributes for Image {
             match attr.expanded() {
                 expanded_name!("", "x") => self.x = attr.parse(value)?,
                 expanded_name!("", "y") => self.y = attr.parse(value)?,
-                expanded_name!("", "width") => {
-                    self.width = attr.parse_and_validate(value, Length::check_nonnegative)?
-                }
-                expanded_name!("", "height") => {
-                    self.height = attr.parse_and_validate(value, Length::check_nonnegative)?
-                }
+                expanded_name!("", "width") => self.width = attr.parse(value)?,
+                expanded_name!("", "height") => self.height = attr.parse(value)?,
                 expanded_name!("", "preserveAspectRatio") => self.aspect = attr.parse(value)?,
 
                 // "path" is used by some older Adobe Illustrator versions
diff --git a/src/length.rs b/src/length.rs
index 432472a0..ea43a6d3 100644
--- a/src/length.rs
+++ b/src/length.rs
@@ -1,46 +1,58 @@
 //! CSS length values.
 //!
-//! [`Length`] is the struct librsvg uses to represent CSS lengths.  See its documentation for
-//! an example of how to construct it.
+//! [`CssLength`] is the struct librsvg uses to represent CSS lengths.
+//! See its documentation for examples of how to construct it.
 //!
-//! Length values need to know whether they will be normalized with respect to the width,
-//! height, or both dimensions of the current viewport.  So, a `Length` has a type parameter
-//! [`Normalize`]; the full type is `Length<N: Normalize>`.  We provide [`Horizontal`],
+//! `CssLength` values need to know whether they will be normalized with respect to the width,
+//! height, or both dimensions of the current viewport.  `CssLength` values can be signed or
+//! unsigned.  So, a `CssLength` has two type parameters, [`Normalize`] and [`Validate`];
+//! the full type is `CssLength<N: Normalize, V: Validate>`.  We provide [`Horizontal`],
 //! [`Vertical`], and [`Both`] implementations of [`Normalize`]; these let length values know
-//! how to normalize themselves with respect to the current viewport.
+//! how to normalize themselves with respect to the current viewport.  We also provide
+//! [`Signed`] and [`Unsigned`] implementations of [`Validate`].
+//!
+//! For ease of use, we define two type aliases [`Length`] and [`ULength`] corresponding to
+//! signed and unsigned.
 //!
 //! For example, the implementation of [`Circle`] defines this structure with fields for the
 //! `(center_x, center_y, radius)`:
 //!
 //! ```
-//! # use librsvg::doctest_only::{Length,Horizontal,Vertical,Both};
+//! # use librsvg::doctest_only::{Length,ULength,Horizontal,Vertical,Both};
 //! pub struct Circle {
 //!     cx: Length<Horizontal>,
 //!     cy: Length<Vertical>,
-//!     r: Length<Both>,
+//!     r: ULength<Both>,
 //! }
 //! ```
 //!
 //! This means that:
 //!
-//! * `cx` and `cy` define the center of the circle, and they will be normalized with respect
-//! to the current viewport's width and height, respectively.  If the SVG document specified
-//! `<circle cx="50%" cy="30%">`, the values would be normalized to be at 50% of the the
-//! viewport's width, and 30% of the viewport's height.
+//! * `cx` and `cy` define the center of the circle, they can be positive or negative, and
+//! they will be normalized with respect to the current viewport's width and height,
+//! respectively.  If the SVG document specified `<circle cx="50%" cy="30%">`, the values
+//! would be normalized to be at 50% of the the viewport's width, and 30% of the viewport's
+//! height.
 //!
-//! * `r` needs to be resolved against the [normalized diagonal][diag] of the current viewport.
+//! * `r` is non-negative and needs to be resolved against the [normalized diagonal][diag]
+//! of the current viewport.
 //!
-//! The `N` type parameter of `Length<N>` is enough to know how to normalize a length value;
-//! the [`normalize`] method will handle it automatically.
+//! The `N` type parameter of `CssLength<N, I>` is enough to know how to normalize a length
+//! value; the [`normalize`] method will handle it automatically.
 //!
 //! [`Circle`]: ../shapes/struct.Circle.html
-//! [`Length`]: struct.Length.html
+//! [`CssLength`]: struct.CssLength.html
+//! [`Length`]: type.Length.html
+//! [`ULength`]: type.ULength.html
 //! [`Horizontal`]: struct.Horizontal.html
 //! [`Vertical`]: struct.Vertical.html
 //! [`Both`]: struct.Both.html
 //! [`Normalize`]: trait.Normalize.html
+//! [`Signed`]: struct.Signed.html
+//! [`Unsigned`]: struct.Unsigned.html
+//! [`Validate`]: trait.Validate.html
 //! [diag]: https://www.w3.org/TR/SVG/coords.html#Units
-//! [`normalize`]: struct.Length.html#method.normalize
+//! [`normalize`]: struct.CssLength.html#method.normalize
 
 use cssparser::{Parser, Token};
 use std::f64::consts::*;
@@ -117,28 +129,28 @@ impl RsvgLength {
     }
 }
 
-/// Used for the type parameter of `Length<N: Normalize>`.
+/// Used for the `N` type parameter of `CssLength<N: Normalize, V: Validate>`.
 pub trait Normalize {
     /// Computes an orientation-based scaling factor.
     ///
-    /// This is used in the [`Length.normalize`] method to resolve lengths with percentage
+    /// This is used in the [`CssLength.normalize`] method to resolve lengths with percentage
     /// units; they need to be resolved with respect to the width, height, or [normalized
     /// diagonal][diag] of the current viewport.
     ///
-    /// [`Length.normalize`]: struct.Length.html#method.normalize
+    /// [`CssLength.normalize`]: struct.CssLength.html#method.normalize
     /// [diag]: https://www.w3.org/TR/SVG/coords.html#Units
     fn normalize(x: f64, y: f64) -> f64;
 }
 
-/// Allows declaring `Length<Horizontal>`.
+/// Allows declaring `CssLength<Horizontal>`.
 #[derive(Debug, PartialEq, Copy, Clone)]
 pub struct Horizontal;
 
-/// Allows declaring `Length<Vertical>`.
+/// Allows declaring `CssLength<Vertical>`.
 #[derive(Debug, PartialEq, Copy, Clone)]
 pub struct Vertical;
 
-/// Allows declaring `Length<Both>`.
+/// Allows declaring `CssLength<Both>`.
 #[derive(Debug, PartialEq, Copy, Clone)]
 pub struct Both;
 
@@ -163,19 +175,51 @@ impl Normalize for Both {
     }
 }
 
+/// Used for the `V` type parameter of `CssLength<N: Normalize, V: Validate>`.
+pub trait Validate {
+    /// Checks if the specified value is acceptable
+    ///
+    /// This is used when parsing a length value
+    fn validate(v: f64) -> Result<f64, ValueErrorKind> {
+        Ok(v)
+    }
+}
+
+#[derive(Debug, PartialEq, Copy, Clone)]
+pub struct Signed;
+
+impl Validate for Signed {}
+
+#[derive(Debug, PartialEq, Copy, Clone)]
+pub struct Unsigned;
+
+impl Validate for Unsigned {
+    fn validate(v: f64) -> Result<f64, ValueErrorKind> {
+        if v >= 0.0 {
+            Ok(v)
+        } else {
+            Err(ValueErrorKind::Value(
+                "value must be non-negative".to_string(),
+            ))
+        }
+    }
+}
+
 /// A CSS length value.
 ///
 /// This is equivalent to [CSS lengths].
 ///
 /// [CSS lengths]: https://www.w3.org/TR/CSS22/syndata.html#length-units
 ///
-/// `Length` implements the [`Parse`] trait, so it can be parsed out of a
+/// `CssLength` implements the [`Parse`] trait, so it can be parsed out of a
 /// [`cssparser::Parser`].
 ///
+/// This type will be normally used through the type aliases [`Length`] and [`ULength`]
+///
 /// Examples of construction:
 ///
 /// ```
-/// # use librsvg::doctest_only::{Length,LengthUnit,Horizontal,Vertical,Both};
+/// # use librsvg::doctest_only::{Length,ULength,LengthUnit,Horizontal,Vertical,Both};
 /// # use librsvg::doctest_only::Parse;
 /// // Explicit type
 /// let width: Length<Horizontal> = Length::new(42.0, LengthUnit::Cm);
@@ -184,12 +228,14 @@ impl Normalize for Both {
 /// let height = Length::<Vertical>::new(42.0, LengthUnit::Cm);
 ///
 /// // Parsed
-/// let radius = Length::<Both>::parse_str("5px").unwrap();
+/// let radius = ULength::<Both>::parse_str("5px").unwrap();
 /// ```
 ///
-/// During the rendering phase, a `Length` needs to be normalized into the current coordinate
-/// system's units with the [`normalize`] method.
+/// During the rendering phase, a `CssLength` needs to be normalized into the current
+/// coordinate system's units with the [`normalize`] method.
 ///
+/// [`Length`]: type.Length.html
+/// [`ULength`]: type.ULength.html
 /// [`Normalize`]: trait.Normalize.html
 /// [`Horizontal`]: struct.Horizontal.html
 /// [`Vertical`]: struct.Vertical.html
@@ -199,7 +245,7 @@ impl Normalize for Both {
 /// [`cssparser::Parser`]: https://docs.rs/cssparser/0.27.1/cssparser/struct.Parser.html
 /// [`Parse`]: ../parsers/trait.Parse.html
 #[derive(Debug, PartialEq, Copy, Clone)]
-pub struct Length<N: Normalize> {
+pub struct CssLength<N: Normalize, V: Validate> {
     /// Numeric part of the length
     pub length: f64,
 
@@ -208,10 +254,13 @@ pub struct Length<N: Normalize> {
 
     /// Dummy; used internally for the type parameter `N`
     orientation: PhantomData<N>,
+
+    /// Dummy; used internally for the type parameter `V`
+    validation: PhantomData<V>,
 }
 
-impl<N: Normalize> From<Length<N>> for RsvgLength {
-    fn from(l: Length<N>) -> RsvgLength {
+impl<N: Normalize, V: Validate> From<CssLength<N, V>> for RsvgLength {
+    fn from(l: CssLength<N, V>) -> RsvgLength {
         RsvgLength {
             length: l.length,
             unit: l.unit,
@@ -219,9 +268,9 @@ impl<N: Normalize> From<Length<N>> for RsvgLength {
     }
 }
 
-impl<N: Normalize> Default for Length<N> {
+impl<N: Normalize, V: Validate> Default for CssLength<N, V> {
     fn default() -> Self {
-        Length::new(0.0, LengthUnit::Px)
+        CssLength::new(0.0, LengthUnit::Px)
     }
 }
 
@@ -230,8 +279,8 @@ const CM_PER_INCH: f64 = 2.54;
 const MM_PER_INCH: f64 = 25.4;
 const PICA_PER_INCH: f64 = 6.0;
 
-impl<N: Normalize> Parse for Length<N> {
-    fn parse<'i>(parser: &mut Parser<'i, '_>) -> Result<Length<N>, ParseError<'i>> {
+impl<N: Normalize, V: Validate> Parse for CssLength<N, V> {
+    fn parse<'i>(parser: &mut Parser<'i, '_>) -> Result<CssLength<N, V>, ParseError<'i>> {
         let l_value;
         let l_unit;
 
@@ -272,15 +321,18 @@ impl<N: Normalize> Parse for Length<N> {
 
         let l_value = f64::from(finite_f32(l_value).map_err(|e| parser.new_custom_error(e))?);
 
-        Ok(Length::new(l_value, l_unit))
+        <V as Validate>::validate(l_value)
+            .map_err(|e| parser.new_custom_error(e))
+            .map(|l_value| CssLength::new(l_value, l_unit))
     }
 }
 
-impl<N: Normalize> Length<N> {
-    /// Creates a Length.
+impl<N: Normalize, V: Validate> CssLength<N, V> {
+    /// Creates a CssLength.
     ///
-    /// The compiler needs to know the type parameter `N` which represents the length's
-    /// orientation.  You can specify it explicitly, or call the parametrized method:
+    /// The compiler needs to know the type parameters `N` and `V` which represents the
+    /// length's orientation and validation.
+    /// You can specify them explicitly, or call the parametrized method:
     ///
     /// ```
     /// # use librsvg::doctest_only::{Length,LengthUnit,Horizontal,Vertical};
@@ -290,42 +342,12 @@ impl<N: Normalize> Length<N> {
     /// // Inferred type
     /// let height = Length::<Vertical>::new(42.0, LengthUnit::Cm);
     /// ```
-    pub fn new(l: f64, unit: LengthUnit) -> Length<N> {
-        Length {
+    pub fn new(l: f64, unit: LengthUnit) -> CssLength<N, V> {
+        CssLength {
             length: l,
             unit,
             orientation: PhantomData,
-        }
-    }
-
-    /// Returns `Ok(self)` if the length is >= 0, or an error.
-    ///
-    /// This is usually used right after parsing a length value, as part of a validation step:
-    ///
-    /// ```
-    /// # use librsvg::doctest_only::{Length,Horizontal,LengthUnit};
-    /// # use librsvg::doctest_only::ElementError;
-    /// # use librsvg::doctest_only::ParseValue;
-    /// # use markup5ever::{QualName, Prefix, Namespace, LocalName};
-    /// # let attr = QualName::new(
-    /// #     Some(Prefix::from("")),
-    /// #     Namespace::from(""),
-    /// #     LocalName::from(""),
-    /// # );
-    /// let length: Length<Horizontal> = attr.parse_and_validate("0", Length::check_nonnegative)?;
-    /// assert_eq!(length, Length::new(0.0, LengthUnit::Px));
-    ///
-    /// let result: Result<Length<Horizontal>, ElementError> = attr.parse_and_validate("-2", 
Length::check_nonnegative);
-    /// assert!(result.is_err());
-    /// # Ok::<(), ElementError>(())
-    /// ```
-    pub fn check_nonnegative(self) -> Result<Self, ValueErrorKind> {
-        if self.length >= 0.0 {
-            Ok(self)
-        } else {
-            Err(ValueErrorKind::Value(
-                "value must be non-negative".to_string(),
-            ))
+            validation: PhantomData,
         }
     }
 
@@ -400,6 +422,12 @@ fn viewport_percentage(x: f64, y: f64) -> f64 {
     (x * x + y * y).sqrt() / SQRT_2
 }
 
+/// Alias for `CssLength` types that can have negative values
+pub type Length<N> = CssLength<N, Signed>;
+
+/// Alias for `CssLength` types that are non negative
+pub type ULength<N> = CssLength<N, Unsigned>;
+
 #[cfg(test)]
 mod tests {
     use super::*;
@@ -471,6 +499,21 @@ mod tests {
         );
     }
 
+    #[test]
+    fn parses_unsigned() {
+        assert_eq!(
+            ULength::<Horizontal>::parse_str("42").unwrap(),
+            ULength::<Horizontal>::new(42.0, LengthUnit::Px)
+        );
+
+        assert_eq!(
+            ULength::<Both>::parse_str("0pt").unwrap(),
+            ULength::<Both>::new(0.0, LengthUnit::Pt)
+        );
+
+        assert!(ULength::<Horizontal>::parse_str("-42px").is_err());
+    }
+
     #[test]
     fn empty_length_yields_error() {
         assert!(Length::<Both>::parse_str("").is_err());
@@ -481,21 +524,6 @@ mod tests {
         assert!(Length::<Both>::parse_str("8furlong").is_err());
     }
 
-    #[test]
-    fn check_nonnegative_works() {
-        // and_then with anonymous function
-        assert!(Length::<Both>::parse_str("0")
-            .unwrap()
-            .check_nonnegative()
-            .is_ok());
-
-        // and_then with named function
-        assert!(Length::<Both>::parse_str("-10")
-            .unwrap()
-            .check_nonnegative()
-            .is_err());
-    }
-
     #[test]
     fn normalize_default_works() {
         let params = ViewParams::new(Dpi::new(40.0, 40.0), 100.0, 100.0);
diff --git a/src/lib.rs b/src/lib.rs
index 0f3c7bea..159c6322 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -152,6 +152,6 @@ pub mod doctest_only {
     pub use crate::error::ValueErrorKind;
     pub use crate::href::is_href;
     pub use crate::href::set_href;
-    pub use crate::length::{Both, Horizontal, Length, LengthUnit, Vertical};
+    pub use crate::length::{Both, CssLength, Horizontal, Length, LengthUnit, ULength, Vertical};
     pub use crate::parsers::{Parse, ParseValue};
 }
diff --git a/src/marker.rs b/src/marker.rs
index 349528c7..18f49bac 100644
--- a/src/marker.rs
+++ b/src/marker.rs
@@ -73,8 +73,8 @@ pub struct Marker {
     units: MarkerUnits,
     ref_x: Length<Horizontal>,
     ref_y: Length<Vertical>,
-    width: Length<Horizontal>,
-    height: Length<Vertical>,
+    width: ULength<Horizontal>,
+    height: ULength<Vertical>,
     orient: MarkerOrient,
     aspect: AspectRatio,
     vbox: Option<ViewBox>,
@@ -87,8 +87,8 @@ impl Default for Marker {
             ref_x: Default::default(),
             ref_y: Default::default(),
             // the following two are per the spec
-            width: Length::<Horizontal>::parse_str("3").unwrap(),
-            height: Length::<Vertical>::parse_str("3").unwrap(),
+            width: ULength::<Horizontal>::parse_str("3").unwrap(),
+            height: ULength::<Vertical>::parse_str("3").unwrap(),
             orient: MarkerOrient::default(),
             aspect: AspectRatio::default(),
             vbox: None,
@@ -181,14 +181,8 @@ impl SetAttributes for Marker {
                 expanded_name!("", "markerUnits") => self.units = attr.parse(value)?,
                 expanded_name!("", "refX") => self.ref_x = attr.parse(value)?,
                 expanded_name!("", "refY") => self.ref_y = attr.parse(value)?,
-                expanded_name!("", "markerWidth") => {
-                    self.width =
-                        attr.parse_and_validate(value, Length::<Horizontal>::check_nonnegative)?
-                }
-                expanded_name!("", "markerHeight") => {
-                    self.height =
-                        attr.parse_and_validate(value, Length::<Vertical>::check_nonnegative)?
-                }
+                expanded_name!("", "markerWidth") => self.width = attr.parse(value)?,
+                expanded_name!("", "markerHeight") => self.height = attr.parse(value)?,
                 expanded_name!("", "orient") => self.orient = attr.parse(value)?,
                 expanded_name!("", "preserveAspectRatio") => self.aspect = attr.parse(value)?,
                 expanded_name!("", "viewBox") => self.vbox = Some(attr.parse(value)?),
diff --git a/src/pattern.rs b/src/pattern.rs
index 66533e1e..598d4cba 100644
--- a/src/pattern.rs
+++ b/src/pattern.rs
@@ -36,8 +36,8 @@ struct Common {
     transform: Option<Transform>,
     x: Option<Length<Horizontal>>,
     y: Option<Length<Vertical>>,
-    width: Option<Length<Horizontal>>,
-    height: Option<Length<Vertical>>,
+    width: Option<ULength<Horizontal>>,
+    height: Option<ULength<Vertical>>,
 }
 
 /// State used during the pattern resolution process
@@ -96,8 +96,8 @@ pub struct ResolvedPattern {
     transform: Transform,
     x: Length<Horizontal>,
     y: Length<Vertical>,
-    width: Length<Horizontal>,
-    height: Length<Vertical>,
+    width: ULength<Horizontal>,
+    height: ULength<Vertical>,
 
     // Link to the node whose children are the pattern's resolved children.
     children: Children,
@@ -146,15 +146,8 @@ impl SetAttributes for Pattern {
                 }
                 expanded_name!("", "x") => self.common.x = Some(attr.parse(value)?),
                 expanded_name!("", "y") => self.common.y = Some(attr.parse(value)?),
-                expanded_name!("", "width") => {
-                    self.common.width = Some(
-                        attr.parse_and_validate(value, Length::<Horizontal>::check_nonnegative)?,
-                    )
-                }
-                expanded_name!("", "height") => {
-                    self.common.height =
-                        Some(attr.parse_and_validate(value, Length::<Vertical>::check_nonnegative)?)
-                }
+                expanded_name!("", "width") => self.common.width = Some(attr.parse(value)?),
+                expanded_name!("", "height") => self.common.height = Some(attr.parse(value)?),
                 _ => (),
             }
         }
diff --git a/src/shapes.rs b/src/shapes.rs
index 1fd72368..90fd522a 100644
--- a/src/shapes.rs
+++ b/src/shapes.rs
@@ -301,12 +301,12 @@ impl BasicShape for Line {
 pub struct Rect {
     x: Length<Horizontal>,
     y: Length<Vertical>,
-    w: Length<Horizontal>,
-    h: Length<Vertical>,
+    width: ULength<Horizontal>,
+    height: ULength<Vertical>,
 
     // Radiuses for rounded corners
-    rx: Option<Length<Horizontal>>,
-    ry: Option<Length<Vertical>>,
+    rx: Option<ULength<Horizontal>>,
+    ry: Option<ULength<Vertical>>,
 }
 
 impl_draw!(Rect);
@@ -317,24 +317,10 @@ impl SetAttributes for Rect {
             match attr.expanded() {
                 expanded_name!("", "x") => self.x = attr.parse(value)?,
                 expanded_name!("", "y") => self.y = attr.parse(value)?,
-                expanded_name!("", "width") => {
-                    self.w =
-                        attr.parse_and_validate(value, Length::<Horizontal>::check_nonnegative)?
-                }
-                expanded_name!("", "height") => {
-                    self.h =
-                        attr.parse_and_validate(value, Length::<Vertical>::check_nonnegative)?
-                }
-                expanded_name!("", "rx") => {
-                    self.rx = attr
-                        .parse_and_validate(value, Length::<Horizontal>::check_nonnegative)
-                        .map(Some)?
-                }
-                expanded_name!("", "ry") => {
-                    self.ry = attr
-                        .parse_and_validate(value, Length::<Vertical>::check_nonnegative)
-                        .map(Some)?
-                }
+                expanded_name!("", "width") => self.width = attr.parse(value)?,
+                expanded_name!("", "height") => self.height = attr.parse(value)?,
+                expanded_name!("", "rx") => self.rx = attr.parse(value).map(Some)?,
+                expanded_name!("", "ry") => self.ry = attr.parse(value).map(Some)?,
                 _ => (),
             }
         }
@@ -349,8 +335,8 @@ impl BasicShape for Rect {
 
         let x = self.x.normalize(values, &params);
         let y = self.y.normalize(values, &params);
-        let w = self.w.normalize(values, &params);
-        let h = self.h.normalize(values, &params);
+        let w = self.width.normalize(values, &params);
+        let h = self.height.normalize(values, &params);
 
         let mut rx;
         let mut ry;
@@ -520,7 +506,7 @@ impl BasicShape for Rect {
 pub struct Circle {
     cx: Length<Horizontal>,
     cy: Length<Vertical>,
-    r: Length<Both>,
+    r: ULength<Both>,
 }
 
 impl_draw!(Circle);
@@ -531,9 +517,7 @@ impl SetAttributes for Circle {
             match attr.expanded() {
                 expanded_name!("", "cx") => self.cx = attr.parse(value)?,
                 expanded_name!("", "cy") => self.cy = attr.parse(value)?,
-                expanded_name!("", "r") => {
-                    self.r = attr.parse_and_validate(value, Length::<Both>::check_nonnegative)?
-                }
+                expanded_name!("", "r") => self.r = attr.parse(value)?,
                 _ => (),
             }
         }
@@ -558,8 +542,8 @@ impl BasicShape for Circle {
 pub struct Ellipse {
     cx: Length<Horizontal>,
     cy: Length<Vertical>,
-    rx: Length<Horizontal>,
-    ry: Length<Vertical>,
+    rx: ULength<Horizontal>,
+    ry: ULength<Vertical>,
 }
 
 impl_draw!(Ellipse);
@@ -570,14 +554,8 @@ impl SetAttributes for Ellipse {
             match attr.expanded() {
                 expanded_name!("", "cx") => self.cx = attr.parse(value)?,
                 expanded_name!("", "cy") => self.cy = attr.parse(value)?,
-                expanded_name!("", "rx") => {
-                    self.rx =
-                        attr.parse_and_validate(value, Length::<Horizontal>::check_nonnegative)?
-                }
-                expanded_name!("", "ry") => {
-                    self.ry =
-                        attr.parse_and_validate(value, Length::<Vertical>::check_nonnegative)?
-                }
+                expanded_name!("", "rx") => self.rx = attr.parse(value)?,
+                expanded_name!("", "ry") => self.ry = attr.parse(value)?,
                 _ => (),
             }
         }
diff --git a/src/structure.rs b/src/structure.rs
index 8219f1a4..73b6e99f 100644
--- a/src/structure.rs
+++ b/src/structure.rs
@@ -89,10 +89,10 @@ impl Draw for Switch {
 #[derive(Debug, Copy, Clone, PartialEq)]
 pub struct IntrinsicDimensions {
     /// Contents of the `width` attribute.
-    pub width: Option<Length<Horizontal>>,
+    pub width: Option<ULength<Horizontal>>,
 
     /// Contents of the `height` attribute.
-    pub height: Option<Length<Vertical>>,
+    pub height: Option<ULength<Vertical>>,
 
     /// Contents of the `viewBox` attribute.
     pub vbox: Option<ViewBox>,
@@ -103,16 +103,16 @@ pub struct Svg {
     preserve_aspect_ratio: AspectRatio,
     x: Option<Length<Horizontal>>,
     y: Option<Length<Vertical>>,
-    w: Option<Length<Horizontal>>,
-    h: Option<Length<Vertical>>,
+    width: Option<ULength<Horizontal>>,
+    height: Option<ULength<Vertical>>,
     vbox: Option<ViewBox>,
 }
 
 impl Svg {
     pub fn get_intrinsic_dimensions(&self) -> IntrinsicDimensions {
         IntrinsicDimensions {
-            width: self.w,
-            height: self.h,
+            width: self.width,
+            height: self.height,
             vbox: self.vbox,
         }
     }
@@ -129,14 +129,14 @@ impl Svg {
         (x, y)
     }
 
-    fn get_unnormalized_size(&self) -> (Length<Horizontal>, Length<Vertical>) {
+    fn get_unnormalized_size(&self) -> (ULength<Horizontal>, ULength<Vertical>) {
         // these defaults are per the spec
         let w = self
-            .w
-            .unwrap_or_else(|| Length::<Horizontal>::parse_str("100%").unwrap());
+            .width
+            .unwrap_or_else(|| ULength::<Horizontal>::parse_str("100%").unwrap());
         let h = self
-            .h
-            .unwrap_or_else(|| Length::<Vertical>::parse_str("100%").unwrap());
+            .height
+            .unwrap_or_else(|| ULength::<Vertical>::parse_str("100%").unwrap());
 
         (w, h)
     }
@@ -212,15 +212,8 @@ impl SetAttributes for Svg {
                 }
                 expanded_name!("", "x") => self.x = Some(attr.parse(value)?),
                 expanded_name!("", "y") => self.y = Some(attr.parse(value)?),
-                expanded_name!("", "width") => {
-                    self.w = Some(
-                        attr.parse_and_validate(value, Length::<Horizontal>::check_nonnegative)?,
-                    )
-                }
-                expanded_name!("", "height") => {
-                    self.h =
-                        Some(attr.parse_and_validate(value, Length::<Vertical>::check_nonnegative)?)
-                }
+                expanded_name!("", "width") => self.width = Some(attr.parse(value)?),
+                expanded_name!("", "height") => self.height = Some(attr.parse(value)?),
                 expanded_name!("", "viewBox") => self.vbox = attr.parse(value).map(Some)?,
                 _ => (),
             }
@@ -253,8 +246,8 @@ pub struct Use {
     link: Option<NodeId>,
     x: Length<Horizontal>,
     y: Length<Vertical>,
-    w: Option<Length<Horizontal>>,
-    h: Option<Length<Vertical>>,
+    width: Option<ULength<Horizontal>>,
+    height: Option<ULength<Vertical>>,
 }
 
 impl Use {
@@ -268,12 +261,12 @@ impl Use {
         // "If the ‘use’ element references a ‘symbol’ element"
 
         let w = self
-            .w
-            .unwrap_or_else(|| Length::<Horizontal>::parse_str("100%").unwrap())
+            .width
+            .unwrap_or_else(|| ULength::<Horizontal>::parse_str("100%").unwrap())
             .normalize(values, &params);
         let h = self
-            .h
-            .unwrap_or_else(|| Length::<Vertical>::parse_str("100%").unwrap())
+            .height
+            .unwrap_or_else(|| ULength::<Vertical>::parse_str("100%").unwrap())
             .normalize(values, &params);
 
         Rect::new(x, y, x + w, y + h)
@@ -289,19 +282,10 @@ impl SetAttributes for Use {
                     &mut self.link,
                     NodeId::parse(value).attribute(attr.clone())?,
                 ),
-
                 expanded_name!("", "x") => self.x = attr.parse(value)?,
                 expanded_name!("", "y") => self.y = attr.parse(value)?,
-                expanded_name!("", "width") => {
-                    self.w = attr
-                        .parse_and_validate(value, Length::<Horizontal>::check_nonnegative)
-                        .map(Some)?
-                }
-                expanded_name!("", "height") => {
-                    self.h = attr
-                        .parse_and_validate(value, Length::<Vertical>::check_nonnegative)
-                        .map(Some)?
-                }
+                expanded_name!("", "width") => self.width = attr.parse(value).map(Some)?,
+                expanded_name!("", "height") => self.height = attr.parse(value).map(Some)?,
                 _ => (),
             }
         }
@@ -392,8 +376,8 @@ coord_units!(MaskContentUnits, CoordUnits::UserSpaceOnUse);
 pub struct Mask {
     x: Length<Horizontal>,
     y: Length<Vertical>,
-    width: Length<Horizontal>,
-    height: Length<Vertical>,
+    width: ULength<Horizontal>,
+    height: ULength<Vertical>,
 
     units: MaskUnits,
     content_units: MaskContentUnits,
@@ -405,8 +389,8 @@ impl Default for Mask {
             // these values are per the spec
             x: Length::<Horizontal>::parse_str("-10%").unwrap(),
             y: Length::<Vertical>::parse_str("-10%").unwrap(),
-            width: Length::<Horizontal>::parse_str("120%").unwrap(),
-            height: Length::<Vertical>::parse_str("120%").unwrap(),
+            width: ULength::<Horizontal>::parse_str("120%").unwrap(),
+            height: ULength::<Vertical>::parse_str("120%").unwrap(),
 
             units: MaskUnits::default(),
             content_units: MaskContentUnits::default(),
@@ -439,14 +423,8 @@ impl SetAttributes for Mask {
             match attr.expanded() {
                 expanded_name!("", "x") => self.x = attr.parse(value)?,
                 expanded_name!("", "y") => self.y = attr.parse(value)?,
-                expanded_name!("", "width") => {
-                    self.width =
-                        attr.parse_and_validate(value, Length::<Horizontal>::check_nonnegative)?
-                }
-                expanded_name!("", "height") => {
-                    self.height =
-                        attr.parse_and_validate(value, Length::<Vertical>::check_nonnegative)?
-                }
+                expanded_name!("", "width") => self.width = attr.parse(value)?,
+                expanded_name!("", "height") => self.height = attr.parse(value)?,
                 expanded_name!("", "maskUnits") => self.units = attr.parse(value)?,
                 expanded_name!("", "maskContentUnits") => self.content_units = attr.parse(value)?,
                 _ => (),


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]