sevgi 0.0.0

Sign up to get free protection for your applications and to get access to all the features.
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"