[librsvg: 1/9] Start updating ARCHITECTURE.md
- From: Federico Mena Quintero <federico src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [librsvg: 1/9] Start updating ARCHITECTURE.md
- Date: Wed, 7 Apr 2021 00:07:32 +0000 (UTC)
commit 8a20ab2b104941fee9153ed233f76e85862b6663
Author: Federico Mena Quintero <federico gnome org>
Date: Thu Feb 11 18:50:32 2021 -0600
Start updating ARCHITECTURE.md
ARCHITECTURE.md | 187 ++++++++++++++++++++++++++++++++++++++++++++++----------
1 file changed, 155 insertions(+), 32 deletions(-)
---
diff --git a/ARCHITECTURE.md b/ARCHITECTURE.md
index 291e8b55..f0babd4e 100644
--- a/ARCHITECTURE.md
+++ b/ARCHITECTURE.md
@@ -1,11 +1,14 @@
Architecture of librsvg
=======================
-This document describes the architecture of librsvg, and future plans
-for it.
+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 being documented at
-https://gnome.pages.gitlab.gnome.org/librsvg/doc/rsvg_internals/index.html
+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/doc/librsvg/index.html
# A bit of history
@@ -18,47 +21,167 @@ 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
+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.
+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`
+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
+[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.
+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.
+
+[libart]: https://levien.com/libart/
+[Cairo]: https://www.cairographics.org/
+[GObject Introspection]: https://developer.gnome.org/platform-overview/unstable/tech-gobject.html.en
+[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`.
-# RsvgHandle
+# Code flow
-The `RsvgHandle` object is the only GObject that librsvg exposes in
-the public API.
+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.
-During its lifetime, an `RsvgHandle` can be in either of two stages:
+## Loading an SVG document
-* Loading - the caller feeds the handle with SVG data. The SVG XML is
- parsed into a DOM-like structure.
+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.
-* Rendering - the SVG is finished loading. The caller can then render
- the image as many times as it wants to Cairo contexts.
+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.
-## Loading SVG data
+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)`.
-The following happens in `rsvg_handle_read_stream_sync()`:
+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`).
-* 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.
+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.
-* 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()`.
+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
+
+FIXME
## Translating SVG data into Nodes
@@ -176,11 +299,11 @@ 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
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]