obf 0.2.2 → 0.2.3
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/lib/obf.rb +3 -0
- data/lib/obf/avaz.rb +5 -0
- data/lib/obf/external.rb +4 -103
- data/lib/obf/pdf.rb +12 -1
- data/lib/obf/picto4me.rb +97 -0
- data/lib/obf/utils.rb +65 -74
- metadata +3 -1
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 5afc4eec32460327902d440eb155542c74c03d66
|
4
|
+
data.tar.gz: f07ee3b08cbd8d54a8871d899d1c706e7d11bdc2
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 2922588d938f988d71e032e826c89c56377d8f208edd8cbd6e082f0307d27767fed792ca10f03652cc49c6f9f6dfdf976323db91820fde343d6d9edd653edbab
|
7
|
+
data.tar.gz: 85c9884e240b60e9ba76de7b8b927779d20386ab640495a3e1075aefa6c412d02d5d6f150db4f3fd65ada8e7334f19ff0fc189bb2b58b01676d52fe89966e49b
|
data/lib/obf.rb
CHANGED
data/lib/obf/avaz.rb
ADDED
data/lib/obf/external.rb
CHANGED
@@ -76,6 +76,7 @@ module OBF::External
|
|
76
76
|
'height' => original_image['height'],
|
77
77
|
'license' => OBF::Utils.parse_license(original_image['license']),
|
78
78
|
'url' => original_image['url'],
|
79
|
+
'data' => original_image['data'],
|
79
80
|
'data_url' => original_image['data_url'],
|
80
81
|
'content_type' => original_image['content_type']
|
81
82
|
}
|
@@ -85,7 +86,7 @@ module OBF::External
|
|
85
86
|
if path_hash['images'] && path_hash['images'][image['id']]
|
86
87
|
image['path'] = path_hash['images'][image['id']]['path']
|
87
88
|
else
|
88
|
-
image_fetch = OBF::Utils.image_raw(image['url'])
|
89
|
+
image_fetch = OBF::Utils.image_raw(image['url'] || image['data'])
|
89
90
|
if image_fetch
|
90
91
|
zip_path = "images/image_#{image['id']}#{image_fetch['extension']}"
|
91
92
|
path_hash['images'] ||= {}
|
@@ -115,7 +116,7 @@ module OBF::External
|
|
115
116
|
if path_hash['sounds'] && path_hash['sounds'][sound['id']]
|
116
117
|
sound['path'] = path_hash['sounds'][sound['id']]['path']
|
117
118
|
else
|
118
|
-
sound_fetch = OBF::Utils.sound_raw(sound['url'])
|
119
|
+
sound_fetch = OBF::Utils.sound_raw(sound['url'] || sound['data'])
|
119
120
|
if sound_fetch
|
120
121
|
zip_path = "sounds/sound_#{sound['id']}#{sound_fetch['extension']}"
|
121
122
|
path_hash['sounds'] ||= {}
|
@@ -175,106 +176,6 @@ module OBF::External
|
|
175
176
|
|
176
177
|
obj['license'] = OBF::Utils.parse_license(obj['license'])
|
177
178
|
obj
|
178
|
-
|
179
|
-
# raise "user required" unless opts['user']
|
180
|
-
# raise "missing id" unless obj['id']
|
181
|
-
#
|
182
|
-
# hashes = {}
|
183
|
-
# [['images_hash', ButtonImage], ['sounds_hash', ButtonSound]].each do |list, klass|
|
184
|
-
# obj[list].each do |id, item|
|
185
|
-
# record = nil
|
186
|
-
# unique_id = obj['id'] + "_" + item['id'].to_s
|
187
|
-
# if opts[list] && opts[list][unique_id]
|
188
|
-
# record = klass.find_by_global_id(opts[list][unique_id])
|
189
|
-
# elsif item['data']
|
190
|
-
# record = klass.create(:user => opts['user'])
|
191
|
-
# item['ref_url'] = item['data']
|
192
|
-
# elsif item['path'] && opts['zipper']
|
193
|
-
# content_type = item['content_type']
|
194
|
-
# data = opts['zipper'].read(item['path'])
|
195
|
-
# str = "data:" + content_type
|
196
|
-
# str += ";base64," + Base64.strict_encode64(data)
|
197
|
-
# record = klass.create(:user => opts['user'])
|
198
|
-
# item['ref_url'] = str
|
199
|
-
# elsif item['url']
|
200
|
-
# record = klass.create(:user => opts['user'])
|
201
|
-
# item['ref_url'] = item['url']
|
202
|
-
# end
|
203
|
-
# if record
|
204
|
-
# item.delete('data')
|
205
|
-
# item.delete('url')
|
206
|
-
# record.process(item)
|
207
|
-
# record.upload_to_remote(item['ref_url']) if item['ref_url']
|
208
|
-
# opts[list] ||= {}
|
209
|
-
# opts[list][unique_id] = record.global_id
|
210
|
-
# hashes[item['id']] = record.global_id
|
211
|
-
# end
|
212
|
-
# end
|
213
|
-
# end
|
214
|
-
|
215
|
-
# params = {}
|
216
|
-
# non_user_params = {'user' => opts['user']}
|
217
|
-
# params['name'] = obj['name']
|
218
|
-
# params['description'] = obj['description_html']
|
219
|
-
# params['image_url'] = obj['image_url']
|
220
|
-
# params['license'] = OBF::Utils.parse_license(obj['license'])
|
221
|
-
# params['buttons'] = obj['buttons'].map do |button|
|
222
|
-
# new_button = {
|
223
|
-
# 'id' => button['id'],
|
224
|
-
# 'label' => button['label'],
|
225
|
-
# 'vocalization' => button['vocalization'],
|
226
|
-
# 'left' => button['left'],
|
227
|
-
# 'top' => button['top'],
|
228
|
-
# 'width' => button['width'],
|
229
|
-
# 'height' => button['height'],
|
230
|
-
# 'border_color' => button['border_color'],
|
231
|
-
# 'background_color' => button['background_color']
|
232
|
-
# }
|
233
|
-
# if button['image_id']
|
234
|
-
# new_button['image_id'] = hashes[button['image_id']]
|
235
|
-
# end
|
236
|
-
# if button['sound_id']
|
237
|
-
# new_button['sound_id'] = hashes[button['sound_id']]
|
238
|
-
# end
|
239
|
-
# if button['load_board']
|
240
|
-
# if opts['boards'] && opts['boards'][button['load_board']['id']]
|
241
|
-
# new_button['load_board'] = opts['boards'][button['load_board']['id']]
|
242
|
-
# else
|
243
|
-
# link = Board.find_by_path(button['load_board']['key'] || button['load_board']['id'])
|
244
|
-
# if link
|
245
|
-
# new_button['load_board'] = {
|
246
|
-
# 'id' => link.global_id,
|
247
|
-
# 'key' => link.key
|
248
|
-
# }
|
249
|
-
# end
|
250
|
-
# end
|
251
|
-
# elsif button['url']
|
252
|
-
# if button['ext_coughdrop_apps']
|
253
|
-
# new_button['apps'] = button['ext_coughdrop_apps']
|
254
|
-
# else
|
255
|
-
# new_button['url'] = button['url']
|
256
|
-
# end
|
257
|
-
# end
|
258
|
-
# new_button
|
259
|
-
# end
|
260
|
-
# params['grid'] = obj['grid']
|
261
|
-
# params['public'] = !(obj['settings'] && obj['settings']['private'])
|
262
|
-
# non_user_params[:key] = (obj['settings'] && obj['settings']['key'])
|
263
|
-
# board = nil
|
264
|
-
# if opts['boards'] && opts['boards'][obj['id']]
|
265
|
-
# board = Board.find_by_path(opts['boards'][obj['id']]['id']) || Board.find_by_path(opts['boards'][obj['id']]['key'])
|
266
|
-
# board.process(params, non_user_params)
|
267
|
-
# else
|
268
|
-
# board = Board.process_new(params, non_user_params)
|
269
|
-
# opts['boards'] ||= {}
|
270
|
-
# opts['boards'][obj['id']] = {
|
271
|
-
# 'id' => board.global_id,
|
272
|
-
# 'key' => board.key
|
273
|
-
# }
|
274
|
-
# end
|
275
|
-
# board
|
276
|
-
#
|
277
|
-
|
278
179
|
end
|
279
180
|
|
280
181
|
def self.to_obz(content, dest_path, opts)
|
@@ -294,7 +195,7 @@ module OBF::External
|
|
294
195
|
if b
|
295
196
|
b['images'] = content['images'] || []
|
296
197
|
b['sounds'] = content['sounds'] || []
|
297
|
-
to_obf(b, nil, paths)
|
198
|
+
to_obf(b, nil, paths)
|
298
199
|
end
|
299
200
|
end
|
300
201
|
manifest = {
|
data/lib/obf/pdf.rb
CHANGED
@@ -1,4 +1,13 @@
|
|
1
1
|
module OBF::PDF
|
2
|
+
@@footer_text ||= nil
|
3
|
+
def self.footer_text
|
4
|
+
@@footer_text
|
5
|
+
end
|
6
|
+
|
7
|
+
def self.footer_text=(text)
|
8
|
+
@@footer_text = text
|
9
|
+
end
|
10
|
+
|
2
11
|
def self.from_obf(obf_json_or_path, dest_path, zipper=nil)
|
3
12
|
obj = obf_json_or_path
|
4
13
|
if obj.is_a?(String)
|
@@ -131,7 +140,9 @@ module OBF::PDF
|
|
131
140
|
|
132
141
|
# footer
|
133
142
|
pdf.fill_color "aaaaaa"
|
134
|
-
|
143
|
+
if OBF::PDF.footer_text
|
144
|
+
pdf.text_box OBF::PDF.footer_text, :at => [doc_width - 300, text_height], :width => 200, :height => text_height, :align => :right, :valign => :center, :overflow => :shrink_to_fit
|
145
|
+
end
|
135
146
|
pdf.fill_color "000000"
|
136
147
|
if options['pages']
|
137
148
|
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
|
data/lib/obf/picto4me.rb
ADDED
@@ -0,0 +1,97 @@
|
|
1
|
+
module OBF::Picto4me
|
2
|
+
def self.to_external(zip_path)
|
3
|
+
boards = []
|
4
|
+
images = []
|
5
|
+
sounds = []
|
6
|
+
OBF::Utils.load_zip(zip_path) do |zipper|
|
7
|
+
json = JSON.parse(zipper.read('*.js'))
|
8
|
+
locale = json['locale']
|
9
|
+
json['sheets'].each_with_index do |sheet, idx|
|
10
|
+
board = OBF::Utils.obf_shell
|
11
|
+
board['id'] = idx.to_s
|
12
|
+
board['locale'] = locale
|
13
|
+
board['name'] = sheet['title']['text']
|
14
|
+
board['ext_picto4me_title'] = sheet['title']
|
15
|
+
board['ext_picto4me_cellsize'] = sheet['cellsize']
|
16
|
+
board['ext_picto4me_pictoOverrule'] = sheet['pictoOverrule']
|
17
|
+
board['ext_picto4me_showPictoTitles'] = sheet['showPictoTitles']
|
18
|
+
board['ext_picto4me_pictoBorder'] = sheet['pictoBorder']
|
19
|
+
grid = []
|
20
|
+
sheet['rows'].times do
|
21
|
+
grid << [nil] * sheet['columns']
|
22
|
+
end
|
23
|
+
board['grid'] = {
|
24
|
+
'rows' => sheet['rows'],
|
25
|
+
'columns' => sheet['columns'],
|
26
|
+
'order' => grid
|
27
|
+
}
|
28
|
+
sheet['pictos'].each_with_index do |picto, jdx|
|
29
|
+
next unless picto
|
30
|
+
button = {}
|
31
|
+
button['id'] = board['id'] + ":" + picto['id']
|
32
|
+
button['label'] = picto['title']['text']
|
33
|
+
button['vocalization'] = picto['description']['text']
|
34
|
+
button['border_color'] = picto['borderColor'] unless picto['borderColor'] == 'transparent'
|
35
|
+
button['background_color'] = picto['bgColor'] unless picto['bgColor'] == 'transparent'
|
36
|
+
button['ext_picto4me_lang'] = picto['lang']
|
37
|
+
button['ext_picto4me_description'] = picto['description']
|
38
|
+
button['ext_picto4me_title'] = picto['title']
|
39
|
+
button['ext_picto4me_overlay'] = picto['overlay']
|
40
|
+
button['ext_picto4me_source'] = picto['source']
|
41
|
+
button['ext_picto4me_key'] = picto['key']
|
42
|
+
button['ext_picto4me_categories'] = picto['categories']
|
43
|
+
button['ext_picto4me_size'] = picto['size']
|
44
|
+
|
45
|
+
if picto['imageurl']
|
46
|
+
image = {}
|
47
|
+
image['id'] = 'img:' + button['id']
|
48
|
+
|
49
|
+
attrs = zipper.read_as_data(picto['imageurl'][1..-1])
|
50
|
+
raise "didn't work" unless attrs['data']
|
51
|
+
image['data'] = attrs['data']
|
52
|
+
image['width'] = attrs['width']
|
53
|
+
image['height'] = attrs['height']
|
54
|
+
image['content_type'] = attrs['content_type']
|
55
|
+
|
56
|
+
images << image
|
57
|
+
button['image_id'] = image['id']
|
58
|
+
end
|
59
|
+
if picto['soundurl']
|
60
|
+
sound = {}
|
61
|
+
sound['id'] = 'snd:' + button['id']
|
62
|
+
|
63
|
+
attrs = zipper.read_as_data(picto['soundurl'][1..-1])
|
64
|
+
raise "didn't work" unless attrs['data']
|
65
|
+
sound['data'] = attrs['data']
|
66
|
+
sound['content_type'] = attrs['content_type']
|
67
|
+
|
68
|
+
sounds << sound
|
69
|
+
button['sound_id'] = sound['id']
|
70
|
+
end
|
71
|
+
if picto['link'] && json['sheets'][picto['link'].to_i]
|
72
|
+
button['load_board'] = {'id' => picto['link']}
|
73
|
+
end
|
74
|
+
board['buttons'] << button
|
75
|
+
row = (jdx / sheet['columns']).floor.to_i
|
76
|
+
col = jdx % sheet['columns']
|
77
|
+
board['grid']['order'][row][col] = button['id']
|
78
|
+
end
|
79
|
+
boards << board
|
80
|
+
end
|
81
|
+
end
|
82
|
+
images.uniq!
|
83
|
+
sounds.uniq!
|
84
|
+
if boards.length == 1
|
85
|
+
board = boards[0]
|
86
|
+
board['images'] = images
|
87
|
+
board['sounds'] = sounds
|
88
|
+
return board
|
89
|
+
else
|
90
|
+
return {
|
91
|
+
'boards' => boards,
|
92
|
+
'images' => images,
|
93
|
+
'sounds' => sounds
|
94
|
+
}
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
data/lib/obf/utils.rb
CHANGED
@@ -1,81 +1,23 @@
|
|
1
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
2
|
def self.get_url(url)
|
70
3
|
return nil unless url
|
71
|
-
|
72
|
-
|
73
|
-
|
4
|
+
content_type = nil
|
5
|
+
data = nil
|
6
|
+
if url.match(/^data:/)
|
7
|
+
content_type = url.split(/;/)[0].split(/:/)[1]
|
8
|
+
data = Base64.strict_decode64(url.split(/\,/, 2)[1])
|
9
|
+
else
|
10
|
+
res = Typhoeus.get(URI.escape(url))
|
11
|
+
content_type = res.headers['Content-Type']
|
12
|
+
data = res.body
|
13
|
+
end
|
14
|
+
type = MIME::Types[content_type]
|
74
15
|
type = type && type[0]
|
16
|
+
extension = ""
|
75
17
|
extension = ("." + type.preferred_extension) if type && type.extensions && type.extensions.length > 0
|
76
18
|
{
|
77
|
-
'content_type' =>
|
78
|
-
'data' =>
|
19
|
+
'content_type' => content_type,
|
20
|
+
'data' => data,
|
79
21
|
'extension' => extension
|
80
22
|
}
|
81
23
|
end
|
@@ -87,7 +29,16 @@ module OBF::Utils
|
|
87
29
|
end
|
88
30
|
|
89
31
|
def self.image_base64(url)
|
90
|
-
image =
|
32
|
+
image = nil
|
33
|
+
if url.match(/:\/\//)
|
34
|
+
image = get_url(url)
|
35
|
+
else
|
36
|
+
types = MIME::Types.type_for(url)
|
37
|
+
image = {
|
38
|
+
'data' => File.read(url),
|
39
|
+
'content_type' => types[0] && types[0].to_s
|
40
|
+
}
|
41
|
+
end
|
91
42
|
return nil unless image
|
92
43
|
str = "data:" + image['content_type']
|
93
44
|
str += ";base64," + Base64.strict_encode64(image['data'])
|
@@ -137,7 +88,16 @@ module OBF::Utils
|
|
137
88
|
end
|
138
89
|
|
139
90
|
def self.sound_base64(url)
|
140
|
-
sound =
|
91
|
+
sound = nil
|
92
|
+
if url.match(/:\/\//)
|
93
|
+
sound = get_url(url)
|
94
|
+
else
|
95
|
+
types = MIME::Types.type_for(url)
|
96
|
+
sound = {
|
97
|
+
'data' => File.read(url),
|
98
|
+
'content_type' => types[0] && types[0].to_s
|
99
|
+
}
|
100
|
+
end
|
141
101
|
return nil unless sound
|
142
102
|
str = "data:" + sound['content_type']
|
143
103
|
str += ";base64," + Base64.strict_encode64(sound['data'])
|
@@ -228,6 +188,37 @@ module OBF::Utils
|
|
228
188
|
entry = @zipfile.glob(path).first
|
229
189
|
entry ? entry.get_input_stream.read : nil
|
230
190
|
end
|
191
|
+
|
192
|
+
def read_as_data(path)
|
193
|
+
attrs = {}
|
194
|
+
raw = @zipfile.read(path)
|
195
|
+
types = MIME::Types.type_for(path)
|
196
|
+
attrs['content_type'] = types[0] && types[0].to_s
|
197
|
+
|
198
|
+
str = "data:" + attrs['content_type']
|
199
|
+
str += ";base64," + Base64.strict_encode64(raw)
|
200
|
+
attrs['data'] = str
|
201
|
+
|
202
|
+
if attrs['content_type'].match(/^image/)
|
203
|
+
fn = OBF::Utils.temp_path('file')
|
204
|
+
file = Tempfile.new('file')
|
205
|
+
file.binmode
|
206
|
+
file.write raw
|
207
|
+
file.close
|
208
|
+
data = `identify -verbose #{file.path}`
|
209
|
+
data.split(/\n/).each do |line|
|
210
|
+
pre, post = line.sub(/^\s+/, '').split(/:\s/, 2)
|
211
|
+
if pre == 'Geometry'
|
212
|
+
match = post.match(/(\d+)x(\d+)/)
|
213
|
+
if match && match[1] && match[2]
|
214
|
+
attrs['width'] = match[1].to_i
|
215
|
+
attrs['height'] = match[2].to_i
|
216
|
+
end
|
217
|
+
end
|
218
|
+
end
|
219
|
+
end
|
220
|
+
attrs
|
221
|
+
end
|
231
222
|
end
|
232
223
|
|
233
224
|
def self.load_zip(path, &block)
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: obf
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.2.
|
4
|
+
version: 0.2.3
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Brian Whitmer
|
@@ -118,10 +118,12 @@ files:
|
|
118
118
|
- LICENSE
|
119
119
|
- README.md
|
120
120
|
- lib/obf.rb
|
121
|
+
- lib/obf/avaz.rb
|
121
122
|
- lib/obf/external.rb
|
122
123
|
- lib/obf/obf.rb
|
123
124
|
- lib/obf/obz.rb
|
124
125
|
- lib/obf/pdf.rb
|
126
|
+
- lib/obf/picto4me.rb
|
125
127
|
- lib/obf/png.rb
|
126
128
|
- lib/obf/utils.rb
|
127
129
|
- lib/tinycolor_convert.js
|