mahoujin 2.0.0

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 (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