[librsvg] Split Length into LengthHorizontal/LengthVertical/LengthBoth



commit cab989b9f749ca71551ad1fb68411bb2e96d96b8
Author: Federico Mena Quintero <federico gnome org>
Date:   Mon Jan 21 18:14:39 2019 -0600

    Split Length into LengthHorizontal/LengthVertical/LengthBoth
    
    This will help remove the Data associated type from the Parse trait.

 rsvg_internals/src/filters/bounds.rs |  18 ++---
 rsvg_internals/src/filters/mod.rs    |  54 ++++++++++-----
 rsvg_internals/src/filters/node.rs   |  65 +++++++++++-------
 rsvg_internals/src/font_props.rs     |  63 ++++++-----------
 rsvg_internals/src/gradient.rs       |  52 +++++++-------
 rsvg_internals/src/image.rs          |  30 ++++----
 rsvg_internals/src/length.rs         | 128 ++++++++++++++++++++++++++---------
 rsvg_internals/src/marker.rs         |  41 +++++------
 rsvg_internals/src/mask.rs           |  51 +++++---------
 rsvg_internals/src/pattern.rs        |  28 ++++----
 rsvg_internals/src/properties.rs     |  38 +++++------
 rsvg_internals/src/shapes.rs         | 119 ++++++++++++++++----------------
 rsvg_internals/src/stop.rs           |   8 +--
 rsvg_internals/src/structure.rs      |  60 ++++++++--------
 rsvg_internals/src/text.rs           |  96 ++++++++++++--------------
 15 files changed, 446 insertions(+), 405 deletions(-)
---
diff --git a/rsvg_internals/src/filters/bounds.rs b/rsvg_internals/src/filters/bounds.rs
index 685fbeba..806b95c9 100644
--- a/rsvg_internals/src/filters/bounds.rs
+++ b/rsvg_internals/src/filters/bounds.rs
@@ -3,7 +3,7 @@ use cairo::{self, MatrixTrait};
 
 use bbox::BoundingBox;
 use drawing_ctx::DrawingCtx;
-use length::Length;
+use length::{LengthHorizontal, LengthVertical};
 use rect::IRect;
 
 use super::context::{FilterContext, FilterInput, FilterOutput};
@@ -21,10 +21,10 @@ pub struct BoundsBuilder<'a> {
     standard_input_was_referenced: bool,
 
     /// Filter primitive properties.
-    x: Option<Length>,
-    y: Option<Length>,
-    width: Option<Length>,
-    height: Option<Length>,
+    x: Option<LengthHorizontal>,
+    y: Option<LengthVertical>,
+    width: Option<LengthHorizontal>,
+    height: Option<LengthVertical>,
 }
 
 impl<'a> BoundsBuilder<'a> {
@@ -32,10 +32,10 @@ impl<'a> BoundsBuilder<'a> {
     #[inline]
     pub fn new(
         ctx: &'a FilterContext,
-        x: Option<Length>,
-        y: Option<Length>,
-        width: Option<Length>,
-        height: Option<Length>,
+        x: Option<LengthHorizontal>,
+        y: Option<LengthVertical>,
+        width: Option<LengthHorizontal>,
+        height: Option<LengthVertical>,
     ) -> Self {
         Self {
             ctx,
diff --git a/rsvg_internals/src/filters/mod.rs b/rsvg_internals/src/filters/mod.rs
index 9420ab34..486fe84e 100644
--- a/rsvg_internals/src/filters/mod.rs
+++ b/rsvg_internals/src/filters/mod.rs
@@ -9,7 +9,7 @@ use attributes::Attribute;
 use coord_units::CoordUnits;
 use drawing_ctx::DrawingCtx;
 use error::{RenderingError, ValueErrorKind};
-use length::{Length, LengthDir, LengthUnit};
+use length::{LengthHorizontal, LengthUnit, LengthVertical};
 use node::{NodeResult, NodeTrait, NodeType, RsvgNode};
 use parsers::{ParseError, ParseValue};
 use properties::{ColorInterpolationFilters, ComputedValues};
@@ -70,10 +70,10 @@ trait Filter: NodeTrait {
 
 /// The base filter primitive node containing common properties.
 struct Primitive {
-    x: Cell<Option<Length>>,
-    y: Cell<Option<Length>>,
-    width: Cell<Option<Length>>,
-    height: Cell<Option<Length>>,
+    x: Cell<Option<LengthHorizontal>>,
+    y: Cell<Option<LengthVertical>>,
+    width: Cell<Option<LengthHorizontal>>,
+    height: Cell<Option<LengthVertical>>,
     result: RefCell<Option<String>>,
 }
 
@@ -124,42 +124,62 @@ impl NodeTrait for Primitive {
             .unwrap_or(CoordUnits::UserSpaceOnUse);
 
         let no_units_allowed = primitiveunits == CoordUnits::ObjectBoundingBox;
-        let check_units = |length: Length| {
+
+        let check_units_horizontal = |length: LengthHorizontal| {
+            if !no_units_allowed {
+                return Ok(length);
+            }
+
+            match length.unit() {
+                LengthUnit::Default | LengthUnit::Percent => Ok(length),
+                _ => Err(ValueErrorKind::Parse(ParseError::new(
+                    "unit identifiers are not allowed with primitiveUnits set to objectBoundingBox",
+                ))),
+            }
+        };
+
+        let check_units_vertical = |length: LengthVertical| {
             if !no_units_allowed {
                 return Ok(length);
             }
 
-            match length.unit {
+            match length.unit() {
                 LengthUnit::Default | LengthUnit::Percent => Ok(length),
                 _ => Err(ValueErrorKind::Parse(ParseError::new(
                     "unit identifiers are not allowed with primitiveUnits set to objectBoundingBox",
                 ))),
             }
         };
-        let check_units_and_ensure_nonnegative =
-            |length: Length| check_units(length).and_then(Length::check_nonnegative);
+
+        let check_units_horizontal_and_ensure_nonnegative = |length: LengthHorizontal| {
+            check_units_horizontal(length).and_then(LengthHorizontal::check_nonnegative)
+        };
+
+        let check_units_vertical_and_ensure_nonnegative = |length: LengthVertical| {
+            check_units_vertical(length).and_then(LengthVertical::check_nonnegative)
+        };
 
         for (attr, value) in pbag.iter() {
             match attr {
                 Attribute::X => self.x.set(Some(attr.parse_and_validate(
                     value,
-                    LengthDir::Horizontal,
-                    check_units,
+                    (),
+                    check_units_horizontal,
                 )?)),
                 Attribute::Y => self.y.set(Some(attr.parse_and_validate(
                     value,
-                    LengthDir::Vertical,
-                    check_units,
+                    (),
+                    check_units_vertical,
                 )?)),
                 Attribute::Width => self.width.set(Some(attr.parse_and_validate(
                     value,
-                    LengthDir::Horizontal,
-                    check_units_and_ensure_nonnegative,
+                    (),
+                    check_units_horizontal_and_ensure_nonnegative,
                 )?)),
                 Attribute::Height => self.height.set(Some(attr.parse_and_validate(
                     value,
-                    LengthDir::Vertical,
-                    check_units_and_ensure_nonnegative,
+                    (),
+                    check_units_vertical_and_ensure_nonnegative,
                 )?)),
                 Attribute::Result => *self.result.borrow_mut() = Some(value.to_string()),
                 _ => (),
diff --git a/rsvg_internals/src/filters/node.rs b/rsvg_internals/src/filters/node.rs
index e68c3d51..2889e7fa 100644
--- a/rsvg_internals/src/filters/node.rs
+++ b/rsvg_internals/src/filters/node.rs
@@ -4,17 +4,17 @@ use std::cell::Cell;
 use attributes::Attribute;
 use coord_units::CoordUnits;
 use error::ValueErrorKind;
-use length::{Length, LengthDir, LengthUnit};
+use length::{LengthHorizontal, LengthUnit, LengthVertical};
 use node::{NodeResult, NodeTrait, RsvgNode};
 use parsers::{Parse, ParseError, ParseValue};
 use property_bag::PropertyBag;
 
 /// The <filter> node.
 pub struct NodeFilter {
-    pub x: Cell<Length>,
-    pub y: Cell<Length>,
-    pub width: Cell<Length>,
-    pub height: Cell<Length>,
+    pub x: Cell<LengthHorizontal>,
+    pub y: Cell<LengthVertical>,
+    pub width: Cell<LengthHorizontal>,
+    pub height: Cell<LengthVertical>,
     pub filterunits: Cell<CoordUnits>,
     pub primitiveunits: Cell<CoordUnits>,
 }
@@ -24,10 +24,10 @@ impl NodeFilter {
     #[inline]
     pub fn new() -> Self {
         Self {
-            x: Cell::new(Length::parse_str("-10%", LengthDir::Horizontal).unwrap()),
-            y: Cell::new(Length::parse_str("-10%", LengthDir::Vertical).unwrap()),
-            width: Cell::new(Length::parse_str("120%", LengthDir::Horizontal).unwrap()),
-            height: Cell::new(Length::parse_str("120%", LengthDir::Vertical).unwrap()),
+            x: Cell::new(LengthHorizontal::parse_str("-10%", ()).unwrap()),
+            y: Cell::new(LengthVertical::parse_str("-10%", ()).unwrap()),
+            width: Cell::new(LengthHorizontal::parse_str("120%", ()).unwrap()),
+            height: Cell::new(LengthVertical::parse_str("120%", ()).unwrap()),
             filterunits: Cell::new(CoordUnits::ObjectBoundingBox),
             primitiveunits: Cell::new(CoordUnits::UserSpaceOnUse),
         }
@@ -46,42 +46,61 @@ impl NodeTrait for NodeFilter {
 
         // With ObjectBoundingBox, only fractions and percents are allowed.
         let no_units_allowed = self.filterunits.get() == CoordUnits::ObjectBoundingBox;
-        let check_units = |length: Length| {
+
+        let check_units_horizontal = |length: LengthHorizontal| {
             if !no_units_allowed {
                 return Ok(length);
             }
 
-            match length.unit {
+            match length.unit() {
                 LengthUnit::Default | LengthUnit::Percent => Ok(length),
                 _ => Err(ValueErrorKind::Parse(ParseError::new(
                     "unit identifiers are not allowed with filterUnits set to objectBoundingBox",
                 ))),
             }
         };
-        let check_units_and_ensure_nonnegative =
-            |length: Length| check_units(length).and_then(Length::check_nonnegative);
+
+        let check_units_vertical = |length: LengthVertical| {
+            if !no_units_allowed {
+                return Ok(length);
+            }
+
+            match length.unit() {
+                LengthUnit::Default | LengthUnit::Percent => Ok(length),
+                _ => Err(ValueErrorKind::Parse(ParseError::new(
+                    "unit identifiers are not allowed with filterUnits set to objectBoundingBox",
+                ))),
+            }
+        };
+
+        let check_units_horizontal_and_ensure_nonnegative = |length: LengthHorizontal| {
+            check_units_horizontal(length).and_then(LengthHorizontal::check_nonnegative)
+        };
+
+        let check_units_vertical_and_ensure_nonnegative = |length: LengthVertical| {
+            check_units_vertical(length).and_then(LengthVertical::check_nonnegative)
+        };
 
         // Parse the rest of the attributes.
         for (attr, value) in pbag.iter() {
             match attr {
-                Attribute::X => self.x.set(attr.parse_and_validate(
-                    value,
-                    LengthDir::Horizontal,
-                    check_units,
-                )?),
+                Attribute::X => {
+                    self.x
+                        .set(attr.parse_and_validate(value, (), check_units_horizontal)?)
+                }
                 Attribute::Y => {
                     self.y
-                        .set(attr.parse_and_validate(value, LengthDir::Vertical, check_units)?)
+                        .set(attr.parse_and_validate(value, (), check_units_vertical)?)
                 }
                 Attribute::Width => self.width.set(attr.parse_and_validate(
                     value,
-                    LengthDir::Horizontal,
-                    check_units_and_ensure_nonnegative,
+                    (),
+                    check_units_horizontal_and_ensure_nonnegative,
                 )?),
                 Attribute::Height => self.height.set(attr.parse_and_validate(
                     value,
-                    LengthDir::Vertical,
-                    check_units_and_ensure_nonnegative,
+                    (),
+                    check_units_vertical_and_ensure_nonnegative,
                 )?),
                 Attribute::PrimitiveUnits => self.primitiveunits.set(attr.parse(value, ())?),
                 _ => (),
diff --git a/rsvg_internals/src/font_props.rs b/rsvg_internals/src/font_props.rs
index ac9b5847..534bd94f 100644
--- a/rsvg_internals/src/font_props.rs
+++ b/rsvg_internals/src/font_props.rs
@@ -2,7 +2,7 @@ use cssparser::{BasicParseError, Parser, Token};
 
 use drawing_ctx::ViewParams;
 use error::*;
-use length::{Length, LengthDir, LengthUnit, POINTS_PER_INCH};
+use length::{LengthBoth, LengthHorizontal, LengthUnit, POINTS_PER_INCH};
 use parsers::{Parse, ParseError};
 use properties::ComputedValues;
 
@@ -18,11 +18,11 @@ pub enum FontSizeSpec {
     Large,
     XLarge,
     XXLarge,
-    Value(Length),
+    Value(LengthBoth),
 }
 
 impl FontSizeSpec {
-    pub fn value(&self) -> Length {
+    pub fn value(&self) -> LengthBoth {
         match self {
             FontSizeSpec::Value(s) => s.clone(),
             _ => unreachable!(),
@@ -35,31 +35,17 @@ impl FontSizeSpec {
         let size = v.font_size.0.value();
 
         let new_size = match self {
-            FontSizeSpec::Smaller => Length::new(size.length / 1.2, size.unit, LengthDir::Both),
-            FontSizeSpec::Larger => Length::new(size.length * 1.2, size.unit, LengthDir::Both),
-            FontSizeSpec::XXSmall => {
-                Length::new(compute_points(-3.0), LengthUnit::Inch, LengthDir::Both)
-            }
-            FontSizeSpec::XSmall => {
-                Length::new(compute_points(-2.0), LengthUnit::Inch, LengthDir::Both)
-            }
-            FontSizeSpec::Small => {
-                Length::new(compute_points(-1.0), LengthUnit::Inch, LengthDir::Both)
-            }
-            FontSizeSpec::Medium => {
-                Length::new(compute_points(0.0), LengthUnit::Inch, LengthDir::Both)
-            }
-            FontSizeSpec::Large => {
-                Length::new(compute_points(1.0), LengthUnit::Inch, LengthDir::Both)
-            }
-            FontSizeSpec::XLarge => {
-                Length::new(compute_points(2.0), LengthUnit::Inch, LengthDir::Both)
-            }
-            FontSizeSpec::XXLarge => {
-                Length::new(compute_points(3.0), LengthUnit::Inch, LengthDir::Both)
-            }
-            FontSizeSpec::Value(s) if s.unit == LengthUnit::Percent => {
-                Length::new(size.length * s.length, size.unit, LengthDir::Both)
+            FontSizeSpec::Smaller => LengthBoth::new(size.length() / 1.2, size.unit()),
+            FontSizeSpec::Larger => LengthBoth::new(size.length() * 1.2, size.unit()),
+            FontSizeSpec::XXSmall => LengthBoth::new(compute_points(-3.0), LengthUnit::Inch),
+            FontSizeSpec::XSmall => LengthBoth::new(compute_points(-2.0), LengthUnit::Inch),
+            FontSizeSpec::Small => LengthBoth::new(compute_points(-1.0), LengthUnit::Inch),
+            FontSizeSpec::Medium => LengthBoth::new(compute_points(0.0), LengthUnit::Inch),
+            FontSizeSpec::Large => LengthBoth::new(compute_points(1.0), LengthUnit::Inch),
+            FontSizeSpec::XLarge => LengthBoth::new(compute_points(2.0), LengthUnit::Inch),
+            FontSizeSpec::XXLarge => LengthBoth::new(compute_points(3.0), LengthUnit::Inch),
+            FontSizeSpec::Value(s) if s.unit() == LengthUnit::Percent => {
+                LengthBoth::new(size.length() * s.length(), size.unit())
             }
             FontSizeSpec::Value(s) => s.clone(),
         };
@@ -82,7 +68,7 @@ impl Parse for FontSizeSpec {
     ) -> Result<FontSizeSpec, ::error::ValueErrorKind> {
         let parser_state = parser.state();
 
-        Length::parse(parser, LengthDir::Both)
+        LengthBoth::parse(parser, ())
             .and_then(|s| Ok(FontSizeSpec::Value(s)))
             .or_else(|e| {
                 parser.reset(&parser_state);
@@ -184,11 +170,11 @@ impl Parse for FontWeightSpec {
 #[derive(Debug, Copy, Clone, PartialEq)]
 pub enum LetterSpacingSpec {
     Normal,
-    Value(Length),
+    Value(LengthHorizontal),
 }
 
 impl LetterSpacingSpec {
-    pub fn value(&self) -> Length {
+    pub fn value(&self) -> LengthHorizontal {
         match self {
             LetterSpacingSpec::Value(s) => s.clone(),
             _ => unreachable!(),
@@ -197,9 +183,7 @@ impl LetterSpacingSpec {
 
     pub fn compute(&self) -> Self {
         let spacing = match self {
-            LetterSpacingSpec::Normal => {
-                Length::new(0.0, LengthUnit::Default, LengthDir::Horizontal)
-            }
+            LetterSpacingSpec::Normal => LengthHorizontal::new(0.0, LengthUnit::Default),
             LetterSpacingSpec::Value(s) => s.clone(),
         };
 
@@ -221,7 +205,7 @@ impl Parse for LetterSpacingSpec {
     ) -> Result<LetterSpacingSpec, ::error::ValueErrorKind> {
         let parser_state = parser.state();
 
-        Length::parse(parser, LengthDir::Horizontal)
+        LengthHorizontal::parse(parser, ())
             .and_then(|s| Ok(LetterSpacingSpec::Value(s)))
             .or_else(|e| {
                 parser.reset(&parser_state);
@@ -323,10 +307,9 @@ mod tests {
         );
         assert_eq!(
             <LetterSpacingSpec as Parse>::parse_str("10em", ()),
-            Ok(LetterSpacingSpec::Value(Length::new(
+            Ok(LetterSpacingSpec::Value(LengthHorizontal::new(
                 10.0,
                 LengthUnit::FontEm,
-                LengthDir::Horizontal
             )))
         );
     }
@@ -335,18 +318,16 @@ mod tests {
     fn computes_letter_spacing() {
         assert_eq!(
             <LetterSpacingSpec as Parse>::parse_str("normal", ()).map(|s| s.compute()),
-            Ok(LetterSpacingSpec::Value(Length::new(
+            Ok(LetterSpacingSpec::Value(LengthHorizontal::new(
                 0.0,
                 LengthUnit::Default,
-                LengthDir::Horizontal
             )))
         );
         assert_eq!(
             <LetterSpacingSpec as Parse>::parse_str("10em", ()).map(|s| s.compute()),
-            Ok(LetterSpacingSpec::Value(Length::new(
+            Ok(LetterSpacingSpec::Value(LengthHorizontal::new(
                 10.0,
                 LengthUnit::FontEm,
-                LengthDir::Horizontal
             )))
         );
     }
diff --git a/rsvg_internals/src/gradient.rs b/rsvg_internals/src/gradient.rs
index 2edd2ae6..4e9fc893 100644
--- a/rsvg_internals/src/gradient.rs
+++ b/rsvg_internals/src/gradient.rs
@@ -91,18 +91,18 @@ pub struct GradientCommon {
 #[derive(Copy, Clone)]
 pub enum GradientVariant {
     Linear {
-        x1: Option<Length>,
-        y1: Option<Length>,
-        x2: Option<Length>,
-        y2: Option<Length>,
+        x1: Option<LengthHorizontal>,
+        y1: Option<LengthVertical>,
+        x2: Option<LengthHorizontal>,
+        y2: Option<LengthVertical>,
     },
 
     Radial {
-        cx: Option<Length>,
-        cy: Option<Length>,
-        r: Option<Length>,
-        fx: Option<Length>,
-        fy: Option<Length>,
+        cx: Option<LengthHorizontal>,
+        cy: Option<LengthVertical>,
+        r: Option<LengthBoth>,
+        fx: Option<LengthHorizontal>,
+        fy: Option<LengthVertical>,
     },
 }
 
@@ -256,10 +256,10 @@ impl GradientVariant {
         // https://www.w3.org/TR/SVG/pservers.html#LinearGradients
 
         GradientVariant::Linear {
-            x1: Some(Length::parse_str("0%", LengthDir::Horizontal).unwrap()),
-            y1: Some(Length::parse_str("0%", LengthDir::Vertical).unwrap()),
-            x2: Some(Length::parse_str("100%", LengthDir::Horizontal).unwrap()),
-            y2: Some(Length::parse_str("0%", LengthDir::Vertical).unwrap()),
+            x1: Some(LengthHorizontal::parse_str("0%", ()).unwrap()),
+            y1: Some(LengthVertical::parse_str("0%", ()).unwrap()),
+            x2: Some(LengthHorizontal::parse_str("100%", ()).unwrap()),
+            y2: Some(LengthVertical::parse_str("0%", ()).unwrap()),
         }
     }
 
@@ -267,9 +267,9 @@ impl GradientVariant {
         // https://www.w3.org/TR/SVG/pservers.html#RadialGradients
 
         GradientVariant::Radial {
-            cx: Some(Length::parse_str("50%", LengthDir::Horizontal).unwrap()),
-            cy: Some(Length::parse_str("50%", LengthDir::Vertical).unwrap()),
-            r: Some(Length::parse_str("50%", LengthDir::Both).unwrap()),
+            cx: Some(LengthHorizontal::parse_str("50%", ()).unwrap()),
+            cy: Some(LengthVertical::parse_str("50%", ()).unwrap()),
+            r: Some(LengthBoth::parse_str("50%", ()).unwrap()),
 
             fx: None,
             fy: None,
@@ -684,16 +684,16 @@ impl NodeTrait for NodeGradient {
 
                 // Attributes specific to each gradient type.  The defaults mandated by the spec
                 // are in GradientVariant::resolve_from_defaults()
-                Attribute::X1 => x1 = Some(attr.parse(value, LengthDir::Horizontal)?),
-                Attribute::Y1 => y1 = Some(attr.parse(value, LengthDir::Vertical)?),
-                Attribute::X2 => x2 = Some(attr.parse(value, LengthDir::Horizontal)?),
-                Attribute::Y2 => y2 = Some(attr.parse(value, LengthDir::Vertical)?),
-
-                Attribute::Cx => cx = Some(attr.parse(value, LengthDir::Horizontal)?),
-                Attribute::Cy => cy = Some(attr.parse(value, LengthDir::Vertical)?),
-                Attribute::R => r = Some(attr.parse(value, LengthDir::Both)?),
-                Attribute::Fx => fx = Some(attr.parse(value, LengthDir::Horizontal)?),
-                Attribute::Fy => fy = Some(attr.parse(value, LengthDir::Vertical)?),
+                Attribute::X1 => x1 = Some(attr.parse(value, ())?),
+                Attribute::Y1 => y1 = Some(attr.parse(value, ())?),
+                Attribute::X2 => x2 = Some(attr.parse(value, ())?),
+                Attribute::Y2 => y2 = Some(attr.parse(value, ())?),
+
+                Attribute::Cx => cx = Some(attr.parse(value, ())?),
+                Attribute::Cy => cy = Some(attr.parse(value, ())?),
+                Attribute::R => r = Some(attr.parse(value, ())?),
+                Attribute::Fx => fx = Some(attr.parse(value, ())?),
+                Attribute::Fy => fy = Some(attr.parse(value, ())?),
 
                 _ => (),
             }
diff --git a/rsvg_internals/src/image.rs b/rsvg_internals/src/image.rs
index 65f7620f..580baa7a 100644
--- a/rsvg_internals/src/image.rs
+++ b/rsvg_internals/src/image.rs
@@ -9,17 +9,17 @@ use bbox::BoundingBox;
 use drawing_ctx::DrawingCtx;
 use error::{NodeError, RenderingError};
 use float_eq_cairo::ApproxEqCairo;
-use length::*;
+use length::{LengthHorizontal, LengthVertical};
 use node::*;
 use parsers::{ParseError, ParseValue};
 use property_bag::PropertyBag;
 
 pub struct NodeImage {
     aspect: Cell<AspectRatio>,
-    x: Cell<Length>,
-    y: Cell<Length>,
-    w: Cell<Length>,
-    h: Cell<Length>,
+    x: Cell<LengthHorizontal>,
+    y: Cell<LengthVertical>,
+    w: Cell<LengthHorizontal>,
+    h: Cell<LengthVertical>,
     href: RefCell<Option<Href>>,
 }
 
@@ -27,10 +27,10 @@ impl NodeImage {
     pub fn new() -> NodeImage {
         NodeImage {
             aspect: Cell::new(AspectRatio::default()),
-            x: Cell::new(Length::default()),
-            y: Cell::new(Length::default()),
-            w: Cell::new(Length::default()),
-            h: Cell::new(Length::default()),
+            x: Cell::new(Default::default()),
+            y: Cell::new(Default::default()),
+            w: Cell::new(Default::default()),
+            h: Cell::new(Default::default()),
             href: RefCell::new(None),
         }
     }
@@ -44,17 +44,17 @@ impl NodeTrait for NodeImage {
 
         for (attr, value) in pbag.iter() {
             match attr {
-                Attribute::X => self.x.set(attr.parse(value, LengthDir::Horizontal)?),
-                Attribute::Y => self.y.set(attr.parse(value, LengthDir::Vertical)?),
+                Attribute::X => self.x.set(attr.parse(value, ())?),
+                Attribute::Y => self.y.set(attr.parse(value, ())?),
                 Attribute::Width => self.w.set(attr.parse_and_validate(
                     value,
-                    LengthDir::Horizontal,
-                    Length::check_nonnegative,
+                    (),
+                    LengthHorizontal::check_nonnegative,
                 )?),
                 Attribute::Height => self.h.set(attr.parse_and_validate(
                     value,
-                    LengthDir::Vertical,
-                    Length::check_nonnegative,
+                    (),
+                    LengthVertical::check_nonnegative,
                 )?),
 
                 Attribute::PreserveAspectRatio => self.aspect.set(attr.parse(value, ())?),
diff --git a/rsvg_internals/src/length.rs b/rsvg_internals/src/length.rs
index 5dd5b65a..80e8fce1 100644
--- a/rsvg_internals/src/length.rs
+++ b/rsvg_internals/src/length.rs
@@ -17,29 +17,105 @@ pub enum LengthUnit {
 }
 
 #[derive(Debug, PartialEq, Copy, Clone)]
-pub enum LengthDir {
+enum LengthDir {
     Horizontal,
     Vertical,
     Both,
 }
 
+macro_rules! define_length_type {
+    ($name:ident, $dir:expr) => {
+        #[derive(Debug, PartialEq, Copy, Clone)]
+        pub struct $name(Length);
+
+        impl $name {
+            pub fn new(length: f64, unit: LengthUnit) -> Self {
+                $name(Length::new(length, unit, $dir))
+            }
+
+            pub fn length(&self) -> f64 {
+                self.0.length
+            }
+
+            pub fn unit(&self) -> LengthUnit {
+                self.0.unit
+            }
+
+            pub fn get_unitless(&self) -> f64 {
+                self.0.get_unitless()
+            }
+
+            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(),
+                    ))
+                }
+            }
+
+            pub fn normalize(&self, values: &ComputedValues, params: &ViewParams) -> f64 {
+                self.0.normalize(values, params)
+            }
+
+            pub fn hand_normalize(
+                &self,
+                pixels_per_inch: f64,
+                width_or_height: f64,
+                font_size: f64,
+            ) -> f64 {
+                self.0
+                    .hand_normalize(pixels_per_inch, width_or_height, font_size)
+            }
+
+            pub fn from_cssparser(parser: &mut Parser<'_, '_>) -> Result<Self, ValueErrorKind> {
+                Ok($name(Length::from_cssparser(parser, $dir)?))
+            }
+        }
+
+        impl Default for $name {
+            fn default() -> Self {
+                $name(Length::new(0.0, LengthUnit::Default, $dir))
+            }
+        }
+
+        impl Parse for $name {
+            type Data = ();
+            type Err = ValueErrorKind;
+
+            fn parse(parser: &mut Parser<'_, '_>, _: ()) -> Result<$name, ValueErrorKind> {
+                Ok($name(Length::parse(parser, $dir)?))
+            }
+        }
+    };
+}
+
+/// Horizontal length
+///
+/// When this is specified as a percent value, it will get resolved
+/// against the current viewport's width.
+define_length_type!(LengthHorizontal, LengthDir::Horizontal);
+
+/// Vertical length
+///
+/// When this is specified as a percent value, it will get resolved
+/// against the current viewport's height.
+define_length_type!(LengthVertical, LengthDir::Vertical);
+
+/// "Both" length
+///
+/// When this is specified as a percent value, it will get resolved
+/// against the current viewport's width and height.
+define_length_type!(LengthBoth, LengthDir::Both);
+
 #[derive(Debug, PartialEq, Copy, Clone)]
-pub struct Length {
+struct Length {
     pub length: f64,
     pub unit: LengthUnit,
     dir: LengthDir,
 }
 
-impl Default for Length {
-    fn default() -> Length {
-        Length {
-            length: 0.0,
-            unit: LengthUnit::Default,
-            dir: LengthDir::Both,
-        }
-    }
-}
-
 pub const POINTS_PER_INCH: f64 = 72.0;
 const CM_PER_INCH: f64 = 2.54;
 const MM_PER_INCH: f64 = 25.4;
@@ -76,7 +152,7 @@ impl Parse for Length {
 }
 
 impl Length {
-    pub fn new(l: f64, unit: LengthUnit, dir: LengthDir) -> Length {
+    fn new(l: f64, unit: LengthUnit, dir: LengthDir) -> Length {
         Length {
             length: l,
             unit,
@@ -84,16 +160,6 @@ impl Length {
         }
     }
 
-    pub fn check_nonnegative(self) -> Result<Length, ValueErrorKind> {
-        if self.length >= 0.0 {
-            Ok(self)
-        } else {
-            Err(ValueErrorKind::Value(
-                "value must be non-negative".to_string(),
-            ))
-        }
-    }
-
     pub fn normalize(&self, values: &ComputedValues, params: &ViewParams) -> f64 {
         match self.unit {
             LengthUnit::Default => self.length,
@@ -236,7 +302,7 @@ fn font_size_from_inch(length: f64, dir: LengthDir, params: &ViewParams) -> f64
 }
 
 fn font_size_from_values(values: &ComputedValues, params: &ViewParams) -> f64 {
-    let v = &values.font_size.0.value();
+    let v = &values.font_size.0.value().0;
 
     match v.unit {
         LengthUnit::Default => v.length,
@@ -263,7 +329,7 @@ fn viewport_percentage(x: f64, y: f64) -> f64 {
 #[derive(Debug, PartialEq, Clone)]
 pub enum Dasharray {
     None,
-    Array(Vec<Length>),
+    Array(Vec<LengthBoth>),
 }
 
 impl Default for Dasharray {
@@ -286,13 +352,11 @@ impl Parse for Dasharray {
 }
 
 // This does not handle "inherit" or "none" state, the caller is responsible for that.
-fn parse_dash_array(parser: &mut Parser<'_, '_>) -> Result<Vec<Length>, ValueErrorKind> {
+fn parse_dash_array(parser: &mut Parser<'_, '_>) -> Result<Vec<LengthBoth>, ValueErrorKind> {
     let mut dasharray = Vec::new();
 
     loop {
-        dasharray.push(
-            Length::from_cssparser(parser, LengthDir::Both).and_then(Length::check_nonnegative)?,
-        );
+        dasharray.push(LengthBoth::from_cssparser(parser).and_then(LengthBoth::check_nonnegative)?);
 
         if parser.is_exhausted() {
             break;
@@ -398,10 +462,10 @@ mod tests {
 
     #[test]
     fn check_nonnegative_works() {
-        assert!(Length::parse_str("0", LengthDir::Both)
+        assert!(LengthBoth::parse_str("0", ())
             .and_then(|l| l.check_nonnegative())
             .is_ok());
-        assert!(Length::parse_str("-10", LengthDir::Both)
+        assert!(LengthBoth::parse_str("-10", ())
             .and_then(|l| l.check_nonnegative())
             .is_err());
     }
@@ -478,7 +542,7 @@ mod tests {
     #[test]
     fn parses_dash_array() {
         // helper to cut down boilderplate
-        let length_parse = |s| Length::parse_str(s, LengthDir::Both).unwrap();
+        let length_parse = |s| LengthBoth::parse_str(s, ()).unwrap();
 
         let expected = Dasharray::Array(vec![
             length_parse("1"),
diff --git a/rsvg_internals/src/marker.rs b/rsvg_internals/src/marker.rs
index ba12cd44..9f6843a9 100644
--- a/rsvg_internals/src/marker.rs
+++ b/rsvg_internals/src/marker.rs
@@ -13,7 +13,7 @@ use drawing_ctx::DrawingCtx;
 use error::*;
 use float_eq_cairo::ApproxEqCairo;
 use iri::IRI;
-use length::{Length, LengthDir};
+use length::{LengthHorizontal, LengthVertical};
 use node::*;
 use parsers::{Parse, ParseError, ParseValue};
 use path_builder::*;
@@ -88,10 +88,10 @@ impl Parse for MarkerOrient {
 
 pub struct NodeMarker {
     units: Cell<MarkerUnits>,
-    ref_x: Cell<Length>,
-    ref_y: Cell<Length>,
-    width: Cell<Length>,
-    height: Cell<Length>,
+    ref_x: Cell<LengthHorizontal>,
+    ref_y: Cell<LengthVertical>,
+    width: Cell<LengthHorizontal>,
+    height: Cell<LengthVertical>,
     orient: Cell<MarkerOrient>,
     aspect: Cell<AspectRatio>,
     vbox: Cell<Option<ViewBox>>,
@@ -101,26 +101,17 @@ impl NodeMarker {
     pub fn new() -> NodeMarker {
         NodeMarker {
             units: Cell::new(MarkerUnits::default()),
-            ref_x: Cell::new(Length::default()),
-            ref_y: Cell::new(Length::default()),
-            width: Cell::new(Self::default_width()),
-            height: Cell::new(Self::default_height()),
+            ref_x: Cell::new(Default::default()),
+            ref_y: Cell::new(Default::default()),
+            // the following two are per the spec
+            width: Cell::new(LengthHorizontal::parse_str("3", ()).unwrap()),
+            height: Cell::new(LengthVertical::parse_str("3", ()).unwrap()),
             orient: Cell::new(MarkerOrient::default()),
             aspect: Cell::new(AspectRatio::default()),
             vbox: Cell::new(None),
         }
     }
 
-    fn default_width() -> Length {
-        // per the spec
-        Length::parse_str("3", LengthDir::Horizontal).unwrap()
-    }
-
-    fn default_height() -> Length {
-        // per the spec
-        Length::parse_str("3", LengthDir::Vertical).unwrap()
-    }
-
     fn render(
         &self,
         node: &RsvgNode,
@@ -220,20 +211,20 @@ impl NodeTrait for NodeMarker {
             match attr {
                 Attribute::MarkerUnits => self.units.set(attr.parse(value, ())?),
 
-                Attribute::RefX => self.ref_x.set(attr.parse(value, LengthDir::Horizontal)?),
+                Attribute::RefX => self.ref_x.set(attr.parse(value, ())?),
 
-                Attribute::RefY => self.ref_y.set(attr.parse(value, LengthDir::Vertical)?),
+                Attribute::RefY => self.ref_y.set(attr.parse(value, ())?),
 
                 Attribute::MarkerWidth => self.width.set(attr.parse_and_validate(
                     value,
-                    LengthDir::Horizontal,
-                    Length::check_nonnegative,
+                    (),
+                    LengthHorizontal::check_nonnegative,
                 )?),
 
                 Attribute::MarkerHeight => self.height.set(attr.parse_and_validate(
                     value,
-                    LengthDir::Vertical,
-                    Length::check_nonnegative,
+                    (),
+                    LengthVertical::check_nonnegative,
                 )?),
 
                 Attribute::Orient => self.orient.set(attr.parse(value, ())?),
diff --git a/rsvg_internals/src/mask.rs b/rsvg_internals/src/mask.rs
index 86dda588..6f6ac58d 100644
--- a/rsvg_internals/src/mask.rs
+++ b/rsvg_internals/src/mask.rs
@@ -5,7 +5,7 @@ use attributes::Attribute;
 use coord_units::CoordUnits;
 use drawing_ctx::DrawingCtx;
 use error::RenderingError;
-use length::{Length, LengthDir};
+use length::{LengthHorizontal, LengthVertical};
 use node::{NodeResult, NodeTrait, RsvgNode};
 use parsers::{Parse, ParseValue};
 use properties::Opacity;
@@ -23,10 +23,10 @@ coord_units!(MaskUnits, CoordUnits::ObjectBoundingBox);
 coord_units!(MaskContentUnits, CoordUnits::UserSpaceOnUse);
 
 pub struct NodeMask {
-    x: Cell<Length>,
-    y: Cell<Length>,
-    width: Cell<Length>,
-    height: Cell<Length>,
+    x: Cell<LengthHorizontal>,
+    y: Cell<LengthVertical>,
+    width: Cell<LengthHorizontal>,
+    height: Cell<LengthVertical>,
 
     units: Cell<MaskUnits>,
     content_units: Cell<MaskContentUnits>,
@@ -35,37 +35,18 @@ pub struct NodeMask {
 impl NodeMask {
     pub fn new() -> NodeMask {
         NodeMask {
-            x: Cell::new(Self::default_x()),
-            y: Cell::new(Self::default_y()),
+            // these values are per the spec
+            x: Cell::new(LengthHorizontal::parse_str("-10%", ()).unwrap()),
+            y: Cell::new(LengthVertical::parse_str("-10%", ()).unwrap()),
 
-            width: Cell::new(Self::default_width()),
-            height: Cell::new(Self::default_height()),
+            width: Cell::new(LengthHorizontal::parse_str("120%", ()).unwrap()),
+            height: Cell::new(LengthVertical::parse_str("120%", ()).unwrap()),
 
             units: Cell::new(MaskUnits::default()),
             content_units: Cell::new(MaskContentUnits::default()),
         }
     }
 
-    fn default_x() -> Length {
-        // per the spec
-        Length::parse_str("-10%", LengthDir::Horizontal).unwrap()
-    }
-
-    fn default_y() -> Length {
-        // per the spec
-        Length::parse_str("-10%", LengthDir::Vertical).unwrap()
-    }
-
-    fn default_width() -> Length {
-        // per the spec
-        Length::parse_str("120%", LengthDir::Horizontal).unwrap()
-    }
-
-    fn default_height() -> Length {
-        // per the spec
-        Length::parse_str("120%", LengthDir::Vertical).unwrap()
-    }
-
     pub fn generate_cairo_mask(
         &self,
         node: &RsvgNode,
@@ -207,17 +188,17 @@ impl NodeTrait for NodeMask {
     fn set_atts(&self, _: &RsvgNode, pbag: &PropertyBag<'_>) -> NodeResult {
         for (attr, value) in pbag.iter() {
             match attr {
-                Attribute::X => self.x.set(attr.parse(value, LengthDir::Horizontal)?),
-                Attribute::Y => self.y.set(attr.parse(value, LengthDir::Vertical)?),
+                Attribute::X => self.x.set(attr.parse(value, ())?),
+                Attribute::Y => self.y.set(attr.parse(value, ())?),
                 Attribute::Width => self.width.set(attr.parse_and_validate(
                     value,
-                    LengthDir::Horizontal,
-                    Length::check_nonnegative,
+                    (),
+                    LengthHorizontal::check_nonnegative,
                 )?),
                 Attribute::Height => self.height.set(attr.parse_and_validate(
                     value,
-                    LengthDir::Vertical,
-                    Length::check_nonnegative,
+                    (),
+                    LengthVertical::check_nonnegative,
                 )?),
                 Attribute::MaskUnits => self.units.set(attr.parse(value, ())?),
                 Attribute::MaskContentUnits => self.content_units.set(attr.parse(value, ())?),
diff --git a/rsvg_internals/src/pattern.rs b/rsvg_internals/src/pattern.rs
index 40afce76..36eb84eb 100644
--- a/rsvg_internals/src/pattern.rs
+++ b/rsvg_internals/src/pattern.rs
@@ -37,10 +37,10 @@ pub struct Pattern {
     pub preserve_aspect_ratio: Option<AspectRatio>,
     pub affine: Option<cairo::Matrix>,
     pub fallback: Option<Fragment>,
-    pub x: Option<Length>,
-    pub y: Option<Length>,
-    pub width: Option<Length>,
-    pub height: Option<Length>,
+    pub x: Option<LengthHorizontal>,
+    pub y: Option<LengthVertical>,
+    pub width: Option<LengthHorizontal>,
+    pub height: Option<LengthVertical>,
 
     // 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
@@ -59,10 +59,10 @@ impl Default for Pattern {
             preserve_aspect_ratio: Some(AspectRatio::default()),
             affine: Some(cairo::Matrix::identity()),
             fallback: None,
-            x: Some(Length::default()),
-            y: Some(Length::default()),
-            width: Some(Length::default()),
-            height: Some(Length::default()),
+            x: Some(Default::default()),
+            y: Some(Default::default()),
+            width: Some(Default::default()),
+            height: Some(Default::default()),
             node: None,
         }
     }
@@ -199,23 +199,23 @@ impl NodeTrait for NodePattern {
                     p.fallback = Some(Fragment::parse(value).attribute(Attribute::XlinkHref)?);
                 }
 
-                Attribute::X => p.x = Some(attr.parse(value, LengthDir::Horizontal)?),
+                Attribute::X => p.x = Some(attr.parse(value, ())?),
 
-                Attribute::Y => p.y = Some(attr.parse(value, LengthDir::Vertical)?),
+                Attribute::Y => p.y = Some(attr.parse(value, ())?),
 
                 Attribute::Width => {
                     p.width = Some(attr.parse_and_validate(
                         value,
-                        LengthDir::Horizontal,
-                        Length::check_nonnegative,
+                        (),
+                        LengthHorizontal::check_nonnegative,
                     )?)
                 }
 
                 Attribute::Height => {
                     p.height = Some(attr.parse_and_validate(
                         value,
-                        LengthDir::Vertical,
-                        Length::check_nonnegative,
+                        (),
+                        LengthVertical::check_nonnegative,
                     )?)
                 }
 
diff --git a/rsvg_internals/src/properties.rs b/rsvg_internals/src/properties.rs
index d00e49d9..baa5c3dc 100644
--- a/rsvg_internals/src/properties.rs
+++ b/rsvg_internals/src/properties.rs
@@ -6,7 +6,7 @@ use attributes::Attribute;
 use error::*;
 use font_props::{FontSizeSpec, FontWeightSpec, LetterSpacingSpec, SingleFontFamily};
 use iri::IRI;
-use length::{Dasharray, Length, LengthDir, LengthUnit};
+use length::{Dasharray, LengthBoth, LengthUnit};
 use paint_server::PaintServer;
 use parsers::{Parse, ParseError};
 use property_bag::PropertyBag;
@@ -407,7 +407,7 @@ impl SpecifiedValues {
                 }
 
                 Attribute::StrokeDashoffset => {
-                    self.stroke_dashoffset = parse_property(value, LengthDir::Both)?;
+                    self.stroke_dashoffset = parse_property(value, ())?;
                 }
 
                 Attribute::StrokeLinecap => {
@@ -427,7 +427,7 @@ impl SpecifiedValues {
                 }
 
                 Attribute::StrokeWidth => {
-                    self.stroke_width = parse_property(value, LengthDir::Both)?;
+                    self.stroke_width = parse_property(value, ())?;
                 }
 
                 Attribute::TextAnchor => {
@@ -593,8 +593,8 @@ where
 make_property!(
     ComputedValues,
     BaselineShift,
-    default: Length::parse_str("0.0", LengthDir::Both).unwrap(),
-    newtype: Length,
+    default: LengthBoth::parse_str("0.0", ()).unwrap(),
+    newtype: LengthBoth,
     property_impl: {
         impl Property<ComputedValues> for BaselineShift {
             fn inherits_automatically() -> bool {
@@ -608,11 +608,11 @@ make_property!(
                 // 1) we only handle 'percent' shifts, but it could also be an absolute offset
                 // 2) we should be able to normalize the lengths and add even if they have
                 //    different units, but at the moment that requires access to the draw_ctx
-                if self.0.unit != LengthUnit::Percent || v.baseline_shift.0.unit != font_size.unit {
-                    return BaselineShift(Length::new(v.baseline_shift.0.length, v.baseline_shift.0.unit, 
LengthDir::Both));
+                if self.0.unit() != LengthUnit::Percent || v.baseline_shift.0.unit() != font_size.unit() {
+                    return BaselineShift(LengthBoth::new(v.baseline_shift.0.length(), 
v.baseline_shift.0.unit()));
                 }
 
-                BaselineShift(Length::new(self.0.length * font_size.length + v.baseline_shift.0.length, 
font_size.unit, LengthDir::Both))
+                BaselineShift(LengthBoth::new(self.0.length() * font_size.length() + 
v.baseline_shift.0.length(), font_size.unit()))
             }
         }
     },
@@ -634,15 +634,15 @@ make_property!(
                     if let Token::Ident(ref cow) = token {
                         match cow.as_ref() {
                             "baseline" => return Ok(BaselineShift(
-                                Length::new(0.0, LengthUnit::Percent, LengthDir::Both)
+                                LengthBoth::new(0.0, LengthUnit::Percent)
                             )),
 
                             "sub" => return Ok(BaselineShift(
-                                Length::new(-0.2, LengthUnit::Percent, LengthDir::Both)
+                                LengthBoth::new(-0.2, LengthUnit::Percent)
                             )),
 
                             "super" => return Ok(BaselineShift(
-                                Length::new(0.4, LengthUnit::Percent, LengthDir::Both),
+                                LengthBoth::new(0.4, LengthUnit::Percent),
                             )),
 
                             _ => (),
@@ -652,7 +652,7 @@ make_property!(
 
                 parser.reset(&parser_state);
 
-                Ok(BaselineShift(Length::from_cssparser(parser, LengthDir::Both)?))
+                Ok(BaselineShift(LengthBoth::from_cssparser(parser)?))
             }
         }
     }
@@ -836,7 +836,7 @@ make_property!(
 make_property!(
     ComputedValues,
     FontSize,
-    default: FontSizeSpec::Value(Length::parse_str("12.0", LengthDir::Both).unwrap()),
+    default: FontSizeSpec::Value(LengthBoth::parse_str("12.0", ()).unwrap()),
     newtype_parse: FontSizeSpec,
     parse_data_type: (),
     property_impl: {
@@ -1060,10 +1060,10 @@ make_property!(
 make_property!(
     ComputedValues,
     StrokeDashoffset,
-    default: Length::default(),
+    default: LengthBoth::default(),
     inherits_automatically: true,
-    newtype_parse: Length,
-    parse_data_type: LengthDir
+    newtype_parse: LengthBoth,
+    parse_data_type: ()
 );
 
 // https://www.w3.org/TR/SVG/painting.html#StrokeLinecapProperty
@@ -1116,10 +1116,10 @@ make_property!(
 make_property!(
     ComputedValues,
     StrokeWidth,
-    default: Length::parse_str("1.0", LengthDir::Both).unwrap(),
+    default: LengthBoth::parse_str("1.0", ()).unwrap(),
     inherits_automatically: true,
-    newtype_parse: Length,
-    parse_data_type: LengthDir
+    newtype_parse: LengthBoth,
+    parse_data_type: ()
 );
 
 // https://www.w3.org/TR/SVG/text.html#TextAnchorProperty
diff --git a/rsvg_internals/src/shapes.rs b/rsvg_internals/src/shapes.rs
index 9ce4c9d6..85161788 100644
--- a/rsvg_internals/src/shapes.rs
+++ b/rsvg_internals/src/shapes.rs
@@ -276,19 +276,19 @@ impl NodeTrait for NodePoly {
 }
 
 pub struct NodeLine {
-    x1: Cell<Length>,
-    y1: Cell<Length>,
-    x2: Cell<Length>,
-    y2: Cell<Length>,
+    x1: Cell<LengthHorizontal>,
+    y1: Cell<LengthVertical>,
+    x2: Cell<LengthHorizontal>,
+    y2: Cell<LengthVertical>,
 }
 
 impl NodeLine {
     pub fn new() -> NodeLine {
         NodeLine {
-            x1: Cell::new(Length::default()),
-            y1: Cell::new(Length::default()),
-            x2: Cell::new(Length::default()),
-            y2: Cell::new(Length::default()),
+            x1: Cell::new(Default::default()),
+            y1: Cell::new(Default::default()),
+            x2: Cell::new(Default::default()),
+            y2: Cell::new(Default::default()),
         }
     }
 }
@@ -297,10 +297,10 @@ impl NodeTrait for NodeLine {
     fn set_atts(&self, _: &RsvgNode, pbag: &PropertyBag<'_>) -> NodeResult {
         for (attr, value) in pbag.iter() {
             match attr {
-                Attribute::X1 => self.x1.set(attr.parse(value, LengthDir::Horizontal)?),
-                Attribute::Y1 => self.y1.set(attr.parse(value, LengthDir::Vertical)?),
-                Attribute::X2 => self.x2.set(attr.parse(value, LengthDir::Horizontal)?),
-                Attribute::Y2 => self.y2.set(attr.parse(value, LengthDir::Vertical)?),
+                Attribute::X1 => self.x1.set(attr.parse(value, ())?),
+                Attribute::Y1 => self.y1.set(attr.parse(value, ())?),
+                Attribute::X2 => self.x2.set(attr.parse(value, ())?),
+                Attribute::Y2 => self.y2.set(attr.parse(value, ())?),
                 _ => (),
             }
         }
@@ -335,23 +335,23 @@ impl NodeTrait for NodeLine {
 
 pub struct NodeRect {
     // x, y, width, height
-    x: Cell<Length>,
-    y: Cell<Length>,
-    w: Cell<Length>,
-    h: Cell<Length>,
+    x: Cell<LengthHorizontal>,
+    y: Cell<LengthVertical>,
+    w: Cell<LengthHorizontal>,
+    h: Cell<LengthVertical>,
 
     // Radiuses for rounded corners
-    rx: Cell<Option<Length>>,
-    ry: Cell<Option<Length>>,
+    rx: Cell<Option<LengthHorizontal>>,
+    ry: Cell<Option<LengthVertical>>,
 }
 
 impl NodeRect {
     pub fn new() -> NodeRect {
         NodeRect {
-            x: Cell::new(Length::default()),
-            y: Cell::new(Length::default()),
-            w: Cell::new(Length::default()),
-            h: Cell::new(Length::default()),
+            x: Cell::new(Default::default()),
+            y: Cell::new(Default::default()),
+            w: Cell::new(Default::default()),
+            h: Cell::new(Default::default()),
 
             rx: Cell::new(None),
             ry: Cell::new(None),
@@ -363,28 +363,24 @@ impl NodeTrait for NodeRect {
     fn set_atts(&self, _: &RsvgNode, pbag: &PropertyBag<'_>) -> NodeResult {
         for (attr, value) in pbag.iter() {
             match attr {
-                Attribute::X => self.x.set(attr.parse(value, LengthDir::Horizontal)?),
-                Attribute::Y => self.y.set(attr.parse(value, LengthDir::Vertical)?),
+                Attribute::X => self.x.set(attr.parse(value, ())?),
+                Attribute::Y => self.y.set(attr.parse(value, ())?),
                 Attribute::Width => self.w.set(attr.parse_and_validate(
                     value,
-                    LengthDir::Horizontal,
-                    Length::check_nonnegative,
+                    (),
+                    LengthHorizontal::check_nonnegative,
                 )?),
                 Attribute::Height => self.h.set(attr.parse_and_validate(
                     value,
-                    LengthDir::Vertical,
-                    Length::check_nonnegative,
+                    (),
+                    LengthVertical::check_nonnegative,
                 )?),
                 Attribute::Rx => self.rx.set(
-                    attr.parse_and_validate(
-                        value,
-                        LengthDir::Horizontal,
-                        Length::check_nonnegative,
-                    )
-                    .map(Some)?,
+                    attr.parse_and_validate(value, (), LengthHorizontal::check_nonnegative)
+                        .map(Some)?,
                 ),
                 Attribute::Ry => self.ry.set(
-                    attr.parse_and_validate(value, LengthDir::Vertical, Length::check_nonnegative)
+                    attr.parse_and_validate(value, (), LengthVertical::check_nonnegative)
                         .map(Some)?,
                 ),
 
@@ -544,17 +540,17 @@ impl NodeTrait for NodeRect {
 }
 
 pub struct NodeCircle {
-    cx: Cell<Length>,
-    cy: Cell<Length>,
-    r: Cell<Length>,
+    cx: Cell<LengthHorizontal>,
+    cy: Cell<LengthVertical>,
+    r: Cell<LengthBoth>,
 }
 
 impl NodeCircle {
     pub fn new() -> NodeCircle {
         NodeCircle {
-            cx: Cell::new(Length::default()),
-            cy: Cell::new(Length::default()),
-            r: Cell::new(Length::default()),
+            cx: Cell::new(Default::default()),
+            cy: Cell::new(Default::default()),
+            r: Cell::new(Default::default()),
         }
     }
 }
@@ -563,13 +559,12 @@ impl NodeTrait for NodeCircle {
     fn set_atts(&self, _: &RsvgNode, pbag: &PropertyBag<'_>) -> NodeResult {
         for (attr, value) in pbag.iter() {
             match attr {
-                Attribute::Cx => self.cx.set(attr.parse(value, LengthDir::Horizontal)?),
-                Attribute::Cy => self.cy.set(attr.parse(value, LengthDir::Vertical)?),
-                Attribute::R => self.r.set(attr.parse_and_validate(
-                    value,
-                    LengthDir::Both,
-                    Length::check_nonnegative,
-                )?),
+                Attribute::Cx => self.cx.set(attr.parse(value, ())?),
+                Attribute::Cy => self.cy.set(attr.parse(value, ())?),
+                Attribute::R => {
+                    self.r
+                        .set(attr.parse_and_validate(value, (), LengthBoth::check_nonnegative)?)
+                }
 
                 _ => (),
             }
@@ -598,19 +593,19 @@ impl NodeTrait for NodeCircle {
 }
 
 pub struct NodeEllipse {
-    cx: Cell<Length>,
-    cy: Cell<Length>,
-    rx: Cell<Length>,
-    ry: Cell<Length>,
+    cx: Cell<LengthHorizontal>,
+    cy: Cell<LengthVertical>,
+    rx: Cell<LengthHorizontal>,
+    ry: Cell<LengthVertical>,
 }
 
 impl NodeEllipse {
     pub fn new() -> NodeEllipse {
         NodeEllipse {
-            cx: Cell::new(Length::default()),
-            cy: Cell::new(Length::default()),
-            rx: Cell::new(Length::default()),
-            ry: Cell::new(Length::default()),
+            cx: Cell::new(Default::default()),
+            cy: Cell::new(Default::default()),
+            rx: Cell::new(Default::default()),
+            ry: Cell::new(Default::default()),
         }
     }
 }
@@ -619,17 +614,17 @@ impl NodeTrait for NodeEllipse {
     fn set_atts(&self, _: &RsvgNode, pbag: &PropertyBag<'_>) -> NodeResult {
         for (attr, value) in pbag.iter() {
             match attr {
-                Attribute::Cx => self.cx.set(attr.parse(value, LengthDir::Horizontal)?),
-                Attribute::Cy => self.cy.set(attr.parse(value, LengthDir::Vertical)?),
+                Attribute::Cx => self.cx.set(attr.parse(value, ())?),
+                Attribute::Cy => self.cy.set(attr.parse(value, ())?),
                 Attribute::Rx => self.rx.set(attr.parse_and_validate(
                     value,
-                    LengthDir::Horizontal,
-                    Length::check_nonnegative,
+                    (),
+                    LengthHorizontal::check_nonnegative,
                 )?),
                 Attribute::Ry => self.ry.set(attr.parse_and_validate(
                     value,
-                    LengthDir::Vertical,
-                    Length::check_nonnegative,
+                    (),
+                    LengthVertical::check_nonnegative,
                 )?),
 
                 _ => (),
diff --git a/rsvg_internals/src/stop.rs b/rsvg_internals/src/stop.rs
index 8b549ba7..158e2e50 100644
--- a/rsvg_internals/src/stop.rs
+++ b/rsvg_internals/src/stop.rs
@@ -24,8 +24,8 @@ impl NodeStop {
     }
 }
 
-fn validate_offset(length: Length) -> Result<Length, ValueErrorKind> {
-    match length.unit {
+fn validate_offset(length: LengthBoth) -> Result<LengthBoth, ValueErrorKind> {
+    match length.unit() {
         LengthUnit::Default | LengthUnit::Percent => Ok(length),
         _ => Err(ValueErrorKind::Value(
             "stop offset must be in default or percent units".to_string(),
@@ -39,8 +39,8 @@ impl NodeTrait for NodeStop {
             match attr {
                 Attribute::Offset => {
                     self.offset.set(
-                        attr.parse_and_validate(value, LengthDir::Both, validate_offset)
-                            .map(|l| UnitInterval::clamp(l.length))?,
+                        attr.parse_and_validate(value, (), validate_offset)
+                            .map(|l| UnitInterval::clamp(l.length()))?,
                     );
                 }
                 _ => (),
diff --git a/rsvg_internals/src/structure.rs b/rsvg_internals/src/structure.rs
index f12cddcb..641be301 100644
--- a/rsvg_internals/src/structure.rs
+++ b/rsvg_internals/src/structure.rs
@@ -97,10 +97,10 @@ impl NodeTrait for NodeSwitch {
 
 pub struct NodeSvg {
     preserve_aspect_ratio: Cell<AspectRatio>,
-    x: Cell<Length>,
-    y: Cell<Length>,
-    w: Cell<Length>,
-    h: Cell<Length>,
+    x: Cell<LengthHorizontal>,
+    y: Cell<LengthVertical>,
+    w: Cell<LengthHorizontal>,
+    h: Cell<LengthVertical>,
     vbox: Cell<Option<ViewBox>>,
     pbag: RefCell<Option<OwnedPropertyBag>>,
 }
@@ -109,10 +109,10 @@ impl NodeSvg {
     pub fn new() -> NodeSvg {
         NodeSvg {
             preserve_aspect_ratio: Cell::new(AspectRatio::default()),
-            x: Cell::new(Length::parse_str("0", LengthDir::Horizontal).unwrap()),
-            y: Cell::new(Length::parse_str("0", LengthDir::Vertical).unwrap()),
-            w: Cell::new(Length::parse_str("100%", LengthDir::Horizontal).unwrap()),
-            h: Cell::new(Length::parse_str("100%", LengthDir::Vertical).unwrap()),
+            x: Cell::new(LengthHorizontal::parse_str("0", ()).unwrap()),
+            y: Cell::new(LengthVertical::parse_str("0", ()).unwrap()),
+            w: Cell::new(LengthHorizontal::parse_str("100%", ()).unwrap()),
+            h: Cell::new(LengthVertical::parse_str("100%", ()).unwrap()),
             vbox: Cell::new(None),
             pbag: RefCell::new(None),
         }
@@ -131,7 +131,7 @@ impl NodeSvg {
                 w.hand_normalize(dpi.x(), vbox.width, 12.0).round() as i32,
                 h.hand_normalize(dpi.y(), vbox.height, 12.0).round() as i32,
             )),
-            (w, h, None) if w.unit != LengthUnit::Percent && h.unit != LengthUnit::Percent => {
+            (w, h, None) if w.unit() != LengthUnit::Percent && h.unit() != LengthUnit::Percent => {
                 Some((
                     w.hand_normalize(dpi.x(), 0.0, 12.0).round() as i32,
                     h.hand_normalize(dpi.y(), 0.0, 12.0).round() as i32,
@@ -160,26 +160,26 @@ impl NodeTrait for NodeSvg {
 
                 Attribute::X => {
                     if is_inner_svg {
-                        self.x.set(attr.parse(value, LengthDir::Horizontal)?);
+                        self.x.set(attr.parse(value, ())?);
                     }
                 }
 
                 Attribute::Y => {
                     if is_inner_svg {
-                        self.y.set(attr.parse(value, LengthDir::Vertical)?);
+                        self.y.set(attr.parse(value, ())?);
                     }
                 }
 
                 Attribute::Width => self.w.set(attr.parse_and_validate(
                     value,
-                    LengthDir::Horizontal,
-                    Length::check_nonnegative,
+                    (),
+                    LengthHorizontal::check_nonnegative,
                 )?),
 
                 Attribute::Height => self.h.set(attr.parse_and_validate(
                     value,
-                    LengthDir::Vertical,
-                    Length::check_nonnegative,
+                    (),
+                    LengthVertical::check_nonnegative,
                 )?),
 
                 Attribute::ViewBox => self.vbox.set(attr.parse(value, ()).map(Some)?),
@@ -237,18 +237,18 @@ impl NodeTrait for NodeSvg {
 
 pub struct NodeUse {
     link: RefCell<Option<Fragment>>,
-    x: Cell<Length>,
-    y: Cell<Length>,
-    w: Cell<Option<Length>>,
-    h: Cell<Option<Length>>,
+    x: Cell<LengthHorizontal>,
+    y: Cell<LengthVertical>,
+    w: Cell<Option<LengthHorizontal>>,
+    h: Cell<Option<LengthVertical>>,
 }
 
 impl NodeUse {
     pub fn new() -> NodeUse {
         NodeUse {
             link: RefCell::new(None),
-            x: Cell::new(Length::default()),
-            y: Cell::new(Length::default()),
+            x: Cell::new(Default::default()),
+            y: Cell::new(Default::default()),
             w: Cell::new(None),
             h: Cell::new(None),
         }
@@ -264,19 +264,15 @@ impl NodeTrait for NodeUse {
                         Some(Fragment::parse(value).attribute(Attribute::XlinkHref)?)
                 }
 
-                Attribute::X => self.x.set(attr.parse(value, LengthDir::Horizontal)?),
-                Attribute::Y => self.y.set(attr.parse(value, LengthDir::Vertical)?),
+                Attribute::X => self.x.set(attr.parse(value, ())?),
+                Attribute::Y => self.y.set(attr.parse(value, ())?),
 
                 Attribute::Width => self.w.set(
-                    attr.parse_and_validate(
-                        value,
-                        LengthDir::Horizontal,
-                        Length::check_nonnegative,
-                    )
-                    .map(Some)?,
+                    attr.parse_and_validate(value, (), LengthHorizontal::check_nonnegative)
+                        .map(Some)?,
                 ),
                 Attribute::Height => self.h.set(
-                    attr.parse_and_validate(value, LengthDir::Vertical, Length::check_nonnegative)
+                    attr.parse_and_validate(value, (), LengthVertical::check_nonnegative)
                         .map(Some)?,
                 ),
 
@@ -338,12 +334,12 @@ impl NodeTrait for NodeUse {
         let nw = self
             .w
             .get()
-            .unwrap_or_else(|| Length::parse_str("100%", LengthDir::Horizontal).unwrap())
+            .unwrap_or_else(|| LengthHorizontal::parse_str("100%", ()).unwrap())
             .normalize(values, &params);
         let nh = self
             .h
             .get()
-            .unwrap_or_else(|| Length::parse_str("100%", LengthDir::Vertical).unwrap())
+            .unwrap_or_else(|| LengthVertical::parse_str("100%", ()).unwrap())
             .normalize(values, &params);
 
         // width or height set to 0 disables rendering of the element
diff --git a/rsvg_internals/src/text.rs b/rsvg_internals/src/text.rs
index 002c122c..ab729916 100644
--- a/rsvg_internals/src/text.rs
+++ b/rsvg_internals/src/text.rs
@@ -45,15 +45,15 @@ use space::{xml_space_normalize, NormalizeDefault, XmlSpaceNormalize};
 /// [text chunk]: https://www.w3.org/TR/SVG11/text.html#TextLayoutIntroduction
 struct Chunk {
     values: ComputedValues,
-    x: Option<Length>,
-    y: Option<Length>,
+    x: Option<LengthHorizontal>,
+    y: Option<LengthVertical>,
     spans: Vec<Span>,
 }
 
 struct MeasuredChunk {
     values: ComputedValues,
-    x: Option<Length>,
-    y: Option<Length>,
+    x: Option<LengthHorizontal>,
+    y: Option<LengthVertical>,
     advance: (f64, f64),
     spans: Vec<MeasuredSpan>,
 }
@@ -67,8 +67,8 @@ struct PositionedChunk {
 struct Span {
     values: ComputedValues,
     text: String,
-    dx: Option<Length>,
-    dy: Option<Length>,
+    dx: Option<LengthHorizontal>,
+    dy: Option<LengthVertical>,
     _depth: usize,
 }
 
@@ -77,8 +77,8 @@ struct MeasuredSpan {
     layout: pango::Layout,
     _layout_size: (f64, f64),
     advance: (f64, f64),
-    dx: Option<Length>,
-    dy: Option<Length>,
+    dx: Option<LengthHorizontal>,
+    dy: Option<LengthVertical>,
 }
 
 struct PositionedSpan {
@@ -89,7 +89,11 @@ struct PositionedSpan {
 }
 
 impl Chunk {
-    fn new(values: &ComputedValues, x: Option<Length>, y: Option<Length>) -> Chunk {
+    fn new(
+        values: &ComputedValues,
+        x: Option<LengthHorizontal>,
+        y: Option<LengthVertical>,
+    ) -> Chunk {
         Chunk {
             values: values.clone(),
             x,
@@ -182,8 +186,8 @@ impl Span {
     fn new(
         text: &str,
         values: ComputedValues,
-        dx: Option<Length>,
-        dy: Option<Length>,
+        dx: Option<LengthHorizontal>,
+        dy: Option<LengthVertical>,
         depth: usize,
     ) -> Span {
         Span {
@@ -415,8 +419,8 @@ fn children_to_chunks(
     node: &RsvgNode,
     cascaded: &CascadedValues<'_>,
     draw_ctx: &mut DrawingCtx,
-    dx: Option<Length>,
-    dy: Option<Length>,
+    dx: Option<LengthHorizontal>,
+    dy: Option<LengthVertical>,
     depth: usize,
 ) {
     for child in node.children() {
@@ -507,8 +511,8 @@ impl NodeChars {
         &self,
         node: &RsvgNode,
         values: &ComputedValues,
-        dx: Option<Length>,
-        dy: Option<Length>,
+        dx: Option<LengthHorizontal>,
+        dy: Option<LengthVertical>,
         depth: usize,
     ) -> Span {
         self.ensure_normalized_string(node, values);
@@ -527,8 +531,8 @@ impl NodeChars {
         node: &RsvgNode,
         values: &ComputedValues,
         chunks: &mut Vec<Chunk>,
-        dx: Option<Length>,
-        dy: Option<Length>,
+        dx: Option<LengthHorizontal>,
+        dy: Option<LengthVertical>,
         depth: usize,
     ) {
         let span = self.make_span(&node, values, dx, dy, depth);
@@ -547,17 +551,17 @@ impl NodeTrait for NodeChars {
 }
 
 pub struct NodeText {
-    x: Cell<Length>,
-    y: Cell<Length>,
-    dx: Cell<Option<Length>>,
-    dy: Cell<Option<Length>>,
+    x: Cell<LengthHorizontal>,
+    y: Cell<LengthVertical>,
+    dx: Cell<Option<LengthHorizontal>>,
+    dy: Cell<Option<LengthVertical>>,
 }
 
 impl NodeText {
     pub fn new() -> NodeText {
         NodeText {
-            x: Cell::new(Length::default()),
-            y: Cell::new(Length::default()),
+            x: Cell::new(Default::default()),
+            y: Cell::new(Default::default()),
             dx: Cell::new(None),
             dy: Cell::new(None),
         }
@@ -587,14 +591,10 @@ impl NodeTrait for NodeText {
     fn set_atts(&self, _: &RsvgNode, pbag: &PropertyBag<'_>) -> NodeResult {
         for (attr, value) in pbag.iter() {
             match attr {
-                Attribute::X => self.x.set(attr.parse(value, LengthDir::Horizontal)?),
-                Attribute::Y => self.y.set(attr.parse(value, LengthDir::Vertical)?),
-                Attribute::Dx => self
-                    .dx
-                    .set(attr.parse(value, LengthDir::Horizontal).map(Some)?),
-                Attribute::Dy => self
-                    .dy
-                    .set(attr.parse(value, LengthDir::Vertical).map(Some)?),
+                Attribute::X => self.x.set(attr.parse(value, ())?),
+                Attribute::Y => self.y.set(attr.parse(value, ())?),
+                Attribute::Dx => self.dx.set(attr.parse(value, ()).map(Some)?),
+                Attribute::Dy => self.dy.set(attr.parse(value, ()).map(Some)?),
                 _ => (),
             }
         }
@@ -624,10 +624,12 @@ impl NodeTrait for NodeText {
 
         let mut positioned_chunks = Vec::new();
         for chunk in &measured_chunks {
-            let normalize = |l: Length| l.normalize(&chunk.values, &params);
-
-            let chunk_x = chunk.x.map_or(x, normalize);
-            let chunk_y = chunk.y.map_or(y, normalize);
+            let chunk_x = chunk
+                .x
+                .map_or_else(|| x, |l| l.normalize(&chunk.values, &params));
+            let chunk_y = chunk
+                .y
+                .map_or_else(|| y, |l| l.normalize(&chunk.values, &params));
 
             let positioned = PositionedChunk::from_measured(&chunk, draw_ctx, chunk_x, chunk_y);
 
@@ -725,10 +727,10 @@ impl NodeTrait for NodeTRef {
 }
 
 pub struct NodeTSpan {
-    x: Cell<Option<Length>>,
-    y: Cell<Option<Length>>,
-    dx: Cell<Option<Length>>,
-    dy: Cell<Option<Length>>,
+    x: Cell<Option<LengthHorizontal>>,
+    y: Cell<Option<LengthVertical>>,
+    dx: Cell<Option<LengthHorizontal>>,
+    dy: Cell<Option<LengthVertical>>,
 }
 
 impl NodeTSpan {
@@ -768,18 +770,10 @@ impl NodeTrait for NodeTSpan {
     fn set_atts(&self, _: &RsvgNode, pbag: &PropertyBag<'_>) -> NodeResult {
         for (attr, value) in pbag.iter() {
             match attr {
-                Attribute::X => self
-                    .x
-                    .set(attr.parse(value, LengthDir::Horizontal).map(Some)?),
-                Attribute::Y => self
-                    .y
-                    .set(attr.parse(value, LengthDir::Vertical).map(Some)?),
-                Attribute::Dx => self
-                    .dx
-                    .set(attr.parse(value, LengthDir::Horizontal).map(Some)?),
-                Attribute::Dy => self
-                    .dy
-                    .set(attr.parse(value, LengthDir::Vertical).map(Some)?),
+                Attribute::X => self.x.set(attr.parse(value, ()).map(Some)?),
+                Attribute::Y => self.y.set(attr.parse(value, ()).map(Some)?),
+                Attribute::Dx => self.dx.set(attr.parse(value, ()).map(Some)?),
+                Attribute::Dy => self.dy.set(attr.parse(value, ()).map(Some)?),
                 _ => (),
             }
         }


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