Architecture ============ 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/rsvg/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, 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 `_ class 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. 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 `__ 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 `librsvg-c/src `_, 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 ``librsvg-c`` 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 | | librsvg-c/src | +----------------+ | calls | v +-------------------+ | Public Rust API | | rsvg/src/api.rs | +-------------------+ | calls | v +-------------------+ | library internals | | rsvg/src/*.rs | +-------------------+ The test suite -------------- The test suite is documented in `rsvg/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 `Document `_, which is a private type that stores an SVG document loaded in memory. ``SvgHandle`` and its companion `CairoRenderer `_ provide 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. ``Node`` is just a type alias for ``rctree::Node``: an ``rctree`` is an N-ary tree of reference-counted nodes, and `NodeData `_ is the enum that librsvg uses to represent either XML element nodes, or text nodes in the XML. Each XML element causes a new ``Node`` to get created with a ``NodeData::Element(e)``. The ``e`` is an `Element `_, which is a struct that holds an XML element's name and its attributes. It also contains an ``element_data`` field, which is an `ElementData `_: an enum that can represent all the SVG element types. For example, a ```` element from XML gets turned into a ``NodeData::Element(e)`` that has its ``element_data`` set to ``ElementData::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 ```` 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. 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 ```` 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: :: Each ```` 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 `_, 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 `_ 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 `_ struct), 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 ```` 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 `_: .. code:: rust pub enum ParsedProperty { BaselineShift(SpecifiedValue), ClipPath(SpecifiedValue), Color(SpecifiedValue), // etc. } What is `SpecifiedValue `_? It is the parsed value for a CSS property directly as it comes out of the SVG document: .. code:: rust pub enum SpecifiedValue 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 `_ 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 ``