summaryrefslogtreecommitdiff
path: root/devel-docs
diff options
context:
space:
mode:
authorFederico Mena Quintero <federico@gnome.org>2021-10-15 13:48:53 -0500
committerMarge Bot <marge-bot@gnome.org>2021-10-15 23:56:45 +0000
commit92671aebbe725d2a8cb8b9093491636090e676c6 (patch)
tree62bf4ce90c3261f5983be553297d06f502b27526 /devel-docs
parent3107a74a09028ad91944b967186de3b8e49448c5 (diff)
downloadlibrsvg-92671aebbe725d2a8cb8b9093491636090e676c6.tar.gz
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>
Diffstat (limited to 'devel-docs')
-rw-r--r--devel-docs/adding-a-property.md184
1 files changed, 184 insertions, 0 deletions
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.
+
+