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 ``