[librsvg: 4/10] Define MaskType for the mask-type property




commit 92671aebbe725d2a8cb8b9093491636090e676c6
Author: Federico Mena Quintero <federico gnome org>
Date:   Fri Oct 15 13:48:53 2021 -0500

    Define MaskType for the mask-type property
    
    Also, continue the description in the properties tutorial.
    
    Part-of: <https://gitlab.gnome.org/GNOME/librsvg/-/merge_requests/609>

 devel-docs/adding-a-property.md | 184 ++++++++++++++++++++++++++++++++++++++++
 src/property_defs.rs            |  13 +++
 2 files changed, 197 insertions(+)
---
diff --git a/devel-docs/adding-a-property.md b/devel-docs/adding-a-property.md
index 8b06acd3..1f119dad 100644
--- a/devel-docs/adding-a-property.md
+++ b/devel-docs/adding-a-property.md
@@ -93,6 +93,190 @@ Let's go on.
 
 ## How librsvg represents properties
 
+Each property has a Rust type that can hold its values.  Remember the part of the masking
+spec from above, that says the `mask-type` property can have values `luminance` or
+`alpha`, and the initial/default is `luminance`?  This translates easily to Rust types:
+
+```rust
+#[derive(Debug, Copy, Clone, PartialEq)]
+pub enum MaskType {
+    Luminance,
+    Alpha,
+}
+
+impl Default for MaskType {
+    fn default() -> MaskType {
+        MaskType::Luminance
+    }
+}
+```
+
+Additionally, we need to be able to say that the property does not inherit by default, and
+that its computed value is the same as the specified value (e.g. we can just copy the
+original value without changing it).  Librsvg defines a `Property` trait for those actions:
+
+```rust
+pub trait Property {
+    fn inherits_automatically() -> bool;
+
+    fn compute(&self, _: &ComputedValues) -> Self;
+}
+```
+
+For the `mask-type` property, we want `inherits_automatically` to return `false`, and
+`compute` to return the value unchanged.  So, like this:
+
+```rust
+impl Property for MaskType {
+    fn inherits_automatically() -> bool {
+        false
+    }
+    
+    fn compute(&self, _: &ComputedValues) -> Self {
+        self.clone()
+    }
+}
+```
+
+Ignore the `ComputedValues` argument for now — it is how librsvg represents an element's
+complete set of property values.
+
+As you can imagine, there are a lot of properties like `mask-type`, whose values are just
+symbolic names that map well to a data-less enum.  For all of them, it would be a lot of
+repetitive code to define their default value, return whether they inherit or not, and
+clone them for the computed value.  Additionally, we have not even written the parser for
+this property's values yet.
+
+Fortunately, librsvg has a `make_property!` macro that lets you
+do this instead:
+
+```rust
+make_property!(
+    /// `mask-type` property.                                          // (1)
+    ///
+    /// https://www.w3.org/TR/css-masking-1/#the-mask-type
+    MaskType,                                                          // (2)
+    default: Luminance,                                                // (3)
+    inherits_automatically: false,                                     // (4)
+
+    identifiers:                                                       // (5)
+    "luminance" => Luminance,
+    "alpha" => Alpha,
+);
+```
+
+* (1) is a documentation comment for the `MaskType` enum being defined.
+
+* (2) is `MaskType`, the name we will use for the `mask-type` property.
+
+* (3) indicates the "initial value", or default, for the property.
+
+* (4) ... whether the spec says the property should inherit or not.
+
+* (5) Finally, `identifiers:` is what makes the `make_property!` macro know that it should
+  generate a parser for the symbolic names `luminance` and `alpha`, and that they should
+  correspond to the values `MaskType::Luminance` and `MaskType::Alpha`, respectively.
+  
+This saves a lot of typing!  Also, it makes it easier to gradually change the way
+properties are represented, as librsvg evolves.
+
+## Properties that use the same data type
+
+Consider the `stroke` and `fill` properties; both store a
+[`<paint>`](https://www.w3.org/TR/SVG2/painting.html#SpecifyingPaint) value, which librsvg
+represents with a type called `PaintServer`.  The `make_property!` macro has a case for
+properties like that, so in the librsvg source code you will find both of thsese:
+
+```rust
+make_property!(
+    /// `fill` property.
+    ///
+    /// https://www.w3.org/TR/SVG/painting.html#FillProperty
+    ///
+    /// https://www.w3.org/TR/SVG2/painting.html#FillProperty
+    Fill,
+    default: PaintServer::parse_str("#000").unwrap(),
+    inherits_automatically: true,
+    newtype_parse: PaintServer,
+);
+
+make_property!(
+    /// `stroke` property.
+    ///
+    /// https://www.w3.org/TR/SVG2/painting.html#SpecifyingStrokePaint
+    Stroke,
+    default: PaintServer::None,
+    inherits_automatically: true,
+    newtype_parse: PaintServer,
+);
+```
+
+The `newtype_parse:` is what tells the macro that it should generate a newtype like
+`struct Stroke(PaintServer)`, and that it should just use the parser that `PaintServer`
+already has.
+
+Which parser is that?  Read on.
+
+## Custom parsers
+
+Librsvg has a `Parse` trait for property values which looks rather scary:
+
+```rust
+pub trait Parse: Sized {
+    fn parse<'i>(parser: &mut Parser<'i, '_>) -> Result<Self, ParseError<'i>>;
+}
+```
+
+Don't let the lifetimes scare you.  They are required because of `cssparser::Parser`, from
+the `cssparser` crate, tries really hard to let you implement zero-copy parsers, which
+give you string tokens as slices from the original string being parsed, instead of
+allocating lots of little `String` values.  What this `Parse` trait means is, you get
+tokens out of the `Parser`, and return what is basically a `Result<Self, Error>`.
+
+In this tutorial we will just show you the parser for simple numeric types, for example,
+for properties that can just be represented with an `f64`.  There is the `stroke-miterlimit` property 
defined like this:
+
+```rust
+make_property!(
+    /// `stroke-miterlimit` property.
+    ///
+    /// https://www.w3.org/TR/SVG2/painting.html#StrokeMiterlimitProperty
+    StrokeMiterlimit,
+    default: 4f64,
+    inherits_automatically: true,
+    newtype_parse: f64,
+);
+```
+
+And the `impl Parse for f64` looks like this:
+
+```rust
+impl Parse for f64 {
+    fn parse<'i>(parser: &mut Parser<'i, '_>) -> Result<Self, ParseError<'i>> {
+        let loc = parser.current_source_location();                                          // (1)
+        let n = parser.expect_number()?;                                                     // (2)
+        if n.is_finite() {                                                                   // (3)
+            Ok(f64::from(n))                                                                 // (4)
+        } else {
+            Err(loc.new_custom_error(ValueErrorKind::value_error("expected finite number"))) // (5)
+        }
+    }
+}
+```
+
+* (1) Store the current location in the parser.
+
+* (2) Ask the parser for a number.  If a non-numeric token comes out (e.g. if the user put 
`stroke-miterlimit: foo` instead of `stroke-miterlimit: 5`), `expect_number` will return an `Err`, which we 
propagate upwards with the `?`.
+
+* (3) Check the number for being non-infinite or NaN....
+
+* (4) ... and return the number converted to f64 (`cssparser` returns f32, but we promote them so that 
subsequent calculations can use the extra precision)...
+
+* (5) ... or return an error based on the location from (1).
+
+My advice: implement new parsers by doing cut&paste from existing ones, and you'll be okay.
+
+
 
 
 
diff --git a/src/property_defs.rs b/src/property_defs.rs
index ff6818a8..e9cd1352 100644
--- a/src/property_defs.rs
+++ b/src/property_defs.rs
@@ -590,6 +590,19 @@ make_property!(
     newtype_parse: Iri,
 );
 
+make_property!(
+    /// `mask-type` property.
+    ///
+    /// https://www.w3.org/TR/css-masking-1/#the-mask-type
+    MaskType,
+    default: Luminance,
+    inherits_automatically: false,
+
+    identifiers:
+    "luminance" => Luminance,
+    "alpha" => Alpha,
+);
+
 make_property!(
     /// `mix-blend-mode` property.
     ///


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