Repository navigation
SVG Output
Code: src/SvgCreator/Svg/SvgWriter.cs
<svg width="W" height="H" viewBox="0 0 W H" xmlns="http://www.w3.org/2000/svg">
<g fill="#000" fill-rule="evenodd" stroke="none">
<path d="M…Z M…Z" /> <!-- one path per component: outer border + holes -->
</g>
</svg>-
Shared attributes. Fill, fill rule and stroke are set once on the
<g>rather than on every path. -
Safe XML. The document is written with
XmlWriter, which escapes attribute values. An injectedFillColorsuch as"><script>can't break out of the attribute; there's a test for this. - No XML declaration. It's omitted because the output is a .NET string (UTF-16), and a declaration would state the wrong encoding once the file is saved as UTF-8.
SvgConverter runs in two halves: LineArtTracer turns the image into a TracedDrawing (canvas size plus fitted outlines), and SvgWriter renders it. How it looks is an SvgStyle, built from two kinds of layer:
| Layer | Interface | What it does | Example |
|---|---|---|---|
| Ink filter (0 or 1) | IInkFilter |
A <filter> in <defs>, applied to the ink group and to every decoration, so everything looks printed the same way |
StampFilter |
| Decorations (0 or more) | IDecoration |
Generated (not traced) artwork in its own <g> after the ink. It can ask for a wider canvas |
PostmarkCancellation |
An OutputStyle is just a named combination of layers (SvgStyle.For): Stamp is the stamp filter alone, and Postmark is the stamp filter plus the cancellation decoration.
Where new artwork goes. Ask what the canvas is:
| The new artwork… | Goes in | Example |
|---|---|---|
| is drawn over a traced drawing, on that drawing's canvas | A new IDecoration plus one line in SvgStyle.For. SvgWriter doesn't change |
PostmarkCancellation; a straight-bar or slogan cancellation would go here too |
| defines its own canvas, or doesn't start from a traced drawing | Its own generator, writing through SvgWriter.WriteDocument with SvgStyle.Clean
|
The postage stamp: its canvas is the perforated paper, and its picture isn't traced. The envelope: no input image at all |
| combines several finished pieces | The composer (Composition/), which nests each generator's output as an inner <svg>
|
The mail piece: a stamp with a postmark over it, on an envelope |
So a perforated edge is never a decoration. It belongs to the stamp's paper, and the stamp generator owns that.
Golden tests. SvgGoldenTests pins the exact SVG text of every style in tests/SvgCreator.Tests/Golden/, so a refactor can't change the output unnoticed. After an intentional output change, run UPDATE_GOLDEN=1 dotnet test and review the diff of those files.
After curve fitting, the segment count dropped by 45%, but the files roughly doubled in size. A cubic C command has 6 numbers to an L command's 2, and they were printed with 2 decimals each.
| Version | image03 size |
|---|---|
Polygon L commands, integer coordinates |
13.3 KB |
Absolute C commands, 2 decimals |
28.7 KB |
Relative c/l commands, 0.1 px precision |
17.5 KB |
-
Relative commands. Each subpath starts with an absolute
M, then lowercasec/lwith deltas from the previous point, which are small numbers with few digits. -
Tenths precision. 0.1 px is invisible at any normal zoom. This is for traced geometry and path data, which are measured in image pixels. Values computed as fractions of a picture, such as everything the postage stamp writes (its paper path included), use up to four decimals instead (
SvgFormat.Number), because they can be far below 0.1 px on a small picture. - No rounding drift. Coordinates are rounded to integer tenths first, and each delta is taken between rounded points. If you rounded the deltas themselves, the errors would pile up along a long path.
-
Shortest number form.
0.5→.5,-0.5→-.5. A space is only inserted where a digit would otherwise run into the next number; a minus sign separates numbers by itself. -
Implicit closing line. If the last segment is a straight line back to the start, it's left out, because
Zdraws it. -
Straight curves become lines. A cubic whose control points lie within 0.1 px of the chord is written as
l.
Example: M2 1c1.3 4.7 10 5 10 .5c0-3.5-9.5-3.5-10-.5Z