obf 0.1

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.
@@ -0,0 +1,17 @@
1
+ module OBF::OBF
2
+ def self.to_external(obf, opts)
3
+ OBF::External.from_obf(obf, opts)
4
+ end
5
+
6
+ def self.from_external(board, dest_path)
7
+ OBF::External.to_obf(board, dest_path)
8
+ end
9
+
10
+ def self.to_pdf(obf, dest_path)
11
+ OBF::PDF.from_obf(obf, dest_path)
12
+ end
13
+
14
+ def self.to_png(obf, dest_path)
15
+ OBF::PNG.from_obf(obf, dest_path)
16
+ end
17
+ end
@@ -0,0 +1,13 @@
1
+ module OBF::OBZ
2
+ def self.to_external(obz, opts)
3
+ OBF::External.from_obz(obz, opts)
4
+ end
5
+
6
+ def self.from_external(board, dest_path, opts)
7
+ OBF::External.to_obz(board, dest_path, opts)
8
+ end
9
+
10
+ def self.to_pdf(obz, dest_path)
11
+ OBF::PDF.from_obz(obz, dest_path)
12
+ end
13
+ end
@@ -0,0 +1,196 @@
1
+ module OBF::PDF
2
+ def self.from_obf(obf_json_or_path, dest_path, zipper=nil)
3
+ obj = obf_json_or_path
4
+ if obj.is_a?(String)
5
+ obj = OBF::Utils.parse_obf(File.read(obf_json_or_path))
6
+ else
7
+ obj = OBF::Utils.parse_obf(obf_json_or_path)
8
+ end
9
+ build_pdf(obj, dest_path, zipper)
10
+ return dest_path
11
+ end
12
+
13
+ def self.build_pdf(obj, dest_path, zipper)
14
+ OBF::Utils.as_progress_percent(0, 1.0) do
15
+ # parse obf, draw as pdf
16
+ pdf = Prawn::Document.new(
17
+ :page_layout => :landscape,
18
+ :page_size => [8.5*72, 11*72],
19
+ :info => {
20
+ :Title => obj['name']
21
+ }
22
+ )
23
+
24
+
25
+ if obj['boards']
26
+ obj['boards'].each_with_index do |board, idx|
27
+ pre = idx.to_f / obj['boards'].length.to_f
28
+ post = (idx + 1).to_f / obj['boards'].length.to_f
29
+ OBF::Utils.as_progress_percent(pre, post) do
30
+ pdf.start_new_page unless idx == 0
31
+ build_page(pdf, board, {'zipper' => zipper, 'pages' => obj['pages']})
32
+ end
33
+ end
34
+ else
35
+ build_page(pdf, obj, {})
36
+ end
37
+
38
+ pdf.render_file(dest_path)
39
+ end
40
+ end
41
+
42
+ def self.build_page(pdf, obj, options)
43
+ OBF::Utils.as_progress_percent(0, 1.0) do
44
+ doc_width = 11*72 - 72
45
+ doc_height = 8.5*72 - 72
46
+ default_radius = 3
47
+ text_height = 20
48
+
49
+ # header
50
+ pdf.bounding_box([0, doc_height], :width => doc_width, :height => 100) do
51
+ pdf.line_width = 2
52
+ pdf.font_size 16
53
+
54
+ pdf.fill_color "eeeeee"
55
+ pdf.stroke_color "888888"
56
+ pdf.fill_and_stroke_rounded_rectangle [0, 100], 100, 100, default_radius
57
+ pdf.fill_color "6D81D1"
58
+ pdf.fill_and_stroke_polygon([5, 50], [35, 85], [35, 70], [95, 70], [95, 30], [35, 30], [35, 15])
59
+ pdf.fill_color "ffffff"
60
+ pdf.text_box "Go Back", :at => [10, 90], :width => 80, :height => 80, :align => :center, :valign => :center, :overflow => :shrink_to_fit
61
+ pdf.fill_color "ffffff"
62
+ pdf.fill_and_stroke_rounded_rectangle [110, 100], (doc_width - 200 - 20), 100, default_radius
63
+ pdf.fill_color "DDDB54"
64
+ pdf.fill_and_stroke do
65
+ pdf.move_to 160, 50
66
+ pdf.line_to 190, 70
67
+ pdf.curve_to [190, 30], :bounds => [[100, 130], [100, -30]]
68
+ pdf.line_to 160, 50
69
+ end
70
+ pdf.fill_color "444444"
71
+ pdf.text_box "Say that sentence out loud for me", :at => [210, 90], :width => (doc_width - 200 - 120), :height => 80, :align => :left, :valign => :center, :overflow => :shrink_to_fit
72
+ pdf.fill_color "eeeeee"
73
+ pdf.fill_and_stroke_rounded_rectangle [(doc_width - 100), 100], 100, 100, default_radius
74
+ pdf.fill_color "aaaaaa"
75
+ pdf.fill_and_stroke_polygon([doc_width - 100 + 5, 50], [doc_width - 100 + 35, 85], [doc_width - 100 + 95, 85], [doc_width - 100 + 95, 15], [doc_width - 100 + 35, 15])
76
+ pdf.fill_color "ffffff"
77
+ pdf.text_box "Erase", :at => [(doc_width - 100 + 10), 90], :width => 80, :height => 80, :align => :center, :valign => :center, :overflow => :shrink_to_fit
78
+ end
79
+
80
+ # board
81
+ pdf.font_size 12
82
+ padding = 10
83
+ grid_height = doc_height - 100 - text_height - (padding * 2)
84
+ grid_width = doc_width
85
+ if obj['grid'] && obj['grid']['rows'] > 0 && obj['grid']['columns'] > 0
86
+ button_height = (grid_height - (padding * (obj['grid']['rows'] - 1))) / obj['grid']['rows'].to_f
87
+ button_width = (grid_width - (padding * (obj['grid']['columns'] - 1))) / obj['grid']['columns'].to_f
88
+ obj['grid']['order'].each_with_index do |buttons, row|
89
+ buttons.each_with_index do |button_id, col|
90
+ button = obj['buttons'].detect{|b| b['id'] == button_id }
91
+ next unless button
92
+ x = (padding * col) + (col * button_width)
93
+ y = text_height + padding - (padding * row) + grid_height - (row * button_height)
94
+ pdf.bounding_box([x, y], :width => button_width, :height => button_height) do
95
+ fill = "ffffff"
96
+ border = "eeeeee"
97
+ if button['background_color']
98
+ fill = `node lib/tinycolor_convert.js "#{button['background_color']}"`.strip
99
+ end
100
+ if button['border_color']
101
+ border = `node lib/tinycolor_convert.js "#{button['border_color']}"`.strip
102
+ end
103
+ pdf.fill_color fill
104
+ pdf.stroke_color border
105
+ pdf.fill_and_stroke_rounded_rectangle [0, button_height], button_width, button_height, default_radius
106
+ pdf.bounding_box([5, button_height - 5], :width => button_width - 10, :height => button_height - text_height - 5) do
107
+ image = obj['images_hash'][button['image_id']]
108
+ image_local_path = image && OBF::Utils.save_image(image, options['zipper'])
109
+ if image_local_path && File.exist?(image_local_path)
110
+ pdf.image image_local_path, :fit => [button_width - 10, button_height - text_height - 5], :position => :center, :vposition => :center
111
+ File.unlink image_local_path
112
+ end
113
+ end
114
+ if options['pages'] && button['load_board']
115
+ page = options['pages'][button['load_board']['id']]
116
+ pdf.fill_color "ffffff"
117
+ pdf.stroke_color "eeeeee"
118
+ pdf.fill_and_stroke_rounded_rectangle [button_width - 25, button_height - 5], 20, text_height, 5
119
+ pdf.fill_color "000000"
120
+ pdf.text_box page, :at => [button_width - 25, button_height - 5], :width => 20, :height => text_height, :align => :center, :valign => :center
121
+ end
122
+
123
+ pdf.fill_color "000000"
124
+ pdf.text_box (button['label'] || button['vocalization']).to_s, :at => [0, text_height], :width => button_width, :height => text_height, :align => :center, :valign => :center, :overflow => :shrink_to_fit
125
+ end
126
+ index = col + (row * obj['grid']['columns'])
127
+ OBF::Utils.update_current_progress(index.to_f / (obj['grid']['rows'] * obj['grid']['columns']).to_f)
128
+ end
129
+ end
130
+ end
131
+
132
+ # footer
133
+ pdf.fill_color "aaaaaa"
134
+ pdf.text_box "mycoughdrop.com", :at => [doc_width - 300, text_height], :width => 200, :height => text_height, :align => :right, :valign => :center, :overflow => :shrink_to_fit
135
+ pdf.fill_color "000000"
136
+ if options['pages']
137
+ pdf.text_box options['pages'][obj['id']], :at => [doc_width - 100, text_height], :width => 100, :height => text_height, :align => :right, :valign => :center, :overflow => :shrink_to_fit
138
+ end
139
+ end
140
+ end
141
+
142
+ def self.from_obz(obz_path, dest_path)
143
+ OBF::Utils.load_zip(obz_path) do |zipper|
144
+ manifest = JSON.parse(zipper.read('manifest.json'))
145
+ root = manifest['root']
146
+ board = OBF::Utils.parse_obf(zipper.read(root))
147
+ board['path'] = root
148
+ unvisited_boards = [board]
149
+ visited_boards = []
150
+ while unvisited_boards.length > 0
151
+ board = unvisited_boards.shift
152
+ visited_boards << board
153
+ children = []
154
+ board['buttons'].each do |button|
155
+ if button['load_board']
156
+ children << button['load_board']
157
+ all_boards = visited_boards + unvisited_boards
158
+ if all_boards.none?{|b| b['id'] == button['load_board']['id'] || b['path'] == button['load_board']['path'] }
159
+ path = button['load_board']['path'] || manifest[button['load_board']['id']]
160
+ b = OBF::Utils.parse_obf(zipper.read(path))
161
+ b['path'] = path
162
+ unvisited_boards << b
163
+ end
164
+ end
165
+ end
166
+ end
167
+
168
+ pages = {}
169
+ visited_boards.each_with_index do |board, idx|
170
+ pages[board['id']] = (idx + 1).to_s
171
+ end
172
+
173
+ build_pdf({
174
+ 'name' => 'Communication Board Set',
175
+ 'boards' => visited_boards,
176
+ 'pages' => pages
177
+ }, dest_path, zipper)
178
+ end
179
+ # parse obz, draw as pdf
180
+
181
+ # TODO: helper files included at the end for emergencies (eg. body parts)
182
+
183
+ return dest_path
184
+ end
185
+
186
+ def self.from_external(board, dest_path, full_set=false)
187
+ tmp_path = OBF::Utils.temp_path("stash")
188
+ from_obz(OBF::OBZ.from_external(board, full_set, tmp_path), dest_path)
189
+ File.unlink(tmp_path) if File.exist?(tmp_path)
190
+ dest_path
191
+ end
192
+
193
+ def self.to_png(pdf, dest_path)
194
+ OBF::PNG.from_pdf(pdf, dest_path)
195
+ end
196
+ end
@@ -0,0 +1,22 @@
1
+ module OBF::PNG
2
+ def self.from_pdf(pdf_path, dest_path, opts={})
3
+ resize = ""
4
+ resize = "-resize 600x321 -quality 100" if opts[:resize]
5
+ `convert -density 300 -crop 3160x1690+0+600 +repage #{resize} #{pdf_path} #{dest_path}`
6
+ dest_path
7
+ end
8
+
9
+ def self.from_obf(obf, dest_path)
10
+ tmp_path = OBF::Utils.temp_path("stash")
11
+ self.from_pdf(OBF::OBF.to_pdf(obf, tmp_path), dest_path)
12
+ File.unlink(tmp_path) if File.exist?(tmp_path)
13
+ dest_path
14
+ end
15
+
16
+ def self.from_external(board, dest_path)
17
+ tmp_path = OBF::Utils.temp_path("stash")
18
+ self.from_pdf(OBF::External.to_pdf(board, tmp_path), dest_path)
19
+ File.unlink(tmp_path) if File.exist?(tmp_path)
20
+ dest_path
21
+ end
22
+ end
@@ -0,0 +1,265 @@
1
+ module OBF::Utils
2
+ # def self.board_to_remote(board, user, file_type, include)
3
+ # OBF::Utils.update_current_progress(0.2, :converting_file)
4
+ # # TODO: key off just the last change id for the board(s) when building the
5
+ # # filename, return existing filename if it exists and isn't about to expire
6
+ # path = OBF::Utils.temp_path("stash")
7
+ #
8
+ # content_type = nil
9
+ # if file_type == 'obf'
10
+ # content_type = 'application/obf'
11
+ # elsif file_type == 'obz'
12
+ # content_type = 'application/obz'
13
+ # elsif file_type == 'pdf'
14
+ # content_type = 'application/pdf'
15
+ # else
16
+ # raise "Unrecognized conversion type: #{file_type}"
17
+ # end
18
+ # key = Security.sha512(board.id.to_s, 'board_id')
19
+ # filename = "board_" + board.current_revision + "." + file_type.to_s
20
+ # remote_path = "downloads/#{key}/#{filename}"
21
+ # url = Uploader.check_existing_upload(remote_path)
22
+ # return url if url
23
+ # OBF::Utils.update_current_progress(0.3, :converting_file)
24
+ #
25
+ # OBF::Utils.as_progress_percent(0.3, 0.8) do
26
+ # if file_type == 'obz'
27
+ # if include == 'all'
28
+ # OBF::CoughDrop.to_obz(board, path, {'user' => user})
29
+ # else
30
+ # OBF::CoughDrop.to_obz(board, path, {'user' => user})
31
+ # end
32
+ # elsif file_type == 'obf'
33
+ # OBF::CoughDrop.to_obf(board, path)
34
+ # elsif file_type == 'pdf'
35
+ # OBF::CoughDrop.to_pdf(board, path, {'user' => user, 'packet' => (include == 'all')})
36
+ # end
37
+ # end
38
+ # OBF::Utils.update_current_progress(0.9, :uploading_file)
39
+ # url = Uploader.remote_upload(remote_path, path, content_type)
40
+ # raise "File not uploaded" unless url
41
+ # File.unlink(path) if File.exist?(path)
42
+ # return url
43
+ # end
44
+
45
+ # def self.remote_to_boards(user, url)
46
+ # result = []
47
+ # OBF::Utils.update_current_progress(0.1, :downloading_file)
48
+ # response = Typhoeus.get(url)
49
+ # file = Tempfile.new('stash')
50
+ # file.binmode
51
+ # file.write response.body
52
+ # file.close
53
+ # OBF::Utils.update_current_progress(0.2, :processing_file)
54
+ # OBF::Utils.as_progress_percent(0.2, 1.0) do
55
+ # if url.match(/\.obz$/) || response.headers['Content-Type'] == 'application/obz'
56
+ # boards = OBF::CoughDrop.from_obz(file.path, {'user' => user})
57
+ # result = boards
58
+ # elsif url.match(/\.obf$/) || response.headers['Content-Type'] == 'application/obf'
59
+ # board = OBF::CoughDrop.from_obf(file.path, {'user' => user})
60
+ # result = [board]
61
+ # else
62
+ # raise "Unrecognized file type: #{response.headers['Content-Type']}"
63
+ # end
64
+ # file.unlink
65
+ # end
66
+ # return result
67
+ # end
68
+
69
+ def self.get_url(url)
70
+ return nil unless url
71
+ res = Typhoeus.get(URI.escape(url))
72
+ extension = ""
73
+ type = MIME::Types[res.headers['Content-Type']]
74
+ type = type && type[0]
75
+ extension = ("." + type.preferred_extension) if type && type.extensions && type.extensions.length > 0
76
+ {
77
+ 'content_type' => res.headers['Content-Type'],
78
+ 'data' => res.body,
79
+ 'extension' => extension
80
+ }
81
+ end
82
+
83
+ def self.image_raw(url)
84
+ image = get_url(url)
85
+ return nil unless image
86
+ image
87
+ end
88
+
89
+ def self.image_base64(url)
90
+ image = get_url(url)
91
+ return nil unless image
92
+ str = "data:" + image['content_type']
93
+ str += ";base64," + Base64.strict_encode64(image['data'])
94
+ str
95
+ end
96
+
97
+ def self.save_image(image, zipper=nil)
98
+ if image['data']
99
+ if !image['content_type']
100
+ image['content_type'] = image['data'].split(/;/)[0].split(/:/)[1]
101
+ end
102
+ elsif image['path'] && zipper
103
+ image['raw_data'] = zipper.read(image['path'])
104
+ if !image['content_type']
105
+ types = MIME::Types.type_for(image['path'])
106
+ image['content_type'] = types[0] && types[0].to_s
107
+ end
108
+ elsif image['url']
109
+ url_data = get_url(image['url'])
110
+ image['raw_data'] = url_data['data']
111
+ image['content_type'] = url_data['content_type']
112
+ elsif image['symbol']
113
+ # not supported
114
+ end
115
+ type = MIME::Types[image['content_type']]
116
+ type = type && type[0]
117
+ extension = type && ("." + type.extensions.first)
118
+ file = Tempfile.new(["image_stash", extension.to_s])
119
+ file.binmode
120
+ if image['data']
121
+ str = Base64.strict_decode64(image['data'].split(/\,/, 2)[1])
122
+ file.write str
123
+ elsif image['raw_data']
124
+ file.write image['raw_data']
125
+ else
126
+ raise "uh-oh"
127
+ end
128
+ file.close
129
+ `convert #{file.path} -density 1200 -resize 300x300 -background none -gravity center -extent 300x300 #{file.path}.png`
130
+ "#{file.path}.png"
131
+ end
132
+
133
+ def self.sound_raw(url)
134
+ sound = get_url(url)
135
+ return nil unless sound
136
+ sound
137
+ end
138
+
139
+ def self.sound_base64(url)
140
+ sound = get_url(url)
141
+ return nil unless sound
142
+ str = "data:" + sound['content_type']
143
+ str += ";base64," + Base64.strict_encode64(sound['data'])
144
+ str
145
+ end
146
+
147
+ def self.obf_shell
148
+ {
149
+ 'format' => 'open-board-0.1',
150
+ 'license' => {'type' => 'private'},
151
+ 'buttons' => [],
152
+ 'grid' => {
153
+ 'rows' => 0,
154
+ 'columns' => 0,
155
+ 'order' => [[]]
156
+ },
157
+ 'images' => [],
158
+ 'sounds' => []
159
+ }
160
+ end
161
+
162
+ def self.parse_obf(obj)
163
+ json = obj
164
+ if obj.is_a?(String)
165
+ json = JSON.parse(obj)
166
+ end
167
+ ['images', 'sounds', 'buttons'].each do |key|
168
+ json["#{key}_hash"] = json[key]
169
+ if json[key].is_a?(Array)
170
+ hash = {}
171
+ json[key].each do |item|
172
+ hash[item['id']] = item
173
+ end
174
+ json["#{key}_hash"] = hash
175
+ else
176
+ array = []
177
+ json["#{key}_hash"].each do |id, item|
178
+ item['id'] ||= id
179
+ array << item
180
+ end
181
+ json[key] = array
182
+ end
183
+ end
184
+ json
185
+ end
186
+
187
+ def self.parse_license(pre_license)
188
+ pre_license = {} unless pre_license.is_a?(Hash)
189
+ license = {}
190
+ ['type', 'copyright_notice_url', 'source_url', 'author_name', 'author_url', 'author_email', 'uneditable'].each do |attr|
191
+ license[attr] = pre_license[attr] if pre_license[attr] != nil
192
+ end
193
+ license['type'] ||= 'private'
194
+ license['copyright_notice_url'] ||= license['copyright_notice_link'] if license.key?('copyright_notice_link')
195
+ license['source_url'] ||= license['source_link'] if license.key?('source_link')
196
+ license['author_url'] ||= license['author_link'] if license.key?('author_link')
197
+ license
198
+ end
199
+
200
+ def self.parse_grid(pre_grid)
201
+ pre_grid ||= {}
202
+ grid = {
203
+ 'rows' => pre_grid['rows'] || 1,
204
+ 'columns' => pre_grid['columns'] || 1,
205
+ 'order' => pre_grid['order'] || [[nil]]
206
+ }
207
+ # TODO: parse order better
208
+ grid
209
+ end
210
+
211
+ def self.temp_path(*args)
212
+ file = Tempfile.new(*args)
213
+ res = file.path
214
+ file.unlink
215
+ res
216
+ end
217
+
218
+ class Zipper
219
+ def initialize(zipfile)
220
+ @zipfile = zipfile
221
+ end
222
+
223
+ def add(path, contents)
224
+ @zipfile.get_output_stream(path) {|os| os.write contents }
225
+ end
226
+
227
+ def read(path)
228
+ entry = @zipfile.glob(path).first
229
+ entry ? entry.get_input_stream.read : nil
230
+ end
231
+ end
232
+
233
+ def self.load_zip(path, &block)
234
+ require 'zip'
235
+
236
+ Zip::File.open(path) do |zipfile|
237
+ block.call(Zipper.new(zipfile))
238
+ end
239
+ end
240
+
241
+ def self.build_zip(dest_path=nil, &block)
242
+ require 'zip'
243
+
244
+ if !dest_path
245
+ dest_path = OBF::Utils.temp_path(['archive', '.obz'])
246
+ end
247
+ Zip::File.open(dest_path, Zip::File::CREATE) do |zipfile|
248
+ block.call(Zipper.new(zipfile))
249
+ end
250
+ end
251
+
252
+ def self.update_current_progress(*args)
253
+ if Object.const_defined?('Progress')
254
+ Progress.update_current_progress(*args)
255
+ end
256
+ end
257
+
258
+ def self.as_progress_percent(a, b, &block)
259
+ if Object.const_defined?('Progress')
260
+ Progress.as_percent(a, b, &block)
261
+ else
262
+ block.call
263
+ end
264
+ end
265
+ end