dieses 0.0.1

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 (55) hide show
  1. checksums.yaml +7 -0
  2. data/BEN/304/260OKU.md +0 -0
  3. data/CHANGELOG.md +14 -0
  4. data/LICENSE.md +675 -0
  5. data/README.md +18 -0
  6. data/bin/dieses +10 -0
  7. data/bin/diesis +10 -0
  8. data/dieses.gemspec +36 -0
  9. data/lib/dieses.rb +7 -0
  10. data/lib/dieses/application.rb +24 -0
  11. data/lib/dieses/application/batch.rb +127 -0
  12. data/lib/dieses/application/canvas.rb +51 -0
  13. data/lib/dieses/application/cli.rb +6 -0
  14. data/lib/dieses/application/cli/multi.rb +106 -0
  15. data/lib/dieses/application/cli/single.rb +100 -0
  16. data/lib/dieses/application/common.rb +27 -0
  17. data/lib/dieses/application/mixins.rb +5 -0
  18. data/lib/dieses/application/mixins/lines.rb +105 -0
  19. data/lib/dieses/application/mixins/scribes.rb +91 -0
  20. data/lib/dieses/application/mixins/squares.rb +23 -0
  21. data/lib/dieses/application/paper.rb +146 -0
  22. data/lib/dieses/application/pen.rb +161 -0
  23. data/lib/dieses/application/sheet.rb +111 -0
  24. data/lib/dieses/application/sheets.rb +58 -0
  25. data/lib/dieses/application/sheets/copperplate.rb +20 -0
  26. data/lib/dieses/application/sheets/cursive.rb +19 -0
  27. data/lib/dieses/application/sheets/graph.rb +27 -0
  28. data/lib/dieses/application/sheets/italics.rb +19 -0
  29. data/lib/dieses/application/sheets/lettering.rb +30 -0
  30. data/lib/dieses/application/sheets/lined.rb +24 -0
  31. data/lib/dieses/application/sheets/print.rb +19 -0
  32. data/lib/dieses/application/sheets/ruled.rb +25 -0
  33. data/lib/dieses/application/sheets/spencerian.rb +20 -0
  34. data/lib/dieses/application/sheets/thumbnail.rb +37 -0
  35. data/lib/dieses/error.rb +5 -0
  36. data/lib/dieses/geometry.rb +8 -0
  37. data/lib/dieses/geometry/element.rb +66 -0
  38. data/lib/dieses/geometry/equation.rb +57 -0
  39. data/lib/dieses/geometry/equation/slant.rb +88 -0
  40. data/lib/dieses/geometry/equation/steep.rb +70 -0
  41. data/lib/dieses/geometry/error.rb +7 -0
  42. data/lib/dieses/geometry/line.rb +67 -0
  43. data/lib/dieses/geometry/point.rb +98 -0
  44. data/lib/dieses/geometry/rect.rb +173 -0
  45. data/lib/dieses/geometry/support.rb +3 -0
  46. data/lib/dieses/support.rb +9 -0
  47. data/lib/dieses/support/class.rb +100 -0
  48. data/lib/dieses/support/const.rb +78 -0
  49. data/lib/dieses/support/enum.rb +63 -0
  50. data/lib/dieses/support/float.rb +43 -0
  51. data/lib/dieses/support/hash.rb +19 -0
  52. data/lib/dieses/support/kernel.rb +36 -0
  53. data/lib/dieses/support/math.rb +20 -0
  54. data/lib/dieses/version.rb +5 -0
  55. metadata +226 -0
@@ -0,0 +1,66 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Dieses
4
+ module Geometry
5
+ class Element
6
+ BoundingBox = Struct.new :minimum, :maximum
7
+
8
+ attr_reader :attributes, :hash
9
+
10
+ def initialize
11
+ @attributes = {}
12
+ @hash = self.class.hash ^ to_h.hash
13
+ freeze
14
+ end
15
+
16
+ def attr(**kwargs)
17
+ tap do
18
+ kwargs.each { |key, value| attributes[key.to_sym] = value }
19
+ end
20
+ end
21
+
22
+ def classify(*tags, **kwargs)
23
+ existing_class = attributes[:class] || Set.new
24
+ attr(**kwargs, tags: existing_class.add(tags))
25
+ end
26
+
27
+ def to_svg
28
+ format to_svgf, attributes: Support.kwargs_to_s(**attributes)
29
+ end
30
+
31
+ def eql?(other)
32
+ return false unless other.is_a? self.class
33
+
34
+ to_h == other.to_h
35
+ end
36
+
37
+ alias == eql?
38
+ end
39
+
40
+ class << self
41
+ def centered(elements, rect)
42
+ bbox = bounding_box_of(*elements)
43
+
44
+ x = (rect.width - bbox.maximum.x + bbox.minimum.x) / 2
45
+ y = (rect.height - bbox.maximum.y + bbox.minimum.y) / 2
46
+
47
+ elements.map { |element| element.translate(x: x, y: y).attr(**element.attributes.dup) }
48
+ end
49
+
50
+ def to_svg(elements, rect = Undefined, prefix: EMPTY_STRING)
51
+ (Undefined.equal?(rect) ? elements : centered(elements, rect)).map do |element|
52
+ "#{prefix}#{element.to_svg}"
53
+ end.join.chomp
54
+ end
55
+
56
+ def bounding_box_of(*elements)
57
+ bboxes = elements.map(&:bbox)
58
+
59
+ minimum = bboxes.map(&:minimum).min
60
+ maximum = bboxes.map(&:maximum).max
61
+
62
+ Element::BoundingBox.new minimum, maximum
63
+ end
64
+ end
65
+ end
66
+ end
@@ -0,0 +1,57 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'equation/slant'
4
+ require_relative 'equation/steep'
5
+
6
+ module Dieses
7
+ module Geometry
8
+ module Equation
9
+ module_function
10
+
11
+ def from_line(line)
12
+ starting, ending = line.starting, line.ending
13
+
14
+ if (c = starting.x) == ending.x
15
+ vertical(c)
16
+ elsif (c = starting.y) == ending.y
17
+ horizontal(c)
18
+ else
19
+ slope = (ending.y - starting.y) / (ending.x - starting.x)
20
+ intercept = starting.y - slope * starting.x
21
+
22
+ slant(slope: slope, intercept: intercept)
23
+ end
24
+ end
25
+
26
+ def slant(slope:, intercept:)
27
+ Slant.new slope: slope, intercept: intercept
28
+ end
29
+
30
+ def slant_from_direction(point:, angle:)
31
+ return horizontal(point.y) if (angle % 180).zero?
32
+ return vertical(point.x) if (angle % 90).zero?
33
+
34
+ slope = Math.tan(Support.to_radian(angle.to_f))
35
+ intercept = point.y - slope * point.x
36
+
37
+ slant(slope: slope, intercept: intercept)
38
+ end
39
+
40
+ def steep(c)
41
+ Steep.new c
42
+ end
43
+
44
+ class << self
45
+ alias vertical steep
46
+ end
47
+
48
+ def horizontal(c)
49
+ slant(slope: 0.0, intercept: c)
50
+ end
51
+
52
+ def intersect(u, v)
53
+ u.intersect(v)
54
+ end
55
+ end
56
+ end
57
+ end
@@ -0,0 +1,88 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Dieses
4
+ module Geometry
5
+ module Equation
6
+ class Slant
7
+ attr_reader :slope, :intercept
8
+
9
+ def initialize(slope:, intercept:)
10
+ @slope, @intercept = slope.to_f, intercept.to_f
11
+ freeze
12
+ end
13
+
14
+ def x(y)
15
+ (y - intercept) / slope
16
+ end
17
+
18
+ def y(x)
19
+ slope * x + intercept
20
+ end
21
+
22
+ # When distance given, y = m * x + n (m, n positive) equation moves to the right (x increases, y decreases)
23
+ def translate(distance = nil, x: nil, y: nil)
24
+ dx, dy = 0, 0
25
+
26
+ intercept = self.intercept
27
+
28
+ dx, dy = distance * Math.cos(angle_in_radian), -distance * Math.sin(angle_in_radian) if distance
29
+
30
+ dx += x if x
31
+ dy += y if y
32
+
33
+ intercept -= slope * dx
34
+ intercept += dy
35
+
36
+ self.class.new slope: slope, intercept: intercept
37
+ end
38
+
39
+ def angle_in_radian
40
+ Math.atan(slope)
41
+ end
42
+
43
+ def intersect(other)
44
+ case other
45
+ when Slant
46
+ x = (other.intercept - intercept) / (slope - other.slope)
47
+ y = slope * x + intercept
48
+ when Steep
49
+ x = other.x
50
+ y = y(x)
51
+ end
52
+
53
+ Point.new(x, y)
54
+ end
55
+
56
+ def left?(point, precision: Support::Float.precision)
57
+ Support.almost_greater_than(point.y, y(point.x), precision: precision)
58
+ end
59
+
60
+ def right?(point, precision: Support::Float.precision)
61
+ Support.almost_less_than(point.y, y(point.x), precision: precision)
62
+ end
63
+
64
+ def onto?(point, precision: Support::Float.precision)
65
+ Support.almost_equal(point.y, y(point.x), precision: precision)
66
+ end
67
+
68
+ def eql?(other)
69
+ self.class == other.class && to_h == other.to_h
70
+ end
71
+
72
+ alias == eql?
73
+
74
+ def hash
75
+ self.class.hash ^ to_h.hash
76
+ end
77
+
78
+ def to_h
79
+ { slope: scope, intercept: intercept }
80
+ end
81
+
82
+ def to_s
83
+ "E(y = #{slope} * x + #{intercept})"
84
+ end
85
+ end
86
+ end
87
+ end
88
+ end
@@ -0,0 +1,70 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Dieses
4
+ module Geometry
5
+ module Equation
6
+ class Steep
7
+ def initialize(c)
8
+ @x = c
9
+ end
10
+
11
+ def y(_ = nil)
12
+ Float::INFINITY
13
+ end
14
+
15
+ def x(_ = nil)
16
+ @x
17
+ end
18
+
19
+ # rubocop:disable Lint/UnusedMethodArgument
20
+ def translate(distance = nil, x: nil, y: nil)
21
+ self.class.new self.x + (distance || 0.0) + (x || 0.0)
22
+ end
23
+ # rubocop:enable Lint/UnusedMethodArgument
24
+
25
+ def intersect(other)
26
+ case other
27
+ when Slant
28
+ x = self.x
29
+ y = other.y(x)
30
+ when Steep
31
+ x = Float::INFINITY
32
+ y = Float::INFINITY
33
+ end
34
+
35
+ Point.new(x, y)
36
+ end
37
+
38
+ def left?(point, precision: Support::Float.precision)
39
+ Support.almost_less_than(point.x, x(point.y), precision: precision)
40
+ end
41
+
42
+ def right?(point, precision: Support::Float.precision)
43
+ Support.almost_greater_than(point.x, x(point.y), precision: precision)
44
+ end
45
+
46
+ def onto?(point, precision: Support::Float.precision)
47
+ Support.almost_equal(point.x, x(point.y), precision: precision)
48
+ end
49
+
50
+ def eql?(other)
51
+ self.class == other.class && x == other.x
52
+ end
53
+
54
+ alias == eql?
55
+
56
+ def hash
57
+ self.class.hash ^ to_h.hash
58
+ end
59
+
60
+ def to_h
61
+ { x: x }
62
+ end
63
+
64
+ def to_s
65
+ "E(x = #{c})"
66
+ end
67
+ end
68
+ end
69
+ end
70
+ end
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Dieses
4
+ module Geometry
5
+ Error = Class.new Error
6
+ end
7
+ end
@@ -0,0 +1,67 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'forwardable'
4
+
5
+ module Dieses
6
+ module Geometry
7
+ class Line < Element
8
+ extend Forwardable
9
+
10
+ def_delegators :@equation, :left?, :right?, :intersect
11
+
12
+ attr_reader :starting, :ending, :equation
13
+
14
+ def initialize(starting, ending)
15
+ @starting, @ending = Point.cast(starting), Point.cast(ending)
16
+ @equation = Equation.from_line(self)
17
+
18
+ super()
19
+ end
20
+
21
+ def translate(x: nil, y: nil)
22
+ starting, ending = [self.starting, self.ending].map { |point| point.translate(x: x, y: y) }
23
+ self.class.new starting, ending
24
+ end
25
+
26
+ def duplicate(x: nil, y: nil, count: 1)
27
+ lines, line = [], self
28
+
29
+ count.times do
30
+ lines << line
31
+ line = line.translate(x: x, y: y)
32
+ end
33
+
34
+ [lines, line]
35
+ end
36
+
37
+ def duplicates(x: nil, y: nil, count: 1)
38
+ lines, = duplicate(x: x, y: y, count: count)
39
+ lines
40
+ end
41
+
42
+ def onto?(point)
43
+ equation.onto?(point) && point >= starting && point <= ending
44
+ end
45
+
46
+ def to_svgf
47
+ x1, y1, x2, y2 = [*starting.to_a, *ending.to_a].map { |value| Support.approx(value) }
48
+
49
+ <<~SVG
50
+ <line x1="#{x1}" y1="#{y1}" x2="#{x2}" y2="#{y2}" %{attributes}/>
51
+ SVG
52
+ end
53
+
54
+ def to_s
55
+ "L(#{starting}, #{ending})"
56
+ end
57
+
58
+ def to_h
59
+ { starting: starting, ending: ending }
60
+ end
61
+
62
+ def bbox
63
+ BoundingBox.new([starting, ending].min, [starting, ending].max)
64
+ end
65
+ end
66
+ end
67
+ end
@@ -0,0 +1,98 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Dieses
4
+ module Geometry
5
+ class Point
6
+ include Comparable
7
+
8
+ attr_reader :x, :y, :hash
9
+
10
+ def initialize(x, y)
11
+ @x, @y = x.to_f, y.to_f
12
+ @hash = Point.hash ^ to_a.hash
13
+
14
+ freeze
15
+ end
16
+
17
+ def translate(x: nil, y: nil)
18
+ self.class.new(self.x + (x || 0), self.y + (y || 0))
19
+ end
20
+
21
+ def distance(other = nil)
22
+ self.class.distance(self, other)
23
+ end
24
+
25
+ def approx(precision = nil)
26
+ self.class.new Support.approx(x, precision), Support.approx(y, precision)
27
+ end
28
+
29
+ def <=>(other)
30
+ return unless other.is_a? Point
31
+
32
+ to_a <=> other.to_a
33
+ end
34
+
35
+ def eql?(other)
36
+ return false unless other.is_a? Point
37
+
38
+ to_a == other.to_a
39
+ end
40
+
41
+ alias == eql?
42
+
43
+ def to_s
44
+ "P(#{x}, #{y})"
45
+ end
46
+
47
+ def to_a
48
+ [x, y]
49
+ end
50
+
51
+ def to_h
52
+ { x: x, y: y }
53
+ end
54
+
55
+ class << self
56
+ def call(*args)
57
+ new(*args)
58
+ end
59
+
60
+ def origin
61
+ new 0.0, 0.0
62
+ end
63
+
64
+ def distance(starting, ending)
65
+ ending ||= origin
66
+ Math.sqrt((ending.x - starting.x)**2 + (starting.y - ending.y)**2)
67
+ end
68
+
69
+ def cast(point)
70
+ Point.new(point.x, point.y)
71
+ end
72
+ end
73
+
74
+ class Mutable < Point
75
+ attr_writer :x, :y
76
+
77
+ def initialize(x, y) # rubocop:disable Lint/MissingSuper
78
+ @x, @y = x.to_f, y.to_f
79
+ end
80
+
81
+ def hash
82
+ (@hash ||= self.class.hash) ^ to_a.hash
83
+ end
84
+
85
+ def translate!(x: nil, y: nil)
86
+ tap do
87
+ self.x += (x || 0)
88
+ self.y += (y || 0)
89
+ end
90
+ end
91
+
92
+ def self.cast(immutable_point)
93
+ Mutable.new(immutable_point.x, immutable_point.y)
94
+ end
95
+ end
96
+ end
97
+ end
98
+ end