sevgi 0.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.
Files changed (82) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +9 -0
  3. data/LICENSE +674 -0
  4. data/README.md +15 -0
  5. data/bin/sevgi +136 -0
  6. data/lib/sevgi/errors.rb +11 -0
  7. data/lib/sevgi/external.rb +8 -0
  8. data/lib/sevgi/function.rb +35 -0
  9. data/lib/sevgi/geometry/elements/rect.rb +133 -0
  10. data/lib/sevgi/geometry/elements/segment.rb +102 -0
  11. data/lib/sevgi/geometry/elements.rb +41 -0
  12. data/lib/sevgi/geometry/equation/line/diagonal.rb +90 -0
  13. data/lib/sevgi/geometry/equation/line/horizontal.rb +15 -0
  14. data/lib/sevgi/geometry/equation/line/vertical.rb +52 -0
  15. data/lib/sevgi/geometry/equation/line.rb +38 -0
  16. data/lib/sevgi/geometry/equation.rb +3 -0
  17. data/lib/sevgi/geometry/errors.rb +5 -0
  18. data/lib/sevgi/geometry/external.rb +15 -0
  19. data/lib/sevgi/geometry/operation/align.rb +40 -0
  20. data/lib/sevgi/geometry/operation/sweep.rb +52 -0
  21. data/lib/sevgi/geometry/operation.rb +28 -0
  22. data/lib/sevgi/geometry/point.rb +62 -0
  23. data/lib/sevgi/geometry.rb +10 -0
  24. data/lib/sevgi/graphics/attribute.rb +125 -0
  25. data/lib/sevgi/graphics/canvas.rb +51 -0
  26. data/lib/sevgi/graphics/content.rb +72 -0
  27. data/lib/sevgi/graphics/document/base.rb +51 -0
  28. data/lib/sevgi/graphics/document/default.rb +19 -0
  29. data/lib/sevgi/graphics/document/html.rb +11 -0
  30. data/lib/sevgi/graphics/document/inkscape.rb +17 -0
  31. data/lib/sevgi/graphics/document/minimal.rb +11 -0
  32. data/lib/sevgi/graphics/document.rb +53 -0
  33. data/lib/sevgi/graphics/element.rb +85 -0
  34. data/lib/sevgi/graphics/external.rb +15 -0
  35. data/lib/sevgi/graphics/mixtures/core.rb +81 -0
  36. data/lib/sevgi/graphics/mixtures/duplicate.rb +36 -0
  37. data/lib/sevgi/graphics/mixtures/hatch.rb +21 -0
  38. data/lib/sevgi/graphics/mixtures/identify.rb +93 -0
  39. data/lib/sevgi/graphics/mixtures/inkscape.rb +15 -0
  40. data/lib/sevgi/graphics/mixtures/lint.rb +33 -0
  41. data/lib/sevgi/graphics/mixtures/render.rb +149 -0
  42. data/lib/sevgi/graphics/mixtures/replicate.rb +125 -0
  43. data/lib/sevgi/graphics/mixtures/save.rb +23 -0
  44. data/lib/sevgi/graphics/mixtures/transform.rb +78 -0
  45. data/lib/sevgi/graphics/mixtures/underscore.rb +21 -0
  46. data/lib/sevgi/graphics/mixtures/validate.rb +27 -0
  47. data/lib/sevgi/graphics/mixtures/wrappers.rb +63 -0
  48. data/lib/sevgi/graphics/mixtures.rb +16 -0
  49. data/lib/sevgi/graphics.rb +10 -0
  50. data/lib/sevgi/internal/constant.rb +5 -0
  51. data/lib/sevgi/internal/dim.rb +13 -0
  52. data/lib/sevgi/internal/function/file.rb +30 -0
  53. data/lib/sevgi/internal/function/float.rb +35 -0
  54. data/lib/sevgi/internal/function/formula.rb +71 -0
  55. data/lib/sevgi/internal/function/math.rb +43 -0
  56. data/lib/sevgi/internal/function/string.rb +13 -0
  57. data/lib/sevgi/internal/function.rb +7 -0
  58. data/lib/sevgi/internal/list.rb +27 -0
  59. data/lib/sevgi/internal/locate.rb +47 -0
  60. data/lib/sevgi/internal/margin.rb +23 -0
  61. data/lib/sevgi/internal/minitest/script.rb +50 -0
  62. data/lib/sevgi/internal/minitest/shell.rb +71 -0
  63. data/lib/sevgi/internal/minitest/suite.rb +35 -0
  64. data/lib/sevgi/internal/minitest.rb +5 -0
  65. data/lib/sevgi/internal/paper.rb +26 -0
  66. data/lib/sevgi/internal.rb +10 -0
  67. data/lib/sevgi/standard/conform.rb +55 -0
  68. data/lib/sevgi/standard/data/attribute.rb +496 -0
  69. data/lib/sevgi/standard/data/element.rb +269 -0
  70. data/lib/sevgi/standard/data/specification.rb +1841 -0
  71. data/lib/sevgi/standard/data.rb +79 -0
  72. data/lib/sevgi/standard/errors.rb +28 -0
  73. data/lib/sevgi/standard/model.rb +82 -0
  74. data/lib/sevgi/standard.rb +26 -0
  75. data/lib/sevgi/utensils/external.rb +22 -0
  76. data/lib/sevgi/utensils/grid.rb +49 -0
  77. data/lib/sevgi/utensils/ruler.rb +47 -0
  78. data/lib/sevgi/utensils/tsquare.rb +28 -0
  79. data/lib/sevgi/utensils.rb +7 -0
  80. data/lib/sevgi/version.rb +5 -0
  81. data/lib/sevgi.rb +10 -0
  82. metadata +127 -0
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Sevgi
4
+ module Geometry
5
+ module External
6
+ class << self
7
+ def included(base)
8
+ super
9
+
10
+ base.const_set(:Geometry, Geometry)
11
+ end
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,40 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Sevgi
4
+ module Geometry
5
+ module Operation
6
+ module Align
7
+ def align(element, other, alignment = :center)
8
+ translate(element, other, alignment).apply(element)
9
+ end
10
+
11
+ def alignment(element, other, alignment = :center)
12
+ this, that = element.bbox, other.bbox
13
+
14
+ case alignment
15
+ when :center then Translation[(this.width - that.width) / 2.0, (this.height - that.height) / 2.0]
16
+ when :left then Translation[this.ne.x - that.ne.x, 0]
17
+ when :right then Translation[this.sw.x - that.sw.x, 0]
18
+ when :top then Translation[0, this.ne.y - that.ne.y]
19
+ when :bottom then Translation[0, this.sw.y - that.sw.y]
20
+ else ArgumentError.("No such type of alignment: #{alignment}")
21
+ end
22
+ end
23
+
24
+ def applicable?(element)
25
+ Translation.applicable?(element)
26
+ end
27
+ end
28
+
29
+ register(Align, :align, :alignment)
30
+ end
31
+
32
+ Translation = Data.define(:dx, :dy) do
33
+ def apply(element) = element.translate(**to_h)
34
+
35
+ class << self
36
+ def applicable?(element) = element.respond_to?(:translate)
37
+ end
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,52 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Sevgi
4
+ module Geometry
5
+ module Operation
6
+ module Sweep
7
+ extend self
8
+
9
+ LIMIT = 1_000
10
+
11
+ def sweep(element, initial:, direction:, step:, limit: LIMIT, &block)
12
+ line = Equation::Line.from_direction(point: initial, direction:)
13
+
14
+ [
15
+ *unisweep(element, line.shift(-step), -step, limit:).reverse,
16
+ *unisweep(element, line, step, limit:),
17
+ ].tap do |segments|
18
+ yield(segments) if block
19
+ end
20
+ end
21
+
22
+ def sweep!(element, initial:, direction:, step:, limit: LIMIT, &block)
23
+ sweep(element, initial: initial, direction:, step:, limit:) do |segments|
24
+ OperationError.("No segments found [initial: #{initial}, direction: #{direction} step: #{step}]") if segments.empty?
25
+
26
+ yield(segments) if block
27
+ end
28
+ end
29
+
30
+ def unisweep(element, line, step, limit: LIMIT)
31
+ segments = []
32
+
33
+ limit.times do
34
+ return segments unless (segment = element.intersection(line))
35
+
36
+ segments << segment unless segment.ignorable?
37
+
38
+ line = line.shift(step)
39
+ end
40
+
41
+ OperationError.("Loop limit reached: #{limit}")
42
+ end
43
+
44
+ def applicable?(element)
45
+ element.respond_to?(:intersection)
46
+ end
47
+ end
48
+
49
+ register(Sweep, :sweep, :sweep!, :unisweep)
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Sevgi
4
+ module Geometry
5
+ module Operation
6
+ extend self
7
+
8
+ OperationError = Class.new(GeometryError)
9
+ OperationInapplicableError = Class.new(GeometryError)
10
+
11
+ def register(handler, *operations) = operations.each { |operation| def_operation(operation, handler) }
12
+
13
+ private
14
+
15
+ def def_operation(operation, handler)
16
+ define_singleton_method(operation) do |element, *args, **kwargs, &block|
17
+ OperationInapplicableError.("Not a Geometric Element: #{element}") unless element.is_a?(Geometry::Element)
18
+ OperationInapplicableError.("Unapplicable operation for #{element}: #{handler}") unless handler.applicable?(element)
19
+
20
+ handler.public_send(operation, element, *args, **kwargs, &block)
21
+ end
22
+ end
23
+ end
24
+ end
25
+ end
26
+
27
+ require_relative "operation/align"
28
+ require_relative "operation/sweep"
@@ -0,0 +1,62 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Sevgi
4
+ module Geometry
5
+ Point = Data.define(:x, :y) do
6
+ include Comparable
7
+
8
+ def initialize(x:, y:)
9
+ super(x: x.to_f, y: y.to_f)
10
+ end
11
+
12
+ def above?(other)
13
+ y <= other.y
14
+ end
15
+
16
+ def below?(other)
17
+ y >= other.y
18
+ end
19
+
20
+ def approx(precision = nil)
21
+ with(x: F.approx(x, precision), y: F.approx(y, precision))
22
+ end
23
+
24
+ def eq?(other, precision: nil)
25
+ self.class.eq?(self, other, precision:)
26
+ end
27
+
28
+ def infinite?
29
+ [x, y].any?(&:infinite?)
30
+ end
31
+
32
+ def nan?
33
+ [x, y].any?(&:nan?)
34
+ end
35
+
36
+ def to_s
37
+ "P(#{F.approx(x)} #{F.approx(y)})"
38
+ end
39
+
40
+ def translate(dx: nil, dy: nil)
41
+ with(x: x + (dx || 0.0), y: y + (dy || 0.0))
42
+ end
43
+
44
+ def unordered_between?(p, q)
45
+ nan? ? false : between?([p, q].min, [p, q].max)
46
+ end
47
+
48
+ def <=>(other)
49
+ return unless other.is_a?(self.class)
50
+ return if other.nan? || nan?
51
+
52
+ deconstruct <=> other.deconstruct
53
+ end
54
+
55
+ class << self
56
+ def eq?(p, q, precision: nil) = F.eq?(p.x, q.x, precision:) && F.eq?(p.y, q.y, precision:)
57
+
58
+ def origin = new(x: 0.0, y: 0.0)
59
+ end
60
+ end
61
+ end
62
+ end
@@ -0,0 +1,10 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "geometry/errors"
4
+
5
+ require_relative "geometry/point"
6
+ require_relative "geometry/equation"
7
+ require_relative "geometry/elements"
8
+ require_relative "geometry/operation"
9
+
10
+ require_relative "geometry/external"
@@ -0,0 +1,125 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Some parts are adapted from https://github.com/DannyBen/victor (lib/victor/attributes.rb)
4
+
5
+ module Sevgi
6
+ module Graphics
7
+ module Attribute
8
+ module Ident
9
+ INTERNAL_PREFIX = "_"
10
+ UPDATE_SUFFIX = "+"
11
+
12
+ def internal?(given)
13
+ (@internal ||= {})[given] ||= given.start_with?(INTERNAL_PREFIX)
14
+ end
15
+
16
+ def id(given)
17
+ (@id ||= {})[given] ||= (updateable?(given) ? given.to_s.delete_suffix(UPDATE_SUFFIX) : given).to_sym
18
+ end
19
+
20
+ def updateable?(given)
21
+ (@updateable ||= {})[given] ||= given.end_with?(UPDATE_SUFFIX)
22
+ end
23
+ end
24
+
25
+ extend Ident
26
+
27
+ class Store
28
+ def initialize(attributes = {})
29
+ @store = {}
30
+
31
+ import(attributes)
32
+ end
33
+
34
+ def import(attributes)
35
+ hash = attributes.compact.to_a.map do |key, value|
36
+ [key.to_sym, value.is_a?(::Hash) ? value.transform_keys!(&:to_sym) : value]
37
+ end.to_h
38
+
39
+ @store.merge!(hash)
40
+ end
41
+
42
+ def [](key)
43
+ @store[Attribute.id(key)]
44
+ end
45
+
46
+ def []=(key, value)
47
+ return if value.nil?
48
+
49
+ @store[id = Attribute.id(key)] = @store.key?(id) && Attribute.updateable?(key) ? update(id, value) : value
50
+ end
51
+
52
+ def delete(key)
53
+ @store.delete(Attribute.id(key))
54
+ end
55
+
56
+ def export
57
+ hash = @store.reject { |id, _| Attribute.internal?(id) }
58
+ return hash unless hash.key?(:id)
59
+
60
+ # A small aesthetic touch: always keep the id attribute first
61
+ { id: hash.delete(:id), **hash }
62
+ end
63
+
64
+ def has?(key)
65
+ @store.key?(Attribute.id(key))
66
+ end
67
+
68
+ def initialize_copy(original)
69
+ @store = {}
70
+ original.store.each { |key, value| @store[key] = value.dup }
71
+
72
+ super
73
+ end
74
+
75
+ def list
76
+ export.keys
77
+ end
78
+
79
+ def to_h
80
+ @store
81
+ end
82
+
83
+ def to_xml_lines
84
+ export.map { |id, value| to_xml(id, value) }
85
+ end
86
+
87
+ protected
88
+
89
+ attr_reader :store
90
+
91
+ private
92
+
93
+ UPDATER = {
94
+ ::String => proc { |old_value, new_value| [old_value, new_value].reject(&:empty?).join(" ") },
95
+ ::Symbol => proc { |old_value, new_value| [old_value, new_value].reject(&:empty?).join(" ").to_sym },
96
+ ::Array => proc { |old_value, new_value| [old_value, new_value] },
97
+ ::Hash => proc { |old_value, new_value| merge(old_value, new_value.transform_keys(&:to_sym)) },
98
+ }.freeze
99
+
100
+ def update(id, new_value)
101
+ (old_value = @store[id]).nil? ? new_value : UPDATER[new_value.class].call(*sanitized(old_value, new_value))
102
+ end
103
+
104
+ def sanitized(old_value, new_value)
105
+ ArgumentError.("Incompatible values: #{new_value} vs #{old_value}") unless new_value.is_a?(old_value.class)
106
+ ArgumentError.("Unsupported value for update: #{new_value}") unless UPDATER.key?(new_value.class)
107
+
108
+ [old_value, new_value]
109
+ end
110
+
111
+ private_constant :UPDATER
112
+
113
+ def to_xml(id, value)
114
+ case value
115
+ when ::Hash then %[#{id}="#{value.map { "#{_1}:#{_2}" }.join("; ")}"]
116
+ when ::Array then %[#{id}="#{value.join(" ")}"]
117
+ else %[#{id}=#{value.to_s.encode(xml: :attr)}]
118
+ end
119
+ end
120
+ end
121
+ end
122
+
123
+ Attributes = Attribute::Store
124
+ end
125
+ end
@@ -0,0 +1,51 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "forwardable"
4
+
5
+ module Sevgi
6
+ module Graphics
7
+ class Canvas
8
+ extend Forwardable
9
+ def_delegators :@margin, *Margin.members
10
+ def_delegators :@dim, *Dim.members
11
+
12
+ attr_reader :dim, :margin, :content
13
+
14
+ def initialize(width:, height:, unit: "mm", margins: [])
15
+ @dim = Dim[width, height, unit]
16
+ @margin = Margin[*margins]
17
+
18
+ compute
19
+ freeze
20
+ end
21
+
22
+ def attributes = { **viewport, viewBox: viewbox }
23
+
24
+ def rect = content.rect
25
+
26
+ def viewport = { width: "#{width}#{unit}", height: "#{height}#{unit}" }
27
+
28
+ def viewbox = F.prettify(-margin.left, -margin.top, width, height).join(" ")
29
+
30
+ def with(*margins) = margins.empty? ? self : self.class.new(**dim.to_h, margins:)
31
+
32
+ private
33
+
34
+ def compute
35
+ @content = dim.with(width: width - margin.left - margin.right, height: height - margin.top - margin.bottom)
36
+ end
37
+
38
+ class << self
39
+ def call(arg = Paper.default, *)
40
+ case arg
41
+ when Canvas then arg
42
+ when Symbol then new(**Paper[arg].to_h)
43
+ else ArgumentError.("Argument must be a Symbol or Canvas instance: #{arg}")
44
+ end => canvas
45
+
46
+ canvas.with(*)
47
+ end
48
+ end
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,72 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Sevgi
4
+ module Graphics
5
+ class Content
6
+ attr_reader :content
7
+
8
+ def initialize(content) = @content = content
9
+
10
+ def render(renderer, depth) = raise NotImplementedError
11
+
12
+ def to_s = content.to_s
13
+
14
+ class CData < Content
15
+ def render(renderer, depth)
16
+ depth += 1
17
+
18
+ renderer.append(depth, "<![CDATA[")
19
+ renderer.append(depth + 1, *Array(content))
20
+ renderer.append(depth, "]]>")
21
+ end
22
+ end
23
+
24
+ class CSS < Content
25
+ def initialize(content)
26
+ ArgumentError.("CSS content must be a hash: #{content}") unless content.is_a?(::Hash)
27
+
28
+ super
29
+ end
30
+
31
+ def render(renderer, depth)
32
+ depth += 1
33
+
34
+ renderer.append(depth, "<![CDATA[")
35
+
36
+ depth += 1
37
+ content.each do |rule, styles|
38
+ case styles
39
+ when ::Hash
40
+ renderer.append(depth, "#{rule} {")
41
+ renderer.append(depth + 1, *styles.map { |key, value| "#{key}: #{value};" })
42
+ renderer.append(depth, "}")
43
+ when ::String, ::Symbol, ::Numeric
44
+ renderer.append(depth, "#{rule}: #{styles};")
45
+ else
46
+ ArgumentError.("Malformed style: #{styles}")
47
+ end
48
+ end
49
+ depth -= 1
50
+
51
+ renderer.append(depth, "]]>")
52
+ end
53
+ end
54
+
55
+ class Encoded < Content
56
+ def to_s = content.to_s.encode(xml: :text)
57
+
58
+ def render(renderer, depth) = renderer.append(depth + 1, to_s)
59
+ end
60
+
61
+ class Verbatim < Content
62
+ def render(renderer, depth) = renderer.append(depth + 1, to_s)
63
+ end
64
+ end
65
+
66
+ extend self
67
+
68
+ def Content(arg) = Content::Encoded.new(arg)
69
+
70
+ def Text(contents) = Array(contents).map(&:to_s).join("\n")
71
+ end
72
+ end
@@ -0,0 +1,51 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Sevgi
4
+ module Graphics
5
+ module Document
6
+ DEFAULTS = { lint: true, validate: true }.freeze
7
+
8
+ class Proto < Element
9
+ extend Profile::DSL
10
+
11
+ mixture Mixtures::Core
12
+ mixture Mixtures::Render
13
+
14
+ def call(*, **)
15
+ options = DEFAULTS.merge(**)
16
+
17
+ self.Process(*, **options) if respond_to?(:Process)
18
+ self.Render(*, **options)
19
+ end
20
+
21
+ class << self
22
+ def attributes = self == Proto ? {} : { **superclass.attributes, **profile.attributes }
23
+
24
+ def preambles = self == Proto ? nil : profile.preambles || superclass.preambles
25
+ end
26
+ end
27
+
28
+ class Base < Proto
29
+ public_class_method :new
30
+
31
+ document :base
32
+
33
+ mixture Mixtures::Duplicate
34
+ mixture Mixtures::Hatch
35
+ mixture Mixtures::Identify
36
+ mixture Mixtures::Lint
37
+ mixture Mixtures::Replicate
38
+ mixture Mixtures::Save
39
+ mixture Mixtures::Transform
40
+ mixture Mixtures::Underscore
41
+ mixture Mixtures::Validate
42
+ mixture Mixtures::Wrappers
43
+
44
+ def Process(**options)
45
+ self.Validate if options[:validate]
46
+ self.Lint if options[:lint]
47
+ end
48
+ end
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Sevgi
4
+ module Graphics
5
+ module Document
6
+ class Default < Minimal
7
+ document :default,
8
+ attributes: {
9
+ "xmlns": "http://www.w3.org/2000/svg",
10
+ "xmlns:xlink": "http://www.w3.org/1999/xlink",
11
+ },
12
+ preambles: [
13
+ '<?xml version="1.0" standalone="no"?>',
14
+ '<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">',
15
+ ]
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Sevgi
4
+ module Graphics
5
+ module Document
6
+ class HTML < Default
7
+ document :html, preambles: []
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Sevgi
4
+ module Graphics
5
+ module Document
6
+ class Inkscape < Default
7
+ document :inkscape, attributes: {
8
+ "xmlns:inkscape": "http://www.inkscape.org/slugs/inkscape",
9
+ "xmlns:sodipodi": "http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd",
10
+ "shape-rendering": "crispEdges",
11
+ }
12
+
13
+ mixture Mixtures::Inkscape
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Sevgi
4
+ module Graphics
5
+ module Document
6
+ class Minimal < Base
7
+ document :minimal
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,53 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Sevgi
4
+ module Graphics
5
+ class Profile
6
+ @available = {}
7
+
8
+ class << self
9
+ attr_reader :available
10
+
11
+ def call(document, canvas = nil, **, &block)
12
+ (klass = self[document]).root(**klass.attributes, **(canvas ? Graphics.Canvas(canvas).attributes : {}), **, &block)
13
+ end
14
+
15
+ def register(name, klass) = (available[name] = klass)
16
+
17
+ def [](name) = available[name]
18
+ end
19
+
20
+ attr_reader :name, :attributes, :preambles
21
+
22
+ def initialize(name, attributes: nil, preambles: nil)
23
+ @name = name
24
+ @attributes = attributes || {}
25
+ @preambles = preambles
26
+ end
27
+
28
+ module DSL
29
+ attr_reader :profile
30
+
31
+ def document(name, attributes: {}, preambles: nil)
32
+ @profile = Profile.new(name, attributes:, preambles:).tap do
33
+ Profile.register(name, self)
34
+ end
35
+ end
36
+
37
+ def mixture(*modules)
38
+ modules.each do |mod|
39
+ include(mod::InstanceMethods) if defined?(mod::InstanceMethods)
40
+ extend(mod::ClassMethods) if defined?(mod::ClassMethods)
41
+ end
42
+ end
43
+ end
44
+ end
45
+ end
46
+ end
47
+
48
+ require_relative "document/base"
49
+ require_relative "document/minimal"
50
+
51
+ require_relative "document/default"
52
+ require_relative "document/html"
53
+ require_relative "document/inkscape"