sevgi-graphics 0.73.2 → 0.93.1
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 +4 -4
- data/README.md +5 -0
- data/lib/sevgi/graphics/attribute.rb +58 -6
- data/lib/sevgi/graphics/auxiliary/canvas.rb +125 -0
- data/lib/sevgi/graphics/auxiliary/content.rb +145 -0
- data/lib/sevgi/graphics/auxiliary/margin.rb +93 -0
- data/lib/sevgi/graphics/auxiliary/paper.rb +163 -0
- data/lib/sevgi/graphics/auxiliary.rb +7 -0
- data/lib/sevgi/graphics/document/base.rb +6 -0
- data/lib/sevgi/graphics/document/default.rb +1 -0
- data/lib/sevgi/graphics/document/html.rb +1 -0
- data/lib/sevgi/graphics/document/inkscape.rb +1 -0
- data/lib/sevgi/graphics/document/minimal.rb +1 -0
- data/lib/sevgi/graphics/document.rb +137 -11
- data/lib/sevgi/graphics/element.rb +68 -8
- data/lib/sevgi/graphics/mixtures/call.rb +35 -3
- data/lib/sevgi/graphics/mixtures/core.rb +107 -18
- data/lib/sevgi/graphics/mixtures/duplicate.rb +14 -0
- data/lib/sevgi/graphics/mixtures/export.rb +29 -6
- data/lib/sevgi/graphics/mixtures/hatch.rb +24 -3
- data/lib/sevgi/graphics/mixtures/identify.rb +23 -1
- data/lib/sevgi/graphics/mixtures/include.rb +31 -4
- data/lib/sevgi/graphics/mixtures/inkscape.rb +51 -5
- data/lib/sevgi/graphics/mixtures/lint.rb +12 -0
- data/lib/sevgi/graphics/mixtures/polyfills.rb +11 -0
- data/lib/sevgi/graphics/mixtures/rdf.rb +40 -1
- data/lib/sevgi/graphics/mixtures/render.rb +90 -2
- data/lib/sevgi/graphics/mixtures/save.rb +21 -3
- data/lib/sevgi/graphics/mixtures/symbols.rb +7 -0
- data/lib/sevgi/graphics/mixtures/tile.rb +41 -6
- data/lib/sevgi/graphics/mixtures/transform.rb +54 -16
- data/lib/sevgi/graphics/mixtures/underscore.rb +10 -1
- data/lib/sevgi/graphics/mixtures/validate.rb +15 -1
- data/lib/sevgi/graphics/mixtures/wrappers.rb +54 -17
- data/lib/sevgi/graphics/mixtures.rb +35 -3
- data/lib/sevgi/graphics/version.rb +2 -1
- data/lib/sevgi/graphics.rb +57 -5
- metadata +9 -8
- data/lib/sevgi/graphics/auxilary/canvas.rb +0 -72
- data/lib/sevgi/graphics/auxilary/content.rb +0 -81
- data/lib/sevgi/graphics/auxilary/margin.rb +0 -49
- data/lib/sevgi/graphics/auxilary/paper.rb +0 -95
- data/lib/sevgi/graphics/auxilary.rb +0 -7
|
@@ -3,39 +3,68 @@
|
|
|
3
3
|
module Sevgi
|
|
4
4
|
module Graphics
|
|
5
5
|
module Mixtures
|
|
6
|
+
# DSL helpers for RDF and license metadata.
|
|
6
7
|
module RDF
|
|
8
|
+
# Adds RDF license metadata inside a metadata element.
|
|
9
|
+
# @param kwargs [Hash] RDF work options
|
|
10
|
+
# @return [Sevgi::Graphics::Element] metadata element
|
|
7
11
|
def License(**kwargs, &block) = metadata { RDFWork(**kwargs, &block) }
|
|
8
12
|
|
|
9
13
|
# Use SPDX license codes in underscored form: https://spdx.org/licenses/
|
|
10
14
|
#
|
|
15
|
+
# Adds Creative Commons BY-SA license metadata.
|
|
16
|
+
# @param kwargs [Hash] RDF work options
|
|
17
|
+
# @return [Sevgi::Graphics::Element] metadata element
|
|
11
18
|
def License_CC_BY_SA(**kwargs, &block)
|
|
12
19
|
License(**kwargs, license: "https://creativecommons.org/licenses/by-sa/4.0/", &block)
|
|
13
20
|
end
|
|
14
21
|
|
|
22
|
+
# Adds Creative Commons BY-NC license metadata.
|
|
23
|
+
# @param kwargs [Hash] RDF work options
|
|
24
|
+
# @return [Sevgi::Graphics::Element] metadata element
|
|
15
25
|
def License_CC_BY_NC(**kwargs, &block)
|
|
16
26
|
License(**kwargs, license: "https://creativecommons.org/licenses/by-nc/4.0/", &block)
|
|
17
27
|
end
|
|
18
28
|
|
|
29
|
+
# Adds Creative Commons BY-NC-SA license metadata.
|
|
30
|
+
# @param kwargs [Hash] RDF work options
|
|
31
|
+
# @return [Sevgi::Graphics::Element] metadata element
|
|
19
32
|
def License_CC_BY_NC_SA(**kwargs, &block)
|
|
20
33
|
License(**kwargs, license: "https://creativecommons.org/licenses/by-nc-sa/4.0/", &block)
|
|
21
34
|
end
|
|
22
35
|
|
|
36
|
+
# Adds Creative Commons BY-ND license metadata.
|
|
37
|
+
# @param kwargs [Hash] RDF work options
|
|
38
|
+
# @return [Sevgi::Graphics::Element] metadata element
|
|
23
39
|
def License_CC_BY_ND(**kwargs, &block)
|
|
24
40
|
License(**kwargs, license: "https://creativecommons.org/licenses/by-nd/4.0/", &block)
|
|
25
41
|
end
|
|
26
42
|
|
|
43
|
+
# Adds Creative Commons BY-NC-ND license metadata.
|
|
44
|
+
# @param kwargs [Hash] RDF work options
|
|
45
|
+
# @return [Sevgi::Graphics::Element] metadata element
|
|
27
46
|
def License_CC_BY_NC_ND(**kwargs, &block)
|
|
28
47
|
License(**kwargs, license: "https://creativecommons.org/licenses/by-nc-nd/4.0/", &block)
|
|
29
48
|
end
|
|
30
49
|
|
|
50
|
+
# Adds Creative Commons Zero metadata.
|
|
51
|
+
# @param kwargs [Hash] RDF work options
|
|
52
|
+
# @return [Sevgi::Graphics::Element] metadata element
|
|
31
53
|
def License_CC0(**kwargs, &block)
|
|
32
54
|
License(**kwargs, license: "https://creativecommons.org/publicdomain/zero/1.0/", &block)
|
|
33
55
|
end
|
|
34
56
|
|
|
57
|
+
# Adds Free Art License metadata.
|
|
58
|
+
# @param kwargs [Hash] RDF work options
|
|
59
|
+
# @return [Sevgi::Graphics::Element] metadata element
|
|
35
60
|
def License_LAL(**kwargs, &block) = License(**kwargs, license: "https://artlibre.org/licence/lal/en/", &block)
|
|
36
61
|
|
|
62
|
+
# Builds an RDF root element.
|
|
63
|
+
# @param _kwargs [Hash] currently unused options
|
|
64
|
+
# @return [Sevgi::Graphics::Element] RDF element
|
|
65
|
+
# @raise [Sevgi::ArgumentError] when no block is given
|
|
37
66
|
def RDF(**_kwargs, &block)
|
|
38
|
-
|
|
67
|
+
ArgumentError.("Block required") unless block
|
|
39
68
|
|
|
40
69
|
Element(
|
|
41
70
|
:"rdf:RDF",
|
|
@@ -48,6 +77,16 @@ module Sevgi
|
|
|
48
77
|
end
|
|
49
78
|
|
|
50
79
|
# rubocop:disable Metrics/MethodLength
|
|
80
|
+
# Builds a Creative Commons RDF Work element.
|
|
81
|
+
# @param kwargs [Hash] RDF work options
|
|
82
|
+
# @option kwargs [String] :title work title
|
|
83
|
+
# @option kwargs [String] :description work description
|
|
84
|
+
# @option kwargs [String] :creator creator
|
|
85
|
+
# @option kwargs [String] :publisher publisher
|
|
86
|
+
# @option kwargs [String] :date date
|
|
87
|
+
# @option kwargs [String] :language language
|
|
88
|
+
# @option kwargs [String] :license license URL
|
|
89
|
+
# @return [Sevgi::Graphics::Element] RDF element
|
|
51
90
|
def RDFWork(**kwargs, &block)
|
|
52
91
|
RDF do
|
|
53
92
|
Element(:"cc:Work", "rdf:about": "") do
|
|
@@ -3,18 +3,36 @@
|
|
|
3
3
|
module Sevgi
|
|
4
4
|
module Graphics
|
|
5
5
|
module Mixtures
|
|
6
|
+
# DSL helpers for rendering SVG documents and children.
|
|
6
7
|
module Render
|
|
8
|
+
# SVG source renderer.
|
|
9
|
+
# @api private
|
|
7
10
|
class Renderer
|
|
11
|
+
# Default renderer options.
|
|
8
12
|
DEFAULTS = {indent: " ", linelength: 140, style: :hybrid}.freeze
|
|
9
13
|
|
|
14
|
+
# Attribute rendering strategies.
|
|
15
|
+
# @api private
|
|
10
16
|
module Attributes
|
|
17
|
+
# Block-style attribute renderer.
|
|
18
|
+
# @api private
|
|
11
19
|
module Block
|
|
20
|
+
# Renders attributes in block form.
|
|
21
|
+
# @param element [Sevgi::Graphics::Element] rendered element
|
|
22
|
+
# @param depth [Integer] element depth
|
|
23
|
+
# @return [void]
|
|
12
24
|
def attributes(element, depth)
|
|
13
25
|
attributes_block(element, depth, element.attributes.to_xml_lines)
|
|
14
26
|
end
|
|
15
27
|
end
|
|
16
28
|
|
|
29
|
+
# Hybrid attribute renderer.
|
|
30
|
+
# @api private
|
|
17
31
|
module Hybrid
|
|
32
|
+
# Renders attributes inline or in block form according to line length.
|
|
33
|
+
# @param element [Sevgi::Graphics::Element] rendered element
|
|
34
|
+
# @param depth [Integer] element depth
|
|
35
|
+
# @return [void]
|
|
18
36
|
def attributes(element, depth)
|
|
19
37
|
if attributes_as_block?(lines = element.attributes.to_xml_lines, depth)
|
|
20
38
|
attributes_block(element, depth, lines)
|
|
@@ -23,16 +41,30 @@ module Sevgi
|
|
|
23
41
|
end
|
|
24
42
|
end
|
|
25
43
|
|
|
44
|
+
# Reports whether attributes should be rendered in block form.
|
|
45
|
+
# @param lines [Array<String>] rendered attribute lines
|
|
46
|
+
# @param depth [Integer] element depth
|
|
47
|
+
# @return [Boolean]
|
|
26
48
|
def attributes_as_block?(lines, depth)
|
|
27
49
|
linelength(lines, depth) > options[:linelength]
|
|
28
50
|
end
|
|
29
51
|
|
|
52
|
+
# Returns the effective inline line length.
|
|
53
|
+
# @param lines [Array<String>] rendered attribute lines
|
|
54
|
+
# @param depth [Integer] element depth
|
|
55
|
+
# @return [Integer]
|
|
30
56
|
def linelength(lines, depth)
|
|
31
57
|
indent(depth).length + lines.sum(&:length)
|
|
32
58
|
end
|
|
33
59
|
end
|
|
34
60
|
|
|
61
|
+
# Inline attribute renderer.
|
|
62
|
+
# @api private
|
|
35
63
|
module Inline
|
|
64
|
+
# Renders attributes inline.
|
|
65
|
+
# @param element [Sevgi::Graphics::Element] rendered element
|
|
66
|
+
# @param depth [Integer] element depth
|
|
67
|
+
# @return [void]
|
|
36
68
|
def attributes(element, depth)
|
|
37
69
|
attributes_inline(element, depth, element.attributes.to_xml_lines)
|
|
38
70
|
end
|
|
@@ -47,17 +79,41 @@ module Sevgi
|
|
|
47
79
|
|
|
48
80
|
private_constant :ELEMENTS_WITH_INLINE_CONTENT, :ELEMENTS_WITH_BLOCK_CONTENT, :SEPARATOR
|
|
49
81
|
|
|
50
|
-
|
|
82
|
+
# @return [Sevgi::Graphics::Element] root element
|
|
83
|
+
attr_reader :root
|
|
51
84
|
|
|
85
|
+
# @return [Hash] renderer options
|
|
86
|
+
attr_reader :options
|
|
87
|
+
|
|
88
|
+
# @return [Array<Array<String>>] buffered output lines
|
|
89
|
+
attr_reader :output
|
|
90
|
+
|
|
91
|
+
# Inline-content output splice helper.
|
|
92
|
+
# @api private
|
|
52
93
|
class Inlines
|
|
94
|
+
# Inline mark with start, stop, and depth.
|
|
53
95
|
Mark = Struct.new(:start, :stop, :depth)
|
|
54
96
|
|
|
97
|
+
# Creates an inline splice tracker.
|
|
98
|
+
# @return [void]
|
|
55
99
|
def initialize = @marks = []
|
|
56
100
|
|
|
101
|
+
# Starts an inline splice range.
|
|
102
|
+
# @param index [Integer] output index
|
|
103
|
+
# @param depth [Integer] element depth
|
|
104
|
+
# @return [Array<Sevgi::Graphics::Mixtures::Render::Renderer::Inlines::Mark>]
|
|
57
105
|
def start(index, depth) = @marks << Mark.new(start: index, depth:)
|
|
58
106
|
|
|
107
|
+
# Ends the latest inline splice range.
|
|
108
|
+
# @param index [Integer] output index
|
|
109
|
+
# @return [Integer]
|
|
59
110
|
def stop(index) = @marks.last.stop = index
|
|
60
111
|
|
|
112
|
+
# Joins marked output ranges into inline content.
|
|
113
|
+
# @param output [Array<Array<String>, nil>] renderer output buffer
|
|
114
|
+
# @param indent [String] indentation unit
|
|
115
|
+
# @param separator [String] line separator
|
|
116
|
+
# @return [Array<Array<String>, nil>, nil]
|
|
61
117
|
def join(output, indent:, separator:)
|
|
62
118
|
return if @marks.empty?
|
|
63
119
|
|
|
@@ -75,6 +131,15 @@ module Sevgi
|
|
|
75
131
|
end
|
|
76
132
|
end
|
|
77
133
|
|
|
134
|
+
# @overload initialize(root, **options)
|
|
135
|
+
# Creates a renderer.
|
|
136
|
+
# @param root [Sevgi::Graphics::Element] root element
|
|
137
|
+
# @param options [Hash] renderer options
|
|
138
|
+
# @option options [String] :indent indentation unit
|
|
139
|
+
# @option options [Integer] :linelength hybrid attribute line length
|
|
140
|
+
# @option options [Symbol] :style attribute style, one of :hybrid, :inline, or :block
|
|
141
|
+
# @return [void]
|
|
142
|
+
# @raise [Sevgi::ArgumentError] when style is missing or unsupported
|
|
78
143
|
def initialize(root, **)
|
|
79
144
|
@root = root
|
|
80
145
|
@options = DEFAULTS.merge(**)
|
|
@@ -84,14 +149,27 @@ module Sevgi
|
|
|
84
149
|
build
|
|
85
150
|
end
|
|
86
151
|
|
|
152
|
+
# @overload call(root, **options)
|
|
153
|
+
# Renders a root element.
|
|
154
|
+
# @param root [Sevgi::Graphics::Element] root element
|
|
155
|
+
# @param options [Hash] renderer options
|
|
156
|
+
# @return [String] SVG source
|
|
157
|
+
# @raise [Sevgi::ArgumentError] when style is missing or unsupported
|
|
87
158
|
def self.call(root, **) = new(root, **).call(*root.class.preambles)
|
|
88
159
|
|
|
160
|
+
# Appends rendered lines to the output buffer.
|
|
161
|
+
# @param depth [Integer, nil] indentation depth
|
|
162
|
+
# @param lines [Array<String>] rendered lines
|
|
163
|
+
# @return [Array<Array<String>>, nil]
|
|
89
164
|
def append(depth, *lines)
|
|
90
165
|
return if lines.empty?
|
|
91
166
|
|
|
92
167
|
output.append(lines.map { "#{indent(depth) if depth}#{it}" })
|
|
93
168
|
end
|
|
94
169
|
|
|
170
|
+
# Renders the document.
|
|
171
|
+
# @param preambles [Array<String>] preamble lines
|
|
172
|
+
# @return [String] SVG source
|
|
95
173
|
def call(*preambles)
|
|
96
174
|
output.append(preambles) unless preambles.empty?
|
|
97
175
|
|
|
@@ -108,6 +186,8 @@ module Sevgi
|
|
|
108
186
|
attr_reader :inlines
|
|
109
187
|
|
|
110
188
|
def attributes_block(element, depth, lines)
|
|
189
|
+
return attributes_inline(element, depth, lines) if lines.empty?
|
|
190
|
+
|
|
111
191
|
append(depth, "<#{element.name}")
|
|
112
192
|
append(depth + 1, *lines)
|
|
113
193
|
append(depth, ">") unless childless?(element)
|
|
@@ -202,9 +282,17 @@ module Sevgi
|
|
|
202
282
|
|
|
203
283
|
private_constant :Renderer
|
|
204
284
|
|
|
285
|
+
# @overload Render(**options)
|
|
286
|
+
# Renders this element as SVG source.
|
|
287
|
+
# @param options [Hash] renderer options
|
|
288
|
+
# @return [String] SVG source
|
|
289
|
+
# @raise [Sevgi::ArgumentError] when style is missing or unsupported
|
|
205
290
|
def Render(**) = Renderer.(self, **)
|
|
206
291
|
|
|
207
|
-
|
|
292
|
+
# Renders only this element's children.
|
|
293
|
+
# @param separator [String] separator between child documents
|
|
294
|
+
# @return [String] rendered children
|
|
295
|
+
def RenderChildren(separator = "\n\n") = children.map(&:Render).join(separator)
|
|
208
296
|
end
|
|
209
297
|
end
|
|
210
298
|
end
|
|
@@ -5,13 +5,23 @@ require "fileutils"
|
|
|
5
5
|
module Sevgi
|
|
6
6
|
module Graphics
|
|
7
7
|
module Mixtures
|
|
8
|
+
# DSL helpers for writing rendered SVG output.
|
|
8
9
|
module Save
|
|
10
|
+
# Default SVG extension.
|
|
9
11
|
EXT = ".svg"
|
|
10
12
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
+
# Writes rendered SVG to standard output.
|
|
14
|
+
# @param kwargs [Hash] render options
|
|
15
|
+
# @return [Object] F.out return value
|
|
16
|
+
def Out(**kwargs, &filter)
|
|
17
|
+
F.out(self.(**kwargs), &filter)
|
|
13
18
|
end
|
|
14
19
|
|
|
20
|
+
# Saves rendered SVG to a path derived from the caller by default.
|
|
21
|
+
# @param path [String, nil] output path or directory
|
|
22
|
+
# @param default [String, nil] default output path
|
|
23
|
+
# @param backup_suffix [String, nil] suffix used for an existing-file backup
|
|
24
|
+
# @return [Object] F.out return value
|
|
15
25
|
def Save(path = nil, default: nil, backup_suffix: nil, &filter)
|
|
16
26
|
default ||= F.subext(EXT, caller_locations(1..1).first.path)
|
|
17
27
|
|
|
@@ -26,7 +36,15 @@ module Sevgi
|
|
|
26
36
|
::FileUtils.cp(path, "#{path}#{backup_suffix}")
|
|
27
37
|
end
|
|
28
38
|
|
|
29
|
-
|
|
39
|
+
Write(path, &filter)
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
# Writes rendered SVG to a path.
|
|
43
|
+
# @param path [String] output path
|
|
44
|
+
# @param kwargs [Hash] render options
|
|
45
|
+
# @return [Object] F.out return value
|
|
46
|
+
def Write(path, **kwargs, &filter)
|
|
47
|
+
F.out(self.(**kwargs), path, &filter)
|
|
30
48
|
end
|
|
31
49
|
end
|
|
32
50
|
end
|
|
@@ -3,7 +3,14 @@
|
|
|
3
3
|
module Sevgi
|
|
4
4
|
module Graphics
|
|
5
5
|
module Mixtures
|
|
6
|
+
# DSL helpers for expanding callable modules into SVG symbols.
|
|
6
7
|
module Symbols
|
|
8
|
+
# Renders module callables as symbols under defs.
|
|
9
|
+
# @param mod [Module] callable drawing module
|
|
10
|
+
# @param args [Array<Object>] callable arguments
|
|
11
|
+
# @param kwargs [Hash] defs attributes
|
|
12
|
+
# @return [Sevgi::Graphics::Element] defs element
|
|
13
|
+
# @raise [Sevgi::ArgumentError] when mod is not a plain module
|
|
7
14
|
def Symbols(mod, *args, **kwargs, &block)
|
|
8
15
|
CallWithin(mod, :defs, :symbol, *args, **kwargs) do |name, element|
|
|
9
16
|
element[:id] = block ? block.call(name) : name.to_s.split("_").join("-")
|
|
@@ -4,9 +4,22 @@ module Sevgi
|
|
|
4
4
|
module Graphics
|
|
5
5
|
module Mixtures
|
|
6
6
|
# rubocop:disable Metrics/MethodLength
|
|
7
|
+
# DSL helpers for repeated SVG use elements.
|
|
7
8
|
module Tile
|
|
9
|
+
# Prefix used for generated tile CSS classes.
|
|
8
10
|
PREFIX = "tile"
|
|
9
11
|
|
|
12
|
+
# Builds a two-dimensional tile grid.
|
|
13
|
+
# @param id [String] referenced template id
|
|
14
|
+
# @param nx [Integer] number of columns
|
|
15
|
+
# @param dx [Numeric] horizontal spacing
|
|
16
|
+
# @param ox [Numeric] horizontal offset
|
|
17
|
+
# @param ny [Integer] number of rows
|
|
18
|
+
# @param dy [Numeric] vertical spacing
|
|
19
|
+
# @param oy [Numeric] vertical offset
|
|
20
|
+
# @param proc [Proc, nil] optional coordinate/customization proc
|
|
21
|
+
# @return [Sevgi::Graphics::Element] self
|
|
22
|
+
# @raise [Sevgi::ArgumentError] when a required tile argument is missing or invalid
|
|
10
23
|
def Tile(
|
|
11
24
|
id = Undefined,
|
|
12
25
|
nx: Undefined,
|
|
@@ -48,8 +61,14 @@ module Sevgi
|
|
|
48
61
|
end
|
|
49
62
|
end
|
|
50
63
|
|
|
51
|
-
|
|
52
|
-
|
|
64
|
+
# Builds a one-dimensional horizontal tile row.
|
|
65
|
+
# @param id [String] referenced template id
|
|
66
|
+
# @param n [Integer] number of instances
|
|
67
|
+
# @param d [Numeric] horizontal spacing
|
|
68
|
+
# @param o [Numeric] horizontal offset
|
|
69
|
+
# @param proc [Proc, nil] optional coordinate/customization proc
|
|
70
|
+
# @return [Sevgi::Graphics::Element] self
|
|
71
|
+
# @raise [Sevgi::ArgumentError] when a required tile argument is missing or invalid
|
|
53
72
|
def TileX(id = Undefined, n: Undefined, d: Undefined, o: 0, proc: nil, &block)
|
|
54
73
|
Helper.assert(id:, n:, d:, o:, proc:)
|
|
55
74
|
|
|
@@ -75,8 +94,14 @@ module Sevgi
|
|
|
75
94
|
end
|
|
76
95
|
end
|
|
77
96
|
|
|
78
|
-
|
|
79
|
-
|
|
97
|
+
# Builds a one-dimensional vertical tile column.
|
|
98
|
+
# @param id [String] referenced template id
|
|
99
|
+
# @param n [Integer] number of instances
|
|
100
|
+
# @param d [Numeric] vertical spacing
|
|
101
|
+
# @param o [Numeric] vertical offset
|
|
102
|
+
# @param proc [Proc, nil] optional coordinate/customization proc
|
|
103
|
+
# @return [Sevgi::Graphics::Element] self
|
|
104
|
+
# @raise [Sevgi::ArgumentError] when a required tile argument is missing or invalid
|
|
80
105
|
def TileY(id = Undefined, n: Undefined, d: Undefined, o: 0, proc: nil, &block)
|
|
81
106
|
Helper.assert(id:, n:, d:, o:, proc:)
|
|
82
107
|
|
|
@@ -102,11 +127,12 @@ module Sevgi
|
|
|
102
127
|
end
|
|
103
128
|
end
|
|
104
129
|
|
|
105
|
-
|
|
106
|
-
|
|
130
|
+
# Tile argument validation and class helpers.
|
|
131
|
+
# @api private
|
|
107
132
|
module Helper
|
|
108
133
|
extend self
|
|
109
134
|
|
|
135
|
+
# Argument validators for tile helpers.
|
|
110
136
|
ASSERTION = {
|
|
111
137
|
id: proc { |name, value| "Argument '#{name}' must be a string" unless value.is_a?(::String) },
|
|
112
138
|
n: proc { |name, value| "Argument '#{name}' must be a positive integer" unless value.positive? },
|
|
@@ -121,6 +147,10 @@ module Sevgi
|
|
|
121
147
|
proc: proc { |name, value| "Argument '#{name}' must be a proc" unless value.nil? || value.is_a?(::Proc) }
|
|
122
148
|
}.freeze
|
|
123
149
|
|
|
150
|
+
# Validates tile arguments.
|
|
151
|
+
# @param kwargs [Hash] tile arguments
|
|
152
|
+
# @return [nil]
|
|
153
|
+
# @raise [Sevgi::ArgumentError] when an argument is missing or invalid
|
|
124
154
|
def assert(**kwargs)
|
|
125
155
|
kwargs.each do |name, value|
|
|
126
156
|
issue = "Argument '#{name}' required" if value == Undefined
|
|
@@ -135,6 +165,11 @@ module Sevgi
|
|
|
135
165
|
# rubocop:enable Metrics/MethodLength
|
|
136
166
|
end
|
|
137
167
|
|
|
168
|
+
# Returns positional tile CSS classes.
|
|
169
|
+
# @param as [String] class axis label
|
|
170
|
+
# @param index [Integer] zero-based index
|
|
171
|
+
# @param upper [Integer] item count
|
|
172
|
+
# @return [Array<String>]
|
|
138
173
|
def classify(as:, index:, upper:)
|
|
139
174
|
[].tap do |classes|
|
|
140
175
|
classes << "#{PREFIX}-#{as}-#{index + 1}"
|
|
@@ -3,7 +3,14 @@
|
|
|
3
3
|
module Sevgi
|
|
4
4
|
module Graphics
|
|
5
5
|
module Mixtures
|
|
6
|
+
# DSL helpers for SVG transform attributes.
|
|
6
7
|
module Transform
|
|
8
|
+
# Aligns an inner box inside an outer box.
|
|
9
|
+
# @param position [Symbol, String, nil] alignment name
|
|
10
|
+
# @param inner [#width, #height, nil] inner box
|
|
11
|
+
# @param outer [#width, #height, nil] outer box
|
|
12
|
+
# @return [Sevgi::Graphics::Element] self
|
|
13
|
+
# @raise [Sevgi::ArgumentError] when alignment is unsupported
|
|
7
14
|
def Align(position, inner:, outer:)
|
|
8
15
|
return self unless position && inner && outer
|
|
9
16
|
|
|
@@ -15,34 +22,47 @@ module Sevgi
|
|
|
15
22
|
end
|
|
16
23
|
end
|
|
17
24
|
|
|
25
|
+
# Appends a scale(-1, -1) transform.
|
|
26
|
+
# @return [Sevgi::Graphics::Element] self
|
|
18
27
|
def Flip
|
|
19
28
|
tap do
|
|
20
|
-
attributes[:"transform#{ATTRIBUTE_UPDATE_SUFFIX}"] = "scale(-1, 1)
|
|
29
|
+
attributes[:"transform#{ATTRIBUTE_UPDATE_SUFFIX}"] = "scale(-1, -1)"
|
|
21
30
|
end
|
|
22
31
|
end
|
|
23
32
|
|
|
33
|
+
# Appends a horizontal flip transform.
|
|
34
|
+
# @return [Sevgi::Graphics::Element] self
|
|
24
35
|
def FlipX
|
|
25
36
|
tap do
|
|
26
37
|
attributes[:"transform#{ATTRIBUTE_UPDATE_SUFFIX}"] = "scale(-1, 1)"
|
|
27
38
|
end
|
|
28
39
|
end
|
|
29
40
|
|
|
41
|
+
# Appends a vertical flip transform.
|
|
42
|
+
# @return [Sevgi::Graphics::Element] self
|
|
30
43
|
def FlipY
|
|
31
44
|
tap do
|
|
32
|
-
attributes[:"transform#{ATTRIBUTE_UPDATE_SUFFIX}"] = "scale(1, -
|
|
45
|
+
attributes[:"transform#{ATTRIBUTE_UPDATE_SUFFIX}"] = "scale(1, -1)"
|
|
33
46
|
end
|
|
34
47
|
end
|
|
35
48
|
|
|
49
|
+
# Appends a six-value SVG matrix transform.
|
|
50
|
+
# @param values [Array<Numeric>] matrix values
|
|
51
|
+
# @return [Sevgi::Graphics::Element] self
|
|
52
|
+
# @raise [Sevgi::ArgumentError] when exactly six values are not supplied
|
|
36
53
|
def Matrix(*values)
|
|
37
54
|
tap do
|
|
38
55
|
ArgumentError.("Incorrect transform matrix (six values required): #{values}") if values.size != 6
|
|
39
56
|
|
|
40
|
-
next if values.map(&:to_f).all? { it == 0.0 }
|
|
41
|
-
|
|
42
57
|
attributes[:"transform#{ATTRIBUTE_UPDATE_SUFFIX}"] = "matrix(#{values.join(" ")})"
|
|
43
58
|
end
|
|
44
59
|
end
|
|
45
60
|
|
|
61
|
+
# Appends an SVG rotate transform.
|
|
62
|
+
# @param a [Numeric] angle in degrees
|
|
63
|
+
# @param origin [Array<Numeric>] optional x and y origin
|
|
64
|
+
# @return [Sevgi::Graphics::Element] self
|
|
65
|
+
# @raise [Sevgi::ArgumentError] when origin is not two coordinates
|
|
46
66
|
def Rotate(a, *origin)
|
|
47
67
|
tap do
|
|
48
68
|
if !origin.empty? && origin.size != 2
|
|
@@ -55,28 +75,39 @@ module Sevgi
|
|
|
55
75
|
end
|
|
56
76
|
end
|
|
57
77
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
78
|
+
# @overload RotateRight(*origin)
|
|
79
|
+
# Appends a 90-degree SVG rotate transform.
|
|
80
|
+
# @param origin [Array<Numeric>] optional x and y origin
|
|
81
|
+
# @return [Sevgi::Graphics::Element] self
|
|
82
|
+
def RotateRight(...) = Rotate(90, ...)
|
|
83
|
+
|
|
84
|
+
# @overload RotateLeft(*origin)
|
|
85
|
+
# Appends a -90-degree SVG rotate transform.
|
|
86
|
+
# @param origin [Array<Numeric>] optional x and y origin
|
|
87
|
+
# @return [Sevgi::Graphics::Element] self
|
|
88
|
+
def RotateLeft(...) = Rotate(-90, ...)
|
|
89
|
+
|
|
90
|
+
# Appends an SVG scale transform.
|
|
91
|
+
# @param x [Numeric] x scale, or uniform scale when y is nil
|
|
92
|
+
# @param y [Numeric, nil] y scale
|
|
93
|
+
# @return [Sevgi::Graphics::Element] self
|
|
62
94
|
def Scale(x, y = nil)
|
|
63
95
|
tap do
|
|
64
|
-
next if x.to_f == 0.0 && (y.nil? || y.to_f == 0.0)
|
|
65
|
-
|
|
66
96
|
attributes[:"transform#{ATTRIBUTE_UPDATE_SUFFIX}"] = "scale(#{(y ? [x, y] : [x]).join(", ")})"
|
|
67
97
|
end
|
|
68
98
|
end
|
|
69
99
|
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
end
|
|
75
|
-
|
|
100
|
+
# Appends a skew transform through an SVG matrix.
|
|
101
|
+
# @param ax [Numeric] x skew angle in degrees
|
|
102
|
+
# @param ay [Numeric] y skew angle in degrees
|
|
103
|
+
# @return [Sevgi::Graphics::Element] self
|
|
76
104
|
def Skew(ax, ay)
|
|
77
105
|
Matrix(1.0, ::Math.tan(ay / 180.0 * ::Math::PI), ::Math.tan(ax / 180.0 * ::Math::PI), 1.0, 0.0, 0.0)
|
|
78
106
|
end
|
|
79
107
|
|
|
108
|
+
# Appends an SVG skewX transform.
|
|
109
|
+
# @param a [Numeric] angle in degrees
|
|
110
|
+
# @return [Sevgi::Graphics::Element] self
|
|
80
111
|
def SkewX(a)
|
|
81
112
|
tap do
|
|
82
113
|
next if a.to_f == 0.0
|
|
@@ -85,6 +116,9 @@ module Sevgi
|
|
|
85
116
|
end
|
|
86
117
|
end
|
|
87
118
|
|
|
119
|
+
# Appends an SVG skewY transform.
|
|
120
|
+
# @param a [Numeric] angle in degrees
|
|
121
|
+
# @return [Sevgi::Graphics::Element] self
|
|
88
122
|
def SkewY(a)
|
|
89
123
|
tap do
|
|
90
124
|
next if a.to_f == 0.0
|
|
@@ -93,6 +127,10 @@ module Sevgi
|
|
|
93
127
|
end
|
|
94
128
|
end
|
|
95
129
|
|
|
130
|
+
# Appends an SVG translate transform.
|
|
131
|
+
# @param x [Numeric] x translation
|
|
132
|
+
# @param y [Numeric, nil] y translation
|
|
133
|
+
# @return [Sevgi::Graphics::Element] self
|
|
96
134
|
def Translate(x, y = nil)
|
|
97
135
|
tap do
|
|
98
136
|
next if x.to_f == 0.0 && (y.nil? || y.to_f == 0.0)
|
|
@@ -3,18 +3,27 @@
|
|
|
3
3
|
module Sevgi
|
|
4
4
|
module Graphics
|
|
5
5
|
module Mixtures
|
|
6
|
+
# DSL helpers for floating text, comments, and inherited internal attributes.
|
|
6
7
|
module Underscore
|
|
8
|
+
# Creates a floating content element.
|
|
9
|
+
# @param contents [Array<Object>] text or content objects
|
|
10
|
+
# @return [Sevgi::Graphics::Element] floating element
|
|
7
11
|
def _(*contents)
|
|
8
12
|
Element(:_, *contents)
|
|
9
13
|
end
|
|
10
14
|
|
|
15
|
+
# Adds an XML comment.
|
|
16
|
+
# @param comment [String] comment text
|
|
17
|
+
# @return [Sevgi::Graphics::Element] floating comment element
|
|
11
18
|
def Comment(comment)
|
|
12
19
|
_(Content.verbatim("<!-- #{comment} -->"))
|
|
13
20
|
end
|
|
14
21
|
|
|
22
|
+
# Merges internal ancestral attributes from the document root to this element.
|
|
23
|
+
# @return [Hash] merged internal attributes
|
|
15
24
|
def Ancestral
|
|
16
25
|
{}.tap do |result|
|
|
17
|
-
Root.Traverse() { |element| result.merge!(element[:_]) if element.has?(:_) }
|
|
26
|
+
Root().Traverse() { |element| result.merge!(element[:_]) if element.has?(:_) }
|
|
18
27
|
end
|
|
19
28
|
end
|
|
20
29
|
end
|
|
@@ -3,19 +3,28 @@
|
|
|
3
3
|
module Sevgi
|
|
4
4
|
module Graphics
|
|
5
5
|
module Mixtures
|
|
6
|
+
# DSL helpers for SVG standard validation.
|
|
6
7
|
module Validate
|
|
8
|
+
# Returns text content used as SVG character data.
|
|
9
|
+
# @return [String, nil] joined text content or nil
|
|
7
10
|
def CData
|
|
8
11
|
return if !contents || contents.empty?
|
|
9
12
|
|
|
10
13
|
Content.text(contents)
|
|
11
14
|
end
|
|
12
15
|
|
|
16
|
+
# Reports whether a namespace is available on this element or an ancestor.
|
|
17
|
+
# @param name [Symbol, String] namespace suffix without xmlns:
|
|
18
|
+
# @return [Boolean]
|
|
13
19
|
def NS?(name)
|
|
14
20
|
self.class.attributes.key?(key = :"xmlns:#{name}") || TraverseUp { Stay(true) if it.has?(key) } || false
|
|
15
21
|
end
|
|
16
22
|
|
|
17
23
|
require "sevgi/standard"
|
|
18
24
|
|
|
25
|
+
# Validates this subtree against the SVG standard metadata.
|
|
26
|
+
# @return [Sevgi::Graphics::Element] self
|
|
27
|
+
# @raise [Sevgi::ValidationError] when SVG validation fails
|
|
19
28
|
def Validate
|
|
20
29
|
Traverse do |element|
|
|
21
30
|
Standard.conform(
|
|
@@ -27,7 +36,12 @@ module Sevgi
|
|
|
27
36
|
end
|
|
28
37
|
end
|
|
29
38
|
|
|
30
|
-
rescue ::LoadError
|
|
39
|
+
rescue ::LoadError => e
|
|
40
|
+
raise unless e.path == "sevgi/standard"
|
|
41
|
+
|
|
42
|
+
# @overload Validate
|
|
43
|
+
# No-op validation fallback when sevgi/standard is unavailable.
|
|
44
|
+
# @return [nil]
|
|
31
45
|
def Validate(...)
|
|
32
46
|
end
|
|
33
47
|
end
|