Getting Started
Sevgi creates SVG with Ruby. A drawing can be an executable .sevgi script or a value built inside another Ruby
application.
Script and library modes
Use script mode when the drawing is the program, such as a generated asset or build job. The runner supplies Sevgi's entry points, and the script usually writes or prints its result:
#!/usr/bin/env -S ruby -S sevgi
canvas = Canvas width: 24, height: 24, unit: :px
SVG :minimal, canvas do
circle cx: 12, cy: 12, r: 10, fill: "tomato"
end.Save "badge.svg"
Use library mode when an application owns the drawing and decides where its rendered string goes:
require "sevgi"
canvas = SVG.Canvas width: 24, height: 24, unit: :px
drawing = SVG :minimal, canvas do
circle cx: 12, cy: 12, r: 10, fill: "tomato"
end
File.write "badge.svg", drawing.Render
The SVG block is the same in both modes. Operations around it are bare words in a script and capitalized methods on
the SVG facade in library code:
| Role | .sevgi script | Ruby library |
|---|---|---|
| Build a document | SVG(...) | SVG(...) |
| Create a canvas | Canvas(...) | SVG.Canvas(...) |
| Refer to the canvas type | SVG::Canvas | SVG::Canvas |
The script passes its document to Save. The application keeps the document and passes its Render result to ordinary
Ruby code. Script Mode and Library Mode cover each form in detail.
See a complete drawing
The tabs below come from the same files that the test suite runs. The Ruby tab contains the script; the SVG tab contains its output.
#!/usr/bin/env -S ruby -S sevgi
# frozen_string_literal: true
LED_COUNT = 10
LED_GAP = 2
LED_WIDTH = 10
TOTAL_WIDTH = (LED_COUNT * (LED_WIDTH + LED_GAP)) + 8
dim = proc do |led, x:, **|
led[:"fill-opacity"] = 0.3 if x >= 7
end
SVG :minimal, width: TOTAL_WIDTH, height: 30, viewBox: "0 0 #{TOTAL_WIDTH} 30" do
rect x: 2, y: 2, width: TOTAL_WIDTH - 4, height: 26,
rx: 4, fill: "none", stroke: "#666"
TileX "led", n: LED_COUNT, d: LED_WIDTH + LED_GAP, o: 5, proc: dim do
rect y: 5, width: LED_WIDTH, height: 20, rx: 3, fill: "green"
end
end.Save
#!/usr/bin/env -S ruby -S sevgi
# frozen_string_literal: true
LED_COUNT = 10
LED_GAP = 2
LED_WIDTH = 10
TOTAL_WIDTH = (LED_COUNT * (LED_WIDTH + LED_GAP)) + 8
dim = proc do |led, x:, **|
led[:"fill-opacity"] = 0.3 if x >= 7
end
SVG :minimal, width: TOTAL_WIDTH, height: 30, viewBox: "0 0 #{TOTAL_WIDTH} 30" do
rect x: 2, y: 2, width: TOTAL_WIDTH - 4, height: 26,
rx: 4, fill: "none", stroke: "#d8d8d8"
TileX "led", n: LED_COUNT, d: LED_WIDTH + LED_GAP, o: 5, proc: dim do
rect y: 5, width: LED_WIDTH, height: 20, rx: 3, fill: "#7bd88f"
end
end.Save
<svg width="128" height="30" viewBox="0 0 128 30">
<rect x="2" y="2" width="124" height="26" rx="4" fill="none" stroke="#666"/>
<defs>
<g id="led">
<rect y="5" width="10" height="20" rx="3" fill="green"/>
</g>
</defs>
<use id="led-1" href="#led" class="tile-col-1 tile-col-first" x="5"/>
<use id="led-2" href="#led" class="tile-col-2" x="17"/>
<use id="led-3" href="#led" class="tile-col-3" x="29"/>
<use id="led-4" href="#led" class="tile-col-4" x="41"/>
<use id="led-5" href="#led" class="tile-col-5" x="53"/>
<use id="led-6" href="#led" class="tile-col-6" x="65"/>
<use id="led-7" href="#led" class="tile-col-7" x="77"/>
<use id="led-8" href="#led" class="tile-col-8" x="89" fill-opacity="0.3"/>
<use id="led-9" href="#led" class="tile-col-9" x="101" fill-opacity="0.3"/>
<use id="led-10" href="#led" class="tile-col-10 tile-col-last" x="113" fill-opacity="0.3"/>
</svg>
<svg width="128" height="30" viewBox="0 0 128 30">
<rect x="2" y="2" width="124" height="26" rx="4" fill="none" stroke="#d8d8d8"/>
<defs>
<g id="led">
<rect y="5" width="10" height="20" rx="3" fill="#7bd88f"/>
</g>
</defs>
<use id="led-1" href="#led" class="tile-col-1 tile-col-first" x="5"/>
<use id="led-2" href="#led" class="tile-col-2" x="17"/>
<use id="led-3" href="#led" class="tile-col-3" x="29"/>
<use id="led-4" href="#led" class="tile-col-4" x="41"/>
<use id="led-5" href="#led" class="tile-col-5" x="53"/>
<use id="led-6" href="#led" class="tile-col-6" x="65"/>
<use id="led-7" href="#led" class="tile-col-7" x="77"/>
<use id="led-8" href="#led" class="tile-col-8" x="89" fill-opacity="0.3"/>
<use id="led-9" href="#led" class="tile-col-9" x="101" fill-opacity="0.3"/>
<use id="led-10" href="#led" class="tile-col-10 tile-col-last" x="113" fill-opacity="0.3"/>
</svg>
Run an example
From a checkout, run the showcase scripts with Bundler:
bundle exec showcase/srv/meter.sevgi
The script writes showcase/srv/meter.svg because it ends with Save. To write SVG to standard output instead,
use Out in the script.
Install Sevgi
For the complete command-line toolkit, install Sevgi through Homebrew on macOS or Linux:
brew install roktas/tap/sevgi
This installs the sevgi executable, Ruby, the native PDF and PNG export stack, and the headless pdfcpu and Poppler
tools. Inkscape remains an optional external backend.
When Sevgi is a dependency of a Ruby application, add the umbrella gem to its bundle instead:
gem "sevgi"
The umbrella gem is the right choice for most applications and drawing scripts. It installs the script runner, the
SVG facade, the Appendix development extras, and all runtime components.
Choose a gem
Libraries that need a smaller dependency surface can install focused component gems:
| Scenario | Install | Entry point |
|---|---|---|
| Build and render SVG only | sevgi-graphics | require "sevgi/graphics" |
| Build and validate SVG without the full toolkit | sevgi-graphics sevgi-standard | require "sevgi/graphics" |
| Use geometry values and transformations without the DSL | sevgi-geometry | require "sevgi/geometry" |
| Convert SVG or XML back into Sevgi source | sevgi-derender | require "sevgi/derender" |
| Use grids, rulers, tiles, or export integrations | sevgi-sundries | require "sevgi/sundries" |
Package the agent skill or lint .sevgi source | sevgi-appendix | require "sevgi/appendix" or the RuboCop plugin |
For example, a service that only builds SVG can install sevgi-graphics and use
Sevgi::Graphics.SVG(...). The full SVG facade and the sevgi executable belong to the umbrella gem. Add
sevgi-standard when the service should validate element and attribute names. Bundler installs shared support gems
such as sevgi-function as transitive dependencies of the components that use them. The umbrella gem adds the
sevgi --skill query for locating the matching Appendix skill.
SVG-only library use needs no native graphics packages. Applications that install gems directly and use PDF or PNG
export must provide the optional cairo, rsvg2, and hexapdf gems and their system libraries themselves. See the
sevgi-sundries package guide for those prerequisites.
The full installation also packages Sevgi's agent skill. Run sevgi --skill to locate it, then follow the
Appendix setup guide.
Choose a document profile
SVG accepts a document profile. :minimal produces compact output, :default writes a standalone SVG document,
:html is suitable for embedding, and :inkscape adds editor metadata and helpers. See the
document-profile matrix for the exact capabilities.
#!/usr/bin/env -S ruby -S sevgi
# frozen_string_literal: true
# Example taken from: https://svg-tutorial.com/svg/use
SVG :minimal, width: 200, height: 200, viewBox: "-100 -100 200 200" do
path id: "branch", stroke: "purple", "stroke-width": 5, d: <<~PATH
M 0 0 L 0 -90
M 0 -20 L 20 -34
M 0 -20 L -20 -34
M 0 -40 L 20 -54
M 0 -40 L -20 -54
M 0 -60 L 20 -74
M 0 -60 L -20 -74
PATH
60.step(300, 60) { use(href: "#branch").Rotate it }
end.Save
#!/usr/bin/env -S ruby -S sevgi
# frozen_string_literal: true
# Example taken from: https://svg-tutorial.com/svg/use
SVG :minimal, width: 200, height: 200, viewBox: "-100 -100 200 200" do
path id: "branch", stroke: "#e5c39c", "stroke-width": 5, d: <<~PATH
M 0 0 L 0 -90
M 0 -20 L 20 -34
M 0 -20 L -20 -34
M 0 -40 L 20 -54
M 0 -40 L -20 -54
M 0 -60 L 20 -74
M 0 -60 L -20 -74
PATH
60.step(300, 60) { use(href: "#branch").Rotate it }
end.Save
<svg width="200" height="200" viewBox="-100 -100 200 200">
<path
id="branch"
stroke="purple"
stroke-width="5"
d="M 0 0 L 0 -90
M 0 -20 L 20 -34
M 0 -20 L -20 -34
M 0 -40 L 20 -54
M 0 -40 L -20 -54
M 0 -60 L 20 -74
M 0 -60 L -20 -74
"
/>
<use href="#branch" transform="rotate(60)"/>
<use href="#branch" transform="rotate(120)"/>
<use href="#branch" transform="rotate(180)"/>
<use href="#branch" transform="rotate(240)"/>
<use href="#branch" transform="rotate(300)"/>
</svg>
<svg width="200" height="200" viewBox="-100 -100 200 200">
<path
id="branch"
stroke="#e5c39c"
stroke-width="5"
d="M 0 0 L 0 -90
M 0 -20 L 20 -34
M 0 -20 L -20 -34
M 0 -40 L 20 -54
M 0 -40 L -20 -54
M 0 -60 L 20 -74
M 0 -60 L -20 -74
"
/>
<use href="#branch" transform="rotate(60)"/>
<use href="#branch" transform="rotate(120)"/>
<use href="#branch" transform="rotate(180)"/>
<use href="#branch" transform="rotate(240)"/>
<use href="#branch" transform="rotate(300)"/>
</svg>