fontaine 0.1.0

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: d703f331a7703b5b79e7a0f297fb61f1dd01901c
4
+ data.tar.gz: c07f87f06bd72a41592634dcc33d5d8420eabd71
5
+ SHA512:
6
+ metadata.gz: fa08b41eb0c8bd02dc377b0e6dbf73ac11b47c33d94a3a18efda2d354a5f741578af91582e6f23b893a9961963303c90ed5fbde24c21d14544719a17cd62864f
7
+ data.tar.gz: 9f99d633d7a9d6b4d95502e580e4f511e6cc927cf4d825e8c8b7e674bbec25ea15984b658c9848cf152cc1979e37942cb786d00e50f862bd6cc4fd0a40cd260a
data/.gitignore ADDED
@@ -0,0 +1,17 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in fontaine.gemspec
4
+ gemspec
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2013 TODO: Write your name
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,199 @@
1
+ # Fontaine
2
+
3
+ A bridge between Ruby and HTML5 canvas **for Sinatra** using Websocket. Basically, this allows you to make <canvas> based
4
+ apps in Ruby.
5
+
6
+ ## Installation
7
+
8
+ Add this line to your application's Gemfile:
9
+
10
+ gem 'fontaine'
11
+
12
+ And then execute:
13
+
14
+ $ bundle
15
+
16
+ Or install it yourself as:
17
+
18
+ $ gem install fontaine
19
+
20
+ ## Usage
21
+
22
+ Just put this in your pipe:
23
+
24
+ require 'sinatra/base'
25
+ require 'fontaine/canvas' #the meat of everything canvas-related
26
+ require 'fontaine/bootstrap'
27
+ require 'sinatra-websocket'
28
+ require 'haml' #not strictly necessary, but recommended
29
+
30
+ class Sample < Sinatra::Base
31
+ register Sinatra::Fontaine::Bootstrap::Assets
32
+ set :server, 'thin'
33
+ set :sockets, []
34
+
35
+ get '/' do
36
+
37
+ @canvas = Fontaine::Canvas.new("TEST_CANVAS", 500, 500, "Your browser does not support the canvas tag",
38
+ :style => "border:1px solid #000000;") do |canvas|
39
+
40
+ canvas.on_click do |x, y, button| #when there's a click, do this:
41
+ canvas.rect(x.to_i-25, y.to_i-25, 50, 50)
42
+
43
+ if button.eql? '0' #left click will give you a solid red rectangle
44
+ canvas.fill_style("#FF0000")
45
+ canvas.fill
46
+ else #right click will give you the outline of a blue rectangle
47
+ canvas.stroke_style("blue")
48
+ canvas.stroke
49
+ end
50
+
51
+ end
52
+ end
53
+
54
+ if request.websocket?
55
+ @canvas.listen(request, settings)
56
+ else
57
+ haml :index
58
+
59
+ end
60
+ end
61
+
62
+ end
63
+
64
+ ## views/index.haml
65
+
66
+ %script{:src => "/javascripts/jquery-1.9.1.js", :type => "text/javascript"}
67
+ = bootstrap_fontaine
68
+ = @canvas.display
69
+
70
+ ## sample_config.ru
71
+
72
+ require './sample'
73
+ run Sample
74
+
75
+ and smoke it! (After you've put jquery-1.9.1.js into /public/javascripts/)
76
+
77
+ rackup sample_config.ru
78
+
79
+ Ok, that's pretty complicated. Let me give you some further explanations. We'll start with
80
+ making a new Canvas:
81
+
82
+ Fontaine::Canvas.new("TEST_CANVAS", 500, 500, "Your browser does not support the canvas tag",
83
+ :style => "border:1px solid #000000;")
84
+
85
+ This is pretty simple. The first parameter is simply the id of the canvas you will be displaying. The second
86
+ and third parameters are the width and height of the canvas. The fourth is the alt text if the user's browser
87
+ doesn't support <canvas>. Finally, the fifth parameter is a hash of any other html options you
88
+ wish to add to the canvas. In this case, I'm giving it a border.
89
+
90
+ Next, while initializing, you can give it a block of code to execute when the user takes actions related to the canvas.
91
+ (Note, you can do this at any point once the canvas is initialized by passing a block to any of the action methods).
92
+ The block will be whatever code you want to run in response to those actions. In addition, each action is yielded
93
+ a few parameters from the javascript event call. For more information on specific actions available and their parameters, see
94
+ the **Action Methods** section.
95
+
96
+ That's only half the story, though. You've got to be able to draw on the canvas! You can use any of the methods from
97
+ [here](http://www.w3schools.com/tags/ref_canvas.asp) and they will work pretty much how you'd expect, with a couple
98
+ exceptions (see **Exceptions and Issues**). Most of the time, you simply can directly use a 'rubyfied' version of the same
99
+ method. For example, instead of calling @canvas.fillRect(), you call @canvas.fill_rect. The other major exception is that rather
100
+ than setting canvas attributes with '=', you'll simply use a method of the same name (canvas.fill_style("#FF0000"),
101
+ not canvas.fill_style = "#FF0000")
102
+
103
+ Alright, so you've got your action methods set up and you know how to draw on the canvas, you just have to get it going. In the
104
+ sinatra file this is pretty simple. When you get a request, listen! Pass in the request and the sinatra settings.
105
+
106
+ if request.websocket?
107
+ @canvas.listen(request, settings)
108
+ end
109
+
110
+ The view is almost as easy:
111
+
112
+ %script{:src => "/javascripts/jquery-1.9.1.js", :type => "text/javascript"}
113
+ = bootstrap_fontaine
114
+ = @canvas.display
115
+
116
+ Bam! You need jquery. I've tested on 1.9.1, but not any other versions. bootstrap_fontaine gives you access to the fontaine javascript
117
+ file. @canvas.display will display the canvas.
118
+
119
+ ## Action Methods
120
+
121
+ **NOTE: All coordinates are in relation to the canvas, not to the document** 0, 0 is the top-left corner of the canvas, for example.
122
+
123
+ **on_click** Yields x, y, and button
124
+
125
+ **on_mousedown** Yields x, y, and button
126
+
127
+ **on_mouseup** Yields x, y, and button
128
+
129
+ **on_mouseover** Yields x and y
130
+
131
+ **on_mouseout** Yields x and y
132
+
133
+ **on_mousemove** Yields x and y
134
+
135
+ **on_keydown** Yields key_code
136
+
137
+ (Note, this is not the ascii value of the key pressed. See javascript documentation on the difference between keyCode and
138
+ the value)
139
+
140
+ **on_keyup** Yields key_code
141
+
142
+ **on_keypress** Yields which
143
+ (This *is* the ascii value of the key pressed)
144
+
145
+ ## Drawing methods
146
+
147
+ You can use any of the methods from
148
+ [here](http://www.w3schools.com/tags/ref_canvas.asp) and they will work pretty much how you'd expect, with a couple
149
+ exceptions (see **Exceptions and Issues**). Most of the time, you simply can directly use a 'rubyfied' version of the same
150
+ method. For example, instead of calling @canvas.fillRect(), you call @canvas.fill_rect. The other major exception is that rather
151
+ than setting canvas attributes with '=', you'll simply use a method of the same name (canvas.fill_style("#FF0000"),
152
+ not canvas.fill_style = "#FF0000")
153
+
154
+ ## Exceptions and Issues
155
+
156
+ Any "Drawing" method that returns an attribute doesn't "really" give you the attribute. It returns the value of the attribute from the
157
+ last time you changed it from the ruby Canvas object. In other words, if I call
158
+
159
+ @canvas.fill_style("blue")
160
+
161
+ on the ruby side, then later call
162
+
163
+ canvas.fillStyle = "#FF0000"
164
+
165
+ in the javascript, when I call
166
+
167
+ @canvas.fill_style
168
+
169
+ in ruby, it'll still return "blue".
170
+
171
+ Basically, this is because websocket is impatient. It doesn't have a protocol for waiting for the javascript to return information before
172
+ executing the next line of code. So, I faked it and track everything on the fontaine canvas object.
173
+
174
+ If you stick to just using the fontaine canvas methods, this mostly doesn't have an impact, **except** that the get_image_data method isn't
175
+ implemented, and doesn't work right now. Don't even try.
176
+
177
+ ## Contributing
178
+
179
+ 1. Fork it
180
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
181
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
182
+ 4. Push to the branch (`git push origin my-new-feature`)
183
+ 5. Create new Pull Request
184
+
185
+ ##Future Features/Goals
186
+
187
+ **Soon**
188
+
189
+ * Implement on_keystroke methods for each key. For example, on_keystroke_a.
190
+
191
+ **Later**
192
+
193
+ * Figure a more elegant way to bootstrap the javascript, avoiding rackup and extending canvas
194
+ * Add a scheduler or some other way to "wait" for get methods from the javascript
195
+
196
+ **In the distant future, when apes rule the Earth**
197
+
198
+ * Implement this in Ruby on Rails
199
+ * Implement audio and video tags
data/Rakefile ADDED
@@ -0,0 +1 @@
1
+ require "bundler/gem_tasks"
data/fontaine.gemspec ADDED
@@ -0,0 +1,27 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'fontaine/version'
5
+ require 'fontaine/canvas'
6
+
7
+ Gem::Specification.new do |spec|
8
+ spec.name = "fontaine"
9
+ spec.version = Fontaine::VERSION
10
+ spec.authors = ["J. Paul Wetstein"]
11
+ spec.email = ["Jeep.Wetstein@gmail.com"]
12
+ spec.description = %q{A bridge between Ruby and HTML5 canvas using Websocket}
13
+ spec.summary = %q{A bridge between Ruby and HTML5 canvas using Websocket}
14
+ spec.homepage = ""
15
+ spec.license = "MIT"
16
+
17
+ spec.files = `git ls-files`.split($/)
18
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
19
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
20
+ spec.require_paths = ["lib"]
21
+
22
+ spec.add_dependency 'sinatra-websocket', '~>0.2.0'
23
+
24
+ spec.add_development_dependency "bundler", "~> 1.3"
25
+ spec.add_development_dependency "rake"
26
+
27
+ end
data/lib/fontaine.rb ADDED
@@ -0,0 +1,4 @@
1
+ require "fontaine/version"
2
+ module Fontaine
3
+
4
+ end
@@ -0,0 +1,452 @@
1
+ $(document).ready(function()
2
+ {
3
+ var ws = new WebSocket('ws://' + window.location.host + window.location.pathname);
4
+
5
+ var canvas;
6
+ var ctx;
7
+ var canvasPosition;
8
+ var current_mouse;
9
+ var objectsHash = {};
10
+
11
+ ws.onmessage = function(msg)
12
+ {
13
+ msg_array = msg.data.split(" ");
14
+ command = msg_array[0];
15
+ msg_array.shift();
16
+ params = msg_array;
17
+
18
+
19
+
20
+ switch(command)
21
+ {
22
+
23
+ case "register":
24
+ canvas = $(params[0]);
25
+ ctx=canvas[0].getContext('2d');
26
+ canvasPosition =
27
+ {
28
+ x: canvas.offset().left,
29
+ y: canvas.offset().top
30
+ };
31
+
32
+ canvas.on('mousedown', function(e)
33
+ {
34
+ current_mouse =
35
+ {
36
+ x: e.pageX - canvasPosition.x,
37
+ y: e.pageY - canvasPosition.y
38
+ }
39
+ ws.send("mousedown x "+current_mouse.x+" y "+current_mouse.y + " button "+ e.button);
40
+ });
41
+
42
+ canvas.on('mouseup', function(e)
43
+ {
44
+ current_mouse =
45
+ {
46
+ x: e.pageX - canvasPosition.x,
47
+ y: e.pageY - canvasPosition.y
48
+ }
49
+ ws.send("mouseup x "+current_mouse.x+" y "+current_mouse.y + " button "+ e.button);
50
+ });
51
+
52
+ canvas.on('click', function(e)
53
+ {
54
+ e.preventDefault();
55
+ current_mouse =
56
+ {
57
+ x: e.pageX - canvasPosition.x,
58
+ y: e.pageY - canvasPosition.y
59
+ }
60
+ ws.send("click x "+current_mouse.x+" y "+current_mouse.y + " button "+ e.button);
61
+ });
62
+
63
+ canvas.on('mousemove', function(e)
64
+ {
65
+ current_mouse =
66
+ {
67
+ x: e.pageX - canvasPosition.x,
68
+ y: e.pageY - canvasPosition.y
69
+ }
70
+ ws.send("mousemove x "+current_mouse.x+" y "+current_mouse.y + " button "+ e.button);
71
+ });
72
+
73
+ canvas.on('mouseover', function(e)
74
+ {
75
+ current_mouse =
76
+ {
77
+ x: e.pageX - canvasPosition.x,
78
+ y: e.pageY - canvasPosition.y
79
+ }
80
+ ws.send("mouseover x "+current_mouse.x+" y "+current_mouse.y + " button "+ e.button);
81
+ });
82
+
83
+ canvas.on('mouseout', function(e)
84
+ {
85
+ current_mouse =
86
+ {
87
+ x: e.pageX - canvasPosition.x,
88
+ y: e.pageY - canvasPosition.y
89
+ }
90
+ ws.send("mouseout x "+current_mouse.x+" y "+current_mouse.y + " button "+ e.button);
91
+ });
92
+
93
+ // canvas.on('touchstart', function(e)
94
+ // {
95
+ // e.preventDefault();
96
+ // var touchList = e.changedTouches;
97
+ // var current_touch;
98
+ // for(var i = 0; i < touchList.length; i++)
99
+ // {
100
+ // current_touch =
101
+ // {
102
+ // x: touchList[i].screenX - canvasPosition.x,
103
+ // y: touchList[i].screenY - canvasPosition.y,
104
+ // id: touchList[i].identifier
105
+ // };
106
+ // ws.send("touchstart x "+current_touch.x+" y "+current_touch.y + " id " + current_touch.id);
107
+ // }
108
+ // });
109
+ //
110
+ // canvas.on('touchmove', function(e)
111
+ // {
112
+ // var touchList = e.changedTouches;
113
+ // var current_touch;
114
+ // for(var i = 0; i < touchList.length; i++)
115
+ // {
116
+ // current_touch =
117
+ // {
118
+ // x: touchList[i].screenX - canvasPosition.x,
119
+ // y: touchList[i].screenY - canvasPosition.y,
120
+ // id: touchList[i].identifier
121
+ // };
122
+ // ws.send("touchmove x "+current_touch.x+" y "+current_touch.y + " id " + current_touch.id);
123
+ // }
124
+ // });
125
+ //
126
+ // canvas.on('touchend', function(e)
127
+ // {
128
+ // var touchList = e.changedTouches;
129
+ // var current_touch;
130
+ // for(var i = 0; i < touchList.length; i++)
131
+ // {
132
+ // current_touch =
133
+ // {
134
+ // x: touchList[i].screenX - canvasPosition.x,
135
+ // y: touchList[i].screenY - canvasPosition.y,
136
+ // id: touchList[i].identifier
137
+ // };
138
+ // ws.send("touchend x "+current_touch.x+" y "+current_touch.y + " id " + current_touch.id);
139
+ // }
140
+ // });
141
+
142
+ document.onkeydown = function(e)
143
+ {
144
+ ws.send("keydown key_code "+e.keyCode); //+ " which " + e.which + " char_code " + e.charCode);
145
+ }
146
+
147
+ document.onkeypress = function(e)
148
+ {
149
+ ws.send("keypress which "+e.which); //+ " which " + e.which + " char_code " + e.charCode);
150
+ }
151
+
152
+ document.onkeyup = function(e)
153
+ {
154
+ ws.send("keyup key_code "+e.keyCode); //+ " which " + e.which + " char_code " + e.charCode);
155
+ }
156
+
157
+ break;
158
+ case "fillStyle":
159
+ if(params.length==1)
160
+ {
161
+ ctx.fillStyle = params[0];
162
+ }
163
+ ws.send("response " + ctx.fillStyle);
164
+ break;
165
+ case 'fillStyleObject':
166
+ ctx.fillStyle = objectsHash[params[0]];
167
+ ws.send("response " + ctx.fillStyle);
168
+ break;
169
+ case "strokeStyle":
170
+ if(params.length>0)
171
+ {
172
+ ctx.strokeStyle = params[0];
173
+ }
174
+ ws.send("response " + ctx.strokeStyle);
175
+ break;
176
+ case 'strokeStyleObject':
177
+ ctx.strokeStyle = objectsHash[params[0]];
178
+ ws.send("response " + ctx.strokeStyle);
179
+ break;
180
+ case "shadowColor":
181
+ if(params[0] != "")
182
+ {
183
+ ctx.shadowColor = params[0];
184
+ }
185
+ //alert("sc " + ctx.shadowColor);
186
+ ws.send("response " + ctx.shadowColor);
187
+ break;
188
+ case "shadowBlur":
189
+ if(params.length>0)
190
+ {
191
+ ctx.shadowBlur = params[0];
192
+ }
193
+ ws.send("response " + ctx.shadowBlur);
194
+ break;
195
+ case "shadowOffsetX":
196
+ if(params.length>0)
197
+ {
198
+ ctx.shadowOffsetX = params[0];
199
+ }
200
+ ws.send("response " + ctx.shadowOffsetX);
201
+ break;
202
+ case "shadowOffsetY":
203
+ if(params.length>0)
204
+ {
205
+ ctx.shadowOffsetY = params[0];
206
+ }
207
+ ws.send("response " + ctx.shadowOffsetY);
208
+ break;
209
+
210
+ case "createLinearGradient":
211
+ objectsHash[params[4]] =
212
+ ctx.createLinearGradient(params[0], params[1], params[2], params[3]);
213
+ break;
214
+ case "createPattern":
215
+ objectsHash[params[2]] = ctx.createPattern(params[0], params[1]);
216
+ break;
217
+ case "createRadialGradient":
218
+ objectsHash[params[6]] =
219
+ ctx.createRadialGradient(params[0], params[1], params[2], params[3], params[4], params[5]);
220
+ break;
221
+ case "addColorStop":
222
+ objectsHash[params[2].addColorStop(params[0], params[1])];
223
+ break;
224
+ case "lineCap":
225
+ if(params.length>0)
226
+ {
227
+ ctx.lineCap = params[0]
228
+ }
229
+ ws.send("response " + ctx.lineCap);
230
+ break;
231
+ case "lineJoin":
232
+ if(params.length>0)
233
+ {
234
+ ctx.lineJoin = params[0]
235
+ }
236
+ ws.send("response " + ctx.lineJoin);
237
+ break;
238
+ case "lineWidth":
239
+ if(params.length>0)
240
+ {
241
+ ctx.lineWidth = params[0]
242
+ }
243
+ ws.send("response " + ctx.lineWidth);
244
+ break;
245
+ case "miterLimit":
246
+ if(params.length>0)
247
+ {
248
+ ctx.miterLimit = params[0]
249
+ }
250
+ ws.send("response " + ctx.miterLimit);
251
+ break;
252
+ case "rect":
253
+ ctx.rect(params[0], params[1], params[2], params[3]);
254
+ break;
255
+ case "strokeRect":
256
+ ctx.strokeRect(params[0], params[1], params[2], params[3]);
257
+ break;
258
+ case "fillRect":
259
+ ctx.fillRect(params[0], params[1], params[2], params[3]);
260
+ break;
261
+ case "clearRect":
262
+ ctx.clearRect(params[0], params[1], params[2], params[3]);
263
+ break;
264
+ case "fill":
265
+ ctx.fill();
266
+ break;
267
+ case "stroke":
268
+ ctx.stroke();
269
+ break;
270
+ case "beginPath":
271
+ ctx.beginPath();
272
+ break;
273
+ case "moveTo":
274
+ ctx.moveTo(params[0], params[1]);
275
+ break;
276
+ case "closePath":
277
+ ctx.closePath();
278
+ break;
279
+ case "lineTo":
280
+ ctx.lineTo(params[0], params[1]);
281
+ break;
282
+ case "clip":
283
+ ctx.clip();
284
+ break;
285
+ case "quadraticCurveTo":
286
+ ctx.quadraticCurveTo(params[0], params[1], params[2], params[3]);
287
+ break;
288
+ case "bezierCurveTo":
289
+ ctx.bezierCurveTo(params[0], params[1], params[2], params[3], params[4], params[5]);
290
+ break;
291
+ case "arc":
292
+ ctx.arc(params[0], params[1], params[2], params[3], params[4], params[5]);
293
+ break;
294
+ case "arcTo":
295
+ ctx.arcTo(params[0], params[1], params[2], params[3], params[4]);
296
+ break;
297
+ case "isPointInPath":
298
+ ctx.isPointInPath(params[0], params[1]);
299
+ break;
300
+ case "scale":
301
+ ctx.scale(params[0], params[1]);
302
+ break;
303
+ case "rotate":
304
+ ctx.rotate(params[0]);
305
+ break;
306
+ case "translate":
307
+ ctx.translate(params[0], params[1]);
308
+ break;
309
+ case "transform":
310
+ ctx.transform(params[0], params[1], params[2], params[3], params[4], params[5]);
311
+ break;
312
+ case "setTransform":
313
+ ctx.setTransform(params[0], params[1], params[2], params[3], params[4], params[5]);
314
+ break;
315
+ case "font":
316
+ if(params.length!=0)
317
+ {
318
+ ctx.font = params.join(" ");
319
+ }
320
+ ws.send("response " + ctx.font);
321
+ break;
322
+ case "textAlign":
323
+ if(params.length!=0)
324
+ {
325
+ ctx.textAlign = params[0];
326
+ }
327
+ ws.send("response " + ctx.textAlign);
328
+ break;
329
+ case "textBaseline":
330
+ if(params.length!=0)
331
+ {
332
+ ctx.textBaseline = params[0];
333
+ }
334
+ ws.send("response " + ctx.textBaseline);
335
+ break;
336
+ case "fillText":
337
+ if(params.length==3)
338
+ {
339
+ ctx.fillText(params[0], params[1], params[2]);
340
+ }
341
+ else
342
+ {
343
+ ctx.fillText(params[0], params[1], params[2], params[3]);
344
+ }
345
+ break;
346
+ case "strokeText":
347
+ if(params.length==3)
348
+ {
349
+ ctx.strokeText(params[0], params[1], params[2]);
350
+ }
351
+ else
352
+ {
353
+ ctx.strokeText(params[0], params[1], params[2], params[3]);
354
+ }
355
+ break;
356
+ case "measureText":
357
+ ws.send("response " + ctx.measureText(params[0]).width);
358
+ break;
359
+ case "drawImage":
360
+ var img=document.getElementById(params[0]);
361
+ if(params.length == 3)
362
+ {
363
+ ctx.drawImage(img, params[1], params[2]);
364
+ }
365
+ else if(params.length == 5)
366
+ {
367
+ ctx.drawImage(img, params[1], params[2], params[3], params[4]);
368
+ }
369
+ else
370
+ {
371
+ ctx.drawImage(img, params[1], params[2], params[3], params[4], params[5], params[6]);
372
+ }
373
+ break;
374
+ case "imageDataWidth":
375
+ var img=objectsHash[params[0]];
376
+ ws.send("response " + img.width);
377
+ break;
378
+ case "imageDataHeight":
379
+ var img=objectsHash[params[0]];
380
+ ws.send("response " + img.height);
381
+ break;
382
+ case "imageDataData":
383
+ var img=objectsHash[params[0]];
384
+ ws.send("response " + img.data);
385
+ break;
386
+ case "createImageData":
387
+ var img;
388
+ if(params.length == 3)
389
+ {
390
+ img = ctx.createImageData(params[0], params[1]);
391
+ }
392
+ else if(params.length == 2)
393
+ {
394
+
395
+ img = ctx.createImageData(objectsHash[params[0]]);
396
+ }
397
+ objectsHash[params[params.length-1]] = img;
398
+ break;
399
+ case "getImageData":
400
+ var img;
401
+ img = ctx.getImageData(params[0], params[1], params[2], params[3])
402
+ objectsHash[params[4]] = img;
403
+ break;
404
+ case "putImageData":
405
+ var img = objectsHash[params[0]];
406
+ if(params.length == 3)
407
+ {
408
+ ctx.putImageData(img, params[1], params[2]);
409
+ }
410
+ else if(params.length == 5)
411
+ {
412
+ ctx.putImageData(img, params[1], params[2], params[3], params[4]);
413
+ }
414
+ else if(params.length == 7)
415
+ {
416
+ ctx.putImageData(img, params[1], params[2], params[3], params[4], params[5], params[6]);
417
+ }
418
+ break;
419
+ case "globalAlpha":
420
+ if(params.length!=0)
421
+ {
422
+ ctx.globalAlpha = params[0];
423
+ }
424
+ ws.send("response " + ctx.globalAlpha);
425
+ break;
426
+ case "globalCompositeOperation":
427
+ if(params.length!=0)
428
+ {
429
+ ctx.globalCompositeOperation = params[0];
430
+ }
431
+ ws.send("response " + ctx.globalCompositeOperation);
432
+ break;
433
+ case "save":
434
+ ctx.save();
435
+ break;
436
+ case "restore":
437
+ ctx.restore();
438
+ break;
439
+ case "toDataUrl":
440
+ ws.send("response " + ctx.toDataURL());
441
+ break;
442
+ default:
443
+ ws.send("Unimplemented command");
444
+ break;
445
+
446
+ }
447
+ }
448
+
449
+
450
+
451
+
452
+ });
@@ -0,0 +1,48 @@
1
+ module Sinatra
2
+ module Fontaine
3
+ module Bootstrap
4
+
5
+ module Assets
6
+
7
+ ASSETS = {
8
+ :js => [
9
+ 'fontaine.js'
10
+ ],
11
+ }
12
+
13
+ def self.generate_bootstrap_asset_routes(app)
14
+ ASSETS.each do |kind, files|
15
+ files.each do |file|
16
+ name = file
17
+
18
+ app.get "/#{kind.to_s}/#{name}", :provides => kind do
19
+ File.read(File.join(File.dirname(__FILE__), 'assets', name))
20
+ end
21
+ end
22
+ end
23
+ end
24
+
25
+ def self.registered(app)
26
+ generate_bootstrap_asset_routes(app)
27
+ app.helpers AssetsHelper
28
+ end
29
+
30
+ end
31
+
32
+ module AssetsHelper
33
+
34
+ def bootstrap_fontaine
35
+ bootstrap_js
36
+ end
37
+
38
+ def bootstrap_js
39
+ output = ''
40
+ Assets::ASSETS[:js].each do |file, _|
41
+ output += '<script type="text/javascript" src="%s"></script>' % url('/js/%s' % file)
42
+ end
43
+ output
44
+ end
45
+ end
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,243 @@
1
+ module Fontaine
2
+ class Canvas
3
+ attr_accessor :id
4
+ attr_accessor :width
5
+ attr_accessor :height
6
+ attr_accessor :alt
7
+ attr_accessor :html_options
8
+ attr_accessor :settings
9
+ attr_accessor :ws
10
+ attr_accessor :last_response
11
+ attr_accessor :attributes
12
+
13
+
14
+ def initialize(id, width, height, alt = "", html_options = {})
15
+ @id = id
16
+ @width = width
17
+ @height = height
18
+ @alt = alt
19
+ @html_options = html_options
20
+ @attributes = {
21
+ #set the initial styles
22
+ :fill_style => "#000000",
23
+ :stroke_style => '#000000',
24
+ :shadow_color => '#000000',
25
+ :shadow_blur => 0,
26
+ :shadow_offset_x => 0,
27
+ :shadow_offset_y => 0,
28
+ :line_cap => 'butt',
29
+ :line_join => 'miter',
30
+ :line_width => '1',
31
+ :miter_limit => '10',
32
+ :font => '10px sans-serif',
33
+ :text_align => 'start',
34
+ :text_baseline => 'alphabetic',
35
+ :global_alpha => 1.0,
36
+ :global_composite_operation => 'source-over'
37
+
38
+ }
39
+ yield self if block_given?
40
+ end
41
+
42
+ def listen(request, settings)
43
+ @settings = settings
44
+ request.websocket do |ws|
45
+ ws.onopen do
46
+ @ws = ws
47
+ @ws.send("register ##{id}")
48
+ @settings.sockets << ws
49
+ end
50
+ ws.onmessage do |msg|
51
+ #puts("recieved message: #{msg}")
52
+ #EM.next_tick { process_message(msg)}
53
+ process_message(msg)
54
+ end
55
+ ws.onclose do
56
+ @settings.sockets.delete(ws)
57
+ end
58
+ end
59
+ end
60
+
61
+ def process_message(s_message)
62
+ a_message = s_message.split(' ')
63
+ command = a_message[0] #the first part of the message is the command
64
+ params = a_message[1..(a_message.length-1)] #get the rest of the message
65
+ params = Hash[*params] if command != "response"#convert the array to a hash if applicable
66
+ respond_to(command, params)
67
+ end
68
+
69
+ def respond_to(s_command, params = {})
70
+ case s_command
71
+ when "mousedown"
72
+ trigger_on_mousedown(params["x"], params["y"], params["button"])
73
+ when "mouseup"
74
+ trigger_on_mouseup(params["x"], params["y"], params["button"])
75
+ when "mouseover"
76
+ trigger_on_mouseover(params["x"], params["y"])
77
+ when "mouseout"
78
+ trigger_on_mouseout(params["x"], params["y"])
79
+ when "mousemove"
80
+ trigger_on_mousemove(params["x"], params["y"])
81
+
82
+ ##Very glitchy right now. Need to fix this some time
83
+
84
+ # when "touchstart"
85
+ # trigger_on_touchstart(params["x"], params["y"], params["id"])
86
+ # when "touchmove"
87
+ # trigger_on_touchstart(params["x"], params["y"], params["id"])
88
+ # when "touchend"
89
+ # trigger_on_touchstart(params["x"], params["y"], params["id"])
90
+ when "keyup"
91
+ trigger_on_keyup(params["key_code"])
92
+ when "keydown"
93
+ trigger_on_keydown(params["key_code"])
94
+ when "keypress"
95
+ trigger_on_keypress(params["which"])
96
+ when "click"
97
+ trigger_on_click(params["x"], params["y"], params["button"])
98
+ when "response"
99
+ @last_response = params.flatten[0].to_s
100
+ trigger_on_response(@last_response)
101
+ else
102
+ puts "unimplemented method #{s_command}"
103
+ end
104
+ end
105
+
106
+ def display
107
+ options = ""
108
+ @html_options.each_pair do |key, value|
109
+ options << "#{key}=\"#{value}\" "
110
+ end
111
+ return "<canvas id=\"#{@id}\" width=\"#{@width}\" height=\"#{@height}\" #{options}>#{@alt}</canvas>"
112
+ end
113
+
114
+ def fill_style(style="")
115
+ @attributes[:fill_style] = style if style != ""
116
+ if style.is_a? String
117
+ send_msg "fillStyle #{style}"
118
+ else
119
+ send_msg "fillStyleObject #{style.id}"
120
+ end
121
+
122
+ # return @last_response
123
+ return @attributes[:fill_style]
124
+
125
+ end
126
+
127
+ def stroke_style(style="")
128
+ @attributes[:stroke_style] = style if style != ""
129
+ if style.is_a? String
130
+ send_msg "strokeStyle #{style}"
131
+ else
132
+ send_msg "strokeStyleObject #{style.id}"
133
+ end
134
+ # return @last_response
135
+ return @attributes[:stroke_style]
136
+ end
137
+
138
+ def create_linear_gradient(x0, y0, x1, y1, id)
139
+ send_msg("createLinearGradient #{x0} #{y0} #{x1} #{y1} #{id}")
140
+ return Gradient.new(id, self)
141
+ end
142
+
143
+ def create_pattern(image, pattern)
144
+ send_msg("createPattern #{image.id} #{pattern}")
145
+ return Pattern.new(id)
146
+ end
147
+
148
+ def create_radial_gradient(x0, y0, r0, x1, y1, r1, id)
149
+ send_msg("createRadialGradient #{x0} #{y0} #{r0} #{x1} #{y1} #{r1} #{id}")
150
+ return Gradient.new(id, self)
151
+ end
152
+
153
+ def create_image_data(id, *args)
154
+ return image_data.new(id, self, args)
155
+ end
156
+
157
+ # def get_image_data(id, x, y, width, height)
158
+ # send_msg("getImageData #{x} #{y} #{width} #{height} #{id}")
159
+ # return image_data.new(id, self)
160
+ # end
161
+
162
+ def put_image_data(image, x, y, dirty_x="", dirty_y="", dirty_width="", dirty_height="")
163
+ send_msg("putImageData #{image.id} #{x} #{y} #{dirty_x} #{dirty_y} #{dirty_width} #{dirty_height}")
164
+ end
165
+
166
+ def send_msg(msg)
167
+ #puts "Sending message: #{msg}"
168
+ #EM.next_tick {@ws.send(msg)} if defined? @ws
169
+ @ws.send(msg) if defined? @ws
170
+ end
171
+
172
+ def method_missing(method_sym, *arguments, &block)
173
+ if canvas_array.include? method_sym.to_s.sub(/trigger_on_/, "")
174
+ variable_name = method_sym.to_s.sub(/trigger_/, "@")
175
+ instance_variable_get(variable_name).call(arguments) if instance_variable_defined?(variable_name)
176
+ elsif canvas_array.include? method_sym.to_s.sub(/on_/, "")
177
+ instance_variable_set("@#{method_sym}", block)
178
+ elsif draw_method_array.include? method_sym.to_s
179
+ send_msg "#{ruby_to_js_command(method_sym)} #{arguments.join(" ")}"
180
+ elsif return_method_array.include? method_sym.to_s
181
+ send_msg "#{ruby_to_js_command(method_sym)} #{arguments.join(" ")}"
182
+ @attributes[method_sym] = arguments[0] if (!arguments[0].nil? && !@attributes[method_sym].nil?)
183
+ return @attributes[method_sym]
184
+ else
185
+ super(method_sym, *arguments, &block)
186
+ end
187
+ end
188
+
189
+ def canvas_array
190
+ return["response", "mousedown", "mouseup", "keyup", "keydown", "keypress", "click", "mouseover",
191
+ "mouseout", "mousemove", "touchstart", "touchmove", "touchend"]
192
+ end
193
+
194
+ def return_method_array
195
+ return[
196
+ #Colors, Styles, and Shadows
197
+ "shadow_color", "shadow_blur", "shadow_offset_x", "shadow_offset_y",
198
+ #Line Styles
199
+ "line_cap", "line_join", "line_width", "miter_limit",
200
+ #Paths
201
+ "isPointInPath",
202
+ #Text
203
+ "font", "text_align", "text_baseline",
204
+ #Composting
205
+ "global_alpha", "global_composite_operation",
206
+ #Other
207
+ "to_data_url"
208
+ ]
209
+ end
210
+
211
+ def draw_method_array
212
+ return [
213
+ #Rectangles
214
+ "rect", "fill_rect", "stroke_rect", "clear_rect",
215
+ #Paths
216
+ "fill", "stroke", "begin_path", "move_to", "close_path", "line_to", "clip",
217
+ "quadratic_curve_to", "bezier_curve_to", "arc", "arc_to",
218
+ #Transformations
219
+ "scale", "rotate", "translate", "transform", "set_transform",
220
+ #Text
221
+ "fill_text", "stroke_text", "measure_text",
222
+ #Image Drawing
223
+ "draw_image",
224
+ #Other
225
+ "save", "restore"
226
+ ]
227
+ end
228
+
229
+ def ruby_to_js_command(method_sym)
230
+ js_command = method_sym.to_s
231
+ js_commands = js_command.split('_')
232
+ if js_commands.size > 1
233
+ js_commands[1..js_commands.size].each do |command|
234
+ command.capitalize!
235
+ end
236
+ end
237
+ js_commands.join
238
+ end
239
+
240
+
241
+
242
+ end
243
+ end
@@ -0,0 +1,15 @@
1
+ module Fontaine
2
+ class Gradient
3
+ attr_accessor :id
4
+ attr_accessor :canvas
5
+ def initialize(id, canvas)
6
+ @id = id
7
+ @canvas = canvas #is this ok? test
8
+ end
9
+
10
+ def add_color_stop(stop, color)
11
+ @canvas.send_msg("addColorStop #{stop} #{color} #{@id}")
12
+ end
13
+
14
+ end
15
+ end
@@ -0,0 +1,36 @@
1
+ module Fontaine
2
+ class ImageData
3
+ attr_accessor :id
4
+ attr_accessor :canvas
5
+
6
+ def initialize(id, canvas, *args)
7
+ @id = id
8
+ @canvas = canvas #is this ok? test
9
+ if(args.size == 2)
10
+
11
+ @canvas.send_msg("createImageData #{args[0]} #{args[1]} #{id}")
12
+
13
+ elsif(args.size == 1)
14
+ @canvas.send_msg("createImageData #{args[0]} #{id}")
15
+ end
16
+
17
+ end
18
+
19
+ def data(pixel, value="")
20
+ @canvas.send_msg("imageDataData #{id} #{pixel} #{value}")
21
+ return @canvas.last_response
22
+ end
23
+
24
+ def width
25
+ @canvas.send_msg("imageDataWidth #{id}")
26
+ return @canvas.last_response
27
+ end
28
+
29
+ def width
30
+ @canvas.send_msg("imageDataheight #{id}")
31
+ return @canvas.last_response
32
+ end
33
+
34
+
35
+ end
36
+ end
@@ -0,0 +1,9 @@
1
+ module Fontaine
2
+ class Pattern
3
+ attr_accessor :id
4
+ def initialize(id)
5
+ @id = id
6
+ end
7
+
8
+ end
9
+ end
@@ -0,0 +1,3 @@
1
+ module Fontaine
2
+ VERSION = "0.1.0"
3
+ end
metadata ADDED
@@ -0,0 +1,100 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: fontaine
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - J. Paul Wetstein
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2013-08-09 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: sinatra-websocket
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ~>
18
+ - !ruby/object:Gem::Version
19
+ version: 0.2.0
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ~>
25
+ - !ruby/object:Gem::Version
26
+ version: 0.2.0
27
+ - !ruby/object:Gem::Dependency
28
+ name: bundler
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ~>
32
+ - !ruby/object:Gem::Version
33
+ version: '1.3'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ~>
39
+ - !ruby/object:Gem::Version
40
+ version: '1.3'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rake
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - '>='
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - '>='
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ description: A bridge between Ruby and HTML5 canvas using Websocket
56
+ email:
57
+ - Jeep.Wetstein@gmail.com
58
+ executables: []
59
+ extensions: []
60
+ extra_rdoc_files: []
61
+ files:
62
+ - .gitignore
63
+ - Gemfile
64
+ - LICENSE.txt
65
+ - README.md
66
+ - Rakefile
67
+ - fontaine.gemspec
68
+ - lib/fontaine.rb
69
+ - lib/fontaine/assets/fontaine.js
70
+ - lib/fontaine/bootstrap.rb
71
+ - lib/fontaine/canvas.rb
72
+ - lib/fontaine/gradient.rb
73
+ - lib/fontaine/image_data.rb
74
+ - lib/fontaine/pattern.rb
75
+ - lib/fontaine/version.rb
76
+ homepage: ''
77
+ licenses:
78
+ - MIT
79
+ metadata: {}
80
+ post_install_message:
81
+ rdoc_options: []
82
+ require_paths:
83
+ - lib
84
+ required_ruby_version: !ruby/object:Gem::Requirement
85
+ requirements:
86
+ - - '>='
87
+ - !ruby/object:Gem::Version
88
+ version: '0'
89
+ required_rubygems_version: !ruby/object:Gem::Requirement
90
+ requirements:
91
+ - - '>='
92
+ - !ruby/object:Gem::Version
93
+ version: '0'
94
+ requirements: []
95
+ rubyforge_project:
96
+ rubygems_version: 2.0.3
97
+ signing_key:
98
+ specification_version: 4
99
+ summary: A bridge between Ruby and HTML5 canvas using Websocket
100
+ test_files: []