Handcrafting your own SVG
Tags: svg, design, lessonslearned
Published: 2020-08-23 16:41:45

A couple of months ago, I wanted to design a custom logo. The goal was to create 4 different variations for a single logo: horizontal, vertical, "condensed", and square. All have to display the same bevel-like effect.

I'm a developer, and not a web designer For this reason, I chose to use an existing point-and-click web application to create a [SVG](https://www.w3.org/Graphics/SVG/). It did the job. But I'm curious, and checked the generated XML: I was disappointed.

This post aims to describe the lessons learned with my first steps into the wondrous world of SVG.

## Issues with generated code

First things first, the issues described are not specific to the web application I used, nor to SVG. I can remember 20 years ago generating HTML with Macromedia (now Adobe) Dreamweaver: the HTML was far from optimal - to say the least.

With the generated SVG, issues are, in no particular order:

* The shape and the bevel use different paths objects
* Moreover, each logo is designed in a different way: one uses an image-encoded in base 64, one uses 2 different `<path>` objects, one 3, and the last one 4
* Because of point-and-click, there's no way to handle metrics precisely
* Transformations are using the `matrix` tag, instead of the more specific `translate`
* Transformations position the text elements, as opposite to directly set the position
* The rounded corners using _Bezier_ curves is much more complex than required
* A single group encompasses all shapes, while I want a group for each logo flavor. Granted, I didn't use the _Group_ tool in the webapp

## Reusing the same form

The main form and the bevel share the same coordinates: the difference is that the bevel is translated.

![Main form and its bevel](https://dev-to-uploads.s3.amazonaws.com/i/9fvp255ugua69h5wvo3y.png)

It doesn't make any sense to write the same coordinates twice, and translate one. In that case, the  <abbr title="Don't Repeat Yourself">DRY</abbr>  is relevant. SVG allows to define a form once, and use it as many times as desired. For that, it respectively offers a `defs` directive, and a `use` one.

```xml
<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg">
  <defs>
    <path id="path" d="M 0 0 H 400 V 250 H 0 V 0 Z" /> <!-- 1 -->
  </defs>
  <use href="#path" />                                 <!-- 2 -->
  <use href="#path" transform="translate(0 30)" />     <!-- 2 -->
</svg>
```

1. Define the `path`
2. Display the defined `path` using `href`. SVG removed the `xlink` namespace, so don't use `xlink:href`

I must confess that I also tried to achieve a bevel effect with filtering, to no avail.

## Coordinates and referential

The web application uses absolute coordinates to define the paths' points. It makes sense. Yet, to manually write coordinates, it's easier to use coordinates in the origin referential, then translate them accordingly.

```xml
<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg">
  <defs>
    <path id="path" d="M 0 0 H 400 V 250 H 0 V 0 Z" /> <!-- 1 -->
  </defs>
  <g transform="translate(30,30)">                     <!-- 3 -->
    <use href="#path" />
    <use href="#path" transform="translate(0 30)" />   <!-- 2 -->
  </g>
</svg>
```

1. Define coordinates starting from the origin
2. Translate the bevel
3. Translate the group

## Matrix transforms

SVG allows to use different transform operations: `scale`, `rotate`, `translate`, `skewX` and `skewY`. Two syntaxes are available to apply those transforms:

1. A "compound" syntax using a single `matrix` attribute _e.g._ `transform="matrix(3 1 -1 3 30 40)`.
2. A "disjointed" syntax using the different above attributes _e.g._ `transform="translate(0 20) rotate(-10 50 0)`

While the first syntax is much more concise, it makes understanding the transform operation much harder. Besides, I used strictly translations: I re-wrote all transforms with the `translate` property.

## CSS transforms

While SVG offers transform operations out-of-the-box, there are other options to achieve the same. One can apply CSS styles to SVG elements as well. In particular, CSS provides the `transform` property for the same purpose.

This allows to apply the same translation to all shapes in a SVG document. It's a great way to uniformly apply... a bevel effect.

```xml
<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg">
  <defs>
    <style>
/* <![CDATA[ */
use       { fill:#0C3C60; stroke-width:4; stroke:#0C3C60; }
use.bevel { fill:#D1E0EB; transform:translate(0px, 30px); }  <!-- 1 -->
/* ]]> */
    </style>
    <path id="path" d="M 0 0 H 400 V 250 H 0 V 0 Z" />
  </defs>
  <use href="#path" />
  <use href="#path" class="bevel" />                         <!-- 2 -->
</svg>
```

1. Define the translation at the `bevel` class level
2. Apply the class and the associated transformation

From that point on, the SVG rendered applies the translation to all shapes with the `bevel` class.

Beware that the syntax is a bit different in SVG and in CSS: in CSS, units are required.

## Rounded corners

Paths allow rounded corners by defining [Bézier curves](https://developer.mozilla.org/en-US/docs/Web/SVG/Tutorial/Paths#Curve_commands). SVG offers 3 different kind of available Bézier curves, cubic, quadratic and shortcut. It depends on the wanted precision: the higher the precision, the higher the number of control points necessary to define the pathL

The web application uses the cubic way, which requires 6 control points (see the above link for the complete documentation). In this case, because the curve needs to be asymptotical to two consecutive borders, the quadratic approach is also suitable. As a bonus, it's both more concise **and** easier to read as it requires just 4 control points.

```xml
<path
  id="path-horizontal"
  d="M 25 0                        <!-- 1 -->
     H 500                         <!-- 2 -->
     Q 525 0 525 25                <!-- 3 -->
     V 135
     Q 525 160 500 160
     H 25
     Q 0 160 0 135
     V 25
     Q 0 0 25 0
     Z" />                         <!-- 4 -->
```

1. Start from point defined by x=25 and y=0
2. From the previous point, draw a horizontal line up to x=500. In effect, it thus ends at (500, 0)
3. The first control point defines a geometrical line with the last point in the path; it plays the role of an asymptote to the curve. The second control point, with the first one, defines a second line. It's also an asymptote.

    ![Defining a cubic Bézier curve](https://dev-to-uploads.s3.amazonaws.com/i/avr1g0g6a09qcc74likx.jpg)

4. Close the path

## Grouping

Defining groups in an adequate way offers several options for each group:

* Apply a transform
* Set a CSS style
* Set a CSS class
* etc.

Don't be afraid to use them!

## Miscellaneous considerations

Twitter cards are not able to use configured SVG images. Likewise, OpenGraph _i.e._ Facebook doesn't allow SVG images for preview. For such reasons, it's a good idea to export SVG in a bitmap format.

The SVG displayed on the production site should be as lightweight as possible. In particular, carriage returns characters are not used by the SVG renderer. On the flip side, they are helpful to maintainers. Hence, I'd recommend to minify the SVG.

If the SVG is never touched again, one can apply minification directly on the source. Otherwise, then minification should be part of the  <abbr title="Continuous Integration">CI</abbr>  pipeline, and applied on the target file. As an example, [SVGO](https://github.com/svg/svgo) is a plugin for Node.js that offers this feature.

## Conclusion

Generating XML (or code) with a tool is always easier - at first: No need to know about the underlying syntax, just click around, and it yields the expected results.

Yet, if one is willing to invest some time reading the documentation, it's possible to achieve much better results. Granted, it's much more time-consuming. It's also so much more satifsfying.