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
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