sevgi-graphics 0.95.0 → 1.0.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 +221 -2
- data/README.md +12 -9
- data/lib/sevgi/graphics/attribute.rb +166 -45
- data/lib/sevgi/graphics/auxiliary/canvas.rb +100 -43
- data/lib/sevgi/graphics/auxiliary/content.rb +56 -47
- data/lib/sevgi/graphics/auxiliary/margin.rb +19 -12
- data/lib/sevgi/graphics/auxiliary/paper.rb +74 -49
- data/lib/sevgi/graphics/auxiliary/path.rb +44 -0
- data/lib/sevgi/graphics/auxiliary/scalar.rb +36 -7
- data/lib/sevgi/graphics/auxiliary.rb +1 -0
- data/lib/sevgi/graphics/document/base.rb +6 -2
- data/lib/sevgi/graphics/document/default.rb +1 -1
- data/lib/sevgi/graphics/document.rb +239 -117
- data/lib/sevgi/graphics/element.rb +132 -34
- data/lib/sevgi/graphics/mixtures/call.rb +234 -88
- data/lib/sevgi/graphics/mixtures/core.rb +67 -27
- data/lib/sevgi/graphics/mixtures/duplicate.rb +47 -25
- data/lib/sevgi/graphics/mixtures/export.rb +54 -12
- data/lib/sevgi/graphics/mixtures/hatch.rb +49 -7
- data/lib/sevgi/graphics/mixtures/identify.rb +30 -17
- data/lib/sevgi/graphics/mixtures/include.rb +26 -8
- data/lib/sevgi/graphics/mixtures/inkscape.rb +214 -47
- data/lib/sevgi/graphics/mixtures/rdf.rb +59 -7
- data/lib/sevgi/graphics/mixtures/render.rb +60 -120
- data/lib/sevgi/graphics/mixtures/save.rb +79 -35
- data/lib/sevgi/graphics/mixtures/symbols.rb +81 -12
- data/lib/sevgi/graphics/mixtures/tile.rb +85 -67
- data/lib/sevgi/graphics/mixtures/transform.rb +89 -30
- data/lib/sevgi/graphics/mixtures/underscore.rb +16 -7
- data/lib/sevgi/graphics/mixtures/validate.rb +2 -2
- data/lib/sevgi/graphics/mixtures/wrappers.rb +111 -23
- data/lib/sevgi/graphics/mixtures.rb +15 -13
- data/lib/sevgi/graphics/version.rb +1 -1
- data/lib/sevgi/graphics/xml.rb +4 -9
- data/lib/sevgi/graphics.rb +69 -18
- metadata +7 -6
|
@@ -5,21 +5,31 @@ module Sevgi
|
|
|
5
5
|
module Mixtures
|
|
6
6
|
# DSL helpers for including derendered SVG/XML fragments.
|
|
7
7
|
#
|
|
8
|
-
# @!method Include(file, id)
|
|
8
|
+
# @!method Include(file, id, omit: nil)
|
|
9
9
|
# Includes a derendered node matching an id.
|
|
10
10
|
# SVG/XML content is treated as data and is not evaluated as Ruby source.
|
|
11
|
+
# @example Import a fragment without editor ids and inline styles
|
|
12
|
+
# Sevgi::Graphics.SVG do
|
|
13
|
+
# Include "badge.svg", "mark", omit: %i[id style]
|
|
14
|
+
# end
|
|
11
15
|
# @param file [String] source SVG/XML file
|
|
12
16
|
# @param id [String, Symbol] source node id
|
|
17
|
+
# @param omit [String, Symbol, Array<String, Symbol>, nil] exact attribute name or names omitted from the selected
|
|
18
|
+
# subtree after id selection
|
|
13
19
|
# @return [Sevgi::Graphics::Element, nil] included element, or nil when it produces no graphics output
|
|
14
20
|
# @raise [Sevgi::ArgumentError] when the file is absent or XML content is malformed, rootless, or lacks the id
|
|
21
|
+
# @raise [SystemCallError] when the file cannot be read
|
|
15
22
|
# @raise [Sevgi::MissingComponentError] when sevgi/derender is unavailable
|
|
16
|
-
# @!method IncludeChildren(file, id)
|
|
23
|
+
# @!method IncludeChildren(file, id, omit: nil)
|
|
17
24
|
# Includes the children of a derendered node matching an id.
|
|
18
25
|
# SVG/XML content is treated as data and is not evaluated as Ruby source.
|
|
19
26
|
# @param file [String] source SVG/XML file
|
|
20
27
|
# @param id [String, Symbol] source node id
|
|
21
|
-
# @
|
|
28
|
+
# @param omit [String, Symbol, Array<String, Symbol>, nil] exact attribute name or names omitted from the selected
|
|
29
|
+
# subtree after id selection
|
|
30
|
+
# @return [Array<Sevgi::Graphics::Element>] immutable included-child snapshot
|
|
22
31
|
# @raise [Sevgi::ArgumentError] when the file is absent or XML content is malformed, rootless, or lacks the id
|
|
32
|
+
# @raise [SystemCallError] when the file cannot be read
|
|
23
33
|
# @raise [Sevgi::MissingComponentError] when sevgi/derender is unavailable
|
|
24
34
|
module Include
|
|
25
35
|
require "sevgi/derender"
|
|
@@ -29,38 +39,46 @@ module Sevgi
|
|
|
29
39
|
# SVG/XML file content is treated as data and is not evaluated as Ruby source.
|
|
30
40
|
# @param file [String] source SVG/XML file
|
|
31
41
|
# @param id [String, Symbol] source node id
|
|
42
|
+
# @param omit [String, Symbol, Array<String, Symbol>, nil] exact attribute name or names omitted from the selected
|
|
43
|
+
# subtree after id selection
|
|
32
44
|
# @return [Sevgi::Graphics::Element, nil] included element, or nil when the selected node produces no graphics
|
|
33
45
|
# output
|
|
34
46
|
# @raise [Sevgi::ArgumentError] when the file cannot be found, file content is malformed or rootless, or the id is
|
|
35
47
|
# absent
|
|
48
|
+
# @raise [SystemCallError] when the file cannot be read
|
|
36
49
|
# @raise [Sevgi::MissingComponentError] when sevgi/derender is unavailable
|
|
37
|
-
def Include(file, id) = Derender.evaluate_file(file, self, id:)
|
|
50
|
+
def Include(file, id, omit: nil) = Derender.evaluate_file(file, self, id:, omit:)
|
|
38
51
|
|
|
39
52
|
# Includes the children of a derendered node matching an id.
|
|
40
53
|
#
|
|
41
54
|
# SVG/XML file content is treated as data and is not evaluated as Ruby source.
|
|
42
55
|
# @param file [String] source SVG/XML file
|
|
43
56
|
# @param id [String, Symbol] source node id
|
|
44
|
-
# @
|
|
57
|
+
# @param omit [String, Symbol, Array<String, Symbol>, nil] exact attribute name or names omitted from the selected
|
|
58
|
+
# subtree after id selection
|
|
59
|
+
# @return [Array<Sevgi::Graphics::Element>] immutable included-child snapshot
|
|
45
60
|
# @raise [Sevgi::ArgumentError] when the file cannot be found, file content is malformed or rootless, or the id is
|
|
46
61
|
# absent
|
|
62
|
+
# @raise [SystemCallError] when the file cannot be read
|
|
47
63
|
# @raise [Sevgi::MissingComponentError] when sevgi/derender is unavailable
|
|
48
|
-
def IncludeChildren(file, id) = Derender.
|
|
64
|
+
def IncludeChildren(file, id, omit: nil) = Derender.evaluate_children_file(file, self, id:, omit:)
|
|
49
65
|
rescue ::LoadError => e
|
|
50
66
|
raise unless e.path == "sevgi/derender"
|
|
51
67
|
|
|
52
|
-
# @overload IncludeChildren(file, id)
|
|
68
|
+
# @overload IncludeChildren(file, id, omit: nil)
|
|
53
69
|
# Raises because sevgi/derender is unavailable.
|
|
54
70
|
# @param file [String] source SVG/XML file
|
|
55
71
|
# @param id [String, Symbol] source node id
|
|
72
|
+
# @param omit [String, Symbol, Array<String, Symbol>, nil] ignored because the component is unavailable
|
|
56
73
|
# @return [void]
|
|
57
74
|
# @raise [Sevgi::MissingComponentError] always
|
|
58
75
|
def IncludeChildren(...) = MissingComponentError.("sevgi/derender")
|
|
59
76
|
|
|
60
|
-
# @overload Include(file, id)
|
|
77
|
+
# @overload Include(file, id, omit: nil)
|
|
61
78
|
# Raises because sevgi/derender is unavailable.
|
|
62
79
|
# @param file [String] source SVG/XML file
|
|
63
80
|
# @param id [String, Symbol] source node id
|
|
81
|
+
# @param omit [String, Symbol, Array<String, Symbol>, nil] ignored because the component is unavailable
|
|
64
82
|
# @return [void]
|
|
65
83
|
# @raise [Sevgi::MissingComponentError] always
|
|
66
84
|
def Include(...) = MissingComponentError.("sevgi/derender")
|
|
@@ -5,6 +5,22 @@ module Sevgi
|
|
|
5
5
|
module Mixtures
|
|
6
6
|
# Inkscape-specific SVG DSL helpers.
|
|
7
7
|
module Inkscape
|
|
8
|
+
# Normalizes callable wrapper attributes outside the DSL method surface.
|
|
9
|
+
# @api private
|
|
10
|
+
module Wrapper
|
|
11
|
+
# Returns owned wrapper attributes with a stable default id when the module is named.
|
|
12
|
+
# @param mod [Module] callable drawing module
|
|
13
|
+
# @param attributes [Hash] wrapper attributes
|
|
14
|
+
# @return [Hash{Symbol => Object}] normalized attributes
|
|
15
|
+
# @raise [Sevgi::ArgumentError] when attributes contains colliding names
|
|
16
|
+
def self.attributes(mod, attributes)
|
|
17
|
+
defaults = mod.name ? {id: F.demodulize(mod.name).to_sym} : {}
|
|
18
|
+
Attribute.defaults(attributes, **defaults)
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
private_constant :Wrapper
|
|
23
|
+
|
|
8
24
|
# Adds Inkscape template metadata.
|
|
9
25
|
# @param name [String] template name
|
|
10
26
|
# @param desc [String, nil] short description
|
|
@@ -23,42 +39,60 @@ module Sevgi
|
|
|
23
39
|
end
|
|
24
40
|
|
|
25
41
|
# Renders a callable module inside a group.
|
|
26
|
-
#
|
|
42
|
+
# Named modules default the group id to their final constant name. Anonymous modules omit the id unless supplied.
|
|
43
|
+
# @example Supply a stable id for an anonymous callable
|
|
44
|
+
# drawing = Module.new do
|
|
45
|
+
# extend Sevgi::Graphics::Module
|
|
46
|
+
# def call = circle r: 5
|
|
47
|
+
# end
|
|
48
|
+
# Sevgi::Graphics.SVG(:inkscape) { Group drawing, attributes: {id: "drawing"} }
|
|
49
|
+
# @param mod [Module] module extended with {Sevgi::Graphics::Module}
|
|
27
50
|
# @param args [Array<Object>] callable arguments
|
|
28
|
-
# @param
|
|
51
|
+
# @param attributes [Hash] group attributes. String and Symbol names are normalized and must not collide
|
|
52
|
+
# @param kwargs [Hash] callable keyword arguments
|
|
29
53
|
# @yield forwards customization to the callable module
|
|
30
54
|
# @yieldreturn [Object] callable customization result
|
|
31
55
|
# @return [Sevgi::Graphics::Element] group element
|
|
32
|
-
# @raise [Sevgi::ArgumentError] when mod is not a
|
|
33
|
-
def Group(mod, *args, **kwargs, &block)
|
|
34
|
-
|
|
35
|
-
|
|
56
|
+
# @raise [Sevgi::ArgumentError] when mod is not a callable drawing module or attributes is not a Hash
|
|
57
|
+
def Group(mod, *args, attributes: {}, **kwargs, &block)
|
|
58
|
+
Graphics::Module.__send__(:callables, mod)
|
|
59
|
+
ArgumentError.("Group attributes must be a Hash") unless attributes.is_a?(::Hash)
|
|
60
|
+
attributes = Wrapper.attributes(mod, attributes)
|
|
61
|
+
g(**attributes) { Call(mod, *args, **kwargs, &block) }
|
|
36
62
|
end
|
|
37
63
|
|
|
38
64
|
# Renders a callable module inside an Inkscape layer.
|
|
39
|
-
#
|
|
65
|
+
# Named modules default the layer id to their final constant name. Anonymous modules omit the id unless supplied.
|
|
66
|
+
# @param mod [Module] module extended with {Sevgi::Graphics::Module}
|
|
40
67
|
# @param args [Array<Object>] callable arguments
|
|
41
|
-
# @param
|
|
68
|
+
# @param attributes [Hash] layer attributes. String and Symbol names are normalized and must not collide
|
|
69
|
+
# @param kwargs [Hash] callable keyword arguments
|
|
42
70
|
# @yield forwards customization to the callable module
|
|
43
71
|
# @yieldreturn [Object] callable customization result
|
|
44
72
|
# @return [Sevgi::Graphics::Element] layer element
|
|
45
|
-
# @raise [Sevgi::ArgumentError] when mod is not a
|
|
46
|
-
def Layer(mod, *args, **kwargs, &block)
|
|
47
|
-
|
|
48
|
-
|
|
73
|
+
# @raise [Sevgi::ArgumentError] when mod is not a callable drawing module or attributes is not a Hash
|
|
74
|
+
def Layer(mod, *args, attributes: {}, **kwargs, &block)
|
|
75
|
+
Graphics::Module.__send__(:callables, mod)
|
|
76
|
+
ArgumentError.("Layer attributes must be a Hash") unless attributes.is_a?(::Hash)
|
|
77
|
+
attributes = Wrapper.attributes(mod, attributes)
|
|
78
|
+
layer(**attributes) { Call(mod, *args, **kwargs, &block) }
|
|
49
79
|
end
|
|
50
80
|
|
|
51
81
|
# Renders a callable module inside an insensitive Inkscape layer.
|
|
52
|
-
#
|
|
82
|
+
# Named modules default the layer id to their final constant name. Anonymous modules omit the id unless supplied.
|
|
83
|
+
# @param mod [Module] module extended with {Sevgi::Graphics::Module}
|
|
53
84
|
# @param args [Array<Object>] callable arguments
|
|
54
|
-
# @param
|
|
85
|
+
# @param attributes [Hash] layer attributes. String and Symbol names are normalized and must not collide
|
|
86
|
+
# @param kwargs [Hash] callable keyword arguments
|
|
55
87
|
# @yield forwards customization to the callable module
|
|
56
88
|
# @yieldreturn [Object] callable customization result
|
|
57
89
|
# @return [Sevgi::Graphics::Element] layer element
|
|
58
|
-
# @raise [Sevgi::ArgumentError] when mod is not a
|
|
59
|
-
def Layer!(mod, *args, **kwargs, &block)
|
|
60
|
-
|
|
61
|
-
|
|
90
|
+
# @raise [Sevgi::ArgumentError] when mod is not a callable drawing module or attributes is not a Hash
|
|
91
|
+
def Layer!(mod, *args, attributes: {}, **kwargs, &block)
|
|
92
|
+
Graphics::Module.__send__(:callables, mod)
|
|
93
|
+
ArgumentError.("Layer attributes must be a Hash") unless attributes.is_a?(::Hash)
|
|
94
|
+
attributes = Wrapper.attributes(mod, attributes)
|
|
95
|
+
layer!(**attributes) { Call(mod, *args, **kwargs, &block) }
|
|
62
96
|
end
|
|
63
97
|
|
|
64
98
|
# @overload layer(**attributes)
|
|
@@ -81,46 +115,179 @@ module Sevgi
|
|
|
81
115
|
layer("sodipodi:insensitive": "true", **, &block)
|
|
82
116
|
end
|
|
83
117
|
|
|
118
|
+
# Validates and constructs Inkscape page collections outside the DSL method surface.
|
|
119
|
+
# @api private
|
|
120
|
+
module Pagination
|
|
121
|
+
class << self
|
|
122
|
+
# Builds a namedview from normalized page attributes.
|
|
123
|
+
# @param context [Sevgi::Graphics::Element] DSL element receiving the namedview
|
|
124
|
+
# @param pages [Array<Hash>] page attributes
|
|
125
|
+
# @param namedview [Hash] namedview attributes
|
|
126
|
+
# @param page [Hash] shared page attributes
|
|
127
|
+
# @return [Sevgi::Graphics::Element] namedview element
|
|
128
|
+
# @raise [Sevgi::ArgumentError] when an attribute channel or page is invalid
|
|
129
|
+
def call(context, pages, namedview:, page:, &block)
|
|
130
|
+
namedview, page = channels(namedview, page)
|
|
131
|
+
pages = pages.each_with_index.map { |attributes, index| normalize(attributes, page, index) }
|
|
132
|
+
|
|
133
|
+
context.Element(:"sodipodi:namedview", **namedview) do
|
|
134
|
+
pages.each do |attributes|
|
|
135
|
+
element = Element(:"inkscape:page", **attributes)
|
|
136
|
+
block&.call(element)
|
|
137
|
+
end
|
|
138
|
+
end
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
# Generates validated attributes for a rectangular page grid.
|
|
142
|
+
# @return [Array<Hash{Symbol => Object}>] page attribute hashes
|
|
143
|
+
# @raise [Sevgi::ArgumentError] when a count, dimension, or gap is invalid
|
|
144
|
+
def tabular(rows:, cols:, width:, height:, gap:)
|
|
145
|
+
width, height, gap = normalize_grid(rows:, cols:, width:, height:, gap:)
|
|
146
|
+
|
|
147
|
+
rows.times.flat_map do |row|
|
|
148
|
+
cols.times.map do |col|
|
|
149
|
+
x = col * (width + gap)
|
|
150
|
+
y = row * (height + gap)
|
|
151
|
+
label = "#{row + 1}x#{col + 1}"
|
|
152
|
+
{id: "pageview-#{label}", x:, y:, width:, height:}
|
|
153
|
+
end
|
|
154
|
+
end
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
private
|
|
158
|
+
|
|
159
|
+
# Validates and normalizes the namedview and page-default channels.
|
|
160
|
+
# @return [Array<Hash>] normalized namedview attributes and page defaults
|
|
161
|
+
# @raise [Sevgi::ArgumentError] when either channel is not a Hash or has invalid attributes
|
|
162
|
+
# @api private
|
|
163
|
+
def channels(namedview, page)
|
|
164
|
+
ArgumentError.("Namedview attributes must be a Hash") unless namedview.is_a?(::Hash)
|
|
165
|
+
ArgumentError.("Page attributes must be a Hash") unless page.is_a?(::Hash)
|
|
166
|
+
[Attribute.defaults(namedview, id: "namedview"), page_attributes(page)]
|
|
167
|
+
end
|
|
168
|
+
|
|
169
|
+
# Normalizes one explicit or generated page id.
|
|
170
|
+
# @return [Hash{String, Symbol => Object}] attributes beginning with a canonical id
|
|
171
|
+
# @raise [Sevgi::ArgumentError] when String and Symbol ids collide
|
|
172
|
+
# @api private
|
|
173
|
+
def identify(attributes, index)
|
|
174
|
+
id = attributes.key?(:id) ? attributes.delete(:id) : "page-#{index + 1}"
|
|
175
|
+
{id:}.merge(attributes)
|
|
176
|
+
end
|
|
177
|
+
|
|
178
|
+
# Normalizes and validates one page.
|
|
179
|
+
# @return [Hash{String, Symbol => Object}] independent page attributes
|
|
180
|
+
# @raise [Sevgi::ArgumentError] when attributes or dimensions are invalid
|
|
181
|
+
# @api private
|
|
182
|
+
def normalize(attributes, defaults, index)
|
|
183
|
+
ArgumentError.("Page #{index + 1} must be a Hash") unless attributes.is_a?(::Hash)
|
|
184
|
+
attributes = defaults.merge(page_attributes(attributes))
|
|
185
|
+
%i[x y].each { number(attributes, it, index) }
|
|
186
|
+
%i[width height].each do |field|
|
|
187
|
+
number(attributes, field, index, positive: true)
|
|
188
|
+
end
|
|
189
|
+
|
|
190
|
+
identify(attributes, index)
|
|
191
|
+
end
|
|
192
|
+
|
|
193
|
+
# Coerces numeric page fields before attribute snapshots stringify mutable numeric subclasses.
|
|
194
|
+
def page_attributes(attributes)
|
|
195
|
+
Attribute.normalize(
|
|
196
|
+
attributes.to_h do |key, value|
|
|
197
|
+
if %w[x y width height].include?(key.to_s) && value.is_a?(::Numeric)
|
|
198
|
+
value = Scalar.number(value, context: "page", field: key)
|
|
199
|
+
end
|
|
200
|
+
|
|
201
|
+
[key, value]
|
|
202
|
+
end
|
|
203
|
+
)
|
|
204
|
+
end
|
|
205
|
+
|
|
206
|
+
# Validates and normalizes page-grid arguments.
|
|
207
|
+
# @return [Array<(Integer, Float)>] normalized width, height, and gap
|
|
208
|
+
# @raise [Sevgi::ArgumentError] when a count, dimension, or gap is invalid
|
|
209
|
+
# @api private
|
|
210
|
+
def normalize_grid(rows:, cols:, width:, height:, gap:)
|
|
211
|
+
{rows:, cols:}.each do |field, count|
|
|
212
|
+
unless count.is_a?(::Integer) && count.positive?
|
|
213
|
+
ArgumentError.("Page #{field} must be a positive Integer")
|
|
214
|
+
end
|
|
215
|
+
end
|
|
216
|
+
|
|
217
|
+
[
|
|
218
|
+
Scalar.number(width, context: "page", field: :width, positive: true),
|
|
219
|
+
Scalar.number(height, context: "page", field: :height, positive: true),
|
|
220
|
+
Scalar.number(gap, context: "page", field: :gap, nonnegative: true)
|
|
221
|
+
]
|
|
222
|
+
end
|
|
223
|
+
|
|
224
|
+
# Replaces one page field with its normalized SVG number.
|
|
225
|
+
# @return [Integer, Float] normalized number
|
|
226
|
+
# @api private
|
|
227
|
+
def number(attributes, field, index, **range)
|
|
228
|
+
key = attributes.key?(field) ? field : field.to_s
|
|
229
|
+
attributes[key] = Scalar.number(value(attributes, field, index), context: "page", field:, **range)
|
|
230
|
+
end
|
|
231
|
+
|
|
232
|
+
# Returns one required page field.
|
|
233
|
+
# @return [Object] page field value
|
|
234
|
+
# @raise [Sevgi::ArgumentError] when the field is absent
|
|
235
|
+
# @api private
|
|
236
|
+
def value(attributes, field, index)
|
|
237
|
+
attributes.fetch(field) do
|
|
238
|
+
attributes.fetch(field.to_s) { ArgumentError.("Page #{index + 1} requires #{field}") }
|
|
239
|
+
end
|
|
240
|
+
end
|
|
241
|
+
end
|
|
242
|
+
end
|
|
243
|
+
|
|
244
|
+
private_constant :Pagination
|
|
245
|
+
|
|
84
246
|
# Builds an Inkscape namedview containing page elements.
|
|
85
|
-
# @
|
|
86
|
-
#
|
|
247
|
+
# @example Build explicit pages with separate namedview and page attributes
|
|
248
|
+
# Sevgi::Graphics.SVG(:inkscape) do
|
|
249
|
+
# Pages(
|
|
250
|
+
# {x: 0, y: 0, width: 100, height: 50, label: "front"},
|
|
251
|
+
# namedview: {id: "views"},
|
|
252
|
+
# page: {class: "print"}
|
|
253
|
+
# )
|
|
254
|
+
# end
|
|
255
|
+
# @param pages [Array<Hash>] page attribute hashes. Numeric page fields are normalized to SVG numbers
|
|
256
|
+
# @param namedview [Hash] namedview attributes. String and Symbol names are normalized and must not collide
|
|
257
|
+
# @param page [Hash] page defaults. Normalized page attributes override these defaults by name
|
|
87
258
|
# @yield [page] customizes each generated page element
|
|
88
259
|
# @yieldparam page [Sevgi::Graphics::Element] generated page element
|
|
89
260
|
# @yieldreturn [Object] ignored customization result
|
|
90
261
|
# @return [Sevgi::Graphics::Element] namedview element
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
pages.each_with_index do |page, i|
|
|
94
|
-
id = page[:id] || "page-#{i + 1}"
|
|
95
|
-
x, y, width, height = page.values_at(*%i[x y width height])
|
|
96
|
-
element = Element(:"inkscape:page", id:, x:, y:, width:, height:)
|
|
97
|
-
yield(element) if block
|
|
98
|
-
end
|
|
99
|
-
end
|
|
100
|
-
end
|
|
262
|
+
# @raise [Sevgi::ArgumentError] when an attribute channel, page, coordinate, or dimension is invalid
|
|
263
|
+
def Pages(*pages, namedview: {}, page: {}, &block) = Pagination.call(self, pages, namedview:, page:, &block)
|
|
101
264
|
|
|
102
265
|
# Builds a tabular set of Inkscape pages.
|
|
266
|
+
# @example Build a page grid using the same attribute channels as {#Pages}
|
|
267
|
+
# Sevgi::Graphics.SVG(:inkscape) do
|
|
268
|
+
# PagesTabular(
|
|
269
|
+
# rows: 2, cols: 3, width: 100, height: 50, gap: 5,
|
|
270
|
+
# namedview: {id: "views"}, page: {class: "print"}
|
|
271
|
+
# )
|
|
272
|
+
# end
|
|
103
273
|
# @param rows [Integer] number of rows
|
|
104
274
|
# @param cols [Integer] number of columns
|
|
105
|
-
# @param width [Numeric] page width
|
|
106
|
-
# @param height [Numeric] page height
|
|
107
|
-
# @param gap [Numeric] gap
|
|
108
|
-
# @param
|
|
109
|
-
# @
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
end
|
|
120
|
-
end
|
|
275
|
+
# @param width [Numeric] finite positive page width, normalized to an SVG number
|
|
276
|
+
# @param height [Numeric] finite positive page height, normalized to an SVG number
|
|
277
|
+
# @param gap [Numeric] finite non-negative gap, normalized to an SVG number
|
|
278
|
+
# @param namedview [Hash] namedview attributes. String and Symbol names are normalized and must not collide
|
|
279
|
+
# @param page [Hash] page defaults. Normalized page attributes override these defaults by name
|
|
280
|
+
# @yield [page] customizes each generated page element
|
|
281
|
+
# @yieldparam page [Sevgi::Graphics::Element] generated page element
|
|
282
|
+
# @yieldreturn [Object] ignored customization result
|
|
283
|
+
# @return [Sevgi::Graphics::Element] namedview element
|
|
284
|
+
# @raise [Sevgi::ArgumentError] when counts, dimensions, gap, or an attribute channel is invalid
|
|
285
|
+
# @see #Pages
|
|
286
|
+
def PagesTabular(rows:, cols:, width:, height:, gap:, namedview: {}, page: {}, &block)
|
|
287
|
+
pages = Pagination.tabular(rows:, cols:, width:, height:, gap:)
|
|
288
|
+
Pages(*pages, namedview:, page:, &block)
|
|
121
289
|
end
|
|
122
290
|
|
|
123
|
-
# Internal symbol which does not show up Symbols Menu
|
|
124
291
|
# @overload symbol!(**attributes)
|
|
125
292
|
# Builds an Inkscape symbol group hidden from the symbols menu.
|
|
126
293
|
# @param attributes [Hash] symbol attributes
|
|
@@ -4,21 +4,47 @@ module Sevgi
|
|
|
4
4
|
module Graphics
|
|
5
5
|
module Mixtures
|
|
6
6
|
# DSL helpers for RDF and license metadata.
|
|
7
|
+
#
|
|
8
|
+
# @example Add Creative Commons metadata
|
|
9
|
+
# Sevgi::Graphics.SVG :inkscape do
|
|
10
|
+
# License_CC0 title: "Example", creator: "A. Creator"
|
|
11
|
+
# end
|
|
7
12
|
module RDF
|
|
13
|
+
WORK_OPTIONS = %i[title description creator publisher date language license].freeze
|
|
14
|
+
private_constant :WORK_OPTIONS
|
|
15
|
+
|
|
8
16
|
# Adds RDF license metadata inside a metadata element.
|
|
9
17
|
# @param kwargs [Hash] RDF work options
|
|
10
18
|
# @yield evaluates additional RDF work metadata
|
|
11
19
|
# @yieldreturn [Object] ignored block result
|
|
12
20
|
# @return [Sevgi::Graphics::Element] metadata element
|
|
13
|
-
|
|
21
|
+
# @raise [Sevgi::ArgumentError] when an option is unknown
|
|
22
|
+
# @see #RDFWork
|
|
23
|
+
def License(**kwargs, &block)
|
|
24
|
+
unknown = kwargs.keys - WORK_OPTIONS
|
|
25
|
+
ArgumentError.("Unknown license options: #{unknown.join(", ")}") unless unknown.empty?
|
|
26
|
+
|
|
27
|
+
metadata { RDFWork(**kwargs, &block) }
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
# Adds Creative Commons BY license metadata.
|
|
31
|
+
# @param kwargs [Hash] RDF work options
|
|
32
|
+
# @yield evaluates additional RDF work metadata
|
|
33
|
+
# @yieldreturn [Object] ignored block result
|
|
34
|
+
# @return [Sevgi::Graphics::Element] metadata element
|
|
35
|
+
# @raise [Sevgi::ArgumentError] when an option is unknown
|
|
36
|
+
# @see #RDFWork
|
|
37
|
+
def License_CC_BY(**kwargs, &block)
|
|
38
|
+
License(**kwargs, license: "https://creativecommons.org/licenses/by/4.0/", &block)
|
|
39
|
+
end
|
|
14
40
|
|
|
15
|
-
# Use SPDX license codes in underscored form: https://spdx.org/licenses/
|
|
16
|
-
#
|
|
17
41
|
# Adds Creative Commons BY-SA license metadata.
|
|
18
42
|
# @param kwargs [Hash] RDF work options
|
|
19
43
|
# @yield evaluates additional RDF work metadata
|
|
20
44
|
# @yieldreturn [Object] ignored block result
|
|
21
45
|
# @return [Sevgi::Graphics::Element] metadata element
|
|
46
|
+
# @raise [Sevgi::ArgumentError] when an option is unknown
|
|
47
|
+
# @see #RDFWork
|
|
22
48
|
def License_CC_BY_SA(**kwargs, &block)
|
|
23
49
|
License(**kwargs, license: "https://creativecommons.org/licenses/by-sa/4.0/", &block)
|
|
24
50
|
end
|
|
@@ -28,6 +54,8 @@ module Sevgi
|
|
|
28
54
|
# @yield evaluates additional RDF work metadata
|
|
29
55
|
# @yieldreturn [Object] ignored block result
|
|
30
56
|
# @return [Sevgi::Graphics::Element] metadata element
|
|
57
|
+
# @raise [Sevgi::ArgumentError] when an option is unknown
|
|
58
|
+
# @see #RDFWork
|
|
31
59
|
def License_CC_BY_NC(**kwargs, &block)
|
|
32
60
|
License(**kwargs, license: "https://creativecommons.org/licenses/by-nc/4.0/", &block)
|
|
33
61
|
end
|
|
@@ -37,6 +65,8 @@ module Sevgi
|
|
|
37
65
|
# @yield evaluates additional RDF work metadata
|
|
38
66
|
# @yieldreturn [Object] ignored block result
|
|
39
67
|
# @return [Sevgi::Graphics::Element] metadata element
|
|
68
|
+
# @raise [Sevgi::ArgumentError] when an option is unknown
|
|
69
|
+
# @see #RDFWork
|
|
40
70
|
def License_CC_BY_NC_SA(**kwargs, &block)
|
|
41
71
|
License(**kwargs, license: "https://creativecommons.org/licenses/by-nc-sa/4.0/", &block)
|
|
42
72
|
end
|
|
@@ -46,6 +76,8 @@ module Sevgi
|
|
|
46
76
|
# @yield evaluates additional RDF work metadata
|
|
47
77
|
# @yieldreturn [Object] ignored block result
|
|
48
78
|
# @return [Sevgi::Graphics::Element] metadata element
|
|
79
|
+
# @raise [Sevgi::ArgumentError] when an option is unknown
|
|
80
|
+
# @see #RDFWork
|
|
49
81
|
def License_CC_BY_ND(**kwargs, &block)
|
|
50
82
|
License(**kwargs, license: "https://creativecommons.org/licenses/by-nd/4.0/", &block)
|
|
51
83
|
end
|
|
@@ -55,6 +87,8 @@ module Sevgi
|
|
|
55
87
|
# @yield evaluates additional RDF work metadata
|
|
56
88
|
# @yieldreturn [Object] ignored block result
|
|
57
89
|
# @return [Sevgi::Graphics::Element] metadata element
|
|
90
|
+
# @raise [Sevgi::ArgumentError] when an option is unknown
|
|
91
|
+
# @see #RDFWork
|
|
58
92
|
def License_CC_BY_NC_ND(**kwargs, &block)
|
|
59
93
|
License(**kwargs, license: "https://creativecommons.org/licenses/by-nc-nd/4.0/", &block)
|
|
60
94
|
end
|
|
@@ -64,6 +98,8 @@ module Sevgi
|
|
|
64
98
|
# @yield evaluates additional RDF work metadata
|
|
65
99
|
# @yieldreturn [Object] ignored block result
|
|
66
100
|
# @return [Sevgi::Graphics::Element] metadata element
|
|
101
|
+
# @raise [Sevgi::ArgumentError] when an option is unknown
|
|
102
|
+
# @see #RDFWork
|
|
67
103
|
def License_CC0(**kwargs, &block)
|
|
68
104
|
License(**kwargs, license: "https://creativecommons.org/publicdomain/zero/1.0/", &block)
|
|
69
105
|
end
|
|
@@ -73,15 +109,19 @@ module Sevgi
|
|
|
73
109
|
# @yield evaluates additional RDF work metadata
|
|
74
110
|
# @yieldreturn [Object] ignored block result
|
|
75
111
|
# @return [Sevgi::Graphics::Element] metadata element
|
|
112
|
+
# @raise [Sevgi::ArgumentError] when an option is unknown
|
|
113
|
+
# @see #RDFWork
|
|
76
114
|
def License_LAL(**kwargs, &block) = License(**kwargs, license: "https://artlibre.org/licence/lal/en/", &block)
|
|
77
115
|
|
|
78
116
|
# Builds an RDF root element.
|
|
79
|
-
# @param
|
|
117
|
+
# @param kwargs [Hash] options. RDF currently accepts none
|
|
80
118
|
# @yield evaluates the RDF drawing DSL
|
|
81
119
|
# @yieldreturn [Object] ignored block result
|
|
82
120
|
# @return [Sevgi::Graphics::Element] RDF element
|
|
83
121
|
# @raise [Sevgi::ArgumentError] when no block is given
|
|
84
|
-
|
|
122
|
+
# @raise [Sevgi::ArgumentError] when an option is given
|
|
123
|
+
def RDF(**kwargs, &block)
|
|
124
|
+
ArgumentError.("Unknown RDF options: #{kwargs.keys.join(", ")}") unless kwargs.empty?
|
|
85
125
|
ArgumentError.("Block required") unless block
|
|
86
126
|
|
|
87
127
|
Element(
|
|
@@ -94,7 +134,6 @@ module Sevgi
|
|
|
94
134
|
end
|
|
95
135
|
end
|
|
96
136
|
|
|
97
|
-
# rubocop:disable Metrics/MethodLength
|
|
98
137
|
# Builds a Creative Commons RDF Work element.
|
|
99
138
|
# @param kwargs [Hash] RDF work options
|
|
100
139
|
# @option kwargs [String] :title work title
|
|
@@ -104,10 +143,24 @@ module Sevgi
|
|
|
104
143
|
# @option kwargs [String] :date date
|
|
105
144
|
# @option kwargs [String] :language language
|
|
106
145
|
# @option kwargs [String] :license license URL
|
|
146
|
+
#
|
|
147
|
+
# | Option | RDF element |
|
|
148
|
+
# | --- | --- |
|
|
149
|
+
# | `title` | `dc:title` |
|
|
150
|
+
# | `description` | `dc:description` |
|
|
151
|
+
# | `creator` | `dc:creator` |
|
|
152
|
+
# | `publisher` | `dc:publisher` |
|
|
153
|
+
# | `date` | `dc:date` |
|
|
154
|
+
# | `language` | `dc:language` |
|
|
155
|
+
# | `license` | `cc:license` resource |
|
|
107
156
|
# @yield evaluates additional RDF work metadata
|
|
108
157
|
# @yieldreturn [Object] ignored block result
|
|
109
158
|
# @return [Sevgi::Graphics::Element] RDF element
|
|
159
|
+
# @raise [Sevgi::ArgumentError] when an option is unknown
|
|
110
160
|
def RDFWork(**kwargs, &block)
|
|
161
|
+
unknown = kwargs.keys - WORK_OPTIONS
|
|
162
|
+
ArgumentError.("Unknown RDF work options: #{unknown.join(", ")}") unless unknown.empty?
|
|
163
|
+
|
|
111
164
|
RDF do
|
|
112
165
|
Element(:"cc:Work", "rdf:about": "") do
|
|
113
166
|
Element(:"dc:format", "image/svg+xml")
|
|
@@ -124,7 +177,6 @@ module Sevgi
|
|
|
124
177
|
end
|
|
125
178
|
end
|
|
126
179
|
end
|
|
127
|
-
# rubocop:enable Metrics/MethodLength
|
|
128
180
|
end
|
|
129
181
|
end
|
|
130
182
|
end
|