mahoujin 2.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (48) hide show
  1. checksums.yaml +7 -0
  2. data/.codeclimate.yml +8 -0
  3. data/.gitignore +9 -0
  4. data/.rspec +1 -0
  5. data/.rubocop.yml +51 -0
  6. data/.travis.yml +7 -0
  7. data/Gemfile +3 -0
  8. data/LICENSE.txt +21 -0
  9. data/README.md +78 -0
  10. data/Rakefile +9 -0
  11. data/bin/console +7 -0
  12. data/bin/rake +10 -0
  13. data/bin/rspec +10 -0
  14. data/bin/rubocop +10 -0
  15. data/bin/setup +7 -0
  16. data/exe/mahoujin +22 -0
  17. data/lib/mahoujin.rb +15 -0
  18. data/lib/mahoujin/atoms.rb +32 -0
  19. data/lib/mahoujin/atoms/choice.rb +12 -0
  20. data/lib/mahoujin/atoms/concatenation.rb +12 -0
  21. data/lib/mahoujin/atoms/exception.rb +11 -0
  22. data/lib/mahoujin/atoms/grouping.rb +11 -0
  23. data/lib/mahoujin/atoms/non_terminal.rb +11 -0
  24. data/lib/mahoujin/atoms/optional.rb +11 -0
  25. data/lib/mahoujin/atoms/prose.rb +11 -0
  26. data/lib/mahoujin/atoms/rule.rb +11 -0
  27. data/lib/mahoujin/atoms/rule_name.rb +11 -0
  28. data/lib/mahoujin/atoms/specific_repetition.rb +11 -0
  29. data/lib/mahoujin/atoms/syntax.rb +11 -0
  30. data/lib/mahoujin/atoms/terminal.rb +11 -0
  31. data/lib/mahoujin/atoms/zero_or_more_repetition.rb +11 -0
  32. data/lib/mahoujin/graphics/layout.rb +203 -0
  33. data/lib/mahoujin/graphics/renderer.rb +337 -0
  34. data/lib/mahoujin/graphics/stringify.rb +67 -0
  35. data/lib/mahoujin/graphics/styles/basic.rb +48 -0
  36. data/lib/mahoujin/graphics/styles/json.rb +48 -0
  37. data/lib/mahoujin/graphics/utilities/rectangle.rb +58 -0
  38. data/lib/mahoujin/parser.rb +62 -0
  39. data/lib/mahoujin/transform.rb +39 -0
  40. data/lib/mahoujin/version.rb +3 -0
  41. data/mahoujin.gemspec +29 -0
  42. data/samples/ebnf/syntax.ebnfspec +20 -0
  43. data/samples/ebnf/syntax.svg +1558 -0
  44. data/samples/ipv4/syntax.ebnfspec +6 -0
  45. data/samples/ipv4/syntax.svg +344 -0
  46. data/samples/json/syntax.ebnfspec +8 -0
  47. data/samples/json/syntax.svg +1169 -0
  48. metadata +202 -0
@@ -0,0 +1,11 @@
1
+ module Mahoujin
2
+ module Atoms
3
+ class SpecificRepetition
4
+ attr_reader :atom, :number
5
+
6
+ def initialize(atom, number)
7
+ @atom, @number = atom, number
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,11 @@
1
+ module Mahoujin
2
+ module Atoms
3
+ class Syntax
4
+ attr_reader :atoms
5
+
6
+ def initialize(atoms)
7
+ @atoms = atoms
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,11 @@
1
+ module Mahoujin
2
+ module Atoms
3
+ class Terminal
4
+ attr_reader :content
5
+
6
+ def initialize(content)
7
+ @content = content.freeze
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,11 @@
1
+ module Mahoujin
2
+ module Atoms
3
+ class ZeroOrMoreRepetition
4
+ attr_reader :atom
5
+
6
+ def initialize(atom)
7
+ @atom = atom
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,203 @@
1
+ module Mahoujin
2
+ module Graphics
3
+ class Layout
4
+ def layout(atom, style)
5
+ Cairo::SVGSurface.new(StringIO.new, 200, 200) do |surface|
6
+ @ctx = Cairo::Context.new(surface)
7
+ @style = style
8
+
9
+ initialize_graphics_environment
10
+ layout_atom(atom)
11
+ end
12
+ end
13
+
14
+ private
15
+
16
+ def initialize_graphics_environment
17
+ @ctx.set_source_color(:black)
18
+ @ctx.set_line_width(@style.basic[:line][:width])
19
+
20
+ @ctx.select_font_face(@style.basic[:font][:family], Cairo::FONT_SLANT_NORMAL, Cairo::FONT_WEIGHT_NORMAL)
21
+ @ctx.set_font_size(@style.basic[:font][:size])
22
+
23
+ @style.basic[:unit] ||= @ctx.text_extents([*('A'..'Z'), *('a'..'z'), *('0'..'9')].join).height
24
+ end
25
+
26
+ def graphics_unit
27
+ @style.basic[:unit]
28
+ end
29
+
30
+ def graphics_value(atom, attribute)
31
+ @style.query(atom.class, attribute)
32
+ end
33
+
34
+ def measure_text_metrics(text, font)
35
+ @ctx.save
36
+ @ctx.select_font_face(@ctx.font_face.family, @ctx.font_face.slant, Cairo::FONT_WEIGHT_BOLD) if font[:bold]
37
+ @ctx.select_font_face(@ctx.font_face.family, Cairo::FONT_SLANT_ITALIC, @ctx.font_face.weight) if font[:italic]
38
+ @ctx.set_font_size(font[:size]) if font[:size]
39
+ @ctx.text_extents(text)
40
+ ensure
41
+ @ctx.restore
42
+ end
43
+
44
+ def layout_atom(atom)
45
+ send "layout_#{atom.class.underscore_name}", atom
46
+ end
47
+
48
+ def layout_syntax(atom)
49
+ atom.atoms.each { |a| layout_atom(a) }
50
+
51
+ space = graphics_value(atom, :space) * graphics_unit
52
+ top = 0
53
+
54
+ atom.atoms.each do |a|
55
+ move_recursively(a, :top, top)
56
+ top += a.bbox.height + space
57
+ end
58
+
59
+ bbox = Graphics::Utilities::Rectangle.new(0, 0, atom.atoms.map(&:bbox).map(&:width).max, top - space)
60
+ bbox.centerline = 0
61
+ atom.bbox.sync_with(bbox)
62
+
63
+ expand_atom_bbox(atom, graphics_value(atom, :padding))
64
+ end
65
+
66
+ def layout_rule(atom)
67
+ layout_atom(atom.rule_name_atom)
68
+ layout_atom(atom.atom)
69
+
70
+ space = graphics_value(atom, :space) * graphics_unit
71
+ top = 0
72
+
73
+ atoms = [atom.rule_name_atom, atom.atom]
74
+ atoms.each do |a|
75
+ move_recursively(a, :top, top)
76
+ top += a.bbox.height + space
77
+ end
78
+
79
+ bbox = Graphics::Utilities::Rectangle.new(0, 0, atoms.map(&:bbox).map(&:width).max, top - space)
80
+ bbox.centerline = atom.atom.bbox.centerline
81
+ atom.bbox.sync_with(bbox)
82
+
83
+ expand_atom_bbox(atom, graphics_value(atom, :padding))
84
+ move_recursively(atom.rule_name_atom, :left, 0)
85
+ end
86
+
87
+ def layout_rule_name(atom)
88
+ initialize_text_atom(atom, graphics_value(atom, :bsize), graphics_value(atom, :font))
89
+ end
90
+
91
+ def layout_choice(atom)
92
+ atom.atoms.each { |a| layout_atom(a) }
93
+
94
+ reference = atom.atoms.max(&->(a1, a2) { a1.bbox.width <=> a2.bbox.width })
95
+ space = graphics_value(atom, :space) * graphics_unit
96
+ top = 0
97
+
98
+ atom.atoms.each do |a|
99
+ move_recursively(a, :top, top)
100
+ top += a.bbox.height + space
101
+ end
102
+
103
+ bbox = Graphics::Utilities::Rectangle.new(0, 0, reference.bbox.width, top - space)
104
+ bbox.centerline = reference.bbox.centerline
105
+ atom.bbox.sync_with(bbox)
106
+
107
+ expand_atom_bbox(atom, graphics_value(atom, :padding))
108
+ end
109
+
110
+ def layout_concatenation(atom)
111
+ atom.atoms.each { |a| layout_atom(a) }
112
+
113
+ reference = atom.atoms.max(&->(a1, a2) { a1.bbox.height <=> a2.bbox.height })
114
+ space = graphics_value(atom, :space) * graphics_unit
115
+ left = 0
116
+
117
+ atom.atoms.each do |a|
118
+ move_recursively(a, :top, reference.bbox.centerline + (a.bbox.top - a.bbox.centerline))
119
+ move_recursively(a, :left, left)
120
+ left += a.bbox.width + space
121
+ end
122
+
123
+ bbox = Graphics::Utilities::Rectangle.new(0, 0, left - space, reference.bbox.height)
124
+ bbox.centerline = reference.bbox.centerline
125
+ atom.bbox.sync_with(bbox)
126
+ end
127
+
128
+ def layout_specific_repetition(atom)
129
+ layout_atom(atom.atom)
130
+ atom.bbox.sync_with(atom.atom.bbox)
131
+
132
+ expand_atom_bbox(atom, graphics_value(atom, :padding))
133
+ end
134
+
135
+ def layout_optional(atom)
136
+ layout_atom(atom.atom)
137
+ atom.bbox.sync_with(atom.atom.bbox)
138
+
139
+ expand_atom_bbox(atom, graphics_value(atom, :padding))
140
+ end
141
+
142
+ def layout_zero_or_more_repetition(atom)
143
+ layout_atom(atom.atom)
144
+ atom.bbox.sync_with(atom.atom.bbox)
145
+
146
+ expand_atom_bbox(atom, graphics_value(atom, :padding))
147
+ end
148
+
149
+ def layout_grouping(atom)
150
+ layout_atom(atom.atom)
151
+ atom.bbox.sync_with(atom.atom.bbox)
152
+ end
153
+
154
+ def layout_prose(atom)
155
+ initialize_text_atom(atom, graphics_value(atom, :bsize), graphics_value(atom, :font))
156
+ end
157
+
158
+ def layout_exception(atom)
159
+ initialize_text_atom(atom, graphics_value(atom, :bsize), graphics_value(atom, :font))
160
+ end
161
+
162
+ def layout_non_terminal(atom)
163
+ initialize_text_atom(atom, graphics_value(atom, :bsize), graphics_value(atom, :font))
164
+ end
165
+
166
+ def layout_terminal(atom)
167
+ initialize_text_atom(atom, graphics_value(atom, :bsize), graphics_value(atom, :font))
168
+ end
169
+
170
+ def initialize_text_atom(atom, bsize, font)
171
+ bbox = atom.bbox
172
+ text = atom.content
173
+
174
+ bbox.top = 0
175
+ bbox.left = 0
176
+ bbox.width = bsize.fetch(:width, 0) * graphics_unit + measure_text_metrics(text, font).width
177
+ bbox.height = bsize.fetch(:height, 0) * graphics_unit
178
+ bbox.centerline = bbox.top + bbox.height.fdiv(2)
179
+ end
180
+
181
+ def expand_atom_bbox(atom, padding)
182
+ bbox = atom.bbox
183
+ x, y = bbox.left, bbox.top
184
+
185
+ bbox.top -= padding.fetch(:top, 0) * graphics_unit
186
+ bbox.bottom += padding.fetch(:bottom, 0) * graphics_unit
187
+ bbox.left -= padding.fetch(:left, 0) * graphics_unit
188
+ bbox.right += padding.fetch(:right, 0) * graphics_unit
189
+
190
+ move_recursively(atom, :top, y)
191
+ move_recursively(atom, :left, x)
192
+ end
193
+
194
+ def move_recursively(atom, bound, coordinate)
195
+ difference = coordinate - atom.bbox.send(bound)
196
+ atom.bbox.send("move_#{bound}", coordinate)
197
+ atom.atom.tap(&->(a) { move_recursively(a, bound, a.bbox.send(bound) + difference) }) if atom.respond_to?(:atom)
198
+ atom.atoms.each(&->(a) { move_recursively(a, bound, a.bbox.send(bound) + difference) }) if atom.respond_to?(:atoms)
199
+ atom.rule_name_atom.tap(&->(a) { move_recursively(a, bound, a.bbox.send(bound) + difference) }) if atom.is_a?(Atoms::Rule)
200
+ end
201
+ end
202
+ end
203
+ end
@@ -0,0 +1,337 @@
1
+ module Mahoujin
2
+ module Graphics
3
+ class Renderer
4
+ def render(atom, iostream, style = nil)
5
+ @style = style || Graphics::Styles::Basic.new
6
+
7
+ @layout = Graphics::Layout.new
8
+ @layout.layout(atom, @style)
9
+
10
+ Cairo::SVGSurface.new(iostream, atom.bbox.width, atom.bbox.height) do |surface|
11
+ @ctx = Cairo::Context.new(surface)
12
+ initialize_graphics_environment(atom.bbox)
13
+ render_atom(atom)
14
+ end
15
+ end
16
+
17
+ private
18
+
19
+ def initialize_graphics_environment(bbox)
20
+ @ctx.rectangle(0, 0, bbox.width, bbox.height)
21
+ @ctx.set_source_color(:white)
22
+ @ctx.fill
23
+
24
+ @ctx.set_source_color(:black)
25
+ @ctx.set_line_width(@style.basic[:line][:width])
26
+
27
+ @ctx.select_font_face(@style.basic[:font][:family], Cairo::FONT_SLANT_NORMAL, Cairo::FONT_WEIGHT_NORMAL)
28
+ @ctx.set_font_size(@style.basic[:font][:size])
29
+ end
30
+
31
+ def graphics_font
32
+ @style.basic[:font]
33
+ end
34
+
35
+ def graphics_unit
36
+ @style.basic[:unit]
37
+ end
38
+
39
+ def graphics_corner_radius
40
+ @style.basic[:corner][:radius] * graphics_unit
41
+ end
42
+
43
+ def graphics_value(atom, attribute)
44
+ @style.query(atom.class, attribute)
45
+ end
46
+
47
+ def measure_text_metrics(text, font)
48
+ @ctx.save
49
+ @ctx.select_font_face(@ctx.font_face.family, @ctx.font_face.slant, Cairo::FONT_WEIGHT_BOLD) if font[:bold]
50
+ @ctx.select_font_face(@ctx.font_face.family, Cairo::FONT_SLANT_ITALIC, @ctx.font_face.weight) if font[:italic]
51
+ @ctx.set_font_size(font[:size]) if font[:size]
52
+ @ctx.text_extents(text)
53
+ ensure
54
+ @ctx.restore
55
+ end
56
+
57
+ def render_atom(atom)
58
+ send "render_#{atom.class.underscore_name}", atom
59
+ end
60
+
61
+ def render_syntax(atom)
62
+ atom.atoms.each { |a| render_atom(a) }
63
+ end
64
+
65
+ def render_rule(atom)
66
+ render_atom(atom.rule_name_atom)
67
+ render_atom(atom.atom)
68
+
69
+ innerbbox, outerbbox, centerline = atom.atom.bbox, atom.bbox, atom.bbox.centerline
70
+ v1, v2, v3, v4 = outerbbox.left, innerbbox.left, innerbbox.right, outerbbox.right
71
+
72
+ draw_straight_line(v1, centerline, v2, centerline)
73
+ draw_straight_line(v3, centerline, v4, centerline)
74
+
75
+ h = graphics_unit
76
+ draw_straight_line(v1, centerline - h.fdiv(2), v1, centerline + h.fdiv(2))
77
+ draw_straight_line(v4, centerline - h.fdiv(2), v4, centerline + h.fdiv(2))
78
+ end
79
+
80
+ def render_rule_name(atom)
81
+ font = graphics_value(atom, :font)
82
+ draw_text(atom.bbox, atom.content, font)
83
+ end
84
+
85
+ def render_choice(atom)
86
+ atom.atoms.each { |a| render_atom(a) }
87
+
88
+ reference = atom.atoms.max(&->(a1, a2) { a1.bbox.width <=> a2.bbox.width })
89
+ centerline = reference.bbox.centerline
90
+ v1, v2, v3, v4 = atom.bbox.left, reference.bbox.left, reference.bbox.right, atom.bbox.right
91
+
92
+ a = v1, centerline
93
+ b = v2, centerline
94
+ c = v3, centerline
95
+ d = v4, centerline
96
+ draw_straight_line(*a, *b)
97
+ draw_straight_line(*c, *d)
98
+
99
+ if (index = atom.atoms.find_index(reference)) != 0
100
+ upper = atom.atoms[index - 1]
101
+ s = (v1 + v2).fdiv(2), (upper.bbox.centerline + centerline).fdiv(2)
102
+ e = (v3 + v4).fdiv(2), (upper.bbox.centerline + centerline).fdiv(2)
103
+
104
+ draw_rounded_corner(a[0], s[1], s[0] - a[0], a[1] - s[1], graphics_corner_radius, :se)
105
+ draw_rounded_corner(e[0], e[1], d[0] - e[0], d[1] - e[1], graphics_corner_radius, :sw)
106
+ atom.atoms[0...index].each do |ua|
107
+ w = v2, ua.bbox.centerline
108
+ x = ua.bbox.left, ua.bbox.centerline
109
+ y = ua.bbox.right, ua.bbox.centerline
110
+ z = v3, ua.bbox.centerline
111
+ draw_rounded_corner(s[0], w[1], w[0] - s[0], s[1] - w[1], graphics_corner_radius, :nw)
112
+ draw_straight_line(*w, *x)
113
+ draw_straight_line(*y, *z)
114
+ draw_rounded_corner(z[0], z[1], e[0] - z[0], e[1] - z[1], graphics_corner_radius, :ne)
115
+ end
116
+ end
117
+
118
+ if (index = atom.atoms.find_index(reference)) != atom.atoms.size - 1
119
+ lower = atom.atoms[index + 1]
120
+ s = (v1 + v2).fdiv(2), (lower.bbox.centerline + centerline).fdiv(2)
121
+ e = (v3 + v4).fdiv(2), (lower.bbox.centerline + centerline).fdiv(2)
122
+
123
+ draw_rounded_corner(a[0], a[1], s[0] - a[0], s[1] - a[1], graphics_corner_radius, :ne)
124
+ draw_rounded_corner(e[0], d[1], d[0] - e[0], e[1] - d[1], graphics_corner_radius, :nw)
125
+ atom.atoms[(index + 1)..-1].each do |la|
126
+ w = v2, la.bbox.centerline
127
+ x = la.bbox.left, la.bbox.centerline
128
+ y = la.bbox.right, la.bbox.centerline
129
+ z = v3, la.bbox.centerline
130
+ draw_rounded_corner(s[0], s[1], w[0] - s[0], w[1] - s[1], graphics_corner_radius, :sw)
131
+ draw_straight_line(*w, *x)
132
+ draw_straight_line(*y, *z)
133
+ draw_rounded_corner(z[0], e[1], e[0] - z[0], z[1] - e[1], graphics_corner_radius, :se)
134
+ end
135
+ end; nil
136
+ end
137
+
138
+ def render_concatenation(atom)
139
+ atom.atoms.each { |a| render_atom(a) }
140
+
141
+ centerline = atom.bbox.centerline
142
+ atom.atoms.each_cons(2) do |a1, a2|
143
+ draw_straight_line(a1.bbox.right, centerline, a2.bbox.left, centerline)
144
+ end
145
+ end
146
+
147
+ def render_specific_repetition(atom)
148
+ render_atom(atom.atom)
149
+
150
+ innerbbox, outerbbox, centerline = atom.atom.bbox, atom.bbox, atom.bbox.centerline
151
+ v1, v2, v3, v4 = outerbbox.left, innerbbox.left, innerbbox.right, outerbbox.right
152
+
153
+ draw_straight_line(v1, centerline, v2, centerline)
154
+ draw_straight_line(v3, centerline, v4, centerline)
155
+ draw_infinite_loop_line(v1, v2, v3, v4, outerbbox.top, centerline, graphics_corner_radius)
156
+
157
+ text = "{ #{atom.number} }"
158
+ font = { size: graphics_font[:size] * 0.8 }
159
+ e = measure_text_metrics(text, font)
160
+ x, y = (v1 + v4).fdiv(2) - e.width.fdiv(2), outerbbox.top - e.height.fdiv(2)
161
+ bbox = Graphics::Utilities::Rectangle.new(x, y, x + e.width, y + e.height)
162
+ fill_bbox(bbox, :white)
163
+ draw_text(bbox, text, font)
164
+ end
165
+
166
+ def render_optional(atom)
167
+ render_atom(atom.atom)
168
+
169
+ innerbbox, outerbbox, centerline = atom.atom.bbox, atom.bbox, atom.bbox.centerline
170
+ v1, v2, v3, v4 = outerbbox.left, innerbbox.left, innerbbox.right, outerbbox.right
171
+
172
+ draw_straight_line(v1, centerline, v2, centerline)
173
+ draw_straight_line(v3, centerline, v4, centerline)
174
+ draw_snake_case_line(v1, v2, v3, v4, centerline, outerbbox.bottom, graphics_corner_radius)
175
+ end
176
+
177
+ def render_zero_or_more_repetition(atom)
178
+ render_atom(atom.atom)
179
+
180
+ innerbbox, outerbbox, centerline = atom.atom.bbox, atom.bbox, atom.bbox.centerline
181
+ v1, v2, v3, v4 = outerbbox.left, innerbbox.left, innerbbox.right, outerbbox.right
182
+
183
+ draw_straight_line(v1, centerline, v2, centerline)
184
+ draw_straight_line(v3, centerline, v4, centerline)
185
+ draw_snake_case_line(v1, v2, v3, v4, centerline, outerbbox.bottom, graphics_corner_radius)
186
+ draw_infinite_loop_line((v1 + v2).fdiv(2), v2, v3, (v3 + v4).fdiv(2), outerbbox.top, centerline, graphics_corner_radius)
187
+ end
188
+
189
+ def render_grouping(atom)
190
+ render_atom(atom.atom)
191
+ end
192
+
193
+ def render_prose(atom)
194
+ font = graphics_value(atom, :font)
195
+ padding = (atom.bbox.width - measure_text_metrics(atom.content, font).width).fdiv(2)
196
+
197
+ draw_hexagon(atom.bbox, padding * 0.618, graphics_value(atom, :background))
198
+ draw_text(atom.bbox, atom.content, font)
199
+ end
200
+
201
+ def render_exception(atom)
202
+ font = graphics_value(atom, :font)
203
+ padding = (atom.bbox.width - measure_text_metrics(atom.content, font).width).fdiv(2)
204
+
205
+ draw_hexagon(atom.bbox, padding * 0.618, graphics_value(atom, :background))
206
+ draw_text(atom.bbox, atom.content, font)
207
+ end
208
+
209
+ def render_non_terminal(atom)
210
+ font = graphics_value(atom, :font)
211
+
212
+ draw_rectangle(atom.bbox, graphics_value(atom, :background))
213
+ draw_text(atom.bbox, atom.content, font)
214
+ end
215
+
216
+ def render_terminal(atom)
217
+ font = graphics_value(atom, :font)
218
+ padding = (atom.bbox.width - measure_text_metrics(atom.content, font).width).fdiv(2)
219
+
220
+ draw_rounded_rectangle(atom.bbox, padding, graphics_value(atom, :background))
221
+ draw_text(atom.bbox, atom.content, font)
222
+ end
223
+
224
+ def fill_bbox(bbox, background)
225
+ @ctx.save do
226
+ @ctx.rounded_rectangle(bbox.left, bbox.top, bbox.width, bbox.height, 0)
227
+ @ctx.set_source_color(background)
228
+ @ctx.fill
229
+ end
230
+ end
231
+
232
+ def draw_text(bbox, text, font)
233
+ e = measure_text_metrics(text, font)
234
+ x = bbox.left + bbox.width.fdiv(2) - ( e.width.fdiv(2) + e.x_bearing)
235
+ y = bbox.top + bbox.height.fdiv(2) - (e.height.fdiv(2) + e.y_bearing)
236
+
237
+ @ctx.save do
238
+ @ctx.select_font_face(@ctx.font_face.family, @ctx.font_face.slant, Cairo::FONT_WEIGHT_BOLD) if font[:bold]
239
+ @ctx.select_font_face(@ctx.font_face.family, Cairo::FONT_SLANT_ITALIC, @ctx.font_face.weight) if font[:italic]
240
+ @ctx.set_font_size(font[:size]) if font[:size]
241
+ @ctx.move_to(x, y).show_text(text)
242
+ end
243
+ end
244
+
245
+ def draw_hexagon(bbox, radius, background)
246
+ left, top, right, bottom = bbox.left, bbox.top, bbox.right, bbox.bottom
247
+ centerline = bbox.centerline
248
+
249
+ a = left, centerline
250
+ b = left + radius, top
251
+ c = right - radius, top
252
+ d = right, centerline
253
+ e = right - radius, bottom
254
+ f = left + radius, bottom
255
+
256
+ @ctx.move_to(*a).line_to(*b).line_to(*c).line_to(*d).line_to(*e).line_to(*f).close_path
257
+ @ctx.save { @ctx.set_source_color(background).fill_preserve }
258
+ @ctx.stroke
259
+ end
260
+
261
+ def draw_rectangle(bbox, background)
262
+ @ctx.rounded_rectangle(bbox.left, bbox.top, bbox.width, bbox.height, 0)
263
+ @ctx.save { @ctx.set_source_color(background).fill_preserve }
264
+ @ctx.stroke
265
+ end
266
+
267
+ def draw_rounded_rectangle(bbox, radius, background)
268
+ @ctx.rounded_rectangle(bbox.left, bbox.top, bbox.width, bbox.height, radius)
269
+ @ctx.save { @ctx.set_source_color(background).fill_preserve }
270
+ @ctx.stroke
271
+ end
272
+
273
+ def draw_infinite_loop_line(v1, v2, v3, v4, h1, h2, radius)
274
+ a = v2, h2
275
+ b = v2, h1
276
+ c = v3, h1
277
+ d = v3, h2
278
+ e = v1, (h1 + h2).fdiv(2)
279
+ f = v4, (h1 + h2).fdiv(2)
280
+
281
+ draw_rounded_corner(*e, a[0] - e[0], a[1] - e[1], radius, :sw)
282
+ draw_rounded_corner(e[0], b[1], b[0] - e[0], e[1] - b[1], radius, :nw)
283
+ draw_straight_line(*b, *c)
284
+ draw_rounded_corner(*c, f[0] - c[0], f[1] - c[1], radius, :ne)
285
+ draw_rounded_corner(d[0], f[1], f[0] - d[0], d[1] - f[1], radius, :se)
286
+ end
287
+
288
+ def draw_snake_case_line(v1, v2, v3, v4, h1, h2, radius)
289
+ a = v1, h1
290
+ b = v2, h2
291
+ c = v3, h2
292
+ d = v4, h1
293
+ e = center_point(*a, *b)
294
+ f = center_point(*c, *d)
295
+
296
+ draw_rounded_corner(*a, e[0] - a[0], e[1] - a[1], radius, :ne)
297
+ draw_rounded_corner(*e, b[0] - e[0], b[1] - e[1], radius, :sw)
298
+ draw_straight_line(*b, *c)
299
+ draw_rounded_corner(c[0], f[1], f[0] - c[0], c[1] - f[1], radius, :se)
300
+ draw_rounded_corner(f[0], d[1], d[0] - f[0], f[1] - d[1], radius, :nw)
301
+ end
302
+
303
+ def draw_straight_line(x1, y1, x2, y2)
304
+ @ctx.move_to(x1, y1).line_to(x2, y2).stroke
305
+ end
306
+
307
+ def draw_rounded_corner(x, y, width, height, radius, corner)
308
+ x1, y1 = x, y
309
+ x2, y2 = x + width, y + height
310
+ radius = [width, height, radius].min
311
+
312
+ case corner
313
+ when :nw
314
+ @ctx.move_to(x1, y2).line_to(x1, y1 + radius).stroke
315
+ @ctx.arc(x1 + radius, y1 + radius, radius, 180 * (Math::PI / 180), 270 * (Math::PI / 180)).stroke
316
+ @ctx.move_to(x1 + radius, y1).line_to(x2, y1).stroke
317
+ when :ne
318
+ @ctx.move_to(x1, y1).line_to(x2 - radius, y1).stroke
319
+ @ctx.arc(x2 - radius, y1 + radius, radius, 270 * (Math::PI / 180), 360 * (Math::PI / 180)).stroke
320
+ @ctx.move_to(x2, y1 + radius).line_to(x2, y2).stroke
321
+ when :se
322
+ @ctx.move_to(x2, y1).line_to(x2, y2 - radius).stroke
323
+ @ctx.arc(x2 - radius, y2 - radius, radius, 0 * (Math::PI / 180), 90 * (Math::PI / 180)).stroke
324
+ @ctx.move_to(x2 - radius, y2).line_to(x1, y2).stroke
325
+ when :sw
326
+ @ctx.move_to(x2, y2).line_to(x1 + radius, y2).stroke
327
+ @ctx.arc(x1 + radius, y2 - radius, radius, 90 * (Math::PI / 180), 180 * (Math::PI / 180)).stroke
328
+ @ctx.move_to(x1, y2 - radius).line_to(x1, y1).stroke
329
+ end
330
+ end
331
+
332
+ def center_point(x1, y1, x2, y2)
333
+ [(x1 + x2).fdiv(2), (y1 + y2).fdiv(2)]
334
+ end
335
+ end
336
+ end
337
+ end