mork 0.0.1 → 0.0.2

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.
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