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.
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,4 @@
1
+ interface:
2
+ display_name: "Sevgi"
3
+ short_description: "Create SVG with the Sevgi DSL"
4
+ default_prompt: "Use $sevgi to create an SVG drawing with the Sevgi DSL."
@@ -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.