This is a relatively long post serving as a technical reference. Don’t feel bad about skipping it.
I wanted to bring some of the custom vector graphics from my Making Software Quality Visible Keynote presentation into the Making Software Quality Visible page. I could’ve just taken Portable Network Graphics (PNG) screenshots and called it a day. However, since I’d just invested in updating my blog’s overall style and adding a dark mode, I challenged myself to do something a little nicer.
I decided to try converting some of these graphics to the Scalable Vector Graphics (SVG) format for two primary reasons:
- Scalability: SVGs retain a sharp resolution at any size.
- Stylability: When inlined in a web page, you can use Cascading Style Sheets (CSS) to update the colors of the SVG. (As opposed to referencing a separate SVG file using <img>, which doesn’t permit the page to change its style.)
I tried searching for any existing solutions for doing this, and found a hint or two, but no thorough references. So I successfully figured out how to do it a few months ago—and stupidly didn’t write down my process. When I needed to import another image recently for Formative Experiences at Google, I had to figure out some of the details all over again. Arguably I improved upon the process this time—but even better, I wrote it down.
So here’s the reference I was hoping someone else had already written, in case it’s useful to anyone else. It’s certainly going to be useful for me as I convert more of the images from my presentation.
Feel free to send me feedback, thoughts, or questions via email or by posting them on the LinkedIn announcement corresponding to this post.
Note 0: This post will stop just shy of going into detail about using CSS to update the SVG colors for dark mode. That topic merits a post in itself one day.
Note 1: This process presumes you’re using macOS, since this is about converting Keynote images. Also, while Keynote now supports importing SVG images, it still doesn’t support exporting them.
- Decide how far down the rabbit hole you want to go
- Decide how to handle fonts
- Export the image as a PDF
- Crop the PDF in Preview
- Convert the PDF to SVG using Inkscape
- Edit the SVG by hand
- Inspect the SVG in a browser before fine tuning
- Optional: Prepare for CSS styling
- Optional: Add a <style> element and CSS rules
- Ensure text elements are rendered properly across platforms
- Finished example
- Bonus: Convert the SVG to a PNG for email and web feeds
- Conclusion
- Footnotes
Decide how far down the rabbit hole you want to go
These steps describe how to make the smallest hand customized SVG I currently know how to make, suitable for inlining into HTML pages. If you don’t need the same degree of optimization or customization, or don’t have the patience to follow all the steps, that’s OK. For example:
- You don’t plan to embed the SVG directly, but only to include it via an <img> element.
- You don’t plan to update any of the colors (or styles in general).
- You’re not concerned about file sizes, and/or maintaining the exact appearance of text elements is a top priority.
- You don’t have a lot of time to do everything, at least not now.
In that case, you can stop after finishing the steps from Inspect the SVG in a browser before fine tuning. Note that most of the steps in Edit the SVG by hand are nice to have, but removing the icc-color() directives is essential.
Decide how to handle fonts
If the graphic you’re converting doesn’t contain any text or font glyphs, skip this section.
Otherwise, before exporting the image, think about the following options for handling any text and fonts:
- Convert all text to drawn <path> elements, described in a later
section, as opposed to storing plain text strings in the final image.
- Pros: The text should remain crisp and look the same across platforms. This makes exporting the image to SVG easier as well.
- Cons: The SVG code will be much larger and difficult to understand. Each letter will become a complete graphical element, instead of having entire readable strings rendered by the system font engine.
- Ensure the image uses only standard system fonts, which are already installed
or have close substitutes on most operating systems (primarily macOS,
UNIX/Linux, and Windows). You can then use CSS to specify the font within
the SVG file.
- Pros: The final SVG code will be much smaller and easier to understand. With the right styling, it will be portable across systems and faster to load and render (not that speed should prove much of a concern).
- Cons: There’s more tweaking required, as described much further below.
If you decide to use a system font, see the following references for options on which to use:
We’ll cover how to manage and style text using system fonts in a later section.
Export the image as a PDF
These steps aim to make sure the resulting SVG contains minimal coordinate transforms (specifically matrix operations). Otherwise, later steps will retain the original image dimensions and coordinate offsets, making the SVG larger, more complex, and more difficult to work with. In particular, adjusting the textLength attributes of <text> elements (as described below) becomes much easier after following these steps.
- Create a new Keynote file.
- Copy the slide from the original presentation that contains the desired graphic.
- Paste the copied slide into the new Keynote file.
- Remove all elements from the slide that you don’t want to keep in the graphic.
- Select all remaining graphic elements.
- If the Format panel on the right side of the window isn’t showing, click Format in the toolbar to reveal it.
- Click the Arrange tab in the Format panel.
- In the Format > Arrange panel:
- Click Group.
- Set X and Y both to 1 (or 0, whichever is desired).
- Click Ungroup until you can’t click it anymore.
- Export the presentation as a PDF.
Crop the PDF in Preview
This also prevents the resulting SVG from containing transforms based on the original image size.
- Open the PDF containing the exported image in Preview.
- Select Tools > Rectangular Selection.
- Drag the selection box as tightly around the graphic as you like.
- Use Tools > Crop to trim the extra space around the image.
- Save the file.
Convert the PDF to SVG using Inkscape
Inkscape is a free software graphics editing program for creating and editing SVG images. It’s also capable of importing PDF images and converting them to SVG.
- Open the PDF in Inkscape and set the following import options:
- Clip to: Crop box
- Fonts:
- If using system fonts as described earlier, select Keep missing fonts’ names.
- Otherwise select Draw all text.
- Precision of approximating gradient meshes: 1.0 rough
- Embed images: off
- Select Layers and Objects > path1. This should be the bounding box for the entire imported image.
- Delete path1.
- Select Edit > Resize Page to Selection (⇧⌘R) to trim the document boundaries to those of the actual graphic.
- Select all graphic elements with ⌘A.
- Ungroup all the elements with ⇧⌘G as many times as needed until no groups remain.
- Regroup objects as desired. Though not strictly required, this can make later inspection, styling, and editing easier.
- Reorder objects as desired by dragging them in the Layers and Objects
tab.
- Note that the groups and objects in Layers and Objects appear in the opposite order than they will appear in the SVG file. In other words, the first object will appear last, and the last object will appear first.
- Optional, but recommended: Install the inkscape-applytransforms
plugin and use it to flatten all <path> elements (by applying any
transform properties).
- Make sure layer 1 is the only object selected in the Layers and Objects tab, then select Extensions > Modify Path > Apply Transform.
- Note: It may report errors about not being able to convert <text> elements. This is OK; any matrix transforms for those elements will remain.
- Note: Some of the elements may appear to change. Don’t worry; they’ll appear correctly after editing the SVG by hand in the next section.
- Select File > Save As… and choose Optimized SVG with the following
options:
- Options:
- Unselect Collapse groups.
- Unselect Create groups for similar attributes.
- SVG output:
- Remove the XML declaration, metadata, and comments.
- Select Strip the ‘xml:space’ attribute from the root SVG element.
- IDs: Accept the defaults.
- Options:
Edit the SVG by hand
The resulting SVG image is basically an XML text file, and will contain a lot of extra information. These steps will guide you through removing that information, then adding elements and attributes to make it stylable.
- Open the SVG file with the text editor of your choice (i.e. Vim, a Vim variant, or any other editor or IDE with Vim keybindings).
- Remove the
width
,height
,version
, andxmlns:xlink
attributes of the root <svg> element.- Note that the viewBox attribute should remain. By removing
width
andheight
, the SVG will scale automatically to fit whatever container it’s in. - However, if you want to leave the
width
andheight
attributes for any reason, you’re welcome to do so.
- Note that the viewBox attribute should remain. By removing
- Remove the entire <defs> section.
- Add a <title> element.
- Add a <desc> element.
- Remove all the clip-path and xml:space attributes in the entire file,
e.g., using the Vim commands:
:%s/ clip-path="[^"]*"//
:%s/ xml:space="[^"]*"//
- Remove all the instances of icc-color() in the entire file, as it is an
Inkscape directive that browsers can’t parse:
:%s/ icc-color([^)]*)//g
- If all the other elements are wrapped in a plain <g> element with no attributes, feel free to remove this outer element.
Inspect the SVG in a browser before fine tuning
You can open the SVG file directly in Safari or the web browser of your choice. My preferred method is to use the macOS “open” command line utility, e.g.,
open -a safari ~/Desktop/my-image.svg
You can use the browser’s built in developer tools to inspect the SVG element by element. We’ll do this a little later to adjust text elements when using system fonts.
If the image doesn’t look right at this stage, retrace the above steps and try again. If all is well, keep going. You can even fine tune the code by hand at this point, if you see the opportunity and you’re comfortable doing so.
Optional: Prepare for CSS styling
As mentioned above, I’ll cover manipulating the style information in a separate post. However, you can take the following steps now to prepare for adding CSS rules in the future.
- Add a class attribute to the root <svg> element. I recommend
using the basename of the SVG file itself (without the
.svg
suffix), e.g.,<svg class="my-image" viewBox="0 0 ... ..." xmlns="...">
- Add empty class attributes to all <path> and <text>
elements:
:%s/<path /&class="" /
:%s/<text /&class="" /
- Add an empty <style> element.
Optional: Add a <style> element and CSS rules
If you’re already comfortable with CSS, feel free to convert any inline style attributes to CSS now rather than wait for my future post. The finished example later in this post will also include CSS information.
Note: Inkscape doesn’t properly handle the currentcolor CSS attribute. Any rules that use it will render the matching elements improperly. At this point, the image should already look the way you expect, so editing it further in Inkscape shouldn’t be necessary.
- Convert inline style information and attributes from individual SVG elements to CSS rules in the <style> element. Prime candidates include:
- Ensure all the style rules contain
svg.class-name
as the first component of the selector.class-name
must be the class name added to the root <svg> element in the first step of this section.- This helps ensure that the image’s styles are not accidentally influenced by other CSS rules, or that this SVG’s rules will influence other page elements.
- It doesn’t completely guarantee collisions won’t happen, but makes them far less likely and hopefully easier to correct.
- Remove the now redundant inline style information from the SVG elements
themselves, e.g.:
:%s/ fill="[^"]*"//
:%s/ stroke="[^"]*"//
:%s/ font-size="[^"]*"//
Ensure text elements are rendered properly across platforms
If you chose to embed the fonts earlier (instead of using system fonts), skip to the next section.
The steps below for ensuring system font text renders properly on different operating systems can be a little tricky and tedious. However, once you get used to the steps, they’re not so bad. You then gain the benefits of reduced image sizes and more readable SVG code.
These steps are necessary because Keynote appears to apply its own kerning to place each individual text character at a precise horizontal offset. The offsets appear within the x attribute of <tspan> elements in the SVG. For example:
<text transform="matrix(1.3333 0 0 1.3333 51.065 92.256)">
<tspan x="0 14.16 42.624001 59.52 87.071999 105.744 130.70399 158.256 186.72"
y="0">Intervene</tspan></text>
These offsets will make text elements on other systems look strange, as they’re customized for the original font, not whatever substitute font the system uses. Just deleting the offsets will also look strange, because the width of the text element may not remain consistent to the original. This can make the text look misaligned with other elements, even on the original platform.
Fortunately, by setting the textLength property appropriately, you can maintain the original text element proportions, and let the system font engine handle the kerning. Chances are you’ll barely notice the difference in kerning from the original:
<text transform="matrix(1.3333 0 0 1.3333 51.065 92.256)"
textLength="214.31">Intervene</text>
Note: You must use Safari for this part. While Chrome and Firefox allow you to inspect elements in a similar fashion, only Safari will show the correct text element widths for this step.
- Chrome and Firefox both show the final, rendered width.
- Safari shows the width relative to the SVG’s coordinate system.
- Since the <text> elements will likely still have matrix functions in their transform properties, we need the relative widths to set the textLength property correctly.
- Open the SVG file in your text editor and in Safari at the same time.
- In Safari, right click on the text element you wish to adjust and select Inspect element to open the web inspector.
- Take note of the exact width of the element in pixels from the Elements > Computed view of the web inspector. It will be the first number of the innermost blue box in the Box model.
- In the text editor, remove the <tspan> element itself, leaving only the inner text string.
- Add the
textLength="<width>"
attribute to the <text> element, replacing<width>
with the width of the text element in pixels reported by the Safari web inspector. - Reload the Safari tab to check that the updated SVG text renders correctly.
Keynote will sometimes break a single line of text into several elements. In this case, you can combine all the <text> elements comprising one line into a single element:
- Get the relative width of each element first, as described above.
- Combine the text from each element into a single <text> element.
- Assign the sum of the relative width of each element as the textLength of the new element.
- Reload the Safari tab to check that the updated SVG text renders correctly.
Finished example
Here’s a finished example—the exact example created using the above steps for Formative Experiences at Google.
First, here’s what the code looks like. Note that it contains other attributes
and CSS classes not mentioned above. Most of these are germane to the dark mode
matching that I’ll eventually describe in another post. The data-png
attribute
I’ll cover at the very end of this post.
<svg class="slide rod-google" viewBox="0 0 2310.7 922.28" xmlns="http://www.w3.org/2000/svg" data-png="rod-google">
<title>The Rainbow of Death: Google Testing Grouplet 2005-2010</title>
<desc>Illustration of the Google Testing Grouplet's activity from 2005 to 2010 using the Rainbow of Death model from <a href="https://mike-bland.com/the-rainbow-of-death">mike-bland.com/the-rainbow-of-death</a>.</desc>
<style>
svg.rod-google .match-text{color:black}
svg.rod-google .match-background{color:white;fill:currentcolor;stroke:none}
svg.rod-google .intervene{fill:#ee220c}
svg.rod-google .validate{fill:#feae00}
svg.rod-google .inform{fill:#ffd932}
svg.rod-google .inspire{fill:#56c1ff}
svg.rod-google .mentor{fill:#af59ae}
svg.rod-google .empower{fill:#1db100}
svg.rod-google text{font-family:"Helvetica Neue",Helvetica,Arial,-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,Oxygen-Sans,Ubuntu,Cantarell,sans-serif;font-size:48px;fill:currentcolor}
svg.rod-google text.rainbow{fill:black;font-weight:bold}
svg.rod-google text.item{fill:black;font-weight:bold;font-size:42px}
svg.rod-google path{stroke:black;stroke-width:3}
svg.rod-google path.spectrum-end{fill:currentcolor;stroke:currentcolor}
svg.rod-google path.spectrum{fill:none;stroke:currentcolor;stroke-width:5}
</style>
<g>
<path class="has-color intervene" d="m1.9129 1.3333h384v133.33h-384z"/>
<text class="rainbow" transform="matrix(1.3333 0 0 1.3333 51.065 92.256)" textLength="214.31">Intervene</text>
</g>
<g>
<path class="has-color validate" d="m386.57 1.3333h384v133.33h-384z"/>
<text class="rainbow" transform="matrix(1.3333 0 0 1.3333 457.74 92.256)" textLength="181.3">Validate</text>
</g>
<g>
<path class="has-color inform" d="m771.05 1.3333h384v133.33h-384z"/>
<text class="rainbow" transform="matrix(1.3333 0 0 1.3333 862.99 92.256)" textLength="150.16">Inform</text>
</g>
<g>
<path class="has-color inspire" d="m1155.5 1.3333h384v133.33h-384z"/>
<text class="rainbow" transform="matrix(1.3333 0 0 1.3333 1243.9 92.256)" textLength="155.52">Inspire</text>
</g>
<g>
<path class="has-color mentor" d="m1540 1.3333h384v133.33h-384z"/>
<text class="rainbow" transform="matrix(1.3333 0 0 1.3333 1622.4 92.256)" textLength="164.48">Mentor</text>
</g>
<g>
<path class="has-color empower" d="m1924.5 1.3333h384v133.33h-384z"/>
<text class="rainbow" transform="matrix(1.3333 0 0 1.3333 1970.8 92.256)" textLength="215.58">Empower</text>
</g>
<g>
<path class="spectrum match-text" d="m2290.8 228.94h-3.33-2265.7-3.3333"/>
<path class="spectrum-end match-text" d="m28.533 215.34-27.2 13.6 27.2 13.6-6.8-13.6z"/>
<path class="spectrum-end match-text" d="m2280.7 242.54 27.2-13.6-27.2-13.6 6.8 13.6z"/>
<path class="match-background" d="m199.93 271.38h373.33v-84.875h-373.33z"/>
<text class="match-text" transform="matrix(1.3333 0 0 1.3333 228.9 252.41)" textLength="236.58">Dependent</text>
<path class="match-background" d="m1713.3 271.38h421.34v-84.874h-421.34z"/>
<text class="match-text" transform="matrix(1.3333 0 0 1.3333 1743.7 252.41)" textLength="270.36">Independent</text>
</g>
<g>
<path class="has-color inform" d="m771.05 317.88h384v177.37h-384z"/>
<text class="item" transform="matrix(1.3333 0 0 1.3333 856.79 393.44)" textLength="159.28">Noogler</text>
<text class="item" transform="matrix(1.3333 0 0 1.3333 856.79 461.44)" textLength="159.22">Training</text>
</g>
<g>
<path class="has-color inform" d="m771.05 494.68h384v106.67h-384z"/>
<text class="item" transform="matrix(1.3333 0 0 1.3333 836.57 568.9)" textLength="189.56">Codelabs</text>
</g>
<g>
<path class="has-color inform" d="m771.05 600.93h384v106.67h-384z"/>
<text class="item" transform="matrix(1.3333 0 0 1.3333 824.11 675.16)" textLength="208.25">Tech Talks</text>
</g>
<g>
<path class="has-color inform" d="m771.05 706.93h384v106.67h-384z"/>
<text class="item" transform="matrix(1.3333 0 0 1.3333 805.91 781.16)" textLength="235.63">Free books!</text>
</g>
<g>
<path class="has-color inform" d="m771.05 813.28h384v106.67h-384z"/>
<text class="item" transform="matrix(1.3333 0 0 1.3333 904.98 887.5)" textLength="86.94">TotT</text>
</g>
<g>
<path class="has-color validate" d="m386.57 318.2h384v601.85h-384z"/>
<text class="item" transform="matrix(1.3333 0 0 1.3333 523.61 605.76)" textLength="82.34">Test</text>
<text class="item" transform="matrix(1.3333 0 0 1.3333 463.49 673.76)" textLength="171.38">Certified</text>
</g>
<g>
<path class="has-color intervene" d="m1.9129 318.2h384v301.33h-384z"/>
<text class="item" transform="matrix(1.3333 0 0 1.3333 64.273 489.76)" textLength="194.31">Tools Dev</text>
</g>
<g>
<path class="has-color intervene" d="m1.9129 618.93h384v301.33h-384z"/>
<text class="item" transform="matrix(1.3333 0 0 1.3333 138.95 757.16)" textLength="82.34">Test</text>
<text class="item" transform="matrix(1.3333 0 0 1.3333 28.013 825.16)" textLength="248.7">Mercenaries</text>
</g>
<g>
<path class="has-color inspire" d="m1155.5 318.67h384v133.33h-384z"/>
<text class="item" transform="matrix(1.3333 0 0 1.3333 1203.3 406.22)" textLength="216.16">GWS Story</text>
</g>
<g>
<path class="has-color inspire" d="m1155.5 451.98h384v133.33h-384z"/>
<text class="item" transform="matrix(1.3333 0 0 1.3333 1207 539.54)" textLength="210.56">Build Orbs</text>
</g>
<g>
<path class="has-color inspire" d="m1155.5 585.56h384v201.43h-384z"/>
<text class="item" transform="matrix(1.3333 0 0 1.3333 1195.1 673.12)" textLength="228.45">All Projects</text>
<text class="item" transform="matrix(1.3333 0 0 1.3333 1208 741.12)" textLength="209.13">TC Level 3</text>
</g>
<g>
<path class="has-color inspire" d="m1155.4 786.69h384v133.33h-384z"/>
<text class="item" transform="matrix(1.3333 0 0 1.3333 1276.4 874.25)" textLength="106.31">Fixits</text>
</g>
<g>
<path class="has-color mentor" d="m1540 318.67h384v301.33h-384z"/>
<text class="item" transform="matrix(1.3333 0 0 1.3333 1636.1 456.89)" textLength="143.67">Testing</text>
<text class="item" transform="matrix(1.3333 0 0 1.3333 1616.4 524.89)" textLength="173.33">Grouplet</text>
</g>
<g>
<path class="has-color mentor" d="m1540.2 618.93h384v301.33h-384z"/>
<text class="item" transform="matrix(1.3333 0 0 1.3333 1554.4 757.16)" textLength="265.48">Test Certified</text>
<text class="item" transform="matrix(1.3333 0 0 1.3333 1621.2 825.16)" textLength="166.3">Mentors</text>
</g>
<g>
<path class="has-color empower" d="m1924.7 318.67h384v133.33h-384z"/>
<text class="item" transform="matrix(1.3333 0 0 1.3333 1990.7 406.22)" textLength="188.86">Tools dev</text>
</g>
<g>
<path class="has-color empower" d="m1924.7 452.6h384v259.89h-384z"/>
<text class="item" transform="matrix(1.3333 0 0 1.3333 1974.1 569.49)" textLength="213.69">Revolution</text>
<text class="item" transform="matrix(1.3333 0 0 1.3333 2060.7 637.49)" textLength="83.81">Fixit</text>
</g>
<g>
<path class="has-color empower" d="m1924.7 711.26h384v209.01h-384z"/>
<text class="item" transform="matrix(1.3333 0 0 1.3333 2000.6 837.48)" textLength="174.08">TAP Fixit</text>
</g>
</svg>
And here’s the result—don’t forget to hover to see the <title> text as a tooltip:
If you’re reading this in an email or via the web feed, however, you may notice the image above is a PNG, not an SVG.
Bonus: Convert the SVG to a PNG for email and web feeds
The reason it’s a PNG in email is because SVG isn’t very well supported in most email clients. The reason it’s a PNG in the web feed is because, apparently, SVGs can contain <script> elements, which is a potential security vulnerability:
- CVE-2023-34845: Unrestricted Upload of File with Dangerous Type (CWE-434)
- CVE-2023-34944: Unrestricted Upload of File with Dangerous Type (CWE-434)
- HackerNoon: The Dangers of SVG Files: A Lesser-Known Vector for XSS Attacks
- Cross-site Scripting Injection Attacks Using SVG Images
This is probably why SVG isn’t yet widely supported in email clients, either.1
I realized I have to provide PNG versions of my SVG images for email and web feeds after all. I found the following steps for generating PNGs in a response to “Command-line application for converting SVG to PNG on Mac OS X” on superuser.com:
- Open the
.svg
file in Safari. - Press ⌥⌘I to open the web inspector.
- Right-click on the
<svg>
tag and select Capture Screenshot.- Note that you mustn’t zoom in on the image.
Since I’m mindful of supporting dark mode in email, I didn’t want to provide only an image with a pure white background. However, trying to switch images for dark mode in email is janky and fraught with peril. Also, designing the original image to look good on both light and dark backgrounds without CSS color adjustments wasn’t a priority.
Basically, this is where my patience ran out, so I decided to just generate one
image with a light gray background as a compromise. Before I took the
screenshot, I added a CSS rule and tweaked another rule to set the background to
#ccc
:
<style>
svg.rod-google {background:#ccc}
...
svg.rod-google .match-background{color:white;fill:#ccc;stroke:none}
...
For those of you reading this on the blog, this is what the PNG looks like:
Perhaps one day I’ll update how I do this, but I feel it’s good enough for now. Those reading the actual blog get the full experience. Those reading on email or feed readers get a slightly degraded experience, but they can click the post title to see the original post.
The final consideration is how I include SVG images in the HTML for the blog and PNG images in email and in the web feed.
Each .svg
file is embedded directly in the original HTML using Jekyll
includes. I save the corresponding .png
files in the /images
folder.
Notice the data-png
data attribute in the code above. This is my own
custom attribute that encodes the basename of the corresponding .png
file
inside the <svg>
element. I then use this attribute, along with the
<title> and <desc> elements, to generate <img>
elements in both the email and web feed generators:
By convention, the .svg
and .png
files share the same basename, but they
could be different if I wanted. I prefered adding the data-png
attribute to
parsing the file name out of the class
attribute, even though the names should
always match. Overloading class
could lead to breakage if I decided to use
some other file name, or decided to update the class of the <svg>
element.
Conclusion
There may be a better way of doing any or all of this, but this is what works for me. I’m not a graphic designer, so being able to use Keynote to add some high quality graphics to my blog is a big upgrade. Plus, as you can see, it’s been a hell of a learning experience.
It may be possible to automate more of the process, but I haven’t yet tried. When one is in the “Exploration Phase,” it’s frequently best to figure out what you need to do via manual experimentation before jumping into automation. That said, having written out the process requirements and repeated the process successfully several times, I think I’ve shifted into the “Settlement Phase.” There may be more automation coming in the future—though I need to get back to writing for now.
Either way, given all the details involved, my future me will thank me, even if nobody else does.
Footnotes
-
The last two articles in the list mention that a Content Security Policy can prevent Cross Site Scripting (XSS) attacks, including those from SVG images. You can see in your browser’s web inspector that I have a Content-Security-Policy HTTP Header configured for each of my HTML pages. No need to worry about any of my inline SVGs wreaking havoc. ↩