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.
- checksums.yaml +7 -0
- data/.codeclimate.yml +8 -0
- data/.gitignore +9 -0
- data/.rspec +1 -0
- data/.rubocop.yml +51 -0
- data/.travis.yml +7 -0
- data/Gemfile +3 -0
- data/LICENSE.txt +21 -0
- data/README.md +78 -0
- data/Rakefile +9 -0
- data/bin/console +7 -0
- data/bin/rake +10 -0
- data/bin/rspec +10 -0
- data/bin/rubocop +10 -0
- data/bin/setup +7 -0
- data/exe/mahoujin +22 -0
- data/lib/mahoujin.rb +15 -0
- data/lib/mahoujin/atoms.rb +32 -0
- data/lib/mahoujin/atoms/choice.rb +12 -0
- data/lib/mahoujin/atoms/concatenation.rb +12 -0
- data/lib/mahoujin/atoms/exception.rb +11 -0
- data/lib/mahoujin/atoms/grouping.rb +11 -0
- data/lib/mahoujin/atoms/non_terminal.rb +11 -0
- data/lib/mahoujin/atoms/optional.rb +11 -0
- data/lib/mahoujin/atoms/prose.rb +11 -0
- data/lib/mahoujin/atoms/rule.rb +11 -0
- data/lib/mahoujin/atoms/rule_name.rb +11 -0
- data/lib/mahoujin/atoms/specific_repetition.rb +11 -0
- data/lib/mahoujin/atoms/syntax.rb +11 -0
- data/lib/mahoujin/atoms/terminal.rb +11 -0
- data/lib/mahoujin/atoms/zero_or_more_repetition.rb +11 -0
- data/lib/mahoujin/graphics/layout.rb +203 -0
- data/lib/mahoujin/graphics/renderer.rb +337 -0
- data/lib/mahoujin/graphics/stringify.rb +67 -0
- data/lib/mahoujin/graphics/styles/basic.rb +48 -0
- data/lib/mahoujin/graphics/styles/json.rb +48 -0
- data/lib/mahoujin/graphics/utilities/rectangle.rb +58 -0
- data/lib/mahoujin/parser.rb +62 -0
- data/lib/mahoujin/transform.rb +39 -0
- data/lib/mahoujin/version.rb +3 -0
- data/mahoujin.gemspec +29 -0
- data/samples/ebnf/syntax.ebnfspec +20 -0
- data/samples/ebnf/syntax.svg +1558 -0
- data/samples/ipv4/syntax.ebnfspec +6 -0
- data/samples/ipv4/syntax.svg +344 -0
- data/samples/json/syntax.ebnfspec +8 -0
- data/samples/json/syntax.svg +1169 -0
- metadata +202 -0
| @@ -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
         |