9.34 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
Architecture of librsvg

This document describes the architecture of librsvg, and future plans
for it.

# 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, instead of first creating a DOM.
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 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.  The plan is to leave
the C API/ABI intact, but to have as much of the internals as possible
implemented in Rust.  This way we can use a memory-safe, modern
language, but retain the traditional API/ABI.

33 34 35 36 37 38 39 40 41 42 43 44
# RsvgHandle

The `RsvgHandle` object is the only GObject that librsvg exposes in
the public API.

During its lifetime, an `RsvgHandle` can be in either of two stages:

* Loading - the caller feeds the handle with SVG data.  The SVG XML is
  parsed into a DOM-like structure.

* Rendering - the SVG is finished loading.  The caller can then render
  the image as many times as it wants to Cairo contexts.

46 47 48 49 50 51 52
## Loading SVG data

The following happens in `rsvg_handle_read_stream_sync()`:

* The function peeks the first bytes of the stream to see if it is
  compressed with gzip.  In that case, it plugs a
  `g_zlib_decompressor_new()` to the use-supplied stream.

54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71
* The function creates an XML parser for the stream.  The SAX parser's
  callbacks are functions that create DOM-like objects within the
  `RsvgHandle`.  The most important callback is
  `rsvg_start_element()`, and the one that actually creates our
  element implementations is `rsvg_standard_element_start()`.

## Translating SVG data into Nodes

`RsvgHandlePrivate` has a `treebase` field, which is the root of the
DOM tree.  Each node in the tree is an `RsvgNode` object.

`rsvg_standard_element_start()` gets called from the XML parsing
machinery; it takes an SVG element name like "`rect`" or "`filter`"
and a key/value list of attributes within the element.  It then creates the
appropriate subclass of an `RsvgNode` object, hooks the node to the
DOM tree, and tells the node to set its attributes from the key/value

72 73 74 75
*Through this document we may use **node** and **element**
interchangeably:* a node is the struct we use to represent an SVG/XML

76 77 78 79 80 81 82 83 84 85 86 87 88
While a node sets its key/value pairs in its `set_atts()` method, it
may encounter an invalid value, for example, a negative width where
only nonnegative ones are allowed.  In this case the element may
decide to set itself to be "in error" via the `node.set_error()`
method.  If a node is in error, the node's children will get parsed as
usual, but the node 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.

89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107
## Element attributes and specified/computed values

Some HTML or SVG engines like Gecko / Servo make a very clear
distinction between "specified values" and "computed values" for
element attributes.  Currently librsvg doesn't have a clear

Unspecified attributes cause librsvg to use defaults, some as per the
spec, and some (erroneously) as values that seemed to make sense at
the time of implementation.  Please help us find these and make them

For specified attributes, sometimes the set_atts() methods will
validate the values and resolve them to their final computed form, and
sometimes they will just store them as they come in the SVG data.  The
computed or actually used values will be generated at rendering time.

# Rendering

The public `rsvg_handle_render_cairo()` and `rsvg_handle_render_cairo_sub()`
109 110 111 112 113 114 115 116 117 118
functions initiate a rendering process; the first function just calls
the second one with the root element of the SVG.

This second function creates `RsvgDrawingCtx`, which contains the
rendering state.  This structure gets passed around into all the
rendering functions.  It carries the vtable for rendering in the
`render` field, the CSS state for the node being rendered in the
`state` field, and other values which are changed as rendering

119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136
## CSS cascading

For historical reasons, librsvg does the CSS cascade *and* rendering
in a single traversal of the tree of nodes.  This is somewhat awkward,
and in the future we hope to move to a Servo-like model where CSS is
cascaded and sizes are resolved before rendering.

Rendering starts at `rsvg_handle_render_cairo_sub()`.  It calls
`rsvg_cairo_new_drawing_ctx()`, which creates an `RsvgDrawingCtx` with
a default `state`:  this is the default CSS state per
`rsvg_state_init()` (in reality that state carries an affine
transformation already set up for this rendering pass; we can ignore
it for now).

Then, `rsvg_handle_render_cairo_sub()` starts the recursive drawing
process by calling
`rsvg_drawing_ctx_draw_node_from_stack()`, starting at the tree root
(`handle->priv->treebase`).  In turn, that function creates a
137 138 139
temporary `state` struct by calling `rsvg_drawing_ctx_state_push()`,
calls `rsvg_node_draw()` on the current node, and destroys the temporary
`state` struct with `rsvg_drawing_ctx_state_pop()`.
140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158

Each node draws itself in the following way:

* It resolves relative lengths from the size of current viewport by calling
  `length.normalize()` on each length value.  The size of the current
  viewport is maintained as a stack of `RsvgBbox` structures (it
  stands for "bounding box").

* It calls drawing_ctx::state_reinherit_top() with the node's own
  `state` field.  This causes the temporary state in the `draw_ctx` to
  obtain the final cascaded CSS values.

* It calls the low-level rendering functions like
  `drawing_ctx::render_path_builder()` or
  `drawing_ctx::render_pango_layout()`.  These functions translate the
  values from the `state` in the `draw_ctx` into Cairo values, they
  configure the `cairo::Context`, and call actual Cairo functions to
  draw paths/text/etc.

159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186
### What about referenced nodes which have a different cascade?

Sometimes, though, the node being considered in the recursive
traversal has to refer to some other node.  For example, a shape like
a `rect`angle may reference a `linearGradient` for its `fill`
attribute.  In this case, the `rect`'s cascaded values will contain
things like its fill opacity, or its stroke width and color.  However,
the `linearGradient` has cascaded values that come from its own place
in the element tree, not from the `rect` that references it (multiple
objects may reference the same gradient; in each case, the gradient
has its own cascade derived only from its ancestors).

In such cases, the code that needs to resolve the referenced node's
CSS properties needs to do this:

* Create a temporary `state` with `rsvg_state_new()`, or grab the
  temporary `draw_ctx.get_state()`.
* Call `state::reconstruct(state, node)`.  This will walk the tree
  from the root directly down to the node, reconstructing the CSS
  cascade state for *that* node.
This is a rather ugly special case for elements that are referenced
outside the "normal" recursion used for rendering.  We hope to move to
a model where all CSS properties are cascaded first, then bounding
boxes are propagated, and finally all rendering can happen in a single
pass in a fully-resolved tree.

187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227
# Comparing floating-point numbers

Librsvg sometimes needs to compute things like "are these points
equal?" or "did this computed result equal this test reference

We use `f64` numbers in Rust, and `double` numbers in C, for all
computations on real numbers.  These types cannot be simply 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

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

As of March 2018 this is not implemented for the C code; the hope is
that we will move all that code to Rust and we'll be able to do this
kind of approximate comparisons there.

[ApproxEqCairo]: rsvg_internals/src/