[librsvg: 1/9] Start updating ARCHITECTURE.md




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]