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,173 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'forwardable'
4
+
5
+ module Dieses
6
+ module Geometry
7
+ # codebeat:disable[TOO_MANY_IVARS]
8
+ class Rect < Element
9
+ Side = Struct.new(*%i[top right bottom left], keyword_init: true) # in CSS margin order
10
+ Corner = Struct.new(*%i[bottom_left top_left top_right bottom_right], keyword_init: true) # cw from position
11
+
12
+ private_constant :Side
13
+ private_constant :Corner
14
+
15
+ extend Forwardable
16
+
17
+ def_delegators :@side, *Side.members
18
+ def_delegators :@corner, *Corner.members
19
+
20
+ attr_reader :width, :height, :position
21
+
22
+ def initialize(width, height, position: Point.origin)
23
+ @width = width.to_f
24
+ @height = height.to_f
25
+ @position = Point.cast(position)
26
+ @corner = calculate_corner
27
+ @side = calculate_side
28
+
29
+ super()
30
+ end
31
+
32
+ def translate(x: 0, y: 0)
33
+ self.class.new width, height, position: position.translate(x: x, y: y)
34
+ end
35
+
36
+ def shrink(width: 0, height: 0)
37
+ self.class.new self.width - width, self.height - height, position: position
38
+ end
39
+
40
+ module Align
41
+ module_function
42
+
43
+ def center(this, that)
44
+ Point.new(this.position.x + (this.width - that.width) / 2,
45
+ this.position.y + (this.height - that.height) / 2)
46
+ end
47
+
48
+ def left(this, that)
49
+ Point.new(this.position.x, that.position.y)
50
+ end
51
+
52
+ def right(this, that)
53
+ Point.new(this.position.x + (this.width - that.width), that.position.y)
54
+ end
55
+
56
+ def top(this, that)
57
+ Point.new(that.position.x, this.position.y + (this.height - that.height))
58
+ end
59
+
60
+ def bottom(this, that)
61
+ Point.new(that.position.x, this.position.y)
62
+ end
63
+ end
64
+
65
+ private_constant :Align
66
+
67
+ def align(other, alignment = :center)
68
+ raise ArgumentError, "No such alignment type: #{alignment}" unless Align.respond_to? alignment
69
+
70
+ self.class.new other.width, other.height, position: Align.public_send(alignment, self, other).approx
71
+ end
72
+
73
+ Orientation = Support::Enum.of(:portrait, :landscape)
74
+
75
+ def orientation
76
+ value = if Support.almost_greater_than(width, height)
77
+ :landscape
78
+ else
79
+ :portrait
80
+ end
81
+ Orientation.(value)
82
+ end
83
+
84
+ def orient(new_orientation)
85
+ new_orientation = Orientation.(new_orientation)
86
+ return self if orientation == new_orientation
87
+
88
+ self.class.new height, width, position: position
89
+ end
90
+
91
+ module Predicate
92
+ def inside?(point)
93
+ onto?(point) || (
94
+ left.right?(point) && right.left?(point) && top.left?(point) && bottom.right?(point)
95
+ )
96
+ end
97
+
98
+ def outside?(point)
99
+ !inside?(point)
100
+ end
101
+
102
+ def onto?(point)
103
+ left.onto?(point) || right.onto?(point) || top.onto?(point) || bottom.onto?(point)
104
+ end
105
+
106
+ def cover?(element)
107
+ bbox = element.bbox
108
+ inside?(bbox.minimum) && inside?(bbox.maximum)
109
+ end
110
+ end
111
+
112
+ include Predicate
113
+
114
+ # codebeat:disable[LOC,ABC]
115
+ def intersect(equation, precision: nil)
116
+ points = Side.members
117
+ .map { |line| public_send(line).intersect(equation).approx(precision) }
118
+ .uniq
119
+ .select { |intersect| onto?(intersect) }
120
+ .sort
121
+
122
+ return if points.empty?
123
+
124
+ raise "Unexpected number of intersection points: #{points.size}" unless points.size <= 2
125
+
126
+ Line.new((starting = points.shift), (points.shift || starting))
127
+ end
128
+ # codebeat:enable[LOC,ABC]
129
+
130
+ def angle
131
+ Math.atan(height / width)
132
+ end
133
+
134
+ def bbox
135
+ BoundingBox.new(@corner.values.min, @corner.values.max)
136
+ end
137
+
138
+ def to_s
139
+ repr = "R(#{width}, #{height})"
140
+ return repr if position == Point.origin
141
+
142
+ "#{repr}@#{position}"
143
+ end
144
+
145
+ def to_h
146
+ { width: width, height: height, **position.to_h }
147
+ end
148
+
149
+ # codebeat:disable[ABC]
150
+ def to_svgf
151
+ <<~SVG
152
+ <rect width="#{Support.approx(width)}" height="#{Support.approx(height)}" x="#{Support.approx(position.x)}" y="#{Support.approx(position.y)}" %{attributes}/>
153
+ SVG
154
+ end
155
+
156
+ private
157
+
158
+ def calculate_corner
159
+ Corner.new(top_left: position,
160
+ top_right: Point.new(position.x + width, position.y),
161
+ bottom_right: Point.new(position.x + width, position.y + height),
162
+ bottom_left: Point.new(position.x, position.y + height))
163
+ end
164
+
165
+ def calculate_side
166
+ Side.new(top: Line.new(@corner.top_left, @corner.top_right),
167
+ right: Line.new(@corner.top_right, @corner.bottom_right),
168
+ bottom: Line.new(@corner.bottom_left, @corner.bottom_right),
169
+ left: Line.new(@corner.top_left, @corner.bottom_left))
170
+ end
171
+ end
172
+ end
173
+ end
@@ -0,0 +1,3 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'diesis/support'
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'support/const'
4
+ require_relative 'support/kernel'
5
+ require_relative 'support/class'
6
+ require_relative 'support/enum'
7
+ require_relative 'support/float'
8
+ require_relative 'support/math'
9
+ require_relative 'support/hash'
@@ -0,0 +1,100 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Dieses
4
+ module Support
5
+ # Stolen and improved from dry-rb/dry-core
6
+ module ClassAttribute
7
+ module Value
8
+ Update = Object.new.tap do |object|
9
+ def object.call(current_value, new_value)
10
+ raise ArgumentError, "Value must be updateable: #{new_value}" unless new_value.respond_to? :[]
11
+
12
+ current_value.tap { new_value.each { |k, v| current_value[k.to_sym] = v } }
13
+ end
14
+ end.freeze
15
+
16
+ Append = Object.new.tap do |object|
17
+ def object.call(current_value, new_value)
18
+ raise ArgumentError, "Value must be appendable: #{new_value}" unless new_value.respond_to? :<<
19
+
20
+ current_value.tap { new_value.each { |v| current_value << v } }
21
+ end
22
+ end.freeze
23
+
24
+ Assign = Object.new.tap do |object|
25
+ def object.call(_, new_value)
26
+ new_value.dup
27
+ end
28
+ end.freeze
29
+
30
+ class << self
31
+ def behave(behave, value = Undefined)
32
+ Undefined.equal?(behave) ? implicit(value) : explicit(behave)
33
+ end
34
+
35
+ private
36
+
37
+ # Map given symbol to relevant module
38
+ def explicit(behave)
39
+ const_get behave.to_s.capitalize
40
+ rescue NameError
41
+ raise ArgumentError, "Unrecognized behave: #{behave}"
42
+ end
43
+
44
+ # Deduce semantics from a value
45
+ def implicit(value)
46
+ require 'ostruct'
47
+ require 'set'
48
+
49
+ case value
50
+ when ::Hash, ::Struct, ::OpenStruct then Update
51
+ when ::Array, ::Set then Append
52
+ else Assign
53
+ end
54
+ end
55
+ end
56
+ end
57
+
58
+ private_constant :Value
59
+
60
+ # rubocop:disable Metrics/MethodLength,Layout/LineLength,Lint/RedundantCopDisableDirective
61
+ def define(name, default: Undefined, behave: Undefined, inherit: true, instance_reader: false)
62
+ ivar = :"@#{name}"
63
+ behave = Value.behave(behave, default)
64
+
65
+ instance_variable_set(ivar, default.dup)
66
+
67
+ mod = ::Module.new do
68
+ define_method(name) do |new_value = Undefined|
69
+ if Undefined.equal?(new_value)
70
+ return instance_variable_defined?(ivar) ? instance_variable_get(ivar) : nil
71
+ end
72
+
73
+ instance_variable_set(
74
+ ivar,
75
+ behave.(
76
+ instance_variable_defined?(ivar) ? instance_variable_get(ivar) : instance_variable_set(ivar, default.dup),
77
+ new_value
78
+ )
79
+ )
80
+ end
81
+
82
+ define_method(:inherited) do |klass|
83
+ klass.send(name, (inherit ? send(name) : default).dup)
84
+
85
+ super(klass)
86
+ end
87
+ end
88
+
89
+ extend(mod)
90
+
91
+ define_method(name) { self.class.send(name) } if instance_reader
92
+ end
93
+ # rubocop:enable Metrics/MethodLength,Layout/LineLength,Lint/RedundantCopDisableDirective
94
+
95
+ def defines(*names, behave: Undefined)
96
+ names.each { |name| define(name, behave: behave) }
97
+ end
98
+ end
99
+ end
100
+ end
@@ -0,0 +1,78 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Dieses
4
+ module Support
5
+ module Const
6
+ # Copied from https://github.com/dry-rb/dry-core. All kudos to the original authors.
7
+
8
+ require 'set'
9
+
10
+ # An empty array
11
+ EMPTY_ARRAY = [].freeze
12
+ # An empty hash
13
+ EMPTY_HASH = {}.freeze
14
+ # An empty list of options
15
+ EMPTY_OPTS = {}.freeze
16
+ # An empty set
17
+ EMPTY_SET = ::Set.new.freeze
18
+ # An empty string
19
+ EMPTY_STRING = ''
20
+ # Identity function
21
+ IDENTITY = (->(x) { x }).freeze
22
+
23
+ Undefined = Object.new.tap do |undefined| # rubocop:disable Metrics/BlockLength
24
+ const_set(:Self, -> { Undefined })
25
+
26
+ def undefined.to_s
27
+ 'Undefined'
28
+ end
29
+
30
+ def undefined.inspect
31
+ 'Undefined'
32
+ end
33
+
34
+ def undefined.default(x, y = self)
35
+ if equal?(x)
36
+ if equal?(y)
37
+ yield
38
+ else
39
+ y
40
+ end
41
+ else
42
+ x
43
+ end
44
+ end
45
+
46
+ def undefined.map(value)
47
+ if equal?(value)
48
+ self
49
+ else
50
+ yield(value)
51
+ end
52
+ end
53
+
54
+ def undefined.dup
55
+ self
56
+ end
57
+
58
+ def undefined.clone
59
+ self
60
+ end
61
+
62
+ def undefined.coalesce(*args)
63
+ args.find(Self) { |x| !equal?(x) }
64
+ end
65
+ end.freeze
66
+
67
+ def self.included(base)
68
+ super
69
+
70
+ constants.each do |const_name|
71
+ base.const_set(const_name, const_get(const_name))
72
+ end
73
+ end
74
+ end
75
+ end
76
+
77
+ include Support::Const
78
+ end
@@ -0,0 +1,63 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Dieses
4
+ module Support
5
+ class Enum
6
+ extend ClassAttribute
7
+
8
+ define :values, behave: :assign, instance_reader: true
9
+
10
+ attr_reader :value
11
+
12
+ def initialize(value)
13
+ self.value = value
14
+ end
15
+
16
+ def value=(value)
17
+ raise ArgumentError, "Invalid enum value: #{value}" unless self.class.values.member?(value = value.to_sym)
18
+
19
+ @value = value
20
+ end
21
+
22
+ def eql?(other)
23
+ return false unless other.is_a? self.class
24
+
25
+ value == other.value
26
+ end
27
+
28
+ alias == eql?
29
+
30
+ def hash
31
+ self.class.hash ^ values.hash ^ value.hash
32
+ end
33
+
34
+ def to_s
35
+ value.to_s
36
+ end
37
+
38
+ class << self
39
+ require 'set'
40
+
41
+ def of(*members) # rubocop:disable Metrics/MethodLength
42
+ values Set.new(members.map(&:to_sym)).freeze
43
+
44
+ members.each do |member|
45
+ define_method("#{member}?") do
46
+ value == member
47
+ end
48
+ end
49
+
50
+ Class.new(self) do
51
+ def self.call(value)
52
+ new(value)
53
+ end
54
+
55
+ define_singleton_method(:default) { members.first }
56
+ end
57
+ end
58
+ end
59
+
60
+ private_class_method :new
61
+ end
62
+ end
63
+ end
@@ -0,0 +1,43 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Dieses
4
+ module Support
5
+ module Float
6
+ @precision = PRECISION = 5
7
+
8
+ class << self
9
+ attr_accessor :precision
10
+ end
11
+
12
+ def round(float, precision)
13
+ precision ? float.round(precision) : float
14
+ end
15
+
16
+ def approx(float, precision = nil)
17
+ float.round(precision || Float.precision)
18
+ end
19
+
20
+ def almost_equal(left, right, precision: Float.precision)
21
+ round(left, precision) == round(right, precision)
22
+ end
23
+
24
+ def almost_less_or_equal(left, right, precision: Float.precision)
25
+ round(left, precision) <= round(right, precision)
26
+ end
27
+
28
+ def almost_greater_or_equal(left, right, precision: Float.precision)
29
+ round(left, precision) >= round(right, precision)
30
+ end
31
+
32
+ def almost_less_than(left, right, precision: Float.precision)
33
+ round(left, precision) < round(right, precision)
34
+ end
35
+
36
+ def almost_greater_than(left, right, precision: Float.precision)
37
+ round(left, precision) > round(right, precision)
38
+ end
39
+ end
40
+
41
+ extend Float
42
+ end
43
+ end