Vekt (from German/Norwegian "vector") is a lightweight, pure Kotlin Multiplatform SVG rendering library. It provides runtime SVG parsing and rendering for Compose Multiplatform, Kuikly Compose, and Android native Views.
- Pure Kotlin - No native dependencies, works on all Kotlin targets
- Compose Multiplatform - JVM, iOS, JS/WASM support
- Kuikly Compose - Tencent Kuikly framework support
- Android View - Native Android View for non-Compose projects
- Full SVG Path Support - M, L, H, V, C, S, Q, T, A, Z commands
- Basic Shapes - rect, circle, ellipse, line, polygon, polyline
- Groups & Transforms - g elements with translate, scale, rotate, skew, matrix
- Styles - fill, stroke, opacity, stroke-width, linecap, linejoin
- Gradients - linearGradient, radialGradient with stops and spread methods
- Defs & Symbols - use, defs, symbol elements for reusable graphics
- Text Support - text and tspan elements with font properties and alignment
- ClipPath - clipPath rendering for element clipping
- Image - Embedded base64 data URI images
- Advanced Parsing - mask, filter, pattern elements (rendering planned)
- viewBox - Proper viewport and scaling support
- Tinting - Apply tint color to override fill colors
Add the dependency to your project:
// build.gradle.kts
// For Compose Multiplatform
implementation("io.aimei:vekt-compose:0.1.0")
// For Kuikly Compose
implementation("io.aimei:vekt-kuikly:0.1.0")
// For Android native View
implementation("io.aimei:vekt-android:0.1.0")
// Core only (parsing without rendering)
implementation("io.aimei:vekt-core:0.1.0")import io.aimei.wk.compose.SvgImage
@Composable
fun MyIcon() {
val svgContent = """
<svg viewBox="0 0 100 100">
<circle cx="50" cy="50" r="40" fill="red"/>
</svg>
""".trimIndent()
SvgImage(
svgContent = svgContent,
modifier = Modifier.size(48.dp),
tint = Color.Blue // Optional: override fill color
)
}import io.aimei.wk.kuikly.SvgImage
@Composable
fun MyIcon() {
SvgImage(
svgContent = svgContent,
modifier = Modifier.size(48.dp)
)
}import io.aimei.wk.android.SvgView
// In Activity/Fragment
val svgView = SvgView(context)
svgView.setSvgContent(svgString)
svgView.setTintColor(Color.BLUE) // Optional
// In XML layout
<io.aimei.wk.android.SvgView
android:id="@+id/svgView"
android:layout_width="48dp"
android:layout_height="48dp"/>import io.aimei.wk.parser.SvgParser
import io.aimei.wk.parser.SvgPathParser
// Parse complete SVG document
val document = SvgParser.parse(svgContent)
println("Width: ${document.width}, Height: ${document.height}")
println("Elements: ${document.elements.size}")
// Parse path data only
val pathCommands = SvgPathParser.parse("M10 20 L30 40 Z")
pathCommands.commands.forEach { cmd ->
when (cmd) {
is PathCommand.MoveTo -> println("Move to ${cmd.x}, ${cmd.y}")
is PathCommand.LineTo -> println("Line to ${cmd.x}, ${cmd.y}")
is PathCommand.Close -> println("Close path")
// ...
}
}| Element | Support |
|---|---|
<path> |
β Full |
<rect> |
β Full (with rx/ry) |
<circle> |
β Full |
<ellipse> |
β Full |
<line> |
β Full |
<polyline> |
β Full |
<polygon> |
β Full |
<g> (group) |
β Full |
<text> |
β Full (font-family, font-size, font-weight, text-anchor, tspan) |
<tspan> |
β Full (x, y, dx, dy, style inheritance) |
<image> |
β Basic (data URI base64, placeholder for external URLs) |
<defs> |
β Full |
<symbol> |
β Full |
<use> |
β Full (href and xlink:href) |
<linearGradient> |
β Full (stops, gradientUnits, spreadMethod) |
<radialGradient> |
β
Compose/Android, |
<clipPath> |
β Full (rendering with clipping) |
<mask> |
β Parsing (rendering planned) |
<filter> |
β Parsing (rendering planned) |
<pattern> |
β Parsing (rendering planned) |
| Command | Description | Support |
|---|---|---|
| M/m | Move to | β |
| L/l | Line to | β |
| H/h | Horizontal line | β |
| V/v | Vertical line | β |
| C/c | Cubic Bezier | β |
| S/s | Smooth cubic | β |
| Q/q | Quadratic Bezier | β |
| T/t | Smooth quadratic | β |
| A/a | Elliptical arc | β |
| Z/z | Close path | β |
| Transform | Support |
|---|---|
| translate(x, y) | β |
| scale(x, y) | β |
| rotate(angle, cx, cy) | β |
| skewX(angle) | β |
| skewY(angle) | β |
| matrix(a,b,c,d,e,f) | β |
| Property | Support |
|---|---|
| fill | β Hex, named colors, url(#gradient) |
| fill-opacity | β |
| stroke | β Hex, named colors, url(#gradient) |
| stroke-width | β |
| stroke-opacity | β |
| stroke-linecap | β butt, round, square |
| stroke-linejoin | β miter, round, bevel |
| stroke-miterlimit | β |
| opacity | β |
| style (CSS inline) | β |
| Property | Support |
|---|---|
| font-family | β |
| font-size | β |
| font-weight | β normal, bold, 100-900 |
| font-style | β normal, italic, oblique |
| text-anchor | β start, middle, end |
| dominant-baseline | β auto, middle, hanging, etc. |
| Primitive | Support |
|---|---|
<feGaussianBlur> |
β Parsing |
<feOffset> |
β Parsing |
<feFlood> |
β Parsing |
<feBlend> |
β Parsing |
<feComposite> |
β Parsing |
<feMerge> |
β Parsing |
<feColorMatrix> |
β Parsing |
<feDropShadow> |
β Parsing |
- CSS classes and external stylesheets
- Mask rendering (parsing is supported)
- Filter rendering (parsing is supported)
- Pattern rendering (parsing is supported)
- External image URLs (data URIs work)
- Animations
- Kuikly: radialGradient not supported (Kuikly's Brush API only has linearGradient). Falls back to first gradient stop color as solid fill.
Each platform has an independent demo module:
# Run Compose Desktop demo
./gradlew :demo-compose:run
# Build Android demo APK
./gradlew :demo-android:assembleDebug
# Build Kuikly demo (iOS framework)
./gradlew :demo-kuikly:linkDebugFrameworkIosSimulatorArm64Demo modules showcase:
- Basic SVG rendering
- All supported shapes and paths
- Linear and radial gradients
- Transforms and groups
- Text and tspan
- ClipPath
- Symbol and use references
- Tint color override
vekt/
βββ vekt-core/ # Pure Kotlin parsing, no UI dependencies
β βββ model/ # SVG data models (SvgDocument, PathCommand, etc.)
β βββ parser/ # Parsers (SvgParser, SvgPathParser, TransformParser)
βββ vekt-compose/ # Compose Multiplatform adapter
βββ vekt-kuikly/ # Kuikly Compose adapter
βββ vekt-android/ # Android native View adapter
βββ demo-compose/ # Compose Desktop demo app
βββ demo-android/ # Android native demo app
βββ demo-kuikly/ # Kuikly demo
Vekt is designed for efficient runtime parsing.
- JVM: OpenJDK 17+ (tested on OpenJDK 21)
- OS: macOS 14.5 ARM64 (Apple M2 Pro)
- Kotlin: 2.1.21
- Warmup: 100 iterations (JIT optimization)
- Benchmark: 1000 iterations per test
- Tests: 404 tests, 100% pass rate
Run benchmarks: ./gradlew :vekt-core:jvmTest --tests "*.ParserBenchmark*"
| Test Case | Description | Average | Median | Ops/sec |
|---|---|---|---|---|
| Simple path | M10 20 L30 40 L50 20 Z (4 commands) |
2.5Β΅s | 2.4Β΅s | 398,340 |
| Medium path | 2 subpaths with cubic curves (~15 commands) | 8.2Β΅s | 7.5Β΅s | 122,161 |
| Complex path | 10+ cubic curves, 8-decimal precision | 20.8Β΅s | 17.9Β΅s | 48,079 |
| Path with scale | Complex path with scale=2x, offset=(10,10) | 5.8Β΅s | 5.9Β΅s | 171,296 |
| Simple SVG | 104 bytes, 1 path | 10.4Β΅s | 10.4Β΅s | 95,862 |
| Medium SVG | 390 bytes, group + shapes | 38.4Β΅s | 37.5Β΅s | 26,021 |
| Complex SVG | 782 bytes, multiple paths with transforms | 55.9Β΅s | 54.6Β΅s | 17,875 |
| File | Size | Elements | Average | Median | Ops/sec |
|---|---|---|---|---|---|
| boy.svg | 35KB | 23 paths, 23 transforms | 683Β΅s | 676Β΅s | 1,463 |
| confetti-ball.svg | 43KB | 21 paths, 21 transforms | 912Β΅s | 891Β΅s | 1,096 |
| maps.svg | 56KB | 19 paths, 19 transforms | 919Β΅s | 916Β΅s | 1,088 |
| swimming-pool.svg | 55KB | 20 paths, 20 transforms | 1.04ms | 1.04ms | 958 |
| woman.svg | 36KB | 19 paths, 19 transforms | 648Β΅s | 638Β΅s | 1,543 |
- Sub-millisecond parsing: Even complex 50KB+ SVGs parse in under 1ms
- Memory efficient: Minimal allocations during parsing
- Cached rendering: Use
remember {}in Compose to cache parsed documents
// Recommended: Cache parsed document
val document = remember(svgContent) {
SvgParser.parse(svgContent)
}
SvgImage(document = document, modifier = Modifier.size(48.dp))Contributions are welcome! Please feel free to submit issues and pull requests.
MIT License
Copyright (c) 2024-2025 xuwakao
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.