Donner is an SVG engine. Instead of treating SVG files as static images, it treats them as dynamic scenes that can be modified, animated, or transformed.
Many SVG libraries load an SVG and render an image as output. Browsers work differently: SVG in a browser is a graphical counterpart to HTML, and HTML is not static. It can be queried, modified, and styled.
Donner intends to provide browser-level functionality as a standalone C++ library:
Donner ships with two rendering backends behind a single Renderer facade: tiny-skia (the library and CLI default, a compact software rasterizer vendored from Rust's tiny-skia library, with no external dependencies) and Geode (a GPU backend that renders vector paths with Slug, a rendering algorithm that computes curve coverage analytically on the GPU; the editor's default renderer, gated behind --config=geode for the library, so default builds do not pull in the GPU runtime).
Donner consists of a core library and a renderer, which are built with separation of concerns to enable future integration with other rendering libraries.
Each component of Donner is designed to be used in isolation, with minimal dependencies on the other components. This keeps each component testable on its own and usable from other systems.
The parser suite consists of parsers in three layers:
| Namespace | Description |
|---|---|
| donner::parser | Parsers for shared data types such as NumberParser and LengthParser |
| donner::css::parser | Parsers for various CSS data types, such as the top-level StylesheetParser and SelectorParser, as well as internal details such as ColorParser. These are wrapped in the donner::css::CSS convenience API. Using these lower-level APIs allows for finer-grained control and error propagation. |
| donner::svg::parser | Parsers for the SVG XML format, SVGParser, as well as individual parsers for SVG components, such as PathParser and TransformParser. |
| donner::xml | XMLParser and an XML document tree represented by XMLDocument and XMLNode. |
A CSS3 toolkit for parsing CSS stylesheets, style strings, and selectors, and for matching those selectors against a document tree.
See Using the CSS API for more details.
The CSS layer parses stylesheets into lists of SelectorRule objects, which contains:
At this layer the style information has no semantics: it holds raw parsed data along with the machinery to cascade it onto the document tree. The Styling component consumes that raw data and parses the values into meaningful styling information.
Consumes information from the CSS parser and implements the SVG style model. This includes:
StyleSystem is the top-level component that manages the styling of the document tree.
Style information is held on each entity inside donner::svg::components::StyleComponent. During the rendering process, CSS cascading and inheritance are performed and cached on donner::svg::components::ComputedStyleComponent.
Donner provides a high-level API for interacting with the SVG document model. It aims to be simple to use while still exposing the whole underlying document model.
The API focuses on:
See Donner API for more details.
The Document Model is built on top of the EnTT Entity-Component-System (ECS), which is used to build a tree of entities, components, and systems that represent the SVG document. It is designed for efficient modification and rendering of SVG documents.
The public DOM facade (SVGDocument, SVGElement, and concrete SVG element types) is lifetime-aware. Public element handles retain detached nodes so a caller can remove, inspect, edit, and reinsert an element without use-after-free or leaked detached subtrees. Detached entities are collected after they are no longer attached to the document, retained by public handles, or needed by an active snapshot/observer epoch.
SVGDocument defaults to ThreadingMode::SingleThreaded. Multi-threaded DOM mutation is opt-in through ThreadingMode::ConcurrentDom, which serializes writes with document access guards while allowing public facade APIs to be used from multiple threads. Direct ECS access remains an advanced escape hatch and must be scoped through document read/write access in concurrent mode.
The rendering backend traverses the internal ECS document model and instantiates rendering components such as RenderingInstanceComponent, which are then consumed by the selected renderer (tiny-skia by default, or Geode with --config=geode).
Rendering components are attached to the same entities as the document model components, which keeps the document model and the rendering backend in sync. When the document model is modified, the associated rendering components are invalidated.
For normal SVGDocument rendering, RendererDriver first captures a RenderSnapshot: an immutable renderer command stream with snapshot-owned resource references. Backend replay uses the snapshot rather than the live ECS registry, so DOM mutations made during backend drawing are serialized for the next captured frame.
The //donner/base library contains common utility code used by the other libraries. This includes:
This library also contains common parsers such as NumberParser, which can parse a string into a number.
The base library has minimal dependencies and the types within it may be suitable for other libraries; however, it is not publicly exported.
Donner has a multi-level testing strategy and aims to make the library production-grade and suitable for parsing untrusted inputs (eventually).
All components should be unit-tested, and test coverage is measured using Codecov.
As SVG is a visual format, image comparison tests are used to validate the rendered output.
These come in three flavors:
Low-level "unittest" image comparison using ASCII art.
High-level "integration" image comparison using the pixelmatch-cpp17 library.
bazel run //donner/svg/renderer/tests:renderer_tests
Using the external Resvg Test Suite to validate against a large corpus of SVG files and comparing against the reference output with pixelmatch.
bazel run //donner/svg/renderer/tests:resvg_test_suite
Since SVG and CSS require a large collection of parsers, fuzz tests are individually written for each parser.