Repository
lib-svg
Kinogaki's SVG library.
- GitHub
- kinogaki/lib-svg (private)
- Folder
libs/lib-svg/- Crates
- 2
- Rust lines
- 17,578 in src, 597 in tests
Crates
| Crate | What it is | Files | Lines | Test lines |
|---|---|---|---|---|
kinogaki-svg | Kinogaki SVG reads SVG documents the way resvg (and so, closely, a browser) does, converts them into a render tree with every property resolved, and draws them with an exact-coverage anti-aliased rasterizer. | 49 | 16,916 | 597 |
kinogaki-svg-tools | Shared pieces of the conformance and benchmark tools: rendering a document with kinogaki-svg and with resvg into premultiplied RGBA8, comparing bitmaps, and writing side-by-side sheets. | 3 | 662 | 0 |
README
lib-svg
Kinogaki's SVG library. kinogaki-svg reads SVG documents the way resvg (and so, closely, a browser) does, converts them into a render tree with every property resolved, and draws them with an exact-coverage anti-aliased rasterizer. lib-ui draws its icons with it. It is a port of luce-svg's full renderer, redesigned in Rust and faster than both luce-svg and resvg on the icon set (see docs/BENCHMARKS.md).
| Crate | Contents |
|---|---|
crates/svg (kinogaki-svg) | The library: XML, CSS, the SVG tree, conversion, the rasterizer and renderer, the icon cache. No dependencies; owned code forbids unsafe |
crates/svg-tools (kinogaki-svg-tools) | conformance (icons against resvg, resvg's test suite against its references) and bench. Uses resvg as the oracle |
IconCache holds named icons parsed once and their rasterizations by size. Malformed documents answer an Error with its kind and source position; Limits bound size, nesting, node count, entity expansion, use copies, bitmap size and the steps reading takes (each selector test, each element converted, a referenced clip path's, mask's or marker's content again at each use), and a drawing cuts at most 16,777,216 dashes, so hostile input cannot exhaust memory or time.
What is supported
Everything luce-svg's full renderer draws: XML with DOCTYPE entities and namespaces; <style> sheets (type, class, id, attribute and :first-child selectors with combinators, specificity, !important) and style attributes; inherit; every length unit; transforms with transform-origin; use, symbol, nested svg, switch and conditional attributes; every basic shape and path command; fills and strokes (rules, dashes, caps, joins including miter-clip, paint-order, shape-rendering); every CSS color syntax including CSS Color 4 spaces; linear and two-point conical radial gradients with spreads; patterns; clip paths and masks (nested, both units); markers with context paints; group opacity, the sixteen blend modes and isolation; images (SVG images converted in place; PNG, JPEG and others through the caller's ImageDecoder); filters: the CSS filter functions and every primitive resvg renders (blend, color matrix, component transfer, composite with arithmetic, convolution, displacement, drop shadow, flood, Gaussian blur, image, merge, morphology, offset, tile, turbulence, diffuse and specular lighting with distant, point and spot lights), in linearRGB or sRGB. Compressed SVG (SVGZ) images are inflated by a built-in, bounded DEFLATE decoder. Text: <text>, <tspan>, <tref>, <a> and <textPath> laid out as usvg lays them out. That covers chunks, per-character x, y, dx, dy and rotate, text-anchor, letter and word spacing, kerning, textLength with lengthAdjust, baseline-shift, dominant and alignment baselines, decorations, text along paths, vertical writing modes, and bidi runs. Glyphs come from a GlyphSource. With the fonts feature, fonts::FontFiles provides them over font files: the system's by default, matched by CSS's rules, shaped by harfrust, with outlines from skrifa.
Conformance
The 449 luciaos icons match resvg within 0.2% of pixels at 24, 48 and 256 px; cargo test checks every icon at 24, 48 and 128 px. Against the 1719 integration tests resvg runs (300 px, a pixel differs when a premultiplied channel is off by more than 40, a test passes with at most 0.5% differing): with the suite's fonts, filters 396/397, masking 93/93, paint servers 151/153, painting 305/306, shapes 133/133, structure 254/258, text 354/379: 1686/1719 in all (luce-svg: 1671). Without fonts: 1308. What fails: color fonts and variable-font axes (font-variation-settings), GIF and WebP images (the tools decode PNG and JPEG), rgba() with a percentage alpha (read as CSS Color 4 reads it; resvg rejects it), and a few sub-pixel placements.
Checks
The Kinogaki Studio tree keeps this repository at libs/lib-svg, beside lib-ui. See docs/ARCHITECTURE.md and THIRD_PARTY_NOTICES.md.
Its documents
The repository's architecture document as written there (code blocks and links left out), and the opening of each other document.
lib-svg architecture (libs/lib-svg/docs/ARCHITECTURE.md)
lib-svg architecture
kinogaki-svg turns SVG text into pixels in five stages, each a module with one responsibility. The design follows luce-svg's full renderer (itself modelled on resvg's usvg/resvg split), so behavior can be compared stage by stage, but the data structures are Rust's: borrowed strings through parsing, an owned render tree with shared (Arc) paint servers, and a renderer that streams coverage rows.
| Module | Responsibility |
|---|---|
xml | Elements and text runs, borrowing from the source; DOCTYPE entities (which may hold markup) with a depth and byte budget; character references; namespace prefixes; errors with line and column |
css | Style sheets: selectors (type, *, class, id, attribute tests, :first-child, descendant/child/sibling combinators), specificity, declarations with !important |
tree | The SVG tree: only SVG elements, attributes after the cascade (presentation attribute < sheet rules by specificity < style), inherit resolved, use targets copied in with cycles refused and a copy budget, reference cycles (clip/mask/filter/pattern) broken. names/ holds the element and attribute names, and names/tables.rs their generated tables with the inheritance flags |
values | Attribute microsyntaxes: numbers, lengths, angles, transforms, viewBox, preserveAspectRatio, CSS colors (hex, named, rgb/hsl/hwb, lab/lch, oklab/oklch, color()), path data, points, IRIs |
convert | Tree to render tree, as usvg: units against the nearest viewport, visibility and conditions, groups only where needed; shapes, paint (fills, strokes, dashes, context paints), servers (gradients, patterns, href chains), structure (use, symbol, nested svg, switch), clip (clip paths, masks), markers (placement, orientation, paint order), filters (filter functions and elements into filter::Filter), image (data URLs, files, nested SVG, the caller's decoder) |
scene | The render tree: Group (transform, opacity, blend, isolation, clip, mask, filters, children), Shape (path, fill, stroke), Image; Gradient, Pattern, ClipPath, Mask |
geom | Points, transforms, rectangles, paths (arcs and quadratics raised to cubics, tight bounds), flattening within 0.05 device pixels, the stroker, dashing |
raster | Coverage: exact-area accumulation in bands, exact sub-scanline rows where the outline overlaps itself, pixel-centre coverage for shape-rendering crisp shapes |
render | Canvases of premultiplied floats, shaders (solid, gradients, pattern and image tiles), the sixteen blend modes, layers, clip paths and masks |
filter | The filter model; render runs a group's filters over layer-sized images (subregions, color-space conversion, named results), ops the combining and recoloring primitives, blur blurs, shadows and morphology, light turbulence and lighting |
icon | Icon and IconCache: parse once, rasterize per size |
The rasterizer
Every fill and stroke becomes closed outlines in device pixels. Rasterizer::resolve sorts the lines by top and walks bands of 16 rows. Each line adds, to an accumulation buffer, the signed area it contributes to every cell it crosses (the technique of font rasterizers): a running sum along a row then gives each pixel's winding-weighted covered area, which the fill rule folds (nonzero: |a| clamped to 1; even-odd: folded at every two). This is exact wherever the outline does not overlap itself within a pixel. When a row has a pixel whose winding exceeds one and an edge crossing it (two coincident contours, say), that row is resolved again exactly: sixteen sub-scanlines, each sweeping its crossings with the winding counted and adding the exact horizontal extent of the spans inside. Coverage rows stream to the painter, so memory is bounded by the shape's width, not its area.
Strokes
A stroke is built in user space and mapped to the device piece by piece, so non-uniform transforms stretch it as SVG asks. An open polyline becomes one outline: the left offset forward, the end cap, the right offset backward, the start cap; a closed one two loops. The outer side of each vertex gets the join (miter, miter-clip, bevel, round arc); the inner side the point where the offset lines cross, or a pivot through the vertex when they do not cross within both segments. As chains this outline equals the sum of every segment's rectangle and every join's wedge, all wound alike, so under the nonzero rule it covers exactly their union. Dashes cut the flattened path first; curves are measured as tiny-skia measures them (a polyline within 0.5 / resolution scale, quadratics by their own rule), so dash ends land where resvg puts them.
Rendering
The renderer walks the tree with the transform so far. A group that needs isolation (opacity, clip, mask, filters, blend mode, isolation) draws into a layer sized to its content's conservative device bounds; clip and mask coverage multiply the layer, which composites onto its parent. A group isolated only by opacity whose sole child is a shape with one paint is drawn without a layer, the opacity folded into the paint (identical output). Compositing is in sRGB as browsers and resvg composite; RenderOptions { linear: true } composites in linear light.
Bounds on work
Limits caps source bytes, nesting depth, node count, entity expansion, use copies, bitmap sides and the steps reading a document takes (max_work: each test of a selector against an element, so a selector of many descendant combinators can't try its combinations without end, and each element converted, so a clip path's, mask's or marker's content, converted again at each use, can't multiply through nesting); past it the document is refused. href chains stop after 64 links, patterns nest at most 16 deep, SVG images 8 deep, a drawing cuts at most 16,777,216 dashes across its strokes and their runs, flattening stops after 1000 pieces a curve, turbulence keeps its lattice in floats so any base frequency is a number, and every geometric computation tolerates NaN and infinite input. tests/robustness.rs feeds mutated icons, random markup and numeric extremes through parse and render.
Dependencies
None at run time. The tools crate and the conformance tests use resvg 0.45.1 as the reference renderer.
Benchmarks (libs/lib-svg/docs/BENCHMARKS.md)
All numbers are parse plus render of every one of the 449 luciaos icons (crates/svg/tests/fixtures/icons), each drawn into a square bitmap of the stated size, per round. Each figure is the median of the rounds, after one warm-up round. Release builds throughout.
