class Mahoujin::Graphics::Renderer

Public Instance Methods

render(atom, iostream, style = nil, format = nil) click to toggle source
# File lib/mahoujin/graphics/renderer.rb, line 4
def render(atom, iostream, style = nil, format = nil)
  style ||= Graphics::Styles::Basic.new
  case (format || 'PNG').upcase.to_sym
  when :PNG then render_in_png_format(atom, iostream, style)
  when :SVG then render_in_svg_format(atom, iostream, style)
  else raise
  end
end

Private Instance Methods

center_point(x1, y1, x2, y2) click to toggle source
# File lib/mahoujin/graphics/renderer.rb, line 353
def center_point(x1, y1, x2, y2)
  [(x1 + x2).fdiv(2), (y1 + y2).fdiv(2)]
end
draw_hexagon(bbox, radius, background) click to toggle source
# File lib/mahoujin/graphics/renderer.rb, line 266
def draw_hexagon(bbox, radius, background)
  left, top, right, bottom = bbox.left, bbox.top, bbox.right, bbox.bottom
  centerline = bbox.centerline

  a =           left, centerline
  b =  left + radius, top
  c = right - radius, top
  d =          right, centerline
  e = right - radius, bottom
  f =  left + radius, bottom

  @ctx.move_to(*a).line_to(*b).line_to(*c).line_to(*d).line_to(*e).line_to(*f).close_path
  @ctx.save { @ctx.set_source_color(background).fill_preserve }
  @ctx.stroke
end
draw_infinite_loop_line(v1, v2, v3, v4, h1, h2, radius) click to toggle source
# File lib/mahoujin/graphics/renderer.rb, line 294
def draw_infinite_loop_line(v1, v2, v3, v4, h1, h2, radius)
  a = v2, h2
  b = v2, h1
  c = v3, h1
  d = v3, h2
  e = v1, (h1 + h2).fdiv(2)
  f = v4, (h1 + h2).fdiv(2)

  draw_rounded_corner(*e, a[0] - e[0], a[1] - e[1], radius, :sw)
  draw_rounded_corner(e[0], b[1], b[0] - e[0], e[1] - b[1], radius, :nw)
  draw_straight_line(*b, *c)
  draw_rounded_corner(*c, f[0] - c[0], f[1] - c[1], radius, :ne)
  draw_rounded_corner(d[0], f[1], f[0] - d[0], d[1] - f[1], radius, :se)
end
draw_rectangle(bbox, background) click to toggle source
# File lib/mahoujin/graphics/renderer.rb, line 282
def draw_rectangle(bbox, background)
  @ctx.rounded_rectangle(bbox.left, bbox.top, bbox.width, bbox.height, 0)
  @ctx.save { @ctx.set_source_color(background).fill_preserve }
  @ctx.stroke
end
draw_rounded_corner(x, y, width, height, radius, corner) click to toggle source
# File lib/mahoujin/graphics/renderer.rb, line 328
def draw_rounded_corner(x, y, width, height, radius, corner)
  x1, y1 = x, y
  x2, y2 = x + width, y + height
  radius = [width, height, radius].min

  case corner
  when :nw
    @ctx.move_to(x1, y2).line_to(x1, y1 + radius).stroke
    @ctx.arc(x1 + radius, y1 + radius, radius, 180 * (Math::PI / 180), 270 * (Math::PI / 180)).stroke
    @ctx.move_to(x1 + radius, y1).line_to(x2, y1).stroke
  when :ne
    @ctx.move_to(x1, y1).line_to(x2 - radius, y1).stroke
    @ctx.arc(x2 - radius, y1 + radius, radius, 270 * (Math::PI / 180), 360 * (Math::PI / 180)).stroke
    @ctx.move_to(x2, y1 + radius).line_to(x2, y2).stroke
  when :se
    @ctx.move_to(x2, y1).line_to(x2, y2 - radius).stroke
    @ctx.arc(x2 - radius, y2 - radius, radius,   0 * (Math::PI / 180),  90 * (Math::PI / 180)).stroke
    @ctx.move_to(x2 - radius, y2).line_to(x1, y2).stroke
  when :sw
    @ctx.move_to(x2, y2).line_to(x1 + radius, y2).stroke
    @ctx.arc(x1 + radius, y2 - radius, radius,  90 * (Math::PI / 180), 180 * (Math::PI / 180)).stroke
    @ctx.move_to(x1, y2 - radius).line_to(x1, y1).stroke
  end
end
draw_rounded_rectangle(bbox, radius, background) click to toggle source
# File lib/mahoujin/graphics/renderer.rb, line 288
def draw_rounded_rectangle(bbox, radius, background)
  @ctx.rounded_rectangle(bbox.left, bbox.top, bbox.width, bbox.height, radius)
  @ctx.save { @ctx.set_source_color(background).fill_preserve }
  @ctx.stroke
end
draw_snake_case_line(v1, v2, v3, v4, h1, h2, radius) click to toggle source
# File lib/mahoujin/graphics/renderer.rb, line 309
def draw_snake_case_line(v1, v2, v3, v4, h1, h2, radius)
  a = v1, h1
  b = v2, h2
  c = v3, h2
  d = v4, h1
  e = center_point(*a, *b)
  f = center_point(*c, *d)

  draw_rounded_corner(*a, e[0] - a[0], e[1] - a[1], radius, :ne)
  draw_rounded_corner(*e, b[0] - e[0], b[1] - e[1], radius, :sw)
  draw_straight_line(*b, *c)
  draw_rounded_corner(c[0], f[1], f[0] - c[0], c[1] - f[1], radius, :se)
  draw_rounded_corner(f[0], d[1], d[0] - f[0], f[1] - d[1], radius, :nw)
end
draw_straight_line(x1, y1, x2, y2) click to toggle source
# File lib/mahoujin/graphics/renderer.rb, line 324
def draw_straight_line(x1, y1, x2, y2)
  @ctx.move_to(x1, y1).line_to(x2, y2).stroke
end
draw_text(bbox, text, font) click to toggle source
# File lib/mahoujin/graphics/renderer.rb, line 253
def draw_text(bbox, text, font)
  e = measure_text_metrics(text, font)
  x = bbox.left + bbox.width.fdiv(2) - ( e.width.fdiv(2) + e.x_bearing)
  y = bbox.top + bbox.height.fdiv(2) - (e.height.fdiv(2) + e.y_bearing)

  @ctx.save do
    @ctx.select_font_face(@ctx.font_face.family, @ctx.font_face.slant, Cairo::FONT_WEIGHT_BOLD) if font[:bold]
    @ctx.select_font_face(@ctx.font_face.family, Cairo::FONT_SLANT_ITALIC, @ctx.font_face.weight) if font[:italic]
    @ctx.set_font_size(font[:size]) if font[:size]
    @ctx.move_to(x, y).show_text(text)
  end
end
fill_bbox(bbox, background) click to toggle source
# File lib/mahoujin/graphics/renderer.rb, line 245
def fill_bbox(bbox, background)
  @ctx.save do
    @ctx.rounded_rectangle(bbox.left, bbox.top, bbox.width, bbox.height, 0)
    @ctx.set_source_color(background)
    @ctx.fill
  end
end
graphics_corner_radius() click to toggle source
# File lib/mahoujin/graphics/renderer.rb, line 60
def graphics_corner_radius
  @style.basic[:corner][:radius] * graphics_unit
end
graphics_font() click to toggle source
# File lib/mahoujin/graphics/renderer.rb, line 52
def graphics_font
  @style.basic[:font]
end
graphics_unit() click to toggle source
# File lib/mahoujin/graphics/renderer.rb, line 56
def graphics_unit
  @style.basic[:unit]
end
graphics_value(atom, attribute) click to toggle source
# File lib/mahoujin/graphics/renderer.rb, line 64
def graphics_value(atom, attribute)
  @style.query(atom.class, attribute)
end
initialize_graphics_environment(bbox) click to toggle source
# File lib/mahoujin/graphics/renderer.rb, line 40
def initialize_graphics_environment(bbox)
  @ctx.rectangle(0, 0, bbox.width, bbox.height)
  @ctx.set_source_color(:white)
  @ctx.fill

  @ctx.set_source_color(:black)
  @ctx.set_line_width(@style.basic[:line][:width])

  @ctx.select_font_face(@style.basic[:font][:family], Cairo::FONT_SLANT_NORMAL, Cairo::FONT_WEIGHT_NORMAL)
  @ctx.set_font_size(@style.basic[:font][:size])
end
measure_text_metrics(text, font) click to toggle source
# File lib/mahoujin/graphics/renderer.rb, line 68
def measure_text_metrics(text, font)
  @ctx.save
  @ctx.select_font_face(@ctx.font_face.family, @ctx.font_face.slant, Cairo::FONT_WEIGHT_BOLD) if font[:bold]
  @ctx.select_font_face(@ctx.font_face.family, Cairo::FONT_SLANT_ITALIC, @ctx.font_face.weight) if font[:italic]
  @ctx.set_font_size(font[:size]) if font[:size]
  @ctx.text_extents(text)
ensure
  @ctx.restore
end
render_atom(atom) click to toggle source
# File lib/mahoujin/graphics/renderer.rb, line 78
def render_atom(atom)
  send "render_#{atom.class.underscore_name}", atom
end
render_choice(atom) click to toggle source
# File lib/mahoujin/graphics/renderer.rb, line 106
def render_choice(atom)
  atom.atoms.each { |a| render_atom(a) }

  reference = atom.atoms.max(&->(a1, a2) { a1.bbox.width <=> a2.bbox.width })
  centerline = reference.bbox.centerline
  v1, v2, v3, v4 = atom.bbox.left, reference.bbox.left, reference.bbox.right, atom.bbox.right

  a = v1, centerline
  b = v2, centerline
  c = v3, centerline
  d = v4, centerline
  draw_straight_line(*a, *b)
  draw_straight_line(*c, *d)

  if (index = atom.atoms.find_index(reference)) != 0
    upper = atom.atoms[index - 1]
    s = (v1 + v2).fdiv(2), (upper.bbox.centerline + centerline).fdiv(2)
    e = (v3 + v4).fdiv(2), (upper.bbox.centerline + centerline).fdiv(2)

    draw_rounded_corner(a[0], s[1], s[0] - a[0], a[1] - s[1], graphics_corner_radius, :se)
    draw_rounded_corner(e[0], e[1], d[0] - e[0], d[1] - e[1], graphics_corner_radius, :sw)
    atom.atoms[0...index].each do |ua|
      w =            v2, ua.bbox.centerline
      x =  ua.bbox.left, ua.bbox.centerline
      y = ua.bbox.right, ua.bbox.centerline
      z =            v3, ua.bbox.centerline
      draw_rounded_corner(s[0], w[1], w[0] - s[0], s[1] - w[1], graphics_corner_radius, :nw)
      draw_straight_line(*w, *x)
      draw_straight_line(*y, *z)
      draw_rounded_corner(z[0], z[1], e[0] - z[0], e[1] - z[1], graphics_corner_radius, :ne)
    end
  end

  if (index = atom.atoms.find_index(reference)) != atom.atoms.size - 1
    lower = atom.atoms[index + 1]
    s = (v1 + v2).fdiv(2), (lower.bbox.centerline + centerline).fdiv(2)
    e = (v3 + v4).fdiv(2), (lower.bbox.centerline + centerline).fdiv(2)

    draw_rounded_corner(a[0], a[1], s[0] - a[0], s[1] - a[1], graphics_corner_radius, :ne)
    draw_rounded_corner(e[0], d[1], d[0] - e[0], e[1] - d[1], graphics_corner_radius, :nw)
    atom.atoms[(index + 1)..-1].each do |la|
      w =            v2, la.bbox.centerline
      x =  la.bbox.left, la.bbox.centerline
      y = la.bbox.right, la.bbox.centerline
      z =            v3, la.bbox.centerline
      draw_rounded_corner(s[0], s[1], w[0] - s[0], w[1] - s[1], graphics_corner_radius, :sw)
      draw_straight_line(*w, *x)
      draw_straight_line(*y, *z)
      draw_rounded_corner(z[0], e[1], e[0] - z[0], z[1] - e[1], graphics_corner_radius, :se)
    end
  end; nil
end
render_concatenation(atom) click to toggle source
# File lib/mahoujin/graphics/renderer.rb, line 159
def render_concatenation(atom)
  atom.atoms.each { |a| render_atom(a) }

  centerline = atom.bbox.centerline
  atom.atoms.each_cons(2) do |a1, a2|
    draw_straight_line(a1.bbox.right, centerline, a2.bbox.left, centerline)
  end
end
render_exception(atom) click to toggle source
# File lib/mahoujin/graphics/renderer.rb, line 222
def render_exception(atom)
  font = graphics_value(atom, :font)
  padding = (atom.bbox.width - measure_text_metrics(atom.content, font).width).fdiv(2)

  draw_hexagon(atom.bbox, padding * 0.618, graphics_value(atom, :background))
  draw_text(atom.bbox, atom.content, font)
end
render_grouping(atom) click to toggle source
# File lib/mahoujin/graphics/renderer.rb, line 210
def render_grouping(atom)
  render_atom(atom.atom)
end
render_in_png_format(atom, iostream, style) click to toggle source
# File lib/mahoujin/graphics/renderer.rb, line 15
def render_in_png_format(atom, iostream, style)
  layout = Graphics::Layout.new
  layout.layout(atom, style, :PNG)

  Cairo::ImageSurface.new(atom.bbox.width, atom.bbox.height) do |surface|
    @ctx = Cairo::Context.new(surface)
    @style = style
    initialize_graphics_environment(atom.bbox)
    render_atom(atom)
    @ctx.target.write_to_png(iostream)
  end
end
render_in_svg_format(atom, iostream, style) click to toggle source
# File lib/mahoujin/graphics/renderer.rb, line 28
def render_in_svg_format(atom, iostream, style)
  layout = Graphics::Layout.new
  layout.layout(atom, style, :SVG)

  Cairo::SVGSurface.new(iostream, atom.bbox.width, atom.bbox.height) do |surface|
    @ctx = Cairo::Context.new(surface)
    @style = style
    initialize_graphics_environment(atom.bbox)
    render_atom(atom)
  end
end
render_non_terminal(atom) click to toggle source
# File lib/mahoujin/graphics/renderer.rb, line 230
def render_non_terminal(atom)
  font = graphics_value(atom, :font)

  draw_rectangle(atom.bbox, graphics_value(atom, :background))
  draw_text(atom.bbox, atom.content, font)
end
render_optional(atom) click to toggle source
# File lib/mahoujin/graphics/renderer.rb, line 187
def render_optional(atom)
  render_atom(atom.atom)

  innerbbox, outerbbox, centerline = atom.atom.bbox, atom.bbox, atom.bbox.centerline
  v1, v2, v3, v4 = outerbbox.left, innerbbox.left, innerbbox.right, outerbbox.right

  draw_straight_line(v1, centerline, v2, centerline)
  draw_straight_line(v3, centerline, v4, centerline)
  draw_snake_case_line(v1, v2, v3, v4, centerline, outerbbox.bottom, graphics_corner_radius)
end
render_prose(atom) click to toggle source
# File lib/mahoujin/graphics/renderer.rb, line 214
def render_prose(atom)
  font = graphics_value(atom, :font)
  padding = (atom.bbox.width - measure_text_metrics(atom.content, font).width).fdiv(2)

  draw_hexagon(atom.bbox, padding * 0.618, graphics_value(atom, :background))
  draw_text(atom.bbox, atom.content, font)
end
render_rule(atom) click to toggle source
# File lib/mahoujin/graphics/renderer.rb, line 86
def render_rule(atom)
  render_atom(atom.rule_name_atom)
  render_atom(atom.atom)

  innerbbox, outerbbox, centerline = atom.atom.bbox, atom.bbox, atom.bbox.centerline
  v1, v2, v3, v4 = outerbbox.left, innerbbox.left, innerbbox.right, outerbbox.right

  draw_straight_line(v1, centerline, v2, centerline)
  draw_straight_line(v3, centerline, v4, centerline)

  h = graphics_unit
  draw_straight_line(v1, centerline - h.fdiv(2), v1, centerline + h.fdiv(2))
  draw_straight_line(v4, centerline - h.fdiv(2), v4, centerline + h.fdiv(2))
end
render_rule_name(atom) click to toggle source
# File lib/mahoujin/graphics/renderer.rb, line 101
def render_rule_name(atom)
  font = graphics_value(atom, :font)
  draw_text(atom.bbox, atom.content, font)
end
render_specific_repetition(atom) click to toggle source
# File lib/mahoujin/graphics/renderer.rb, line 168
def render_specific_repetition(atom)
  render_atom(atom.atom)

  innerbbox, outerbbox, centerline = atom.atom.bbox, atom.bbox, atom.bbox.centerline
  v1, v2, v3, v4 = outerbbox.left, innerbbox.left, innerbbox.right, outerbbox.right

  draw_straight_line(v1, centerline, v2, centerline)
  draw_straight_line(v3, centerline, v4, centerline)
  draw_infinite_loop_line(v1, v2, v3, v4, outerbbox.top, centerline, graphics_corner_radius)

  text = "{ #{atom.number} }"
  font = { size: graphics_font[:size] * 0.8 }
  e = measure_text_metrics(text, font)
  x, y = (v1 + v4).fdiv(2) - e.width.fdiv(2), outerbbox.top - e.height.fdiv(2)
  bbox = Graphics::Utilities::Rectangle.new(x, y, x + e.width, y + e.height)
  fill_bbox(bbox, :white)
  draw_text(bbox, text, font)
end
render_syntax(atom) click to toggle source
# File lib/mahoujin/graphics/renderer.rb, line 82
def render_syntax(atom)
  atom.atoms.each { |a| render_atom(a) }
end
render_terminal(atom) click to toggle source
# File lib/mahoujin/graphics/renderer.rb, line 237
def render_terminal(atom)
  font = graphics_value(atom, :font)
  padding = (atom.bbox.width - measure_text_metrics(atom.content, font).width).fdiv(2)

  draw_rounded_rectangle(atom.bbox, padding, graphics_value(atom, :background))
  draw_text(atom.bbox, atom.content, font)
end
render_zero_or_more_repetition(atom) click to toggle source
# File lib/mahoujin/graphics/renderer.rb, line 198
def render_zero_or_more_repetition(atom)
  render_atom(atom.atom)

  innerbbox, outerbbox, centerline = atom.atom.bbox, atom.bbox, atom.bbox.centerline
  v1, v2, v3, v4 = outerbbox.left, innerbbox.left, innerbbox.right, outerbbox.right

  draw_straight_line(v1, centerline, v2, centerline)
  draw_straight_line(v3, centerline, v4, centerline)
  draw_snake_case_line(v1, v1 + (v2 - v1).fdiv(3) * 2, v4 - (v4 - v3).fdiv(3) * 2, v4, centerline, outerbbox.bottom, graphics_corner_radius)
  draw_infinite_loop_line(v2 - (v2 - v1).fdiv(3) * 1, v2, v3, v3 + (v4 - v3).fdiv(3) * 1, outerbbox.top, centerline, graphics_corner_radius)
end