Overview
Donner provides several standalone APIs for core functionality which can be used in any project.
Each library has minimal dependencies and can be used in external projects. For example, the CSS API may be used to add CSS support to your project using an adapter that satisfies the donner::ElementLike concept.
flowchart TD
SVG(SVG)
CSS(CSS)
Rendering(SVG Rendering)
SVGParsing(SVG Parsing)
XML(XML Parsing)
SVG --> CSS
Rendering --> SVG
SVGParsing --> SVG
SVGParsing --> XML
Principles
Minimal memory allocations
- API invocations do not incur memory allocations unless required, and internally memory allocations are kept to a minimum. Supporting constructs such as donner::RcString and donner::SmallVector are used to reduce the number of allocations.
Value types
- Implementation details about memory allocation are hidden, and the API is designed to be used with value types. For example, donner::svg::SVGElement is can be copied and passed by value.
Error handling
C++20 design patterns
String handling
- donner::RcString is used to store strings. This is a reference counted string, which allows cheap copy and move operations. RcString implements the small-string optimization and does not allocate memory for strings shorter than 31 characters (on 64-bit platforms).
- Use std::string_view for APIs that only need to read the input string and not store it.
- For APIs that want to store the string, use donner::RcStringOrRef which can hold either a std::string_view or a donner::RcString, and is used to avoid unnecessary copies. This allows string literals to be used at the API surface while still allowing RcString references to be transferred.
Limitations
- Donner is NOT thread-safe, the caller must ensure they are not accessing the same document from multiple threads.
Details
Namespace hierarchy
Parsing an SVG
To parse an SVG document, use the SVGParser class:
donner::ParseResult contains either the document or an error, which can be checked with hasError() and error():
std::cerr <<
"Parse Error " << maybeResult.
error() <<
"\n";
std::abort();
}
donner::svg::parser::SVGParser::ParseSVG accepts a string containing SVG data.
std::ifstream file(argv[1]);
if (!file) {
std::cerr << "Could not open file " << argv[1] << "\n";
return 1;
}
std::string fileData;
file.seekg(0, std::ios::end);
const size_t fileLength = file.tellg();
file.seekg(0);
fileData.resize(fileLength);
file.read(fileData.data(), static_cast<std::streamsize>(fileLength));
Store the resulting donner::svg::SVGDocument to keep the document in-memory, and use it to inspect or modify the document.
For example, to get the donner::svg::SVGPathElement for a "<path>" element:
std::optional<donner::svg::SVGElement> maybePath = document.
querySelector(
"path");
Using the DOM
donner::svg::SVGElement implements a DOM-like API for querying and modifying the SVG document.
The document tree is traversable with firstChild(), lastChild(), nextSibling(), and previousSibling(). The element's tag name can be retrieved with tagName().
Example iterating over children:
for (std::optional<SVGElement> child = element.firstChild(); child;
child = child->nextSibling()) {
std::cout << "Child tag name: " << child->tagName() << "\n";
}
Every SVG element has its own DOM type:
SVGElement element = ...;
if (element.isa<SVGCircleElement>()) {
SVGCircleElement circle = element.cast<SVGCircleElement>();
}
Full list of DOM types
Manipulating the tree
To add a child to an element, use one of these methods:
To remove an element from the tree:
NOTE: The remove() method will remove the element from the tree, but the underlying data storage is not currently cleaned up.
To create an element, use the element-specific Create method:
Rendering
To render an SVG document, use the donner::svg::RendererSkia class.
std::cout <<
"Final size: " << renderer.
width() <<
"x" << renderer.
height() <<
"\n";
if (renderer.
save(
"output.png")) {
std::cout << "Saved to file: " << std::filesystem::absolute("output.png") << "\n";
return 0;
} else {
std::cerr << "Failed to save to file: " << std::filesystem::absolute("output.png") << "\n";
return 1;
}
RendererSkia is prototype-level, and has limited documentation and may be subject to change.
The output size is determined by donner::svg::SVGDocument, which can either be detected from the file itself or overridden with SVGDocument APIs:
Using the CSS API
The CSS API is used internally within the SVG library, but can be used standalone.
To parse CSS stylesheets, style strings, or selectors, use the donner::css::CSS wrapper API.
Parsing a stylesheet
Matching selectors
- To use Selectors, implement the donner::ElementLike concept for your own element type. This allows the CSS library to traverse your document tree.
Selectors are available within a parsed stylesheet, or can be parsed from a string using donner::css::CSS::ParseSelector(std::string_view).
std::cout << "Parsed selector: " << *selector << "\n";
std::cout << "Matched " << path1 << " - " << match.specificity << "\n";
} else {
std::cout << "No match\n";
}
} else {
std::cerr << "Failed to parse selector\n";
std::abort();
}
Use Selector::matches(const ElementLike& target) to see if a selector matches against a specific element. This can be repeatedly applied to all elements of a document to find all matches.
for (
const auto& rule : stylesheet.
rules()) {
bool foundMatch = false;
std::cout << "Matching " << rule.selector << ":\n";
for (const auto& element : {group, path1, path2}) {
foundMatch = true;
std::cout << " - Matched " << element << " - " << match.specificity << "\n";
}
}
if (foundMatch) {
std::cout << "\n";
} else {
std::cout << " - No match\n\n";
}
}