Donner uses a data-oriented Entity Component System (ECS) design pattern to store and manipulate the SVG document. This is a common pattern in game development, since it optimizes for performance through cache-friendly data structures and parallelism.
Entities are the primary data structure in ECS. They are a unique identifier for a set of components. In Donner, entities are represented by a 32-bit unsigned integer, using a typedef for entt::entity.
An entity is created by calling registry.create() without any components. Here is how the Entity is created for SVG elements.
Components are the data associated with an entity, stored so that both lookup and iteration are fast.
Each component may only have one instance per entity, and each entity has an independent list of components. For example, a component can be added to an entity to tag it or to attach data.
For a concrete example, here is the ViewBoxComponent used by SVG elements.
The tree structure uses the same mechanism: each entity is given a TreeComponent holding Entity references to its parent and children.
Systems are singletons within the Registry that hold global state and operate on the components. Donner calls these contexts, for example SVGDocumentContext and RenderingContext.
There are also systems that are stateless, such as LayoutSystem, which is instantiated on-demand to manipulate the ECS state.
For example:
A list of systems can be found at ECS Systems.
In Donner, each Entity corresponds to a single SVGElement.
Donner groups its components into categories, based on transformations that occur as the document is parsed and rendered.
The rendering pipeline performs a series of operations:
graph LR
User(User-provided tree)
Style(Style propagation)
Computed(Computed tree)
Render(Render tree)
User-->Style
Style-->Computed
Computed-->Render
Each operation saves its state through a set of components. For example, during Style propagation in StyleSystem::computeAllStyles, these transformations occur:
| User component | Styled component |
|---|---|
| StyleComponent | ComputedStyleComponent |
| SizedElementComponent | ComputedSizedElementComponent |
While StyleComponent stores user-provided style information, it does not apply the inherited properties or the CSS stylesheet. ComputedStyleComponent stores the final style information after applying all the rules.
This continues for the Computed tree and Render tree.
PathComponent and shape-specific components such as RectComponent store the user-provided shape information. These are transformed at the Computed tree stage into ComputedPathComponent, which contains the Path for the shape.
This step must happen after the styling phase to ensure SVG2 presentation attributes are properly propagated.
For example, shape properties can be specified entirely in CSS:
This is a new feature in SVG2 which strongly influences this pipeline.
The details above are simplified; see RenderingContext for the source of truth and the full list of operations.
The final transformation instantiates the render tree, within RenderingContext::instantiateRenderTree.
This step traverses the tree and produces a sorted list of RenderingInstanceComponent corresponding to the draw order.
This list is then processed by the selected backend (donner::svg::Renderer, which resolves to RendererTinySkia by default or RendererGeode under --config=geode) to produce the final output.
On top of the ECS model the user-facing API is implemented as a thin wrapper. For example, SVGPathElement proxies calls to the components layer with minimal logic:
This enables API objects to be zero-cost and stateless. The only object held within these objects is the EntityHandle, which is a lightweight wrapper around the Entity and Registry (~16 bytes with alignment).