A Multiplatform Kotlin SVG image DSL.

Related tags

Kotlin kotlin svg dsl
Overview

Coverage license Travis Download

Kotlin Multiplatform SVG DSL

I had a Kotlin microservice that needed to produce a simple heatmap chart on an HTML page to a large audience very rapidly. This was an ad hoc DevOps requirement, so I didn't want to take on an involved Javascript solution. Instead, I went with spitting out a half page of inline SVG text generated by the server itself. This DSL made the server code trivial and has easily met the performance needs.

Example

fun codeMonkey() {
    val svg = SVG.svg(true) {
         height = "300"
         width = "300"
         style {
             body = """

                 svg .black-stroke { stroke: black; stroke-width: 2; }
                 svg .fur-color { fill: white; }

             """.trimIndent()
         }
         // Label
         text {
             x = "40"
             y = "50"
             body = "#CODE"
             fontFamily = "monospace"
             fontSize = "40px"
         }
         // Ears - example using a function because USE tag doesn't work in Safari
         ear(100, 100)
         ear(240, 70)
         // Face
         circle {
             cssClass = "black-stroke"
             id = "face"
             cx = "180"
             cy = "140"
             r = "80"
             fill = "#aa450f"
         }
         // Eyes
         circle {
             cssClass = "black-stroke fur-color"
             id = "eye"
             cx = "160"
             cy = "95"
             r = "20"
         }
         use {
             x = "45"
             y = "-5"
             href = "#eye"
         }
         // Muzzle
         circle {
             cssClass = "black-stroke fur-color"
             cx = "195"
             cy = "178"
             r = "65"
         }
         // Nostrils
         circle {
             id = "nostril"
             cx = "178"
             cy = "138"
             r = "4"
             fill = "black"
         }
         use {
             x = "35"
             y = "-5"
             href = "#nostril"
         }
         // Mouth
         path {
             cssClass = "black-stroke"
             d = "M 150 150 C 100,250 305,260 230,140 C 205,190 165,170 150,150 Z"
             fill = "red"
         }
     }

     FileWriter("build/tmp/codeMonkey.svg").use {
         svg.render(it, SVG.RenderMode.FILE)
     }
    }

    private fun Container.ear(x: Int, y: Int) {
        circle {
            cssClass = "black-stroke fur-color"
            cx = x.toString()
            cy = y.toString()
            r = "40"
        }
        circle {
            cssClass = "black-stroke fur-color"
            cx = x.toString()
            cy = y.toString()
            r = "28"
        }
    }

Yields a code monkey: code monkey

Validation

Originally the attribute values were appropriate types, but eventually as support for percentage values etc. was added, and considering they are always represented as strings in SVG, the value type was changed to String. But that made errors like length = "foo" possible. Therefore, attribute validation was added. Over time other validations became apparent too. With validation on, while the SVG is created, things are checked, for example when an attribute string is assigned, if it can be properly validated, it is. Validation can be turned off for performance reasons.

About Inline vs File SVG

SVG is an XML tag based format. Those tags can be put into an .svg file, or in modern browsers appear directly inline in the HTML5. However some attributes and other details differ slightly between these modes. This DSL is biased toward the inline representation because that's its origin, but it supports indicating the rendering mode and in the limitted scenarios tested it works.

A Limited Set of Elements

Currently only a small set of SVG Elements are supported. Adding more is straight forward, I just met my own needs, and so additions can be done by others, or as my needs increase.

Multiplatform Support

This is a multiplatform project currently targeting JVM and JavaScript. The JVM target is fully baked, the JavaScript is still a bit doughy. Looking for JavaScript folks to help me polish it.

As Compared To kotlinx.html

Why did I write yet another SVG DSL when SVG is covered by the kotlinx.html? Let's just say ... you try using it. I could not figure out how to use it based on the SVG specification. This package is as close to a one to one mapping as I could make it. So what if you want to combine them? Not a problem, just use unsafe/raw:

 val svg = SVG.svg {
   // ....
 }
 System.out.appendHTML().html {
     body {
        unsafe {
            raw(svg.toString())
        }
     }
 }

See Also

Comments
  • Repository needs new host

    Repository needs new host

    With the closure of Bintray, the old repository at https://dl.bintray.com/nwillc/maven is no longer available. Install instructions in #8 are no longer accurate, but cannot be updated until a new repository url is available.

    opened by redbassett 8
  • Update build script for compatibility with JitPack.io & enable IR JS compiler

    Update build script for compatibility with JitPack.io & enable IR JS compiler

    This PR means that:

    1. You can now fetch the library from JitPack.io:
    repositories {
        mavenCentral()
        maven("https://jitpack.io")
    }
    
    sourceSets {
        commonMain {
            dependencies {
                implementation("com.github.nwillc.ksvg:ksvg:master-SNAPSHOT") // WILL NOT WORK UNTIL PR IS MERGED! To test use com.github.harry1453.ksvg:ksvg:master-SNAPSHOT
            }
        }
    
        val jvmMain by getting {
            dependencies {
                // No dependency required!
            }
        }
    
        val jsMain by getting  {
            dependencies {
                // No dependency required!
            }
        }
    }
    

    The version string master-SNAPSHOT can be replaced with a tag name or commit hash. I have updated the README accordingly. Once this is merged, it would be good to create a tag to allow users to get the library via a normal-looking version number.

    1. It enables the compatibility mode of the Kotlin/JS compiler which builds 2 JS libraries, one for the legacy compiler and one for the IR compiler. This means it is now possible to use this library with the IR compiler.
    opened by harry1453 2
  • JS Tests (wrong branch)

    JS Tests (wrong branch)

    so far i fixed up test names with @JsName this works, testnames are still reported with spaces, but it is a pain to write a extra line

    tests have to be started with CHROME_BIN env variable set

    test failures mentioned here still exist: https://github.com/nwillc/ksvg/issues/3#issuecomment-581003906

    opened by NikkyAI 1
  • add publishing to mavenlocal

    add publishing to mavenlocal

    I was also looking around for ho to add bintray again

    seems like jetbrains uses their own plugin to configure multiplatform deployment and teamcity integration https://github.com/Kotlin/kotlinx.team.infra

    and i found this writeup, not sure if it is still correct: https://natanfudge.github.io/fudgedocs/publish-kotlin-mpp-lib.html

    another example i found: https://github.com/data2viz/data2viz/blob/master/build.gradle#L36-L38 https://github.com/data2viz/data2viz/blob/master/gradle/publish-bintray.gradle

    never used bintray myself, seems to be a pain

    opened by NikkyAI 0
  • Bump Gradle and Kotlin versions

    Bump Gradle and Kotlin versions

    • Bump Gradle to 7.4.2
    • Bump Kotlin to 1.7.0

    I also refactored the Gradle scripts to be a little more idiomatic

    • dependencies defined in 'common', if they are platform-independent, are inherited by JS and JVM automatically
    • change forEach to configureEach - uses Gradle lazy-config API
    • set up central repo definition
    opened by aSemy 0
  • add function to add raw SVG elements

    add function to add raw SVG elements

    having the option to insert "raw" child elements from string would be convenient

    example:

    fun generateSVG(
        shapeViewBox: String = "0 0 100 100",
        shapeContent: String = """<path d="M0 13.3333C0 5.96954 5.96954 0 13.3333 0H86.6667C94.0305 0 100 5.96954 100 13.3333V86.6667C100 94.0305 94.0305 100 86.6667 100H13.3333C5.96954 100 0 94.0305 0 86.6667V13.3333Z" fill="currentColor"/>"""
    ): String {
        val svg = SVG.svg {
            /* ...*/
            children += SVG.svg {
                id = "shape"
                width = "100%"
                height = "100%"
                attributes["x"] = "0"
                attributes["y"] = "0"
                viewBox = shapeViewBox
                body = "replace_shape"
            }
        }
        return buildString {
            svg.render(this, RenderMode.INLINE)
        }.replace("replace_shape", "\n" +shapeContent + "\n")
    }
    

    i recently came across a problem in which i was given raw svg paths and viewport of a SVG and had to assemble nested SVGs out of them

    i solved it by assigning a "replace_thing" value to tthe body of the nested SVGs and did some string manipulation on the rendered output

    regardless.. a nicer api would make this type of problem easier to solve

    opened by NikkyAI 0
  • nested SVGs will generate XML declaration in each SVG

    nested SVGs will generate XML declaration in each SVG

    nested SVGs are a easy way to group elements together and drawing a picture inside a picture

    sadly it seems like RenderMode.FILE did not take this into account (also the x, y properties)

    example:

    val svg = SVG.svg {
        id = "s0"
        width = "256"
        height = "256"
        rect {
            id = "r0"
            width = "100%"
            height = "100%"
            x = "0"
            y = "0"
            stroke = "red"
            strokeWidth = "3"
            fill = "purple"
        }
        children += SVG.svg {
            id = "s1"
            width = "80%"
            height = "80%"
            attributes["x"] = "10%"
            attributes["y"] = "10%"
            rect {
                id = "r1"
                width = "100%"
                height = "100%"
                x = "0"
                y = "0"
                stroke = "red"
                strokeWidth = "3"
                fill = "blue"
            }
            children += SVG.svg {
                id = "s2"
                width = "80%"
                height = "80%"
                attributes["x"] = "10%"
                attributes["y"] = "10%"
                rect {
                    id = "r2"
                    width = "100%"
                    height = "100%"
                    x = "0"
                    y = "0"
                    stroke = "red"
                    strokeWidth = "3"
                    fill = "cyan"
                }
            }
        }
    }
    val borkedSVG = buildString {
        svg.render(this, RenderMode.FILE)
    }.also {
        File("test_broken.svg").writeText(it)
        println(it)
    }
    

    broken output:

    <?xml version="1.0" encoding="UTF-8" ?>
    <svg height="256" id="s0" width="256" xmlns="http://www.w3.org/2000/svg">
    <rect fill="purple" height="100%" id="r0" stroke="red" stroke-width="3" width="100%" x="0" y="0"/>
    <?xml version="1.0" encoding="UTF-8" ?>
    <svg height="80%" id="s1" width="80%" x="10%" xmlns="http://www.w3.org/2000/svg" y="10%">
    <rect fill="blue" height="100%" id="r1" stroke="red" stroke-width="3" width="100%" x="0" y="0"/>
    <?xml version="1.0" encoding="UTF-8" ?>
    <svg height="80%" id="s2" width="80%" x="10%" xmlns="http://www.w3.org/2000/svg" y="10%">
    <rect fill="cyan" height="100%" id="r2" stroke="red" stroke-width="3" width="100%" x="0" y="0"/>
    </svg>
    </svg>
    </svg>
    

    expected/fixed:

    <?xml version="1.0" encoding="UTF-8" ?>
    <svg height="256" id="s0" width="256" xmlns="http://www.w3.org/2000/svg">
    <rect fill="purple" height="100%" id="r0" stroke="red" stroke-width="3" width="100%" x="0" y="0"/>
    <svg height="80%" id="s1" width="80%" x="10%" y="10%">
    <rect fill="blue" height="100%" id="r1" stroke="red" stroke-width="3" width="100%" x="0" y="0"/>
    <svg height="80%" id="s2" width="80%" x="10%" y="10%">
    <rect fill="cyan" height="100%" id="r2" stroke="red" stroke-width="3" width="100%" x="0" y="0"/>
    </svg>
    </svg>
    </svg>
    
    opened by NikkyAI 0
  • JS Tests

    JS Tests

    so far i fixed up test names with @JsName this works, testnames are still reported with spaces, but it is a pain to write a extra line

    test tasks allTests, jsBrowserTest have to be started with CHROME_BIN env variable set

    test failures mentioned here still exist: https://github.com/nwillc/ksvg/issues/3#issuecomment-581003906

    opened by NikkyAI 0
Owner
Nwillc
I'm a graybeard software engineer. Been coding since high school in the 1980's.  I've been doing it professionally since college.
Nwillc
Android + Kotlin + Github Actions + ktlint + Detekt + Gradle Kotlin DSL + buildSrc = ❤️

kotlin-android-template ?? A simple Github template that lets you create an Android/Kotlin project and be up and running in a few seconds. This templa

Nicola Corti 1.5k Jan 3, 2023
A Kotlin DSL wrapper around the mikepenz/MaterialDrawer library.

MaterialDrawerKt Create navigation drawers in your Activities and Fragments without having to write any XML, in pure Kotlin code, with access to all t

Márton Braun 517 Nov 19, 2022
{ } Declarative Kotlin DSL for choreographing Android transitions

Transition X Kotlin DSL for choreographing Android Transitions TransitionManager makes it easy to animate simple changes to layout without needing to

Arunkumar 520 Dec 16, 2022
Kotlin Dsl for Android RecyclerView

KRecyclerDsl Kotlin Dsl for Android RecyclerView Exemple Sample project recyclerView.adapter = dataClassAdapter<MyView, MyDataClass>(R.layout.my_view,

Thomas Girard 14 Mar 31, 2019
The most complete and powerful data-binding library and persistence infra for Kotlin 1.3, Android & Splitties Views DSL, JavaFX & TornadoFX, JSON, JDBC & SQLite, SharedPreferences.

Lychee (ex. reactive-properties) Lychee is a library to rule all the data. ToC Approach to declaring data Properties Other data-binding libraries Prop

Mike 112 Dec 9, 2022
Nice and simple DSL for Espresso in Kotlin

Kakao Nice and simple DSL for Espresso in Kotlin Introduction At Agoda, we have more than 1000 automated tests to ensure our application's quality and

Agoda Company Pte Ltd. 1.1k Nov 22, 2022
DSL for constructing the drawables in Kotlin instead of in XML

Android Drawable Kotlin DSL DSL for constructing the drawables in Kotlin instead of in XML Examples Shape drawables <?xml version="1.0" encoding="utf-

Infotech Group 178 Dec 4, 2022
Lightweight Kotlin DSL dependency injection library

Warehouse DSL Warehouse is a lightweight Kotlin DSL dependency injection library this library has an extremely faster learning curve and more human fr

Osama Raddad 18 Jul 17, 2022
Code generation of Kotlin DSL for AWS CDK

Code generation of Kotlin DSL for AWS CDK

Semantic Configuration 5 Dec 24, 2022
Regular expression DSL on Kotlin

Examples Characters Construct Equivalent Matches x character(Char) The character x \\ character('\\') The backslash character \0n octal(OctalValue(7))

null 1 Oct 7, 2021
Kotlin Object Notation - Lightweight DSL to build fluid JSON trees

Kotlin Object Notation Lightweight kotlin MPP DSL for building JSON trees Setup Just drop the dependency in your commonMain sourceSet kotlin { sourc

Martynas Petuška 43 Dec 10, 2022
Kotlin parser library with an easy-to-use DSL

Pratt Library for parsing expressions and a beautiful Kotlin DSL Just define your operators and operands with the Kotlin DSL and the parser is ready!

furetur 9 Oct 17, 2022
Kotlin DSL for Junit5

Kupiter is Kotlin DSL for Junit5. Current API is only for dynamic tests. Get it repositories { maven { url 'https://jitpack.io' } } dependencies

Andrius Semionovas 14 Oct 3, 2022
Kotlin-dsl-sample - Preferences project on android

kotlin-dsl-example Sample preferences project on android. How to use val

null 1 Dec 30, 2021
GitHub Actions Kotlin DSL

GitHub Actions Kotlin DSL Work in progress! The goal is to be able to describe GH Actions in Kotlin with all its perks, like: workflow( name = "Te

Piotr Krzemiński 271 Dec 26, 2022
A simple, classic Kotlin MVI implementation based on coroutines with Android support, clean DSL and easy to understand logic

A simple, classic Kotlin MVI implementation based on coroutines with Android support, clean DSL and easy to understand logic

Nek.12 4 Oct 31, 2022
Kotlin DSL inspired by bhailang.js

Kotlin DSL inspired by bhailang.js

kaiwalya 1 Mar 22, 2022
A light weight Compose Animation library to choreograph low level Animation API through Kotlin DSL.

Koreography Choreograph your Compose Animation ?? ?? A lightweight Compose Animation utility library to choreograph low-level Animation API (https://d

Sagar Viradiya 107 Jan 8, 2023
A small DSL to make building a conversation in Bukkit easy.

Konversation Konversation provides a simple builder to construct chains of prompts to be used in the conversation API present in Bukkit. Bukkit only p

Tim Hagemann 2 Dec 4, 2022