sevgi-graphics 0.93.1 → 0.95.0
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/CHANGELOG.md +152 -0
- data/LICENSE +674 -0
- data/README.md +34 -2
- data/lib/sevgi/graphics/attribute.rb +146 -32
- data/lib/sevgi/graphics/auxiliary/canvas.rb +64 -14
- data/lib/sevgi/graphics/auxiliary/content.rb +156 -21
- data/lib/sevgi/graphics/auxiliary/margin.rb +14 -2
- data/lib/sevgi/graphics/auxiliary/paper.rb +91 -84
- data/lib/sevgi/graphics/auxiliary/scalar.rb +40 -0
- data/lib/sevgi/graphics/auxiliary/sizes.rb +62 -0
- data/lib/sevgi/graphics/auxiliary.rb +2 -0
- data/lib/sevgi/graphics/document.rb +234 -56
- data/lib/sevgi/graphics/element.rb +42 -15
- data/lib/sevgi/graphics/mixtures/call.rb +29 -3
- data/lib/sevgi/graphics/mixtures/core.rb +138 -35
- data/lib/sevgi/graphics/mixtures/duplicate.rb +66 -16
- data/lib/sevgi/graphics/mixtures/export.rb +10 -2
- data/lib/sevgi/graphics/mixtures/identify.rb +3 -1
- data/lib/sevgi/graphics/mixtures/include.rb +28 -2
- data/lib/sevgi/graphics/mixtures/inkscape.rb +15 -0
- data/lib/sevgi/graphics/mixtures/rdf.rb +20 -0
- data/lib/sevgi/graphics/mixtures/render.rb +103 -24
- data/lib/sevgi/graphics/mixtures/save.rb +9 -0
- data/lib/sevgi/graphics/mixtures/symbols.rb +3 -0
- data/lib/sevgi/graphics/mixtures/tile.rb +17 -3
- data/lib/sevgi/graphics/mixtures/underscore.rb +14 -3
- data/lib/sevgi/graphics/mixtures/validate.rb +12 -0
- data/lib/sevgi/graphics/mixtures/wrappers.rb +4 -1
- data/lib/sevgi/graphics/mixtures.rb +4 -0
- data/lib/sevgi/graphics/version.rb +1 -1
- data/lib/sevgi/graphics/xml.rb +127 -0
- data/lib/sevgi/graphics.rb +33 -18
- metadata +10 -5
|
@@ -5,9 +5,15 @@ require "forwardable"
|
|
|
5
5
|
module Sevgi
|
|
6
6
|
module Graphics
|
|
7
7
|
module Mixtures
|
|
8
|
-
#
|
|
8
|
+
# Traversal stop token returned by {Core#Stay}.
|
|
9
|
+
#
|
|
10
|
+
# @!attribute [r] value
|
|
11
|
+
# @return [Object] value returned from traversal
|
|
12
|
+
Stop = Data.define(:value)
|
|
13
|
+
|
|
14
|
+
# Internal traversal engine.
|
|
9
15
|
# @api private
|
|
10
|
-
Traversal
|
|
16
|
+
module Traversal
|
|
11
17
|
# Runs a depth-first traversal.
|
|
12
18
|
# @param element [Sevgi::Graphics::Element] starting element
|
|
13
19
|
# @param depth [Integer] starting depth
|
|
@@ -21,7 +27,7 @@ module Sevgi
|
|
|
21
27
|
end
|
|
22
28
|
|
|
23
29
|
def self.stop(value)
|
|
24
|
-
throw(:traversal, value.value) if value.is_a?(
|
|
30
|
+
throw(:traversal, value.value) if value.is_a?(Stop)
|
|
25
31
|
end
|
|
26
32
|
|
|
27
33
|
def self.visit(element, depth, leave, &block)
|
|
@@ -46,18 +52,17 @@ module Sevgi
|
|
|
46
52
|
# @param index [Integer] insertion index
|
|
47
53
|
# @return [Sevgi::Graphics::Element] self
|
|
48
54
|
# @raise [Sevgi::ArgumentError] when the target parent has a different element class
|
|
55
|
+
# @raise [Sevgi::ArgumentError] when the target parent is this element or one of its descendants
|
|
56
|
+
# @raise [Sevgi::ArgumentError] when index is not an Integer insertion position
|
|
49
57
|
def Adopt(new_parent = nil, index: -1)
|
|
50
58
|
tap do
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
else
|
|
56
|
-
new_parent = parent
|
|
57
|
-
end
|
|
59
|
+
new_parent ||= parent
|
|
60
|
+
Adoption.validate(self, new_parent)
|
|
61
|
+
|
|
62
|
+
insertion = Adoption.index_for(self, new_parent, index)
|
|
58
63
|
|
|
59
64
|
self.Orphan()
|
|
60
|
-
(@parent = new_parent).children.insert(
|
|
65
|
+
(@parent = new_parent).children.insert(insertion, self)
|
|
61
66
|
end
|
|
62
67
|
end
|
|
63
68
|
|
|
@@ -65,35 +70,38 @@ module Sevgi
|
|
|
65
70
|
# Moves this element to the beginning of a parent.
|
|
66
71
|
# @param new_parent [Sevgi::Graphics::Element, nil] target parent or current parent
|
|
67
72
|
# @return [Sevgi::Graphics::Element] self
|
|
73
|
+
# @raise [Sevgi::ArgumentError] when the target parent has a different element class
|
|
74
|
+
# @raise [Sevgi::ArgumentError] when the target parent is this element or one of its descendants
|
|
68
75
|
def AdoptFirst(*)
|
|
69
76
|
Adopt(*, index: 0)
|
|
70
77
|
end
|
|
71
78
|
|
|
72
|
-
# Appends existing elements as children.
|
|
73
|
-
#
|
|
79
|
+
# Appends distinct existing elements as children in argument order.
|
|
80
|
+
# Each element transfers from its current parent. The complete batch is validated before any element moves.
|
|
81
|
+
# @param elements [Array<Sevgi::Graphics::Element>] distinct elements to append
|
|
74
82
|
# @return [Sevgi::Graphics::Element] self
|
|
83
|
+
# @raise [Sevgi::ArgumentError] when an argument has a different element class, is repeated, or is this target or its ancestor
|
|
75
84
|
def Append(*elements)
|
|
76
|
-
tap { elements
|
|
85
|
+
tap { Adoption.batch(elements, self, front: false) }
|
|
77
86
|
end
|
|
78
87
|
|
|
79
88
|
# Adds CSS classes without duplicating existing values.
|
|
80
|
-
# @param classes [Array<String, Symbol>] class
|
|
89
|
+
# @param classes [Array<String, Symbol, Array>] class tokens; strings are split on whitespace
|
|
81
90
|
# @return [Sevgi::Graphics::Element] self
|
|
82
91
|
def Classify(*classes)
|
|
83
92
|
tap do
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
+
tokens = proc do |value|
|
|
94
|
+
case value
|
|
95
|
+
when nil
|
|
96
|
+
[]
|
|
97
|
+
when ::Array
|
|
98
|
+
value.flat_map { tokens.call(it) }
|
|
99
|
+
else
|
|
100
|
+
value.to_s.split
|
|
101
|
+
end
|
|
93
102
|
end
|
|
94
103
|
|
|
95
|
-
|
|
96
|
-
self[:class] = klasses
|
|
104
|
+
self[:class] = [*tokens.call(self[:class]), *tokens.call(classes)].uniq
|
|
97
105
|
end
|
|
98
106
|
end
|
|
99
107
|
|
|
@@ -112,11 +120,14 @@ module Sevgi
|
|
|
112
120
|
|
|
113
121
|
# Builds a child element with an explicit tag name.
|
|
114
122
|
# @param tag [Symbol, String] SVG tag name
|
|
115
|
-
# @param contents [Array<Object>] text or content objects
|
|
123
|
+
# @param contents [Array<Object>] text or content objects; non-content objects are stringified and XML-encoded
|
|
116
124
|
# @param attributes [Hash] SVG attributes
|
|
125
|
+
# @yield evaluates the drawing DSL in the new child element
|
|
126
|
+
# @yieldreturn [Object] ignored block result
|
|
117
127
|
# @return [Sevgi::Graphics::Element] new child element
|
|
128
|
+
# @raise [Sevgi::ArgumentError] when the tag, attributes, or content are not valid XML
|
|
118
129
|
def Element(tag, *contents, **attributes, &block)
|
|
119
|
-
self.class.send(:new, tag
|
|
130
|
+
self.class.send(:new, tag, contents: Content.contents(*contents), attributes:, parent: self, &block)
|
|
120
131
|
end
|
|
121
132
|
|
|
122
133
|
# Forwards this element as the first argument to another receiver.
|
|
@@ -138,16 +149,18 @@ module Sevgi
|
|
|
138
149
|
end
|
|
139
150
|
|
|
140
151
|
# Removes this element from its parent.
|
|
141
|
-
# @return [
|
|
152
|
+
# @return [Sevgi::Graphics::Element, nil] the deleted element, or nil for root elements
|
|
142
153
|
def Orphan
|
|
143
154
|
parent.children&.delete(self) unless Root?()
|
|
144
155
|
end
|
|
145
156
|
|
|
146
|
-
# Prepends existing elements as children.
|
|
147
|
-
#
|
|
157
|
+
# Prepends distinct existing elements as children in argument order.
|
|
158
|
+
# Each element transfers from its current parent. The complete batch is validated before any element moves.
|
|
159
|
+
# @param elements [Array<Sevgi::Graphics::Element>] distinct elements to prepend
|
|
148
160
|
# @return [Sevgi::Graphics::Element] self
|
|
161
|
+
# @raise [Sevgi::ArgumentError] when an argument has a different element class, is repeated, or is this target or its ancestor
|
|
149
162
|
def Prepend(*elements)
|
|
150
|
-
tap { elements
|
|
163
|
+
tap { Adoption.batch(elements, self, front: true) }
|
|
151
164
|
end
|
|
152
165
|
|
|
153
166
|
# Returns the root document element.
|
|
@@ -168,12 +181,16 @@ module Sevgi
|
|
|
168
181
|
# @overload Stay(value)
|
|
169
182
|
# Wraps a traversal return value as a stop token.
|
|
170
183
|
# @param value [Object] value returned from traversal
|
|
171
|
-
# @return [Sevgi::Graphics::Mixtures::
|
|
172
|
-
def Stay(...) =
|
|
184
|
+
# @return [Sevgi::Graphics::Mixtures::Stop]
|
|
185
|
+
def Stay(...) = Stop.new(...)
|
|
173
186
|
|
|
174
187
|
# Traverses the subtree depth-first.
|
|
175
188
|
# @param depth [Integer] starting depth
|
|
176
189
|
# @param leave [Proc, nil] optional leave callback
|
|
190
|
+
# @yield [element, depth] visits each element before its children
|
|
191
|
+
# @yieldparam element [Sevgi::Graphics::Element] visited element
|
|
192
|
+
# @yieldparam depth [Integer] element depth
|
|
193
|
+
# @yieldreturn [Object] ignored unless it is a {Stop} token
|
|
177
194
|
# @return [Sevgi::Graphics::Element, Object] self or the value passed through Stay
|
|
178
195
|
# @raise [Sevgi::ArgumentError] when no block is given
|
|
179
196
|
def Traverse(depth = 0, leave = nil, &block)
|
|
@@ -184,6 +201,10 @@ module Sevgi
|
|
|
184
201
|
|
|
185
202
|
# Traverses ancestors from this element to the root.
|
|
186
203
|
# @param height [Integer] starting height
|
|
204
|
+
# @yield [element, height] visits this element and each ancestor
|
|
205
|
+
# @yieldparam element [Sevgi::Graphics::Element] visited element
|
|
206
|
+
# @yieldparam height [Integer] ancestor height
|
|
207
|
+
# @yieldreturn [Object] ignored unless it is a {Stop} token
|
|
187
208
|
# @return [Object, nil] value passed through Stay, or nil
|
|
188
209
|
# @raise [Sevgi::ArgumentError] when no block is given
|
|
189
210
|
def TraverseUp(height = 0, &block)
|
|
@@ -192,7 +213,7 @@ module Sevgi
|
|
|
192
213
|
element = self
|
|
193
214
|
|
|
194
215
|
loop do
|
|
195
|
-
yield(element, height).tap { return it.value if it.is_a?(
|
|
216
|
+
yield(element, height).tap { return it.value if it.is_a?(Stop) }
|
|
196
217
|
|
|
197
218
|
break if element.Root?()
|
|
198
219
|
|
|
@@ -204,6 +225,8 @@ module Sevgi
|
|
|
204
225
|
# Evaluates a block in the parent element context.
|
|
205
226
|
# @param args [Array<Object>] optional receiver override followed by block arguments
|
|
206
227
|
# @param kwargs [Hash] keyword arguments passed to the block
|
|
228
|
+
# @yield evaluates in the selected receiver's parent context
|
|
229
|
+
# @yieldreturn [Object] ignored block result
|
|
207
230
|
# @return [Sevgi::Graphics::Element] self
|
|
208
231
|
def With(*args, **kwargs, &block)
|
|
209
232
|
tap { (args.shift || self).parent.instance_exec(*args, **kwargs, &block) }
|
|
@@ -212,6 +235,8 @@ module Sevgi
|
|
|
212
235
|
# Evaluates a block in this element context.
|
|
213
236
|
# @param args [Array<Object>] optional receiver override followed by block arguments
|
|
214
237
|
# @param kwargs [Hash] keyword arguments passed to the block
|
|
238
|
+
# @yield evaluates in the selected receiver context
|
|
239
|
+
# @yieldreturn [Object] ignored block result
|
|
215
240
|
# @return [Sevgi::Graphics::Element] self
|
|
216
241
|
def Within(*args, **kwargs, &block)
|
|
217
242
|
tap { (args.shift || self).instance_exec(*args, **kwargs, &block) }
|
|
@@ -223,6 +248,84 @@ module Sevgi
|
|
|
223
248
|
def <<(element)
|
|
224
249
|
Append(element)
|
|
225
250
|
end
|
|
251
|
+
|
|
252
|
+
# Adoption target validation that keeps tree mutation atomic.
|
|
253
|
+
# @api private
|
|
254
|
+
module Adoption
|
|
255
|
+
# Moves a validated batch to the front or back of a parent.
|
|
256
|
+
# @param elements [Array<Sevgi::Graphics::Element>] elements to move
|
|
257
|
+
# @param parent [Sevgi::Graphics::Element] target parent
|
|
258
|
+
# @param front [Boolean] whether to prepend instead of append
|
|
259
|
+
# @return [void]
|
|
260
|
+
# @raise [Sevgi::ArgumentError] when an argument is incompatible, repeated, or would create a cycle
|
|
261
|
+
def self.batch(elements, parent, front:)
|
|
262
|
+
validate_batch(elements, parent)
|
|
263
|
+
|
|
264
|
+
if front
|
|
265
|
+
elements.reverse_each { it.AdoptFirst(parent) }
|
|
266
|
+
else
|
|
267
|
+
elements.each { it.Adopt(parent) }
|
|
268
|
+
end
|
|
269
|
+
end
|
|
270
|
+
|
|
271
|
+
# Rejects target parents that would create a cycle.
|
|
272
|
+
# @param element [Sevgi::Graphics::Element] element being moved
|
|
273
|
+
# @param parent [Sevgi::Graphics::Element, Object] target parent
|
|
274
|
+
# @return [void]
|
|
275
|
+
# @raise [Sevgi::ArgumentError] when the parent is incompatible or would create a cycle
|
|
276
|
+
def self.validate(element, parent)
|
|
277
|
+
unless element.instance_of?(parent.class)
|
|
278
|
+
ArgumentError.("Element type does not match the new parent type: #{element.class}")
|
|
279
|
+
end
|
|
280
|
+
|
|
281
|
+
ArgumentError.("Element cannot be adopted under itself") if parent.equal?(element)
|
|
282
|
+
|
|
283
|
+
while parent.respond_to?(:Root?)
|
|
284
|
+
ArgumentError.("Element cannot be adopted under its descendant") if parent.equal?(element)
|
|
285
|
+
break if parent.Root?()
|
|
286
|
+
|
|
287
|
+
parent = parent.parent
|
|
288
|
+
end
|
|
289
|
+
end
|
|
290
|
+
|
|
291
|
+
# Normalizes an insertion index before tree mutation.
|
|
292
|
+
# @param index [Integer] requested insertion index
|
|
293
|
+
# @param size [Integer] number of available child positions
|
|
294
|
+
# @return [Integer] normalized non-negative insertion index
|
|
295
|
+
# @raise [Sevgi::ArgumentError] when the index is not an insertion position
|
|
296
|
+
def self.index(index, size)
|
|
297
|
+
ArgumentError.("Adoption index must be an Integer") unless index.is_a?(Integer)
|
|
298
|
+
|
|
299
|
+
normalized = index.negative? ? size + index + 1 : index
|
|
300
|
+
ArgumentError.("Adoption index is outside the child list") unless normalized.between?(0, size)
|
|
301
|
+
|
|
302
|
+
normalized
|
|
303
|
+
end
|
|
304
|
+
|
|
305
|
+
# Returns an insertion index after accounting for a same-parent move.
|
|
306
|
+
# @param element [Sevgi::Graphics::Element] element being moved
|
|
307
|
+
# @param parent [Sevgi::Graphics::Element] target parent
|
|
308
|
+
# @param index [Integer] requested insertion index
|
|
309
|
+
# @return [Integer] normalized insertion index
|
|
310
|
+
def self.index_for(element, parent, index)
|
|
311
|
+
same_parent = element.parent.equal?(parent) && parent.children.include?(element)
|
|
312
|
+
index(index, parent.children.size - (same_parent ? 1 : 0))
|
|
313
|
+
end
|
|
314
|
+
|
|
315
|
+
def self.validate_batch(elements, parent)
|
|
316
|
+
seen = {}.compare_by_identity
|
|
317
|
+
elements.each do |element|
|
|
318
|
+
ArgumentError.("Element appears more than once in adoption batch") if seen.key?(element)
|
|
319
|
+
|
|
320
|
+
seen[element] = true
|
|
321
|
+
validate(element, parent)
|
|
322
|
+
end
|
|
323
|
+
end
|
|
324
|
+
|
|
325
|
+
private_class_method :validate_batch
|
|
326
|
+
end
|
|
327
|
+
|
|
328
|
+
private_constant :Adoption
|
|
226
329
|
end
|
|
227
330
|
end
|
|
228
331
|
end
|
|
@@ -3,41 +3,91 @@
|
|
|
3
3
|
module Sevgi
|
|
4
4
|
module Graphics
|
|
5
5
|
module Mixtures
|
|
6
|
-
# DSL helpers for duplicating
|
|
6
|
+
# DSL helpers for duplicating independent element subtrees.
|
|
7
7
|
module Duplicate
|
|
8
|
-
# Duplicates an element subtree and optionally translates it.
|
|
8
|
+
# Duplicates an element subtree as an independent tree and optionally translates it.
|
|
9
|
+
# Copied elements receive new child arrays, attribute stores, and content arrays. Public `id` attributes are
|
|
10
|
+
# moved to an internal `-id` attribute before the optional block runs, allowing the block to derive replacement
|
|
11
|
+
# ids without rendering duplicate public ids.
|
|
9
12
|
# @param dx [Numeric, nil] x translation
|
|
10
13
|
# @param dy [Numeric, nil] y translation
|
|
11
14
|
# @param parent [Sevgi::Graphics::Element, nil] parent for the duplicated subtree
|
|
15
|
+
# @yield [element] optional customization hook for each copied element
|
|
16
|
+
# @yieldparam element [Sevgi::Graphics::Element] copied element
|
|
17
|
+
# @yieldreturn [Object] ignored customization result
|
|
12
18
|
# @return [Sevgi::Graphics::Element] duplicated element
|
|
19
|
+
# @raise [Sevgi::ArgumentError] when the target parent has a different element class
|
|
20
|
+
# @raise [Sevgi::ArgumentError] when copied attributes or contents contain cyclic payloads
|
|
13
21
|
def Duplicate(dx: nil, dy: nil, parent: nil, &block)
|
|
14
|
-
duplicated =
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
id = (element.attributes = element.attributes.dup).delete(:id)
|
|
19
|
-
element[:"#{ATTRIBUTE_INTERNAL_PREFIX}id"] = id if id
|
|
20
|
-
block&.call(element)
|
|
21
|
-
end
|
|
22
|
-
|
|
23
|
-
duplicated.Translate(dx, dy) if dx || dy
|
|
24
|
-
|
|
25
|
-
duplicated.Adopt(parent)
|
|
26
|
-
|
|
27
|
-
duplicated
|
|
22
|
+
duplicated = Subtree.copy(self)
|
|
23
|
+
Subtree.prepare(duplicated, &block)
|
|
24
|
+
Subtree.translate(duplicated, dx, dy)
|
|
25
|
+
Subtree.attach(duplicated, self, parent)
|
|
28
26
|
end
|
|
29
27
|
|
|
30
28
|
# Duplicates an element subtree along the x-axis.
|
|
31
29
|
# @param dx [Numeric] x translation
|
|
32
30
|
# @param parent [Sevgi::Graphics::Element, nil] parent for the duplicated subtree
|
|
31
|
+
# @yield [element] optional customization hook for each copied element
|
|
32
|
+
# @yieldparam element [Sevgi::Graphics::Element] copied element
|
|
33
|
+
# @yieldreturn [Object] ignored customization result
|
|
33
34
|
# @return [Sevgi::Graphics::Element] duplicated element
|
|
35
|
+
# @raise [Sevgi::ArgumentError] when the target parent has a different element class
|
|
36
|
+
# @raise [Sevgi::ArgumentError] when copied attributes or contents contain cyclic payloads
|
|
34
37
|
def DuplicateX(dx, parent: nil, &block) = Duplicate(dx:, dy: 0, parent:, &block)
|
|
35
38
|
|
|
36
39
|
# Duplicates an element subtree along the y-axis.
|
|
37
40
|
# @param dy [Numeric] y translation
|
|
38
41
|
# @param parent [Sevgi::Graphics::Element, nil] parent for the duplicated subtree
|
|
42
|
+
# @yield [element] optional customization hook for each copied element
|
|
43
|
+
# @yieldparam element [Sevgi::Graphics::Element] copied element
|
|
44
|
+
# @yieldreturn [Object] ignored customization result
|
|
39
45
|
# @return [Sevgi::Graphics::Element] duplicated element
|
|
46
|
+
# @raise [Sevgi::ArgumentError] when the target parent has a different element class
|
|
47
|
+
# @raise [Sevgi::ArgumentError] when copied attributes or contents contain cyclic payloads
|
|
40
48
|
def DuplicateY(dy, parent: nil, &block) = Duplicate(dx: 0, dy:, parent:, &block)
|
|
49
|
+
|
|
50
|
+
# Recursive subtree copier that keeps duplicate implementation state out of the DSL surface.
|
|
51
|
+
# @api private
|
|
52
|
+
module Subtree
|
|
53
|
+
# Builds an independent copy of an element subtree.
|
|
54
|
+
# @param element [Sevgi::Graphics::Element] source subtree root
|
|
55
|
+
# @param parent [Sevgi::Graphics::Element, Object] parent for the copied root
|
|
56
|
+
# @return [Sevgi::Graphics::Element] copied subtree root
|
|
57
|
+
def self.copy(element, parent = element.parent)
|
|
58
|
+
element.dup.tap do |duplicated|
|
|
59
|
+
duplicated.send(:parent=, parent)
|
|
60
|
+
duplicated.send(:attributes=, element.attributes.dup)
|
|
61
|
+
duplicated.send(:contents=, element.contents.map(&:dup))
|
|
62
|
+
duplicated.send(:children=, element.children.map { |child| copy(child, duplicated) })
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
# Removes copied public ids and applies an optional customization hook.
|
|
67
|
+
# @api private
|
|
68
|
+
def self.prepare(element, &block)
|
|
69
|
+
element.Traverse() do |node|
|
|
70
|
+
id = node.attributes.delete(:id)
|
|
71
|
+
node[:"#{ATTRIBUTE_INTERNAL_PREFIX}id"] = id if id
|
|
72
|
+
block&.call(node)
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
# Applies an optional translation to a copied subtree.
|
|
77
|
+
# @api private
|
|
78
|
+
def self.translate(element, dx, dy)
|
|
79
|
+
element.Translate(dx, dy) if dx || dy
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
# Attaches a copied subtree unless it is a detached root copy.
|
|
83
|
+
# @api private
|
|
84
|
+
def self.attach(element, source, parent)
|
|
85
|
+
element.Adopt(parent) if parent || !source.Root?()
|
|
86
|
+
element
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
private_constant :Subtree
|
|
41
91
|
end
|
|
42
92
|
end
|
|
43
93
|
end
|
|
@@ -8,9 +8,13 @@ module Sevgi
|
|
|
8
8
|
# Exports the document as PDF.
|
|
9
9
|
# @param path [String, nil] output path or directory
|
|
10
10
|
# @param kwargs [Hash] export options
|
|
11
|
+
# @yield [svg] transforms SVG source before rendering
|
|
12
|
+
# @yieldparam svg [String] rendered SVG source
|
|
13
|
+
# @yieldreturn [String] transformed SVG source
|
|
11
14
|
# @return [String] output path
|
|
12
15
|
# @raise [Sevgi::MissingComponentError] when sevgi/sundries is unavailable
|
|
13
|
-
# @raise [Sevgi::
|
|
16
|
+
# @raise [Sevgi::MissingComponentError] when native export gems are unavailable
|
|
17
|
+
# @raise [Sevgi::Sundries::Export::ExportError] when native export fails
|
|
14
18
|
def PDF(path = nil, **kwargs, &block)
|
|
15
19
|
begin
|
|
16
20
|
require "sevgi/sundries"
|
|
@@ -27,9 +31,13 @@ module Sevgi
|
|
|
27
31
|
# Exports the document as PNG.
|
|
28
32
|
# @param path [String, nil] output path or directory
|
|
29
33
|
# @param kwargs [Hash] export options
|
|
34
|
+
# @yield [svg] transforms SVG source before rendering
|
|
35
|
+
# @yieldparam svg [String] rendered SVG source
|
|
36
|
+
# @yieldreturn [String] transformed SVG source
|
|
30
37
|
# @return [String] output path
|
|
31
38
|
# @raise [Sevgi::MissingComponentError] when sevgi/sundries is unavailable
|
|
32
|
-
# @raise [Sevgi::
|
|
39
|
+
# @raise [Sevgi::MissingComponentError] when native export gems are unavailable
|
|
40
|
+
# @raise [Sevgi::Sundries::Export::ExportError] when native export fails
|
|
33
41
|
def PNG(path = nil, **kwargs, &block)
|
|
34
42
|
begin
|
|
35
43
|
require "sevgi/sundries"
|
|
@@ -4,22 +4,48 @@ module Sevgi
|
|
|
4
4
|
module Graphics
|
|
5
5
|
module Mixtures
|
|
6
6
|
# DSL helpers for including derendered SVG/XML fragments.
|
|
7
|
+
#
|
|
8
|
+
# @!method Include(file, id)
|
|
9
|
+
# Includes a derendered node matching an id.
|
|
10
|
+
# SVG/XML content is treated as data and is not evaluated as Ruby source.
|
|
11
|
+
# @param file [String] source SVG/XML file
|
|
12
|
+
# @param id [String, Symbol] source node id
|
|
13
|
+
# @return [Sevgi::Graphics::Element, nil] included element, or nil when it produces no graphics output
|
|
14
|
+
# @raise [Sevgi::ArgumentError] when the file is absent or XML content is malformed, rootless, or lacks the id
|
|
15
|
+
# @raise [Sevgi::MissingComponentError] when sevgi/derender is unavailable
|
|
16
|
+
# @!method IncludeChildren(file, id)
|
|
17
|
+
# Includes the children of a derendered node matching an id.
|
|
18
|
+
# SVG/XML content is treated as data and is not evaluated as Ruby source.
|
|
19
|
+
# @param file [String] source SVG/XML file
|
|
20
|
+
# @param id [String, Symbol] source node id
|
|
21
|
+
# @return [Array<Sevgi::Graphics::Element>] included children
|
|
22
|
+
# @raise [Sevgi::ArgumentError] when the file is absent or XML content is malformed, rootless, or lacks the id
|
|
23
|
+
# @raise [Sevgi::MissingComponentError] when sevgi/derender is unavailable
|
|
7
24
|
module Include
|
|
8
25
|
require "sevgi/derender"
|
|
9
26
|
|
|
10
27
|
# Includes a derendered node matching an id.
|
|
28
|
+
#
|
|
29
|
+
# SVG/XML file content is treated as data and is not evaluated as Ruby source.
|
|
11
30
|
# @param file [String] source SVG/XML file
|
|
12
31
|
# @param id [String, Symbol] source node id
|
|
13
|
-
# @return [Sevgi::Graphics::Element] included element
|
|
32
|
+
# @return [Sevgi::Graphics::Element, nil] included element, or nil when the selected node produces no graphics
|
|
33
|
+
# output
|
|
34
|
+
# @raise [Sevgi::ArgumentError] when the file cannot be found, file content is malformed or rootless, or the id is
|
|
35
|
+
# absent
|
|
14
36
|
# @raise [Sevgi::MissingComponentError] when sevgi/derender is unavailable
|
|
15
37
|
def Include(file, id) = Derender.evaluate_file(file, self, id:)
|
|
16
38
|
|
|
17
39
|
# Includes the children of a derendered node matching an id.
|
|
40
|
+
#
|
|
41
|
+
# SVG/XML file content is treated as data and is not evaluated as Ruby source.
|
|
18
42
|
# @param file [String] source SVG/XML file
|
|
19
43
|
# @param id [String, Symbol] source node id
|
|
20
44
|
# @return [Array<Sevgi::Graphics::Element>] included children
|
|
45
|
+
# @raise [Sevgi::ArgumentError] when the file cannot be found, file content is malformed or rootless, or the id is
|
|
46
|
+
# absent
|
|
21
47
|
# @raise [Sevgi::MissingComponentError] when sevgi/derender is unavailable
|
|
22
|
-
def IncludeChildren(file, id) = Derender.
|
|
48
|
+
def IncludeChildren(file, id) = Derender.evaluate_file_children(file, self, id:)
|
|
23
49
|
rescue ::LoadError => e
|
|
24
50
|
raise unless e.path == "sevgi/derender"
|
|
25
51
|
|
|
@@ -26,6 +26,8 @@ module Sevgi
|
|
|
26
26
|
# @param mod [Module] callable drawing module
|
|
27
27
|
# @param args [Array<Object>] callable arguments
|
|
28
28
|
# @param kwargs [Hash] group attributes
|
|
29
|
+
# @yield forwards customization to the callable module
|
|
30
|
+
# @yieldreturn [Object] callable customization result
|
|
29
31
|
# @return [Sevgi::Graphics::Element] group element
|
|
30
32
|
# @raise [Sevgi::ArgumentError] when mod is not a plain module
|
|
31
33
|
def Group(mod, *args, **kwargs, &block)
|
|
@@ -37,6 +39,8 @@ module Sevgi
|
|
|
37
39
|
# @param mod [Module] callable drawing module
|
|
38
40
|
# @param args [Array<Object>] callable arguments
|
|
39
41
|
# @param kwargs [Hash] layer attributes
|
|
42
|
+
# @yield forwards customization to the callable module
|
|
43
|
+
# @yieldreturn [Object] callable customization result
|
|
40
44
|
# @return [Sevgi::Graphics::Element] layer element
|
|
41
45
|
# @raise [Sevgi::ArgumentError] when mod is not a plain module
|
|
42
46
|
def Layer(mod, *args, **kwargs, &block)
|
|
@@ -48,6 +52,8 @@ module Sevgi
|
|
|
48
52
|
# @param mod [Module] callable drawing module
|
|
49
53
|
# @param args [Array<Object>] callable arguments
|
|
50
54
|
# @param kwargs [Hash] layer attributes
|
|
55
|
+
# @yield forwards customization to the callable module
|
|
56
|
+
# @yieldreturn [Object] callable customization result
|
|
51
57
|
# @return [Sevgi::Graphics::Element] layer element
|
|
52
58
|
# @raise [Sevgi::ArgumentError] when mod is not a plain module
|
|
53
59
|
def Layer!(mod, *args, **kwargs, &block)
|
|
@@ -58,6 +64,8 @@ module Sevgi
|
|
|
58
64
|
# @overload layer(**attributes)
|
|
59
65
|
# Builds an Inkscape layer group.
|
|
60
66
|
# @param attributes [Hash] layer attributes
|
|
67
|
+
# @yield evaluates the drawing DSL in the layer
|
|
68
|
+
# @yieldreturn [Object] ignored block result
|
|
61
69
|
# @return [Sevgi::Graphics::Element] layer group
|
|
62
70
|
def layer(**, &block)
|
|
63
71
|
g("inkscape:groupmode": "layer", **, &block)
|
|
@@ -66,6 +74,8 @@ module Sevgi
|
|
|
66
74
|
# @overload layer!(**attributes)
|
|
67
75
|
# Builds an insensitive Inkscape layer group.
|
|
68
76
|
# @param attributes [Hash] layer attributes
|
|
77
|
+
# @yield evaluates the drawing DSL in the layer
|
|
78
|
+
# @yieldreturn [Object] ignored block result
|
|
69
79
|
# @return [Sevgi::Graphics::Element] layer group
|
|
70
80
|
def layer!(**, &block)
|
|
71
81
|
layer("sodipodi:insensitive": "true", **, &block)
|
|
@@ -74,6 +84,9 @@ module Sevgi
|
|
|
74
84
|
# Builds an Inkscape namedview containing page elements.
|
|
75
85
|
# @param pages [Array<Hash>] page attribute hashes
|
|
76
86
|
# @param id [String] namedview id
|
|
87
|
+
# @yield [page] customizes each generated page element
|
|
88
|
+
# @yieldparam page [Sevgi::Graphics::Element] generated page element
|
|
89
|
+
# @yieldreturn [Object] ignored customization result
|
|
77
90
|
# @return [Sevgi::Graphics::Element] namedview element
|
|
78
91
|
def Pages(*pages, id: "namedview", **, &block)
|
|
79
92
|
Element(:"sodipodi:namedview", id:, **) do
|
|
@@ -111,6 +124,8 @@ module Sevgi
|
|
|
111
124
|
# @overload symbol!(**attributes)
|
|
112
125
|
# Builds an Inkscape symbol group hidden from the symbols menu.
|
|
113
126
|
# @param attributes [Hash] symbol attributes
|
|
127
|
+
# @yield evaluates the drawing DSL in the symbol group
|
|
128
|
+
# @yieldreturn [Object] ignored block result
|
|
114
129
|
# @return [Sevgi::Graphics::Element] symbol group
|
|
115
130
|
def symbol!(**, &block)
|
|
116
131
|
if Is?(:defs)
|
|
@@ -7,6 +7,8 @@ module Sevgi
|
|
|
7
7
|
module RDF
|
|
8
8
|
# Adds RDF license metadata inside a metadata element.
|
|
9
9
|
# @param kwargs [Hash] RDF work options
|
|
10
|
+
# @yield evaluates additional RDF work metadata
|
|
11
|
+
# @yieldreturn [Object] ignored block result
|
|
10
12
|
# @return [Sevgi::Graphics::Element] metadata element
|
|
11
13
|
def License(**kwargs, &block) = metadata { RDFWork(**kwargs, &block) }
|
|
12
14
|
|
|
@@ -14,6 +16,8 @@ module Sevgi
|
|
|
14
16
|
#
|
|
15
17
|
# Adds Creative Commons BY-SA license metadata.
|
|
16
18
|
# @param kwargs [Hash] RDF work options
|
|
19
|
+
# @yield evaluates additional RDF work metadata
|
|
20
|
+
# @yieldreturn [Object] ignored block result
|
|
17
21
|
# @return [Sevgi::Graphics::Element] metadata element
|
|
18
22
|
def License_CC_BY_SA(**kwargs, &block)
|
|
19
23
|
License(**kwargs, license: "https://creativecommons.org/licenses/by-sa/4.0/", &block)
|
|
@@ -21,6 +25,8 @@ module Sevgi
|
|
|
21
25
|
|
|
22
26
|
# Adds Creative Commons BY-NC license metadata.
|
|
23
27
|
# @param kwargs [Hash] RDF work options
|
|
28
|
+
# @yield evaluates additional RDF work metadata
|
|
29
|
+
# @yieldreturn [Object] ignored block result
|
|
24
30
|
# @return [Sevgi::Graphics::Element] metadata element
|
|
25
31
|
def License_CC_BY_NC(**kwargs, &block)
|
|
26
32
|
License(**kwargs, license: "https://creativecommons.org/licenses/by-nc/4.0/", &block)
|
|
@@ -28,6 +34,8 @@ module Sevgi
|
|
|
28
34
|
|
|
29
35
|
# Adds Creative Commons BY-NC-SA license metadata.
|
|
30
36
|
# @param kwargs [Hash] RDF work options
|
|
37
|
+
# @yield evaluates additional RDF work metadata
|
|
38
|
+
# @yieldreturn [Object] ignored block result
|
|
31
39
|
# @return [Sevgi::Graphics::Element] metadata element
|
|
32
40
|
def License_CC_BY_NC_SA(**kwargs, &block)
|
|
33
41
|
License(**kwargs, license: "https://creativecommons.org/licenses/by-nc-sa/4.0/", &block)
|
|
@@ -35,6 +43,8 @@ module Sevgi
|
|
|
35
43
|
|
|
36
44
|
# Adds Creative Commons BY-ND license metadata.
|
|
37
45
|
# @param kwargs [Hash] RDF work options
|
|
46
|
+
# @yield evaluates additional RDF work metadata
|
|
47
|
+
# @yieldreturn [Object] ignored block result
|
|
38
48
|
# @return [Sevgi::Graphics::Element] metadata element
|
|
39
49
|
def License_CC_BY_ND(**kwargs, &block)
|
|
40
50
|
License(**kwargs, license: "https://creativecommons.org/licenses/by-nd/4.0/", &block)
|
|
@@ -42,6 +52,8 @@ module Sevgi
|
|
|
42
52
|
|
|
43
53
|
# Adds Creative Commons BY-NC-ND license metadata.
|
|
44
54
|
# @param kwargs [Hash] RDF work options
|
|
55
|
+
# @yield evaluates additional RDF work metadata
|
|
56
|
+
# @yieldreturn [Object] ignored block result
|
|
45
57
|
# @return [Sevgi::Graphics::Element] metadata element
|
|
46
58
|
def License_CC_BY_NC_ND(**kwargs, &block)
|
|
47
59
|
License(**kwargs, license: "https://creativecommons.org/licenses/by-nc-nd/4.0/", &block)
|
|
@@ -49,6 +61,8 @@ module Sevgi
|
|
|
49
61
|
|
|
50
62
|
# Adds Creative Commons Zero metadata.
|
|
51
63
|
# @param kwargs [Hash] RDF work options
|
|
64
|
+
# @yield evaluates additional RDF work metadata
|
|
65
|
+
# @yieldreturn [Object] ignored block result
|
|
52
66
|
# @return [Sevgi::Graphics::Element] metadata element
|
|
53
67
|
def License_CC0(**kwargs, &block)
|
|
54
68
|
License(**kwargs, license: "https://creativecommons.org/publicdomain/zero/1.0/", &block)
|
|
@@ -56,11 +70,15 @@ module Sevgi
|
|
|
56
70
|
|
|
57
71
|
# Adds Free Art License metadata.
|
|
58
72
|
# @param kwargs [Hash] RDF work options
|
|
73
|
+
# @yield evaluates additional RDF work metadata
|
|
74
|
+
# @yieldreturn [Object] ignored block result
|
|
59
75
|
# @return [Sevgi::Graphics::Element] metadata element
|
|
60
76
|
def License_LAL(**kwargs, &block) = License(**kwargs, license: "https://artlibre.org/licence/lal/en/", &block)
|
|
61
77
|
|
|
62
78
|
# Builds an RDF root element.
|
|
63
79
|
# @param _kwargs [Hash] currently unused options
|
|
80
|
+
# @yield evaluates the RDF drawing DSL
|
|
81
|
+
# @yieldreturn [Object] ignored block result
|
|
64
82
|
# @return [Sevgi::Graphics::Element] RDF element
|
|
65
83
|
# @raise [Sevgi::ArgumentError] when no block is given
|
|
66
84
|
def RDF(**_kwargs, &block)
|
|
@@ -86,6 +104,8 @@ module Sevgi
|
|
|
86
104
|
# @option kwargs [String] :date date
|
|
87
105
|
# @option kwargs [String] :language language
|
|
88
106
|
# @option kwargs [String] :license license URL
|
|
107
|
+
# @yield evaluates additional RDF work metadata
|
|
108
|
+
# @yieldreturn [Object] ignored block result
|
|
89
109
|
# @return [Sevgi::Graphics::Element] RDF element
|
|
90
110
|
def RDFWork(**kwargs, &block)
|
|
91
111
|
RDF do
|