summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMarge Bot <marge-bot@gnome.org>2022-08-26 01:20:53 +0000
committerMarge Bot <marge-bot@gnome.org>2022-08-26 01:20:53 +0000
commitd823f14c26881d38803bb04829d1c008b7b74f98 (patch)
treee1d6d9211b40d7dd6007f98df13b0d9bc33faf94
parent5658411389a75da7141bdfba57fc965389e7bbec (diff)
parentd01fb71464c4bab0bcf0b0811005ff2eccbea7a4 (diff)
downloadlibrsvg-d823f14c26881d38803bb04829d1c008b7b74f98.tar.gz
Merge branch 'devel-docs' into 'main'
Move more content to the development guide Closes #886 See merge request GNOME/librsvg!735
-rw-r--r--ARCHITECTURE.md407
-rw-r--r--Cargo.lock250
-rw-r--r--Cargo.toml1
-rw-r--r--Makefile.am2
-rw-r--r--NEWS (renamed from NEWS.md)0
-rw-r--r--README.md6
-rw-r--r--RELEASING.md219
-rw-r--r--devel-docs/README.md11
-rw-r--r--devel-docs/adding-a-property.md587
-rw-r--r--devel-docs/adding_a_property.rst679
-rw-r--r--devel-docs/architecture.rst474
-rw-r--r--devel-docs/index.rst52
-rw-r--r--devel-docs/releasing.rst241
13 files changed, 1577 insertions, 1352 deletions
diff --git a/ARCHITECTURE.md b/ARCHITECTURE.md
deleted file mode 100644
index 7a9e7e3c..00000000
--- a/ARCHITECTURE.md
+++ /dev/null
@@ -1,407 +0,0 @@
-Architecture of librsvg
-=======================
-
-This document roughly describes the architecture of librsvg, and
-future plans for it. The code is continually evolving, so don't
-consider this as the ground truth, but rather like a cheap map you buy
-at a street corner.
-
-The library's internals are documented as Rust documentation comments;
-you can look at the rendered version at
-https://gnome.pages.gitlab.gnome.org/librsvg/internals/librsvg/index.html
-
-You may also want to see the section below on [interesting parts of
-the code](#some_interesting_parts_of_the_code).
-
-# A bit of history
-
-Librsvg is an old library. It started around 2001, when Eazel (the
-original makers of GNOME's file manager Nautilus) needed a library to
-render SVG images. At that time the SVG format was being
-standardized, so librsvg grew along with the SVG specification. This
-is why you will sometimes see references to deprecated SVG features in
-the source code.
-
-Librsvg started as an experiment to use libxml2's new SAX parser, so
-that SVG could be streamed in and rendered on the fly, instead of
-first creating a DOM tree. Originally it used [libart] as a rendering
-library; this was GNOME's first antialiased renderer with alpha
-compositing. Later, the renderer was replaced with [Cairo]. Librsvg
-is currently striving to support other rendering backends.
-
-(These days librsvg indeed builds a DOM tree by itself; it needs the
-tree to run the CSS cascade and do selector matching.)
-
-Librsvg started as a C library with an ad-hoc API. At some point it
-got turned into a [GObject] library, so that the main `RsvgHandle`
-object defines most of the entry points into the library. Through
-[GObject Introspection], this allows librsvg to be used from other
-programming languages.
-
-In 2016, librsvg started getting ported to Rust. As of early 2021,
-the whole library is implemented in Rust, and exports an intact C
-API/ABI. It also exports a more idiomatic Rust API as well.
-
-[libart]: https://levien.com/libart/
-[Cairo]: https://www.cairographics.org/
-[GObject Introspection]: https://gi.readthedocs.io/en/latest/
-[gi]: https://people.gnome.org/~federico/blog/magic-of-gobject-introspection.html
-
-# The C and Rust APIs
-
-Librsvg exports two public APIs, one for C and one for Rust.
-
-The C API has hard requirements for API/ABI stability, because it is
-used all over the GNOME project and API/ABI breaks would be highly
-disruptive. Also, the C API is what allows librsvg to be called from
-other programming languages, through [GObject Introspection].
-
-The Rust API is a bit more lax in its API stability, but we try to
-stick to [semantic versioning][semver] as is common in Rust.
-
-The public Rust API is implemented in `src/api.rs`. This has all the
-primitives needed to load and render SVG documents or individual
-elements, and to configure loading/rendering options.
-
-The public C API is implemented in `src/c_api/`, and it is implemented
-in terms of the public Rust API. Note that as of 2021/Feb the
-corresponding C header files are hand-written in `include/librsvg/`;
-maybe in the future they will be generated automatically with
-[cbindgen].
-
-We consider it good practice to provide simple and clean primitives in
-the Rust API, and have `c_api` deal with all the idiosyncrasies and
-historical considerations for the C API.
-
-```
-+----------------+
-| Public C API |
-| src/c_api |
-+----------------+
- |
- calls
- |
- v
-+-------------------+
-| Public Rust API |
-| src/api.rs |
-+-------------------+
- |
- calls
- |
- v
-+-------------------+
-| library internals |
-| src/*.rs |
-+-------------------+
-```
-
-[semver]: https://semver.org/
-[cbindgen]: https://github.com/eqrion/cbindgen/blob/master/docs.md
-
-# The test suite
-
-The test suite is documented in `tests/README.md`.
-
-# Code flow
-
-The caller of librsvg loads a document into a handle, and later may
-ask to render the document or one of its elements, or measure their
-geometries.
-
-## Loading an SVG document
-
-The Rust API starts by constructing an `SvgHandle` from a `Loader`;
-both of those are public types. Internally the `SvgHandle` is just a
-wrapper around a `Handle`, which is a private type. `Handle`
-represents an SVG document loaded in memory; it acts as a wrapper
-around a `Document`, and provides the basic primitive operations like
-"render the whole document" or "compute the geometry of an element"
-that are needed to implement the public APIs.
-
-A `Document` gets created by loading XML from a stream, into a tree of
-`Node` structures. This is similar to a web browser's DOM tree.
-
-Each XML element causes a new `Node` to get created with an `Element`
-in it. The `Element` enum can represent all the SVG element types;
-for example, a `<path>` element from XML gets turned into a
-`Node::Element(Element::Path)`.
-
-When an `Element` is created from its corresponding XML, its
-`Attributes` get parsed. On one hand, attributes that are specific to
-a particular element type, like the `d` in `<path d="...">` get parsed
-by the `set_attributes` method of each particular element type (in
-that case, `Path::set_attributes`).
-
-On the other hand, attributes that refer to styles, and which may
-appear for any kind of element, get all parsed into a
-`SpecifiedValues` struct. This is a memory-efficient representation
-of the CSS style properties that an element has.
-
-There is a lot of parsing going on; see below for the details of
-[parsing](#parsing).
-
-When the XML document is fully parsed, a `Document` contains a tree of
-`Node` structs and their inner `Element` structs. The tree has also
-been validated to ensure that the root is an `<svg>` element.
-
-After that, the CSS cascade step gets run.
-
-## The CSS cascade
-
-Each `Element` has a `SpecifiedValues`, which has the CSS style
-properties that the XML specified for that element. However,
-`SpecifiedValues` is sparse, as not all the possible style properties
-may have been filled in. Cascading means following the CSS/SVG rules
-for each property type to inherit missing properties from parent
-elements. For example, in this document fragment:
-
-```
-<g stroke-width="2" stroke="black">
- <path d="M0,0 L10,0" fill="blue"/>
- <path d="M20,0 L30,0" fill="green"/>
-</g>
-```
-
-Each `<path>` element has a different fill color, but they both
-*inherit* the `stroke-width` and `stroke` values from their parent
-group. This is because both the `stroke-width` and `stroke`
-properties are defined in the CSS/SVG specifications to inherit
-automatically. Some other properties, like `opacity`, do not inherit
-and are thus not copied to child elements.
-
-In librsvg, the individual types for CSS properties are defined with
-the `make_property` macro.
-
-The cascading step takes each element's `SpecifiedValues` and composes
-it by CSS inheritance onto a `ComputedValues`. The latter is a
-memory-efficient representation of all the possible CSS properties
-that an element can have.
-
-When cascading is done, each `Element` has a fully resolved
-`ComputedValues` struct, which is what gets used during rendering to
-look up things like the element's stroke width or fill color.
-
-## Parsing
-
-### XML into a tree of Nodes / Elements
-
-Librsvg uses an XML parser (libxml2 at the time of this writing) to do
-the first-stage parsing of the SVG document. `XmlState` contains the
-XML parsing state, which is a stack of contexts depending on the XML
-nesting structure. `XmlState` has public methods, called from the XML
-parser as it goes. The most important one is `start_element`; this
-is responsible for creating new `Node` structures in the tree, within
-the `DocumentBuilder` being built.
-
-Nodes are either SVG elements (the `Element` enum), or text data
-inside elements (the `Chars` struct); this last one will not concern
-us here, and we will only talk about `Element`.
-
-Each supported kind of `Element` parses its attributes in a
-`set_attributes()` method. Each attribute is just a key/value pair;
-for example, the `<rect width="5px">` element has a `width` attribute
-whose value is `5px`.
-
-While parsing its attributes, an element may encounter an invalid
-value, for example, a negative width where only nonnegative ones are
-allowed. In this case, the element's `set_attributes` method may
-return a `Result::Err`. The caller will then do `set_error` to mark
-that element as being in an error state. If an element is in error,
-its children will get parsed as usual, but the element and its
-children will be ignored during the rendering stage.
-
-The SVG spec says that SVG rendering should stop on the first element
-that is "in error". However, most implementations simply seem to
-ignore erroneous elements instead of completely stopping rendering,
-and we do the same in librsvg.
-
-### CSS and styles
-
-Librsvg uses Servo's `cssparser` crate as a CSS tokenizer, and
-`selectors` as a high-level parser for CSS style data.
-
-With the `cssparser` crate, the caller is responsible for providing an
-implementation of the `DeclarationParser` trait. Its `parse_value`
-method takes the name of a CSS property name like `fill`, plus a value
-like `rgb(255, 0, 0)`, and it must return a value that represents a
-parsed declaration. Librsvg uses the `Declaration` struct for this.
-
-The core of parsing CSS is the `parse_value` function, which returns a `ParsedProperty`:
-
-```rust
-pub enum ParsedProperty {
- BaselineShift(SpecifiedValue<BaselineShift>),
- ClipPath(SpecifiedValue<ClipPath>),
- Color(SpecifiedValue<Color>),
- // etc.
-}
-```
-
-What is `SpecifiedValue`? It is the parsed value for a CSS property directly as it comes out of the SVG document:
-
-```rust
-pub enum SpecifiedValue<T>
-where
- T: Property<ComputedValues> + Clone + Default,
-{
- Unspecified,
- Inherit,
- Specified(T),
-}
-```
-
-A property declaration can look like `opacity: inherit;` - this would
-create a `ParsedProperty::Opacity(SpecifiedValue::Inherit)`.
-
-Or it can look like `opacity: 0.5;` - this would create a
-`ParsedProperty::Opacity(SpecifiedValue::Specified(Opacity(UnitInterval(0.5))))`.
-Let's break this down:
-
-* `ParsedProperty::Opacity` - which property did we parse?
-
-* `SpecifiedValue::Specified` - it actually was specified by the
- document with a value; the other interesting alternative is
- `Inherit`, which corresponds to the value `inherit` that all CSS
- property declarations can have.
-
-* `Opacity(UnitInterval(0.5))` - This is the type `Opacity` property,
- which is a newtype around an internal `UnitInterval` type, which in
- turn guarantees that we have a float in the range `[0.0, 1.0]`.
-
-There is a Rust type for every CSS property that librsvg supports;
-many of these types are newtypes around primitive types like `f64`.
-
-Eventually an entire CSS stylesheet, like the contents of a `<style>`
-element, gets parsed into a `Stylesheet` struct. A stylesheet has a
-list of rules, where each rule is the CSS selectors defined for it,
-and the style declarations that should be applied for the `Node`s that
-match the selectors. For example, in a little stylesheet like this:
-
-```xml
-<style type="text/css">
- rect, #some_id {
- fill: blue;
- stroke-width: 5px;
- }
-</style>
-```
-
-This stylesheet has a single rule. The rule has a selector list with
-two selectors (`rect` and `#some_id`) and two style declarations
-(`fill: blue` and `stroke-width: 5px`).
-
-After parsing is done, there is a **cascading stage** where librsvg
-walks the tree of nodes, and for each node it finds the CSS rules that
-should be applied to it.
-
-## Rendering
-
-The rendering process starts at the `draw_tree()` function. This sets
-up a `DrawingCtx`, which carries around all the mutable state during
-rendering.
-
-Rendering is a recursive process, which goes back and forth between
-the utility functions in `DrawingCtx` and the `draw` method in
-elements.
-
-The main job of `DrawingCtx` is to deal with the SVG drawing model.
-Each element renders itself independently, and its result gets
-modified before getting composited onto the final image:
-
-1. Render an element to a temporary surface (example: stroke and fill a path).
-2. Apply filter effects (blur, color mapping, etc.).
-3. Apply clipping paths.
-4. Apply masks.
-5. Composite the result onto the final image.
-
-The temporary result from the last step also gets put in a stack; this
-is because filter effects sometimes need to look at the
-currently-drawn background to apply further filtering to it.
-
-You'll see that most of the rendering-related functions return a
-`Result<BoundingBox, RenderingError>`. Some SVG features require
-knowing the bounding box of the object that is being rendered; for
-historical reasons this bounding box is computed as part of the
-rendering process in librsvg. When computing a subtree's bounding
-box, the bounding boxes from the leaves get aggregated up to the root
-of the subtree. Each node in the tree has its own coordinate system;
-`BoundingBox` is able to transform coordinate systems to get a
-bounding box that is meaningful with respect to the root's transform.
-
-# Comparing floating-point numbers
-
-Librsvg sometimes needs to compute things like "are these points
-equal?" or "did this computed result equal this test reference
-number?".
-
-We use `f64` numbers in Rust for all computations on real numbers.
-Floating-point numbers cannot be compared with `==` effectively, since
-it doesn't work when the numbers are slightly different due to
-numerical inaccuracies.
-
-Similarly, we don't `assert_eq!(a, b)` for floating-point numbers.
-
-Most of the time we are dealing with coordinates which will get passed
-to Cairo. In turn, Cairo converts them from doubles to a fixed-point
-representation (as of March 2018, Cairo uses 24.8 fixnums with 24 bits of
-integral part and 8 bits of fractional part).
-
-So, we can consider two numbers to be "equal" if they would be represented
-as the same fixed-point value by Cairo. Librsvg implements this in
-the [`ApproxEqCairo` trait][ApproxEqCairo] trait. You can use it like
-this:
-
-```rust
-use float_eq_cairo::ApproxEqCairo; // bring the trait into scope
-
-let a: f64 = ...;
-let b: f64 = ...;
-
-if a.approx_eq_cairo(&b) { // not a == b
- ... // equal!
-}
-
-assert!(1.0_f64.approx_eq_cairo(&1.001953125_f64)); // 1 + 1/512 - cairo rounds to 1
-```
-
-[ApproxEqCairo]: src/float_eq_cairo.rs
-
-# Some interesting parts of the code
-
-* Are you adding support for a CSS property? Look in the
- [`property_defs`] and [`properties`] modules. `property_defs`
- defines most of the CSS properties that librsvg supports, and
- `properties` actually puts all those properties in the
- `SpecifiedValues` and `ComputedValues` structs.
-
-* The [`Handle`] struct provides the primitives to implement the
- public APIs, such as loading an SVG file and rendering it.
-
-* The [`DrawingCtx`] struct is active while an SVG handle is being
- drawn. It has all the mutable state related to the drawing process,
- such as the stack of temporary rendered surfaces, and the viewport
- stack.
-
-* The [`Document`] struct represents a loaded SVG document. It holds
- the tree of [`Node`] structs, some of which contain [`Element`]s. A
- `Document` also contains a mapping of `id` attributes to the
- corresponding element nodes.
-
-* The [`xml`] module receives events from an XML parser, and builds a
- [`Document`] tree.
-
-* The [`css`] module has the high-level machinery for parsing CSS and
- representing parsed stylesheets. The low-level parsers for
- individual properties are in [`property_defs`] and [`font_props`].
-
-[`css`]: src/css.rs
-[`Document`]: src/document.rs
-[`DrawingCtx`]: src/drawing_ctx.rs
-[`Element`]: src/element.rs
-[`font_props`]: src/font_props.rs
-[`Handle`]: src/handle.rs
-[`Node`]: src/node.rs
-[`properties`]: src/properties.rs
-[`property_defs`]: src/property_defs.rs
-[`xml`]: src/xml/mod.rs
diff --git a/Cargo.lock b/Cargo.lock
index 5c066733..0d9a5a5e 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -24,6 +24,15 @@ dependencies = [
]
[[package]]
+name = "android_system_properties"
+version = "0.1.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d7ed72e1635e121ca3e79420540282af22da58be50de153d36f81ddc6b83aa9e"
+dependencies = [
+ "libc",
+]
+
+[[package]]
name = "ansi_term"
version = "0.12.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -34,9 +43,9 @@ dependencies = [
[[package]]
name = "anyhow"
-version = "1.0.59"
+version = "1.0.62"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c91f1f46651137be86f3a2b9a8359f9ab421d04d941c62b5982e1ca21113adf9"
+checksum = "1485d4d2cc45e7b201ee3767015c96faa5904387c9d87c6efdd0fb511f12d305"
[[package]]
name = "approx"
@@ -125,15 +134,15 @@ dependencies = [
[[package]]
name = "bumpalo"
-version = "3.10.0"
+version = "3.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "37ccbd214614c6783386c1af30caf03192f17891059cecc394b4fb119e363de3"
+checksum = "c1ad822118d20d2c234f427000d5acc36eabe1e29a348c89b63dd60b13f28e5d"
[[package]]
name = "bytemuck"
-version = "1.11.0"
+version = "1.12.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a5377c8865e74a160d21f29c2d40669f53286db6eab59b88540cbb12ffc8b835"
+checksum = "2f5715e491b5a1598fc2bef5a606847b5dc1d48ea625bd3c02c00de8285591da"
[[package]]
name = "byteorder"
@@ -188,14 +197,16 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]]
name = "chrono"
-version = "0.4.19"
+version = "0.4.22"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "670ad68c9088c2a963aaa298cb369688cf3f9465ce5e2d4ca10e6e0098a1ce73"
+checksum = "bfd4d1b31faaa3a89d7934dbded3111da0d2ef28e3ebccdb4f0179f5929d1ef1"
dependencies = [
- "libc",
+ "iana-time-zone",
+ "js-sys",
"num-integer",
"num-traits",
"time 0.1.44",
+ "wasm-bindgen",
"winapi",
]
@@ -233,6 +244,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6245d59a3e82a7fc217c5828a6692dbc6dfb63a0c8c90495621f7b9d79704a0e"
[[package]]
+name = "core-foundation-sys"
+version = "0.8.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5827cebf4670468b8772dd191856768aedcb1b0278a04f989f7766351917b9dc"
+
+[[package]]
name = "crc32fast"
version = "1.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -333,10 +350,10 @@ dependencies = [
"itoa 0.4.8",
"matches",
"phf",
- "proc-macro2 1.0.42",
- "quote 1.0.20",
+ "proc-macro2 1.0.43",
+ "quote 1.0.21",
"smallvec",
- "syn 1.0.98",
+ "syn 1.0.99",
]
[[package]]
@@ -345,8 +362,8 @@ version = "0.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dfae75de57f2b2e85e8768c3ea840fd159c8f33e2b6522c7835b7abac81be16e"
dependencies = [
- "quote 1.0.20",
- "syn 1.0.98",
+ "quote 1.0.21",
+ "syn 1.0.99",
]
[[package]]
@@ -396,10 +413,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4fb810d30a7c1953f91334de7244731fc3f3c10d7fe163338a35b9f640960321"
dependencies = [
"convert_case",
- "proc-macro2 1.0.42",
- "quote 1.0.20",
+ "proc-macro2 1.0.43",
+ "quote 1.0.21",
"rustc_version 0.4.0",
- "syn 1.0.98",
+ "syn 1.0.99",
]
[[package]]
@@ -437,9 +454,9 @@ dependencies = [
[[package]]
name = "either"
-version = "1.7.0"
+version = "1.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3f107b87b6afc2a64fd13cac55fe06d6c8859f12d4b14cbcdd2c67d0976781be"
+checksum = "90e5c1c8368803113bf0c9584fc495a58b86dc8a29edbf8fe877d21d9507e797"
[[package]]
name = "encoding"
@@ -561,24 +578,24 @@ dependencies = [
[[package]]
name = "futures-channel"
-version = "0.3.21"
+version = "0.3.23"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c3083ce4b914124575708913bca19bfe887522d6e2e6d0952943f5eac4a74010"
+checksum = "2bfc52cbddcfd745bf1740338492bb0bd83d76c67b445f91c5fb29fae29ecaa1"
dependencies = [
"futures-core",
]
[[package]]
name = "futures-core"
-version = "0.3.21"
+version = "0.3.23"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0c09fd04b7e4073ac7156a9539b57a484a8ea920f79c7c675d05d289ab6110d3"
+checksum = "d2acedae88d38235936c3922476b10fced7b2b68136f5e3c03c2d5be348a1115"
[[package]]
name = "futures-executor"
-version = "0.3.21"
+version = "0.3.23"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9420b90cfa29e327d0429f19be13e7ddb68fa1cccb09d65e5706b8c7a749b8a6"
+checksum = "1d11aa21b5b587a64682c0094c2bdd4df0076c5324961a40cc3abd7f37930528"
dependencies = [
"futures-core",
"futures-task",
@@ -587,21 +604,21 @@ dependencies = [
[[package]]
name = "futures-io"
-version = "0.3.21"
+version = "0.3.23"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "fc4045962a5a5e935ee2fdedaa4e08284547402885ab326734432bed5d12966b"
+checksum = "93a66fc6d035a26a3ae255a6d2bca35eda63ae4c5512bef54449113f7a1228e5"
[[package]]
name = "futures-task"
-version = "0.3.21"
+version = "0.3.23"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "57c66a976bf5909d801bbef33416c41372779507e7a6b3a5e25e4749c58f776a"
+checksum = "842fc63b931f4056a24d59de13fb1272134ce261816e063e634ad0c15cdc5306"
[[package]]
name = "futures-util"
-version = "0.3.21"
+version = "0.3.23"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d8b7abd5d659d9b90c8cba917f6ec750a74e2dc23902ef9cd4cc8c8b22e6036a"
+checksum = "f0828a5471e340229c11c77ca80017937ce3c58cb788a17e5f1c2d5c485a9577"
dependencies = [
"futures-core",
"futures-task",
@@ -727,9 +744,9 @@ dependencies = [
"heck",
"proc-macro-crate",
"proc-macro-error",
- "proc-macro2 1.0.42",
- "quote 1.0.20",
- "syn 1.0.98",
+ "proc-macro2 1.0.43",
+ "quote 1.0.21",
+ "syn 1.0.99",
]
[[package]]
@@ -781,6 +798,19 @@ dependencies = [
]
[[package]]
+name = "iana-time-zone"
+version = "0.1.46"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ad2bfd338099682614d3ee3fe0cd72e0b6a41ca6a87f6a74a3bd593c91650501"
+dependencies = [
+ "android_system_properties",
+ "core-foundation-sys",
+ "js-sys",
+ "wasm-bindgen",
+ "winapi",
+]
+
+[[package]]
name = "idna"
version = "0.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -817,9 +847,9 @@ checksum = "b71991ff56294aa922b450139ee08b3bfc70982c6b2c7562771375cf73542dd4"
[[package]]
name = "itoa"
-version = "1.0.2"
+version = "1.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "112c678d4050afce233f4f2852bb2eb519230b3cf12f33585275537d7e41578d"
+checksum = "6c8af84674fe1f223a982c933a0ee1086ac4d4052aa0fb8060c12c6ad838e754"
[[package]]
name = "js-sys"
@@ -844,9 +874,9 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
[[package]]
name = "libc"
-version = "0.2.126"
+version = "0.2.132"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "349d5a591cd28b49e1d1037471617a32ddcda5731b99419008085f72d5a53836"
+checksum = "8371e4e5341c3a96db127eb2465ac681ced4c433e01dd0e938adbef26ba93ba5"
[[package]]
name = "librsvg"
@@ -1051,9 +1081,9 @@ version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "01fcc0b8149b4632adc89ac3b7b31a12fb6099a0317a4eb2ebff574ef7de7218"
dependencies = [
- "proc-macro2 1.0.42",
- "quote 1.0.20",
- "syn 1.0.98",
+ "proc-macro2 1.0.43",
+ "quote 1.0.21",
+ "syn 1.0.99",
]
[[package]]
@@ -1154,9 +1184,9 @@ dependencies = [
[[package]]
name = "once_cell"
-version = "1.13.0"
+version = "1.13.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "18a6dbe30758c9f83eb00cbea4ac95966305f5a7772f3f42ebfc7fc7eddbd8e1"
+checksum = "074864da206b4973b84eb91683020dbefd6a8c3f0f38e054d93954e891935e4e"
[[package]]
name = "oorandom"
@@ -1241,9 +1271,9 @@ dependencies = [
[[package]]
name = "paste"
-version = "1.0.7"
+version = "1.0.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0c520e05135d6e763148b6426a837e239041653ba7becd2e538c076c738025fc"
+checksum = "9423e2b32f7a043629287a536f21951e8c6a82482d0acb1eeebfc90bc2225b22"
[[package]]
name = "percent-encoding"
@@ -1301,9 +1331,9 @@ dependencies = [
"phf_generator 0.8.0",
"phf_shared 0.8.0",
"proc-macro-hack",
- "proc-macro2 1.0.42",
- "quote 1.0.20",
- "syn 1.0.98",
+ "proc-macro2 1.0.43",
+ "quote 1.0.21",
+ "syn 1.0.99",
]
[[package]]
@@ -1344,9 +1374,9 @@ checksum = "1df8c4ec4b0627e53bdf214615ad287367e482558cf84b109250b37464dc03ae"
[[package]]
name = "plotters"
-version = "0.3.2"
+version = "0.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9428003b84df1496fb9d6eeee9c5f8145cb41ca375eb0dad204328888832811f"
+checksum = "716b4eeb6c4a1d3ecc956f75b43ec2e8e8ba80026413e70a3f41fd3313d3492b"
dependencies = [
"num-traits",
"plotters-backend",
@@ -1363,9 +1393,9 @@ checksum = "193228616381fecdc1224c62e96946dfbc73ff4384fba576e052ff8c1bea8142"
[[package]]
name = "plotters-svg"
-version = "0.3.2"
+version = "0.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e0918736323d1baff32ee0eade54984f6f201ad7e97d5cfb5d6ab4a358529615"
+checksum = "f9a81d2759aae1dae668f783c308bc5c8ebd191ff4184aaa1b37f65a6ae5a56f"
dependencies = [
"plotters-backend",
]
@@ -1432,9 +1462,9 @@ dependencies = [
[[package]]
name = "proc-macro-crate"
-version = "1.2.0"
+version = "1.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "26d50bfb8c23f23915855a00d98b5a35ef2e0b871bb52937bacadb798fbb66c8"
+checksum = "eda0fc3b0fb7c975631757e14d9049da17374063edb6ebbcbc54d880d4fe94e9"
dependencies = [
"once_cell",
"thiserror",
@@ -1448,9 +1478,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c"
dependencies = [
"proc-macro-error-attr",
- "proc-macro2 1.0.42",
- "quote 1.0.20",
- "syn 1.0.98",
+ "proc-macro2 1.0.43",
+ "quote 1.0.21",
+ "syn 1.0.99",
"version_check",
]
@@ -1460,8 +1490,8 @@ version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869"
dependencies = [
- "proc-macro2 1.0.42",
- "quote 1.0.20",
+ "proc-macro2 1.0.43",
+ "quote 1.0.21",
"version_check",
]
@@ -1482,9 +1512,9 @@ dependencies = [
[[package]]
name = "proc-macro2"
-version = "1.0.42"
+version = "1.0.43"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c278e965f1d8cf32d6e0e96de3d3e79712178ae67986d9cf9151f51e95aac89b"
+checksum = "0a2ca2c61bc9f3d74d2886294ab7b9853abd9c1ad903a3ac7815c58989bb7bab"
dependencies = [
"unicode-ident",
]
@@ -1532,11 +1562,11 @@ dependencies = [
[[package]]
name = "quote"
-version = "1.0.20"
+version = "1.0.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3bcdf212e9776fbcb2d23ab029360416bb1706b1aea2d1a5ba002727cbcab804"
+checksum = "bbe448f377a7d6961e30f5955f9b8d106c3f5e449d493ee1b125c1d43c2b5179"
dependencies = [
- "proc-macro2 1.0.42",
+ "proc-macro2 1.0.43",
]
[[package]]
@@ -1730,7 +1760,7 @@ version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366"
dependencies = [
- "semver 1.0.12",
+ "semver 1.0.13",
]
[[package]]
@@ -1747,9 +1777,9 @@ dependencies = [
[[package]]
name = "ryu"
-version = "1.0.10"
+version = "1.0.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f3f6f92acf49d1b98f7a81226834412ada05458b7364277387724a237f062695"
+checksum = "4501abdff3ae82a1c1b477a17252eb69cee9e66eb915c1abaa4f44d873df9f09"
[[package]]
name = "safe_arch"
@@ -1804,9 +1834,9 @@ dependencies = [
[[package]]
name = "semver"
-version = "1.0.12"
+version = "1.0.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a2333e6df6d6598f2b1974829f853c2b4c5f4a6e503c10af918081aa6f8564e1"
+checksum = "93f6841e709003d68bb2deee8c343572bf446003ec20a583e76f7b15cebf3711"
[[package]]
name = "semver-parser"
@@ -1816,9 +1846,9 @@ checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3"
[[package]]
name = "serde"
-version = "1.0.141"
+version = "1.0.144"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7af873f2c95b99fcb0bd0fe622a43e29514658873c8ceba88c4cb88833a22500"
+checksum = "0f747710de3dcd43b88c9168773254e809d8ddbdf9653b84e2554ab219f17860"
dependencies = [
"serde_derive",
]
@@ -1835,22 +1865,22 @@ dependencies = [
[[package]]
name = "serde_derive"
-version = "1.0.141"
+version = "1.0.144"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "75743a150d003dd863b51dc809bcad0d73f2102c53632f1e954e738192a3413f"
+checksum = "94ed3a816fb1d101812f83e789f888322c34e291f894f19590dc310963e87a00"
dependencies = [
- "proc-macro2 1.0.42",
- "quote 1.0.20",
- "syn 1.0.98",
+ "proc-macro2 1.0.43",
+ "quote 1.0.21",
+ "syn 1.0.99",
]
[[package]]
name = "serde_json"
-version = "1.0.82"
+version = "1.0.85"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "82c2c1fdcd807d1098552c5b9a36e425e42e9fbd7c6a37a8425f390f781f7fa7"
+checksum = "e55a28e3aaef9d5ce0506d0a14dbba8054ddc7e499ef522dd8b26859ec9d4a44"
dependencies = [
- "itoa 1.0.2",
+ "itoa 1.0.3",
"ryu",
"serde",
]
@@ -1949,11 +1979,11 @@ version = "0.5.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c87a60a40fccc84bef0652345bbbbbe20a605bf5d0ce81719fc476f5c03b50ef"
dependencies = [
- "proc-macro2 1.0.42",
- "quote 1.0.20",
+ "proc-macro2 1.0.43",
+ "quote 1.0.21",
"serde",
"serde_derive",
- "syn 1.0.98",
+ "syn 1.0.99",
]
[[package]]
@@ -1963,13 +1993,13 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "58fa5ff6ad0d98d1ffa8cb115892b6e69d67799f6763e162a1c9db421dc22e11"
dependencies = [
"base-x",
- "proc-macro2 1.0.42",
- "quote 1.0.20",
+ "proc-macro2 1.0.43",
+ "quote 1.0.21",
"serde",
"serde_derive",
"serde_json",
"sha1",
- "syn 1.0.98",
+ "syn 1.0.99",
]
[[package]]
@@ -2000,8 +2030,8 @@ checksum = "6bb30289b722be4ff74a408c3cc27edeaad656e06cb1fe8fa9231fa59c728988"
dependencies = [
"phf_generator 0.10.0",
"phf_shared 0.10.0",
- "proc-macro2 1.0.42",
- "quote 1.0.20",
+ "proc-macro2 1.0.43",
+ "quote 1.0.21",
]
[[package]]
@@ -2023,12 +2053,12 @@ dependencies = [
[[package]]
name = "syn"
-version = "1.0.98"
+version = "1.0.99"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c50aef8a904de4c23c788f104b7dddc7d6f79c647c7c8ce4cc8f73eb0ca773dd"
+checksum = "58dbef6ec655055e20b86b15a8cc6d439cca19b667537ac6a1369572d151ab13"
dependencies = [
- "proc-macro2 1.0.42",
- "quote 1.0.20",
+ "proc-macro2 1.0.43",
+ "quote 1.0.21",
"unicode-ident",
]
@@ -2099,22 +2129,22 @@ dependencies = [
[[package]]
name = "thiserror"
-version = "1.0.31"
+version = "1.0.32"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "bd829fe32373d27f76265620b5309d0340cb8550f523c1dda251d6298069069a"
+checksum = "f5f6586b7f764adc0231f4c79be7b920e766bb2f3e51b3661cdb263828f19994"
dependencies = [
"thiserror-impl",
]
[[package]]
name = "thiserror-impl"
-version = "1.0.31"
+version = "1.0.32"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0396bc89e626244658bef819e22d0cc459e795a5ebe878e6ec336d1674a8d79a"
+checksum = "12bafc5b54507e0149cdf1b145a5d80ab80a90bcd9275df43d4fff68460f6c21"
dependencies = [
- "proc-macro2 1.0.42",
- "quote 1.0.20",
- "syn 1.0.98",
+ "proc-macro2 1.0.43",
+ "quote 1.0.21",
+ "syn 1.0.99",
]
[[package]]
@@ -2160,10 +2190,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fd3c141a1b43194f3f56a1411225df8646c55781d5f26db825b3d98507eb482f"
dependencies = [
"proc-macro-hack",
- "proc-macro2 1.0.42",
- "quote 1.0.20",
+ "proc-macro2 1.0.43",
+ "quote 1.0.21",
"standback",
- "syn 1.0.98",
+ "syn 1.0.99",
]
[[package]]
@@ -2214,9 +2244,9 @@ checksum = "099b7128301d285f79ddd55b9a83d5e6b9e97c92e0ea0daebee7263e932de992"
[[package]]
name = "unicode-ident"
-version = "1.0.2"
+version = "1.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "15c61ba63f9235225a22310255a29b806b907c9b8c964bcbd0a2c70f3f2deea7"
+checksum = "c4f5b37a154999a8f3f98cc23a628d850e154479cd94decf3414696e12e31aaf"
[[package]]
name = "unicode-normalization"
@@ -2332,9 +2362,9 @@ dependencies = [
"bumpalo",
"log",
"once_cell",
- "proc-macro2 1.0.42",
- "quote 1.0.20",
- "syn 1.0.98",
+ "proc-macro2 1.0.43",
+ "quote 1.0.21",
+ "syn 1.0.99",
"wasm-bindgen-shared",
]
@@ -2344,7 +2374,7 @@ version = "0.2.82"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b260f13d3012071dfb1512849c033b1925038373aea48ced3012c09df952c602"
dependencies = [
- "quote 1.0.20",
+ "quote 1.0.21",
"wasm-bindgen-macro-support",
]
@@ -2354,9 +2384,9 @@ version = "0.2.82"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5be8e654bdd9b79216c2929ab90721aa82faf65c48cdf08bdc4e7f51357b80da"
dependencies = [
- "proc-macro2 1.0.42",
- "quote 1.0.20",
- "syn 1.0.98",
+ "proc-macro2 1.0.43",
+ "quote 1.0.21",
+ "syn 1.0.99",
"wasm-bindgen-backend",
"wasm-bindgen-shared",
]
diff --git a/Cargo.toml b/Cargo.toml
index 2caf1402..e29b8ea4 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -9,6 +9,7 @@ homepage = "https://wiki.gnome.org/Projects/LibRsvg"
repository = "https://gitlab.gnome.org/GNOME/librsvg/"
build = "build.rs"
edition = "2021"
+rust-version = "1.58"
[package.metadata.system-deps]
cairo-pdf = { version = "1.16", optional = true }
diff --git a/Makefile.am b/Makefile.am
index b962614c..3c6388c9 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -254,7 +254,7 @@ EXTRA_DIST = \
$(RSVG_CONVERT_SRC) \
AUTHORS \
COPYING.LIB \
- NEWS.md \
+ NEWS \
SECURITY.md \
Rsvg-2.0-custom.vala \
Rsvg-2.0.metadata \
diff --git a/NEWS.md b/NEWS
index 4119561f..4119561f 100644
--- a/NEWS.md
+++ b/NEWS
diff --git a/README.md b/README.md
index 03dcb13e..7aa8aea2 100644
--- a/README.md
+++ b/README.md
@@ -197,7 +197,7 @@ ways:
[contributing]: CONTRIBUTING.md
[reporting-bugs]: CONTRIBUTING.md#reporting-bugs
[d-d-l]: https://mail.gnome.org/mailman/listinfo/desktop-devel-list
-[federico]: https://people.gnome.org/~federico/
+[federico]: https://viruta.org/
[platform]: https://developer.gnome.org/
-[guadec-presentation-1]: https://people.gnome.org/~federico/blog/docs/fmq-porting-c-to-rust.pdf
-[guadec-presentation-2]: https://people.gnome.org/~federico/blog/docs/fmq-refactoring-c-to-rust.pdf
+[guadec-presentation-1]: https://viruta.org/docs/fmq-porting-c-to-rust.pdf
+[guadec-presentation-2]: https://viruta.org/docs/fmq-refactoring-c-to-rust.pdf
diff --git a/RELEASING.md b/RELEASING.md
deleted file mode 100644
index 4eab87ff..00000000
--- a/RELEASING.md
+++ /dev/null
@@ -1,219 +0,0 @@
-# Release process checklist for librsvg
-
-Feel free to print this document or copy it to a text editor to check
-off items while making a release.
-
-- [ ] Refresh your memory with https://wiki.gnome.org/MaintainersCorner/Releasing
-- [ ] Increase the package version number in `configure.ac` (it may
- already be increased but not released; double-check it).
-- [ ] Copy version number to `Cargo.toml`.
-- [ ] Copy version number to `doc/librsvg.toml`.
-- [ ] `cargo update` - needed because you tweaked `Cargo.toml`, and
- also to get new dependencies.
-- [ ] Tweak the library version number in `configure.ac` if the API changed; follow the steps there.
-- [ ] Update `NEWS.md`, see below for the preferred format.
-- [ ] Commit the changes above.
-- [ ] Make a tarball with `./autogen.sh --enable-vala && make distcheck DESTDIR=/tmp/foo` - fix things until it passes.
-- [ ] Create a signed tag - `git tag -s x.y.z` with the version number.
-- [ ] `git push` to the appropriate branch to gitlab.gnome.org/GNOME/librsvg
-- [ ] `git push` the signed tag to gitlab.gnome.org/GNOME/librsvg
-- [ ] `scp librsvg-x.y.z.tar.xz master.gnome.org:`
-- [ ] `ssh master.gnome.org` and then `ftpadmin install librsvg-x.y.z.tar.xz`
-- [ ] Create a [release in Gitlab](#gitlab-release).
-
-For `x.y.0` releases, at least, do the following:
-
-- [ ] [Notify the release team][release-team] on whether to use this
- `librsvg-x.y.0` for the next GNOME version via an issue on their
- `GNOME/releng` project.
-
-- [ ] `cargo-audit audit` and ensure we don't have vulnerable dependencies.
-
-## Gitlab release
-
-- [ ] Go to https://gitlab.gnome.org/GNOME/librsvg/-/releases and click the **New release** button.
-
-- [ ] Select the tag `x.y.z` you created as part of the release steps.
-
-- [ ] If there is an associated milestone, select it too.
-
-- [ ] Fill in the release title - `x.y.z - stable` or `x.y.z - development`.
-
-- [ ] Copy the release notes from NEWS.md.
-
-- [ ] Add a release asset link to
- `https://download.gnome.org/sources/librsvg/x.y/librsvg-x.y.z.tar.xz`
- and call it `librsvg-x.y.z.tar.xz - release tarball`.
-
-- [ ] Add a release asset link to
- `https://download.gnome.org/sources/librsvg/x.y/librsvg-x.y.z.sha256sum`
- and call it `librsvg-x.y.z.sha256sum - release tarball
- sha256sum`.
-
-## Version numbers
-
-`configure.ac` and `Cargo.toml` must have the same **package version**
-number - this is the number that users of the library see.
-
-`configure.ac` is where the **library version** is defined; this is
-what gets encoded in the SONAME of `librsvg.so`.
-
-Librsvg follows an even/odd numbering scheme for the **package
-version**. For example, the 2.50.x series is for stable releases, and
-2.51.x is for unstable/development ones. The [release-team] needs to
-be notified when a new series comes about, so they can adjust their
-tooling for the stable or development GNOME releases. File an issue
-in their [repository][release-team] to indicate whether the new
-`librsvg-x.y.0` is a stable or development series.
-
-## Minimum supported Rust version (MSRV)
-
-While it may seem desirable to always require the latest released
-version of the Rust toolchain, to get new language features and such,
-this is really inconvenient for distributors of librsvg which do not
-update Rust all the time. So, we make a compromise.
-
-The `configure.ac` script defines `MININUM_RUST_MAJOR` and
-`MINIMUM_RUST_MINOR` variables with librsvg's minimum supported Rust
-version (MSRV). These ensure that distros will get an early failure during a
-build, at the `configure` step, if they have a version of Rust that is
-too old — instead of getting an obscure error message from `rustc` in
-the middle of the build when it finds an unsupported language
-construct.
-
-As of March 2021, Cargo does not allow setting a minimum supported
-Rust version; you may want to keep an eye on [the MSRV RFC][msrv-rfc].
-
-Sometimes librsvg's dependencies update their MSRV and librsvg may
-need to increase it as well. Please consider the following before
-doing this:
-
-* Absolutely do not require a nightly snapshot of the compiler, or
- crates that only build on nightly.
-
-* Distributions with rolling releases usually keep their Rust
- toolchains fairly well updated, maybe not always at the latest, but
- within two or three releases earlier than the latest. If the MSRV
- you want is within about six months of the latest, things are
- probably safe.
-
-* Enterprise distributions update more slowly. It is useful to watch
- for the MSRV that Firefox requires, although sometimes Firefox
- updates Rust very slowly as well. Now that distributions are
- shipping packages other than Firefox that require Rust, they will
- probably start updating more frequently.
-
-Generally — two or three releases earlier than the latest stable Rust
-is OK for rolling distros, probably perilous for enterprise distros.
-Releases within a year of an enterprise distro's shipping date are
-probably OK.
-
-If you are not sure, ask on the [forum for GNOME
-distributors][distributors] about their plans! (That is, posts on
-`discourse.gnome.org` with the `distributor` tag.)
-
-[msrv-rfc]: https://github.com/rust-lang/rfcs/pull/2495
-[distributors]: https://discourse.gnome.org/tag/distributor
-
-## Format for release notes in NEWS.md
-
-The `NEWS.md` file contains the release notes. Please use something
-close to this format; it is not mandatory, but makes the formatting
-consistent, and is what tooling expects elsewhere - also by writing
-Markdown, you can just cut&paste it into a Gitlab release. You can
-skim bits of the `NEWS.md` file for examples on style and content.
-
-New entries go at the **top** of the file.
-
-```
-Version x.y.z
-=============
-
-Commentary on the release; put anything here that you want to
-highlight. Note changes in the build process, if any, or any other
-things that may trip up distributors.
-
-## Description of a special feature
-
-You can include headings with `##` in Markdown syntax.
-
-Blah blah blah.
-
-
-Next is a list of features added and issues fixed; use gitlab's issue
-numbers. I tend to use this order: first security bugs, then new
-features and user-visible changes, finally regular bugs. The
-rationale is that if people stop reading early, at least they will
-have seen the most important stuff first.
-
-## Changes:
-
-- #123 - title of the issue, or short summary if it warrants more
- discussion than just the title.
-
-- #456 - fix blah blah (Contributor's Name).
-
-## Special thanks for this release:
-
-- Any people that you want to highlight. Feel free to omit this
- section if the release is otherwise unremarkable.
-```
-
-## Making a tarball
-
-```
-make distcheck DESTDIR=/tmp/foo
-```
-
-The `DESTDIR` is a quirk, required because otherwise the gdk-pixbuf
-loader will try to install itself into the system's location for
-pixbuf loaders, and it won't work. The `DESTDIR` is what Linux
-distribution packaging scripts use to `make install` the compiled
-artifacts to a temporary location before building a system package.
-
-## Copying the tarball to master.gnome.org
-
-If you don't have a maintainer account there, ask federico@gnome.org
-to do it or [ask the release team][release-team] to do it by filing an
-issue on their `GNOME/releng` project.
-
-[release-team]: https://gitlab.gnome.org/GNOME/releng/-/issues
-
-## Rust dependencies
-
-Release tarballs get generated with *vendored dependencies*, that is,
-the source code for all the crates that librsvg depends on gets bundled
-into the tarball itself. It is important to keep these dependencies
-updated; you can do that regularly with the `cargo update` step listed
-in the checklist above.
-
-[`cargo-audit`][cargo-audit] is very useful to scan the list of
-dependencies for registered vulnerabilities in the [RustSec
-vulnerability database][rustsec]. Run it especially before making a
-new `x.y.0` release.
-
-Sometimes cargo-audit will report crates that are not vulnerable, but
-that are unmaintained. Keep an eye of those; you may want to file
-bugs upstream to see if the crates are really unmaintained or if they
-should be substituted for something else.
-
-[cargo-audit]: https://github.com/RustSec/cargo-audit
-[rustsec]: https://rustsec.org/
-
-## Creating a stable release branch
-
-* Create a branch named `librsvg-xx.yy`, e.g. `librsvg-2.54`
-
-* Make the `BASE_TAG` in `ci/container-builds.yml` refer to the new
- `librsvg-xx.yy` branch instead of `main`.
-
-* Push that branch to origin.
-
-* (Branches with that naming scheme are already automatically
- protected in gitlab's Settings/Repository/Protected branches.)
-
-* Edit the badge for the stable branch so it points to the new branch:
- Settings/General/Badges, find the existing badge for the stable
- branch, click on the edit button that looks like a pencil. Change
- the **Link** and **Badge image URL**; usually it is enough to just
- change the version number in both.
diff --git a/devel-docs/README.md b/devel-docs/README.md
deleted file mode 100644
index 5443fc2f..00000000
--- a/devel-docs/README.md
+++ /dev/null
@@ -1,11 +0,0 @@
-# Development documentation for librsvg
-
-Before embarking on big changes, please write a little design document
-modeled on the following ones, and submit a merge request. We can
-then discuss it before coding. This way we will have a sort of
-big-picture development history apart from commit messages.
-
-Design documents:
-
-* [`adding-a-property.md`](adding-a-property.md) - Tutorial on how to
- add support for a new CSS property. Should remain always current.
diff --git a/devel-docs/adding-a-property.md b/devel-docs/adding-a-property.md
deleted file mode 100644
index 90f953e2..00000000
--- a/devel-docs/adding-a-property.md
+++ /dev/null
@@ -1,587 +0,0 @@
-# Adding a new CSS property to librsvg
-
-This document is a little tour on how to add support for a CSS property to librsvg. We
-will implement the [`mask-type`
-property](https://www.w3.org/TR/css-masking-1/#the-mask-type) from the **CSS Masking
-Module Level 1** specification.
-
-## What is `mask-type`?
-
-[The spec says about `mask-type`](https://www.w3.org/TR/css-masking-1/#the-mask-type):
-
-> The mask-type property defines whether the content of the mask element is treated as as
-> luminance mask or alpha mask, as described in Calculating mask values.
-
-A **luminance mask** takes the RGB values of each pixel, converts them to a single luminance
-value, and uses that as a mask.
-
-An **alpha mask** just takes the alpha value of each pixel and uses it as a mask.
-
-The only mask type that SVG1.1 supported was luminance masks; there
-wasn't even a `mask-type` property back then. The SVG2 spec removed
-descriptions of masking, and offloaded them to the [CSS Masking Module
-Level 1](https://www.w3.org/TR/css-masking-1/) specification, which it
-adds the `mask-type` property and others as well.
-
-Let's start by figuring out how to read the spec.
-
-## What the specification says
-
-The specification for `mask-type` is in https://www.w3.org/TR/css-masking-1/#the-mask-type
-
-In the specs, most of the descriptions for properties start with a table that summarizes
-the property. For example, if you visit that link, you will find a table that starts with
-these items:
-
-* **Name:** `mask-type`
-* **Value:** `luminance | alpha`
-* **Initial:** `luminance`
-* **Applies to:** mask elements
-* **Inherited:** no
-* **Computed value:** as specified
-
-Let's go through each of these:
-
-**Name:** We have the name of the property (`mask-type`). Properties are case-insensitive, and
-librsvg already has machinery to handle that.
-
-**Value:** The possible values for the property can be `luminance` or `alpha`. In the spec's web page,
-even the little `|` between those two values is a hyperlink; clicking it will take you to
-the specification for CSS Values and Units, where it describes the grammar that the CSS
-specs use to describe their values. Here you just need to know that `|` means
-that exactly one of the two alternatives must occur.
-
-As you may imagine, librsvg already parses a lot of similar properties that are just
-symbolic values. For example, the `stroke-linecap` property can have values `butt | round
-| square`. We'll see how to write a parser for this kind of property with a minimal amount of code.
-
-**Initial:** Then there is the initial or default value, which is `luminance`. This means
-that if the `mask-type` property is not specified on an element, it takes `luminance` as
-its default. This is a sensible choice, since an SVG1.1 file that is processed by SVG2
-software should retain the same semantics. It also means that if there is a parse error,
-for example if you typed `ahlpha`, the property will silently revert back to the default
-`luminance` value.
-
-**Applies to:** Librsvg doesn't pay much attention to "applies to" — it just carries
-property values for all elements, and the elements that don't handle a property just
-ignore it.
-
-**Inherited:** This property is not inherited, which means that by default, its value does
-not cascade. So if you have this:
-
-```xml
-<mask style="mask-type: alpha;">
- <other>
- <elements>
- <here/>
- </elements>
- </other>
-</mask>
-```
-
-Then the `other`, `elements`, `here` will not inherit the `mask-type` value from their ancestor.
-
-**Computed value:** Finally, the computed value is "as specified", which means that
-librsvg does not need to modify it in any way when resolving the CSS cascade. Other
-properties, like `width: 1em;` may need to be resolved against the `font-size` to obtain
-the computed value.
-
-The W3C specifications can get pretty verbose and it takes some practice to read them, but
-fortunately this property is short and sweet.
-
-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.
-
-## Registering the property
-
-Okay! We defined `MaskType` and its symbolic identifiers with the `make_property!` macro,
-and the macro took care of writing a parser for it and implementing the traits that the
-property needs.
-
-Now we need to modify the code in a few places to process the property.
-
-## Register the property
-
-* First, look for `longhands:` in `properties.rs`. You will find that it is part of a long macro invocation:
-
-```rust
-make_properties! {
- // ... stuff omitted here
-
- longhands: {
- // ... stuff omitted here
-
- "marker-end" => (PresentationAttr::Yes, marker_end : MarkerEnd),
- "marker-mid" => (PresentationAttr::Yes, marker_mid : MarkerMid),
- "marker-start" => (PresentationAttr::Yes, marker_start : MarkerStart),
- "mask" => (PresentationAttr::Yes, mask : Mask),
- // "mask-type" => (PresentationAttr::Yes, unimplemented),
- "opacity" => (PresentationAttr::Yes, opacity : Opacity),
- "overflow" => (PresentationAttr::Yes, overflow : Overflow),
-
- // ... stuff omitted here
- }
-}
-```
-
-In there, there is an entry for `mask-type` commented out. Let's uncomment it and turn it into this:
-
-```rust
- "mask-type" => (PresentationAttr::Yes, mask_type : MaskType),
-```
-
-`PresentationAttr::Yes` indicates whether the property has a corresponding presentation attribute. This means that you can do `<mask style="mask-type: alpha;">` which is property, as well as `<mask mask-type="alpha">`, which is a presentation attribute.
-
-How did we find out that `mask-type` also exists as a presentation attribute? Well, [the spec](https://www.w3.org/TR/css-masking-1/#the-mask-type) says:
-
-> The mask-type property is a presentation attribute for SVG elements.
-
-But wait! If we compile, we get this:
-
-```
-error: no rules expected the token `"mask-type"`
- --> src/properties.rs:450:9
- |
-450 | "mask-type" => (PresentationAttr::Yes, mask_type : MaskType),
- | ^^^^^^^^^^^ no rules expected this token in macro call
-```
-
-When you see that error in exactly that macro invocation, it means this: librsvg uses a
-crate called `markup5ever` to have a compact representation of the names of
-properties/attributes/elements. It uses string interning so that, for example, there is a
-single definition of `rect` in the program's heap instead of there being a thousands of
-duplicated `rect` strings when you load a big document. The thing is, `markup5ever` only
-has ready-made definitions of the most common HTML/SVG/CSS names, but unfortunately
-`mask-type` is not one of them.
-
-So, we scroll down in `properties.rs` and move the `mask-type` registration there:
-
-```rust
- longhands_not_supported_by_markup5ever: {
- "line-height" => (PresentationAttr::No, line_height : LineHeight),
- "mask-type" => (PresentationAttr::Yes, mask_type : MaskType), // <- right here
- "mix-blend-mode" => (PresentationAttr::No, mix_blend_mode : MixBlendMode),
- "paint-order" => (PresentationAttr::Yes, paint_order : PaintOrder),
- }
-```
-
-That block named `longhands_not_supported_by_markup5ever` is, well, exactly what it says —
-a separate section with property names that are not built into `markup5ever`, so they must
-be dealt with specially. Just put the property there and that's it.
-
-Next, we have to calculate the computed value for the property.
-
-## Calculate the computed value
-
-In `properties.rs`, look for `compute!`. You will find many invocations of this macro:
-
-```rust
- compute!(MarkerEnd, marker_end);
- compute!(MarkerMid, marker_mid);
- compute!(MarkerStart, marker_start);
- compute!(Mask, mask);
- compute!(MixBlendMode, mix_blend_mode);
- compute!(Opacity, opacity);
- compute!(Overflow, overflow);
-```
-
-Add a call for `MaskType`:
-
-```rust
- compute!(MarkerEnd, marker_end);
- compute!(MarkerMid, marker_mid);
- compute!(MarkerStart, marker_start);
- compute!(Mask, mask);
- compute!(MaskType, mask_type); // this is new
- compute!(MixBlendMode, mix_blend_mode);
- compute!(Opacity, opacity);
- compute!(Overflow, overflow);
-```
-
-You will see that all those calls to `compute!` are inside a method
-called `SpecifiedValues::to_computed_values()`. This method is run as
-part of the CSS cascade: it takes the `SpecifiedValues` from an
-element and composes them onto the `ComputedValues` from its parent
-element. For example, if you have a document with this bit:
-
-```xml
-<g stroke="red" fill="blue"> // ComputedValues with stroke:red, fill:blue
- <rect fill="green"/> // SpecifiedValues with fill:green
-</g>
-```
-
-The `ComputedValues` that results from the `<g>` will have properties
-`stroke:red` and `fill:blue` in it. The `SpecifiedValues` from the
-`<rect>` just has `fill:green`. Composing them together for the
-`<rect>` gives us `ComputedValues` with `stroke:red` and `fill:green`.
-
-Now that the property is registered, we can actually handle it in the drawing code!
-
-## Handling the property
-
-First, a digression: let's change the name of a few methods to better reflect what the new
-structure of the code will be like.
-
-There are a few methods called `to_mask` in the code, that take an RGBA surface and turn
-it into an Alpha-only surface with the luminance of the original surface; and also the
-corresponding method to do this for a single pixel. Let's do this kind of renaming:
-
-```
-- pub fn to_mask(&self, opacity: UnitInterval) -> Result<SharedImageSurface, cairo::Error> {
-+ pub fn to_luminance_mask(&self, opacity: UnitInterval) -> Result<SharedImageSurface, cairo::Error> {
-```
-
-Librsvg only effectively supported `mask-type: luminance` since that is what was in
-SVG1.1, but now for SVG2 we want to add behavior for `mask-type: alpha` as well. So, it
-makes sense to rename `to_mask` as `to_luminance_mask`.
-
-`SharedImageSurface` is the type that librsvg uses to represent images in memory. They
-can be RGBA or Alpha-only. There is already a method called `extract_alpha` that we can
-use to create an Alpha-only mask:
-
-```rust
-// there's a type alias SharedImageSurface for this
-impl ImageSurface<Shared> {
- pub fn extract_alpha(&self, bounds: IRect) -> Result<SharedImageSurface, cairo::Error> { ... }
-}
-```
-
-Now let's look at where `drawing_ctx.rs` has this:
-
-```rust
- let mask = SharedImageSurface::wrap(mask_content_surface, SurfaceType::SRgb)? // (1)
- .to_luminance_mask()? // (2)
- .into_image_surface()?; // (3)
-```
-
-* (1) Wraps a `SharedImageSurface` around the Cairo surface that was just rendered with the mask contents.
-
-* (2) Converts it to a luminance mask. We will need to change this!
-
-* (3) Extracts the Cairo image surface from the `SharedImageSurface`, for further processing.
-
-Remember the `ComputedValues` where we had the `mask_type`? We can extract it with
-`values.mask_type()`. Now let's change the lines above to this:
-
-```rust
- let tmp = SharedImageSurface::wrap(mask_content_surface, SurfaceType::SRgb)?;
-
- let mask_result = match values.mask_type() {
- MaskType::Luminance => tmp.to_luminance_mask()?,
- MaskType::Alpha => tmp.extract_alpha(IRect::from_size(tmp.width(), tmp.height()))?,
- };
-
- let mask = mask_result.into_image_surface()?;
-```
-
-But wait! We don't have a test for this yet! Aaaaaargh, we are doing test-driven development backwards!
-
-No biggie. Let's write the tests.
-
-## Adding tests
-
-Testing graphical output is really annoying if you compare PNG files, because any time
-Cairo changes something and antialiasing changes juuuuuust a bit, the tests break. So,
-librsvg tries to do "reftests", or reference tests, by comparing the rendered results of
-two things:
-
-* The SVG you actually want to test.
-* An equivalent SVG that works only with known-good features.
-
-For `mask-type`, we need an SVG document that actually uses that property with both of its
-values, and another document that produces the same results but with simpler primitives.
-
-Librsvg already has tests for luminance masks, as they were the only available kind in
-SVG1.1. So we can be confident that they already work - we just need to test that the
-presence of `mask-type="luminance"` actually does the same thing.
-
-First, let's dissect the SVG that we want to test:
-
-```xml
-<?xml version="1.0" encoding="UTF-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="200" height="100">
- <mask id="luminance" mask-type="luminance" maskContentUnits="objectBoundingBox">
- <rect x="0.1" y="0.1" width="0.8" height="0.8" fill="white"/>
- </mask>
- <mask id="alpha" mask-type="alpha" maskContentUnits="objectBoundingBox">
- <rect x="0.1" y="0.1" width="0.8" height="0.8" fill="black"/>
- </mask>
-
- <rect x="0" y="0" width="100" height="100" fill="green" mask="url(#luminance)"/>
-
- <rect x="100" y="0" width="100" height="100" fill="green" mask="url(#alpha)"/>
-</svg>
-```
-
-The image has two 100x100 `green` squares side by side. The one on the left gets masked
-with the `luminance` mask, which reduces it to an 80x80 rectangle. That mask is a
-**white** square, so its has full luminance at every pixel.
-
-The square on the right gets masked with the `alpha` mask. That mask is a **black**
-square, but with alpha=1.0, so it should produce the same result as the first one.
-
-Note that to make things easy, we use **white** for the luminance mask. White pixels have
-full luminance (1.0), which gets used as the mask. Conversely, we use **black** for the
-alpha mask. Those black pixels are fully opaque, and since `mask-type="alpha"` only
-considers the alpha channel, it will be using the full opacity of each pixel (1.0), which
-also gets used as the mask. So, the masks should be equivalent.
-
-Okay! Now let's write the reference SVG, the one built out of simpler elements but that
-should produce the same rendering:
-
-```xml
-<?xml version="1.0" encoding="UTF-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="200" height="100">
- <rect x="10" y="10" width="80" height="80" fill="green"/>
-
- <rect x="110" y="10" width="80" height="80" fill="green"/>
-</svg>
-```
-
-This is just the two original squares, but already clipped or masked to the final result.
-
-Now, where do we put those SVG documents for the tests?
-
-Near the end of `tests/src/filters.rs` we can include this:
-
-```rust
-test_compare_render_output!(
- mask_type,
- 200,
- 100,
- br##"<?xml version="1.0" encoding="UTF-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="200" height="100">
- <mask id="luminance" mask-type="luminance" maskContentUnits="objectBoundingBox">
- <rect x="0.1" y="0.1" width="0.8" height="0.8" fill="white"/>
- </mask>
- <mask id="alpha" mask-type="alpha" maskContentUnits="objectBoundingBox">
- <rect x="0.1" y="0.1" width="0.8" height="0.8" fill="black"/>
- </mask>
-
- <rect x="0" y="0" width="100" height="100" fill="green" mask="url(#luminance)"/>
-
- <rect x="100" y="0" width="100" height="100" fill="green" mask="url(#alpha)"/>
-</svg>
-"##,
- br##"<?xml version="1.0" encoding="UTF-8"?>
-<svg xmlns="http://www.w3.org/2000/svg" width="200" height="100">
- <rect x="10" y="10" width="80" height="80" fill="green"/>
-
- <rect x="110" y="10" width="80" height="80" fill="green"/>
-</svg>
-"##,
-);
-```
-
-Here, `test_compare_render_output!` is a macro that takes two SVG documents, the test and
-the reference, and compares their rendered results. It also takes a test name
-(`mask_type` in this case), and the pixel size of the image to generate for testing
-(200x100).
-
-## Final steps: documentation
-
-To help people who are wondering what SVG features are supported in librsvg, there is a
-`FEATURES.md` file. It has a section called "CSS properties" with a big list of property
-names and notes about them.
-
-We'll patch it like this:
-
-```
- | marker-mid | |
- | marker-start | |
- | mask | |
-+| mask-type | |
- | mix-blend-mode | Not available as a presentation attribute. |
- | opacity | |
- | overflow | |
-```
-
-There is nothing remarkable about `mask-type`, it is a plain old property that also has a
-presentation attribute (remember the `PresentationAttr::Yes` from above?), so we don't
-need to list any extra information.
-
-And with that, we are done implementing `mask-type`. Have fun!
diff --git a/devel-docs/adding_a_property.rst b/devel-docs/adding_a_property.rst
new file mode 100644
index 00000000..b8ade985
--- /dev/null
+++ b/devel-docs/adding_a_property.rst
@@ -0,0 +1,679 @@
+Adding a new CSS property to librsvg
+====================================
+
+This document is a little tour on how to add support for a CSS property
+to librsvg. We will implement the ```mask-type``
+property <https://www.w3.org/TR/css-masking-1/#the-mask-type>`__ from
+the **CSS Masking Module Level 1** specification.
+
+What is ``mask-type``?
+----------------------
+
+`The spec says about
+``mask-type`` <https://www.w3.org/TR/css-masking-1/#the-mask-type>`__:
+
+ The mask-type property defines whether the content of the mask
+ element is treated as as luminance mask or alpha mask, as described
+ in Calculating mask values.
+
+A **luminance mask** takes the RGB values of each pixel, converts them
+to a single luminance value, and uses that as a mask.
+
+An **alpha mask** just takes the alpha value of each pixel and uses it
+as a mask.
+
+The only mask type that SVG1.1 supported was luminance masks; there
+wasn’t even a ``mask-type`` property back then. The SVG2 spec removed
+descriptions of masking, and offloaded them to the `CSS Masking Module
+Level 1 <https://www.w3.org/TR/css-masking-1/>`__ specification, which
+it adds the ``mask-type`` property and others as well.
+
+Let’s start by figuring out how to read the spec.
+
+What the specification says
+---------------------------
+
+The specification for ``mask-type`` is in
+https://www.w3.org/TR/css-masking-1/#the-mask-type
+
+In the specs, most of the descriptions for properties start with a table
+that summarizes the property. For example, if you visit that link, you
+will find a table that starts with these items:
+
+- **Name:** ``mask-type``
+- **Value:** ``luminance | alpha``
+- **Initial:** ``luminance``
+- **Applies to:** mask elements
+- **Inherited:** no
+- **Computed value:** as specified
+
+Let’s go through each of these:
+
+**Name:** We have the name of the property (``mask-type``). Properties
+are case-insensitive, and librsvg already has machinery to handle that.
+
+**Value:** The possible values for the property can be ``luminance`` or
+``alpha``. In the spec’s web page, even the little ``|`` between those
+two values is a hyperlink; clicking it will take you to the
+specification for CSS Values and Units, where it describes the grammar
+that the CSS specs use to describe their values. Here you just need to
+know that ``|`` means that exactly one of the two alternatives must
+occur.
+
+As you may imagine, librsvg already parses a lot of similar properties
+that are just symbolic values. For example, the ``stroke-linecap``
+property can have values ``butt | round | square``. We’ll see how to
+write a parser for this kind of property with a minimal amount of code.
+
+**Initial:** Then there is the initial or default value, which is
+``luminance``. This means that if the ``mask-type`` property is not
+specified on an element, it takes ``luminance`` as its default. This is
+a sensible choice, since an SVG1.1 file that is processed by SVG2
+software should retain the same semantics. It also means that if there
+is a parse error, for example if you typed ``ahlpha``, the property will
+silently revert back to the default ``luminance`` value.
+
+**Applies to:** Librsvg doesn’t pay much attention to “applies to” — it
+just carries property values for all elements, and the elements that
+don’t handle a property just ignore it.
+
+**Inherited:** This property is not inherited, which means that by
+default, its value does not cascade. So if you have this:
+
+.. code:: xml
+
+ <mask style="mask-type: alpha;">
+ <other>
+ <elements>
+ <here/>
+ </elements>
+ </other>
+ </mask>
+
+Then the ``other``, ``elements``, ``here`` will not inherit the
+``mask-type`` value from their ancestor.
+
+**Computed value:** Finally, the computed value is “as specified”, which
+means that librsvg does not need to modify it in any way when resolving
+the CSS cascade. Other properties, like ``width: 1em;`` may need to be
+resolved against the ``font-size`` to obtain the computed value.
+
+The W3C specifications can get pretty verbose and it takes some practice
+to read them, but fortunately this property is short and sweet.
+
+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:
+
+.. code:: 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:
+
+.. code:: 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:
+
+.. code:: 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:
+
+.. code:: 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:
+
+.. code:: 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:
+
+.. code:: 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:
+
+.. code:: 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:
+
+.. code:: 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.
+
+Registering the property
+------------------------
+
+Okay! We defined ``MaskType`` and its symbolic identifiers with the
+``make_property!`` macro, and the macro took care of writing a parser
+for it and implementing the traits that the property needs.
+
+Now we need to modify the code in a few places to process the property.
+
+Register the property
+---------------------
+
+- First, look for ``longhands:`` in ``properties.rs``. You will find
+ that it is part of a long macro invocation:
+
+.. code:: rust
+
+ make_properties! {
+ // ... stuff omitted here
+
+ longhands: {
+ // ... stuff omitted here
+
+ "marker-end" => (PresentationAttr::Yes, marker_end : MarkerEnd),
+ "marker-mid" => (PresentationAttr::Yes, marker_mid : MarkerMid),
+ "marker-start" => (PresentationAttr::Yes, marker_start : MarkerStart),
+ "mask" => (PresentationAttr::Yes, mask : Mask),
+ // "mask-type" => (PresentationAttr::Yes, unimplemented),
+ "opacity" => (PresentationAttr::Yes, opacity : Opacity),
+ "overflow" => (PresentationAttr::Yes, overflow : Overflow),
+
+ // ... stuff omitted here
+ }
+ }
+
+In there, there is an entry for ``mask-type`` commented out. Let’s
+uncomment it and turn it into this:
+
+.. code:: rust
+
+ "mask-type" => (PresentationAttr::Yes, mask_type : MaskType),
+
+``PresentationAttr::Yes`` indicates whether the property has a
+corresponding presentation attribute. This means that you can do
+``<mask style="mask-type: alpha;">`` which is property, as well as
+``<mask mask-type="alpha">``, which is a presentation attribute.
+
+How did we find out that ``mask-type`` also exists as a presentation
+attribute? Well, `the
+spec <https://www.w3.org/TR/css-masking-1/#the-mask-type>`__ says:
+
+ The mask-type property is a presentation attribute for SVG elements.
+
+But wait! If we compile, we get this:
+
+::
+
+ error: no rules expected the token `"mask-type"`
+ --> src/properties.rs:450:9
+ |
+ 450 | "mask-type" => (PresentationAttr::Yes, mask_type : MaskType),
+ | ^^^^^^^^^^^ no rules expected this token in macro call
+
+When you see that error in exactly that macro invocation, it means this:
+librsvg uses a crate called ``markup5ever`` to have a compact
+representation of the names of properties/attributes/elements. It uses
+string interning so that, for example, there is a single definition of
+``rect`` in the program’s heap instead of there being a thousands of
+duplicated ``rect`` strings when you load a big document. The thing is,
+``markup5ever`` only has ready-made definitions of the most common
+HTML/SVG/CSS names, but unfortunately ``mask-type`` is not one of them.
+
+So, we scroll down in ``properties.rs`` and move the ``mask-type``
+registration there:
+
+.. code:: rust
+
+ longhands_not_supported_by_markup5ever: {
+ "line-height" => (PresentationAttr::No, line_height : LineHeight),
+ "mask-type" => (PresentationAttr::Yes, mask_type : MaskType), // <- right here
+ "mix-blend-mode" => (PresentationAttr::No, mix_blend_mode : MixBlendMode),
+ "paint-order" => (PresentationAttr::Yes, paint_order : PaintOrder),
+ }
+
+That block named ``longhands_not_supported_by_markup5ever`` is, well,
+exactly what it says — a separate section with property names that are
+not built into ``markup5ever``, so they must be dealt with specially.
+Just put the property there and that’s it.
+
+Next, we have to calculate the computed value for the property.
+
+Calculate the computed value
+----------------------------
+
+In ``properties.rs``, look for ``compute!``. You will find many
+invocations of this macro:
+
+.. code:: rust
+
+ compute!(MarkerEnd, marker_end);
+ compute!(MarkerMid, marker_mid);
+ compute!(MarkerStart, marker_start);
+ compute!(Mask, mask);
+ compute!(MixBlendMode, mix_blend_mode);
+ compute!(Opacity, opacity);
+ compute!(Overflow, overflow);
+
+Add a call for ``MaskType``:
+
+.. code:: rust
+
+ compute!(MarkerEnd, marker_end);
+ compute!(MarkerMid, marker_mid);
+ compute!(MarkerStart, marker_start);
+ compute!(Mask, mask);
+ compute!(MaskType, mask_type); // this is new
+ compute!(MixBlendMode, mix_blend_mode);
+ compute!(Opacity, opacity);
+ compute!(Overflow, overflow);
+
+You will see that all those calls to ``compute!`` are inside a method
+called ``SpecifiedValues::to_computed_values()``. This method is run as
+part of the CSS cascade: it takes the ``SpecifiedValues`` from an
+element and composes them onto the ``ComputedValues`` from its parent
+element. For example, if you have a document with this bit:
+
+.. code:: xml
+
+ <g stroke="red" fill="blue"> // ComputedValues with stroke:red, fill:blue
+ <rect fill="green"/> // SpecifiedValues with fill:green
+ </g>
+
+The ``ComputedValues`` that results from the ``<g>`` will have
+properties ``stroke:red`` and ``fill:blue`` in it. The
+``SpecifiedValues`` from the ``<rect>`` just has ``fill:green``.
+Composing them together for the ``<rect>`` gives us ``ComputedValues``
+with ``stroke:red`` and ``fill:green``.
+
+Now that the property is registered, we can actually handle it in the
+drawing code!
+
+Handling the property
+---------------------
+
+First, a digression: let’s change the name of a few methods to better
+reflect what the new structure of the code will be like.
+
+There are a few methods called ``to_mask`` in the code, that take an
+RGBA surface and turn it into an Alpha-only surface with the luminance
+of the original surface; and also the corresponding method to do this
+for a single pixel. Let’s do this kind of renaming:
+
+::
+
+ - pub fn to_mask(&self, opacity: UnitInterval) -> Result<SharedImageSurface, cairo::Error> {
+ + pub fn to_luminance_mask(&self, opacity: UnitInterval) -> Result<SharedImageSurface, cairo::Error> {
+
+Librsvg only effectively supported ``mask-type: luminance`` since that
+is what was in SVG1.1, but now for SVG2 we want to add behavior for
+``mask-type: alpha`` as well. So, it makes sense to rename ``to_mask``
+as ``to_luminance_mask``.
+
+``SharedImageSurface`` is the type that librsvg uses to represent images
+in memory. They can be RGBA or Alpha-only. There is already a method
+called ``extract_alpha`` that we can use to create an Alpha-only mask:
+
+.. code:: rust
+
+ // there's a type alias SharedImageSurface for this
+ impl ImageSurface<Shared> {
+ pub fn extract_alpha(&self, bounds: IRect) -> Result<SharedImageSurface, cairo::Error> { ... }
+ }
+
+Now let’s look at where ``drawing_ctx.rs`` has this:
+
+.. code:: rust
+
+ let mask = SharedImageSurface::wrap(mask_content_surface, SurfaceType::SRgb)? // (1)
+ .to_luminance_mask()? // (2)
+ .into_image_surface()?; // (3)
+
+-
+
+ (1) Wraps a ``SharedImageSurface`` around the Cairo surface that was
+ just rendered with the mask contents.
+
+-
+
+ (2) Converts it to a luminance mask. We will need to change this!
+
+-
+
+ (3) Extracts the Cairo image surface from the ``SharedImageSurface``,
+ for further processing.
+
+Remember the ``ComputedValues`` where we had the ``mask_type``? We can
+extract it with ``values.mask_type()``. Now let’s change the lines above
+to this:
+
+.. code:: rust
+
+ let tmp = SharedImageSurface::wrap(mask_content_surface, SurfaceType::SRgb)?;
+
+ let mask_result = match values.mask_type() {
+ MaskType::Luminance => tmp.to_luminance_mask()?,
+ MaskType::Alpha => tmp.extract_alpha(IRect::from_size(tmp.width(), tmp.height()))?,
+ };
+
+ let mask = mask_result.into_image_surface()?;
+
+But wait! We don’t have a test for this yet! Aaaaaargh, we are doing
+test-driven development backwards!
+
+No biggie. Let’s write the tests.
+
+Adding tests
+------------
+
+Testing graphical output is really annoying if you compare PNG files,
+because any time Cairo changes something and antialiasing changes
+juuuuuust a bit, the tests break. So, librsvg tries to do “reftests”, or
+reference tests, by comparing the rendered results of two things:
+
+- The SVG you actually want to test.
+- An equivalent SVG that works only with known-good features.
+
+For ``mask-type``, we need an SVG document that actually uses that
+property with both of its values, and another document that produces the
+same results but with simpler primitives.
+
+Librsvg already has tests for luminance masks, as they were the only
+available kind in SVG1.1. So we can be confident that they already work
+- we just need to test that the presence of ``mask-type="luminance"``
+actually does the same thing.
+
+First, let’s dissect the SVG that we want to test:
+
+.. code:: xml
+
+ <?xml version="1.0" encoding="UTF-8"?>
+ <svg xmlns="http://www.w3.org/2000/svg" width="200" height="100">
+ <mask id="luminance" mask-type="luminance" maskContentUnits="objectBoundingBox">
+ <rect x="0.1" y="0.1" width="0.8" height="0.8" fill="white"/>
+ </mask>
+ <mask id="alpha" mask-type="alpha" maskContentUnits="objectBoundingBox">
+ <rect x="0.1" y="0.1" width="0.8" height="0.8" fill="black"/>
+ </mask>
+
+ <rect x="0" y="0" width="100" height="100" fill="green" mask="url(#luminance)"/>
+
+ <rect x="100" y="0" width="100" height="100" fill="green" mask="url(#alpha)"/>
+ </svg>
+
+The image has two 100x100 ``green`` squares side by side. The one on the
+left gets masked with the ``luminance`` mask, which reduces it to an
+80x80 rectangle. That mask is a **white** square, so its has full
+luminance at every pixel.
+
+The square on the right gets masked with the ``alpha`` mask. That mask
+is a **black** square, but with alpha=1.0, so it should produce the same
+result as the first one.
+
+Note that to make things easy, we use **white** for the luminance mask.
+White pixels have full luminance (1.0), which gets used as the mask.
+Conversely, we use **black** for the alpha mask. Those black pixels are
+fully opaque, and since ``mask-type="alpha"`` only considers the alpha
+channel, it will be using the full opacity of each pixel (1.0), which
+also gets used as the mask. So, the masks should be equivalent.
+
+Okay! Now let’s write the reference SVG, the one built out of simpler
+elements but that should produce the same rendering:
+
+.. code:: xml
+
+ <?xml version="1.0" encoding="UTF-8"?>
+ <svg xmlns="http://www.w3.org/2000/svg" width="200" height="100">
+ <rect x="10" y="10" width="80" height="80" fill="green"/>
+
+ <rect x="110" y="10" width="80" height="80" fill="green"/>
+ </svg>
+
+This is just the two original squares, but already clipped or masked to
+the final result.
+
+Now, where do we put those SVG documents for the tests?
+
+Near the end of ``tests/src/filters.rs`` we can include this:
+
+.. code:: rust
+
+ test_compare_render_output!(
+ mask_type,
+ 200,
+ 100,
+ br##"<?xml version="1.0" encoding="UTF-8"?>
+ <svg xmlns="http://www.w3.org/2000/svg" width="200" height="100">
+ <mask id="luminance" mask-type="luminance" maskContentUnits="objectBoundingBox">
+ <rect x="0.1" y="0.1" width="0.8" height="0.8" fill="white"/>
+ </mask>
+ <mask id="alpha" mask-type="alpha" maskContentUnits="objectBoundingBox">
+ <rect x="0.1" y="0.1" width="0.8" height="0.8" fill="black"/>
+ </mask>
+
+ <rect x="0" y="0" width="100" height="100" fill="green" mask="url(#luminance)"/>
+
+ <rect x="100" y="0" width="100" height="100" fill="green" mask="url(#alpha)"/>
+ </svg>
+ "##,
+ br##"<?xml version="1.0" encoding="UTF-8"?>
+ <svg xmlns="http://www.w3.org/2000/svg" width="200" height="100">
+ <rect x="10" y="10" width="80" height="80" fill="green"/>
+
+ <rect x="110" y="10" width="80" height="80" fill="green"/>
+ </svg>
+ "##,
+ );
+
+Here, ``test_compare_render_output!`` is a macro that takes two SVG
+documents, the test and the reference, and compares their rendered
+results. It also takes a test name (``mask_type`` in this case), and the
+pixel size of the image to generate for testing (200x100).
+
+Final steps: documentation
+--------------------------
+
+To help people who are wondering what SVG features are supported in
+librsvg, there is a ``FEATURES.md`` file. It has a section called “CSS
+properties” with a big list of property names and notes about them.
+
+We’ll patch it like this:
+
+::
+
+ | marker-mid | |
+ | marker-start | |
+ | mask | |
+ +| mask-type | |
+ | mix-blend-mode | Not available as a presentation attribute. |
+ | opacity | |
+ | overflow | |
+
+There is nothing remarkable about ``mask-type``, it is a plain old
+property that also has a presentation attribute (remember the
+``PresentationAttr::Yes`` from above?), so we don’t need to list any
+extra information.
+
+And with that, we are done implementing ``mask-type``. Have fun!
diff --git a/devel-docs/architecture.rst b/devel-docs/architecture.rst
new file mode 100644
index 00000000..c8b7d4e1
--- /dev/null
+++ b/devel-docs/architecture.rst
@@ -0,0 +1,474 @@
+Architecture of librsvg
+=======================
+
+This document roughly describes the architecture of librsvg, and future
+plans for it. The code is continually evolving, so don’t consider this
+as the ground truth, but rather like a cheap map you buy at a street
+corner.
+
+The library’s internals are documented as Rust documentation comments;
+you can look at the rendered version at
+https://gnome.pages.gitlab.gnome.org/librsvg/internals/librsvg/index.html
+
+You may also want to see the section below on `interesting parts of the
+code <#some-interesting-parts-of-the-code>`__.
+
+A bit of history
+----------------
+
+Librsvg is an old library. It started around 2001, when Eazel (the
+original makers of GNOME’s file manager Nautilus) needed a library to
+render SVG images. At that time the SVG format was being standardized,
+so librsvg grew along with the SVG specification. This is why you will
+sometimes see references to deprecated SVG features in the source code.
+
+Librsvg started as an experiment to use libxml2’s new SAX parser, so
+that SVG could be streamed in and rendered on the fly, instead of first
+creating a DOM tree. Originally it used
+`libart <https://levien.com/libart/>`__ as a rendering library; this was
+GNOME’s first antialiased renderer with alpha compositing. Later, the
+renderer was replaced with `Cairo <https://www.cairographics.org/>`__.
+Librsvg is currently striving to support other rendering backends.
+
+These days librsvg indeed builds a DOM tree by itself; it needs the
+tree to run the CSS cascade, do selector matching, and to support
+cross-element references like in SVG filters.
+
+Librsvg started as a C library with an ad-hoc API. At some point it
+got turned into a GObject library, so that the main `RsvgHandle
+<https://gnome.pages.gitlab.gnome.org/librsvg/internals/librsvg/c_api/handle/struct.RsvgHandle.html>`_
+class defines most of the entry points into the library. Through
+`GObject Introspection <https://gi.readthedocs.io/en/latest/>`__, this
+allows librsvg to be used from other programming languages.
+
+In 2016, librsvg started getting ported to Rust. As of early 2021, the
+whole library is implemented in Rust, and exports an intact C API/ABI.
+It also exports a more idiomatic Rust API as well.
+
+The C and Rust APIs
+-------------------
+
+Librsvg exports two public APIs, one for C and one for Rust.
+
+The C API has hard requirements for API/ABI stability, because it is
+used all over the GNOME project and API/ABI breaks would be highly
+disruptive. Also, the C API is what allows librsvg to be called from
+other programming languages, through `GObject
+Introspection <https://gi.readthedocs.io/en/latest/>`__.
+
+The Rust API is a bit more lax in its API stability, but we try to stick
+to `semantic versioning <https://semver.org/>`__ as is common in Rust.
+
+The public Rust API is implemented in `src/api.rs
+<https://gitlab.gnome.org/GNOME/librsvg/-/blob/main/src/api.rs>`_. This
+has all the primitives needed to load and render SVG documents or
+individual elements, and to configure loading/rendering options.
+
+The public C API is implemented in `src/c_api/
+<https://gitlab.gnome.org/GNOME/librsvg/-/tree/main/src/c_api>`_, and
+it is implemented in terms of the public Rust API. Note that as of
+2021/Feb the corresponding C header files are hand-written in
+`include/librsvg/
+<https://gitlab.gnome.org/GNOME/librsvg/-/tree/main/include/librsvg>`_;
+maybe in the future they will be generated automatically with
+`cbindgen <https://github.com/eqrion/cbindgen/blob/master/docs.md>`__.
+
+We consider it good practice to provide simple and clean primitives in
+the Rust API, and have ``c_api`` deal with all the idiosyncrasies and
+historical considerations for the C API.
+
+In short: the public C API calls the public Rust API, and the public
+Rust API calls into the library's internals.
+
+::
+
+ +----------------+
+ | Public C API |
+ | src/c_api |
+ +----------------+
+ |
+ calls
+ |
+ v
+ +-------------------+
+ | Public Rust API |
+ | src/api.rs |
+ +-------------------+
+ |
+ calls
+ |
+ v
+ +-------------------+
+ | library internals |
+ | src/*.rs |
+ +-------------------+
+
+The test suite
+--------------
+
+The test suite is documented in ``tests/README.md``.
+
+Code flow
+---------
+
+The caller of librsvg loads a document into a handle, and later may ask
+to render the document or one of its elements, or measure their
+geometries.
+
+Loading an SVG document
+~~~~~~~~~~~~~~~~~~~~~~~
+
+The Rust API starts by constructing an `SvgHandle
+<https://gnome.pages.gitlab.gnome.org/librsvg/internals/librsvg/api/struct.SvgHandle.html>`_
+from a `Loader
+<https://gnome.pages.gitlab.gnome.org/librsvg/internals/librsvg/api/struct.Loader.html>`_;
+both of those are public types. Internally the ``SvgHandle`` is just a
+wrapper around a `Handle
+<https://gnome.pages.gitlab.gnome.org/librsvg/internals/librsvg/handle/struct.Handle.html>`_,
+which is a private type. ``Handle`` represents an SVG document loaded
+in memory; it acts as a wrapper around a `Document
+<https://gnome.pages.gitlab.gnome.org/librsvg/internals/librsvg/document/struct.Document.html>`_,
+and provides the basic primitive operations like “render the whole
+document” or “compute the geometry of an element” that are needed to
+implement the public APIs.
+
+A ``Document`` gets created by loading XML from a stream, into a tree
+of `Node
+<https://gnome.pages.gitlab.gnome.org/librsvg/internals/librsvg/node/type.Node.html>`_
+structures. This is similar to a web browser’s DOM tree.
+
+Each XML element causes a new ``Node`` to get created with an `Element
+<https://gnome.pages.gitlab.gnome.org/librsvg/internals/librsvg/element/enum.Element.html>`_
+in it. The ``Element`` enum can represent all the SVG element types;
+for example, a ``<path>`` element from XML gets turned into a
+``Node::Element(Element::Path)``.
+
+When an ``Element`` is created from its corresponding XML, its
+`Attributes
+<https://gnome.pages.gitlab.gnome.org/librsvg/internals/librsvg/xml/attributes/struct.Attributes.html>`_
+get parsed. On one hand, attributes that are specific to a particular
+element type, like the ``d`` in ``<path d="...">`` get parsed by the
+`set_attributes
+<https://gnome.pages.gitlab.gnome.org/librsvg/internals/librsvg/element/trait.SetAttributes.html#method.set_attributes>`_
+method of each particular element type (in that case,
+`Path::set_attributes
+<https://gnome.pages.gitlab.gnome.org/librsvg/internals/librsvg/shapes/struct.Path.html#method.set_attributes>`_).
+
+On the other hand, attributes that refer to styles, and which may
+appear for any kind of element, get all parsed into a `SpecifiedValues
+<https://gnome.pages.gitlab.gnome.org/librsvg/internals/librsvg/properties/struct.SpecifiedValues.html>`_
+struct. This is a memory-efficient representation of the CSS style
+properties that an element has.
+
+When the XML document is fully parsed, a ``Document`` contains a tree of
+``Node`` structs and their inner ``Element`` structs. The tree has also
+been validated to ensure that the root is an ``<svg>`` element.
+
+After that, the CSS cascade step gets run.
+
+The CSS cascade
+~~~~~~~~~~~~~~~
+
+Each ``Element`` has a `SpecifiedValues
+<https://gnome.pages.gitlab.gnome.org/librsvg/internals/librsvg/properties/struct.SpecifiedValues.html>`_,
+which has the CSS style properties that the XML specified for that
+element. However, ``SpecifiedValues`` is sparse, as not all the
+possible style properties may have been filled in. Cascading means
+following the CSS/SVG rules for each property type to inherit missing
+properties from parent elements. For example, in this document
+fragment:
+
+::
+
+ <g stroke-width="2" stroke="black">
+ <path d="M0,0 L10,0" fill="blue"/>
+ <path d="M20,0 L30,0" fill="green"/>
+ </g>
+
+Each ``<path>`` element has a different fill color, but they both
+*inherit* the ``stroke-width`` and ``stroke`` values from their parent
+group. This is because both the ``stroke-width`` and ``stroke``
+properties are defined in the CSS/SVG specifications to inherit
+automatically. Some other properties, like ``opacity``, do not inherit
+and are thus not copied to child elements.
+
+In librsvg, the individual types for CSS properties are defined with
+the `make_property
+<https://gnome.pages.gitlab.gnome.org/librsvg/internals/librsvg/macro.make_property.html>`_
+macro.
+
+The cascading step takes each element’s ``SpecifiedValues`` and
+composes it by CSS inheritance onto a `ComputedValues
+<https://gnome.pages.gitlab.gnome.org/librsvg/internals/librsvg/properties/struct.ComputedValues.html>`_,
+which has the result of the cascade for each element's properties.
+
+When cascading is done, each ``Element`` has a fully resolved
+``ComputedValues`` struct, which is what gets used during rendering to
+look up things like the element’s stroke width or fill color.
+
+Parsing XML into a tree of Nodes / Elements
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Librsvg uses an XML parser (`libxml2
+<https://gitlab.gnome.org/GNOME/libxml2/-/wikis/home>`_ at the time of
+this writing) to do the first-stage parsing of the SVG
+document. `XmlState
+<https://gnome.pages.gitlab.gnome.org/librsvg/internals/librsvg/xml/struct.XmlState.html>`_
+contains the XML parsing state, which is a stack of contexts depending
+on the XML nesting structure. ``XmlState`` has public methods, called
+from the XML parser as it goes. The most important one is
+`start_element
+<https://gnome.pages.gitlab.gnome.org/librsvg/internals/librsvg/xml/struct.XmlState.html#method.start_element>`_;
+this is responsible for creating new ``Node`` structures in the tree,
+within the `DocumentBuilder
+<https://gnome.pages.gitlab.gnome.org/librsvg/internals/librsvg/document/struct.DocumentBuilder.html>`_
+being built.
+
+Nodes are either SVG elements (the `Element
+<https://gnome.pages.gitlab.gnome.org/librsvg/internals/librsvg/element/enum.Element.html>`_
+enum), or text data inside elements (the `Chars
+<https://gnome.pages.gitlab.gnome.org/librsvg/internals/librsvg/text/struct.Chars.html>`_
+struct); this last one will not concern us here, and we will only talk
+about ``Element``.
+
+Each supported kind of ``Element`` parses its attributes in a
+`set_attributes
+<https://gnome.pages.gitlab.gnome.org/librsvg/internals/librsvg/element/trait.SetAttributes.html#method.set_attributes>`_
+method. Each attribute is just a key/value pair; for example, the
+``<rect width="5px">`` element has a ``width`` attribute whose value
+is ``5px``.
+
+While parsing its attributes, an element may encounter an invalid value,
+for example, a negative width where only nonnegative ones are allowed.
+In this case, the element’s ``set_attributes`` method may return a
+``Result::Err``. The caller will then do ``set_error`` to mark that
+element as being in an error state. If an element is in error, its
+children will get parsed as usual, but the element and its children will
+be ignored during the rendering stage.
+
+The SVG spec says that SVG rendering should stop on the first element
+that is “in error”. However, most implementations simply seem to ignore
+erroneous elements instead of completely stopping rendering, and we do
+the same in librsvg.
+
+CSS and styles
+~~~~~~~~~~~~~~
+
+Librsvg uses Servo’s `cssparser <https://crates.io/crates/cssparser>`_
+crate as a CSS tokenizer, and `selectors
+<https://crates.io/crates/selectors>`_ as a high-level parser for CSS
+style data.
+
+With the ``cssparser`` crate, the caller is responsible for providing
+an implementation of the `DeclarationParser
+<https://docs.rs/cssparser/0.29.6/cssparser/trait.DeclarationParser.html>`_
+trait. Its `parse_value
+<https://docs.rs/cssparser/0.29.6/cssparser/trait.DeclarationParser.html#tymethod.parse_value>`_
+method takes the name of a CSS property name like ``fill``, plus a
+value like ``rgb(255, 0, 0)``, and it must return a value that
+represents a parsed declaration. Librsvg uses the `Declaration
+<https://gnome.pages.gitlab.gnome.org/librsvg/internals/librsvg/css/struct.Declaration.html>`_
+struct for this.
+
+The core of parsing CSS is the ``parse_value`` function, which returns
+a `ParsedProperty
+<https://gnome.pages.gitlab.gnome.org/librsvg/internals/librsvg/properties/enum.ParsedProperty.html>`_:
+
+.. code:: rust
+
+ pub enum ParsedProperty {
+ BaselineShift(SpecifiedValue<BaselineShift>),
+ ClipPath(SpecifiedValue<ClipPath>),
+ Color(SpecifiedValue<Color>),
+ // etc.
+ }
+
+What is `SpecifiedValue
+<https://gnome.pages.gitlab.gnome.org/librsvg/internals/librsvg/properties/enum.SpecifiedValue.html>`_?
+It is the parsed value for a CSS property directly as it comes out of
+the SVG document:
+
+.. code:: rust
+
+ pub enum SpecifiedValue<T>
+ where
+ T: Property + Clone + Default,
+ {
+ Unspecified,
+ Inherit,
+ Specified(T),
+ }
+
+A property declaration can look like ``opacity: inherit;`` - this would
+create a ``ParsedProperty::Opacity(SpecifiedValue::Inherit)``.
+
+Or it can look like ``opacity: 0.5;`` - this would create a
+``ParsedProperty::Opacity(SpecifiedValue::Specified(Opacity(UnitInterval(0.5))))``.
+Let’s break this down:
+
+- ``ParsedProperty::Opacity`` - which property did we parse?
+
+- ``SpecifiedValue::Specified`` - it actually was specified by the
+ document with a value; the other interesting alternative is
+ ``Inherit``, which corresponds to the value ``inherit`` that all CSS
+ property declarations can have.
+
+- ``Opacity(UnitInterval(0.5))`` - This is the type `Opacity
+ <https://gnome.pages.gitlab.gnome.org/librsvg/internals/librsvg/property_defs/struct.Opacity.html>`_
+ property, which is a newtype around an internal `UnitInterval
+ <https://gnome.pages.gitlab.gnome.org/librsvg/internals/librsvg/unit_interval/struct.UnitInterval.html>`_
+ type, which in turn guarantees that we have a float in the range
+ ``[0.0, 1.0]``.
+
+There is a Rust type for every CSS property that librsvg supports; many
+of these types are newtypes around primitive types like ``f64``.
+
+Eventually an entire CSS stylesheet, like the contents of a
+``<style>`` element, gets parsed into a `Stylesheet
+<https://gnome.pages.gitlab.gnome.org/librsvg/internals/librsvg/css/struct.Stylesheet.html>`_
+struct. A stylesheet has a list of rules, where each rule is the CSS
+selectors defined for it, and the style declarations that should be
+applied for the ``Node``\ s that match the selectors. For example, in
+a little stylesheet like this:
+
+.. code:: xml
+
+ <style type="text/css">
+ rect, #some_id {
+ fill: blue;
+ stroke-width: 5px;
+ }
+ </style>
+
+This stylesheet has a single rule. The rule has a selector list with two
+selectors (``rect`` and ``#some_id``) and two style declarations
+(``fill: blue`` and ``stroke-width: 5px``).
+
+After parsing is done, there is a **cascading stage** where librsvg
+walks the tree of nodes, and for each node it finds the CSS rules that
+should be applied to it.
+
+Rendering
+---------
+
+The rendering process starts at the `draw_tree()
+<https://gnome.pages.gitlab.gnome.org/librsvg/internals/librsvg/drawing_ctx/fn.draw_tree.html>`_
+function. This sets up a `DrawingCtx
+<https://gnome.pages.gitlab.gnome.org/librsvg/internals/librsvg/drawing_ctx/struct.DrawingCtx.html>`_,
+which carries around all the mutable state during rendering.
+
+Rendering is a recursive process, which goes back and forth between
+the utility functions in ``DrawingCtx`` and the `draw
+<https://gnome.pages.gitlab.gnome.org/librsvg/internals/librsvg/element/trait.Draw.html#method.draw>`_
+method in elements.
+
+The main job of ``DrawingCtx`` is to deal with the SVG drawing model.
+Each element renders itself independently, and its result gets modified
+before getting composited onto the final image:
+
+1. Render an element to a temporary surface (example: stroke and fill a
+ path).
+2. Apply filter effects (blur, color mapping, etc.).
+3. Apply clipping paths.
+4. Apply masks.
+5. Composite the result onto the final image.
+
+The temporary result from the last step also gets put in a stack; this
+is because filter effects sometimes need to look at the currently-drawn
+background to apply further filtering to it.
+
+You’ll see that most of the rendering-related functions return a
+``Result<BoundingBox, RenderingError>``. Some SVG features require
+knowing the bounding box of the object that is being rendered; for
+historical reasons this bounding box is computed as part of the
+rendering process in librsvg. When computing a subtree’s bounding box,
+the bounding boxes from the leaves get aggregated up to the root of
+the subtree. Each node in the tree has its own coordinate system;
+`BoundingBox
+<https://gnome.pages.gitlab.gnome.org/librsvg/internals/librsvg/bbox/struct.BoundingBox.html>`_
+is able to transform coordinate systems to get a bounding box that is
+meaningful with respect to the root’s transform.
+
+Comparing floating-point numbers
+--------------------------------
+
+Librsvg sometimes needs to compute things like “are these points equal?”
+or “did this computed result equal this test reference number?”.
+
+We use ``f64`` numbers in Rust for all computations on real numbers.
+Floating-point numbers cannot be compared with ``==`` effectively, since
+it doesn’t work when the numbers are slightly different due to numerical
+inaccuracies.
+
+Similarly, we don’t ``assert_eq!(a, b)`` for floating-point numbers.
+
+Most of the time we are dealing with coordinates which will get passed
+to Cairo. In turn, Cairo converts them from doubles to a fixed-point
+representation (as of March 2018, Cairo uses 24.8 fixnums with 24 bits
+of integral part and 8 bits of fractional part).
+
+So, we can consider two numbers to be “equal” if they would be
+represented as the same fixed-point value by Cairo. Librsvg implements
+this in the `ApproxEqCairo
+<https://gnome.pages.gitlab.gnome.org/librsvg/internals/librsvg/float_eq_cairo/trait.ApproxEqCairo.html>`_
+trait. You can use it like this:
+
+.. code:: rust
+
+ use float_eq_cairo::ApproxEqCairo; // bring the trait into scope
+
+ let a: f64 = ...;
+ let b: f64 = ...;
+
+ if a.approx_eq_cairo(&b) { // not a == b
+ ... // equal!
+ }
+
+ assert!(1.0_f64.approx_eq_cairo(&1.001953125_f64)); // 1 + 1/512 - cairo rounds to 1
+
+Some interesting parts of the code
+----------------------------------
+
+- Are you adding support for a CSS property? Look at the
+ :doc:`adding_a_property` tutorial; look in the `property_defs
+ <https://gnome.pages.gitlab.gnome.org/librsvg/internals/librsvg/property_defs/index.html>`_
+ and `properties
+ <https://gnome.pages.gitlab.gnome.org/librsvg/internals/librsvg/properties/index.html>`_
+ modules. ``property_defs`` defines most of the CSS properties that
+ librsvg supports, and ``properties`` actually puts all those
+ properties in the ``SpecifiedValues`` and ``ComputedValues``
+ structs.
+
+- The `Handle
+ <https://gnome.pages.gitlab.gnome.org/librsvg/internals/librsvg/handle/struct.Handle.html>`_
+ struct provides the primitives to implement the public APIs, such as
+ loading an SVG file and rendering it.
+
+- The `DrawingCtx
+ <https://gnome.pages.gitlab.gnome.org/librsvg/internals/librsvg/drawing_ctx/struct.DrawingCtx.html>`_
+ struct is active while an SVG handle is being drawn. It has all the
+ mutable state related to the drawing process, such as the stack of
+ temporary rendered surfaces, and the viewport stack.
+
+- The `Document
+ <https://gnome.pages.gitlab.gnome.org/librsvg/internals/librsvg/document/struct.Document.html>`_
+ struct represents a loaded SVG document. It holds the tree of `Node
+ <https://gnome.pages.gitlab.gnome.org/librsvg/internals/librsvg/node/type.Node.html>`_
+ structs, some of which contain `Element
+ <https://gnome.pages.gitlab.gnome.org/librsvg/internals/librsvg/element/enum.Element.html>`_
+ and some other contain `Chars
+ <https://gnome.pages.gitlab.gnome.org/librsvg/internals/librsvg/text/struct.Chars.html>`_
+ for text data in the XML. A ``Document`` also contains a mapping of
+ ``id`` attributes to the corresponding element nodes.
+
+- The `xml
+ <https://gnome.pages.gitlab.gnome.org/librsvg/internals/librsvg/xml/index.html>`_
+ module receives events from an XML parser, and builds a
+ ``Document`` tree.
+
+- The `css
+ <https://gnome.pages.gitlab.gnome.org/librsvg/internals/librsvg/css/index.html>`_
+ module has the high-level machinery for parsing CSS and representing
+ parsed stylesheets. The low-level parsers for individual properties
+ are in `property_defs <https://gnome.pages.gitlab.gnome.org/librsvg/internals/librsvg/property_defs/index.html>`_ and
+ `font_props <https://gnome.pages.gitlab.gnome.org/librsvg/internals/librsvg/font_props/index.html>`_.
diff --git a/devel-docs/index.rst b/devel-docs/index.rst
index 3d8f28a9..3a3d2e42 100644
--- a/devel-docs/index.rst
+++ b/devel-docs/index.rst
@@ -5,11 +5,14 @@ Development guide for librsvg
product
roadmap
devel_environment
+ architecture
+ adding_a_property
memory_leaks
contributing
ci
text_layout
api_observability
+ releasing
:maxdepth: 1
:caption: Contents:
@@ -41,19 +44,17 @@ FIXME: link to doc with stuff from CONTRIBUTING.md's "Hacking on librsvg"
Add basic info on cloning the repo, getting a gitlab account, forking.
-Development roadmap.
-
Understand the code
-------------------
-FIXME: Overview of the source tree.
-
Tour of the code - load a file, render it.
Test suite - move tests/readme here?
-Link to the internals documentation.
+- `Documentation of the library's internals <https://gnome.pages.gitlab.gnome.org/librsvg/internals/librsvg/index.html>`_
+- :doc:`architecture`
+- :doc:`adding_a_property`
- :doc:`memory_leaks`
Design documents
@@ -74,7 +75,7 @@ contributions.
Information for maintainers
---------------------------
-FIXME: Move RELEASING.md here
+- :doc:`releasing`
Overview of the maintainer's workflow.
@@ -85,15 +86,38 @@ Documentation on the CI.
References
----------
-Link to SVG/CSS specs; other useful bits.
+- `SVG2 specification <https://www.w3.org/TR/SVG2/>`_. This is the current Candidate Recommendation and it should
+ be your main reference...
-Links to Mozilla's SVG, WebKit, resvg, Inkscape.
+- ... except for things which are later clarified in the `SVG2 Editor's Draft <https://svgwg.org/svg2-draft/>`_.
-Talks on librsvg.
+- `Filter Effects Module Level 1 <https://www.w3.org/TR/filter-effects/>`_.
+
+- `References listed in the SVG2 spec
+ <https://www.w3.org/TR/SVG2/refs.html>`_ - if you need to consult
+ the CSS specifications.
+
+- `SVG1.1 specification <https://www.w3.org/TR/SVG11/>`_. Use this mostly for historical reference.
+
+- `SVG Working Group repository
+ <https://github.com/w3c/svgwg/tree/master>`_. The github issues are
+ especially interesting. Use this to ask for clarifications of the
+ spec.
-Indices and tables
-------------------
+- `SVG Working Group page <https://svgwg.org/>`_.
-* :ref:`genindex`
-* :ref:`modindex`
-* :ref:`search`
+- Presentation at GUADEC 2017, `Replacing C library code with Rust: What I learned with
+ librsvg <https://viruta.org/docs/fmq-porting-c-to-rust.pdf>`_. It gives
+ a little history of librsvg, and how/why it was being ported to Rust
+ from C.
+
+- Presentation at GUADEC 2018, `Patterns of refactoring C to Rust: the case of
+ librsvg <https://viruta.org/docs/fmq-refactoring-c-to-rust.pdf>`_. It
+ describes ways in which librsvg's C code was refactored to allow
+ porting it to Rust.
+
+- `Federico Mena's blog posts on librsvg
+ <https://viruta.org/tag/librsvg.html>`_ - plenty of of history and
+ stories from the development process.
+
+Talks on librsvg.
diff --git a/devel-docs/releasing.rst b/devel-docs/releasing.rst
new file mode 100644
index 00000000..b0d9579d
--- /dev/null
+++ b/devel-docs/releasing.rst
@@ -0,0 +1,241 @@
+Release process checklist for librsvg
+=====================================
+
+Feel free to print this document or copy it to a text editor to check
+off items while making a release.
+
+- ☐ Refresh your memory with
+ https://wiki.gnome.org/MaintainersCorner/Releasing
+- ☐ Increase the package version number in ``configure.ac`` (it may
+ already be increased but not released; double-check it).
+- ☐ Copy version number to ``Cargo.toml``.
+- ☐ Copy version number to ``doc/librsvg.toml``.
+- ☐ ``cargo update`` - needed because you tweaked ``Cargo.toml``, and
+ also to get new dependencies.
+- ☐ Tweak the library version number in ``configure.ac`` if the API
+ changed; follow the steps there.
+- ☐ Update ``NEWS``, see below for the preferred format.
+- ☐ Commit the changes above.
+- ☐ Make a tarball with
+ ``./autogen.sh --enable-vala && make distcheck DESTDIR=/tmp/foo`` -
+ fix things until it passes.
+- ☐ Create a signed tag - ``git tag -s x.y.z`` with the version number.
+- ☐ ``git push`` to the appropriate branch to
+ gitlab.gnome.org/GNOME/librsvg
+- ☐ ``git push`` the signed tag to gitlab.gnome.org/GNOME/librsvg
+- ☐ ``scp librsvg-x.y.z.tar.xz master.gnome.org:``
+- ☐ ``ssh master.gnome.org`` and then
+ ``ftpadmin install librsvg-x.y.z.tar.xz``
+- ☐ Create a `release in Gitlab <https://gitlab.gnome.org/GNOME/librsvg/-/releases>`_.
+
+For ``x.y.0`` releases, at least, do the following:
+
+- ☐ `Notify the release
+ team <https://gitlab.gnome.org/GNOME/releng/-/issues>`__ on whether
+ to use this ``librsvg-x.y.0`` for the next GNOME version via an issue
+ on their ``GNOME/releng`` project.
+
+- ☐ ``cargo-audit audit`` and ensure we don’t have vulnerable
+ dependencies.
+
+Gitlab release
+--------------
+
+- ☐ Go to https://gitlab.gnome.org/GNOME/librsvg/-/releases and click
+ the **New release** button.
+
+- ☐ Select the tag ``x.y.z`` you created as part of the release steps.
+
+- ☐ If there is an associated milestone, select it too.
+
+- ☐ Fill in the release title - ``x.y.z - stable`` or
+ ``x.y.z - development``.
+
+- ☐ Copy the release notes from NEWS.
+
+- ☐ Add a release asset link to
+ ``https://download.gnome.org/sources/librsvg/x.y/librsvg-x.y.z.tar.xz``
+ and call it ``librsvg-x.y.z.tar.xz - release tarball``.
+
+- ☐ Add a release asset link to
+ ``https://download.gnome.org/sources/librsvg/x.y/librsvg-x.y.z.sha256sum``
+ and call it
+ ``librsvg-x.y.z.sha256sum - release tarball sha256sum``.
+
+Version numbers
+---------------
+
+``configure.ac`` and ``Cargo.toml`` must have the same **package
+version** number - this is the number that users of the library see.
+
+``configure.ac`` is where the **library version** is defined; this is
+what gets encoded in the SONAME of ``librsvg.so``.
+
+Librsvg follows an even/odd numbering scheme for the **package
+version**. For example, the 2.50.x series is for stable releases, and
+2.51.x is for unstable/development ones. The
+`release-team <https://gitlab.gnome.org/GNOME/releng/-/issues>`__ needs
+to be notified when a new series comes about, so they can adjust their
+tooling for the stable or development GNOME releases. File an issue in
+their `repository <https://gitlab.gnome.org/GNOME/releng/-/issues>`__ to
+indicate whether the new ``librsvg-x.y.0`` is a stable or development
+series.
+
+Minimum supported Rust version (MSRV)
+-------------------------------------
+
+While it may seem desirable to always require the latest released
+version of the Rust toolchain, to get new language features and such,
+this is really inconvenient for distributors of librsvg which do not
+update Rust all the time. So, we make a compromise.
+
+The ``configure.ac`` script defines ``MININUM_RUST_MAJOR`` and
+``MINIMUM_RUST_MINOR`` variables with librsvg’s minimum supported Rust
+version (MSRV). These ensure that distros will get an early failure
+during a build, at the ``configure`` step, if they have a version of
+Rust that is too old — instead of getting an obscure error message from
+``rustc`` in the middle of the build when it finds an unsupported
+language construct.
+
+Please update both of these when increasing the MSRV:
+
+- The ``MININUM_RUST_MAJOR`` and ``MINIMUM_RUST_MINOR`` values in ``configure.ac``.
+
+- The ``rust-version`` value in ``Cargo.toml``.
+
+Sometimes librsvg’s dependencies update their MSRV and librsvg may need
+to increase it as well. Please consider the following before doing this:
+
+- Absolutely do not require a nightly snapshot of the compiler, or
+ crates that only build on nightly.
+
+- Distributions with rolling releases usually keep their Rust
+ toolchains fairly well updated, maybe not always at the latest, but
+ within two or three releases earlier than the latest. If the MSRV you
+ want is within about six months of the latest, things are probably
+ safe.
+
+- Enterprise distributions update more slowly. It is useful to watch
+ for the MSRV that Firefox requires, although sometimes Firefox
+ updates Rust very slowly as well. Now that distributions are shipping
+ packages other than Firefox that require Rust, they will probably
+ start updating more frequently.
+
+Generally — two or three releases earlier than the latest stable Rust is
+OK for rolling distros, probably perilous for enterprise distros.
+Releases within a year of an enterprise distro’s shipping date are
+probably OK.
+
+If you are not sure, ask on the `forum for GNOME
+distributors <https://discourse.gnome.org/tag/distributor>`__ about
+their plans! (That is, posts on ``discourse.gnome.org`` with the
+``distributor`` tag.)
+
+Format for release notes in NEWS
+--------------------------------
+
+The ``NEWS`` file contains the release notes. Please use something
+close to this format; it is not mandatory, but makes the formatting
+consistent, and is what tooling expects elsewhere - also by writing
+Markdown, you can just cut&paste it into a Gitlab release. You can skim
+bits of the ``NEWS`` file for examples on style and content.
+
+New entries go at the **top** of the file.
+
+::
+
+ Version x.y.z
+ =============
+
+ Commentary on the release; put anything here that you want to
+ highlight. Note changes in the build process, if any, or any other
+ things that may trip up distributors.
+
+ ## Description of a special feature
+
+ You can include headings with `##` in Markdown syntax.
+
+ Blah blah blah.
+
+
+ Next is a list of features added and issues fixed; use gitlab's issue
+ numbers. I tend to use this order: first security bugs, then new
+ features and user-visible changes, finally regular bugs. The
+ rationale is that if people stop reading early, at least they will
+ have seen the most important stuff first.
+
+ ## Changes:
+
+ - #123 - title of the issue, or short summary if it warrants more
+ discussion than just the title.
+
+ - #456 - fix blah blah (Contributor's Name).
+
+ ## Special thanks for this release:
+
+ - Any people that you want to highlight. Feel free to omit this
+ section if the release is otherwise unremarkable.
+
+Making a tarball
+----------------
+
+::
+
+ make distcheck DESTDIR=/tmp/foo
+
+The ``DESTDIR`` is a quirk, required because otherwise the gdk-pixbuf
+loader will try to install itself into the system’s location for pixbuf
+loaders, and it won’t work. The ``DESTDIR`` is what Linux distribution
+packaging scripts use to ``make install`` the compiled artifacts to a
+temporary location before building a system package.
+
+Copying the tarball to master.gnome.org
+---------------------------------------
+
+If you don’t have a maintainer account there, ask federico@gnome.org to
+do it or `ask the release
+team <https://gitlab.gnome.org/GNOME/releng/-/issues>`__ to do it by
+filing an issue on their ``GNOME/releng`` project.
+
+Rust dependencies
+-----------------
+
+Librsvg's `Cargo.lock` is checked into git because the resolved
+versions of crates that it mentions are the ones that were actually
+used to run the test suite automatically in CI, and are "known good".
+In other words: `keep the results of dependency resolution in version
+control, and update those results manually
+<https://blog.ometer.com/2017/01/10/dear-package-managers-dependency-resolution-results-should-be-in-version-control/>`_.
+
+It is important to keep these dependencies updated; you can do that
+regularly with the ``cargo update`` step listed in the checklist
+above.
+
+`cargo-audit <https://github.com/RustSec/cargo-audit>`__ is very
+useful to scan the list of dependencies for registered vulnerabilities
+in the `RustSec vulnerability database <https://rustsec.org/>`__. Run
+it especially before making a new ``x.y.0`` release.
+
+Sometimes cargo-audit will report crates that are not vulnerable, but
+that are unmaintained. Keep an eye of those; you may want to file bugs
+upstream to see if the crates are really unmaintained or if they should
+be substituted for something else.
+
+Creating a stable release branch
+--------------------------------
+
+- Create a branch named ``librsvg-xx.yy``, e.g. ``librsvg-2.54``
+
+- Make the ``BASE_TAG`` in ``ci/container-builds.yml`` refer to the new
+ ``librsvg-xx.yy`` branch instead of ``main``.
+
+- Push that branch to origin.
+
+- (Branches with that naming scheme are already automatically protected
+ in gitlab’s Settings/Repository/Protected branches.)
+
+- Edit the badge for the stable branch so it points to the new branch:
+ Settings/General/Badges, find the existing badge for the stable
+ branch, click on the edit button that looks like a pencil. Change the
+ **Link** and **Badge image URL**; usually it is enough to just change
+ the version number in both.