fontaine 0.1.0

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.
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: []