require 'yaml' module Mork # The Grid is a set of expectations on what the response sheet should look like # It knows nothing about the actual scanned image # All returned values are in the arbitrary units given in the configuration file class Grid def initialize(options = {}) case options.class.to_s when "Hash" @params = DGRID.merge symbolize options when "String" @params = DGRID.merge symbolize YAML.load_file(options) else raise "Invalid options parameter: #{options.class.inspect}" end end # symbolize(object) # # recursively turn hash keys into symbols. pasted from # http://stackoverflow.com/questions/800122/best-way-to-convert-strings-to-symbols-in-hash def symbolize(obj) return obj.inject({}){|memo,(k,v)| memo[k.to_sym] = symbolize(v); memo} if obj.is_a? Hash return obj.inject([]){|memo,v | memo << symbolize(v); memo} if obj.is_a? Array return obj end def options @params end def set_page_size(x, y) @px = x.to_f @py = y.to_f end def max_questions columns * rows end def max_choices_per_question @params[:items][:max_cells].to_i end def code_bits @params[:code][:bits].to_i end # ==================================================== # = Returning {x, y, w, h} hashes for area locations = # ==================================================== # {} = rm_search_area(x, y) # # the 4 values needed to locate a single registration mark def rm_search_area(corner, i) { x: (ppu_x * rmx(corner, i)).round, y: (ppu_y * rmy(corner, i)).round, w: (ppu_x * (reg_search + reg_step * i)).round, h: (ppu_y * (reg_search + reg_step * i)).round } end def rm_edgy_x (ppu_x * reg_radius).round + 5 end def rm_edgy_y (ppu_y * reg_radius).round + 5 end def rm_max_search_area_side (ppu_x * page_width / 4).round end def choice_cell_area(q, c) cell_area cell_x(q, c), cell_y(q) end def ctrl_area_dark cell_area ctrl_cell_x, ctrl_cell_y end def ctrl_area_light cell_area ctrl_cell_x + cell_spacing, ctrl_cell_y end def cal_area_white code_cell_area -1 end def cal_area_black code_cell_area 0 end def code_bit_area(i) raise "Invalid code bit" if i >= code_bits code_cell_area i+1 end # ============ # = to Prawn = # ============ def pdf_page_size [page_width.mm, page_height.mm] end def pdf_margins reg_margin.mm end def pdf_reg_marks r = reg_radius.mm [ { p: [0, 0 ], r: r }, { p: [0, reg_frame_height.mm], r: r }, { p: [reg_frame_width.mm, reg_frame_height.mm], r: r }, { p: [reg_frame_width.mm, 0 ], r: r } ] end def pdf_dark_calibration_area pdf_code_cell_area 0 end def pdf_code_areas_for(code) a = [] code_bits.times do |bit| a << pdf_code_cell_area(bit+1) if code[bit] == 1 end a end def pdf_code_bit_areas (1..code_bits).collect do |bit| pdf_code_cell_area bit end end def pdf_code_cell_area(i) { p: [code_cell_x(i).mm, (reg_frame_height - code_y).mm], w: code_width.mm, h: code_height.mm * 2 } end def pdf_choice_cell_area(q, c) { p: [cell_x(q, c).mm, (reg_frame_height - cell_y(q)).mm], w: cell_width.mm, h: cell_height.mm } end def pdf_choice_letter_xy(q, c) [ cell_x(q, c).mm + 2.mm, (reg_frame_height - cell_y(q)).mm - (cell_height*2) ] end def pdf_qnum_xy(q) [ cell_x(q, 0).mm - pdf_qnum_width - @params[:items][:number_margin].to_f.mm, (reg_frame_height - cell_y(q)).mm - cell_height ] end def pdf_qnum_size @params[:items][:number_size].to_f end def pdf_chlett_size @params[:items][:letter_size].to_f end def pdf_qnum_width @params[:items][:number_width].to_f.mm end def pdf_header_xy(k) [ @params[:header][k][:left].to_f.mm, (reg_frame_height - @params[:header][k][:top].to_f).mm ] end def pdf_header_padding(k) [ 1.mm, pdf_header_height(k) - 1.mm ] end def pdf_header_width(k) @params[:header][k][:width].to_f.mm end def pdf_header_height(k) @params[:header][k][:height].to_f.mm end def pdf_header_size(k) @params[:header][k][:size].to_f end def pdf_header_boxed?(k) @params[:header][k][:box] == true end def pdf_ctrl_area_dark { p: [pdf_control_xy[0]+pdf_control_width+ctrl_margin.mm, pdf_control_xy[1]], w: cell_width.mm, h: cell_height.mm } end def pdf_ctrl_area_light { p: [ cell_spacing.mm + pdf_control_xy[0] + pdf_control_width + ctrl_margin.mm, pdf_control_xy[1] ], w: cell_width.mm, h: cell_height.mm } end def pdf_dark_control_letter_xy xy = pdf_ctrl_area_dark[:p] [ xy[0] + 2.mm, xy[1] - 4.mm ] end def pdf_light_control_letter_xy xy = pdf_ctrl_area_light[:p] [ xy[0] + 2.mm, xy[1] - 4.mm ] end def pdf_control_width @params[:control][:width].to_f.mm end def pdf_control_xy [ @params[:control][:left].to_f.mm, (reg_frame_height - ctrl_cell_y).mm ] end def pdf_control_size @params[:control][:size] end private def cx @px / reg_frame_width end def cy @py / reg_frame_height end def ppu_x # horizontal pixels per unit of length (mm by def) @px / page_width end def ppu_y # vertical pixels per unit of length (mm by def) @py / page_height end # {} = cell_area(x, y) # # the 4 values needed to locate a single cell area def cell_area(x,y) { x: (cx * x ).round, y: (cy * y ).round, w: (cx * cell_width ).round, h: (cy * cell_height ).round } end # cell_y(q) # # the distance from the registration frame to the top edge # of all choice cells in the q-th question def cell_y(q) first_y + item_spacing * (q % rows) - cell_height / 2 end # cell_x(q,c) # # the distance from the registration frame to the left edge # of the c-th choice cell of the q-th question def cell_x(q,c) first_x + column_width * (q / rows) + cell_spacing * c - cell_width / 2 end # ============== # = sheet code = # ============== def code_cell_area(i) { x: (cx * code_cell_x(i)).round, y: (cy * code_y ).round, w: (cx * code_width ).round, h: (cy * code_height ).round } end def code_cell_x(i) @params[:code][:left] + @params[:code][:spacing] * i end def code_y reg_frame_height - code_height end # ====================== # = registration sides = # ====================== def rmx(corner, i) case corner when :tl; reg_off when :tr; page_width - reg_search - reg_off - reg_step * i when :br; page_width - reg_search - reg_off - reg_step * i when :bl; reg_off end end def rmy(corner, i) case corner when :tl; reg_off when :tr; reg_off when :br; page_height - reg_search - reg_off - reg_step * i when :bl; page_height - reg_search - reg_off - reg_step * i end end def reg_step reg_radius end # =============================== # = Simple parameter extraction = # =============================== def ctrl_cell_x() @params[:control][:left].to_f + ctrl_width + ctrl_margin end def ctrl_margin() @params[:control][:margin].to_f end def ctrl_width() @params[:control][:width].to_f end def ctrl_cell_y() @params[:control][:top].to_f end def code_height() @params[:code][:height].to_f end def code_width() @params[:code][:width].to_f end def page_width() @params[:page_size][:width].to_f end def page_height() @params[:page_size][:height].to_f end def cell_width() @params[:items][:cell_width].to_f end def cell_height() @params[:items][:cell_height].to_f end def cell_spacing() @params[:items][:x_spacing].to_f end def item_spacing() @params[:items][:y_spacing].to_f end def column_width() @params[:items][:column_width].to_f end def row_spacing() @params[:items][:y_spacing].to_f end def first_x() @params[:items][:first_x].to_f end def first_y() @params[:items][:first_y].to_f end def rows() @params[:items][:rows] end def columns() @params[:items][:columns] end def reg_margin() @params[:regmarks][:margin].to_f end def reg_search() @params[:regmarks][:search].to_f end def reg_radius() @params[:regmarks][:radius].to_f end def reg_frame_width() page_width - reg_margin * 2 end def reg_frame_height() page_height - reg_margin * 2 end def reg_off() @params[:regmarks][:offset].to_f end end end