Show HN: LunaSVG – C++ Library for Rendering and Manipulating SVG Documents

3 months ago 1

This release introduces significant improvements to text rendering and interactivity in LunaSVG. It adds support for CSS generic font families (such as serif, sans-serif, and monospace) and automatic resolution of system-installed fonts, enabling more consistent and portable typography.

Additionally, LunaSVG now includes pointer event hit testing via the new Document::elementFromPoint() API, allowing developers to query which SVG element is located at a specific screen coordinate. These features lay the foundation for building interactive SVG applications with clickable regions, tooltips, and more.

Generic Fonts

LunaSVG now supports CSS generic font families, allowing SVG authors to specify widely recognized font categories such as serif, sans-serif, monospace, cursive, and fantasy. These generic families are automatically resolved to appropriate system-installed fonts at runtime, enabling more portable and visually consistent text rendering across different platforms.

The example below demonstrates how to apply generic fonts using inline styles:

<svg xmlns="http://www.w3.org/2000/svg" width="400" height="300"> <style> .label { font-size: 40px; fill: black; } .serif { font-family: serif; } .sans { font-family: sans-serif; } .mono { font-family: monospace; } .cursive { font-family: cursive; } .fantasy { font-family: fantasy; } </style> <text x="20" y="50" class="label serif">Serif</text> <text x="20" y="100" class="label sans">Sans-serif</text> <text x="20" y="150" class="label mono">Monospace</text> <text x="20" y="200" class="label cursive">Cursive</text> <text x="20" y="250" class="label fantasy">Fantasy</text> </svg>

System Fonts

LunaSVG now supports automatic resolution of system-installed fonts using font family names. This allows SVG content to reference fonts that are already available on the user's system without requiring manual font registration. When multiple font names are provided in the font-family list, LunaSVG will try each in order until a matching font is found, following the standard CSS fallback behavior.

This feature is enabled by default, but it can be turned off at build time if needed. To disable system font loading, define the LUNASVG_DISABLE_LOAD_SYSTEM_FONTS macro when building the library. In CMake-based builds, this can be done by passing -DLUNASVG_DISABLE_LOAD_SYSTEM_FONTS=ON on the command line. In Meson builds, use -Dload-system-fonts=disabled.

The example below demonstrates how to use a prioritized list of common CJK system fonts:

<text x="20" y="40">你好,世界</text> <text x="20" y="80">こんにちは世界</text> <text x="20" y="120">안녕하세요 세계</text> </svg>'><svg xmlns="http://www.w3.org/2000/svg" width="400" height="150"> <style> text { font-family: "Noto Sans CJK SC", "PingFang SC", "SimHei", sans-serif; font-size: 24px; fill: black; } </style> <!-- Chinese (Simplified) --> <text x="20" y="40">你好,世界</text> <!-- Japanese --> <text x="20" y="80">こんにちは世界</text> <!-- Korean --> <text x="20" y="120">안녕하세요 세계</text> </svg>

DOM Hit Testing

LunaSVG now supports DOM hit testing through the new Document::elementFromPoint(x, y) API. This function allows applications to determine which SVG element is located at a specific coordinate within the document's viewport.

Hit testing respects the current visual layout of the SVG content, including geometry, transforms, visibility, and the newly supported pointer-events property. The pointer-events attribute controls whether an element can receive pointer input; for example, setting pointer-events="none" excludes an element from hit testing, making it transparent to interaction.

The following example demonstrates how Document::elementFromPoint(x, y) can be used to perform DOM hit testing in LunaSVG. It renders two circles: one with pointer-events="auto" and another with pointer-events="none". Although both are visible, only the green circle is considered interactive. When queried with specific coordinates, the API identifies the green circle as the target element, while the red circle is ignored due to its pointer-events setting. This highlights how LunaSVG integrates pointer event semantics into its hit testing logic.

<circle id="circle-none" cx="75" cy="50" r="40" fill="red" pointer-events="none"/> <circle id="circle-auto" cx="225" cy="50" r="40" fill="green" pointer-events="auto"/> </svg> )SVG"; int main() { auto document = Document::loadFromData(kSVGContent); const std::pair<float, float> points[] = { {225, 50}, // center of green circle (pointer-events: auto) {75, 50}, // center of red circle (pointer-events: none) }; for(const auto& [x, y] : points) { if(auto element = document->elementFromPoint(x, y)) { std::cout << "Hit element: " << element.getAttribute("id") << "\n"; } else { std::cout << "No element found at (" << x << ", " << y << ")\n"; } } return 0; }'>#include <lunasvg.h> #include <iostream> #include <utility> using namespace lunasvg; static const char kSVGContent[] = R"SVG( <svg width="300" height="100" xmlns="http://www.w3.org/2000/svg"> <!-- This circle will be ignored by hit testing --> <circle id="circle-none" cx="75" cy="50" r="40" fill="red" pointer-events="none"/> <!-- This circle will be detected --> <circle id="circle-auto" cx="225" cy="50" r="40" fill="green" pointer-events="auto"/> </svg> )SVG"; int main() { auto document = Document::loadFromData(kSVGContent); const std::pair<float, float> points[] = { {225, 50}, // center of green circle (pointer-events: auto) {75, 50}, // center of red circle (pointer-events: none) }; for(const auto& [x, y] : points) { if(auto element = document->elementFromPoint(x, y)) { std::cout << "Hit element: " << element.getAttribute("id") << "\n"; } else { std::cout << "No element found at (" << x << ", " << y << ")\n"; } } return 0; }

Expected Output:

Hit element: circle-auto No element found at (75, 50)
Read Entire Article