[librsvg: 2/9] Move the architecture document to the development guide




commit 18b12439aa963b719cea45a4071e9950f4c24d9d
Author: Federico Mena Quintero <federico gnome org>
Date:   Thu Aug 25 15:02:39 2022 -0500

    Move the architecture document to the development guide
    
    Part-of: <https://gitlab.gnome.org/GNOME/librsvg/-/merge_requests/735>

 ARCHITECTURE.md             | 407 -------------------------------------
 devel-docs/architecture.rst | 474 ++++++++++++++++++++++++++++++++++++++++++++
 devel-docs/index.rst        |   8 +-
 3 files changed, 477 insertions(+), 412 deletions(-)
---
diff --git a/devel-docs/architecture.rst b/devel-docs/architecture.rst
new file mode 100644
index 000000000..c8b7d4e15
--- /dev/null
+++ b/devel-docs/architecture.rst
@@ -0,0 +1,474 @@
+Architecture of librsvg
+=======================
+
+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/librsvg/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 <https://levien.com/libart/>`__ as a rendering library; this was
+GNOME’s first antialiased renderer with alpha compositing. Later, the
+renderer was replaced with `Cairo <https://www.cairographics.org/>`__.
+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
+<https://gnome.pages.gitlab.gnome.org/librsvg/internals/librsvg/c_api/handle/struct.RsvgHandle.html>`_
+class defines most of the entry points into the library. Through
+`GObject Introspection <https://gi.readthedocs.io/en/latest/>`__, 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 <https://gi.readthedocs.io/en/latest/>`__.
+
+The Rust API is a bit more lax in its API stability, but we try to stick
+to `semantic versioning <https://semver.org/>`__ as is common in Rust.
+
+The public Rust API is implemented in `src/api.rs
+<https://gitlab.gnome.org/GNOME/librsvg/-/blob/main/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/
+<https://gitlab.gnome.org/GNOME/librsvg/-/tree/main/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/
+<https://gitlab.gnome.org/GNOME/librsvg/-/tree/main/include/librsvg>`_;
+maybe in the future they will be generated automatically with
+`cbindgen <https://github.com/eqrion/cbindgen/blob/master/docs.md>`__.
+
+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.
+
+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  |
+   |    src/c_api   |
+   +----------------+
+           |
+         calls
+           |
+           v
+   +-------------------+
+   |  Public Rust API  |
+   |     src/api.rs    |
+   +-------------------+
+           |
+         calls
+           |
+           v
+   +-------------------+
+   | library internals |
+   |      src/*.rs     |
+   +-------------------+
+
+The test suite
+--------------
+
+The test suite is documented in ``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
+<https://gnome.pages.gitlab.gnome.org/librsvg/internals/librsvg/api/struct.SvgHandle.html>`_
+from a `Loader
+<https://gnome.pages.gitlab.gnome.org/librsvg/internals/librsvg/api/struct.Loader.html>`_;
+both of those are public types. Internally the ``SvgHandle`` is just a
+wrapper around a `Handle
+<https://gnome.pages.gitlab.gnome.org/librsvg/internals/librsvg/handle/struct.Handle.html>`_,
+which is a private type. ``Handle`` represents an SVG document loaded
+in memory; it acts as a wrapper around a `Document
+<https://gnome.pages.gitlab.gnome.org/librsvg/internals/librsvg/document/struct.Document.html>`_,
+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.
+
+A ``Document`` gets created by loading XML from a stream, into a tree
+of `Node
+<https://gnome.pages.gitlab.gnome.org/librsvg/internals/librsvg/node/type.Node.html>`_
+structures. This is similar to a web browser’s DOM tree.
+
+Each XML element causes a new ``Node`` to get created with an `Element
+<https://gnome.pages.gitlab.gnome.org/librsvg/internals/librsvg/element/enum.Element.html>`_
+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)``.
+
+When an ``Element`` is created from its corresponding XML, its
+`Attributes
+<https://gnome.pages.gitlab.gnome.org/librsvg/internals/librsvg/xml/attributes/struct.Attributes.html>`_
+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
+<https://gnome.pages.gitlab.gnome.org/librsvg/internals/librsvg/element/trait.SetAttributes.html#method.set_attributes>`_
+method of each particular element type (in that case,
+`Path::set_attributes
+<https://gnome.pages.gitlab.gnome.org/librsvg/internals/librsvg/shapes/struct.Path.html#method.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
+<https://gnome.pages.gitlab.gnome.org/librsvg/internals/librsvg/properties/struct.SpecifiedValues.html>`_
+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 ``<svg>`` element.
+
+After that, the CSS cascade step gets run.
+
+The CSS cascade
+~~~~~~~~~~~~~~~
+
+Each ``Element`` has a `SpecifiedValues
+<https://gnome.pages.gitlab.gnome.org/librsvg/internals/librsvg/properties/struct.SpecifiedValues.html>`_,
+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
+<https://gnome.pages.gitlab.gnome.org/librsvg/internals/librsvg/macro.make_property.html>`_
+macro.
+
+The cascading step takes each element’s ``SpecifiedValues`` and
+composes it by CSS inheritance onto a `ComputedValues
+<https://gnome.pages.gitlab.gnome.org/librsvg/internals/librsvg/properties/struct.ComputedValues.html>`_,
+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
+<https://gitlab.gnome.org/GNOME/libxml2/-/wikis/home>`_ at the time of
+this writing) to do the first-stage parsing of the SVG
+document. `XmlState
+<https://gnome.pages.gitlab.gnome.org/librsvg/internals/librsvg/xml/struct.XmlState.html>`_
+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
+<https://gnome.pages.gitlab.gnome.org/librsvg/internals/librsvg/xml/struct.XmlState.html#method.start_element>`_;
+this is responsible for creating new ``Node`` structures in the tree,
+within the `DocumentBuilder
+<https://gnome.pages.gitlab.gnome.org/librsvg/internals/librsvg/document/struct.DocumentBuilder.html>`_
+being built.
+
+Nodes are either SVG elements (the `Element
+<https://gnome.pages.gitlab.gnome.org/librsvg/internals/librsvg/element/enum.Element.html>`_
+enum), or text data inside elements (the `Chars
+<https://gnome.pages.gitlab.gnome.org/librsvg/internals/librsvg/text/struct.Chars.html>`_
+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
+<https://gnome.pages.gitlab.gnome.org/librsvg/internals/librsvg/element/trait.SetAttributes.html#method.set_attributes>`_
+method. Each attribute is just a key/value pair; for example, the
+``<rect width="5px">`` 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 <https://crates.io/crates/cssparser>`_
+crate as a CSS tokenizer, and `selectors
+<https://crates.io/crates/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
+<https://docs.rs/cssparser/0.29.6/cssparser/trait.DeclarationParser.html>`_
+trait. Its `parse_value
+<https://docs.rs/cssparser/0.29.6/cssparser/trait.DeclarationParser.html#tymethod.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
+<https://gnome.pages.gitlab.gnome.org/librsvg/internals/librsvg/css/struct.Declaration.html>`_
+struct for this.
+
+The core of parsing CSS is the ``parse_value`` function, which returns
+a `ParsedProperty
+<https://gnome.pages.gitlab.gnome.org/librsvg/internals/librsvg/properties/enum.ParsedProperty.html>`_:
+
+.. code:: rust
+
+   pub enum ParsedProperty {
+       BaselineShift(SpecifiedValue<BaselineShift>),
+       ClipPath(SpecifiedValue<ClipPath>),
+       Color(SpecifiedValue<Color>),
+       // etc.
+   }
+
+What is `SpecifiedValue
+<https://gnome.pages.gitlab.gnome.org/librsvg/internals/librsvg/properties/enum.SpecifiedValue.html>`_?
+It is the parsed value for a CSS property directly as it comes out of
+the SVG document:
+
+.. code:: rust
+
+   pub enum SpecifiedValue<T>
+   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
+  <https://gnome.pages.gitlab.gnome.org/librsvg/internals/librsvg/property_defs/struct.Opacity.html>`_
+  property, which is a newtype around an internal `UnitInterval
+  <https://gnome.pages.gitlab.gnome.org/librsvg/internals/librsvg/unit_interval/struct.UnitInterval.html>`_
+  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
+``<style>`` element, gets parsed into a `Stylesheet
+<https://gnome.pages.gitlab.gnome.org/librsvg/internals/librsvg/css/struct.Stylesheet.html>`_
+struct. A stylesheet has a list of rules, where each rule is the CSS
+selectors defined for it, and the style declarations that should be
+applied for the ``Node``\ s that match the selectors. For example, in
+a little stylesheet like this:
+
+.. code:: xml
+
+   <style type="text/css">
+     rect, #some_id {
+       fill: blue;
+       stroke-width: 5px;
+     }
+   </style>
+
+This stylesheet has a single rule. The rule has a selector list with two
+selectors (``rect`` and ``#some_id``) and two style declarations
+(``fill: blue`` and ``stroke-width: 5px``).
+
+After parsing is done, there is a **cascading stage** where librsvg
+walks the tree of nodes, and for each node it finds the CSS rules that
+should be applied to it.
+
+Rendering
+---------
+
+The rendering process starts at the `draw_tree()
+<https://gnome.pages.gitlab.gnome.org/librsvg/internals/librsvg/drawing_ctx/fn.draw_tree.html>`_
+function. This sets up a `DrawingCtx
+<https://gnome.pages.gitlab.gnome.org/librsvg/internals/librsvg/drawing_ctx/struct.DrawingCtx.html>`_,
+which carries around all the mutable state during rendering.
+
+Rendering is a recursive process, which goes back and forth between
+the utility functions in ``DrawingCtx`` and the `draw
+<https://gnome.pages.gitlab.gnome.org/librsvg/internals/librsvg/element/trait.Draw.html#method.draw>`_
+method in elements.
+
+The main job of ``DrawingCtx`` is to deal with the SVG drawing model.
+Each element renders itself independently, and its result gets modified
+before getting composited onto the final image:
+
+1. Render an element to a temporary surface (example: stroke and fill a
+   path).
+2. Apply filter effects (blur, color mapping, etc.).
+3. Apply clipping paths.
+4. Apply masks.
+5. Composite the result onto the final image.
+
+The temporary result from the last step also gets put in a stack; this
+is because filter effects sometimes need to look at the currently-drawn
+background to apply further filtering to it.
+
+You’ll see that most of the rendering-related functions return a
+``Result<BoundingBox, RenderingError>``. Some SVG features require
+knowing the bounding box of the object that is being rendered; for
+historical reasons this bounding box is computed as part of the
+rendering process in librsvg. When computing a subtree’s bounding box,
+the bounding boxes from the leaves get aggregated up to the root of
+the subtree. Each node in the tree has its own coordinate system;
+`BoundingBox
+<https://gnome.pages.gitlab.gnome.org/librsvg/internals/librsvg/bbox/struct.BoundingBox.html>`_
+is able to transform coordinate systems to get a bounding box that is
+meaningful with respect to the root’s transform.
+
+Comparing floating-point numbers
+--------------------------------
+
+Librsvg sometimes needs to compute things like “are these points equal?”
+or “did this computed result equal this test reference number?”.
+
+We use ``f64`` numbers in Rust for all computations on real numbers.
+Floating-point numbers cannot be 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
+<https://gnome.pages.gitlab.gnome.org/librsvg/internals/librsvg/float_eq_cairo/trait.ApproxEqCairo.html>`_
+trait.  You can use it like this:
+
+.. code:: rust
+
+   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
+
+Some interesting parts of the code
+----------------------------------
+
+- Are you adding support for a CSS property? Look at the 
+  :doc:`adding_a_property` tutorial; look in the `property_defs
+  <https://gnome.pages.gitlab.gnome.org/librsvg/internals/librsvg/property_defs/index.html>`_
+  and `properties
+  <https://gnome.pages.gitlab.gnome.org/librsvg/internals/librsvg/properties/index.html>`_
+  modules. ``property_defs`` defines most of the CSS properties that
+  librsvg supports, and ``properties`` actually puts all those
+  properties in the ``SpecifiedValues`` and ``ComputedValues``
+  structs.
+
+- The `Handle
+  <https://gnome.pages.gitlab.gnome.org/librsvg/internals/librsvg/handle/struct.Handle.html>`_
+  struct provides the primitives to implement the public APIs, such as
+  loading an SVG file and rendering it.
+
+- The `DrawingCtx
+  <https://gnome.pages.gitlab.gnome.org/librsvg/internals/librsvg/drawing_ctx/struct.DrawingCtx.html>`_
+  struct is active while an SVG handle is being drawn. It has all the
+  mutable state related to the drawing process, such as the stack of
+  temporary rendered surfaces, and the viewport stack.
+
+- The `Document
+  <https://gnome.pages.gitlab.gnome.org/librsvg/internals/librsvg/document/struct.Document.html>`_
+  struct represents a loaded SVG document. It holds the tree of `Node
+  <https://gnome.pages.gitlab.gnome.org/librsvg/internals/librsvg/node/type.Node.html>`_
+  structs, some of which contain `Element
+  <https://gnome.pages.gitlab.gnome.org/librsvg/internals/librsvg/element/enum.Element.html>`_
+  and some other contain `Chars
+  <https://gnome.pages.gitlab.gnome.org/librsvg/internals/librsvg/text/struct.Chars.html>`_
+  for text data in the XML. A ``Document`` also contains a mapping of
+  ``id`` attributes to the corresponding element nodes.
+
+- The `xml
+  <https://gnome.pages.gitlab.gnome.org/librsvg/internals/librsvg/xml/index.html>`_
+  module receives events from an XML parser, and builds a
+  ``Document`` tree.
+
+- The `css
+  <https://gnome.pages.gitlab.gnome.org/librsvg/internals/librsvg/css/index.html>`_
+  module has the high-level machinery for parsing CSS and representing
+  parsed stylesheets. The low-level parsers for individual properties
+  are in `property_defs 
<https://gnome.pages.gitlab.gnome.org/librsvg/internals/librsvg/property_defs/index.html>`_ and
+  `font_props <https://gnome.pages.gitlab.gnome.org/librsvg/internals/librsvg/font_props/index.html>`_.
diff --git a/devel-docs/index.rst b/devel-docs/index.rst
index b053fcce8..a5b2af8c2 100644
--- a/devel-docs/index.rst
+++ b/devel-docs/index.rst
@@ -5,6 +5,7 @@ Development guide for librsvg
    product
    roadmap
    devel_environment
+   architecture
    adding_a_property
    memory_leaks
    contributing
@@ -42,19 +43,16 @@ FIXME: link to doc with stuff from CONTRIBUTING.md's "Hacking on librsvg"
 
 Add basic info on cloning the repo, getting a gitlab account, forking.
 
-Development roadmap.
-
 Understand the code
 -------------------
 
-FIXME: Overview of the source tree.
-
 Tour of the code - load a file, render it.
 
 Test suite - move tests/readme here?
 
-Link to the internals documentation.
+- `Documentation of the library's internals 
<https://gnome.pages.gitlab.gnome.org/librsvg/internals/librsvg/index.html>`_
 
+- :doc:`architecture`
 - :doc:`adding_a_property`
 - :doc:`memory_leaks`
 


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]