sevgi-appendix 0.98.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/CHANGELOG.md +277 -0
- data/LICENSE +674 -0
- data/README.md +115 -0
- data/agents/skills/sevgi/SKILL.md +63 -0
- data/agents/skills/sevgi/agents/openai.yaml +4 -0
- data/agents/skills/sevgi/references/derender.md +81 -0
- data/agents/skills/sevgi/references/drawing.md +79 -0
- data/agents/skills/sevgi/references/dsl.md +99 -0
- data/agents/skills/sevgi/references/inspection.md +82 -0
- data/agents/skills/sevgi/references/layout.md +55 -0
- data/agents/skills/sevgi/references/output.md +61 -0
- data/agents/skills/sevgi/references/ruby.md +79 -0
- data/agents/skills/sevgi/references/svg.md +35 -0
- data/agents/skills/sevgi/references/toolkit.md +48 -0
- data/lib/rubocop/cop/sevgi/block_delimiters.rb +14 -0
- data/lib/rubocop/cop/sevgi/parentheses.rb +27 -0
- data/lib/rubocop/cop/sevgi/string_literals.rb +13 -0
- data/lib/rubocop/cop/sevgi_cops.rb +5 -0
- data/lib/rubocop/sevgi/plugin.rb +41 -0
- data/lib/rubocop/sevgi.rb +6 -0
- data/lib/sevgi/appendix/version.rb +8 -0
- data/lib/sevgi/appendix.rb +9 -0
- data/lib/sevgi-appendix.rb +4 -0
- data/rubocop/config/default.yml +67 -0
- metadata +104 -0
data/README.md
ADDED
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
# Sevgi Appendix
|
|
2
|
+
|
|
3
|
+
Sevgi Appendix packages development extras for the Sevgi SVG DSL: an agent skill and RuboCop rules for readable
|
|
4
|
+
`.sevgi` source. Installing the umbrella `sevgi` gem installs the matching Appendix version. A project that only needs
|
|
5
|
+
the development extras can install this package separately:
|
|
6
|
+
|
|
7
|
+
```sh
|
|
8
|
+
gem install sevgi-appendix
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Require
|
|
12
|
+
|
|
13
|
+
The package version is available without loading RuboCop:
|
|
14
|
+
|
|
15
|
+
```ruby
|
|
16
|
+
require "sevgi/appendix"
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
## Agent skill
|
|
20
|
+
|
|
21
|
+
For a complete user installation, install Sevgi with Homebrew on macOS or Linux:
|
|
22
|
+
|
|
23
|
+
```sh
|
|
24
|
+
brew install roktas/tap/sevgi
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
Locate the skill without guessing a Homebrew prefix or Cellar version:
|
|
28
|
+
|
|
29
|
+
```sh
|
|
30
|
+
sevgi --skill
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
The command prints one validated absolute path and nothing else. It fails if the installed `sevgi-appendix` version
|
|
34
|
+
does not match Sevgi or its `SKILL.md` is missing. Homebrew reports a stable path below its `opt` tree, so an agent's
|
|
35
|
+
skill directory may safely link to it. Homebrew keeps that path current across upgrades.
|
|
36
|
+
|
|
37
|
+
Coding agents look for skills in different directories. Paste this prompt into the agent you want to configure:
|
|
38
|
+
|
|
39
|
+
> Run `sevgi --skill`. Install the reported directory as a user-level skill named `sevgi` using this agent product's
|
|
40
|
+
> supported skill location. Use a symbolic link because this is a stable Homebrew path. Report the destination and
|
|
41
|
+
> whether a new session is required.
|
|
42
|
+
|
|
43
|
+
When the umbrella `sevgi` gem was installed through RubyGems or Bundler, the same query works, including inside a
|
|
44
|
+
bundle:
|
|
45
|
+
|
|
46
|
+
```sh
|
|
47
|
+
bundle exec sevgi --skill
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
Gem paths contain the package version and can disappear after an upgrade. Copy the complete reported directory into
|
|
51
|
+
the agent's skill location instead of keeping a long-lived symbolic link, then repeat the copy after updating Sevgi.
|
|
52
|
+
The skill uses progressive disclosure to route an agent through the DSL, SVG rendering semantics, Geometry and
|
|
53
|
+
Sundries helpers, Derender, and output workflows.
|
|
54
|
+
|
|
55
|
+
## RuboCop
|
|
56
|
+
|
|
57
|
+
Add the gem to development dependencies when the umbrella gem is not already present:
|
|
58
|
+
|
|
59
|
+
```ruby
|
|
60
|
+
gem "sevgi-appendix", require: false
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
Then enable the plugin in `.rubocop.yml`:
|
|
64
|
+
|
|
65
|
+
```yaml
|
|
66
|
+
plugins:
|
|
67
|
+
- sevgi-appendix
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
Run RuboCop through the application's bundle:
|
|
71
|
+
|
|
72
|
+
```sh
|
|
73
|
+
bundle exec rubocop
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
The plugin adds `*.sevgi` files to RuboCop and checks the DSL's deliberate source shape: optional parentheses are
|
|
77
|
+
omitted from statement-like calls, one-line blocks use braces, multiline blocks use `do`/`end`, and strings use double
|
|
78
|
+
quotes. Parentheses remain valid where Ruby needs them or they clarify a compact chained expression.
|
|
79
|
+
|
|
80
|
+
### With rubyfmt
|
|
81
|
+
|
|
82
|
+
`rubyfmt` remains authoritative for ordinary Ruby. Keep `.sevgi` files hand-formatted and let Sevgi Appendix inspect
|
|
83
|
+
their DSL-specific shape:
|
|
84
|
+
|
|
85
|
+
```yaml
|
|
86
|
+
inherit_gem:
|
|
87
|
+
rubocop-rubyfmt:
|
|
88
|
+
- config/full.yml
|
|
89
|
+
|
|
90
|
+
plugins:
|
|
91
|
+
- rubocop-rubyfmt
|
|
92
|
+
- sevgi-appendix
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
Add the scripts to `.rubyfmtignore` so `rubyfmt` does not erase the shape the plugin protects:
|
|
96
|
+
|
|
97
|
+
```text
|
|
98
|
+
*.sevgi
|
|
99
|
+
**/*.sevgi
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
## Ruby compatibility
|
|
103
|
+
|
|
104
|
+
Requires Ruby 3.4.0 or newer. CI verifies the current Ruby 3.4 release and the development Ruby from `.ruby-version`.
|
|
105
|
+
|
|
106
|
+
## Native prerequisites
|
|
107
|
+
|
|
108
|
+
This gem needs only Ruby and its Ruby dependencies.
|
|
109
|
+
|
|
110
|
+
## Links
|
|
111
|
+
|
|
112
|
+
- Documentation: <https://sevgi.roktas.dev>
|
|
113
|
+
- API documentation: <https://www.rubydoc.info/gems/sevgi-appendix>
|
|
114
|
+
- Source: <https://github.com/roktas/sevgi/tree/main/appendix>
|
|
115
|
+
- Changelog: <https://github.com/roktas/sevgi/blob/main/CHANGELOG.md>
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: sevgi
|
|
3
|
+
description: Create, edit, review, and debug SVG drawings written with the Sevgi Ruby DSL. Use for `.sevgi` scripts, Ruby code that builds SVG with Sevgi, reusable drawing modules, editor-authored SVG/XML integration through Derender, PDF or PNG export, visual regressions, or requests to express an SVG design through Sevgi rather than raw XML.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Sevgi
|
|
7
|
+
|
|
8
|
+
Sevgi is first a Ruby DSL: write idiomatic Ruby that describes SVG while preserving SVG semantics and the user's
|
|
9
|
+
visible intent.
|
|
10
|
+
|
|
11
|
+
## Core Contract
|
|
12
|
+
|
|
13
|
+
1. **Delegate rendering to SVG.** Before calculating any value that affects appearance, decide who owns that result. If
|
|
14
|
+
SVG can express the intent and the final value depends on renderer knowledge, encode the intent in SVG and let the
|
|
15
|
+
renderer resolve it. Use Ruby or `Sevgi::Geometry` only for information the program genuinely must derive. Read
|
|
16
|
+
[drawing.md](references/drawing.md) before adding rendering-related arithmetic.
|
|
17
|
+
2. **Fix the cause, not the symptom.** Treat the visible result as the acceptance criterion. Trace a mismatch through
|
|
18
|
+
drawing geometry, viewport, transforms, styles, strokes, and renderer behavior; fix the layer that owns the faulty
|
|
19
|
+
contract. Do not hide it with empty margins, oversized frames, clipping, non-uniform scaling, or a one-case offset.
|
|
20
|
+
3. **Stay native to Sevgi.** Use SVG elements through the Sevgi DSL and prefer Sevgi's existing Graphics, Geometry,
|
|
21
|
+
Sundries, and Function helpers where they fit. Do not generate raw SVG/XML, another graphics format, or an
|
|
22
|
+
intermediate string and then convert it into Sevgi. Use Derender only when existing SVG/XML is a genuine input
|
|
23
|
+
artifact.
|
|
24
|
+
|
|
25
|
+
## Workflow
|
|
26
|
+
|
|
27
|
+
1. Determine the host and installed dependency surface: executable `.sevgi` script, full-toolkit Ruby library, or a
|
|
28
|
+
focused component. Read [dsl.md](references/dsl.md) for its grammar and facade boundaries.
|
|
29
|
+
2. Choose the component that owns each nontrivial operation. Read [toolkit.md](references/toolkit.md) before writing a
|
|
30
|
+
project-local substitute for a Sevgi helper.
|
|
31
|
+
3. Structure data, control flow, and reuse as ordinary Ruby while preserving the DSL shape. Read
|
|
32
|
+
[ruby.md](references/ruby.md).
|
|
33
|
+
4. Identify the owner of each visual result before writing arithmetic, offsets, or scaling. Read
|
|
34
|
+
[drawing.md](references/drawing.md), and use [svg.md](references/svg.md) when SVG may own the behavior.
|
|
35
|
+
5. Read [layout.md](references/layout.md) for repetition, tiling, alignment, rulers, grids, drawing, or hatching.
|
|
36
|
+
6. For editor-authored SVG/XML, `igves` prints Sevgi source and `igsev` round-trips to normalized SVG; both accept files
|
|
37
|
+
or standard input. Read
|
|
38
|
+
[derender.md](references/derender.md). For PDF/PNG output, read [output.md](references/output.md).
|
|
39
|
+
7. Before introducing a Sevgi word or signature not established by nearby code or the loaded references, verify it in
|
|
40
|
+
the DSL catalog or owning YARD. Do not infer an API from an English name or use `Element` to bypass an unknown
|
|
41
|
+
operation.
|
|
42
|
+
8. Write the smallest clear Sevgi expression. Keep case-sensitive, normally lowercase-leading SVG element calls,
|
|
43
|
+
capitalized Sevgi operations, ordinary Ruby control flow, and SVG attributes visibly distinct. Do not mechanically
|
|
44
|
+
parenthesize statement-like DSL calls.
|
|
45
|
+
9. Render and inspect the actual output in each context it claims to support. Compare visible bounds and density—not
|
|
46
|
+
only canvas or DOM dimensions. For size, alignment, clipping, density, or visual-regression evidence, read
|
|
47
|
+
[inspection.md](references/inspection.md) and identify the measurement space before choosing a tool.
|
|
48
|
+
10. Re-read the finished source for raw-XML detours, avoidable calculations, magic offsets, duplicated helpers, and
|
|
49
|
+
stale artifacts.
|
|
50
|
+
|
|
51
|
+
## References
|
|
52
|
+
|
|
53
|
+
| Need | Read |
|
|
54
|
+
| --- | --- |
|
|
55
|
+
| Choose script/library syntax, profiles, SVG elements, or Sevgi DSL words | [dsl.md](references/dsl.md) |
|
|
56
|
+
| Choose a component and locate user, YARD, or checkout documentation | [toolkit.md](references/toolkit.md) |
|
|
57
|
+
| Structure `.sevgi` and library code as idiomatic Ruby | [ruby.md](references/ruby.md) |
|
|
58
|
+
| Apply the renderer/program ownership boundary; diagnose visual mismatches | [drawing.md](references/drawing.md) |
|
|
59
|
+
| Measure SVG geometry, browser layout, or painted pixels | [inspection.md](references/inspection.md) |
|
|
60
|
+
| Find an SVG capability and its authoritative specification | [svg.md](references/svg.md) |
|
|
61
|
+
| Choose repetition, tiling, ruler, grid, Draw, pattern, or Hatch | [layout.md](references/layout.md) |
|
|
62
|
+
| Integrate editor-authored SVG/XML through Derender | [derender.md](references/derender.md) |
|
|
63
|
+
| Render, save, or export SVG, PDF, or PNG | [output.md](references/output.md) |
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
# Derender
|
|
2
|
+
|
|
3
|
+
## Purpose
|
|
4
|
+
|
|
5
|
+
Some vector geometry is better authored in a visual editor than programmed: a Bezier-heavy logo, traced illustration,
|
|
6
|
+
or hand-adjusted path may be correct as artwork but impractical as handwritten Ruby. Derender brings that SVG/XML into
|
|
7
|
+
the Sevgi element model so editor-authored geometry can participate in Ruby-driven composition, styling, layout, and
|
|
8
|
+
output. This combines both workflows without pretending every path should originate in code.
|
|
9
|
+
|
|
10
|
+
Do not use Derender as a detour for ordinary shapes or relationships that are clearer in the Sevgi DSL. Use it when
|
|
11
|
+
SVG/XML is a real input artifact.
|
|
12
|
+
|
|
13
|
+
## Choose the Relationship
|
|
14
|
+
|
|
15
|
+
| Intent | Inline content | File | Result |
|
|
16
|
+
| --- | --- | --- | --- |
|
|
17
|
+
| Inspect an immutable parsed node | `SVG.Decompile` | `SVG.DecompileFile` | `Sevgi::Derender::Node` |
|
|
18
|
+
| Generate editable Sevgi Ruby source | `SVG.Derender` | `SVG.DerenderFile` | formatted Ruby String |
|
|
19
|
+
| Add the selected node to an existing tree | `SVG.Evaluate` | `SVG.EvaluateFile` | included element or `nil` |
|
|
20
|
+
| Add only the selected node's children | `SVG.EvaluateChildren` | `SVG.EvaluateChildrenFile` | frozen child snapshot |
|
|
21
|
+
| Include a selected file node inside an SVG block | — | `Include` | included element or `nil` |
|
|
22
|
+
| Include only a selected file node's children inside a block | — | `IncludeChildren` | frozen child snapshot |
|
|
23
|
+
|
|
24
|
+
Use `DerenderFile` when generated Ruby should become the maintained source. Use `Include`, `EvaluateFile`, or their
|
|
25
|
+
children variants when the editor file should remain the source of its geometry. Use `DecompileFile` to inspect names,
|
|
26
|
+
attributes, namespaces, metadata, and children before choosing. Choose one maintained representation; do not keep
|
|
27
|
+
changing both editor SVG and generated Sevgi as parallel sources.
|
|
28
|
+
|
|
29
|
+
Keep the editor file as the geometry source and compose a selected group in Sevgi:
|
|
30
|
+
|
|
31
|
+
```ruby
|
|
32
|
+
SVG :minimal do
|
|
33
|
+
Include "brand.svg", "logo", omit: :id
|
|
34
|
+
end.Save "badge.svg"
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
Add inline content to an existing document by passing the target explicitly:
|
|
38
|
+
|
|
39
|
+
```ruby
|
|
40
|
+
drawing = SVG :minimal
|
|
41
|
+
SVG.Evaluate '<circle id="mark" r="4"/>', drawing, omit: :id
|
|
42
|
+
drawing.Render
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
Generate Ruby for review and integration into the maintained source instead:
|
|
46
|
+
|
|
47
|
+
```ruby
|
|
48
|
+
source = SVG.DerenderFile "brand.svg", id: "logo", omit: :id
|
|
49
|
+
puts source
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
At the command line, `igves` prints generated Sevgi source. Use `igsev` from the umbrella gem only when the intended
|
|
53
|
+
result is normalized SVG produced by a complete SVG-to-Sevgi-to-SVG round trip. Both commands accept repeatable
|
|
54
|
+
`--omit ATTRIBUTE` and read standard input when the file is omitted or `-`; `igsev` is a structural formatter, not a
|
|
55
|
+
byte-preserving XML rewrite.
|
|
56
|
+
|
|
57
|
+
A selected subtree may produce a fragment rather than a standalone `.sevgi` script. Review the conversion, then place
|
|
58
|
+
it inside the document or callable module that owns it; do not merely rename an arbitrary fragment to `.sevgi`.
|
|
59
|
+
|
|
60
|
+
## Selection and Cleanup
|
|
61
|
+
|
|
62
|
+
- Give reusable editor groups stable IDs and select one with `id:`. `Include` takes that ID as its second positional
|
|
63
|
+
argument.
|
|
64
|
+
- Inspect an SVG tree with `SVG.DecompileFile` (or component-level `Sevgi::Derender.decompile_file`) before falling back
|
|
65
|
+
to text search. Grep can mistake editor helpers, generated IDs, metadata, or unrelated layers for reusable artwork.
|
|
66
|
+
- Use `omit:` with one String/Symbol or an Array of exact, case-sensitive attribute names. Selection happens before
|
|
67
|
+
omission, so the selecting ID can be removed.
|
|
68
|
+
- Namespace declarations remain intact, and omitting the `style` attribute does not remove `style` elements. Omit
|
|
69
|
+
style, transform, or geometry attributes only when their behavior is intentionally replaced.
|
|
70
|
+
- Expect paths and other low-level editor geometry to remain low-level. Derender preserves the SVG tree; it cannot
|
|
71
|
+
reconstruct the loops, modules, or design operations that originally created it.
|
|
72
|
+
- Treat parsing and executing as separate trust boundaries. Derender parses XML as data and direct evaluation builds
|
|
73
|
+
graphics elements without executing generated Ruby. Execute generated source only when it is trusted code.
|
|
74
|
+
- After renaming or removing a selected id or source path, update its consumers, regenerate derived drawings, and check
|
|
75
|
+
for stale references.
|
|
76
|
+
|
|
77
|
+
Script mode uses bare names such as `DerenderFile`; library mode uses the `SVG.` facade. Component-only consumers use
|
|
78
|
+
the lowercase `Sevgi::Derender` methods and `_file` variants.
|
|
79
|
+
|
|
80
|
+
Read the [Derender guide](https://sevgi.roktas.dev/derender/) for the complete workflow and
|
|
81
|
+
[`sevgi-derender` YARD](https://www.rubydoc.info/gems/sevgi-derender) for exact signatures and failure contracts.
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
# Drawing Discipline
|
|
2
|
+
|
|
3
|
+
## Renderer First
|
|
4
|
+
|
|
5
|
+
Treat SVG as the rendering model, not merely as an output format. The governing question is not whether Ruby *can*
|
|
6
|
+
calculate a value, but which layer has the knowledge and responsibility to determine it.
|
|
7
|
+
|
|
8
|
+
Use this ownership boundary:
|
|
9
|
+
|
|
10
|
+
| Owner | Test | Action |
|
|
11
|
+
| --- | --- | --- |
|
|
12
|
+
| SVG renderer | SVG can state the intent and the final result depends on rendering context | Encode the intent with SVG elements, attributes, CSS, or transforms |
|
|
13
|
+
| Sevgi | Sevgi already names the SVG operation or supplies the required layout abstraction | Use the DSL or helper instead of rebuilding it |
|
|
14
|
+
| Program | The value must exist before rendering and SVG neither derives nor exposes it | Compute it with ordinary Ruby or `Sevgi::Geometry` |
|
|
15
|
+
|
|
16
|
+
Before computing, first ask whether SVG can state the desired relationship rather than its current numeric result. If
|
|
17
|
+
it can, keep that relationship declarative. Then check whether Sevgi already wraps it. Compute only when the program
|
|
18
|
+
needs geometry or data that the renderer cannot provide as part of the supported workflow.
|
|
19
|
+
|
|
20
|
+
Geometry is appropriate for constructed points and lines, intersections, bounds consumed by later algorithms, sweeps,
|
|
21
|
+
and hatching. It is not a parallel rendering engine.
|
|
22
|
+
|
|
23
|
+
## Example: Preserve the Ownership Boundary
|
|
24
|
+
|
|
25
|
+
The Showcase Ruler illustrates the general rule; it does not introduce a text-specific recipe. The desired relationship
|
|
26
|
+
is “center each label on this tick,” while the rendered width of a label depends on font metrics known by the SVG
|
|
27
|
+
renderer.
|
|
28
|
+
|
|
29
|
+
Computing a guessed width in Ruby takes ownership away from the renderer:
|
|
30
|
+
|
|
31
|
+
```ruby
|
|
32
|
+
(10..(WIDTH - 10)).step(10).each_with_index do |x, i|
|
|
33
|
+
label = (i + 1).to_s
|
|
34
|
+
estimated_width = label.length * 4 * 0.6
|
|
35
|
+
text label, x: x - (estimated_width / 2), y: length + 5.5, class: "labels"
|
|
36
|
+
end
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
Instead, keep the tick coordinate and express the relationship in SVG:
|
|
40
|
+
|
|
41
|
+
```ruby
|
|
42
|
+
(10..(WIDTH - 10)).step(10).each_with_index do |x, i|
|
|
43
|
+
label = (i + 1).to_s
|
|
44
|
+
text label, x:, y: length + 5.5, "text-anchor": "middle", class: "labels"
|
|
45
|
+
end
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
The broader lesson is to encode the stable relationship and leave context-dependent rendering values to the renderer.
|
|
49
|
+
Apply that reasoning before introducing measured compensations or Geometry objects solely to imitate rendering.
|
|
50
|
+
|
|
51
|
+
## Root-Cause Review
|
|
52
|
+
|
|
53
|
+
When a drawing looks wrong, inspect these contracts separately:
|
|
54
|
+
|
|
55
|
+
1. **Source geometry:** coordinates, intervals, repetition counts, and intended visible bounds.
|
|
56
|
+
2. **Layout framing:** content bounds, margins, alignment, and repetition policy.
|
|
57
|
+
3. **Viewport:** `width`, `height`, `viewBox`, and `preserveAspectRatio`.
|
|
58
|
+
4. **Painting:** stroke width, line caps/joins, fill, opacity, filters, and CSS cascade.
|
|
59
|
+
5. **Transforms:** order, origin, inherited transforms, and duplicated translations.
|
|
60
|
+
6. **Environment:** font availability, renderer support, output size, and theme-specific styles.
|
|
61
|
+
|
|
62
|
+
Fix the first contract that is false. Do not make a frame larger to hide unequal content, add whitespace to simulate
|
|
63
|
+
alignment, clip an overflow caused by wrong geometry, or use a special-case offset that only matches one label or
|
|
64
|
+
viewport.
|
|
65
|
+
|
|
66
|
+
Verify every rendering context the artifact claims to support. For browser assets this may include representative
|
|
67
|
+
desktop/mobile widths and light/dark themes; for print output it may include page sizes and an independent PDF renderer.
|
|
68
|
+
Equal SVG dimensions or DOM boxes do not prove equal visible size.
|
|
69
|
+
|
|
70
|
+
## Layout Changes
|
|
71
|
+
|
|
72
|
+
Treat the viewport, visible drawing bounds, and visual density as separate contracts. When changing a canvas, grid, or
|
|
73
|
+
repeated layout, reconsider interval counts, hatch spacing, margins, and stroke weights together instead of scaling one
|
|
74
|
+
number mechanically.
|
|
75
|
+
|
|
76
|
+
For a drawing parameterized across sizes or profiles, check representative extremes and nearby values that share the
|
|
77
|
+
same layout rule. Judge actual whitespace, visual balance, row and column counts, clipping, and perceived density.
|
|
78
|
+
Rounding or repetition changes can fix one variant while regressing another. Do not use an offset to hide a wrong
|
|
79
|
+
geometry, interval, or viewport model.
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
# DSL Map
|
|
2
|
+
|
|
3
|
+
## Host Grammar
|
|
4
|
+
|
|
5
|
+
| Form | Meaning |
|
|
6
|
+
| --- | --- |
|
|
7
|
+
| `rect`, `circle`, `linearGradient`, `clipPath`, ... | Standard SVG elements created directly by exact, case-sensitive names; they normally start lowercase |
|
|
8
|
+
| `Translate`, `Tile`, `Call`, `Render`, ... | Sevgi operations, normally capitalized to stand apart from SVG elements |
|
|
9
|
+
| `css`, `layer`, `layer!`, `base` | Deliberate lowercase Sevgi words; `base` belongs to callable-module definitions |
|
|
10
|
+
| `SVG(...)` | Build a document in both script and library code |
|
|
11
|
+
| `SVG.Canvas(...)` | Call a full-toolkit facade operation in library code |
|
|
12
|
+
| `SVG::Canvas` | Refer to a type or namespace |
|
|
13
|
+
| `Canvas(...)` | Call the promoted operation in an executable `.sevgi` script |
|
|
14
|
+
|
|
15
|
+
Use Ruby hashes for SVG attributes. Quote hyphenated SVG names:
|
|
16
|
+
|
|
17
|
+
```ruby
|
|
18
|
+
text "Ready", x: 12, y: 16, "text-anchor": "middle", "font-weight": "bold"
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
## Dependency Surface
|
|
22
|
+
|
|
23
|
+
| Host | Load | Vocabulary |
|
|
24
|
+
| --- | --- | --- |
|
|
25
|
+
| Executable script | `ruby -S sevgi` through the `.sevgi` shebang | bare promoted operations such as `Canvas`, `Paper`, and `Grid` |
|
|
26
|
+
| Full-toolkit library | `require "sevgi"` | `SVG(...)` plus facade operations such as `SVG.Canvas` and `SVG.Grid` |
|
|
27
|
+
| Focused graphics library | `require "sevgi/graphics"` | `Sevgi::Graphics.SVG` and lowercase component constructors; no full `SVG` facade |
|
|
28
|
+
|
|
29
|
+
Follow the consumer's declared gems and existing dialect. Do not require the full toolkit merely to obtain facade
|
|
30
|
+
spelling, and do not use facade operations when only a focused component is installed.
|
|
31
|
+
|
|
32
|
+
## Minimal Forms
|
|
33
|
+
|
|
34
|
+
Executable script:
|
|
35
|
+
|
|
36
|
+
```ruby
|
|
37
|
+
#!/usr/bin/env -S ruby -S sevgi
|
|
38
|
+
|
|
39
|
+
canvas = Canvas width: 24, height: 24, unit: :px
|
|
40
|
+
|
|
41
|
+
SVG :minimal, canvas do
|
|
42
|
+
circle cx: 12, cy: 12, r: 10, fill: "tomato"
|
|
43
|
+
end.Save "badge.svg"
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
Ruby library:
|
|
47
|
+
|
|
48
|
+
```ruby
|
|
49
|
+
require "sevgi"
|
|
50
|
+
|
|
51
|
+
canvas = SVG.Canvas width: 24, height: 24, unit: :px
|
|
52
|
+
drawing = SVG(:minimal, canvas) { circle cx: 12, cy: 12, r: 10, fill: "tomato" }
|
|
53
|
+
|
|
54
|
+
File.write "badge.svg", drawing.Render
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
## Paper, Canvas, and Document
|
|
58
|
+
|
|
59
|
+
Keep physical size and serialization dialect independent:
|
|
60
|
+
|
|
61
|
+
| Need | Use | Owns |
|
|
62
|
+
| --- | --- | --- |
|
|
63
|
+
| Register or look up a named physical size | `SVG.Paper` / script `Paper` | width, height, and unit |
|
|
64
|
+
| Build one drawing surface | `SVG.Canvas` / script `Canvas` | size, margins, unit, name, and resulting `viewBox` |
|
|
65
|
+
| Define or select an SVG document profile | `SVG.Document` / script `Document` | root attributes and preambles |
|
|
66
|
+
|
|
67
|
+
The first argument to `SVG` selects a document profile; the optional second argument supplies a canvas. Use an
|
|
68
|
+
anonymous `Document` for one-off metadata and a named profile only for shared process-wide vocabulary.
|
|
69
|
+
Prefer non-bang registration; use `Paper!` or `Document!` only for an intentional overwrite.
|
|
70
|
+
|
|
71
|
+
## Task-to-Word Map
|
|
72
|
+
|
|
73
|
+
| Task | Start with |
|
|
74
|
+
| --- | --- |
|
|
75
|
+
| Create SVG structure | SVG element names; nest containers with blocks |
|
|
76
|
+
| Choose page dimensions and document metadata | `Paper`, `Canvas`, `Document`; keep their responsibilities separate |
|
|
77
|
+
| Set reusable styles | `css`, classes, presentation attributes |
|
|
78
|
+
| Transform an element or group | `Translate`, `Rotate`, `Scale`, `Skew`, `Flip` |
|
|
79
|
+
| Center known inner and outer boxes | element `Align`; use `Sevgi::Geometry::Operation` for edge alignment or a Ruby result |
|
|
80
|
+
| Draw simple line/path wrappers | `LineTo`, `LineBy`, `HLineTo`, `HLineBy`, `VLineTo`, `VLineBy` |
|
|
81
|
+
| Reuse or repeat drawing | `defs`/`use`, `Tile`, `TileX`, `TileY`, `Duplicate` |
|
|
82
|
+
| Compose reusable drawing code | `SVG::Module`, `base`, `Call`; profile-specific `Group`, `Layer`, `Layer!`, `Symbols` |
|
|
83
|
+
| Move existing element trees | `Append`, `Prepend`, `Adopt`, `AdoptFirst`, `Orphan` |
|
|
84
|
+
| Draw or hatch Geometry values | `Draw`, `Hatch` on `:inkscape` or an explicitly extended custom profile |
|
|
85
|
+
| Inspect or validate output | `Identifiers`, `Validate`, `Lint` |
|
|
86
|
+
| Produce output | `Render`, `Out`, `Save`; optional `PDF` and `PNG` export |
|
|
87
|
+
| Import existing SVG/XML | `Include`, `Evaluate`, `Derender`, `Decompile` and their file variants |
|
|
88
|
+
|
|
89
|
+
## Profiles
|
|
90
|
+
|
|
91
|
+
| Profile | Use |
|
|
92
|
+
| --- | --- |
|
|
93
|
+
| `:minimal` | SVG without default preamble or namespace metadata |
|
|
94
|
+
| `:default` | Standalone SVG with XML declaration and SVG namespace |
|
|
95
|
+
| `:html` | SVG intended for HTML embedding |
|
|
96
|
+
| `:inkscape` | Editor metadata plus convenient `Draw` and `Hatch` helpers |
|
|
97
|
+
|
|
98
|
+
Do not invent a Sevgi word from a likely name. Check the [online DSL catalog](https://sevgi.roktas.dev/dsl/) for
|
|
99
|
+
executable examples and the appropriate YARD component reference for exact arguments and return values.
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
# Inspection
|
|
2
|
+
|
|
3
|
+
Measure rendered output only when the acceptance criterion requires evidence about size, alignment, clipping, density,
|
|
4
|
+
or a visual regression. Measurement is diagnostic evidence, not permission to copy observed coordinates back into the
|
|
5
|
+
DSL as compensating offsets. Use the evidence to locate the faulty source, viewport, layout, paint, transform, or
|
|
6
|
+
environment contract, then fix that owner.
|
|
7
|
+
|
|
8
|
+
## Choose the Evidence
|
|
9
|
+
|
|
10
|
+
State the question before selecting a tool:
|
|
11
|
+
|
|
12
|
+
| Question | Evidence | Space |
|
|
13
|
+
| --- | --- | --- |
|
|
14
|
+
| Is the declared coordinate model correct? | `viewBox`, viewport dimensions, `preserveAspectRatio` | SVG user units and viewport policy |
|
|
15
|
+
| What geometry does this SVG element contain? | Browser `getBBox()` | SVG user units |
|
|
16
|
+
| Where does it appear after layout and transforms? | Browser `getBoundingClientRect()` or an equivalent automation API | Viewport-relative CSS pixels |
|
|
17
|
+
| What is a path's renderer-computed length or point? | `getTotalLength()` or `getPointAtLength()` | SVG user units |
|
|
18
|
+
| Which pixels were actually painted? | A deterministic PNG or browser screenshot inspected as raster data | Raster pixels |
|
|
19
|
+
| Does the result have the intended balance or density? | Visual inspection at representative outputs | Perceptual; no single bounding box proves it |
|
|
20
|
+
|
|
21
|
+
Do not compare values from different spaces as if they shared units. Label every recorded measurement with:
|
|
22
|
+
|
|
23
|
+
```text
|
|
24
|
+
kind: geometry | layout | paint
|
|
25
|
+
space: svg-user-unit | css-px | raster-px
|
|
26
|
+
bounds: x, y, width, height
|
|
27
|
+
context: renderer, viewport, DPR, theme, font state
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
## Browser Recipe
|
|
31
|
+
|
|
32
|
+
Use an installed browser automation facility, preferably Playwright when available, for responsive layout, CSS, text,
|
|
33
|
+
and integration-context checks.
|
|
34
|
+
|
|
35
|
+
1. Open the artifact in the context that owns the reported behavior. A standalone SVG does not reproduce a host page's
|
|
36
|
+
container, inherited CSS, theme, or responsive rules.
|
|
37
|
+
2. Fix the viewport, device-pixel ratio, theme, and output scale. Disable animation and wait for the page and fonts to
|
|
38
|
+
settle before measuring.
|
|
39
|
+
3. Select the smallest element or group that represents the questioned content. Record its identity with the result.
|
|
40
|
+
4. Read `getBBox()` for local SVG geometry and `getBoundingClientRect()` for viewport-relative layout. Use
|
|
41
|
+
`getCTM()` or `getScreenCTM()` to map points into a common space when transforms matter.
|
|
42
|
+
5. Capture the same target and surrounding context. A tightly clipped element screenshot can hide incorrect margins,
|
|
43
|
+
overflow, or neighboring alignment.
|
|
44
|
+
6. Repeat only the contexts covered by the drawing's contract, such as representative desktop/mobile widths and
|
|
45
|
+
light/dark themes. Use the actual target engine when diagnosing renderer-specific behavior.
|
|
46
|
+
|
|
47
|
+
`getBBox()` applies geometry attributes but does not apply transforms on the element or its parents.
|
|
48
|
+
`getBoundingClientRect()` is an axis-aligned CSS-pixel rectangle after layout and transforms. Do not treat either value
|
|
49
|
+
as the exact painted-pixel boundary: strokes, markers, clipping, filters, font rasterization, and antialiasing may require
|
|
50
|
+
raster evidence. With rotated or skewed content, transform all relevant corners instead of scaling a width and height.
|
|
51
|
+
|
|
52
|
+
## Raster Recipe
|
|
53
|
+
|
|
54
|
+
Use raster evidence when the question concerns visible paint, whitespace, clipping, renderer output, or a pixel-level
|
|
55
|
+
regression.
|
|
56
|
+
|
|
57
|
+
1. Produce a PNG at a fixed viewport or export size, device-pixel ratio, theme, font environment, and background.
|
|
58
|
+
Prefer transparency when the painted foreground must be isolated.
|
|
59
|
+
2. Inspect the image visually before reducing it to numbers. Confirm that the selected image contains the intended
|
|
60
|
+
context and that no crop already concealed the defect.
|
|
61
|
+
3. When ImageMagick or an equivalent tool is available, derive the occupied-pixel box from the alpha channel or a known
|
|
62
|
+
background. Record the transparency threshold or color tolerance; antialiased edge pixels make that policy part of
|
|
63
|
+
the measurement.
|
|
64
|
+
4. Compare occupied bounds, edge margins, and clipping against the stated acceptance criterion. Pixel counts and boxes
|
|
65
|
+
do not measure perceived weight or balance by themselves.
|
|
66
|
+
5. Use pixel diffs only with a pinned rendering environment. Font changes, renderer versions, DPR, color management,
|
|
67
|
+
and antialiasing can create noise without a source regression.
|
|
68
|
+
|
|
69
|
+
For PDF evidence, first validate document structure, then rasterize representative pages through an independent
|
|
70
|
+
renderer and apply the same fixed-context checks. Do not convert a raster observation into maintained drawing source.
|
|
71
|
+
|
|
72
|
+
## Interpret the Result
|
|
73
|
+
|
|
74
|
+
- Wrong SVG-user-unit geometry points to coordinates, repetition, transforms, or a Sevgi layout helper.
|
|
75
|
+
- Correct geometry but wrong CSS-pixel placement points to the viewport, host layout, responsive CSS, or transforms.
|
|
76
|
+
- Stable browser boxes but different raster bounds point to paint, clipping, filters, fonts, or renderer behavior.
|
|
77
|
+
- Equal boxes with visibly unequal results point to spacing, stroke weight, contrast, or density; keep visual inspection
|
|
78
|
+
as the acceptance criterion.
|
|
79
|
+
|
|
80
|
+
Do not measure routine API or source-only changes merely because a browser or image tool is available. Stop when the
|
|
81
|
+
chosen evidence answers the original question; extra metrics add confidence only when they test another relevant
|
|
82
|
+
contract.
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
# Layout Routing
|
|
2
|
+
|
|
3
|
+
Choose by the value the caller needs after the operation, not merely by the visible pattern.
|
|
4
|
+
|
|
5
|
+
## Repetition
|
|
6
|
+
|
|
7
|
+
| Need | Use | Result |
|
|
8
|
+
| --- | --- | --- |
|
|
9
|
+
| Repeat one SVG subtree in the rendered document | `defs`/`use`, or DSL `Tile`, `TileX`, `TileY` | SVG references with generated positions |
|
|
10
|
+
| Copy and independently edit an existing subtree | `Duplicate`, `DuplicateX`, `DuplicateY` | Independent SVG element trees |
|
|
11
|
+
| Inspect repeated cells or row/column bounds in Ruby | `Sevgi::Sundries::Tile` | Geometry values and boxes, no SVG elements |
|
|
12
|
+
|
|
13
|
+
## Intervals and Grids
|
|
14
|
+
|
|
15
|
+
| Need | Use |
|
|
16
|
+
| --- | --- |
|
|
17
|
+
| Fit whole major/minor intervals into a span and inspect their distances | `Sevgi::Sundries::Ruler` |
|
|
18
|
+
| Require an even major-interval count | `Sevgi::Sundries::RulerEven` |
|
|
19
|
+
| Combine two fitted rulers and obtain lines, points, cells, or a fitted canvas | `SVG.Grid` or `Sevgi::Sundries::Grid` |
|
|
20
|
+
|
|
21
|
+
`Ruler` is a Ruby value, not a drawing word. `Grid` is also a Ruby layout model; `Draw` materializes its geometry as SVG
|
|
22
|
+
when lines are required. In a Grid, `grid.x` returns horizontal lines and `grid.y` vertical lines—the names describe
|
|
23
|
+
line direction.
|
|
24
|
+
|
|
25
|
+
## Alignment
|
|
26
|
+
|
|
27
|
+
| Need | Use |
|
|
28
|
+
| --- | --- |
|
|
29
|
+
| Center known inner and outer boxes with an SVG translation | element `Align` with `:center` |
|
|
30
|
+
| Align Geometry at center or an edge and return the value or offset | `Sevgi::Geometry::Operation.align` or `Sevgi::Geometry::Operation.alignment` |
|
|
31
|
+
| Align rendered text or other renderer-owned content | SVG anchoring, baseline, layout, or transform semantics |
|
|
32
|
+
|
|
33
|
+
Geometry alignment accepts `:center`, `:left`, `:right`, `:top`, and `:bottom`; the element DSL's narrower `Align`
|
|
34
|
+
contract accepts only `:center`. Do not calculate font or painted-content bounds merely to feed either API; use them
|
|
35
|
+
when the program already owns meaningful box geometry.
|
|
36
|
+
|
|
37
|
+
## Drawing and Hatching
|
|
38
|
+
|
|
39
|
+
| Need | Use |
|
|
40
|
+
| --- | --- |
|
|
41
|
+
| A visual repeated fill whose individual strokes are irrelevant | SVG `pattern`; let the renderer repeat and clip it |
|
|
42
|
+
| Explicit finite hatch segments that must remain separate geometry/SVG paths | Geometry sweep or `Hatch` |
|
|
43
|
+
| Existing Geometry values rendered as SVG elements | `Draw` |
|
|
44
|
+
|
|
45
|
+
`Hatch` computes finite segments and emits each as a separate SVG path. Use it for editable, inspectable, plotter-like,
|
|
46
|
+
or otherwise explicit line geometry—not merely because a region should look striped. `Draw` and `Hatch` are included by
|
|
47
|
+
`:inkscape`; add the Hatch mixture to another profile only when that profile deliberately owns the capability. For a
|
|
48
|
+
scoped extension, subclass `SVG::Document::Base`, then call `SVG.Mixin :Hatch, profile`; targeting `Base` itself changes
|
|
49
|
+
every descendant profile process-wide.
|
|
50
|
+
|
|
51
|
+
Read the [Sundries guide](https://sevgi.roktas.dev/sundries/) for Ruler, Grid, and both Tile models; read
|
|
52
|
+
[Geometry sweeps and hatching](https://sevgi.roktas.dev/geometry/#sweeps) for explicit hatch lines; use the
|
|
53
|
+
[DSL Catalog](https://sevgi.roktas.dev/dsl/) for exact drawing words. Exact Ruby contracts live in
|
|
54
|
+
[`sevgi-sundries`](https://www.rubydoc.info/gems/sevgi-sundries) and
|
|
55
|
+
[`sevgi-graphics`](https://www.rubydoc.info/gems/sevgi-graphics).
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
# Output Routing
|
|
2
|
+
|
|
3
|
+
Keep document construction and output policy separate. Choose the final operation from the artifact the caller needs:
|
|
4
|
+
|
|
5
|
+
| Artifact | Use |
|
|
6
|
+
| --- | --- |
|
|
7
|
+
| SVG String owned by surrounding Ruby code | `Render` |
|
|
8
|
+
| SVG on standard output | `Out` |
|
|
9
|
+
| SVG file | `Save` |
|
|
10
|
+
| PDF file | `PDF` |
|
|
11
|
+
| PNG file | `PNG` |
|
|
12
|
+
| Export an existing SVG String outside the document | `Sevgi::Sundries::Export.call` |
|
|
13
|
+
|
|
14
|
+
The `sevgi` command reads standard input when no file is given. Use `sevgi --as badge` when an implicit `Save`, `PDF`,
|
|
15
|
+
or `PNG` should derive `badge.svg`, `badge.pdf`, or `badge.png` instead of the default `output` basename. `NAME` is a
|
|
16
|
+
basename, not a path; explicit destinations in the source remain authoritative.
|
|
17
|
+
|
|
18
|
+
## PDF
|
|
19
|
+
|
|
20
|
+
For a document, use the convenience operation:
|
|
21
|
+
|
|
22
|
+
```ruby
|
|
23
|
+
canvas = SVG.Canvas width: 40, height: 40, unit: :px
|
|
24
|
+
drawing = SVG :minimal, canvas do
|
|
25
|
+
circle cx: 20, cy: 20, r: 16, fill: "tomato"
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
drawing.PDF "badge.pdf"
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
For an application that owns the rendered SVG and output policy separately:
|
|
32
|
+
|
|
33
|
+
```ruby
|
|
34
|
+
canvas = SVG.Canvas width: 40, height: 40, unit: :px
|
|
35
|
+
svg = SVG(:minimal, canvas) { circle cx: 20, cy: 20, r: 16, fill: "tomato" }.Render
|
|
36
|
+
Sevgi::Sundries::Export.call(svg, "badge.pdf")
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
The output suffix selects PDF when `format:` is omitted. `width:` and `height:` are export dimensions; they do not
|
|
40
|
+
repair or replace the drawing's canvas, `viewBox`, or visible geometry. Fix those in the SVG document. Use `css:` only
|
|
41
|
+
for deliberate export-only styling, and use `dpi:` when the CSS-pixel-to-output conversion policy must differ.
|
|
42
|
+
|
|
43
|
+
SVG output has no native graphics dependency. PDF and PNG export lazily require Cairo, RSVG, and HexaPDF; report a
|
|
44
|
+
missing optional component rather than replacing the path with an unrequested external command or raster workaround.
|
|
45
|
+
|
|
46
|
+
## Verification
|
|
47
|
+
|
|
48
|
+
Inspect the produced artifact; a successful write does not prove correct rendering. For a parameterized or multi-page
|
|
49
|
+
family, start with a representative output and then check other variants affected by the same rule.
|
|
50
|
+
|
|
51
|
+
For PDF output, validate document structure with `qpdf` when available, render representative pages through Poppler or
|
|
52
|
+
an equivalent independent renderer, and inspect embedded fonts when fallback would change the result. Compare raster
|
|
53
|
+
output only as evidence; fix discrepancies in the SVG source, export policy, or environment that owns them.
|
|
54
|
+
|
|
55
|
+
Treat rendered SVGs, PDFs, PNGs, and visual snapshots as derived evidence, not implementation sources. Fix the
|
|
56
|
+
maintained Sevgi or editor-owned SVG/XML source and regenerate. Update expected artifacts only for an intentional output
|
|
57
|
+
change, then review the visible diff before accepting it.
|
|
58
|
+
|
|
59
|
+
Read [Sundries export](https://sevgi.roktas.dev/sundries/#export) for installation and examples, and
|
|
60
|
+
[`Sevgi::Sundries::Export`](https://www.rubydoc.info/gems/sevgi-sundries/Sevgi/Sundries/Export) for exact dimensions,
|
|
61
|
+
options, return paths, and errors.
|