Inside an SVG file: XML, paths & a byte-exact optimizer
An SVG is a picture written as text — a small XML program your browser runs to draw shapes. This is a tour of what's actually inside the file: the XML skeleton, the viewBox coordinate system, path-data syntax and gradients — and then how a dependency-free optimizer shrinks it, with real JavaScript you can lift.
What is SVG?
SVG — Scalable Vector Graphics — is a W3C standard, first published in 2001, for describing two-dimensional graphics as vectors: shapes, paths and text with exact coordinates, rather than a grid of pixels. Because it's vectors, an SVG is resolution-independent — the same file is crisp on a watch face and a billboard — and because it's plain XML text, you can read it, diff it, template it and edit it with nothing but a text editor.
That dual nature — a text file that is also an image — is exactly why it gets its own toolkit here: you can pretty-print and optimize it like source code, and render it like a picture. It's the format behind almost every icon set, logo and chart on the modern web.
The XML skeleton
Every SVG is an XML document whose root element is <svg>. Inside, elements are drawing primitives — <rect>, <circle>, <path>, <text> — grouped by <g> and painted in document order (later elements draw on top). Here is a complete, valid SVG:
<svg xmlns="http://www.w3.org/2000/svg"
width="120" height="120" viewBox="0 0 120 120">
<title>A badge</title> <!-- accessible name -->
<circle cx="60" cy="60" r="56" fill="#1b2a4a"/>
<g fill="#e8eef7">
<path d="M20 92 L60 36 L100 92 Z"/> <!-- a triangle -->
<text x="60" y="110" text-anchor="middle">PEAK</text>
</g>
</svg>
A few things worth naming, because they matter later:
- The
xmlnsattribute declares the SVG namespace. Without it a browser treats the markup as generic XML and draws nothing. - Presentation attributes (
fill,stroke,stroke-width,opacity) can sit on any element and inherit down through<g>, just like CSS. - Everything is coordinates and numbers — which is where the weight of a real-world SVG lives, and where an optimizer earns its keep.
The viewBox: SVG's coordinate system
The most misunderstood attribute in SVG is viewBox. It defines the user coordinate system — the rectangle of "SVG units" the drawing is authored in — as four numbers: min-x min-y width height. The width/height attributes (or CSS) then say how big to render that box on screen, and the browser scales between them.
<svg viewBox="0 0 100 100" width="400" height="400">
<!-- authored in a 100×100 grid, painted at 400×400: everything ×4 -->
<circle cx="50" cy="50" r="40"/>
</svg>
This is the whole reason SVG is "scalable": you draw once in convenient units, and the same coordinates render at any pixel size. It also means the numbers in the file are arbitrary-precision decimals — an authoring tool will happily write cx="49.99999983" where cx="50" would look identical. Multiply that across thousands of path points and you have real, removable bytes.
Path data: a mini-language inside an attribute
The <path> element is where SVG's expressive power (and its file size) concentrates. Its d attribute is a compact command language: a letter names an operation, numbers are its arguments. Uppercase letters use absolute coordinates; lowercase are relative to the current point.
| Command | Meaning | Example |
|---|---|---|
M x y | Move to (start a subpath) | M10 10 |
L x y | Line to | L90 90 |
H x / V y | Horizontal / vertical line | H90 |
C … | Cubic Bézier curve | C20 40 60 40 80 10 |
A … | Elliptical arc | A30 30 0 0 1 80 80 |
Z | Close the subpath | Z |
So <path d="M20 92 L60 36 L100 92 Z"/> means: move to (20, 92), line to (60, 36), line to (100, 92), close — a triangle. Curves work the same way with control points. A logo exported from design software is typically one or more of these paths with hundreds of high-precision coordinates each — legible to the renderer, wasteful on disk.
Gradients, defs and reuse
Fills aren't limited to flat colours. A <linearGradient> or <radialGradient> is defined once, usually inside <defs> (a holding pen for reusable definitions that don't draw themselves), given an id, and referenced by URL:
<defs>
<linearGradient id="sky" x1="0" y1="0" x2="0" y2="1">
<stop offset="0" stop-color="#1b2a4a"/>
<stop offset="1" stop-color="#3b6ea5"/>
</defs>
</defs>
<circle cx="60" cy="60" r="56" fill="url(#sky)"/> <!-- reference by id -->
The same url(#id) mechanism powers <use> (clone an element), clip paths, masks, patterns and filters. The catch for a tool that wants to shrink a file: because anything can reference an id, you can't blindly delete "unused" ids or rename them without tracing every reference. A safe optimizer leaves ids alone.
How to shrink an SVG — safely
Design tools produce enormous SVGs: editor metadata, comments, unused namespaces, a full RDF licence block, whitespace between every tag, and coordinates carrying twelve decimal places. The popular tool for cleaning this up is SVGO, which runs dozens of plugins. You don't need all of them — a handful of conservative passes recover most of the bytes without any risk of changing how the image looks:
- Drop the XML declaration and DOCTYPE.
<?xml …?>and the legacy SVG DOCTYPE are inert for a browser. - Remove comments and
<metadata>. Authoring notes and RDF licence blocks don't render. - Strip editor namespaces.
inkscape:andsodipodi:attributes and elements exist only for the editor. - Collapse insignificant whitespace. The newlines and indentation between elements are decorative.
- Round coordinate precision. Trimming numbers to three decimals — and dropping a leading zero (
0.5→.5) — is invisible on screen but adds up fast.
Deliberately not on that list: renaming or removing ids, merging paths, or rewriting shapes — each needs whole-document reference analysis to stay safe, and the payoff is small next to the passes above. On a typical Inkscape export the conservative set alone shrinks the file by a third to a half.
The optimizer, in code
Because SVG is just XML, the optimizer is a tree transform: parse to a node tree, filter and rewrite, re-serialise with no indentation. No dependency, no DOM — it runs in a Web Worker and in Node alike. Here is the core, condensed:
const CRUFT_NS = new Set(['inkscape', 'sodipodi']);
const NUMERIC = new Set(['d','points','transform','viewBox','x','y','cx','cy',
'r','rx','ry','width','height','x1','y1','x2','y2','offset','stroke-width']);
// Round one number to `p` decimals, then drop a leading zero: 0.500 -> .5
function compact(raw, p) {
const n = Number(raw); if (!Number.isFinite(n)) return raw;
return String(Math.round(n * 10**p) / 10**p).replace(/^(-?)0\./, '$1.');
}
const roundAll = (v, p) => v.replace(/-?(?:\d+\.\d+|\.\d+|\d+)/g, m => compact(m, p));
function transform(node, p) {
if (node.type === 'comment' || node.type === 'pi' || node.type === 'doctype') return null;
if (node.type === 'text') return node.value.trim() ? node : null; // drop pure whitespace
if (node.type !== 'element') return node;
const prefix = node.name.includes(':') ? node.name.split(':')[0] : '';
if (CRUFT_NS.has(prefix) || node.name === 'metadata') return null; // editor / RDF cruft
const attrs = node.attrs
.filter(([k]) => !CRUFT_NS.has(k.split(':')[0]) && !k.startsWith('xmlns:inkscape'))
.map(([k, v]) => [k, NUMERIC.has(k) ? roundAll(v, p) : v]);
const children = node.children.map(c => transform(c, p)).filter(Boolean);
return { ...node, attrs, children };
}
// Serialise with no whitespace between tags — that's the minification.
function serialize(n) {
if (n.type === 'text') return escapeXml(n.value);
const open = `<${n.name}${n.attrs.map(([k,v]) => ` ${k}="${escapeAttr(v)}"`).join('')}`;
return n.children.length
? open + '>' + n.children.map(serialize).join('') + `</${n.name}>`
: open + '/>';
}
That's the whole idea: transform prunes and rounds; serialize writes it back tight. Feed it the sample badge on the OPTIMIZE tab and it reports the exact byte savings and what it did — comments removed, editor attributes stripped, numbers rounded — all locally, with the file never leaving your browser.
Drop an SVG into the OmniViewer SVG tab to render it safely, pretty-print the source, run the optimizer and read its stats — no upload, no server.
Rendering an untrusted SVG safely
SVG is a program, not just data: it can carry <script>, event handlers and external references. That's why you should never inject an unknown SVG straight into your page's DOM. The safe way to show one is to render it as an image:
// An <img> renders SVG in "secure static mode": no scripts run,
// no external resources load. Wrap the bytes with the right MIME.
const blob = new Blob([svgText], { type: 'image/svg+xml' });
img.src = URL.createObjectURL(blob); // safe to display untrusted SVG
An <img> (or a background-image) puts the SVG in a sandbox where scripting and external fetches are disabled by specification. OmniViewer's PREVIEW tab renders exactly this way, so even a hostile file can be looked at without running anything — and the STATS tab flags whether a document contains a <script> or external references so you know what you're dealing with.
Scaling, and what the toolkit caps
An SVG is text, so the same rule every format on this site follows applies: window what you view, bound what you parse. The raw and hex views are windowed — they read only the byte range currently on screen via file.slice() — so they open a file of any size instantly. The FORMATTED, OPTIMIZE, TREE and STATS tabs parse the document into a tree in memory, so they work on a generous bounded prefix and tell you when a giant file was capped.
In practice SVGs are small — a complex illustration is a few hundred kilobytes — so the cap almost never bites. And it can't sensibly: a browser can't render a multi-gigabyte SVG anyway, so PREVIEW and OPTIMIZE (which need the whole, valid document) gate on size and point you at the windowed views for anything larger. The parser itself is a single-pass XML tokenizer with no back-references, so it streams — the bound is a policy choice about how much to hold, not a limit of the algorithm.
OmniViewer opens every file format in your browser — JSON, CSV, XML, PNG, Markdown and more — powered by the same windowed engine as fastjsonviewer.com and hugecsv.com. SVG is one of the formats with dedicated tooling.