SVG
Render diagrams as SVG, style them with CSS variables, and handle untrusted input.
The svg package renders a scene as an SVG document that looks the way Pele draws it. Like the rest of limu, it uses only Go’s standard library.
1sc, err := limu.Layout(src, nil)2if err != nil {3 return err4}5out := svg.Render(sc, &svg.Options{6 Look: style.Look{Theme: style.Kohu()},7 FontFamily: "Arial, Helvetica, sans-serif",8})Options
| Field | What it does |
|---|---|
Look | Sets the theme and overrides. See Theming. |
Literal | Writes theme colors as plain colors instead of CSS variables. |
FontFamily, MonoFamily | Name the fonts the scene was measured with. They default to sans-serif and monospace. |
LinkSchemes, ImageSchemes | Set the URL schemes that links and images may use besides relative URLs. See Untrusted diagrams. |
IconMarkup | Returns the markup for an icon by name. See Icons. |
Draw | Writes each item in place of the default. See Custom drawing. |
Fonts
Layout measures text with text.Arial unless you give it another measurer. The SVG has to use the same fonts the text was measured with, or labels won’t fit their shapes. With text.Arial, use Arial or a font of the same width.
1svg.Render(sc, &svg.Options{FontFamily: "Arial, Helvetica, sans-serif"})If a page sets --limu-font or --limu-font-mono, those take precedence. A page that measures text in its own font can use them to set the SVG in that font too.
Theme variables
The SVG writes each theme color as a CSS variable with the theme’s color as the fallback, as in var(--limu-surface,#e5eeec). When the SVG is inline in a page, any variables the page sets win. That lets the page change the colors, for example between light and dark themes, without rendering the diagram again.
1.diagram {2 --limu-bg: #f1f6f5;3 --limu-fg: #0f262a;4 --limu-line: #23393d;5 --limu-surface: #e5eeec;6 --limu-border: #76939a;7 --limu-accent: #a92e4a;8}910@media (prefers-color-scheme: dark) {11 .diagram {12 --limu-bg: #0c1618;13 --limu-fg: #d0dddc;14 --limu-line: #b4c7c7;15 --limu-surface: #142428;16 --limu-border: #557176;17 --limu-accent: #ee8494;18 }19}| Variable | Token |
|---|---|
--limu-bg | Bg |
--limu-fg | Fg |
--limu-muted | Muted |
--limu-line | Line |
--limu-surface | Surface |
--limu-surface-alt | SurfaceAlt |
--limu-border | Border |
--limu-accent | Accent |
--limu-critical | Critical |
--limu-series-1 … --limu-series-8 | Series(0) … Series(7) |
--limu-font, --limu-font-mono | The fonts described above |
Theming lists the values of limu’s themes. Colors that don’t come from the theme, such as those a diagram’s author sets, are written as plain colors and ignore the variables.
An SVG shown through <img> or saved to a file can’t see the page’s variables, so it uses the fallbacks. Set Literal for programs that read SVG but don’t understand CSS variables.
Classes
Every item’s group carries classes that describe it, so a page’s CSS can target them.
| Class | Element |
|---|---|
limu, limu-<type> | The root <svg>, such as limu-flowchart |
limu-node, limu-group, limu-edge, limu-text, limu-mark | An item’s group, by kind |
limu-<role> | An item’s group, by role with the dot replaced by a dash, such as limu-flowchart-node |
limu-shape-<shape> | An item’s group, by shape, such as limu-shape-diam |
| the item’s tags and classes | Extra facts the diagram records, such as a gantt task’s done, and the classes the author assigned |
limu-marker | A marker at the end of an edge |
limu-icon | An icon |
Items with an id also get a data-id attribute.
1.limu-flowchart-node[data-id="scene"] rect {2 stroke: var(--limu-accent);3 stroke-width: 2;4}The root’s width, height and viewBox match the scene’s size. To let it shrink on a narrow page, give it max-width: 100% and height: auto.
For assistive technology, the diagram’s accTitle and accDescr become its <title> and <desc>, and the root has the role graphics-document.
Icons
Labels can contain icons such as fa:fa-car, and flowchart nodes can show one. IconMarkup returns the markup that goes inside an icon’s <svg> on a 24 by 24 grid. The markup should draw in currentColor, which is set to the item’s text color. Layout leaves room in labels only for icons you report through layout.Options.Icons, so give both the same set.
1icons := map[string]string{2 "fa:fa-car": `<path d="M5 17h14v-5l-2-5H7l-2 5z" fill="currentColor"/>`,3}4sc, err := limu.Layout(src, &layout.Options{5 Icons: func(name string) bool { _, ok := icons[name]; return ok },6})7out := svg.Render(sc, &svg.Options{8 IconMarkup: func(name string) string { return icons[name] },9})Without IconMarkup, icons are left out. Architecture and tree view diagrams also have built-in icons, which need no markup.
Custom drawing
Draw writes each item in place of the default, and next writes it the default way. The svg.Writer it receives can write the default pieces with Item, Parts, Ends, Picture and Label, and your own markup with Raw, escaped text with Text, and numbers with Num. The style it receives is cascaded from the look but not yet resolved to colors, so Paint and PaintAttr can still write theme colors as CSS variables.
1svg.Render(sc, &svg.Options{2 Look: look,3 Draw: func(w *svg.Writer, it *scene.Item, s style.Style, next func()) {4 if it.Kind != scene.Node {5 next()6 return7 }8 // Draw the node as a box of our own, and its label the default way.9 b := it.Box10 w.Raw(`<rect rx="8" x="`)11 w.Num(b.X)12 w.Raw(`" y="`)13 w.Num(b.Y)14 w.Raw(`" width="`)15 w.Num(b.W)16 w.Raw(`" height="`)17 w.Num(b.H)18 w.Raw(`"`)19 w.PaintAttr("fill", s.Fill)20 w.Raw(`/>`)21 w.Label(it, s)22 },23})Untrusted diagrams
Diagram text sometimes comes from people you don’t trust, for example in notes shared with your app. limu escapes all the text it writes, and it never writes scripts, event handlers or HTML. A click that calls a function is dropped. The rest is up to your app.
Links and images
Diagrams can link parts to URLs and show images from URLs. Layout keeps a link or an image only if its URL is relative or uses a scheme you allow.
| Used for | Schemes allowed by default |
|---|---|
| Links | http, https, mailto, tel |
| Images | http, https |
javascript:, data: and vbscript: are always blocked, and so are network paths such as //host/share. Images make a network request when the SVG is displayed. To drop all links or all images, set NoLinks or NoImages in layout.Options.
Schemes are checked twice, once by layout using layout.Options.LinkSchemes and ImageSchemes, and again by the svg package using its own options as a final check. To allow another scheme, add it to both.
1schemes := []string{"http", "https", "mailto", "obsidian"}2sc, err := limu.Layout(src, &layout.Options{LinkSchemes: schemes})3out := svg.Render(sc, &svg.Options{LinkSchemes: schemes})Links are written as <a href> without a target. When the SVG is inline, the page decides how links open, just as it does for its own links.
Limits
By default, diagram text is limited to 50,000 UTF-16 code units, and flowcharts and agent flows to 5,000 edges. See Limits. limu can’t stop a layout once it has started, so keep these limits for untrusted text.
What your app is responsible for
The svg package inserts the markup from IconMarkup as is. Only return markup from a set you trust, return nothing for unknown names, and never put the name itself into the markup.
Error messages can quote the diagram, so display them as text, not markup.
limu reads an author’s styles property by property and writes them as attributes, and it drops values that could reach outside the diagram, such as url(). Don’t let diagram authors set your options, such as diagram.Options.Config or the allowed schemes.