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.
- checksums.yaml +7 -0
- data/BEN/304/260OKU.md +0 -0
- data/CHANGELOG.md +14 -0
- data/LICENSE.md +675 -0
- data/README.md +18 -0
- data/bin/dieses +10 -0
- data/bin/diesis +10 -0
- data/dieses.gemspec +36 -0
- data/lib/dieses.rb +7 -0
- data/lib/dieses/application.rb +24 -0
- data/lib/dieses/application/batch.rb +127 -0
- data/lib/dieses/application/canvas.rb +51 -0
- data/lib/dieses/application/cli.rb +6 -0
- data/lib/dieses/application/cli/multi.rb +106 -0
- data/lib/dieses/application/cli/single.rb +100 -0
- data/lib/dieses/application/common.rb +27 -0
- data/lib/dieses/application/mixins.rb +5 -0
- data/lib/dieses/application/mixins/lines.rb +105 -0
- data/lib/dieses/application/mixins/scribes.rb +91 -0
- data/lib/dieses/application/mixins/squares.rb +23 -0
- data/lib/dieses/application/paper.rb +146 -0
- data/lib/dieses/application/pen.rb +161 -0
- data/lib/dieses/application/sheet.rb +111 -0
- data/lib/dieses/application/sheets.rb +58 -0
- data/lib/dieses/application/sheets/copperplate.rb +20 -0
- data/lib/dieses/application/sheets/cursive.rb +19 -0
- data/lib/dieses/application/sheets/graph.rb +27 -0
- data/lib/dieses/application/sheets/italics.rb +19 -0
- data/lib/dieses/application/sheets/lettering.rb +30 -0
- data/lib/dieses/application/sheets/lined.rb +24 -0
- data/lib/dieses/application/sheets/print.rb +19 -0
- data/lib/dieses/application/sheets/ruled.rb +25 -0
- data/lib/dieses/application/sheets/spencerian.rb +20 -0
- data/lib/dieses/application/sheets/thumbnail.rb +37 -0
- data/lib/dieses/error.rb +5 -0
- data/lib/dieses/geometry.rb +8 -0
- data/lib/dieses/geometry/element.rb +66 -0
- data/lib/dieses/geometry/equation.rb +57 -0
- data/lib/dieses/geometry/equation/slant.rb +88 -0
- data/lib/dieses/geometry/equation/steep.rb +70 -0
- data/lib/dieses/geometry/error.rb +7 -0
- data/lib/dieses/geometry/line.rb +67 -0
- data/lib/dieses/geometry/point.rb +98 -0
- data/lib/dieses/geometry/rect.rb +173 -0
- data/lib/dieses/geometry/support.rb +3 -0
- data/lib/dieses/support.rb +9 -0
- data/lib/dieses/support/class.rb +100 -0
- data/lib/dieses/support/const.rb +78 -0
- data/lib/dieses/support/enum.rb +63 -0
- data/lib/dieses/support/float.rb +43 -0
- data/lib/dieses/support/hash.rb +19 -0
- data/lib/dieses/support/kernel.rb +36 -0
- data/lib/dieses/support/math.rb +20 -0
- data/lib/dieses/version.rb +5 -0
- 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,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
|