LunaSVG: a small C++ library for rendering SVG, and for poking at it
SVG rendering and manipulation library in C++
At a glance
- What is it?
- LunaSVG is an MIT licensed C++ library for rendering and manipulating SVG, lightweight and portable, with a runtime CSS styling API, hit testing through elementFromPoint, and PNG output. It covers nearly all of the SVG 1.1 and SVG 1.2 Tiny specifications, and it deliberately leaves out animation, filters and scripts because it is built for static rendering.
- Who is it for?
- LunaSVG fits a C++ project that needs to rasterise SVG without pulling in a browser engine or a heavyweight toolkit, especially one that also wants to style or interrogate the document before rendering. It does not fit anything that needs filters, animation or scripting inside the SVG, since those are the three capabilities the project leaves out on purpose.
- Can I use it commercially?
- Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository last received commits 2 days ago.
- What is it written in?
- Mainly C, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on October 23, 2026, and from our analysis. They are not legal advice.
Editorial analysis
A renderer built to stay small
LunaSVG is an SVG rendering library in C++, designed to be lightweight and portable, offering efficient rendering and manipulation of Scalable Vector Graphics files.
Lightweight and portable are the two claims to test, and the way the project expresses them is in the build options rather than in the marketing. The library is consumed as a single target, lunasvg::lunasvg, and it can be installed with CMake or built with Meson, so it drops into an existing project without imposing a build system. The repository is a plain C++ tree with include/, source/, cmake/, examples/ and the two build files, and the last push was on 2026-09-17 with the most recent release, v3.5.0, from 2025-09-14.
There is also a note at the top of the README for people who came looking for something else. Interested in HTML rendering? Explore a related library, PlutoBook, built for paged HTML rendering.
That pointer is worth following, because it tells you where the author's attention goes. LunaSVG is the graphics half, and the same author maintains a separate paged output library for documents. The split is sensible: SVG rasterisation and paged layout are different problems, and folding them into one library would have made the graphics half heavier for no benefit.
The repository also contains a plutovg/ directory, which is the vector graphics engine underneath, and the example tool is a small command line converter at examples/svg2png.cpp, which is the quickest way to see what the library does with a file you already have.
Ten lines from file to PNG
The basic usage example is deliberately small, and it shows the shape of the whole API. You include the single header, you work in the lunasvg namespace, and then you load a document, render it to a bitmap and write the bitmap out as a PNG.
#include <lunasvg.h>
using namespace lunasvg;
int main()
{
auto document = Document::loadFromFile("tiger.svg");
if(document == nullptr)
return -1;
auto bitmap = document->renderToBitmap();
if(bitmap.isNull())
return -1;
bitmap.writeToPng("tiger.png");
return 0;
}Two things about the error handling are worth copying. A failed load returns a null document rather than throwing, and a failed render returns a null bitmap, and both are checked explicitly. For a library that renders untrusted input, a renderer that returns a null on a malformed file and lets you decide what to do is a much better citizen than one that throws an exception out of a destructor.
There is a second loading entry point for content you already have in memory, Document::loadFromData, which the other two examples use with a string literal.
The PNG output is a method on the bitmap rather than a separate export step, so the pipeline is load, render, write, with no intermediate file and no format negotiation unless you ask for one.
CSS that you apply at runtime
The dynamic styling example is the one that shows this is a manipulation library and not just a rasteriser.
The document is loaded once, from an inline SVG string where the shapes carry class attributes, sky, sun, mountain, cloud and ground, and no colours at all. Then a stylesheet is applied to the document, the document is rendered, and the result is written to a PNG. The same document is then styled with a second, different stylesheet and rendered again to a second PNG. The two images in the README are the same landscape in summer and in winter.
The API is a single call.
document->applyStyleSheet(kSummerStyle);
document->renderToBitmap().writeToPng("summer.png");
document->applyStyleSheet(kWinterStyle);
document->renderToBitmap().writeToPng("winter.png");The stylesheets are ordinary CSS in a raw string literal, with the same selectors as you would write in a style sheet for the web, so fill, opacity and the rest behave as expected. Note that the second application replaces the first, so the model is apply a sheet rather than accumulate sheets, which keeps the semantics predictable.
The practical use is theming. One SVG asset, several colour schemes, generated at runtime, with no second copy of the artwork to keep in step. For a tool that has to ship with the system, a dark and a light variant, that is a considerably nicer workflow than maintaining two files.
Hit testing, and then editing what you hit
The hit testing example is the one that shows the document is a live object model rather than a parsed buffer.
The example loads an SVG with three shapes, a red rectangle, a blue circle and a green rectangle, renders it as original.png, and then queries a set of points with elementFromPoint. The query points are chosen to be inside the rectangle, at the centre of the circle, inside the other rectangle, and outside all of them, and the expected output is printed in the README.
Element at (30, 30): red-rect
Element at (200, 70): blue-circle
Element at (310, 50): green-rect
No element found at (0, 0)The point that is outside everything returns nothing, and the example handles that case rather than assuming a hit, which is the difference between a hit test you can build on and one that dereferences a null.
What makes it more than a lookup is what happens next. The matched element is modified, with setAttribute for a black stroke, a stroke width, and a skewX transform, and the document is rendered again to modified.png so the two can be compared side by side.
So the loop is: find the element under a point, change it, render. That is the shape of an editor tool, an icon inspector, or a test that verifies a shape is where it should be, and it is a capability that a pure rasteriser does not offer.
Three omissions, and one of them is permanent
The features section is precise about coverage, and its precision is the most useful thing in it.
LunaSVG supports nearly all graphical features outlined in the SVG 1.1 and SVG 1.2 Tiny specifications. The primary exceptions are animation, filters and scripts. As LunaSVG is designed for static rendering, animation is unlikely to be supported in the future. However, support for filters may be added.
The difference in the wording between the three is deliberate. Animation is ruled out by the design, since a static renderer has nowhere to put a timeline, and the project says so rather than leaving you to guess. Filters are a matter of effort and might arrive. Scripts are simply not in the plan, which is the right call for a library that parses files you did not write.
The element list follows, and it is long: a, circle, clipPath, defs, ellipse, g, image, line, linearGradient, marker, mask, path, pattern, polygon, polyline, radialGradient, rect, stop, style, svg, symbol, text, tspan and use.
The practical reading of that list is that the shapes, the gradients, the masks, the patterns, the markers, the symbols and the text runs are all there, and the things that are missing are the ones that need a renderer with a computation model. The one to check before you commit is filters, because a drop shadow or a blur in a design asset is common and is the gap that would show up in production.
Two build systems, two ways to consume it
Installation is documented for both CMake and Meson, and the CMake route is a familiar four commands.
git clone https://github.com/sammycage/lunasvg.git
cd lunasvg
cmake -B build .
cmake --build build
cmake --install buildAfter that, a consuming project finds the package and links the target.
find_package(lunasvg REQUIRED)
# Link LunaSVG to your target
target_link_libraries(your_target_name PRIVATE lunasvg::lunasvg)Or, if you would rather not install it, FetchContent pulls the repository in directly at configure time, with a GIT_TAG you set to the branch or tag you want.
include(FetchContent)
FetchContent_Declare(
lunasvg
GIT_REPOSITORY https://github.com/sammycage/lunasvg.git
GIT_TAG master # Specify the desired branch or tag
)
FetchContent_MakeAvailable(lunasvg)The README notes that you should replace your_target_name with the name of your executable or library target, which is the kind of instruction that exists because people copy the snippet verbatim.
The FetchContent example uses master as its tag, and that is worth a second thought even in a template, because master moves. The project publishes tagged releases, and pinning a tag there is what makes the build reproducible. The Meson route exists for projects that already use it, with meson.build and meson_options.txt in the tree, and the build options themselves are configured with -D flags, which is where the project's own tuning knobs live.
Editorial conclusion
LunaSVG fits a C++ project that needs to rasterise SVG without pulling in a browser engine or a heavyweight toolkit, especially one that also wants to style or interrogate the document before rendering. It does not fit anything that needs filters, animation or scripting inside the SVG, since those are the three capabilities the project leaves out on purpose. Verify three things before you adopt it: that the SVG features you rely on are in the element list, because a missing element is a silent difference rather than an error, which serialisation features you need beyond the ones the README names, and the build options, since the project exposes several -D flags and the defaults are aimed at a small static library rather than at a feature complete renderer.
Frequently asked questions
What is LunaSVG and what does it do?
It is an SVG rendering library in C++, designed to be lightweight and portable, offering efficient rendering and manipulation of Scalable Vector Graphics files. You load a document from a file or from data, optionally style it, render it to a bitmap and write the result as a PNG.
How do I use LunaSVG to convert an SVG file to a PNG?
Include lunasvg.h, load the document with Document::loadFromFile, render it with document->renderToBitmap and write it out with bitmap.writeToPng. A null document and a null bitmap are both possible and should be checked, since a failed load returns a null document rather than throwing.
Can LunaSVG apply CSS to an SVG?
Yes. The document has an applyStyleSheet method that takes ordinary CSS, and the README's example styles one inline SVG document with a summer sheet and then a winter sheet, rendering a PNG after each. The second application replaces the first rather than adding to it.
What SVG features does LunaSVG not support?
The primary exceptions are animation, filters and scripts. Animation is unlikely to be supported in the future because the library is designed for static rendering, while support for filters may be added. Everything else in the SVG 1.1 and SVG 1.2 Tiny specifications is described as supported, including gradients, masks, patterns, markers, symbols and text runs.
How do I build and link LunaSVG in a CMake project?
Build and install it with cmake -B build ., cmake --build build and cmake --install build, then use find_package(lunasvg REQUIRED) and target_link_libraries(your_target_name PRIVATE lunasvg::lunasvg). Alternatively include(FetchContent) with a GIT_REPOSITORY and GIT_TAG, and call FetchContent_MakeAvailable. There is also a Meson build, with meson.build and meson_options.txt in the repository.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/sammycage-lunasvg)Was this useful?