diff options
author | Federico Mena Quintero <federico@gnome.org> | 2021-10-15 13:48:53 -0500 |
---|---|---|
committer | Marge Bot <marge-bot@gnome.org> | 2021-10-15 23:56:45 +0000 |
commit | 92671aebbe725d2a8cb8b9093491636090e676c6 (patch) | |
tree | 62bf4ce90c3261f5983be553297d06f502b27526 /devel-docs | |
parent | 3107a74a09028ad91944b967186de3b8e49448c5 (diff) | |
download | librsvg-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.md | 184 |
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. + + |