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
data/README.md ADDED
@@ -0,0 +1,15 @@
1
+ [![test status](https://github.com/roktas/sevgi/workflows/Test/badge.svg)](https://github.com/roktas/sevgi/actions?query=workflow%3ATest)
2
+ [![code quality](https://codebeat.co/badges/f2de3389-dd66-4d7a-b7fb-452330d62176)](https://codebeat.co/projects/github-com-roktas-sevgi-dev)
3
+
4
+ # Sevgi
5
+
6
+ <p align="center"><img src="srv/static/assets/light/logo.svg#gh-light-mode-only"/></p>
7
+ <p align="center"><img src="srv/static/assets/dark/logo.svg#gh-dark-mode-only"/></p>
8
+
9
+ <p align="center"><img src="srv/static/assets/light/pacman-animation.png"/></p>
10
+
11
+ Inspired by [Victor](https://github.com/DannyBen/victor), which might be a better choice for those seeking something
12
+ simpler. Please note that a fair amount of the examples used for demonstration purposes come from this project (thanks
13
+ to the author).
14
+
15
+ ### Roadmap
data/bin/sevgi ADDED
@@ -0,0 +1,136 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ require "sevgi/external"
5
+
6
+ module Sevgi
7
+ module CLI
8
+ extend self
9
+
10
+ CLIError = Class.new(Sevgi::Error)
11
+
12
+ PROGNAME = "sevgi"
13
+ ENVVOMIT = "SEVGI_VOMIT"
14
+
15
+ Options = Struct.new(:library, :vomit, :help, :version) do
16
+ class << self
17
+ def parse(argv)
18
+ new.tap do |options|
19
+ argv.first.start_with?("-") ? option(argv, options) : break until argv.empty?
20
+
21
+ next unless options.library
22
+
23
+ CLIError.("No library found: #{options.library}") unless ::File.exist?(options.library)
24
+ end
25
+ end
26
+
27
+ private
28
+
29
+ def option(argv, options)
30
+ case (arg = argv.shift)
31
+ when "-l", "--library" then options.library = argv.shift
32
+ when "-x", "--exception" then options.vomit = true
33
+ when "-h", "--help" then options.help = true
34
+ when "-v", "--version" then options.version = true
35
+ else CLIError.("Not a valid option: #{arg}")
36
+ end
37
+ end
38
+ end
39
+ end
40
+
41
+ private_constant :Options
42
+
43
+ def call(argv)
44
+ return puts(help) if (options = Options.parse(argv)).help
45
+ return puts(Sevgi::VERSION) if options.version
46
+
47
+ Signal.trap("INT") { Kernel.abort("") }
48
+ Kernel.load(file = options.library) if options.library
49
+ Kernel.load(file = script(argv))
50
+ rescue Exception => e # rubocop:disable Lint/RescueException
51
+ die(e, file, options)
52
+ end
53
+
54
+ private
55
+
56
+ def description(e, file)
57
+ case e
58
+ when ArgumentError then "Programming error"
59
+ when GeometryError then "Geometry error"
60
+ when ValidationError then "Validation error"
61
+ else "Error"
62
+ end => error
63
+
64
+ <<~DESCRIPTION
65
+ #{error} #{scene(e.backtrace, file)}
66
+ #{e.message}
67
+ DESCRIPTION
68
+ end
69
+
70
+ def die(e, file, options)
71
+ abort(e.message) if e.is_a?(CLIError)
72
+
73
+ raise e if options.vomit || ENV[ENVVOMIT]
74
+
75
+ warn(description(e, file))
76
+ warn("")
77
+ abort(postmortem(file))
78
+ end
79
+
80
+ def help
81
+ <<~HELP
82
+ Usage: #{PROGNAME} [options...] <SCRIPT> [ARGS...]
83
+
84
+ See documentation for detailed help.
85
+
86
+ Options:
87
+
88
+ -l, --library FILE Preload library FILE
89
+ -x, --exception Raise exceptions instead of abort
90
+ -h, --help Show this help
91
+ -v, --version Display version
92
+ HELP
93
+ end
94
+
95
+ def postmortem(file)
96
+ <<~POSTMORTEM
97
+ For more details, run the script again:
98
+
99
+ - By using the -x switch
100
+
101
+ #{PROGNAME} -x #{file}
102
+
103
+ - By setting the #{ENVVOMIT} environment variable
104
+
105
+ #{ENVVOMIT}=t #{file}
106
+
107
+ If you think this is a bug, you can report it by creating an issue.
108
+ POSTMORTEM
109
+ end
110
+
111
+ def scene(backtrace, file)
112
+ default = "in #{file}"
113
+ return default unless backtrace
114
+
115
+ path = ::File.expand_path(file)
116
+ _, line = backtrace.map { _1.split(":")[..1] }.find do |spot|
117
+ ::File.expand_path(spot.first) == path
118
+ end
119
+
120
+ line ? "in '#{file}', around line #{line}" : default
121
+ end
122
+
123
+ def script(argv)
124
+ CLIError.("No script file given.") unless (file = argv.shift)
125
+ CLIError.("No such script file: #{file}") unless ::File.exist?(file)
126
+
127
+ file
128
+ end
129
+ end
130
+ end
131
+
132
+ def main
133
+ Sevgi::CLI.(ARGV)
134
+ end
135
+
136
+ main
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Sevgi
4
+ class Error < StandardError
5
+ class << self
6
+ def call(...) = raise(self, ...)
7
+ end
8
+ end
9
+
10
+ ArgumentError = Class.new(Error)
11
+ end
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../sevgi"
4
+
5
+ include Sevgi::Function::External
6
+ include Sevgi::Geometry::External
7
+ include Sevgi::Graphics::External
8
+ include Sevgi::Utensils::External
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Sevgi
4
+ module Function
5
+ module External
6
+ EXTENSIONS = ["sevgi", "rb"].freeze
7
+ DIRECTORIES = ["lib", "library"].freeze
8
+
9
+ def Lib(name)
10
+ start = ::File.dirname(caller_locations(1..1).first.path)
11
+
12
+ paths = F.variations(name, DIRECTORIES, EXTENSIONS)
13
+
14
+ raise Error, "No library found matching: #{name}" unless (location = Locate.(paths, start))
15
+
16
+ Kernel.load(location.file)
17
+ end
18
+
19
+ module Function
20
+ extend Sevgi::Function::Float
21
+ extend Sevgi::Function::Math
22
+ end
23
+
24
+ class << self
25
+ def included(base)
26
+ super
27
+
28
+ base.const_set(:F, External::Function)
29
+ end
30
+ end
31
+ end
32
+ end
33
+
34
+ F = Function
35
+ end
@@ -0,0 +1,133 @@
1
+ # frozen_string_literal: true
2
+
3
+ # codebeat:disable[TOO_MANY_IVARS]
4
+
5
+ require "forwardable"
6
+
7
+ module Sevgi
8
+ module Geometry
9
+ class Rect < Element
10
+ # Argument order is in clockwise rotation from position point
11
+ Corner = Data.define(:top_left, :top_right, :bottom_right, :bottom_left) do
12
+ class << self
13
+ def of(rect)
14
+ position = rect.position
15
+
16
+ new(
17
+ top_left: position,
18
+ top_right: position.translate(dx: rect.width),
19
+ bottom_right: position.translate(dx: rect.width, dy: rect.height),
20
+ bottom_left: position.translate(dy: rect.height),
21
+ )
22
+ end
23
+ end
24
+ end
25
+
26
+ # Argument order is in clockwise rotation from top segment (as in CSS)
27
+ Side = Data.define(:top, :right, :bottom, :left) do
28
+ class << self
29
+ def of(rect)
30
+ corner = rect.corner
31
+
32
+ new(
33
+ top: Segment[corner.top_left, corner.top_right],
34
+ right: Segment[corner.top_right, corner.bottom_right],
35
+ bottom: Segment[corner.bottom_left, corner.bottom_right],
36
+ left: Segment[corner.top_left, corner.bottom_left],
37
+ )
38
+ end
39
+ end
40
+ end
41
+
42
+ extend Forwardable
43
+
44
+ def_delegators :@side, *Side.members
45
+ def_delegators :@corner, *Corner.members
46
+
47
+ attr_reader :position, :width, :height
48
+ attr_reader :corner, :corners, :side, :sides
49
+
50
+ def initialize(position: nil, width:, height:)
51
+ super()
52
+
53
+ @position = position || Point.origin
54
+ @width = width.to_f
55
+ @height = height.to_f
56
+
57
+ compute
58
+ end
59
+
60
+ def bbox
61
+ BBox[*@corner.deconstruct]
62
+ end
63
+
64
+ def diagonal
65
+ Segment.forward(top_left, bottom_right)
66
+ end
67
+
68
+ def draw(container, **)
69
+ container.rect(x: position.x, y: position.y, width: width, height: height, **)
70
+ end
71
+
72
+ def inside?(point)
73
+ onto?(point) || (left.right?(point) && right.left?(point) && top.left?(point) && bottom.right?(point))
74
+ end
75
+
76
+ # codebeat:disable[ABC]
77
+ def intersection(line, precision: nil)
78
+ points = sides.map { _1.intersection(line).approx(precision) }.uniq.select { onto?(_1) }
79
+ return if points.empty?
80
+
81
+ GeometryError.("Unexpected number of intersection points: #{points.size}") if points.size > 2
82
+
83
+ Segment.forward(*points)
84
+ end
85
+ # codebeat:enable[ABC]
86
+
87
+ def onto?(point)
88
+ left.onto?(point) || right.onto?(point) || top.onto?(point) || bottom.onto?(point)
89
+ end
90
+
91
+ def orient(new_orientation)
92
+ return self if orientation == new_orientation
93
+
94
+ with(width: height, height: width)
95
+ end
96
+
97
+ def orientation
98
+ F.gt?(width, height) ? :landscape : :portrait
99
+ end
100
+
101
+ def outside?(point)
102
+ !inside?(point)
103
+ end
104
+
105
+ def to_s
106
+ strings = []
107
+
108
+ strings << "R[#{F.approx(width)}x#{F.approx(height)}]"
109
+ strings << position.to_s if Point.eq?(position, Point.origin)
110
+
111
+ strings.join("@")
112
+ end
113
+
114
+ def translate(dx: nil, dy: nil)
115
+ self.class.new(position: position.translate(dx:, dy:), width: width, height: height)
116
+ end
117
+
118
+ private
119
+
120
+ def compute
121
+ @corner = Corner.of(self)
122
+ @corners = Corner.members.map { public_send(_1) }
123
+
124
+ @side = Side.of(self)
125
+ @sides = Side.members.map { public_send(_1) }
126
+ end
127
+
128
+ class << self
129
+ def [](width, height) = new(width:, height:)
130
+ end
131
+ end
132
+ end
133
+ end
@@ -0,0 +1,102 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "forwardable"
4
+
5
+ module Sevgi
6
+ module Geometry
7
+ class Segment < Element
8
+ extend Forwardable
9
+
10
+ def_delegators :line, :left?, :right?, :intersection
11
+
12
+ attr_reader :position, :ending
13
+
14
+ def initialize(position: Point.origin, ending:, &block)
15
+ super()
16
+
17
+ @position = position
18
+ @ending = ending
19
+
20
+ instance_exec(&block) if block # optimization backdoor for constructors
21
+ end
22
+
23
+ def bbox
24
+ BBox[position, ending]
25
+ end
26
+
27
+ def direction
28
+ @direction ||= F.angler(dx, dy)
29
+ end
30
+
31
+ def dx
32
+ @dx ||= F.dxp(position, ending)
33
+ end
34
+
35
+ def dy
36
+ @dy ||= F.dyp(position, ending)
37
+ end
38
+
39
+ def draw(container, **)
40
+ container.segment(x1: position.x, y1: position.y, x2: ending.x, y2: ending.y, **)
41
+ end
42
+
43
+ def horizontal?(precision = nil)
44
+ F.horizontal?(direction, precision:)
45
+ end
46
+
47
+ def infinite?
48
+ [position, ending].any(&:infinite?)
49
+ end
50
+
51
+ def length
52
+ @length ||= F.distance(position, ending)
53
+ end
54
+
55
+ def line
56
+ @line ||= Equation::Line.from_segment(self)
57
+ end
58
+
59
+ def onto?(point)
60
+ point.unordered_between?(ending, position) && line.onto?(point)
61
+ end
62
+
63
+ def rect
64
+ @rect ||= Rect[dx.abs, dy.abs]
65
+ end
66
+
67
+ def to_s
68
+ "S[#{position} -> #{ending}]"
69
+ end
70
+
71
+ def translate(dx: nil, dy: nil)
72
+ self.class.new(position: position.translate(dx:, dy:), ending: ending.translate(dx:, dy:))
73
+ end
74
+
75
+ def vertical?(precision = nil)
76
+ F.vertical?(direction, precision:)
77
+ end
78
+
79
+ class << self
80
+ def [](position, ending)
81
+ new(position:, ending:)
82
+ end
83
+
84
+ def forward(p, q = nil)
85
+ new(position: (points = [p, q || p]).sort!.shift, ending: points.shift)
86
+ end
87
+
88
+ def directed(position: Point.origin, length:, direction:)
89
+ dx = F.dxa(length, direction)
90
+ dy = F.dya(length, direction)
91
+
92
+ new(position: position, ending: position.translate(dx:, dy:)) do # avoid recalculations
93
+ @direction = direction.to_f
94
+ @length = length.to_f
95
+ @dx = dx
96
+ @dy = dy
97
+ end
98
+ end
99
+ end
100
+ end
101
+ end
102
+ end
@@ -0,0 +1,41 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Sevgi
4
+ module Geometry
5
+ class Element
6
+ def bbox = raise(NotImplementedError, "Instance method required: bbox")
7
+
8
+ def ignorable?(precision = nil) = bbox.zero?(precision)
9
+ end
10
+
11
+ class BBox
12
+ BBoxError = Class.new(GeometryError)
13
+
14
+ attr_reader :ne, :sw
15
+
16
+ def initialize(ne:, sw:) = (@ne, @sw = ne, sw)
17
+
18
+ def height = @height ||= F.height(ne, sw)
19
+
20
+ def to_s = "[#{width}x#{height}]@[#{ne} ↘ #{sw}]"
21
+
22
+ def width = @width ||= F.width(ne, sw)
23
+
24
+ def zero?(precision = nil) = Point.eq?(ne, sw, precision:)
25
+
26
+ private
27
+
28
+ def validate!
29
+ BBoxError.("North-East is not to the left of South-West: #{self}") if ne > sw
30
+ BBoxError.("North-East is not below South-West: #{self}") unless ne.below?(sw)
31
+ end
32
+
33
+ class << self
34
+ def [](*points) = new(ne: points.min, sw: points.max)
35
+ end
36
+ end
37
+ end
38
+ end
39
+
40
+ require_relative "elements/segment"
41
+ require_relative "elements/rect"
@@ -0,0 +1,90 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Sevgi
4
+ module Geometry
5
+ module Equation
6
+ module Line
7
+ class Diagonal
8
+ attr_reader :slope, :intercept, :direction
9
+
10
+ def initialize(slope:, intercept:)
11
+ @slope = slope.to_f
12
+ @intercept = intercept.to_f
13
+ @direction = F.angles(@slope)
14
+ end
15
+
16
+ def approx(precision = nil)
17
+ self.class.new(slope: F.approx(slope, precision), intercept: F.approx(intercept, precision))
18
+ end
19
+
20
+ def eql?(other)
21
+ self.class == other.class && [slope, intercept] == [other.slope, other.intercept]
22
+ end
23
+
24
+ alias_method :==, :eql?
25
+
26
+ def hash
27
+ [self.class, slope, intercept].hash
28
+ end
29
+
30
+ def intersection(other)
31
+ case other
32
+ when Diagonal
33
+ x = (other.intercept - intercept) / (slope - other.slope)
34
+ y = y(x)
35
+ when Horizontal
36
+ y = other.y
37
+ x = x(y)
38
+ when Vertical
39
+ x = other.x
40
+ y = y(x)
41
+ end
42
+
43
+ Point[x, y]
44
+ end
45
+
46
+ def left?(point)
47
+ F.gt?(point.y, y(point.x))
48
+ end
49
+
50
+ def onto?(point)
51
+ F.eq?(point.y, y(point.x))
52
+ end
53
+
54
+ def right?(point)
55
+ F.lt?(point.y, y(point.x))
56
+ end
57
+
58
+ def shift(distance = nil, dx: nil, dy: nil)
59
+ dx ||= 0.0
60
+ dy ||= 0.0
61
+
62
+ if distance
63
+ dx += F.rx(distance, direction)
64
+ dy -= F.ry(distance, direction)
65
+ end
66
+
67
+ Diagonal.new(slope:, intercept: intercept - slope * dx + dy)
68
+ end
69
+
70
+ def to_s
71
+ strings = []
72
+
73
+ strings << "#{F.approx(slope)} * x" if F.nonzero?(slope)
74
+ strings << F.approx(intercept).abs.to_s if F.nonzero?(intercept)
75
+
76
+ "L<y = #{strings.join(intercept.positive? ? " + " : " - ")}>"
77
+ end
78
+
79
+ def x(y)
80
+ (y - intercept) / slope
81
+ end
82
+
83
+ def y(x)
84
+ (slope * x) + intercept
85
+ end
86
+ end
87
+ end
88
+ end
89
+ end
90
+ end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Sevgi
4
+ module Geometry
5
+ module Equation
6
+ module Line
7
+ class Horizontal < Diagonal
8
+ def initialize(c)
9
+ super(slope: 0, intercept: c)
10
+ end
11
+ end
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,52 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Sevgi
4
+ module Geometry
5
+ module Equation
6
+ module Line
7
+ class Vertical
8
+ def initialize(c)
9
+ @x = c
10
+ end
11
+
12
+ def intersection(other)
13
+ case other
14
+ when Diagonal, Horizontal then x, y = self.x, other.y(self.x)
15
+ when Vertical then x, y = ::Float::INFINITY, ::Float::INFINITY
16
+ end
17
+
18
+ Point[x, y]
19
+ end
20
+
21
+ def left?(point)
22
+ F.lt?(point.x, x(point.y))
23
+ end
24
+
25
+ def onto?(point)
26
+ F.eq?(point.x, x(point.y))
27
+ end
28
+
29
+ def right?(point)
30
+ F.gt?(point.x, x(point.y))
31
+ end
32
+
33
+ def shift(distance = nil, dx: nil, dy: nil)
34
+ self.class.new(x + (distance || 0.0) + (dx || 0.0))
35
+ end
36
+
37
+ def to_s
38
+ "L<x = #{F.approx(x)}>"
39
+ end
40
+
41
+ def x(_ = nil)
42
+ @x
43
+ end
44
+
45
+ def y(_ = nil)
46
+ ::Float::INFINITY
47
+ end
48
+ end
49
+ end
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,38 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "line/diagonal"
4
+ require_relative "line/horizontal"
5
+ require_relative "line/vertical"
6
+
7
+ module Sevgi
8
+ module Geometry
9
+ module Equation
10
+ module Line
11
+ extend self
12
+
13
+ def diagonal(slope:, intercept:)
14
+ Diagonal.new(slope:, intercept:)
15
+ end
16
+
17
+ def from_direction(point:, direction:)
18
+ return horizontal(point.y) if F.horizontal?(direction)
19
+ return vertical(point.x) if F.vertical?(direction)
20
+
21
+ diagonal(slope: (slope = F.slopea(direction)), intercept: F.intercept(point, direction, slope))
22
+ end
23
+
24
+ def from_segment(segment)
25
+ from_direction(point: segment.position, direction: segment.direction)
26
+ end
27
+
28
+ def horizontal(const)
29
+ Horizontal.new(const)
30
+ end
31
+
32
+ def vertical(const)
33
+ Vertical.new(const)
34
+ end
35
+ end
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,3 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "equation/line"
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Sevgi
4
+ GeometryError = Class.new(Error)
5
+ end