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,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'ostruct'
4
+
5
+ module Dieses
6
+ module Application
7
+ Error = Class.new Error
8
+
9
+ NotImplementedError = Class.new Error
10
+ NonApplicableError = Class.new Error
11
+
12
+ struct = Class.new OpenStruct do
13
+ def self.call(**kwargs)
14
+ new(**kwargs)
15
+ end
16
+
17
+ def to_a
18
+ to_h.values
19
+ end
20
+ end
21
+
22
+ Style = Class.new struct
23
+ Param = Class.new struct
24
+
25
+ Orientation = Geometry::Rect::Orientation
26
+ end
27
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'mixins/lines'
4
+ require_relative 'mixins/squares'
5
+ require_relative 'mixins/scribes'
@@ -0,0 +1,105 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Dieses
4
+ module Application
5
+ module Mixins
6
+ module Lines
7
+ module ClassMethods
8
+ Line = Struct.new :tag, :after, :style, :step, keyword_init: true
9
+
10
+ def hline(tag, after: Undefined, style: EMPTY_HASH)
11
+ (param.hlines ||= []) << Line.new(tag: tag, after: after, style: style)
12
+ end
13
+
14
+ def vline(tag, after: Undefined, style: EMPTY_HASH)
15
+ (param.vlines ||= []) << Line.new(tag: tag, after: after, style: style)
16
+ end
17
+
18
+ Cross = Struct.new :tag, :angle, :style, keyword_init: true
19
+
20
+ def cline(tag, angle:, style: EMPTY_HASH)
21
+ (param.clines ||= []) << Cross.new(tag: tag, angle: angle, style: style)
22
+ end
23
+ end
24
+
25
+ module InstanceMethods
26
+ def lines(unit: Undefined, multiple: Undefined)
27
+ step_lines(param)
28
+
29
+ unit = Undefined.default(unit, param.unit)
30
+ multiple = Undefined.default(multiple, param.multiple || param.hlines&.map(&:step)&.sum)
31
+
32
+ draw_hlines unit: unit, multiple: multiple
33
+ draw_vlines unit: unit, multiple: multiple
34
+ draw_clines unit: unit, multiple: multiple
35
+ end
36
+
37
+ private
38
+
39
+ def step_lines(param)
40
+ [*param.hlines, *param.vlines].compact.each do |line|
41
+ line.step = case line.after
42
+ when Proc then param.instance_exec(&line.after)
43
+ when Numeric then line.after
44
+ when Undefined, NilClass then 1
45
+ else raise ArgumentError, "Wrong type for after: #{line.after.class}"
46
+ end
47
+ end
48
+ end
49
+
50
+ def draw_hlines(unit:, multiple:)
51
+ return unless param.hlines
52
+
53
+ param = self.param
54
+
55
+ draw unit: unit, multiple: multiple do
56
+ repeat do
57
+ param.hlines.each do |line|
58
+ hline line.tag, style: line.style
59
+ down line.step
60
+ end
61
+ end
62
+ end
63
+ end
64
+
65
+ def draw_vlines(unit:, multiple:)
66
+ return unless param.vlines
67
+
68
+ param = self.param
69
+
70
+ draw unit: unit, multiple: multiple do
71
+ repeat do
72
+ param.vlines.each do |line|
73
+ vline line.tag, style: line.style
74
+ right line.step
75
+ end
76
+ end
77
+ end
78
+ end
79
+
80
+ def draw_clines(unit:, multiple:) # rubocop:disable Metrics/MethodLength
81
+ return unless param.clines
82
+
83
+ param = self.param
84
+
85
+ draw unit: unit, multiple: multiple do
86
+ repeat do
87
+ param.clines.each do |slant|
88
+ repeat do
89
+ cline slant.tag, angle: slant.angle, style: slant.style
90
+ cross
91
+ end
92
+ end
93
+ end
94
+ end
95
+ end
96
+ end
97
+
98
+ def self.included(base)
99
+ base.extend ClassMethods
100
+ base.include InstanceMethods
101
+ end
102
+ end
103
+ end
104
+ end
105
+ end
@@ -0,0 +1,91 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Dieses
4
+ module Application
5
+ module Mixins
6
+ class Scribes < Module
7
+ def self.[](type)
8
+ raise ArgumentError, "No such Scribes type available: #{type}" unless Bundle.method_defined? type
9
+
10
+ new(type)
11
+ end
12
+
13
+ def initialize(type)
14
+ super()
15
+ @type = type.to_sym
16
+ end
17
+
18
+ def with(unit:, ratio: [1/1r], gap: [0])
19
+ tap do
20
+ @unit = unit
21
+ @ratio = ratio
22
+ @gap = gap
23
+ end
24
+ end
25
+
26
+ module ClassMethods
27
+ def variate_scribes(unit:, ratio: [1/1r], gap: [0])
28
+ variate(unit: unit, ratio: ratio, gap: gap) do
29
+ self.name = :"#{self.unit}#{self.ratio.to_s.delete('/')}#{self.gap}"
30
+ self.desc = "#{self.unit} mm x-height with #{self.ratio} ratio and #{self.gap} mm gap"
31
+ end
32
+ end
33
+ end
34
+
35
+ module InstanceMethods
36
+ def scribes
37
+ param.x_height = 1.0
38
+ param.height = param.hlines.size > 2 ? 2 * param.ratio / (param.hlines.size - 2) : 1
39
+
40
+ lines
41
+ end
42
+ end
43
+
44
+ module Bundle
45
+ def quartet
46
+ hline :ascender, after: proc { height },
47
+ style: Style.(stroke: 'blue', 'stroke-width': '0.2')
48
+ hline :waist, after: proc { x_height },
49
+ style: Style.(stroke: 'grey', 'stroke-width': '0.1', 'stroke-dasharray': '2, 2')
50
+ hline :base, after: proc { height },
51
+ style: Style.(stroke: 'red', 'stroke-width': '0.1', 'stroke-dasharray': '2, 2')
52
+ hline :descender, after: proc { gap },
53
+ style: Style.(stroke: 'blue', 'stroke-width': '0.2')
54
+ end
55
+
56
+ # rubocop:disable Metrics/MethodLength
57
+ # codebeat:disable[ABC]
58
+ def sextet
59
+ hline :ascender2, after: proc { height },
60
+ style: Style.(stroke: 'blue', 'stroke-width': '0.2')
61
+ hline :ascender1, after: proc { height },
62
+ style: Style.(stroke: 'grey', 'stroke-width': '0.1', 'stroke-dasharray': '2, 2')
63
+ hline :waist, after: proc { x_height },
64
+ style: Style.(stroke: 'grey', 'stroke-width': '0.1')
65
+ hline :base, after: proc { height },
66
+ style: Style.(stroke: 'red', 'stroke-width': '0.1')
67
+ hline :descender1, after: proc { height },
68
+ style: Style.(stroke: 'grey', 'stroke-width': '0.1', 'stroke-dasharray': '2, 2')
69
+ hline :descender2, after: proc { gap },
70
+ style: Style.(stroke: 'blue', 'stroke-width': '0.2')
71
+ end
72
+ # codebeat:enable[ABC]
73
+ # rubocop:enable Metrics/MethodLength
74
+ end
75
+
76
+ def included(base)
77
+ base.include Lines
78
+
79
+ base.extend ClassMethods
80
+ base.include InstanceMethods
81
+
82
+ base.extend Bundle
83
+
84
+ base.variate_scribes(unit: @unit, ratio: @ratio, gap: @gap) if @unit
85
+
86
+ base.send(@type)
87
+ end
88
+ end
89
+ end
90
+ end
91
+ end
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Dieses
4
+ module Application
5
+ module Mixins
6
+ module Squares
7
+ def squares(unit: Undefined, multiple: Undefined)
8
+ param = self.param
9
+
10
+ draw unit: Undefined.default(unit, param.unit), multiple: Undefined.default(multiple, param.multiple) do
11
+ repeat do
12
+ repeat do
13
+ square :square, width: multiple, style: Style.(stroke: 'blue', 'stroke-width': '0.2', fill: 'none')
14
+ right multiple
15
+ end
16
+ down multiple
17
+ end
18
+ end
19
+ end
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,146 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'delegate'
4
+
5
+ module Dieses
6
+ module Application
7
+ module Paper
8
+ Dim = Struct.new(*%i[width height], keyword_init: true) do
9
+ def self.call(width, height)
10
+ new width: width, height: height
11
+ end
12
+
13
+ def short
14
+ values.min
15
+ end
16
+
17
+ def long
18
+ values.max
19
+ end
20
+ end
21
+
22
+ Margin = Struct.new(*%i[top right bottom left], keyword_init: true) do # in CSS margin order
23
+ def self.call(*args)
24
+ values = case args.size
25
+ when 1 then Array.new(members.size, *args)
26
+ when members.size then args
27
+ else raise ArgumentError, "Incorrect number of arguments: #{args}"
28
+ end
29
+
30
+ new(Hash[*members.zip(values).flatten])
31
+ end
32
+
33
+ def self.build(dim, short:, long:)
34
+ kwargs = if dim.height > dim.width
35
+ { top: long, right: short, bottom: long, left: short }
36
+ else
37
+ { top: short, right: long, bottom: short, left: long }
38
+ end
39
+
40
+ new(**kwargs)
41
+ end
42
+ end
43
+
44
+ Variant = Struct.new :type, :name, :width, :height, :floor, :scale, keyword_init: true do
45
+ def build # rubocop:disable Metrics/MethodLength,Metrics/AbcSize
46
+ dim = Dim.(width, height)
47
+
48
+ if scale.nil? || (short = Support.approx(dim.short * scale)) < floor
49
+ short = floor
50
+ scale = floor / dim.short
51
+ end
52
+
53
+ long = Support.approx(dim.long * scale)
54
+ margin = Margin.build(dim, short: short, long: long)
55
+
56
+ variant = self
57
+
58
+ Paper.define_singleton_method(name) do |**kwargs|
59
+ klass = Class.new(Instance)
60
+
61
+ klass.type variant.type
62
+ klass.variant variant.name
63
+
64
+ klass.new(*dim.values, **margin.to_h.merge(**kwargs))
65
+ end
66
+ end
67
+ end
68
+
69
+ module DSL
70
+ VARIANTS = [
71
+ { suffix: 'n', floor: 7.0 }, # Narrow margins (default)
72
+ { suffix: 'm', floor: 12.0 }, # Medium margins: bare minimum margin to accommodate ISO 838 filing holes
73
+ { suffix: 'w', floor: 20.0 } # Wide margins: safe minimum margin to accommodate ISO 838 filing holes
74
+ ].freeze
75
+
76
+ def family(type, width:, height:, scale: nil)
77
+ VARIANTS.map do |hash|
78
+ name = :"#{type}#{hash[:suffix]}"
79
+ floor = hash[:floor]
80
+ Variant.new(type: type, name: name, width: width, height: height, floor: floor, scale: scale).tap(&:build)
81
+ end.first.tap do |variant| # rubocop:disable Style/MultilineBlockChain
82
+ # set the first variant as the default paper
83
+ (class << self; self; end).alias_method type, variant.name
84
+ end
85
+ end
86
+ end
87
+
88
+ class Instance < DelegateClass(Geometry::Rect)
89
+ extend Support::ClassAttribute
90
+
91
+ define :type, instance_reader: true
92
+ define :variant, instance_reader: true
93
+
94
+ extend Forwardable
95
+ def_delegators :@margin, *Margin.members
96
+
97
+ def initialize(width, height, **margin)
98
+ @margin = Margin.new(**margin)
99
+ super(Geometry::Rect.new(width, height))
100
+ end
101
+
102
+ def inner
103
+ @inner ||= shrink(width: left + right, height: top + bottom)
104
+ end
105
+
106
+ def orient(orientation)
107
+ self.class.new((rect = super).width, rect.height, **margin.to_h)
108
+ end
109
+
110
+ def to_h
111
+ super.merge(margin.to_h)
112
+ end
113
+
114
+ protected
115
+
116
+ attr_reader :margin
117
+ end
118
+
119
+ extend DSL
120
+
121
+ A4 = Dim.new width: 210.0, height: 297.0
122
+ B4 = Dim.new width: 250.0, height: 353.0
123
+ US = Dim.new width: 215.9, height: 279.4
124
+
125
+ family :a3, width: A4.height, height: A4.width * 2
126
+ family :a4, width: A4.width, height: A4.height
127
+ family :a5, width: A4.height / 2, height: A4.width
128
+ family :a6, width: A4.width / 2, height: A4.height / 2
129
+
130
+ family :b3, width: B4.height, height: B4.width * 2
131
+ family :b4, width: B4.width, height: B4.height
132
+ family :b5, width: B4.height / 2, height: B4.width
133
+ family :b6, width: B4.width / 2, height: B4.height / 2
134
+
135
+ family :us, width: US.width, height: US.height
136
+
137
+ class << self
138
+ alias letter us
139
+
140
+ def default
141
+ :a4
142
+ end
143
+ end
144
+ end
145
+ end
146
+ end
@@ -0,0 +1,161 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Dieses
4
+ module Application
5
+ class Pen
6
+ attr_reader :canvas
7
+
8
+ def initialize(canvas)
9
+ @canvas = canvas
10
+ end
11
+
12
+ def draw(unit:, multiple: nil, &block)
13
+ Draw.(self, Ruler.(unit, multiple), &block)
14
+ end
15
+
16
+ Ruler = Struct.new :unit, :multiple do
17
+ def self.call(unit, multiple = nil)
18
+ new unit, (multiple || 1)
19
+ end
20
+
21
+ def major
22
+ @major ||= multiple * unit
23
+ end
24
+
25
+ def even(length)
26
+ major * (length / major).to_i
27
+ end
28
+
29
+ def measure(n)
30
+ n * unit
31
+ end
32
+ end
33
+
34
+ class Draw
35
+ extend Forwardable
36
+
37
+ def_delegators :@pen, :canvas
38
+ def_delegators :@buffer, :<<
39
+ def_delegators :@ruler, :unit, :multiple
40
+
41
+ def self.call(pen, ruler, &block)
42
+ Draw.new(pen, ruler).(&block)
43
+ end
44
+
45
+ def initialize(pen, ruler, pos: Undefined)
46
+ @pen = pen
47
+ @pos = Undefined.default(pos, Geometry::Point::Mutable.cast(canvas.position))
48
+ @ruler = ruler
49
+ @buffer = Set.new
50
+ end
51
+
52
+ def repeat(count = nil, &block)
53
+ self.class.new(pen, ruler, pos: pos.dup).instance_exec do
54
+ 1.step(count) do
55
+ prev = pos.dup
56
+ instance_exec(&block)
57
+ put
58
+
59
+ break if pos == prev || perfect.outside?(pos)
60
+ rescue Offsite
61
+ break
62
+ end
63
+ end
64
+ end
65
+
66
+ def call(&block)
67
+ instance_exec(&block)
68
+ put
69
+ end
70
+
71
+ private
72
+
73
+ attr_reader :pen, :pos, :ruler, :buffer
74
+
75
+ def perfect
76
+ @perfect ||= Geometry::Rect.new(ruler.even(canvas.width), ruler.even(canvas.height))
77
+ end
78
+
79
+ Offsite = Class.new StopIteration
80
+
81
+ module Elements
82
+ def hline(tag = Undefined, length: Undefined, style: Undefined)
83
+ length = Undefined.equal?(length) ? perfect.width : ruler.measure(length)
84
+ add Geometry::Line.new(pos, pos.translate(x: length)), tag, style
85
+ end
86
+
87
+ def vline(tag = Undefined, length: Undefined, style: Undefined)
88
+ length = Undefined.equal?(length) ? perfect.height : ruler.measure(length)
89
+ add Geometry::Line.new(pos, pos.translate(y: length)), tag, style
90
+ end
91
+
92
+ def cline(tag = Undefined, angle:, style: Undefined)
93
+ add perfect.intersect(Geometry::Equation.slant_from_direction(point: pos, angle: -angle)), tag, style
94
+ end
95
+
96
+ def rect(tag = Undefined, width:, height:, style: Undefined)
97
+ width, height = ruler.measure(width), ruler.measure(height)
98
+ add Geometry::Rect.new(width, height, position: pos), tag, style
99
+ end
100
+
101
+ def square(tag = Undefined, width:, style: Undefined)
102
+ rect(tag, width: width, height: width, style: style)
103
+ end
104
+
105
+ private
106
+
107
+ BASE_STYLE = { stroke: 'black', 'stroke-width': '0.1' }.freeze
108
+
109
+ def add(element, tag, style)
110
+ raise Offsite unless element && perfect.cover?(element)
111
+
112
+ tag = Undefined.default(tag, caller_locations(1, 1).first.label.to_sym)
113
+
114
+ element.tap do
115
+ buffer << element.classify(tag, **BASE_STYLE.merge(Undefined.default(style, EMPTY_HASH).to_h))
116
+ end
117
+ end
118
+
119
+ def put
120
+ canvas.tap do
121
+ buffer.each { |element| canvas << element }
122
+ end
123
+ end
124
+ end
125
+
126
+ module Movements
127
+ def move(x: Undefined, y: Undefined)
128
+ tap do
129
+ pos.translate!(x: x * ruler.unit) unless Undefined.equal?(x)
130
+ pos.translate!(y: y * ruler.unit) unless Undefined.equal?(y)
131
+ end
132
+ end
133
+
134
+ def up(y = 1)
135
+ move y: -y
136
+ end
137
+
138
+ def down(y = 1)
139
+ move y: y
140
+ end
141
+
142
+ def left(x = 1)
143
+ move x: -x
144
+ end
145
+
146
+ def right(x = 1)
147
+ move x: x
148
+ end
149
+
150
+ def cross(distance = 1, angle: Undefined)
151
+ radian = Undefined.equal?(angle) ? perfect.angle : Support.to_radian(angle)
152
+ move x: distance * Math.cos(radian), y: distance * Math.sin(radian)
153
+ end
154
+ end
155
+
156
+ include Elements
157
+ include Movements
158
+ end
159
+ end
160
+ end
161
+ end