class Mahoujin::Graphics::Layout

Public Instance Methods

layout(atom, style, format) click to toggle source
# File lib/mahoujin/graphics/layout.rb, line 4
def layout(atom, style, format)
  case format
  when :PNG then layout_in_png_format(atom, style)
  when :SVG then layout_in_svg_format(atom, style)
  else raise
  end
end

Private Instance Methods

expand_atom_bbox(atom, padding) click to toggle source
# File lib/mahoujin/graphics/layout.rb, line 197
def expand_atom_bbox(atom, padding)
  bbox = atom.bbox
  x, y = bbox.left, bbox.top

  bbox.top    -= padding.fetch(:top,    0) * graphics_unit
  bbox.bottom += padding.fetch(:bottom, 0) * graphics_unit
  bbox.left   -= padding.fetch(:left,   0) * graphics_unit
  bbox.right  += padding.fetch(:right,  0) * graphics_unit

  move_recursively(atom, :top,  y)
  move_recursively(atom, :left, x)
end
graphics_unit() click to toggle source
# File lib/mahoujin/graphics/layout.rb, line 42
def graphics_unit
  @style.basic[:unit]
end
graphics_value(atom, attribute) click to toggle source
# File lib/mahoujin/graphics/layout.rb, line 46
def graphics_value(atom, attribute)
  @style.query(atom.class, attribute)
end
initialize_graphics_environment() click to toggle source
# File lib/mahoujin/graphics/layout.rb, line 32
def initialize_graphics_environment
  @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])

  @style.basic[:unit] ||= @ctx.text_extents([*('A'..'Z'), *('a'..'z'), *('0'..'9')].join).height
end
initialize_text_atom(atom, bsize, font) click to toggle source
# File lib/mahoujin/graphics/layout.rb, line 186
def initialize_text_atom(atom, bsize, font)
  bbox = atom.bbox
  text = atom.content

  bbox.top    = 0
  bbox.left   = 0
  bbox.width  = bsize.fetch(:width,  0) * graphics_unit + measure_text_metrics(text, font).width
  bbox.height = bsize.fetch(:height, 0) * graphics_unit
  bbox.centerline = bbox.top + bbox.height.fdiv(2)
end
layout_atom(atom) click to toggle source
# File lib/mahoujin/graphics/layout.rb, line 60
def layout_atom(atom)
  send "layout_#{atom.class.underscore_name}", atom
end
layout_choice(atom) click to toggle source
# File lib/mahoujin/graphics/layout.rb, line 107
def layout_choice(atom)
  atom.atoms.each { |a| layout_atom(a) }

  reference = atom.atoms.max(&->(a1, a2) { a1.bbox.width <=> a2.bbox.width })
  space = graphics_value(atom, :space) * graphics_unit
  top = 0

  atom.atoms.each do |a|
    move_recursively(a, :top, top)
    top += a.bbox.height + space
  end

  bbox = Graphics::Utilities::Rectangle.new(0, 0, reference.bbox.width, top - space)
  bbox.centerline = reference.bbox.centerline
  atom.bbox.sync_with(bbox)

  expand_atom_bbox(atom, graphics_value(atom, :padding))
end
layout_concatenation(atom) click to toggle source
# File lib/mahoujin/graphics/layout.rb, line 126
def layout_concatenation(atom)
  atom.atoms.each { |a| layout_atom(a) }

  reference = atom.atoms.max(&->(a1, a2) { a1.bbox.height <=> a2.bbox.height })
  space = graphics_value(atom, :space) * graphics_unit
  left = 0

  atom.atoms.each do |a|
    move_recursively(a, :top, reference.bbox.centerline + (a.bbox.top - a.bbox.centerline))
    move_recursively(a, :left, left)
    left += a.bbox.width + space
  end

  bbox = Graphics::Utilities::Rectangle.new(0, 0, left - space, reference.bbox.height)
  bbox.centerline = reference.bbox.centerline
  atom.bbox.sync_with(bbox)
end
layout_exception(atom) click to toggle source
# File lib/mahoujin/graphics/layout.rb, line 174
def layout_exception(atom)
  initialize_text_atom(atom, graphics_value(atom, :bsize), graphics_value(atom, :font))
end
layout_grouping(atom) click to toggle source
# File lib/mahoujin/graphics/layout.rb, line 165
def layout_grouping(atom)
  layout_atom(atom.atom)
  atom.bbox.sync_with(atom.atom.bbox)
end
layout_in_png_format(atom, style) click to toggle source
# File lib/mahoujin/graphics/layout.rb, line 14
def layout_in_png_format(atom, style)
  Cairo::ImageSurface.new(200, 200) do |surface|
    @ctx = Cairo::Context.new(surface)
    @style = style
    initialize_graphics_environment
    layout_atom(atom)
  end
end
layout_in_svg_format(atom, style) click to toggle source
# File lib/mahoujin/graphics/layout.rb, line 23
def layout_in_svg_format(atom, style)
  Cairo::SVGSurface.new(StringIO.new, 200, 200) do |surface|
    @ctx = Cairo::Context.new(surface)
    @style = style
    initialize_graphics_environment
    layout_atom(atom)
  end
end
layout_non_terminal(atom) click to toggle source
# File lib/mahoujin/graphics/layout.rb, line 178
def layout_non_terminal(atom)
  initialize_text_atom(atom, graphics_value(atom, :bsize), graphics_value(atom, :font))
end
layout_optional(atom) click to toggle source
# File lib/mahoujin/graphics/layout.rb, line 151
def layout_optional(atom)
  layout_atom(atom.atom)
  atom.bbox.sync_with(atom.atom.bbox)

  expand_atom_bbox(atom, graphics_value(atom, :padding))
end
layout_prose(atom) click to toggle source
# File lib/mahoujin/graphics/layout.rb, line 170
def layout_prose(atom)
  initialize_text_atom(atom, graphics_value(atom, :bsize), graphics_value(atom, :font))
end
layout_rule(atom) click to toggle source
# File lib/mahoujin/graphics/layout.rb, line 82
def layout_rule(atom)
  layout_atom(atom.rule_name_atom)
  layout_atom(atom.atom)

  space = graphics_value(atom, :space) * graphics_unit
  top = 0

  atoms = [atom.rule_name_atom, atom.atom]
  atoms.each do |a|
    move_recursively(a, :top, top)
    top += a.bbox.height + space
  end

  bbox = Graphics::Utilities::Rectangle.new(0, 0, atoms.map(&:bbox).map(&:width).max, top - space)
  bbox.centerline = atom.atom.bbox.centerline
  atom.bbox.sync_with(bbox)

  expand_atom_bbox(atom, graphics_value(atom, :padding))
  move_recursively(atom.rule_name_atom, :left, 0)
end
layout_rule_name(atom) click to toggle source
# File lib/mahoujin/graphics/layout.rb, line 103
def layout_rule_name(atom)
  initialize_text_atom(atom, graphics_value(atom, :bsize), graphics_value(atom, :font))
end
layout_specific_repetition(atom) click to toggle source
# File lib/mahoujin/graphics/layout.rb, line 144
def layout_specific_repetition(atom)
  layout_atom(atom.atom)
  atom.bbox.sync_with(atom.atom.bbox)

  expand_atom_bbox(atom, graphics_value(atom, :padding))
end
layout_syntax(atom) click to toggle source
# File lib/mahoujin/graphics/layout.rb, line 64
def layout_syntax(atom)
  atom.atoms.each { |a| layout_atom(a) }

  space = graphics_value(atom, :space) * graphics_unit
  top = 0

  atom.atoms.each do |a|
    move_recursively(a, :top, top)
    top += a.bbox.height + space
  end

  bbox = Graphics::Utilities::Rectangle.new(0, 0, atom.atoms.map(&:bbox).map(&:width).max, top - space)
  bbox.centerline = 0
  atom.bbox.sync_with(bbox)

  expand_atom_bbox(atom, graphics_value(atom, :padding))
end
layout_terminal(atom) click to toggle source
# File lib/mahoujin/graphics/layout.rb, line 182
def layout_terminal(atom)
  initialize_text_atom(atom, graphics_value(atom, :bsize), graphics_value(atom, :font))
end
layout_zero_or_more_repetition(atom) click to toggle source
# File lib/mahoujin/graphics/layout.rb, line 158
def layout_zero_or_more_repetition(atom)
  layout_atom(atom.atom)
  atom.bbox.sync_with(atom.atom.bbox)

  expand_atom_bbox(atom, graphics_value(atom, :padding))
end
measure_text_metrics(text, font) click to toggle source
# File lib/mahoujin/graphics/layout.rb, line 50
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
move_recursively(atom, bound, coordinate) click to toggle source
# File lib/mahoujin/graphics/layout.rb, line 210
def move_recursively(atom, bound, coordinate)
  difference = coordinate - atom.bbox.send(bound)
  atom.bbox.send("move_#{bound}", coordinate)
  atom.atom.tap(&->(a) { move_recursively(a, bound, a.bbox.send(bound) + difference) }) if atom.respond_to?(:atom)
  atom.atoms.each(&->(a) { move_recursively(a, bound, a.bbox.send(bound) + difference) }) if atom.respond_to?(:atoms)
  atom.rule_name_atom.tap(&->(a) { move_recursively(a, bound, a.bbox.send(bound) + difference) }) if atom.is_a?(Atoms::Rule)
end