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
|
@@ -5,11 +5,47 @@ require "forwardable"
|
|
|
5
5
|
module Sevgi
|
|
6
6
|
module Graphics
|
|
7
7
|
module Mixtures
|
|
8
|
+
# Internal traversal stop token.
|
|
9
|
+
# @api private
|
|
10
|
+
Traversal = Data.define(:value) do
|
|
11
|
+
# Runs a depth-first traversal.
|
|
12
|
+
# @param element [Sevgi::Graphics::Element] starting element
|
|
13
|
+
# @param depth [Integer] starting depth
|
|
14
|
+
# @param leave [Proc, nil] optional leave callback
|
|
15
|
+
# @return [Sevgi::Graphics::Element, Object]
|
|
16
|
+
def self.call(element, depth, leave, &block)
|
|
17
|
+
catch(:traversal) do
|
|
18
|
+
visit(element, depth, leave, &block)
|
|
19
|
+
element
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def self.stop(value)
|
|
24
|
+
throw(:traversal, value.value) if value.is_a?(self)
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def self.visit(element, depth, leave, &block)
|
|
28
|
+
stop(block.call(element, depth))
|
|
29
|
+
element.children.each { |child| visit(child, depth + 1, leave, &block) }
|
|
30
|
+
stop(leave.call(element, depth)) if leave
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
private_class_method :stop, :visit
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
private_constant :Traversal
|
|
37
|
+
|
|
38
|
+
# Core SVG tree and attribute DSL helpers.
|
|
8
39
|
module Core
|
|
9
40
|
extend Forwardable
|
|
10
41
|
def_delegators :@attributes, :[], :[]=, :has?
|
|
11
42
|
def_delegators :@children, :first, :last, :at
|
|
12
43
|
|
|
44
|
+
# Moves this element under a new parent.
|
|
45
|
+
# @param new_parent [Sevgi::Graphics::Element, nil] target parent or current parent
|
|
46
|
+
# @param index [Integer] insertion index
|
|
47
|
+
# @return [Sevgi::Graphics::Element] self
|
|
48
|
+
# @raise [Sevgi::ArgumentError] when the target parent has a different element class
|
|
13
49
|
def Adopt(new_parent = nil, index: -1)
|
|
14
50
|
tap do
|
|
15
51
|
if new_parent
|
|
@@ -25,89 +61,131 @@ module Sevgi
|
|
|
25
61
|
end
|
|
26
62
|
end
|
|
27
63
|
|
|
64
|
+
# @overload AdoptFirst(new_parent = nil)
|
|
65
|
+
# Moves this element to the beginning of a parent.
|
|
66
|
+
# @param new_parent [Sevgi::Graphics::Element, nil] target parent or current parent
|
|
67
|
+
# @return [Sevgi::Graphics::Element] self
|
|
28
68
|
def AdoptFirst(*)
|
|
29
69
|
Adopt(*, index: 0)
|
|
30
70
|
end
|
|
31
71
|
|
|
72
|
+
# Appends existing elements as children.
|
|
73
|
+
# @param elements [Array<Sevgi::Graphics::Element>] elements to append
|
|
74
|
+
# @return [Sevgi::Graphics::Element] self
|
|
32
75
|
def Append(*elements)
|
|
33
76
|
tap { elements.each { it.Adopt(self) } }
|
|
34
77
|
end
|
|
35
78
|
|
|
79
|
+
# Adds CSS classes without duplicating existing values.
|
|
80
|
+
# @param classes [Array<String, Symbol>] class names
|
|
81
|
+
# @return [Sevgi::Graphics::Element] self
|
|
36
82
|
def Classify(*classes)
|
|
37
83
|
tap do
|
|
38
|
-
|
|
39
|
-
self[:class] = classes
|
|
40
|
-
next
|
|
41
|
-
end
|
|
42
|
-
|
|
43
|
-
case self[:class]
|
|
84
|
+
klasses = case self[:class]
|
|
44
85
|
when ::Array
|
|
45
86
|
self[:class]
|
|
46
87
|
when ::String
|
|
47
88
|
self[:class].split
|
|
48
|
-
|
|
89
|
+
when nil
|
|
90
|
+
[]
|
|
91
|
+
else
|
|
92
|
+
[self[:class]]
|
|
93
|
+
end
|
|
49
94
|
|
|
50
95
|
classes.each { klasses << it unless klasses.include?(it) }
|
|
96
|
+
self[:class] = klasses
|
|
51
97
|
end
|
|
52
98
|
end
|
|
53
99
|
|
|
100
|
+
# Assigns default attributes only when they are absent.
|
|
101
|
+
# @param attributes [Hash] default attributes
|
|
102
|
+
# @return [Sevgi::Graphics::Element] self
|
|
54
103
|
def Defaults(**attributes)
|
|
55
104
|
tap do
|
|
56
105
|
attributes.each do |key, value|
|
|
57
|
-
next if
|
|
106
|
+
next if has?(key)
|
|
58
107
|
|
|
59
108
|
self[key] = value
|
|
60
109
|
end
|
|
61
110
|
end
|
|
62
111
|
end
|
|
63
112
|
|
|
113
|
+
# Builds a child element with an explicit tag name.
|
|
114
|
+
# @param tag [Symbol, String] SVG tag name
|
|
115
|
+
# @param contents [Array<Object>] text or content objects
|
|
116
|
+
# @param attributes [Hash] SVG attributes
|
|
117
|
+
# @return [Sevgi::Graphics::Element] new child element
|
|
64
118
|
def Element(tag, *contents, **attributes, &block)
|
|
65
119
|
self.class.send(:new, tag.to_sym, contents: Content.contents(*contents), attributes:, parent: self, &block)
|
|
66
120
|
end
|
|
67
121
|
|
|
122
|
+
# Forwards this element as the first argument to another receiver.
|
|
123
|
+
# @overload Forward(receiver, method, *args, **kwargs)
|
|
124
|
+
# @param receiver [Object] target receiver
|
|
125
|
+
# @param method [Symbol, String] method name
|
|
126
|
+
# @param args [Array<Object>] additional arguments
|
|
127
|
+
# @param kwargs [Hash] additional keyword arguments
|
|
128
|
+
# @return [Object] forwarded call result
|
|
68
129
|
def Forward(receiver, method, ...)
|
|
69
130
|
receiver.public_send(method, self, ...)
|
|
70
131
|
end
|
|
71
132
|
|
|
133
|
+
# Reports whether this element has the given SVG name.
|
|
134
|
+
# @param name [Symbol, String] SVG name
|
|
135
|
+
# @return [Boolean]
|
|
72
136
|
def Is?(name)
|
|
73
137
|
self.name() == name.to_sym
|
|
74
138
|
end
|
|
75
139
|
|
|
140
|
+
# Removes this element from its parent.
|
|
141
|
+
# @return [Array<Sevgi::Graphics::Element>, nil] parent children after deletion, or nil for root elements
|
|
76
142
|
def Orphan
|
|
77
143
|
parent.children&.delete(self) unless Root?()
|
|
78
144
|
end
|
|
79
145
|
|
|
146
|
+
# Prepends existing elements as children.
|
|
147
|
+
# @param elements [Array<Sevgi::Graphics::Element>] elements to prepend
|
|
148
|
+
# @return [Sevgi::Graphics::Element] self
|
|
80
149
|
def Prepend(*elements)
|
|
81
150
|
tap { elements.each { it.AdoptFirst(self) } }
|
|
82
151
|
end
|
|
83
152
|
|
|
153
|
+
# Returns the root document element.
|
|
154
|
+
# @return [Sevgi::Graphics::Element]
|
|
84
155
|
def Root
|
|
85
156
|
element = self
|
|
86
|
-
element = element.parent
|
|
157
|
+
element = element.parent until element.Root?()
|
|
87
158
|
|
|
88
159
|
element
|
|
89
160
|
end
|
|
90
161
|
|
|
162
|
+
# Reports whether this element is the root document element.
|
|
163
|
+
# @return [Boolean]
|
|
91
164
|
def Root?
|
|
92
165
|
self.class.root?(self)
|
|
93
166
|
end
|
|
94
167
|
|
|
95
|
-
|
|
96
|
-
|
|
168
|
+
# @overload Stay(value)
|
|
169
|
+
# Wraps a traversal return value as a stop token.
|
|
170
|
+
# @param value [Object] value returned from traversal
|
|
171
|
+
# @return [Sevgi::Graphics::Mixtures::Traversal]
|
|
97
172
|
def Stay(...) = Traversal.new(...)
|
|
98
173
|
|
|
174
|
+
# Traverses the subtree depth-first.
|
|
175
|
+
# @param depth [Integer] starting depth
|
|
176
|
+
# @param leave [Proc, nil] optional leave callback
|
|
177
|
+
# @return [Sevgi::Graphics::Element, Object] self or the value passed through Stay
|
|
178
|
+
# @raise [Sevgi::ArgumentError] when no block is given
|
|
99
179
|
def Traverse(depth = 0, leave = nil, &block)
|
|
100
180
|
ArgumentError.("Block required") unless block
|
|
101
181
|
|
|
102
|
-
|
|
103
|
-
yield(self, depth).tap { return it.value if it.is_a?(Traversal) }
|
|
104
|
-
|
|
105
|
-
children.each { |child| child.Traverse(depth + 1, leave, &block) }
|
|
106
|
-
|
|
107
|
-
leave&.call(self, depth).tap { return it.value if it.is_a?(Traversal) }
|
|
108
|
-
end
|
|
182
|
+
Traversal.call(self, depth, leave, &block)
|
|
109
183
|
end
|
|
110
184
|
|
|
185
|
+
# Traverses ancestors from this element to the root.
|
|
186
|
+
# @param height [Integer] starting height
|
|
187
|
+
# @return [Object, nil] value passed through Stay, or nil
|
|
188
|
+
# @raise [Sevgi::ArgumentError] when no block is given
|
|
111
189
|
def TraverseUp(height = 0, &block)
|
|
112
190
|
ArgumentError.("Block required") unless block
|
|
113
191
|
|
|
@@ -123,14 +201,25 @@ module Sevgi
|
|
|
123
201
|
end
|
|
124
202
|
end
|
|
125
203
|
|
|
204
|
+
# Evaluates a block in the parent element context.
|
|
205
|
+
# @param args [Array<Object>] optional receiver override followed by block arguments
|
|
206
|
+
# @param kwargs [Hash] keyword arguments passed to the block
|
|
207
|
+
# @return [Sevgi::Graphics::Element] self
|
|
126
208
|
def With(*args, **kwargs, &block)
|
|
127
209
|
tap { (args.shift || self).parent.instance_exec(*args, **kwargs, &block) }
|
|
128
210
|
end
|
|
129
211
|
|
|
212
|
+
# Evaluates a block in this element context.
|
|
213
|
+
# @param args [Array<Object>] optional receiver override followed by block arguments
|
|
214
|
+
# @param kwargs [Hash] keyword arguments passed to the block
|
|
215
|
+
# @return [Sevgi::Graphics::Element] self
|
|
130
216
|
def Within(*args, **kwargs, &block)
|
|
131
217
|
tap { (args.shift || self).instance_exec(*args, **kwargs, &block) }
|
|
132
218
|
end
|
|
133
219
|
|
|
220
|
+
# Appends an element as a child.
|
|
221
|
+
# @param element [Sevgi::Graphics::Element] element to append
|
|
222
|
+
# @return [Sevgi::Graphics::Element] self
|
|
134
223
|
def <<(element)
|
|
135
224
|
Append(element)
|
|
136
225
|
end
|
|
@@ -3,7 +3,13 @@
|
|
|
3
3
|
module Sevgi
|
|
4
4
|
module Graphics
|
|
5
5
|
module Mixtures
|
|
6
|
+
# DSL helpers for duplicating elements and their subtrees.
|
|
6
7
|
module Duplicate
|
|
8
|
+
# Duplicates an element subtree and optionally translates it.
|
|
9
|
+
# @param dx [Numeric, nil] x translation
|
|
10
|
+
# @param dy [Numeric, nil] y translation
|
|
11
|
+
# @param parent [Sevgi::Graphics::Element, nil] parent for the duplicated subtree
|
|
12
|
+
# @return [Sevgi::Graphics::Element] duplicated element
|
|
7
13
|
def Duplicate(dx: nil, dy: nil, parent: nil, &block)
|
|
8
14
|
duplicated = dup
|
|
9
15
|
|
|
@@ -21,8 +27,16 @@ module Sevgi
|
|
|
21
27
|
duplicated
|
|
22
28
|
end
|
|
23
29
|
|
|
30
|
+
# Duplicates an element subtree along the x-axis.
|
|
31
|
+
# @param dx [Numeric] x translation
|
|
32
|
+
# @param parent [Sevgi::Graphics::Element, nil] parent for the duplicated subtree
|
|
33
|
+
# @return [Sevgi::Graphics::Element] duplicated element
|
|
24
34
|
def DuplicateX(dx, parent: nil, &block) = Duplicate(dx:, dy: 0, parent:, &block)
|
|
25
35
|
|
|
36
|
+
# Duplicates an element subtree along the y-axis.
|
|
37
|
+
# @param dy [Numeric] y translation
|
|
38
|
+
# @param parent [Sevgi::Graphics::Element, nil] parent for the duplicated subtree
|
|
39
|
+
# @return [Sevgi::Graphics::Element] duplicated element
|
|
26
40
|
def DuplicateY(dy, parent: nil, &block) = Duplicate(dx: 0, dy:, parent:, &block)
|
|
27
41
|
end
|
|
28
42
|
end
|
|
@@ -3,21 +3,44 @@
|
|
|
3
3
|
module Sevgi
|
|
4
4
|
module Graphics
|
|
5
5
|
module Mixtures
|
|
6
|
+
# DSL helpers for native SVG export formats.
|
|
6
7
|
module Export
|
|
8
|
+
# Exports the document as PDF.
|
|
9
|
+
# @param path [String, nil] output path or directory
|
|
10
|
+
# @param kwargs [Hash] export options
|
|
11
|
+
# @return [String] output path
|
|
12
|
+
# @raise [Sevgi::MissingComponentError] when sevgi/sundries is unavailable
|
|
13
|
+
# @raise [Sevgi::Sundries::ExportError] when native export fails
|
|
7
14
|
def PDF(path = nil, **kwargs, &block)
|
|
8
|
-
|
|
15
|
+
begin
|
|
16
|
+
require "sevgi/sundries"
|
|
17
|
+
|
|
18
|
+
rescue ::LoadError => e
|
|
19
|
+
raise unless e.path == "sevgi/sundries"
|
|
20
|
+
|
|
21
|
+
MissingComponentError.("sevgi/sundries")
|
|
22
|
+
end
|
|
9
23
|
|
|
10
24
|
Export(path, **kwargs, format: :pdf, &block)
|
|
11
|
-
rescue ::LoadError
|
|
12
|
-
raise NoMethodError, "\"sevgi/sundries\" required"
|
|
13
25
|
end
|
|
14
26
|
|
|
27
|
+
# Exports the document as PNG.
|
|
28
|
+
# @param path [String, nil] output path or directory
|
|
29
|
+
# @param kwargs [Hash] export options
|
|
30
|
+
# @return [String] output path
|
|
31
|
+
# @raise [Sevgi::MissingComponentError] when sevgi/sundries is unavailable
|
|
32
|
+
# @raise [Sevgi::Sundries::ExportError] when native export fails
|
|
15
33
|
def PNG(path = nil, **kwargs, &block)
|
|
16
|
-
|
|
34
|
+
begin
|
|
35
|
+
require "sevgi/sundries"
|
|
36
|
+
|
|
37
|
+
rescue ::LoadError => e
|
|
38
|
+
raise unless e.path == "sevgi/sundries"
|
|
39
|
+
|
|
40
|
+
MissingComponentError.("sevgi/sundries")
|
|
41
|
+
end
|
|
17
42
|
|
|
18
43
|
Export(path, **kwargs, format: :png, &block)
|
|
19
|
-
rescue ::LoadError
|
|
20
|
-
raise NoMethodError, "\"sevgi/sundries\" required"
|
|
21
44
|
end
|
|
22
45
|
|
|
23
46
|
private
|
|
@@ -3,14 +3,35 @@
|
|
|
3
3
|
module Sevgi
|
|
4
4
|
module Graphics
|
|
5
5
|
module Mixtures
|
|
6
|
+
# DSL helpers for drawing geometry-derived hatch lines.
|
|
6
7
|
module Hatch
|
|
8
|
+
# Draws one or more geometry line-like objects into this element.
|
|
9
|
+
# @param lines [Object, Array<Object>] drawable geometry objects
|
|
10
|
+
# @param kwargs [Hash] SVG attributes passed to each draw call
|
|
11
|
+
# @return [Array<Sevgi::Graphics::Element>] rendered line elements
|
|
7
12
|
def Draw(lines, **kwargs)
|
|
8
13
|
Array(lines).map { it.draw(self, **kwargs) }
|
|
9
14
|
end
|
|
10
15
|
|
|
11
|
-
#
|
|
12
|
-
|
|
13
|
-
|
|
16
|
+
# Draws hatch lines swept through a geometry element.
|
|
17
|
+
# @param element [Object] geometry element responding to position
|
|
18
|
+
# @param angle [Numeric] hatch angle in degrees
|
|
19
|
+
# @param step [Numeric] distance between hatch lines
|
|
20
|
+
# @param initial [Object, nil] initial point for the sweep
|
|
21
|
+
# @param kwargs [Hash] SVG attributes passed to each draw call
|
|
22
|
+
# @return [Array<Sevgi::Graphics::Element>] rendered line elements
|
|
23
|
+
# @raise [Sevgi::MissingComponentError] when sevgi/geometry is unavailable
|
|
24
|
+
def Hatch(element, angle:, step:, initial: nil, **kwargs)
|
|
25
|
+
begin
|
|
26
|
+
require "sevgi/geometry"
|
|
27
|
+
|
|
28
|
+
rescue ::LoadError => e
|
|
29
|
+
raise unless e.path == "sevgi/geometry"
|
|
30
|
+
|
|
31
|
+
MissingComponentError.("sevgi/geometry")
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
Draw(Geometry::Operation.sweep!(element, initial: initial || element.position, angle:, step:), **kwargs)
|
|
14
35
|
end
|
|
15
36
|
end
|
|
16
37
|
end
|
|
@@ -3,10 +3,22 @@
|
|
|
3
3
|
module Sevgi
|
|
4
4
|
module Graphics
|
|
5
5
|
module Mixtures
|
|
6
|
+
# DSL helpers for collecting and hiding SVG ids.
|
|
6
7
|
module Identify
|
|
8
|
+
# Index of element ids under a subtree.
|
|
7
9
|
class Identifiers
|
|
8
|
-
|
|
10
|
+
# @return [Sevgi::Graphics::Element] indexed root element
|
|
11
|
+
attr_reader :element
|
|
9
12
|
|
|
13
|
+
# @return [Hash<String, Sevgi::Graphics::Element>] id namespace
|
|
14
|
+
attr_reader :namespace
|
|
15
|
+
|
|
16
|
+
# @return [Hash<String, Array<Sevgi::Graphics::Element>>] duplicate id groups
|
|
17
|
+
attr_reader :collision
|
|
18
|
+
|
|
19
|
+
# Builds an id index for an element subtree.
|
|
20
|
+
# @param element [Sevgi::Graphics::Element] root element
|
|
21
|
+
# @return [void]
|
|
10
22
|
def initialize(element)
|
|
11
23
|
@element = element
|
|
12
24
|
@namespace = {}
|
|
@@ -15,10 +27,16 @@ module Sevgi
|
|
|
15
27
|
build
|
|
16
28
|
end
|
|
17
29
|
|
|
30
|
+
# Reports whether duplicate ids were found.
|
|
31
|
+
# @return [Boolean]
|
|
18
32
|
def conflict?
|
|
19
33
|
!@collision.empty?
|
|
20
34
|
end
|
|
21
35
|
|
|
36
|
+
# @overload [](id)
|
|
37
|
+
# Returns the element registered for an id.
|
|
38
|
+
# @param id [String] SVG id
|
|
39
|
+
# @return [Sevgi::Graphics::Element, nil]
|
|
22
40
|
def [](*)
|
|
23
41
|
@namespace[*]
|
|
24
42
|
end
|
|
@@ -38,6 +56,8 @@ module Sevgi
|
|
|
38
56
|
end
|
|
39
57
|
end
|
|
40
58
|
|
|
59
|
+
# Moves visible id attributes to Sevgi-internal id storage.
|
|
60
|
+
# @return [Sevgi::Graphics::Element] self
|
|
41
61
|
def Disidentify
|
|
42
62
|
Traverse do |element|
|
|
43
63
|
next unless element[:id]
|
|
@@ -46,6 +66,8 @@ module Sevgi
|
|
|
46
66
|
end
|
|
47
67
|
end
|
|
48
68
|
|
|
69
|
+
# Builds an id index for this element subtree.
|
|
70
|
+
# @return [Sevgi::Graphics::Mixtures::Identify::Identifiers]
|
|
49
71
|
def Identifiers = Identifiers.new(self)
|
|
50
72
|
end
|
|
51
73
|
end
|
|
@@ -3,14 +3,41 @@
|
|
|
3
3
|
module Sevgi
|
|
4
4
|
module Graphics
|
|
5
5
|
module Mixtures
|
|
6
|
+
# DSL helpers for including derendered SVG/XML fragments.
|
|
6
7
|
module Include
|
|
7
8
|
require "sevgi/derender"
|
|
8
9
|
|
|
10
|
+
# Includes a derendered node matching an id.
|
|
11
|
+
# @param file [String] source SVG/XML file
|
|
12
|
+
# @param id [String, Symbol] source node id
|
|
13
|
+
# @return [Sevgi::Graphics::Element] included element
|
|
14
|
+
# @raise [Sevgi::MissingComponentError] when sevgi/derender is unavailable
|
|
9
15
|
def Include(file, id) = Derender.evaluate_file(file, self, id:)
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
16
|
+
|
|
17
|
+
# Includes the children of a derendered node matching an id.
|
|
18
|
+
# @param file [String] source SVG/XML file
|
|
19
|
+
# @param id [String, Symbol] source node id
|
|
20
|
+
# @return [Array<Sevgi::Graphics::Element>] included children
|
|
21
|
+
# @raise [Sevgi::MissingComponentError] when sevgi/derender is unavailable
|
|
22
|
+
def IncludeChildren(file, id) = Derender.evaluate_file!(file, self, id:)
|
|
23
|
+
rescue ::LoadError => e
|
|
24
|
+
raise unless e.path == "sevgi/derender"
|
|
25
|
+
|
|
26
|
+
# @overload IncludeChildren(file, id)
|
|
27
|
+
# Raises because sevgi/derender is unavailable.
|
|
28
|
+
# @param file [String] source SVG/XML file
|
|
29
|
+
# @param id [String, Symbol] source node id
|
|
30
|
+
# @return [void]
|
|
31
|
+
# @raise [Sevgi::MissingComponentError] always
|
|
32
|
+
def IncludeChildren(...) = MissingComponentError.("sevgi/derender")
|
|
33
|
+
|
|
34
|
+
# @overload Include(file, id)
|
|
35
|
+
# Raises because sevgi/derender is unavailable.
|
|
36
|
+
# @param file [String] source SVG/XML file
|
|
37
|
+
# @param id [String, Symbol] source node id
|
|
38
|
+
# @return [void]
|
|
39
|
+
# @raise [Sevgi::MissingComponentError] always
|
|
40
|
+
def Include(...) = MissingComponentError.("sevgi/derender")
|
|
14
41
|
end
|
|
15
42
|
end
|
|
16
43
|
end
|
|
@@ -3,7 +3,15 @@
|
|
|
3
3
|
module Sevgi
|
|
4
4
|
module Graphics
|
|
5
5
|
module Mixtures
|
|
6
|
+
# Inkscape-specific SVG DSL helpers.
|
|
6
7
|
module Inkscape
|
|
8
|
+
# Adds Inkscape template metadata.
|
|
9
|
+
# @param name [String] template name
|
|
10
|
+
# @param desc [String, nil] short description
|
|
11
|
+
# @param author [String, nil] author
|
|
12
|
+
# @param date [String, nil] date
|
|
13
|
+
# @param keywords [Array<String>, String, nil] keywords
|
|
14
|
+
# @return [Sevgi::Graphics::Element] template metadata element
|
|
7
15
|
def InkscapeTemplateInfo(name:, desc: nil, author: nil, date: nil, keywords: nil)
|
|
8
16
|
Element(:"inkscape:_templateinfo") do
|
|
9
17
|
Element(:"inkscape:_name", name)
|
|
@@ -14,29 +22,59 @@ module Sevgi
|
|
|
14
22
|
end
|
|
15
23
|
end
|
|
16
24
|
|
|
25
|
+
# Renders a callable module inside a group.
|
|
26
|
+
# @param mod [Module] callable drawing module
|
|
27
|
+
# @param args [Array<Object>] callable arguments
|
|
28
|
+
# @param kwargs [Hash] group attributes
|
|
29
|
+
# @return [Sevgi::Graphics::Element] group element
|
|
30
|
+
# @raise [Sevgi::ArgumentError] when mod is not a plain module
|
|
17
31
|
def Group(mod, *args, **kwargs, &block)
|
|
18
32
|
kwargs = kwargs.merge(id: F.demodulize(mod).to_sym) unless kwargs.key?(:id)
|
|
19
33
|
g(**kwargs) { Call(mod, *args, &block) }
|
|
20
34
|
end
|
|
21
35
|
|
|
36
|
+
# Renders a callable module inside an Inkscape layer.
|
|
37
|
+
# @param mod [Module] callable drawing module
|
|
38
|
+
# @param args [Array<Object>] callable arguments
|
|
39
|
+
# @param kwargs [Hash] layer attributes
|
|
40
|
+
# @return [Sevgi::Graphics::Element] layer element
|
|
41
|
+
# @raise [Sevgi::ArgumentError] when mod is not a plain module
|
|
22
42
|
def Layer(mod, *args, **kwargs, &block)
|
|
23
43
|
kwargs = kwargs.merge(id: F.demodulize(mod).to_sym) unless kwargs.key?(:id)
|
|
24
44
|
layer(**kwargs) { Call(mod, *args, &block) }
|
|
25
45
|
end
|
|
26
46
|
|
|
47
|
+
# Renders a callable module inside an insensitive Inkscape layer.
|
|
48
|
+
# @param mod [Module] callable drawing module
|
|
49
|
+
# @param args [Array<Object>] callable arguments
|
|
50
|
+
# @param kwargs [Hash] layer attributes
|
|
51
|
+
# @return [Sevgi::Graphics::Element] layer element
|
|
52
|
+
# @raise [Sevgi::ArgumentError] when mod is not a plain module
|
|
27
53
|
def Layer!(mod, *args, **kwargs, &block)
|
|
28
54
|
kwargs = kwargs.merge(id: F.demodulize(mod).to_sym) unless kwargs.key?(:id)
|
|
29
55
|
layer!(**kwargs) { Call(mod, *args, &block) }
|
|
30
56
|
end
|
|
31
57
|
|
|
58
|
+
# @overload layer(**attributes)
|
|
59
|
+
# Builds an Inkscape layer group.
|
|
60
|
+
# @param attributes [Hash] layer attributes
|
|
61
|
+
# @return [Sevgi::Graphics::Element] layer group
|
|
32
62
|
def layer(**, &block)
|
|
33
63
|
g("inkscape:groupmode": "layer", **, &block)
|
|
34
64
|
end
|
|
35
65
|
|
|
66
|
+
# @overload layer!(**attributes)
|
|
67
|
+
# Builds an insensitive Inkscape layer group.
|
|
68
|
+
# @param attributes [Hash] layer attributes
|
|
69
|
+
# @return [Sevgi::Graphics::Element] layer group
|
|
36
70
|
def layer!(**, &block)
|
|
37
71
|
layer("sodipodi:insensitive": "true", **, &block)
|
|
38
72
|
end
|
|
39
73
|
|
|
74
|
+
# Builds an Inkscape namedview containing page elements.
|
|
75
|
+
# @param pages [Array<Hash>] page attribute hashes
|
|
76
|
+
# @param id [String] namedview id
|
|
77
|
+
# @return [Sevgi::Graphics::Element] namedview element
|
|
40
78
|
def Pages(*pages, id: "namedview", **, &block)
|
|
41
79
|
Element(:"sodipodi:namedview", id:, **) do
|
|
42
80
|
pages.each_with_index do |page, i|
|
|
@@ -48,12 +86,20 @@ module Sevgi
|
|
|
48
86
|
end
|
|
49
87
|
end
|
|
50
88
|
|
|
89
|
+
# Builds a tabular set of Inkscape pages.
|
|
90
|
+
# @param rows [Integer] number of rows
|
|
91
|
+
# @param cols [Integer] number of columns
|
|
92
|
+
# @param width [Numeric] page width
|
|
93
|
+
# @param height [Numeric] page height
|
|
94
|
+
# @param gap [Numeric] gap between pages
|
|
95
|
+
# @param id [String] namedview id
|
|
96
|
+
# @return [Array<Array>] matrix entries as x, y, and label tuples
|
|
51
97
|
def PagesTabular(rows:, cols:, width:, height:, gap:, id: "namedview", **)
|
|
52
98
|
[].tap do |matrix|
|
|
53
99
|
Element(:"sodipodi:namedview", id:) do
|
|
54
100
|
rows.times do |row|
|
|
55
101
|
cols.times do |col|
|
|
56
|
-
matrix << (x, y, label = col * (
|
|
102
|
+
matrix << (x, y, label = col * (width + gap), row * (height + gap), "#{row + 1}x#{col + 1}")
|
|
57
103
|
Element(:"inkscape:page", id: "pageview-#{label}", x:, y:, width:, height:, **)
|
|
58
104
|
end
|
|
59
105
|
end
|
|
@@ -62,6 +108,10 @@ module Sevgi
|
|
|
62
108
|
end
|
|
63
109
|
|
|
64
110
|
# Internal symbol which does not show up Symbols Menu
|
|
111
|
+
# @overload symbol!(**attributes)
|
|
112
|
+
# Builds an Inkscape symbol group hidden from the symbols menu.
|
|
113
|
+
# @param attributes [Hash] symbol attributes
|
|
114
|
+
# @return [Sevgi::Graphics::Element] symbol group
|
|
65
115
|
def symbol!(**, &block)
|
|
66
116
|
if Is?(:defs)
|
|
67
117
|
g(role: "inkscape:symbol", **, &block)
|
|
@@ -70,10 +120,6 @@ module Sevgi
|
|
|
70
120
|
end
|
|
71
121
|
end
|
|
72
122
|
|
|
73
|
-
def Symbol!(mod, *args, **kwargs, &block)
|
|
74
|
-
kwargs = kwargs.merge(id: F.demodulize(mod).to_sym) unless kwargs.key?(:id)
|
|
75
|
-
symbol!(**kwargs) { Call(mod, *args, &block) }
|
|
76
|
-
end
|
|
77
123
|
end
|
|
78
124
|
end
|
|
79
125
|
end
|
|
@@ -2,17 +2,29 @@
|
|
|
2
2
|
|
|
3
3
|
module Sevgi
|
|
4
4
|
module Graphics
|
|
5
|
+
# Raised when graphics lint checks fail.
|
|
5
6
|
LintError = Class.new(Error)
|
|
6
7
|
|
|
7
8
|
module Mixtures
|
|
9
|
+
# DSL helpers for pre-render structural linting.
|
|
8
10
|
module Lint
|
|
11
|
+
# @overload Lint
|
|
12
|
+
# Runs graphics lint checks.
|
|
13
|
+
# @return [true]
|
|
14
|
+
# @raise [Sevgi::Graphics::LintError] when a lint check fails
|
|
9
15
|
def Lint(...)
|
|
10
16
|
IdentitiesAreUniq.(self, ...)
|
|
11
17
|
end
|
|
12
18
|
|
|
19
|
+
# Lint check that rejects duplicate visible ids.
|
|
20
|
+
# @api private
|
|
13
21
|
module IdentitiesAreUniq
|
|
14
22
|
extend self
|
|
15
23
|
|
|
24
|
+
# Checks an element subtree for duplicate ids.
|
|
25
|
+
# @param element [Sevgi::Graphics::Element] root element
|
|
26
|
+
# @return [true]
|
|
27
|
+
# @raise [Sevgi::Graphics::LintError] when duplicate ids exist
|
|
16
28
|
def call(element)
|
|
17
29
|
return true unless (identifiers = element.Identifiers()).conflict?
|
|
18
30
|
|
|
@@ -3,9 +3,20 @@
|
|
|
3
3
|
module Sevgi
|
|
4
4
|
module Graphics
|
|
5
5
|
module Mixtures
|
|
6
|
+
# Compatibility helpers for profiles that do not load richer mixtures.
|
|
6
7
|
module Polyfills
|
|
8
|
+
# @overload layer(*args, **attributes)
|
|
9
|
+
# Builds a generic group as a layer fallback.
|
|
10
|
+
# @param args [Array<Object>] group content arguments
|
|
11
|
+
# @param attributes [Hash] group attributes
|
|
12
|
+
# @return [Sevgi::Graphics::Element] group element
|
|
7
13
|
def layer(...) = g(...)
|
|
8
14
|
|
|
15
|
+
# @overload symbol!(*args, **attributes)
|
|
16
|
+
# Builds a regular symbol as a fallback for hidden symbols.
|
|
17
|
+
# @param args [Array<Object>] symbol content arguments
|
|
18
|
+
# @param attributes [Hash] symbol attributes
|
|
19
|
+
# @return [Sevgi::Graphics::Element] symbol element
|
|
9
20
|
def symbol!(...) = symbol(...)
|
|
10
21
|
end
|
|
11
22
|
end
|