JMM’s notes on

Scalable Vector Graphics (SVG)

SVG is one of my favorite web technologies. It’s a great way to make stylable, accessible, and interactive graphics. It’s not only human-readable, it’s easily programmatically modified using the DOM API, which lets you make data visualizations fairly easily (also check out d3.js).

Misc

textPath vertical and horizontal alignment and offsets

Just a note for myself since I couldn’t really google this. When you put text on a path, how do you shift its horizontal and vertical position?

You shift the horizontal position by putting a startOffset attribute on the <textPath> element (e.g. startOffset=50%). To move it in the vertical direction, you can set the dy attribute of an internal <tspan> element.

<text
     xml:space="preserve"
     x="1"
     y="0"
     dominant-baseline="hanging"><textPath
       xlink:href="#somepath"
       startOffset="50%"
       ><tspan
            style="text-align:center;letter-spacing:0px;text-anchor:middle"
	    dy="10">This text is kinda moved off the path</tspan></textPath></text>

This is documented pretty well in the textPath attributes section of the SVG2 spec.

External style sheets

You can either use a CSS @import rule, or use an HTML link element in (often in <defs>, but not necessarily) like so:

<link
    xmlns="http://www.w3.org/1999/xhtml"
    rel="stylesheet"
    href="/some-style.css"
    type="text/css" />

Internal style sheets

Sometimes you want to include the style directly in the SVG. This kind of internal style sheet is helpful for defining classes for Inkscape.

<style xml:space="preserve"><![CDATA[
/* Your style here */
.someclass {
    fill: #bcbcbc;
}
]]></style>

Inline icons

Sometimes you want icons to be the same size as surrounding text. I think the way to do this is to use text-relative heights like 1em.

<p>Hello <svg xmlns="http://www.w3.org/2000/svg" height="1em" width="1em" viewBox="0 0 10 10"><circle cx="5" cy="5" r="5" fill="blue"/></svg>.</p>

This gives:

Hello .

contenteditable Text

contenteditable="true" doesn’t seem to work on SVG <text> elements, at least in pure SVG contexts. For one printable template I had to use a <foreignObject> with a <xhtml:textarea> because it also didn’t seem to work on a plain <xhtml:div>.

Another workaround is to have the SVG enclosed in an XHTML context, where the XHTML context is contenteditable, like so:

<p contenteditable="true">
       This text is HTML <svg:svg xmlns:svg="http://www.w3.org/2000/svg" viewBox="0 0 100 25" width="100" height="25"><svg:g font-size="12" fill="blue" stroke="red" stroke-width="1"><svg:text x="0" y="12">This text is SVG</svg:text></svg:g></svg:svg>
      </p>

This text is HTML This text is SVG

Other miscellaneous

Very short notes for myself.

  • Use nested <svg/> elements for a coordinate transform.
  • If you define width="8.5in" height="11in" in inches (or similar measure), it doesn’t get resized with zoom in Firefox.
  • vector-effect="non-scaling-size" is a cool way to ensure that text doesn’t get shrunk down or up. It works in Inkscape (1.3) but not Firefox (125, see this bug). However, this vector effect is currently “at risk”.
  • For rendering SVG to PDF, see my Inkscape notes.
    The “cairosvg” package (nix-shell -p python311Packages.cairosvg) can also render an SVG to PDF like: cairosvg input.svg -o output.pdf
    However, this doesn’t work as well for me in terms of rasterizing filters.

Filters

Text readability filter v2

This text readability filter has an advantage over the old way by making text still selectable when rendered to a PDF. Basically, you apply the filter on a <use> that goes before the text. The <use> element then also references the text.

<filter id="blur4bg" style="color-interpolation-filters:sRGB">
  <feMorphology in="SourceGraphic" operator="dilate" radius="2" result="result1" />
  <feComposite in="FillPaint" in2="result1" operator="in" result="composite1" />
  <feGaussianBlur in="composite1" stdDeviation="1" result="blur" />
</filter>

We’ll also add the following CSS:

use.blurbg {
  filter: url(#blur4bg);
  user-select: none; /* This copy of a text element shouldn’t be selectable. */
  fill: white; /* Set the default fill. */
}

Then here’s how you’d add text:

<use class="blurbg" xlink:href="#useme" style="fill:lightcyan;"/>
<text id="useme">Hello there!</text>

Notice that we define an ID for the text, then reference that in the previous <use> element. This way also lets you choose the blur color using the fill. Here’s an example of what it looks like:

Hello there!

Text readability filter (old way)

This just adds a white blurred background behind text to make text easier to read when the background otherwise doesn’t have enough contrast.

<filter id="bgblur-white" style="color-interpolation-filters:sRGB">
  <feFlood flood-opacity="1" flood-color="white" result="flood" />
  <feMorphology in="SourceGraphic" operator="dilate" radius="1" result="result1" />
  <feComposite in="flood" in2="result1" operator="in" result="composite1" />
  <feGaussianBlur in="composite1" stdDeviation="1" result="blur" />
  <feOffset dx="0" dy="0" result="offset" />
  <feComposite in="SourceGraphic" in2="offset" operator="over" result="composite2" />
</filter>

Then to use the filter, set style="filter:url(#bgblur-white);".

Patterns

Here’s an example of a pattern for lined paper, kind of close to college rule, but not exactly:

<pattern
       width="96"
       height="96"
       patternUnits="userSpaceOnUse"
       patternContentUnits="userSpaceOnUse"
       id="collegerule1"
       viewBox="0,0,96,96">
      <g id="collegerulegroup">
        <rect style="opacity:1;vector-effect:none;fill:white;fill-opacity:1;stroke:none" width="96" height="96" x="0" y="0" />
        <path style="fill:none;stroke:#99c1f1;stroke-width:0.53333333;opacity:0.4" d="M 0,0 h 96" id="blueline1"/>
	<use x="0" y="0" xlink:href="#blueline1" transform="translate(0,24)" />
        <use x="0" y="0" xlink:href="#blueline1" transform="translate(0,48)" />
        <use x="0" y="0" xlink:href="#blueline1" transform="translate(0,72)" />
        <use x="0" y="0" xlink:href="#blueline1" transform="translate(0,96)" />
      </g>
    </pattern>

The <g id="collegerulegroup"> isn’t necessary, but helpful if you want to edit it in Inkscape by swapping it with a <use x="0" y="0" xlink:href="#collegerulegroup" />.

To use a pattern: style="fill:url(#collegerule1)".

Patterns don’t always seem as crisp as <use> elements.

Questions

Questions I have about SVG that I’ll try to figure out later

Wishlist

Some features I hope SVG gains

Mesh gradients
Instead of just interpolating colors linearly, mesh gradients let you interpolate between several points on a polygon. This is a feature that appears (IIRC) in SVG 2. Inkscape (see my Inkscape notes) has support for this. However, when exporting Inkscape has to use a mesh gradient JavaScript polyfill that renders the mesh gradient to a bitmap canvas.
Non-rectangular patterns
I forget what this is actually called, but it’d be nice to have patterns that aren’t just repeated rectangles. Perhaps some other kinds of tiling that makes repetition less evident.