mork 0.0.1 → 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore CHANGED
@@ -2,3 +2,6 @@
2
2
  .bundle
3
3
  Gemfile.lock
4
4
  pkg/*
5
+ tmp/*
6
+ .rvmrc
7
+ .rspec
data/Gemfile CHANGED
@@ -1,4 +1,3 @@
1
1
  source "http://rubygems.org"
2
-
3
- # Specify your gem's dependencies in mork.gemspec
2
+ gem 'prawn', git: 'git://github.com/prawnpdf/prawn', submodules: true
4
3
  gemspec
data/Guardfile ADDED
@@ -0,0 +1,10 @@
1
+ guard 'rspec', all_after_pass: false do
2
+ watch(%r{^spec/.+_spec\.rb$})
3
+ watch(%r{^lib/mork/(.+)\.rb$}) { |m| "spec/mork/#{m[1]}_spec.rb" }
4
+ end
5
+
6
+ guard :shell do
7
+ watch "tmp/code_sample.pdf" do
8
+ system "open tmp/code_sample.pdf"
9
+ end
10
+ end
data/config/grids.yml ADDED
@@ -0,0 +1,42 @@
1
+ default:
2
+ page_size:
3
+ width: 210.0
4
+ height: 297.0
5
+ header:
6
+ name:
7
+ top: 3
8
+ left: 7.5
9
+ width: 120
10
+ size: 14
11
+ code:
12
+ top: 3
13
+ left: 170
14
+ width: 25
15
+ size: 12
16
+ title:
17
+ top: 12
18
+ left: 7.5
19
+ width: 180.0
20
+ size: 12
21
+ date:
22
+ top: 18
23
+ left: 7.5
24
+ width: 180.0
25
+ size: 12
26
+ responses:
27
+ columns: 4
28
+ column_width: 50.0
29
+ rows: 40
30
+ # from the top-left registration mark
31
+ # to the center of the first choice cell
32
+ first_x: 10.5
33
+ first_y: 35.5
34
+ # between choices
35
+ x_spacing: 7.0
36
+ # between rows
37
+ y_spacing: 6.0
38
+ # darkened area
39
+ cell_width: 6.0
40
+ cell_height: 4.0
41
+ # the maximum number of choices per question
42
+ max_cells: 5
data/lib/mork.rb CHANGED
@@ -1,5 +1,9 @@
1
1
  require "mork/version"
2
-
3
- module Mork
4
- # Your code goes here...
5
- end
2
+ require 'mork/sheet'
3
+ require 'mork/grid'
4
+ require 'mork/grid_const'
5
+ require 'mork/mimage'
6
+ require 'mork/mimage_list'
7
+ require 'mork/report'
8
+ require 'mork/npatch'
9
+ require 'mork/sheet_pdf'
data/lib/mork/grid.rb ADDED
@@ -0,0 +1,307 @@
1
+ require 'yaml'
2
+
3
+ module Mork
4
+ # The Grid is a set of expectations on what the response sheet should look like
5
+ # It knows nothing about the actual scanned image
6
+ # All returned values are in the arbitrary units given in the configuration file
7
+ class Grid
8
+
9
+ def initialize(type = :default, fname="config/grids.yml")
10
+ # from the full YAML file, only get the requested grid type
11
+ c = YAML.load_file(fname)
12
+ @params = c[type.to_s]
13
+ end
14
+
15
+ def set_page_size(x, y)
16
+ @px = x.to_f
17
+ @py = y.to_f
18
+ end
19
+
20
+ def default_chlist
21
+ [@params["responses"]["max_cells"]] * max_questions
22
+ end
23
+
24
+ def max_questions
25
+ columns * rows
26
+ end
27
+
28
+ def max_choices_per_question
29
+ @params["responses"]["max_cells"]
30
+ end
31
+
32
+ def code_bits
33
+ CODE_BITS
34
+ end
35
+
36
+ def header
37
+ @params["header"]
38
+ end
39
+
40
+ def reg_mark_search_area(n)
41
+ rmp = REG_MARGIN + REG_SEARCH
42
+ rmm = REG_MARGIN - REG_SEARCH
43
+ case n
44
+ when :top_left
45
+ reg_mark_sa rmm, rmm
46
+ when :top_right
47
+ reg_mark_sa page_width - rmp, rmm
48
+ when :bottom_right
49
+ reg_mark_sa page_width - rmp, page_height - rmp
50
+ when :bottom_left
51
+ reg_mark_sa rmm, page_height - rmp
52
+ end
53
+ end
54
+
55
+ def reg_mark_sa(x, y)
56
+ cw = @px / page_width
57
+ ch = @py / page_height
58
+ {
59
+ x: (cw * x).round,
60
+ y: (ch * y).round,
61
+ w: (cw * REG_SEARCH * 2).round,
62
+ h: (ch * REG_SEARCH * 2).round
63
+ }
64
+ end
65
+
66
+ def choice_cell_area(q, c)
67
+ {
68
+ x: (cx * cell_x(q, c)).round,
69
+ y: (cy * cell_y(q) ).round,
70
+ w: (cx * cell_width ).round,
71
+ h: (cy * cell_height ).round
72
+ }
73
+ end
74
+
75
+ def white_calibration_area
76
+ code_cell_area -1
77
+ end
78
+
79
+ def black_calibration_area
80
+ code_cell_area 0
81
+ end
82
+
83
+ def code_bit_area(i)
84
+ code_cell_area i+1
85
+ end
86
+
87
+ # ============
88
+ # = to Prawn =
89
+ # ============
90
+ def pdf_page_size
91
+ [page_width.mm, page_height.mm]
92
+ end
93
+
94
+ def pdf_margins
95
+ REG_MARGIN.mm
96
+ end
97
+
98
+ def pdf_reg_marks
99
+ r = REG_RADIUS.mm
100
+ [
101
+ { p: [0, 0 ], r: r },
102
+ { p: [0, reg_frame_height.mm], r: r },
103
+ { p: [reg_frame_width.mm, reg_frame_height.mm], r: r },
104
+ { p: [reg_frame_width.mm, 0 ], r: r }
105
+ ]
106
+ end
107
+
108
+ def pdf_dark_calibration_area
109
+ pdf_code_cell_area 0
110
+ end
111
+
112
+ def pdf_code_areas_for(code)
113
+ a = []
114
+ CODE_BITS.times do |bit|
115
+ a << pdf_code_cell_area(bit+1) if code[bit] == 1
116
+ end
117
+ a
118
+ end
119
+
120
+ def pdf_code_bit_areas
121
+ (1..CODE_BITS).collect do |bit|
122
+ pdf_code_cell_area bit
123
+ end
124
+ end
125
+
126
+ def pdf_code_cell_area(i)
127
+ {
128
+ p: [code_cell_x(i).mm, (reg_frame_height - code_y).mm],
129
+ w: CODE_WIDTH.mm,
130
+ h: CODE_HEIGHT.mm * 2
131
+ }
132
+ end
133
+
134
+ def pdf_choice_cell_area(q, c)
135
+ {
136
+ p: [cell_x(q, c).mm, (reg_frame_height - cell_y(q)).mm],
137
+ w: cell_width.mm,
138
+ h: cell_height.mm
139
+ }
140
+ end
141
+
142
+ def pdf_choice_letter_xy(q, c)
143
+ [
144
+ cell_x(q, c).mm + 2.mm,
145
+ (reg_frame_height - cell_y(q)).mm - 3.mm
146
+ ]
147
+ end
148
+
149
+ def pdf_qnum_xy(q)
150
+ [
151
+ cell_x(q, 0).mm - pdf_qnum_width - Q_NUM_GAP.mm,
152
+ (reg_frame_height - cell_y(q)).mm - 0.5.mm
153
+ ]
154
+ end
155
+
156
+ def pdf_qnum_width
157
+ Q_NUM_WIDTH.mm
158
+ end
159
+
160
+ def pdf_header_xy(k)
161
+ [
162
+ header[k.to_s]["left"].to_f.mm,
163
+ (reg_frame_height - header[k.to_s]["top"].to_f).mm
164
+ ]
165
+ end
166
+
167
+ def pdf_header_width(k)
168
+ header[k.to_s]["width"].to_f.mm
169
+ end
170
+
171
+ def pdf_header_size(k)
172
+ header[k.to_s]["size"].to_f
173
+ end
174
+
175
+ private
176
+
177
+ def cx
178
+ @px / reg_frame_width
179
+ end
180
+
181
+ def cy
182
+ @py / reg_frame_height
183
+ end
184
+
185
+ # x, y = cell_xy(q,c)
186
+ #
187
+ # the distances from the registration frame of the left and top edges
188
+ # of the c-th choice cell of the q-th question
189
+ def cell_xy(q, c)
190
+ return cell_x(q, c), cell_y(q)
191
+ end
192
+
193
+ # cell_y(q)
194
+ #
195
+ # the distance from the registration frame to the top edge
196
+ # of all choice cells in the q-th question
197
+ def cell_y(q)
198
+ first_y + response_spacing * (q % rows) - cell_height / 2
199
+ end
200
+
201
+ # cell_x(q,c)
202
+ #
203
+ # the distance from the registration frame to the left edge
204
+ # of the c-th choice cell of the q-th question
205
+ def cell_x(q,c)
206
+ first_x + column_width * (q / rows) + cell_spacing * c - cell_width / 2
207
+ end
208
+
209
+ def cell_width
210
+ @params["responses"]["cell_width"].to_f
211
+ end
212
+
213
+ def cell_height
214
+ @params["responses"]["cell_height"].to_f
215
+ end
216
+
217
+ def cell_spacing
218
+ @params["responses"]["x_spacing"].to_f
219
+ end
220
+
221
+ def response_spacing
222
+ @params["responses"]["y_spacing"].to_f
223
+ end
224
+
225
+ def column_width
226
+ @params["responses"]["column_width"].to_f
227
+ end
228
+
229
+ def row_spacing
230
+ @params["responses"]["y_spacing"].to_f
231
+ end
232
+
233
+ def first_x
234
+ @params["responses"]["first_x"].to_f
235
+ end
236
+
237
+ def first_y
238
+ @params["responses"]["first_y"].to_f
239
+ end
240
+
241
+ def rows
242
+ @params["responses"]["rows"]
243
+ end
244
+
245
+ def columns
246
+ @params["responses"]["columns"]
247
+ end
248
+
249
+ # ==============
250
+ # = sheet code =
251
+ # ==============
252
+ def code_cell_area(i)
253
+ {
254
+ x: (cx * code_cell_x(i) ).round,
255
+ y: (cy * code_y ).round,
256
+ w: (cx * CODE_WIDTH ).round,
257
+ h: (cy * CODE_HEIGHT).round
258
+ }
259
+ end
260
+
261
+ def code_cell_x(i)
262
+ CODE_LEFT + CODE_SPACING * i
263
+ end
264
+
265
+ def code_y
266
+ reg_frame_height - CODE_HEIGHT
267
+ end
268
+
269
+ # ======================
270
+ # = registration sides =
271
+ # ======================
272
+ def reg_frame_width
273
+ page_width - REG_MARGIN * 2
274
+ end
275
+
276
+ def reg_frame_height
277
+ page_height - REG_MARGIN * 2
278
+ end
279
+
280
+ def page_width
281
+ @params["page_size"]["width"].to_f
282
+ end
283
+
284
+ def page_height
285
+ @params["page_size"]["height"].to_f
286
+ end
287
+
288
+ # ============
289
+ # = Header
290
+ # ============
291
+ def name_x
292
+ @params["header"]["name"]["top"].to_f
293
+ end
294
+
295
+ def name_y
296
+ @params["header"]["name"]["left"].to_f
297
+ end
298
+
299
+ def name_w
300
+ @params["header"]["name"]["width"].to_f
301
+ end
302
+
303
+ def name_size
304
+ @params["header"]["name"]["size"]
305
+ end
306
+ end
307
+ end
@@ -0,0 +1,14 @@
1
+ module Mork
2
+ REG_MARGIN = 7.5
3
+ REG_RADIUS = 2.5
4
+ REG_SEARCH = 5.0
5
+ CODE_LEFT = 7.5
6
+ CODE_WIDTH = 2.0
7
+ CODE_HEIGHT = 2.5
8
+ CODE_SPACING = 2.5
9
+ CODE_BITS = 64
10
+ Q_NUM_GAP = 2.0 # distance between right side of q num and left side of first choice cell
11
+ Q_NUM_WIDTH = 8.0 # width of question number text box
12
+ Q_NUM_SIZE = 10
13
+ CH_LETTER_SZ = 8
14
+ end
@@ -0,0 +1,86 @@
1
+ require 'RMagick'
2
+
3
+ module Mork
4
+ # The class Mimage is a wrapper for the core image library, currently RMagick
5
+ class Mimage
6
+ def initialize(img, page=0)
7
+ if img.class == String
8
+ if File.extname(img) == '.pdf'
9
+ @image = Magick::Image.read(img) { self.density = 200 }[page]
10
+ else
11
+ @image = Magick::ImageList.new(img)[page]
12
+ end
13
+ elsif img.class == Magick::ImageList
14
+ @image = img[page]
15
+ elsif img.class == Magick::Image
16
+ @image = img
17
+ else
18
+ raise "Invalid initialization argument"
19
+ end
20
+ end
21
+
22
+ # =============
23
+ # = Highlight =
24
+ # =============
25
+ def highlight!(c)
26
+ m = Magick::Image.new(c[:w], c[:h]) { self.background_color = "red" }
27
+ @image.composite! m, c[:x], c[:y], Magick::CopyCompositeOp
28
+ end
29
+
30
+ # ============
31
+ # = Cropping =
32
+ # ============
33
+ def crop(c)
34
+ Mimage.new @image.crop(c[:x], c[:y], c[:w], c[:h])
35
+ end
36
+
37
+ def crop!(c)
38
+ @image.crop!(c[:x], c[:y], c[:w], c[:h])
39
+ self
40
+ end
41
+
42
+ # ============
43
+ # = Blurring =
44
+ # ============
45
+ def blur(a, b)
46
+ Mimage.new @image.blur_image(a, b)
47
+ end
48
+
49
+ def blur!(a, b)
50
+ @image = @image.blur_image(a, b)
51
+ self
52
+ end
53
+
54
+ # ==============
55
+ # = Stretching =
56
+ # ==============
57
+ def stretch(points)
58
+ Mimage.new @image.distort(Magick::PerspectiveDistortion, points)
59
+ end
60
+
61
+ def stretch!(points)
62
+ @image = @image.distort(Magick::PerspectiveDistortion, points)
63
+ self
64
+ end
65
+
66
+ # returns the raw pixels from the entire image or from the area
67
+ # defined in opts
68
+ def pixels(opts = {})
69
+ c = {x: 0, y: 0, w: width, h: height}.merge(opts)
70
+ @image.export_pixels(c[:x], c[:y], c[:w], c[:h], "I")
71
+ end
72
+
73
+ def width
74
+ @image.columns
75
+ end
76
+
77
+ def height
78
+ @image.rows
79
+ end
80
+
81
+ # write the underlying Magick::Image to disk
82
+ def write(fname)
83
+ @image.write fname
84
+ end
85
+ end
86
+ end