Roughly ten months ago, some fellow students told me that i was crazy. This was certainly not the first time i was hearing that, i am a (web-) developer after all, and it will probably not be the last. But this time was slightly different.

It was different because i was doing what a programmer is supposed to do: I wrote code. However i was not writing program code — at least not technically. I was writing XML, specifically the subset that is known as SVG. Different people might stand on different positions regarding XML. Some might love the expressiveness, other hate the syntax and find it unreadable. On most days i find myself on the latter side of things. When it comes to SVG however, i start to like the way XML documents are formed. The way SVG is designed nicely aligns with XML syntax. And i really like the expressiveness one gets when writing icons or symbolic images (not paintings, i am not a designer or even a painter) in SVG.

When most people look at the issue of creating a graphic, they immediately jump to pencil and paper and start scribbling away. If that is not possible they reach for a stylus and a graphics tablet. And if even that is not possible — most notably because one does not own a graphics tablet — they just use their mouse or touchpad. And that is completely fine. Especially if you have found that piece of design-software which lets you work the way you want. I most certainly have not, or at least am not willing to spend rather huge amounts of money on trying out a software which then still isn't my preferred tool for the job.

Well, that is the main reason why i started writing SVG. Additionally i do not think that there is actually a better way of getting familiar with the way the format works than just writing a few documents; really, you should just give it a try. The actual document i was designing back then was a SVG-version of the Ontohub logo, as the original logo (taken from the OOR-Initiative) was only provided in a png-version.

The Ontohub Logo in <abbr title="Scalable Vector Graphics">SVG</abbr>

One thing everyone might be able to determine from the image is, that it is really made up of basic shapes: a rectangle, eigth circles and twelve lines. This makes it essentially a problem of relation between the specific elements. And one thing that can really annoy me about SVG — and actually most data formats in general — is the fact that SVG does not have variables. So you just find yourself repeating certain numbers over and over:

<!-- Excerpt from ontohub-logo.svg - Lines 30-34 -->
  <!-- Top -->
  <circle id="top-k" cx="32" cy="6.5" r="4" stroke="white" stroke-width="0" fill="white" />
  <circle id="top-j" cx="32" cy="40.5" r="4" stroke="white" stroke-width="0" fill="white" />
  <circle id="top-h" cx="12" cy="23.5" r="4" stroke="white" stroke-width="0" fill="white" />
  <circle id="top-l" cx="52" cy="23.5" r="4" stroke="white" stroke-width="0" fill="white" />

And in addition to that it is actually quite hard to change something, because if you change one value, at least 3 other values (if not all) must be changed in coordination with the new value, as they actually have an inherent dependency.

So when i was called crazy for writing this SVG by hand — and in XML nonetheless — i thought:

Well there must be a way to bring this to head.

Write code that writes code, or is code...

Rather quickly i came up with the idea of creating a DSL for writing SVG-Documents. The DSL acronym stands for domain specific language. Technically every programming language is a DSL, which belongs to the domain of program description. And SVG itself is also a DSL on top of XML which is specific to the purview of graphics.

The idea stuck with me, and after a few weeks i started looking around for actually existing implementations of SVG-libraries. As always there are a few around. But most of them are intended to make writing of SVG-Documents in another Host-Language (like Ruby or Python) possible or at least feasible. They are supposed to provide a direct mapping from SVG element-names (like circle or polyline) to methods/functions and attributes to, well, attributes or fields.

Still i wasn't sure which language to choose, as this was a project i did not actually want to do with my all-time favorite Ruby. I'm still not sure why actually, but i wanted to try another language. I quickly searched around but couldn't decide. And as it so often is, i came to forget about that idea altogether.

Reiterating on christmas

Let's fast-forward to christmas time. When the holidays come around i usually start some absurd project just to have something to do, as one does not really want to be around the family 24/7. After all there are only so many boardgames you can play. So when i was driving home for christmas, i remembered the idea from way back when.

A few weeks before i started diving into Lisps again. Lisp, which as a name originated with the term of List processing, is a small family of functional languages that is quite different from other languages. First of all it is using prefix notation for everything. And you'll have to get familiar with parentheses, because one will be using them a lot when in Lisp-Land.

; In Scheme, more precisely: ChickenScheme
; Simple local variable binding (to: two) of 1 + 1
(let ((two (+ 1 1)))
  two)
; returns => 2

The tipping point when deciding on Lisp for this project were the rather famous (in some circles at least) macro-capabilities of Lisp-style languages. I really like Clojure, which is a Lisp on top of the JVM, so i decided to use that implementation.

Clojure and the pitfalls of using a new language

Even though i wasn't really new to Clojure i was experiencing new-language-syndrome: this is the one where one tries, for some absurd reason, to use as much as possible of these cool new language features.
Shortly after New Year i basically had representing the basic shapes working. On the other hand i had used almost every 'object-oriented' language feature of clojure: Records, which allow you to define your own datatypes, Protocols which allow you to train function-names to resolve to different functions for different types and of course Macros, which basically allow you to add your own syntax by programming compile-time functions. And it was a mess. Especially because i wanted to do the one thing that didn't seem possible: Having two protocols declare the same function-name.

; Two Protocols which declare the same function-name/arity pair.

(defprotocol AdjustPosition
  (update-pos-attrs [this]))

(defprotocol AdjustPositionAndSize
  (update-pos-attrs [this]))

As i was the programmer i was entirely certain that none of my types were to implement both Protocols — it was really an either or situation — but Clojure just wouldn't let me do it. At least not without a warning, which would result in huge pains. Usually i don't let languages (or libraries) get away that easily, which is why i started to investigate this warning message. And sadly i have to admit, that i can understand why it is, at least currently, not possible to declare two protocols in such a way. The reason is pretty easily explained. Both actually declare a function with the specific name and arity in the current namespace — if your familar with Ruby: think module — which of course yields a problem when two or more protocols do this for the same function-name/arity pair.

Of course i could've worked around the issue, but i wanted an elegant solution and i wanted to learn whether there actually was an elegant solution. So let's go back to the proverbial drawing board, which in this case means my copy of Clojure Programming. I remembered something about a language feature called multimethods so i looked it up. Multimethods allow the programmer to define functions in such a way that they have different function-bodies depending on the type of the arguments it is called with. And the most important thing: type is not restricted to Datatypes, like String, List, Integer, Rational, or java.util.ArrayList, but can actually also mean constant values or custom hierarchies.

So let's take a small look at such a hierarchy.

(def shape-hierarchy
  (-> (make-hierarchy)
      (derive :rect ::shape)
      (derive :circle ::shape)
      (derive :ellipse ::shape)
      (derive :line ::shape)
      (derive ::points-based ::shape)
      (derive :polyline ::points-based)
      (derive :polygon ::points-based)))

The first parameter to the derive function is the hierarchy, the second is the child and the third is the parent. So :rect is declared as the child of ::shape. The difference between :shape and ::shape is that ::shape is fully qualified by the current namespace, whereas :shape is unqualified (actually a standard keyword), which is usually used in functions and data-structures.

The hierarchy yielded by the Clojure snippet

And now we'll go on to a multimethod which actually utilizes this hierarchy.

(defmulti dimensions
  :element-type
  :hierarchy #'shape-hierarchy)

(defmethod dimensions :rect
  [this]
  [(-> this :attrs :width) (-> this :attrs :height)])

(defmethod dimensions :circle
  [this]
  (let [width-height (* (-> this :attrs :r) 2)]
    [width-height width-height]))

As keywords like :element-type can be used as lookup functions in maps and records, we'll use this as a dispatch-function. The so-called dispatch function has the task of returning a value that is hopefully a part of the given hierarchy and can be matched.

Specific instances (read: function-bodies) are defined with defmethod. In our case :rect and :circle are constant values which are matched against the result of the dispatch-function according to the hierarchy. If i wanted to provide a default implementation for all shapes i could do so by defining a method via defmethod and passing ::shape as a value to compare against.

This allowed me to throw out the protocols, reduce the number of records to two, which i only kept for clarity; i didn't actually need any of the records, as they are most useful when used in conjunction with protocols. And because i kept reading i also threw out the macros and went for the Clojure credo of:

Code is data.

Instead of providing a functional API which would have been used by a macro which in turn would convert everything into the construct i wanted it to be, i decided to use a vectorized DSL. And that actually seems to be somewhat of a convention in Clojure as far as i understand it.

Vectors are the brother of lists with one major difference. If you add an element to it, it will be appended instead of prepended. The same way lists are optimized for the latter, vectors are optimized for the former.

Introducing...

Ok by now we've glimpsed around the corner in order to look at the cake, but we actually want to eat it. Let's take a look at this DSL.

(make-svg {:width 128 :height 128}
  [:rect :x 86 :y 54 :width 20 :height 20 :fill "red"]
  [:left-of
    [:rect :x 86 :y 54 :width 20 :height 20 :fill "red"]
    [:circle :r 10 :fill "black"] :by 64
   :and :below-of
    [:rect :x 86 :y 54 :width 20 :height 20 :fill "red"]])

This will define a graphic in which a rectangle(red) is placed on the right of the image and a circle(black) which is placed relative to the rectangle, with the same height and moved left by 64 units. Actually it should look exactly like the picture below, which is the resulting SVG-document after an additional call to export-svg with the make-svg form as its sole argument.

Result of the code above

Obviously the code could look a lot better if we were to accept the fact that we are in a programming language:

; equivalent to the one above, but a lot more readable
(let [width 128
      height width
      rect [:rect :x 86 :y 54 :width 20 :height 20 :fill "red"]
      circle [:circle :r 10 :fill "black"]]
  (make-svg {:width width :height height}
    rect
    [:left-of rect circle :by 64 :and :below-of rect]))

Even with the rather simple example we should be able to see that positioning one element relative to another has its benefits. Now let's take a look at my version of the Ontohub-SVG in this clojure DSL.

(let [background [:rect :width 64 :height 64 :fill "black" :rx 16]
      ten% {:dimension :width :percentage 10}
      twenty% {:dimension :width :percentage 20}
      thirtyfive% {:dimension :width :percentage 35}
      fifty% {:dimension :width :percentage 50}
      sixty% {:dimension :width :percentage 60}
      eighty% {:dimension :width :percentage 80}

      top-k [:circle :cx fifty% :cy ten% :r 4]
      top-j [:circle :cx fifty% :cy sixty% :r 4]
      top-h [:circle :cx twenty% :cy thirtyfive% :r 4]
      top-l [:circle :cx eighty% :cy thirtyfive% :r 4]

      bottom-k [:below-of top-k [:circle :cx fifty% :r 4] :by 17]
      bottom-j [:below-of top-j [:circle :cx fifty% :r 4] :by 17]
      bottom-h [:below-of top-h [:circle :cx twenty% :r 4] :by 17]
      bottom-l [:below-of top-l [:circle :cx eighty% :r 4] :by 17]

      k-k [:line :from top-k :to bottom-k]
      j-j [:line :from top-j :to bottom-j]
      h-h [:line :from top-h :to bottom-h]
      l-l [:line :from top-l :to bottom-l]

      bottom-k-l [:line :from bottom-k :to bottom-l]
      bottom-l-j [:line :from bottom-l :to bottom-j]
      bottom-j-h [:line :from bottom-j :to bottom-h]
      bottom-h-k [:line :from bottom-h :to bottom-k]

      top-k-l [:line :from top-k :to top-l]
      top-l-j [:line :from top-l :to top-j]
      top-j-h [:line :from top-j :to top-h]
      top-h-k [:line :from top-h :to top-k]]
  (make-svg
    {:width 64 :height 64}
    background
    [:default-attrs {:stroke-width 0 :fill "white"}
     top-k top-j top-h top-l
     bottom-k bottom-j bottom-h bottom-l]
    [:default-attrs {:stroke "white" :stroke-width "1.5"}
     k-k j-j h-h l-l
     bottom-k-l bottom-l-j bottom-j-h bottom-h-k
     top-k-l top-l-j top-j-h top-h-k]))

Okay this is a rather huge chunk of code, so we'll dive in and take a look at each part individually.

The features, missing ones and the name

As we have seen before we can create simple rectangles in a style similar to that of the xml-writer library.

background [:rect :width 64 :height 64 :fill "black" :rx 16]

The first element is the type of the SVG-element and the other elements are key-value pairs representing the attributes.

Additionally we are able to provide percentage values, relative to the size of the paper provided when declaring the svg-root element.

ten% {:dimension :width :percentage 10}
twenty% {:dimension :width :percentage 20}

As this syntax is quite peculiar i'm actually planning of supporting a direct use of keywords like this: :10%, in which the actual dimension is inferred from the key of the key-value pair in attributes (e.g. :width, when the key is :x or :cx). If however this is not possible, for example because there is no corresponding key, we will still be able to use the explicit syntax as provided here.

Then, of course, often when defining elements we want to place them in relation to each other. This can be done with the [:below-of source target] syntax.

bottom-k [:below-of top-k [:circle :cx fifty% :r 4] :by 17]
bottom-j [:below-of top-j [:circle :cx fifty% :r 4] :by 17]

This allows the circle of bottom-k to be placed relative to the y-position of top-k. An optional offset can be declared by providing the :by clause. Additionally two such clauses (with different dimensions), can be chained by providing an :and between them and skipping the target for the second clause.

Lines can be drawn easily between elements with the [:line :from from :to to]-form.

k-k [:line :from top-k :to bottom-k]
j-j [:line :from top-j :to bottom-j]

And last but not least we can provide common attributes to multiple elements at once with the [:default-attrs attribute-map & elements] form.

[:default-attrs {:stroke-width 0 :fill "white"}
 top-k top-j top-h top-l
 bottom-k bottom-j bottom-h bottom-l]

Currently this is very much a work in progress, as there are a lot of features i really want to support, but which are not implemented yet. However savage should now be in stage where it actually can be used for some icons. I will certainly use it. And if that didn't scale my crazy i do not really know what will. So, dear reader, the next time when someone is calling you crazy because you are doing something peculiar, you may also want to do something about that. And if this results in a nice project, please tell the world about it.

I know of at least one person, who would like to know more about it...