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.
Files changed (43) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +5 -0
  3. data/lib/sevgi/graphics/attribute.rb +58 -6
  4. data/lib/sevgi/graphics/auxiliary/canvas.rb +125 -0
  5. data/lib/sevgi/graphics/auxiliary/content.rb +145 -0
  6. data/lib/sevgi/graphics/auxiliary/margin.rb +93 -0
  7. data/lib/sevgi/graphics/auxiliary/paper.rb +163 -0
  8. data/lib/sevgi/graphics/auxiliary.rb +7 -0
  9. data/lib/sevgi/graphics/document/base.rb +6 -0
  10. data/lib/sevgi/graphics/document/default.rb +1 -0
  11. data/lib/sevgi/graphics/document/html.rb +1 -0
  12. data/lib/sevgi/graphics/document/inkscape.rb +1 -0
  13. data/lib/sevgi/graphics/document/minimal.rb +1 -0
  14. data/lib/sevgi/graphics/document.rb +137 -11
  15. data/lib/sevgi/graphics/element.rb +68 -8
  16. data/lib/sevgi/graphics/mixtures/call.rb +35 -3
  17. data/lib/sevgi/graphics/mixtures/core.rb +107 -18
  18. data/lib/sevgi/graphics/mixtures/duplicate.rb +14 -0
  19. data/lib/sevgi/graphics/mixtures/export.rb +29 -6
  20. data/lib/sevgi/graphics/mixtures/hatch.rb +24 -3
  21. data/lib/sevgi/graphics/mixtures/identify.rb +23 -1
  22. data/lib/sevgi/graphics/mixtures/include.rb +31 -4
  23. data/lib/sevgi/graphics/mixtures/inkscape.rb +51 -5
  24. data/lib/sevgi/graphics/mixtures/lint.rb +12 -0
  25. data/lib/sevgi/graphics/mixtures/polyfills.rb +11 -0
  26. data/lib/sevgi/graphics/mixtures/rdf.rb +40 -1
  27. data/lib/sevgi/graphics/mixtures/render.rb +90 -2
  28. data/lib/sevgi/graphics/mixtures/save.rb +21 -3
  29. data/lib/sevgi/graphics/mixtures/symbols.rb +7 -0
  30. data/lib/sevgi/graphics/mixtures/tile.rb +41 -6
  31. data/lib/sevgi/graphics/mixtures/transform.rb +54 -16
  32. data/lib/sevgi/graphics/mixtures/underscore.rb +10 -1
  33. data/lib/sevgi/graphics/mixtures/validate.rb +15 -1
  34. data/lib/sevgi/graphics/mixtures/wrappers.rb +54 -17
  35. data/lib/sevgi/graphics/mixtures.rb +35 -3
  36. data/lib/sevgi/graphics/version.rb +2 -1
  37. data/lib/sevgi/graphics.rb +57 -5
  38. metadata +9 -8
  39. data/lib/sevgi/graphics/auxilary/canvas.rb +0 -72
  40. data/lib/sevgi/graphics/auxilary/content.rb +0 -81
  41. data/lib/sevgi/graphics/auxilary/margin.rb +0 -49
  42. data/lib/sevgi/graphics/auxilary/paper.rb +0 -95
  43. 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
- unless self[:class]
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
- end => klasses
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 self[key]
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 while element.Root?()
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
- Traversal = Data.define(:value)
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
- tap do
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
- require "sevgi/sundries"
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
- require "sevgi/sundries"
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
- # TODO: angle vs direction
12
- def Hatch(element, direction:, step:, initial: nil, **kwargs)
13
- Draw(Geometry::Operation.sweep!(element, initial: initial || element.position, direction:, step:), **kwargs)
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
- attr_reader :element, :namespace, :collision
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
- def Include!(file, id) = Derender.evaluate_file!(file, self, id:)
11
- rescue ::LoadError
12
- def Include(...) = raise NoMethodError, "\"sevgi/derender\" required"
13
- def Include!(...) = raise NoMethodError, "\"sevgi/derender\" required"
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 * (height + gap), row * (width + gap), "#{row + 1}x#{col + 1}")
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