canvas_exporting 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 87c0ec1e5a813f6c937e22ed3bb51cf1f25297c3
4
+ data.tar.gz: c53831121a9dd9ae28e2aa156ad83ebdf81b7311
5
+ SHA512:
6
+ metadata.gz: 7b23a241c14c8754f7d9fcf461fb7e2428396a42e5d6972f125759541993fb8030eb2578b3f9a78ec881c3833518a658c4d431b4896b741ef6323595fa2cab89
7
+ data.tar.gz: dcd0e8a762d7bfe781a061b564c400bc3b7e305566c64e3b922613ca3134743c2fa8a1de259c97cff074339fe948500dedff304ade93b888e7b84911c5d71921
data/MIT-LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright 2015 bastengao
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/Rakefile ADDED
@@ -0,0 +1,26 @@
1
+ begin
2
+ require 'bundler/setup'
3
+ rescue LoadError
4
+ puts 'You must `gem install bundler` and `bundle install` to run rake tasks'
5
+ end
6
+
7
+ require 'rdoc/task'
8
+
9
+ RDoc::Task.new(:rdoc) do |rdoc|
10
+ rdoc.rdoc_dir = 'rdoc'
11
+ rdoc.title = 'HighchartsExporting'
12
+ rdoc.options << '--line-numbers'
13
+ rdoc.rdoc_files.include('README.rdoc')
14
+ rdoc.rdoc_files.include('lib/**/*.rb')
15
+ end
16
+
17
+
18
+
19
+
20
+
21
+
22
+ Bundler::GemHelper.install_tasks
23
+ require File.join('rspec', 'core', 'rake_task')
24
+ RSpec::Core::RakeTask.new(:spec)
25
+
26
+ task :default => :spec
@@ -0,0 +1,102 @@
1
+ require 'fileutils'
2
+ require 'tempfile'
3
+ require 'phantomjs'
4
+
5
+ # references:
6
+ # http://www.highcharts.com/docs/export-module/export-module-overview
7
+ # https://github.com/highslide-software/highcharts.com/tree/master/exporting-server/phantomjs
8
+ module CanvasExporting
9
+ module Exporter
10
+ def export
11
+ @infile_tmp_file = infile_file
12
+
13
+ type = params[:type] || 'image/png'
14
+ extension = MIME::Types[type].first.extensions.first
15
+
16
+ if params[:outputpath] == nil
17
+ output_path = tmp_dir
18
+ else
19
+ output_path = params[:outputpath]
20
+ end
21
+
22
+ output_path = output_path + '/' if output_path[-1] != '/'
23
+
24
+ if params[:filename] == nil
25
+ filename = 'Chart.' + extension
26
+ else
27
+ filename = params[:filename]
28
+ end
29
+
30
+ @output_file = output_path + filename
31
+
32
+ scale = params[:scale] || 2
33
+ width = params[:width]
34
+ constr = params[:constr] || 'Chart'
35
+
36
+ convert_args = convert_args({infile: @infile_tmp_file.path,
37
+ outfile: @output_file,
38
+ scale: scale,
39
+ width: width,
40
+ constr: constr,
41
+ callback: callback_path
42
+ })
43
+
44
+ result = ::Phantomjs.run(*convert_args)
45
+ #puts result if VERBOSE
46
+
47
+ # TODO: clean @output_tmp_file
48
+ @infile_tmp_file.delete
49
+ @callback_tmp_file.delete if @callback_tmp_file
50
+
51
+ if /Error/ =~ result
52
+ render text: result, status: 500
53
+ else
54
+ send_file @output_file, filename: "#{filename}", type: type
55
+ end
56
+ end
57
+
58
+ protected
59
+ def tmp_dir
60
+ Rails.root.join('tmp/canvas_charts').to_s.tap { |f| FileUtils.mkdir_p f }
61
+ end
62
+
63
+ def infile_file
64
+ tmp_file = nil
65
+ if params[:options]
66
+ tmp_file = Tempfile.new(['options', '.json'], tmp_dir)
67
+ temp_write(tmp_file, params[:options])
68
+ elsif params[:svg]
69
+ tmp_file = Tempfile.new(['options', '.svg'], tmp_dir)
70
+ temp_write(tmp_file, params[:svg])
71
+ end
72
+
73
+ tmp_file
74
+ end
75
+
76
+ def callback_path
77
+ if params[:callback]
78
+ @callback_tmp_file = Tempfile.new(['callbacks', '.js'], tmp_dir)
79
+ temp_write(@callback_tmp_file, params[:callback])
80
+ @callback_tmp_file.path
81
+ else
82
+ nil
83
+ end
84
+ end
85
+
86
+ def convert_args(args)
87
+ convert_args = args.reject { |k, v| v.blank? }.to_a.map { |pair| ["-#{pair[0]}", pair[1].to_s] }.flatten
88
+
89
+ convert_args.unshift("--web-security=false", convert_js_path)
90
+ end
91
+
92
+ def convert_js_path
93
+ File.join(ROOT, 'phantomjs/canvas-convert.js').to_s
94
+ end
95
+
96
+ def temp_write(tmp_file, content)
97
+ File.open(tmp_file.path, 'r+') do |f|
98
+ f.write content
99
+ end
100
+ end
101
+ end
102
+ end
@@ -0,0 +1,3 @@
1
+ module CanvasExporting
2
+ VERSION = "0.0.1"
3
+ end
@@ -0,0 +1,7 @@
1
+ require 'canvas_exporting/version'
2
+ require 'canvas_exporting/exporter'
3
+
4
+ module CanvasExporting
5
+ ROOT = File.expand_path("../..", __FILE__)
6
+ VERBOSE = false
7
+ end
@@ -0,0 +1 @@
1
+ var dummy = 0;
@@ -0,0 +1,290 @@
1
+
2
+ (function () {
3
+ "use strict";
4
+
5
+ var config = {
6
+ /* define locations of mandatory javascript files.
7
+ */
8
+ files: {
9
+ JQUERY: 'jquery.1.9.1.min.js',
10
+ /* JQUERY: 'jquery.js', */
11
+ CANVAS: 'canvasjs.js',
12
+ CANVAS_EXPORT: 'excanvas.js'
13
+ },
14
+ TIMEOUT: 5000 /* 5 seconds timout for loading images */
15
+ },
16
+ mapCLArguments,
17
+ render,
18
+ startServer = false,
19
+ args,
20
+ pick,
21
+ SVG_DOCTYPE = '<?xml version=\"1.0" standalone=\"no\"?><!DOCTYPE svg PUBLIC \"-//W3C//DTD SVG 1.1//EN\" \"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd\">',
22
+ dpiCorrection = 1.4,
23
+ system = require('system'),
24
+ fs = require('fs'),
25
+ serverMode = false;
26
+
27
+ pick = function () {
28
+ var args = arguments, i, arg, length = args.length;
29
+ for (i = 0; i < length; i += 1) {
30
+ arg = args[i];
31
+ if (arg !== undefined && arg !== null && arg !== 'null' && arg != '0') {
32
+ return arg;
33
+ }
34
+ }
35
+ };
36
+
37
+ mapCLArguments = function () {
38
+ var map = {},
39
+ i,
40
+ key;
41
+
42
+ if (system.args.length < 1) {
43
+ console.log('Commandline Usage: canvas-convert.js -infile URL -outfile filename -scale 2.5 -width 300 -constr Chart -callback callback.js');
44
+ console.log(', or run PhantomJS as server: canvas-convert.js -host 127.0.0.1 -port 1234');
45
+ }
46
+
47
+ for (i = 0; i < system.args.length; i += 1) {
48
+ if (system.args[i].charAt(0) === '-') {
49
+ key = system.args[i].substr(1, i.length);
50
+ if (key === 'infile' || key === 'callback' || key === 'dataoptions' || key === 'globaloptions' || key === 'customcode') {
51
+ // get string from file
52
+ try {
53
+ map[key] = fs.read(system.args[i + 1]).replace(/^\s+/, '');
54
+ } catch (e) {
55
+ console.log('Error: cannot find file, ' + system.args[i + 1]);
56
+ phantom.exit();
57
+ }
58
+ } else {
59
+ map[key] = system.args[i + 1];
60
+ }
61
+ }
62
+ }
63
+ return map;
64
+ };
65
+
66
+ render = function (params, exitCallback) {
67
+
68
+ var page = require('webpage').create(),
69
+ messages = {},
70
+ scaleAndClipPage,
71
+ loadChart,
72
+ createChart,
73
+ input,
74
+ constr,
75
+ callback,
76
+ width,
77
+ output,
78
+ outType,
79
+ timer,
80
+ renderSVG,
81
+ convert,
82
+ exit,
83
+ interval,
84
+ counter,
85
+ imagesLoaded = false;
86
+
87
+ messages.optionsParsed = 'Canvas.options.parsed';
88
+ messages.callbackParsed = 'Canvas.cb.parsed';
89
+
90
+ window.optionsParsed = false;
91
+ window.callbackParsed = false;
92
+
93
+ page.onConsoleMessage = function (msg) {
94
+ console.log(msg);
95
+
96
+ /*
97
+ * Ugly hack, but only way to get messages out of the 'page.evaluate()'
98
+ * sandbox. If any, please contribute with improvements on this!
99
+ */
100
+
101
+ /* to check options or callback are properly parsed */
102
+ if (msg === messages.optionsParsed) {
103
+ window.optionsParsed = true;
104
+ }
105
+
106
+ if (msg === messages.callbackParsed) {
107
+ window.callbackParsed = true;
108
+ }
109
+ };
110
+
111
+ page.onAlert = function (msg) {
112
+ console.log(msg);
113
+ };
114
+
115
+
116
+ exit = function (result) {
117
+ if (serverMode) {
118
+ //Calling page.close(), may stop the increasing heap allocation
119
+ page.close();
120
+ }
121
+ exitCallback(result);
122
+ };
123
+
124
+ loadChart = function( string_in, outputType ) {
125
+
126
+ try {
127
+
128
+ var container, htmlout;
129
+
130
+ $(document.body).css('margin', '0px');
131
+
132
+ $(document.body).css('backgroundColor', 'white');
133
+
134
+ container = $('<div>').appendTo(document.body);
135
+ container.attr('id', 'chartContainer' );
136
+ // var sizing = 'width: ' + width + 'px;';
137
+ // $container.attr('style', sizing );
138
+ //console.log(" json_in: " + JSON.parse(json_in) );
139
+ var json_in = JSON.parse(string_in);
140
+ //json_in = JSON.parse(json_in);
141
+ json_in["animationEnabled"] = false;
142
+
143
+ //console.log ("just before canvas rendering");
144
+
145
+ var chartTest = new CanvasJS.Chart("chartContainer", json_in );
146
+ console.log ("complete the object create");
147
+
148
+ var rs = chartTest.render();
149
+ console.log ("completed the rendering");
150
+
151
+ var htmlout = $('.canvasjs-chart-canvas')[0];
152
+ //var imageData = htmlout.toDataURL(outType, 0.92 );
153
+ var imageData = htmlout.toDataURL('image/'+outputType, 0.92 );
154
+ } catch (e) {
155
+ console.log('ERROR: Cannot create CanvasJS object.');
156
+ console.log('Error message: ' + e.number + " - " + e.message)
157
+ return { html: "<p>Error output</p>" };
158
+ }
159
+ return {
160
+ html: imageData
161
+ };
162
+ };
163
+
164
+
165
+ if (params.length < 1) {
166
+ exit("Error: Insufficient parameters");
167
+ } else {
168
+ input = params.infile;
169
+ output = params.outfile;
170
+ console.log("input: " + input);
171
+ console.log("output: " + output);
172
+
173
+ if (output !== undefined) {
174
+ outType = pick(output.split('.').pop(),'png');
175
+ } else {
176
+ outType = pick(params.type,'png');
177
+ }
178
+
179
+ constr = pick(params.constr, 'Chart');
180
+ callback = params.callback;
181
+ width = params.width;
182
+
183
+ if (input === undefined || input.length === 0) {
184
+ exit('Error: Insuficient or wrong parameters for rendering');
185
+ }
186
+
187
+ page.open('about:blank', function (status) {
188
+ var svg,
189
+ globalOptions = params.globaloptions,
190
+ dataOptions = params.dataoptions,
191
+ customCode = 'function customCode(options) {\n' + params.customcode + '}\n',
192
+ jsfile;
193
+
194
+ // load necessary libraries
195
+ for (jsfile in config.files) {
196
+ if (config.files.hasOwnProperty(jsfile)) {
197
+ page.injectJs(config.files[jsfile]);
198
+ //console.log("injecting: " + config.files[jsfile] );
199
+ }
200
+ }
201
+
202
+ var rs = page.evaluate(loadChart, input, outType );
203
+
204
+ try {
205
+ var result = rs["html"].split(",")
206
+ var result_blob = window.atob(result[1])
207
+ var imageFile = fs.open(params.outfile, "wb");
208
+ imageFile.write(result_blob);
209
+ imageFile.close();
210
+ } catch (e) {
211
+ console.log("error evaluating document object. error: " + e.message);
212
+ }
213
+
214
+ // return the tmp file name and path to the calling process
215
+ exit(params.outfile)
216
+
217
+ });
218
+ }
219
+ };
220
+
221
+ startServer = function (host, port) {
222
+ var server = require('webserver').create();
223
+
224
+ server.listen(host + ':' + port,
225
+ function (request, response) {
226
+ var jsonStr = request.postRaw || request.post,
227
+ params,
228
+ msg;
229
+ try {
230
+ params = JSON.parse(jsonStr);
231
+ if (params.status) {
232
+ // for server health validation
233
+ response.statusCode = 200;
234
+ response.write('OK');
235
+ response.close();
236
+ } else {
237
+ render(params, function (result) {
238
+ response.statusCode = 200;
239
+ response.write(result);
240
+ response.close();
241
+ });
242
+ }
243
+ } catch (e) {
244
+ msg = "Failed rendering: \n" + e;
245
+ response.statusCode = 500;
246
+ response.setHeader('Content-Type', 'text/plain');
247
+ response.setHeader('Content-Length', msg.length);
248
+ response.write(msg);
249
+ response.close();
250
+ }
251
+ }); // end server.listen
252
+
253
+ // switch to serverMode
254
+ serverMode = true;
255
+
256
+ console.log("OK, PhantomJS is ready.");
257
+ };
258
+
259
+ args = mapCLArguments();
260
+
261
+ //console.log("made it past the args mapper")
262
+ //console.log("arguments: " + JSON.stringify(args));
263
+ // set tmpDir, for output temporary files.
264
+ if (args.tmpdir === undefined) {
265
+ config.tmpDir = fs.workingDirectory + '/tmp';
266
+ } else {
267
+ config.tmpDir = args.tmpdir;
268
+ }
269
+
270
+ // exists tmpDir and is it writable?
271
+ if (!fs.exists(config.tmpDir)) {
272
+ try{
273
+ fs.makeDirectory(config.tmpDir);
274
+ } catch (e) {
275
+ console.log('ERROR: Cannot create temp directory for ' + config.tmpDir);
276
+ }
277
+ }
278
+
279
+
280
+ if (args.host !== undefined && args.port !== undefined) {
281
+ startServer(args.host, args.port);
282
+ } else {
283
+ // presume commandline usage
284
+ //console.log("calling the renderer");
285
+ render(args, function (msg) {
286
+ console.log(msg);
287
+ phantom.exit();
288
+ });
289
+ }
290
+ }());