Module: Sevgi::Graphics::Mixtures::Core

Extended by:
Forwardable
Defined in:
lib/sevgi/graphics/mixtures/core.rb

Overview

Core SVG tree and attribute DSL helpers.

Instance Method Summary collapse

Instance Method Details

#<<(element) ⇒ Sevgi::Graphics::Element

Appends an element as a child.

Parameters:

Returns:

Raises:

  • (Sevgi::ArgumentError)

    when the element has a different concrete class or would create a tree cycle



287
288
289
# File 'lib/sevgi/graphics/mixtures/core.rb', line 287

def <<(element)
  Append(element)
end

#Adopt(new_parent = nil, index: -1)) ⇒ Sevgi::Graphics::Element

Moves this element under a new parent.

Parameters:

  • new_parent (Sevgi::Graphics::Element, nil) (defaults to: nil)

    target parent or current parent

  • index (Integer) (defaults to: -1))

    insertion index

Returns:

Raises:

  • (Sevgi::ArgumentError)

    when the target parent has a different element class

  • (Sevgi::ArgumentError)

    when the target parent is this element or one of its descendants

  • (Sevgi::ArgumentError)

    when index is not an Integer insertion position



59
60
61
62
63
64
65
66
67
68
69
# File 'lib/sevgi/graphics/mixtures/core.rb', line 59

def Adopt(new_parent = nil, index: -1)
  tap do
    new_parent ||= parent
    Adoption.validate(self, new_parent)

    insertion = Adoption.index_for(self, new_parent, index)

    self.Orphan()
    Element.send(:attach, self, new_parent, index: insertion)
  end
end

#AdoptFirst(new_parent = nil) ⇒ Sevgi::Graphics::Element

Moves this element to the beginning of a parent.

Parameters:

Returns:

Raises:

  • (Sevgi::ArgumentError)

    when the target parent has a different element class

  • (Sevgi::ArgumentError)

    when the target parent is this element or one of its descendants



77
78
79
# File 'lib/sevgi/graphics/mixtures/core.rb', line 77

def AdoptFirst(*)
  Adopt(*, index: 0)
end

#Append(*elements) ⇒ Sevgi::Graphics::Element

Appends distinct existing elements as children in argument order. Each element transfers from its current parent. The complete batch is validated before any element moves.

Examples:

Move existing elements into a group

Sevgi::Graphics.SVG(:minimal) do
  dot = circle r: 2
  label = text "Ready", x: 6
  g(id: "status").Append(dot, label)
end

Parameters:

Returns:

Raises:

  • (Sevgi::ArgumentError)

    when an argument has a different element class, is repeated, or is this target or its ancestor



92
93
94
# File 'lib/sevgi/graphics/mixtures/core.rb', line 92

def Append(*elements)
  tap { Adoption.batch(elements, self, front: false) }
end

#Classify(*classes) ⇒ Sevgi::Graphics::Element

Adds CSS classes without duplicating existing values.

Parameters:

  • classes (Array<String, Symbol, Array>)

    class tokens; strings are split on whitespace

Returns:



99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
# File 'lib/sevgi/graphics/mixtures/core.rb', line 99

def Classify(*classes)
  tap do
    tokens = proc do |value|
      case value
      when nil
        []
      when ::Array
        value.flat_map { tokens.call(it) }
      else
        value.to_s.split
      end
    end

    self[:class] = [*tokens.call(self[:class]), *tokens.call(classes)].uniq
  end
end

#Defaults(**attributes) ⇒ Sevgi::Graphics::Element

Assigns default attributes only when they are absent.

Parameters:

  • attributes (Hash)

    default attributes

Returns:



119
120
121
122
123
124
125
126
127
# File 'lib/sevgi/graphics/mixtures/core.rb', line 119

def Defaults(**attributes)
  tap do
    attributes.each do |key, value|
      next if has?(key)

      self[key] = value
    end
  end
end

#Element(tag, *contents, **attributes) { ... } ⇒ Sevgi::Graphics::Element

Builds a child element with an explicit tag name.

Parameters:

  • tag (Symbol, String)

    SVG tag name

  • contents (Array<Object>)

    text or content objects; non-content objects are stringified and XML-encoded

  • attributes (Hash)

    SVG attributes

Yields:

  • evaluates the drawing DSL in the new child element

Yield Returns:

  • (Object)

    ignored block result

Returns:

Raises:

  • (Sevgi::ArgumentError)

    when the tag, attributes, or content are not valid XML



137
138
139
140
# File 'lib/sevgi/graphics/mixtures/core.rb', line 137

def Element(tag, *contents, **attributes, &block)
  contents.map! { it.is_a?(Content) ? it : Content.encoded(it) }
  self.class.send(:new, tag, contents:, attributes:, parent: self, &block)
end

#Forward(receiver, method, *args, **kwargs) ⇒ Object

Forwards this element as the first argument to another receiver.

Returns forwarded call result.

Parameters:

  • receiver (Object)

    target receiver

  • method (Symbol, String)

    method name

  • args (Array<Object>)

    additional arguments

  • kwargs (Hash)

    additional keyword arguments

Returns:

  • (Object)

    forwarded call result



149
150
151
# File 'lib/sevgi/graphics/mixtures/core.rb', line 149

def Forward(receiver, method, ...)
  receiver.public_send(method, self, ...)
end

#Is?(name) ⇒ Boolean

Reports whether this element has the given SVG name.

Parameters:

  • name (Symbol, String)

    SVG name

Returns:

  • (Boolean)


156
157
158
# File 'lib/sevgi/graphics/mixtures/core.rb', line 156

def Is?(name)
  self.name() == name.to_sym
end

#OrphanSevgi::Graphics::Element?

Removes this element from its parent and makes it a detached subtree root.

Returns:



162
163
164
165
166
167
# File 'lib/sevgi/graphics/mixtures/core.rb', line 162

def Orphan
  return if Root?()

  Element.send(:detach, self)
  self
end

#Prepend(*elements) ⇒ Sevgi::Graphics::Element

Prepends distinct existing elements as children in argument order. Each element transfers from its current parent. The complete batch is validated before any element moves.

Parameters:

Returns:

Raises:

  • (Sevgi::ArgumentError)

    when an argument has a different element class, is repeated, or is this target or its ancestor



174
175
176
# File 'lib/sevgi/graphics/mixtures/core.rb', line 174

def Prepend(*elements)
  tap { Adoption.batch(elements, self, front: true) }
end

#RootSevgi::Graphics::Element

Returns the topmost element in this tree. A detached subtree returns its detached topmost element; use #Root? to distinguish a document root.

Returns:



181
182
183
184
185
186
# File 'lib/sevgi/graphics/mixtures/core.rb', line 181

def Root
  element = self
  element = element.parent while element.parent

  element
end

#Root?Boolean

Reports whether this element is the root document element.

Returns:

  • (Boolean)


190
191
192
# File 'lib/sevgi/graphics/mixtures/core.rb', line 190

def Root?
  Element.root?(self)
end

#Stay(value) ⇒ Sevgi::Graphics::Mixtures::Stop

Wraps a traversal return value as a stop token.

Parameters:

  • value (Object)

    value returned from traversal

Returns:



198
# File 'lib/sevgi/graphics/mixtures/core.rb', line 198

def Stay(...) = Stop.send(:new, ...)

#Traverse(depth = 0, leave = nil) {|element, depth| ... } ⇒ Sevgi::Graphics::Element, Object

Traverses the subtree depth-first.

Examples:

Find the first circle and stop the traversal

drawing = Sevgi::Graphics.SVG(:minimal) { g { circle id: "target"; circle id: "later" } }
found = drawing.Traverse { |node| node.Stay(node) if node.Is? :circle }
found[:id] # => "target"

Parameters:

  • depth (Integer) (defaults to: 0)

    starting depth

  • leave (Proc, nil) (defaults to: nil)

    optional leave callback

Yields:

  • (element, depth)

    visits each element before its children

Yield Parameters:

Yield Returns:

  • (Object)

    ignored unless it is a Stop token

Returns:

Raises:

  • (Sevgi::ArgumentError)

    when no block is given



213
214
215
216
217
# File 'lib/sevgi/graphics/mixtures/core.rb', line 213

def Traverse(depth = 0, leave = nil, &block)
  ArgumentError.("Block required") unless block

  Traversal.call(self, depth, leave, &block)
end

#TraverseUp(height = 0) {|element, height| ... } ⇒ Object?

Traverses ancestors from this element to the root.

Parameters:

  • height (Integer) (defaults to: 0)

    starting height

Yields:

  • (element, height)

    visits this element and each ancestor

Yield Parameters:

Yield Returns:

  • (Object)

    ignored unless it is a Stop token

Returns:

  • (Object, nil)

    value passed through Stay, or nil

Raises:

  • (Sevgi::ArgumentError)

    when no block is given



227
228
229
230
231
232
233
234
235
236
237
238
239
240
# File 'lib/sevgi/graphics/mixtures/core.rb', line 227

def TraverseUp(height = 0, &block)
  ArgumentError.("Block required") unless block

  element = self

  loop do
    yield(element, height).tap { return it.value if it.is_a?(Stop) }

    break unless element.parent

    element = element.parent
    height += 1
  end
end

#With(*args, receiver: self, **kwargs) {|*args, **kwargs| ... } ⇒ Sevgi::Graphics::Element

Evaluates a block in the parent element context.

Examples:

Add a sibling while forwarding its id

root = Sevgi::Graphics.SVG id: "root"
child = root.g id: "child"
child.With("sibling") { |id| line id: }

Parameters:

  • args (Array<Object>)

    positional arguments passed to the block

  • receiver (Sevgi::Graphics::Element) (defaults to: self)

    element whose parent becomes the block receiver

  • kwargs (Hash)

    keyword arguments passed to the block

Yields:

  • (*args, **kwargs)

    evaluates in the selected element's parent context

Yield Returns:

  • (Object)

    ignored block result

Returns:

Raises:

  • (Sevgi::ArgumentError)

    when no block is given

  • (Sevgi::ArgumentError)

    when receiver is not an element or has no parent



255
256
257
258
259
260
261
262
263
# File 'lib/sevgi/graphics/mixtures/core.rb', line 255

def With(*args, receiver: self, **kwargs, &block)
  ArgumentError.("Block required") unless block
  ArgumentError.("Receiver must be an element") unless receiver.is_a?(Element)

  parent = receiver.parent
  ArgumentError.("Receiver has no parent") unless parent

  tap { parent.instance_exec(*args, **kwargs, &block) }
end

#Within(*args, receiver: self, **kwargs) {|*args, **kwargs| ... } ⇒ Sevgi::Graphics::Element

Evaluates a block in this element context.

Examples:

Select a receiver without consuming the block argument

target = Sevgi::Graphics.SVG id: "target"
source = Sevgi::Graphics.SVG id: "source"
source.Within("child", receiver: target) { |id| g id: }

Parameters:

  • args (Array<Object>)

    positional arguments passed to the block

  • receiver (Object) (defaults to: self)

    block receiver

  • kwargs (Hash)

    keyword arguments passed to the block

Yields:

  • (*args, **kwargs)

    evaluates in the selected receiver context

Yield Returns:

  • (Object)

    ignored block result

Returns:

Raises:

  • (Sevgi::ArgumentError)

    when no block is given



277
278
279
280
281
# File 'lib/sevgi/graphics/mixtures/core.rb', line 277

def Within(*args, receiver: self, **kwargs, &block)
  ArgumentError.("Block required") unless block

  tap { receiver.instance_exec(*args, **kwargs, &block) }
end