easy_as_pie 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,5 @@
1
+ lib/**/*.rb
2
+ bin/*
3
+ -
4
+ features/**/*.feature
5
+ LICENSE.txt
data/.rspec ADDED
@@ -0,0 +1 @@
1
+ --color
data/Gemfile ADDED
@@ -0,0 +1,9 @@
1
+ source "http://rubygems.org"
2
+
3
+ group :development do
4
+ gem "rspec", ">= 2.8.0"
5
+ gem "rdoc", ">= 3.12"
6
+ gem "bundler", ">= 1.0.0"
7
+ gem "jeweler", ">= 1.8.4"
8
+ gem "simplecov",">= 0.5"
9
+ end
@@ -0,0 +1,37 @@
1
+ GEM
2
+ remote: http://rubygems.org/
3
+ specs:
4
+ diff-lcs (1.1.3)
5
+ git (1.2.5)
6
+ jeweler (1.8.4)
7
+ bundler (~> 1.0)
8
+ git (>= 1.2.5)
9
+ rake
10
+ rdoc
11
+ json (1.7.5)
12
+ multi_json (1.3.6)
13
+ rake (0.9.2.2)
14
+ rdoc (3.12)
15
+ json (~> 1.4)
16
+ rspec (2.11.0)
17
+ rspec-core (~> 2.11.0)
18
+ rspec-expectations (~> 2.11.0)
19
+ rspec-mocks (~> 2.11.0)
20
+ rspec-core (2.11.1)
21
+ rspec-expectations (2.11.2)
22
+ diff-lcs (~> 1.1.3)
23
+ rspec-mocks (2.11.2)
24
+ simplecov (0.6.4)
25
+ multi_json (~> 1.0)
26
+ simplecov-html (~> 0.5.3)
27
+ simplecov-html (0.5.3)
28
+
29
+ PLATFORMS
30
+ ruby
31
+
32
+ DEPENDENCIES
33
+ bundler (>= 1.0.0)
34
+ jeweler (>= 1.8.4)
35
+ rdoc (>= 3.12)
36
+ rspec (>= 2.8.0)
37
+ simplecov (>= 0.5)
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2012 Kristian Mandrup
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.
@@ -0,0 +1,164 @@
1
+ Easy as pie
2
+ ==============
3
+
4
+ A little Rails wrapper for [easy-pie-chart](https://github.com/rendro/easy-pie-chart) which adds the assets to the asset pipeline and adds some useful Rails helpers.
5
+
6
+ Usage
7
+ -----------
8
+
9
+ In Gemfile:
10
+
11
+ `gem 'easy_as_pie'`
12
+
13
+ Configuration
14
+ -----------
15
+
16
+ In `application.css` manifest file:
17
+
18
+ ```css
19
+ /*
20
+ * require jquery.easy-pie-chart
21
+ */
22
+ ```
23
+
24
+ Using Compass, f.ex in `application.css.scss.erb`
25
+
26
+ ```
27
+ @import 'jquery.easy-pie-chart';
28
+ ```
29
+
30
+ In `application.js` manifest file:
31
+
32
+ ```javascript
33
+ //= require jquery.easy-pie-chart
34
+ ```
35
+
36
+ Optionally include `excanvas` as well, which can be used if/when canvas is not available!
37
+
38
+ Easy pie chart
39
+ -----------
40
+
41
+ Easy pie chart is a jQuery plugin that uses the canvas element to render simple pie charts for single values.
42
+ These chars are highly customizable and very easy to implement.
43
+
44
+ ![](https://github.com/rendro/easy-pie-chart/raw/master/img/easy-pie-chart.png)
45
+
46
+ Get started
47
+ -----------
48
+
49
+ Follow Configuration instructions above and also include jQuery 1.7+.
50
+
51
+ The second step is to add a element to your site to represent chart and add the `data-percent` attribute with the percent number the pie chart should have:
52
+
53
+ <div class="chart" data-percent="73">73%</div>
54
+
55
+ The engine adds a view helper `easy_as_pie(percent, label = nil)` to all Rails views ;)
56
+
57
+ ```haml
58
+ = easy_as_pie 73
59
+ ```
60
+
61
+ Or with a custom label:
62
+
63
+ ```erb
64
+ <%= easy_as_pie (6/24.0 * 100), '6 done' %>
65
+ ```
66
+
67
+ 6 out of 24 done is 25% :)
68
+
69
+ Finally you have to initialize the plugin with your desired configuration:
70
+
71
+ ```javascript
72
+ $(document).ready(function() {
73
+ $('.chart').easyPieChart({
74
+ //your configuration goes here
75
+ });
76
+ });
77
+ ```
78
+
79
+ Configuration parameter
80
+ -----------------------
81
+
82
+ You can pass a set of these options to the initialize function to set a custom behaviour and look for the plugin.
83
+
84
+ <table>
85
+ <tr>
86
+ <th>Property (Type)</th>
87
+ <th>Default</th>
88
+ <th>Description</th>
89
+ </tr>
90
+ <tr>
91
+ <td><strong>barColor</strong></td>
92
+ <td>#ef1e25</td>
93
+ <td>The color of the curcular bar. You can pass either a css valid color string like rgb, rgba hex or string colors. But you can also pass a function that accepts the current percentage as a value to return a dynamically generated color.</td>
94
+ </tr>
95
+ <tr>
96
+ <td><strong>trackColor</strong></td>
97
+ <td>#f2f2f2</td>
98
+ <td>The color of the track for the bar, false to disable rendering.</td>
99
+ </tr>
100
+ <tr>
101
+ <td><strong>scaleColor</strong></td>
102
+ <td>#dfe0e0</td>
103
+ <td>The color of the scale lines, false to disable rendering.</td>
104
+ </tr>
105
+ <tr>
106
+ <td><strong>lineCap</strong></td>
107
+ <td>round</td>
108
+ <td>Defines how the ending of the bar line looks like. Possible values are: <code>butt</code>, <code>round</code> and <code>square</code>.</td>
109
+ </tr>
110
+ <tr>
111
+ <td><strong>lineWidth</strong></td>
112
+ <td>3</td>
113
+ <td>Width of the bar line in px.</td>
114
+ </tr>
115
+ <tr>
116
+ <td><strong>size</strong></td>
117
+ <td>110</td>
118
+ <td>Size of the pie chart in px. It will always be a square.</td>
119
+ </tr>
120
+ <tr>
121
+ <td><strong>animate</strong></td>
122
+ <td>false</td>
123
+ <td>Time in milliseconds for a eased animation of the bar growing, or false to deactivate.</td>
124
+ </tr>
125
+ <tr>
126
+ <td><strong>onStart</strong></td>
127
+ <td>$.noop</td>
128
+ <td>Callback function that is called at the start of any animation (only if animate is not false).</td>
129
+ </tr>
130
+ <tr>
131
+ <td><strong>onStop</strong></td>
132
+ <td>$.noop</td>
133
+ <td>Callback function that is called at the end of any animation (only if animate is not false).</td>
134
+ </tr>
135
+ </table>
136
+
137
+ Public plugin methods
138
+ -----------
139
+
140
+ If you want to update the current percentage of the a pie chart, you can call the `update method. The instance of the plugin is saved in the jQuery-data.
141
+
142
+ <script type="text/javascript">
143
+ $(function() {
144
+ //create instance
145
+ $('.chart').easyPieChart({
146
+ animate: 2000
147
+ });
148
+ //update instance after 5 sec
149
+ setTimeout(function() {
150
+ $('.chart').data('easyPieChart').update(40);
151
+ }, 5000);
152
+ });
153
+ </script>
154
+
155
+ ## Contributing to easy_as_pie
156
+
157
+ * Check out the latest master to make sure the feature hasn't been implemented or the bug hasn't been fixed yet.
158
+ * Check out the issue tracker to make sure someone already hasn't requested it and/or contributed it.
159
+ * Fork the project.
160
+ * Start a feature/bugfix branch.
161
+ * Commit and push until you are happy with your contribution.
162
+ * Make sure to add tests for it. This is important so I don't break it in a future version unintentionally.
163
+ * Please try not to mess with the Rakefile, version, or history. If you want to have your own version, or is otherwise necessary, that is fine, but please isolate to its own commit so I can cherry-pick around it.
164
+
@@ -0,0 +1,49 @@
1
+ # encoding: utf-8
2
+
3
+ require 'rubygems'
4
+ require 'bundler'
5
+ begin
6
+ Bundler.setup(:default, :development)
7
+ rescue Bundler::BundlerError => e
8
+ $stderr.puts e.message
9
+ $stderr.puts "Run `bundle install` to install missing gems"
10
+ exit e.status_code
11
+ end
12
+ require 'rake'
13
+
14
+ require 'jeweler'
15
+ Jeweler::Tasks.new do |gem|
16
+ # gem is a Gem::Specification... see http://docs.rubygems.org/read/chapter/20 for more options
17
+ gem.name = "easy_as_pie"
18
+ gem.homepage = "http://github.com/kristianmandrup/easy_as_pie"
19
+ gem.license = "MIT"
20
+ gem.summary = %Q{Rails asset wrapper for jQuery easy-pie-chart}
21
+ gem.description = %Q{Makes it easy to add Pie charts to Rails 3+ apps}
22
+ gem.email = "kmandrup@gmail.com"
23
+ gem.authors = ["Kristian Mandrup"]
24
+ # dependencies defined in Gemfile
25
+ end
26
+ Jeweler::RubygemsDotOrgTasks.new
27
+
28
+ require 'rspec/core'
29
+ require 'rspec/core/rake_task'
30
+ RSpec::Core::RakeTask.new(:spec) do |spec|
31
+ spec.pattern = FileList['spec/**/*_spec.rb']
32
+ end
33
+
34
+ RSpec::Core::RakeTask.new(:rcov) do |spec|
35
+ spec.pattern = 'spec/**/*_spec.rb'
36
+ spec.rcov = true
37
+ end
38
+
39
+ task :default => :spec
40
+
41
+ require 'rdoc/task'
42
+ Rake::RDocTask.new do |rdoc|
43
+ version = File.exist?('VERSION') ? File.read('VERSION') : ""
44
+
45
+ rdoc.rdoc_dir = 'rdoc'
46
+ rdoc.title = "easy_as_pie #{version}"
47
+ rdoc.rdoc_files.include('README*')
48
+ rdoc.rdoc_files.include('lib/**/*.rb')
49
+ end
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.1.0
@@ -0,0 +1,69 @@
1
+ # Generated by jeweler
2
+ # DO NOT EDIT THIS FILE DIRECTLY
3
+ # Instead, edit Jeweler::Tasks in Rakefile, and run 'rake gemspec'
4
+ # -*- encoding: utf-8 -*-
5
+
6
+ Gem::Specification.new do |s|
7
+ s.name = "easy_as_pie"
8
+ s.version = "0.1.0"
9
+
10
+ s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
+ s.authors = ["Kristian Mandrup"]
12
+ s.date = "2012-08-22"
13
+ s.description = "Makes it easy to add Pie charts to Rails 3+ apps"
14
+ s.email = "kmandrup@gmail.com"
15
+ s.extra_rdoc_files = [
16
+ "LICENSE.txt",
17
+ "README.md"
18
+ ]
19
+ s.files = [
20
+ ".document",
21
+ ".rspec",
22
+ "Gemfile",
23
+ "Gemfile.lock",
24
+ "LICENSE.txt",
25
+ "README.md",
26
+ "Rakefile",
27
+ "VERSION",
28
+ "easy_as_pie.gemspec",
29
+ "lib/easy_as_pie.rb",
30
+ "lib/easy_as_pie/engine.rb",
31
+ "lib/easy_as_pie/view_helper.rb",
32
+ "spec/easy_as_pie_spec.rb",
33
+ "spec/spec_helper.rb",
34
+ "vendor/assets/javascripts/excanvas.js",
35
+ "vendor/assets/javascripts/jquery.easy-pie-chart.js",
36
+ "vendor/assets/javascripts/jquery.easy-pie-chart.js.coffee",
37
+ "vendor/assets/stylesheets/jquery.easy-pie-chart.css"
38
+ ]
39
+ s.homepage = "http://github.com/kristianmandrup/easy_as_pie"
40
+ s.licenses = ["MIT"]
41
+ s.require_paths = ["lib"]
42
+ s.rubygems_version = "1.8.24"
43
+ s.summary = "Rails asset wrapper for jQuery easy-pie-chart"
44
+
45
+ if s.respond_to? :specification_version then
46
+ s.specification_version = 3
47
+
48
+ if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
49
+ s.add_development_dependency(%q<rspec>, [">= 2.8.0"])
50
+ s.add_development_dependency(%q<rdoc>, [">= 3.12"])
51
+ s.add_development_dependency(%q<bundler>, [">= 1.0.0"])
52
+ s.add_development_dependency(%q<jeweler>, [">= 1.8.4"])
53
+ s.add_development_dependency(%q<simplecov>, [">= 0.5"])
54
+ else
55
+ s.add_dependency(%q<rspec>, [">= 2.8.0"])
56
+ s.add_dependency(%q<rdoc>, [">= 3.12"])
57
+ s.add_dependency(%q<bundler>, [">= 1.0.0"])
58
+ s.add_dependency(%q<jeweler>, [">= 1.8.4"])
59
+ s.add_dependency(%q<simplecov>, [">= 0.5"])
60
+ end
61
+ else
62
+ s.add_dependency(%q<rspec>, [">= 2.8.0"])
63
+ s.add_dependency(%q<rdoc>, [">= 3.12"])
64
+ s.add_dependency(%q<bundler>, [">= 1.0.0"])
65
+ s.add_dependency(%q<jeweler>, [">= 1.8.4"])
66
+ s.add_dependency(%q<simplecov>, [">= 0.5"])
67
+ end
68
+ end
69
+
@@ -0,0 +1,2 @@
1
+ require 'easy_as_pie/view_helper'
2
+ require 'easy_as_pie/engine' if defined?(::Rails::Engine)
@@ -0,0 +1,9 @@
1
+ module EasyAsPie
2
+ module Rails
3
+ class Engine < ::Rails::Engine
4
+ initializer 'easy as pie config' do
5
+ ActionView::Base.send :include, EasyAsPie::ViewHelper
6
+ end
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,12 @@
1
+ module EasyAsPie
2
+ module ViewHelper
3
+
4
+ # <div class="chart" data-percent="73">73%</div>
5
+ def easy_as_pie percent, label = nil
6
+ raise ArgumentError, "Must take a percent argument" unless percent
7
+ pie_options = {:"data-percent" => percent}
8
+ label ||= "#{percent}%"
9
+ content_tag :div, label, pie_options
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,7 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
2
+
3
+ describe "EasyAsPie" do
4
+ it "fails" do
5
+ fail "hey buddy, you should probably rename this file and start specing for real"
6
+ end
7
+ end
@@ -0,0 +1,12 @@
1
+ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
2
+ $LOAD_PATH.unshift(File.dirname(__FILE__))
3
+ require 'rspec'
4
+ require 'easy_as_pie'
5
+
6
+ # Requires supporting files with custom matchers and macros, etc,
7
+ # in ./support/ and its subdirectories.
8
+ Dir["#{File.dirname(__FILE__)}/support/**/*.rb"].each {|f| require f}
9
+
10
+ RSpec.configure do |config|
11
+
12
+ end
@@ -0,0 +1,924 @@
1
+ // Copyright 2006 Google Inc.
2
+ //
3
+ // Licensed under the Apache License, Version 2.0 (the "License");
4
+ // you may not use this file except in compliance with the License.
5
+ // You may obtain a copy of the License at
6
+ //
7
+ // http://www.apache.org/licenses/LICENSE-2.0
8
+ //
9
+ // Unless required by applicable law or agreed to in writing, software
10
+ // distributed under the License is distributed on an "AS IS" BASIS,
11
+ // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ // See the License for the specific language governing permissions and
13
+ // limitations under the License.
14
+
15
+
16
+ // Known Issues:
17
+ //
18
+ // * Patterns are not implemented.
19
+ // * Radial gradient are not implemented. The VML version of these look very
20
+ // different from the canvas one.
21
+ // * Clipping paths are not implemented.
22
+ // * Coordsize. The width and height attribute have higher priority than the
23
+ // width and height style values which isn't correct.
24
+ // * Painting mode isn't implemented.
25
+ // * Canvas width/height should is using content-box by default. IE in
26
+ // Quirks mode will draw the canvas using border-box. Either change your
27
+ // doctype to HTML5
28
+ // (http://www.whatwg.org/specs/web-apps/current-work/#the-doctype)
29
+ // or use Box Sizing Behavior from WebFX
30
+ // (http://webfx.eae.net/dhtml/boxsizing/boxsizing.html)
31
+ // * Non uniform scaling does not correctly scale strokes.
32
+ // * Optimize. There is always room for speed improvements.
33
+
34
+ // Only add this code if we do not already have a canvas implementation
35
+ if (!document.createElement('canvas').getContext) {
36
+
37
+ (function() {
38
+
39
+ // alias some functions to make (compiled) code shorter
40
+ var m = Math;
41
+ var mr = m.round;
42
+ var ms = m.sin;
43
+ var mc = m.cos;
44
+ var abs = m.abs;
45
+ var sqrt = m.sqrt;
46
+
47
+ // this is used for sub pixel precision
48
+ var Z = 10;
49
+ var Z2 = Z / 2;
50
+
51
+ /**
52
+ * This funtion is assigned to the <canvas> elements as element.getContext().
53
+ * @this {HTMLElement}
54
+ * @return {CanvasRenderingContext2D_}
55
+ */
56
+ function getContext() {
57
+ return this.context_ ||
58
+ (this.context_ = new CanvasRenderingContext2D_(this));
59
+ }
60
+
61
+ var slice = Array.prototype.slice;
62
+
63
+ /**
64
+ * Binds a function to an object. The returned function will always use the
65
+ * passed in {@code obj} as {@code this}.
66
+ *
67
+ * Example:
68
+ *
69
+ * g = bind(f, obj, a, b)
70
+ * g(c, d) // will do f.call(obj, a, b, c, d)
71
+ *
72
+ * @param {Function} f The function to bind the object to
73
+ * @param {Object} obj The object that should act as this when the function
74
+ * is called
75
+ * @param {*} var_args Rest arguments that will be used as the initial
76
+ * arguments when the function is called
77
+ * @return {Function} A new function that has bound this
78
+ */
79
+ function bind(f, obj, var_args) {
80
+ var a = slice.call(arguments, 2);
81
+ return function() {
82
+ return f.apply(obj, a.concat(slice.call(arguments)));
83
+ };
84
+ }
85
+
86
+ var G_vmlCanvasManager_ = {
87
+ init: function(opt_doc) {
88
+ if (/MSIE/.test(navigator.userAgent) && !window.opera) {
89
+ var doc = opt_doc || document;
90
+ // Create a dummy element so that IE will allow canvas elements to be
91
+ // recognized.
92
+ doc.createElement('canvas');
93
+ doc.attachEvent('onreadystatechange', bind(this.init_, this, doc));
94
+ }
95
+ },
96
+
97
+ init_: function(doc) {
98
+ // create xmlns
99
+ if (!doc.namespaces['g_vml_']) {
100
+ doc.namespaces.add('g_vml_', 'urn:schemas-microsoft-com:vml',
101
+ '#default#VML');
102
+
103
+ }
104
+ if (!doc.namespaces['g_o_']) {
105
+ doc.namespaces.add('g_o_', 'urn:schemas-microsoft-com:office:office',
106
+ '#default#VML');
107
+ }
108
+
109
+ // Setup default CSS. Only add one style sheet per document
110
+ if (!doc.styleSheets['ex_canvas_']) {
111
+ var ss = doc.createStyleSheet();
112
+ ss.owningElement.id = 'ex_canvas_';
113
+ ss.cssText = 'canvas{display:inline-block;overflow:hidden;' +
114
+ // default size is 300x150 in Gecko and Opera
115
+ 'text-align:left;width:300px;height:150px}' +
116
+ 'g_vml_\\:*{behavior:url(#default#VML)}' +
117
+ 'g_o_\\:*{behavior:url(#default#VML)}';
118
+
119
+ }
120
+
121
+ // find all canvas elements
122
+ var els = doc.getElementsByTagName('canvas');
123
+ for (var i = 0; i < els.length; i++) {
124
+ this.initElement(els[i]);
125
+ }
126
+ },
127
+
128
+ /**
129
+ * Public initializes a canvas element so that it can be used as canvas
130
+ * element from now on. This is called automatically before the page is
131
+ * loaded but if you are creating elements using createElement you need to
132
+ * make sure this is called on the element.
133
+ * @param {HTMLElement} el The canvas element to initialize.
134
+ * @return {HTMLElement} the element that was created.
135
+ */
136
+ initElement: function(el) {
137
+ if (!el.getContext) {
138
+
139
+ el.getContext = getContext;
140
+
141
+ // Remove fallback content. There is no way to hide text nodes so we
142
+ // just remove all childNodes. We could hide all elements and remove
143
+ // text nodes but who really cares about the fallback content.
144
+ el.innerHTML = '';
145
+
146
+ // do not use inline function because that will leak memory
147
+ el.attachEvent('onpropertychange', onPropertyChange);
148
+ el.attachEvent('onresize', onResize);
149
+
150
+ var attrs = el.attributes;
151
+ if (attrs.width && attrs.width.specified) {
152
+ // TODO: use runtimeStyle and coordsize
153
+ // el.getContext().setWidth_(attrs.width.nodeValue);
154
+ el.style.width = attrs.width.nodeValue + 'px';
155
+ } else {
156
+ el.width = el.clientWidth;
157
+ }
158
+ if (attrs.height && attrs.height.specified) {
159
+ // TODO: use runtimeStyle and coordsize
160
+ // el.getContext().setHeight_(attrs.height.nodeValue);
161
+ el.style.height = attrs.height.nodeValue + 'px';
162
+ } else {
163
+ el.height = el.clientHeight;
164
+ }
165
+ //el.getContext().setCoordsize_()
166
+ }
167
+ return el;
168
+ }
169
+ };
170
+
171
+ function onPropertyChange(e) {
172
+ var el = e.srcElement;
173
+
174
+ switch (e.propertyName) {
175
+ case 'width':
176
+ el.style.width = el.attributes.width.nodeValue + 'px';
177
+ el.getContext().clearRect();
178
+ break;
179
+ case 'height':
180
+ el.style.height = el.attributes.height.nodeValue + 'px';
181
+ el.getContext().clearRect();
182
+ break;
183
+ }
184
+ }
185
+
186
+ function onResize(e) {
187
+ var el = e.srcElement;
188
+ if (el.firstChild) {
189
+ el.firstChild.style.width = el.clientWidth + 'px';
190
+ el.firstChild.style.height = el.clientHeight + 'px';
191
+ }
192
+ }
193
+
194
+ G_vmlCanvasManager_.init();
195
+
196
+ // precompute "00" to "FF"
197
+ var dec2hex = [];
198
+ for (var i = 0; i < 16; i++) {
199
+ for (var j = 0; j < 16; j++) {
200
+ dec2hex[i * 16 + j] = i.toString(16) + j.toString(16);
201
+ }
202
+ }
203
+
204
+ function createMatrixIdentity() {
205
+ return [
206
+ [1, 0, 0],
207
+ [0, 1, 0],
208
+ [0, 0, 1]
209
+ ];
210
+ }
211
+
212
+ function matrixMultiply(m1, m2) {
213
+ var result = createMatrixIdentity();
214
+
215
+ for (var x = 0; x < 3; x++) {
216
+ for (var y = 0; y < 3; y++) {
217
+ var sum = 0;
218
+
219
+ for (var z = 0; z < 3; z++) {
220
+ sum += m1[x][z] * m2[z][y];
221
+ }
222
+
223
+ result[x][y] = sum;
224
+ }
225
+ }
226
+ return result;
227
+ }
228
+
229
+ function copyState(o1, o2) {
230
+ o2.fillStyle = o1.fillStyle;
231
+ o2.lineCap = o1.lineCap;
232
+ o2.lineJoin = o1.lineJoin;
233
+ o2.lineWidth = o1.lineWidth;
234
+ o2.miterLimit = o1.miterLimit;
235
+ o2.shadowBlur = o1.shadowBlur;
236
+ o2.shadowColor = o1.shadowColor;
237
+ o2.shadowOffsetX = o1.shadowOffsetX;
238
+ o2.shadowOffsetY = o1.shadowOffsetY;
239
+ o2.strokeStyle = o1.strokeStyle;
240
+ o2.globalAlpha = o1.globalAlpha;
241
+ o2.arcScaleX_ = o1.arcScaleX_;
242
+ o2.arcScaleY_ = o1.arcScaleY_;
243
+ o2.lineScale_ = o1.lineScale_;
244
+ }
245
+
246
+ function processStyle(styleString) {
247
+ var str, alpha = 1;
248
+
249
+ styleString = String(styleString);
250
+ if (styleString.substring(0, 3) == 'rgb') {
251
+ var start = styleString.indexOf('(', 3);
252
+ var end = styleString.indexOf(')', start + 1);
253
+ var guts = styleString.substring(start + 1, end).split(',');
254
+
255
+ str = '#';
256
+ for (var i = 0; i < 3; i++) {
257
+ str += dec2hex[Number(guts[i])];
258
+ }
259
+
260
+ if (guts.length == 4 && styleString.substr(3, 1) == 'a') {
261
+ alpha = guts[3];
262
+ }
263
+ } else {
264
+ str = styleString;
265
+ }
266
+
267
+ return {color: str, alpha: alpha};
268
+ }
269
+
270
+ function processLineCap(lineCap) {
271
+ switch (lineCap) {
272
+ case 'butt':
273
+ return 'flat';
274
+ case 'round':
275
+ return 'round';
276
+ case 'square':
277
+ default:
278
+ return 'square';
279
+ }
280
+ }
281
+
282
+ /**
283
+ * This class implements CanvasRenderingContext2D interface as described by
284
+ * the WHATWG.
285
+ * @param {HTMLElement} surfaceElement The element that the 2D context should
286
+ * be associated with
287
+ */
288
+ function CanvasRenderingContext2D_(surfaceElement) {
289
+ this.m_ = createMatrixIdentity();
290
+
291
+ this.mStack_ = [];
292
+ this.aStack_ = [];
293
+ this.currentPath_ = [];
294
+
295
+ // Canvas context properties
296
+ this.strokeStyle = '#000';
297
+ this.fillStyle = '#000';
298
+
299
+ this.lineWidth = 1;
300
+ this.lineJoin = 'miter';
301
+ this.lineCap = 'butt';
302
+ this.miterLimit = Z * 1;
303
+ this.globalAlpha = 1;
304
+ this.canvas = surfaceElement;
305
+
306
+ var el = surfaceElement.ownerDocument.createElement('div');
307
+ el.style.width = surfaceElement.clientWidth + 'px';
308
+ el.style.height = surfaceElement.clientHeight + 'px';
309
+ el.style.overflow = 'hidden';
310
+ el.style.position = 'absolute';
311
+ surfaceElement.appendChild(el);
312
+
313
+ this.element_ = el;
314
+ this.arcScaleX_ = 1;
315
+ this.arcScaleY_ = 1;
316
+ this.lineScale_ = 1;
317
+ }
318
+
319
+ var contextPrototype = CanvasRenderingContext2D_.prototype;
320
+ contextPrototype.clearRect = function() {
321
+ this.element_.innerHTML = '';
322
+ };
323
+
324
+ contextPrototype.beginPath = function() {
325
+ // TODO: Branch current matrix so that save/restore has no effect
326
+ // as per safari docs.
327
+ this.currentPath_ = [];
328
+ };
329
+
330
+ contextPrototype.moveTo = function(aX, aY) {
331
+ var p = this.getCoords_(aX, aY);
332
+ this.currentPath_.push({type: 'moveTo', x: p.x, y: p.y});
333
+ this.currentX_ = p.x;
334
+ this.currentY_ = p.y;
335
+ };
336
+
337
+ contextPrototype.lineTo = function(aX, aY) {
338
+ var p = this.getCoords_(aX, aY);
339
+ this.currentPath_.push({type: 'lineTo', x: p.x, y: p.y});
340
+
341
+ this.currentX_ = p.x;
342
+ this.currentY_ = p.y;
343
+ };
344
+
345
+ contextPrototype.bezierCurveTo = function(aCP1x, aCP1y,
346
+ aCP2x, aCP2y,
347
+ aX, aY) {
348
+ var p = this.getCoords_(aX, aY);
349
+ var cp1 = this.getCoords_(aCP1x, aCP1y);
350
+ var cp2 = this.getCoords_(aCP2x, aCP2y);
351
+ bezierCurveTo(this, cp1, cp2, p);
352
+ };
353
+
354
+ // Helper function that takes the already fixed cordinates.
355
+ function bezierCurveTo(self, cp1, cp2, p) {
356
+ self.currentPath_.push({
357
+ type: 'bezierCurveTo',
358
+ cp1x: cp1.x,
359
+ cp1y: cp1.y,
360
+ cp2x: cp2.x,
361
+ cp2y: cp2.y,
362
+ x: p.x,
363
+ y: p.y
364
+ });
365
+ self.currentX_ = p.x;
366
+ self.currentY_ = p.y;
367
+ }
368
+
369
+ contextPrototype.quadraticCurveTo = function(aCPx, aCPy, aX, aY) {
370
+ // the following is lifted almost directly from
371
+ // http://developer.mozilla.org/en/docs/Canvas_tutorial:Drawing_shapes
372
+
373
+ var cp = this.getCoords_(aCPx, aCPy);
374
+ var p = this.getCoords_(aX, aY);
375
+
376
+ var cp1 = {
377
+ x: this.currentX_ + 2.0 / 3.0 * (cp.x - this.currentX_),
378
+ y: this.currentY_ + 2.0 / 3.0 * (cp.y - this.currentY_)
379
+ };
380
+ var cp2 = {
381
+ x: cp1.x + (p.x - this.currentX_) / 3.0,
382
+ y: cp1.y + (p.y - this.currentY_) / 3.0
383
+ };
384
+
385
+ bezierCurveTo(this, cp1, cp2, p);
386
+ };
387
+
388
+ contextPrototype.arc = function(aX, aY, aRadius,
389
+ aStartAngle, aEndAngle, aClockwise) {
390
+ aRadius *= Z;
391
+ var arcType = aClockwise ? 'at' : 'wa';
392
+
393
+ var xStart = aX + mc(aStartAngle) * aRadius - Z2;
394
+ var yStart = aY + ms(aStartAngle) * aRadius - Z2;
395
+
396
+ var xEnd = aX + mc(aEndAngle) * aRadius - Z2;
397
+ var yEnd = aY + ms(aEndAngle) * aRadius - Z2;
398
+
399
+ // IE won't render arches drawn counter clockwise if xStart == xEnd.
400
+ if (xStart == xEnd && !aClockwise) {
401
+ xStart += 0.125; // Offset xStart by 1/80 of a pixel. Use something
402
+ // that can be represented in binary
403
+ }
404
+
405
+ var p = this.getCoords_(aX, aY);
406
+ var pStart = this.getCoords_(xStart, yStart);
407
+ var pEnd = this.getCoords_(xEnd, yEnd);
408
+
409
+ this.currentPath_.push({type: arcType,
410
+ x: p.x,
411
+ y: p.y,
412
+ radius: aRadius,
413
+ xStart: pStart.x,
414
+ yStart: pStart.y,
415
+ xEnd: pEnd.x,
416
+ yEnd: pEnd.y});
417
+
418
+ };
419
+
420
+ contextPrototype.rect = function(aX, aY, aWidth, aHeight) {
421
+ this.moveTo(aX, aY);
422
+ this.lineTo(aX + aWidth, aY);
423
+ this.lineTo(aX + aWidth, aY + aHeight);
424
+ this.lineTo(aX, aY + aHeight);
425
+ this.closePath();
426
+ };
427
+
428
+ contextPrototype.strokeRect = function(aX, aY, aWidth, aHeight) {
429
+ var oldPath = this.currentPath_;
430
+ this.beginPath();
431
+
432
+ this.moveTo(aX, aY);
433
+ this.lineTo(aX + aWidth, aY);
434
+ this.lineTo(aX + aWidth, aY + aHeight);
435
+ this.lineTo(aX, aY + aHeight);
436
+ this.closePath();
437
+ this.stroke();
438
+
439
+ this.currentPath_ = oldPath;
440
+ };
441
+
442
+ contextPrototype.fillRect = function(aX, aY, aWidth, aHeight) {
443
+ var oldPath = this.currentPath_;
444
+ this.beginPath();
445
+
446
+ this.moveTo(aX, aY);
447
+ this.lineTo(aX + aWidth, aY);
448
+ this.lineTo(aX + aWidth, aY + aHeight);
449
+ this.lineTo(aX, aY + aHeight);
450
+ this.closePath();
451
+ this.fill();
452
+
453
+ this.currentPath_ = oldPath;
454
+ };
455
+
456
+ contextPrototype.createLinearGradient = function(aX0, aY0, aX1, aY1) {
457
+ var gradient = new CanvasGradient_('gradient');
458
+ gradient.x0_ = aX0;
459
+ gradient.y0_ = aY0;
460
+ gradient.x1_ = aX1;
461
+ gradient.y1_ = aY1;
462
+ return gradient;
463
+ };
464
+
465
+ contextPrototype.createRadialGradient = function(aX0, aY0, aR0,
466
+ aX1, aY1, aR1) {
467
+ var gradient = new CanvasGradient_('gradientradial');
468
+ gradient.x0_ = aX0;
469
+ gradient.y0_ = aY0;
470
+ gradient.r0_ = aR0;
471
+ gradient.x1_ = aX1;
472
+ gradient.y1_ = aY1;
473
+ gradient.r1_ = aR1;
474
+ return gradient;
475
+ };
476
+
477
+ contextPrototype.drawImage = function(image, var_args) {
478
+ var dx, dy, dw, dh, sx, sy, sw, sh;
479
+
480
+ // to find the original width we overide the width and height
481
+ var oldRuntimeWidth = image.runtimeStyle.width;
482
+ var oldRuntimeHeight = image.runtimeStyle.height;
483
+ image.runtimeStyle.width = 'auto';
484
+ image.runtimeStyle.height = 'auto';
485
+
486
+ // get the original size
487
+ var w = image.width;
488
+ var h = image.height;
489
+
490
+ // and remove overides
491
+ image.runtimeStyle.width = oldRuntimeWidth;
492
+ image.runtimeStyle.height = oldRuntimeHeight;
493
+
494
+ if (arguments.length == 3) {
495
+ dx = arguments[1];
496
+ dy = arguments[2];
497
+ sx = sy = 0;
498
+ sw = dw = w;
499
+ sh = dh = h;
500
+ } else if (arguments.length == 5) {
501
+ dx = arguments[1];
502
+ dy = arguments[2];
503
+ dw = arguments[3];
504
+ dh = arguments[4];
505
+ sx = sy = 0;
506
+ sw = w;
507
+ sh = h;
508
+ } else if (arguments.length == 9) {
509
+ sx = arguments[1];
510
+ sy = arguments[2];
511
+ sw = arguments[3];
512
+ sh = arguments[4];
513
+ dx = arguments[5];
514
+ dy = arguments[6];
515
+ dw = arguments[7];
516
+ dh = arguments[8];
517
+ } else {
518
+ throw Error('Invalid number of arguments');
519
+ }
520
+
521
+ var d = this.getCoords_(dx, dy);
522
+
523
+ var w2 = sw / 2;
524
+ var h2 = sh / 2;
525
+
526
+ var vmlStr = [];
527
+
528
+ var W = 10;
529
+ var H = 10;
530
+
531
+ // For some reason that I've now forgotten, using divs didn't work
532
+ vmlStr.push(' <g_vml_:group',
533
+ ' coordsize="', Z * W, ',', Z * H, '"',
534
+ ' coordorigin="0,0"' ,
535
+ ' style="width:', W, 'px;height:', H, 'px;position:absolute;');
536
+
537
+ // If filters are necessary (rotation exists), create them
538
+ // filters are bog-slow, so only create them if abbsolutely necessary
539
+ // The following check doesn't account for skews (which don't exist
540
+ // in the canvas spec (yet) anyway.
541
+
542
+ if (this.m_[0][0] != 1 || this.m_[0][1]) {
543
+ var filter = [];
544
+
545
+ // Note the 12/21 reversal
546
+ filter.push('M11=', this.m_[0][0], ',',
547
+ 'M12=', this.m_[1][0], ',',
548
+ 'M21=', this.m_[0][1], ',',
549
+ 'M22=', this.m_[1][1], ',',
550
+ 'Dx=', mr(d.x / Z), ',',
551
+ 'Dy=', mr(d.y / Z), '');
552
+
553
+ // Bounding box calculation (need to minimize displayed area so that
554
+ // filters don't waste time on unused pixels.
555
+ var max = d;
556
+ var c2 = this.getCoords_(dx + dw, dy);
557
+ var c3 = this.getCoords_(dx, dy + dh);
558
+ var c4 = this.getCoords_(dx + dw, dy + dh);
559
+
560
+ max.x = m.max(max.x, c2.x, c3.x, c4.x);
561
+ max.y = m.max(max.y, c2.y, c3.y, c4.y);
562
+
563
+ vmlStr.push('padding:0 ', mr(max.x / Z), 'px ', mr(max.y / Z),
564
+ 'px 0;filter:progid:DXImageTransform.Microsoft.Matrix(',
565
+ filter.join(''), ", sizingmethod='clip');")
566
+ } else {
567
+ vmlStr.push('top:', mr(d.y / Z), 'px;left:', mr(d.x / Z), 'px;');
568
+ }
569
+
570
+ vmlStr.push(' ">' ,
571
+ '<g_vml_:image src="', image.src, '"',
572
+ ' style="width:', Z * dw, 'px;',
573
+ ' height:', Z * dh, 'px;"',
574
+ ' cropleft="', sx / w, '"',
575
+ ' croptop="', sy / h, '"',
576
+ ' cropright="', (w - sx - sw) / w, '"',
577
+ ' cropbottom="', (h - sy - sh) / h, '"',
578
+ ' />',
579
+ '</g_vml_:group>');
580
+
581
+ this.element_.insertAdjacentHTML('BeforeEnd',
582
+ vmlStr.join(''));
583
+ };
584
+
585
+ contextPrototype.stroke = function(aFill) {
586
+ var lineStr = [];
587
+ var lineOpen = false;
588
+ var a = processStyle(aFill ? this.fillStyle : this.strokeStyle);
589
+ var color = a.color;
590
+ var opacity = a.alpha * this.globalAlpha;
591
+
592
+ var W = 10;
593
+ var H = 10;
594
+
595
+ lineStr.push('<g_vml_:shape',
596
+ ' filled="', !!aFill, '"',
597
+ ' style="position:absolute;width:', W, 'px;height:', H, 'px;"',
598
+ ' coordorigin="0 0" coordsize="', Z * W, ' ', Z * H, '"',
599
+ ' stroked="', !aFill, '"',
600
+ ' path="');
601
+
602
+ var newSeq = false;
603
+ var min = {x: null, y: null};
604
+ var max = {x: null, y: null};
605
+
606
+ for (var i = 0; i < this.currentPath_.length; i++) {
607
+ var p = this.currentPath_[i];
608
+ var c;
609
+
610
+ switch (p.type) {
611
+ case 'moveTo':
612
+ c = p;
613
+ lineStr.push(' m ', mr(p.x), ',', mr(p.y));
614
+ break;
615
+ case 'lineTo':
616
+ lineStr.push(' l ', mr(p.x), ',', mr(p.y));
617
+ break;
618
+ case 'close':
619
+ lineStr.push(' x ');
620
+ p = null;
621
+ break;
622
+ case 'bezierCurveTo':
623
+ lineStr.push(' c ',
624
+ mr(p.cp1x), ',', mr(p.cp1y), ',',
625
+ mr(p.cp2x), ',', mr(p.cp2y), ',',
626
+ mr(p.x), ',', mr(p.y));
627
+ break;
628
+ case 'at':
629
+ case 'wa':
630
+ lineStr.push(' ', p.type, ' ',
631
+ mr(p.x - this.arcScaleX_ * p.radius), ',',
632
+ mr(p.y - this.arcScaleY_ * p.radius), ' ',
633
+ mr(p.x + this.arcScaleX_ * p.radius), ',',
634
+ mr(p.y + this.arcScaleY_ * p.radius), ' ',
635
+ mr(p.xStart), ',', mr(p.yStart), ' ',
636
+ mr(p.xEnd), ',', mr(p.yEnd));
637
+ break;
638
+ }
639
+
640
+
641
+ // TODO: Following is broken for curves due to
642
+ // move to proper paths.
643
+
644
+ // Figure out dimensions so we can do gradient fills
645
+ // properly
646
+ if (p) {
647
+ if (min.x == null || p.x < min.x) {
648
+ min.x = p.x;
649
+ }
650
+ if (max.x == null || p.x > max.x) {
651
+ max.x = p.x;
652
+ }
653
+ if (min.y == null || p.y < min.y) {
654
+ min.y = p.y;
655
+ }
656
+ if (max.y == null || p.y > max.y) {
657
+ max.y = p.y;
658
+ }
659
+ }
660
+ }
661
+ lineStr.push(' ">');
662
+
663
+ if (!aFill) {
664
+ var lineWidth = this.lineScale_ * this.lineWidth;
665
+
666
+ // VML cannot correctly render a line if the width is less than 1px.
667
+ // In that case, we dilute the color to make the line look thinner.
668
+ if (lineWidth < 1) {
669
+ opacity *= lineWidth;
670
+ }
671
+
672
+ lineStr.push(
673
+ '<g_vml_:stroke',
674
+ ' opacity="', opacity, '"',
675
+ ' joinstyle="', this.lineJoin, '"',
676
+ ' miterlimit="', this.miterLimit, '"',
677
+ ' endcap="', processLineCap(this.lineCap), '"',
678
+ ' weight="', lineWidth, 'px"',
679
+ ' color="', color, '" />'
680
+ );
681
+ } else if (typeof this.fillStyle == 'object') {
682
+ var fillStyle = this.fillStyle;
683
+ var angle = 0;
684
+ var focus = {x: 0, y: 0};
685
+
686
+ // additional offset
687
+ var shift = 0;
688
+ // scale factor for offset
689
+ var expansion = 1;
690
+
691
+ if (fillStyle.type_ == 'gradient') {
692
+ var x0 = fillStyle.x0_ / this.arcScaleX_;
693
+ var y0 = fillStyle.y0_ / this.arcScaleY_;
694
+ var x1 = fillStyle.x1_ / this.arcScaleX_;
695
+ var y1 = fillStyle.y1_ / this.arcScaleY_;
696
+ var p0 = this.getCoords_(x0, y0);
697
+ var p1 = this.getCoords_(x1, y1);
698
+ var dx = p1.x - p0.x;
699
+ var dy = p1.y - p0.y;
700
+ angle = Math.atan2(dx, dy) * 180 / Math.PI;
701
+
702
+ // The angle should be a non-negative number.
703
+ if (angle < 0) {
704
+ angle += 360;
705
+ }
706
+
707
+ // Very small angles produce an unexpected result because they are
708
+ // converted to a scientific notation string.
709
+ if (angle < 1e-6) {
710
+ angle = 0;
711
+ }
712
+ } else {
713
+ var p0 = this.getCoords_(fillStyle.x0_, fillStyle.y0_);
714
+ var width = max.x - min.x;
715
+ var height = max.y - min.y;
716
+ focus = {
717
+ x: (p0.x - min.x) / width,
718
+ y: (p0.y - min.y) / height
719
+ };
720
+
721
+ width /= this.arcScaleX_ * Z;
722
+ height /= this.arcScaleY_ * Z;
723
+ var dimension = m.max(width, height);
724
+ shift = 2 * fillStyle.r0_ / dimension;
725
+ expansion = 2 * fillStyle.r1_ / dimension - shift;
726
+ }
727
+
728
+ // We need to sort the color stops in ascending order by offset,
729
+ // otherwise IE won't interpret it correctly.
730
+ var stops = fillStyle.colors_;
731
+ stops.sort(function(cs1, cs2) {
732
+ return cs1.offset - cs2.offset;
733
+ });
734
+
735
+ var length = stops.length;
736
+ var color1 = stops[0].color;
737
+ var color2 = stops[length - 1].color;
738
+ var opacity1 = stops[0].alpha * this.globalAlpha;
739
+ var opacity2 = stops[length - 1].alpha * this.globalAlpha;
740
+
741
+ var colors = [];
742
+ for (var i = 0; i < length; i++) {
743
+ var stop = stops[i];
744
+ colors.push(stop.offset * expansion + shift + ' ' + stop.color);
745
+ }
746
+
747
+ // When colors attribute is used, the meanings of opacity and o:opacity2
748
+ // are reversed.
749
+ lineStr.push('<g_vml_:fill type="', fillStyle.type_, '"',
750
+ ' method="none" focus="100%"',
751
+ ' color="', color1, '"',
752
+ ' color2="', color2, '"',
753
+ ' colors="', colors.join(','), '"',
754
+ ' opacity="', opacity2, '"',
755
+ ' g_o_:opacity2="', opacity1, '"',
756
+ ' angle="', angle, '"',
757
+ ' focusposition="', focus.x, ',', focus.y, '" />');
758
+ } else {
759
+ lineStr.push('<g_vml_:fill color="', color, '" opacity="', opacity,
760
+ '" />');
761
+ }
762
+
763
+ lineStr.push('</g_vml_:shape>');
764
+
765
+ this.element_.insertAdjacentHTML('beforeEnd', lineStr.join(''));
766
+ };
767
+
768
+ contextPrototype.fill = function() {
769
+ this.stroke(true);
770
+ }
771
+
772
+ contextPrototype.closePath = function() {
773
+ this.currentPath_.push({type: 'close'});
774
+ };
775
+
776
+ /**
777
+ * @private
778
+ */
779
+ contextPrototype.getCoords_ = function(aX, aY) {
780
+ var m = this.m_;
781
+ return {
782
+ x: Z * (aX * m[0][0] + aY * m[1][0] + m[2][0]) - Z2,
783
+ y: Z * (aX * m[0][1] + aY * m[1][1] + m[2][1]) - Z2
784
+ }
785
+ };
786
+
787
+ contextPrototype.save = function() {
788
+ var o = {};
789
+ copyState(this, o);
790
+ this.aStack_.push(o);
791
+ this.mStack_.push(this.m_);
792
+ this.m_ = matrixMultiply(createMatrixIdentity(), this.m_);
793
+ };
794
+
795
+ contextPrototype.restore = function() {
796
+ copyState(this.aStack_.pop(), this);
797
+ this.m_ = this.mStack_.pop();
798
+ };
799
+
800
+ function matrixIsFinite(m) {
801
+ for (var j = 0; j < 3; j++) {
802
+ for (var k = 0; k < 2; k++) {
803
+ if (!isFinite(m[j][k]) || isNaN(m[j][k])) {
804
+ return false;
805
+ }
806
+ }
807
+ }
808
+ return true;
809
+ }
810
+
811
+ function setM(ctx, m, updateLineScale) {
812
+ if (!matrixIsFinite(m)) {
813
+ return;
814
+ }
815
+ ctx.m_ = m;
816
+
817
+ if (updateLineScale) {
818
+ // Get the line scale.
819
+ // Determinant of this.m_ means how much the area is enlarged by the
820
+ // transformation. So its square root can be used as a scale factor
821
+ // for width.
822
+ var det = m[0][0] * m[1][1] - m[0][1] * m[1][0];
823
+ ctx.lineScale_ = sqrt(abs(det));
824
+ }
825
+ }
826
+
827
+ contextPrototype.translate = function(aX, aY) {
828
+ var m1 = [
829
+ [1, 0, 0],
830
+ [0, 1, 0],
831
+ [aX, aY, 1]
832
+ ];
833
+
834
+ setM(this, matrixMultiply(m1, this.m_), false);
835
+ };
836
+
837
+ contextPrototype.rotate = function(aRot) {
838
+ var c = mc(aRot);
839
+ var s = ms(aRot);
840
+
841
+ var m1 = [
842
+ [c, s, 0],
843
+ [-s, c, 0],
844
+ [0, 0, 1]
845
+ ];
846
+
847
+ setM(this, matrixMultiply(m1, this.m_), false);
848
+ };
849
+
850
+ contextPrototype.scale = function(aX, aY) {
851
+ this.arcScaleX_ *= aX;
852
+ this.arcScaleY_ *= aY;
853
+ var m1 = [
854
+ [aX, 0, 0],
855
+ [0, aY, 0],
856
+ [0, 0, 1]
857
+ ];
858
+
859
+ setM(this, matrixMultiply(m1, this.m_), true);
860
+ };
861
+
862
+ contextPrototype.transform = function(m11, m12, m21, m22, dx, dy) {
863
+ var m1 = [
864
+ [m11, m12, 0],
865
+ [m21, m22, 0],
866
+ [dx, dy, 1]
867
+ ];
868
+
869
+ setM(this, matrixMultiply(m1, this.m_), true);
870
+ };
871
+
872
+ contextPrototype.setTransform = function(m11, m12, m21, m22, dx, dy) {
873
+ var m = [
874
+ [m11, m12, 0],
875
+ [m21, m22, 0],
876
+ [dx, dy, 1]
877
+ ];
878
+
879
+ setM(this, m, true);
880
+ };
881
+
882
+ /******** STUBS ********/
883
+ contextPrototype.clip = function() {
884
+ // TODO: Implement
885
+ };
886
+
887
+ contextPrototype.arcTo = function() {
888
+ // TODO: Implement
889
+ };
890
+
891
+ contextPrototype.createPattern = function() {
892
+ return new CanvasPattern_;
893
+ };
894
+
895
+ // Gradient / Pattern Stubs
896
+ function CanvasGradient_(aType) {
897
+ this.type_ = aType;
898
+ this.x0_ = 0;
899
+ this.y0_ = 0;
900
+ this.r0_ = 0;
901
+ this.x1_ = 0;
902
+ this.y1_ = 0;
903
+ this.r1_ = 0;
904
+ this.colors_ = [];
905
+ }
906
+
907
+ CanvasGradient_.prototype.addColorStop = function(aOffset, aColor) {
908
+ aColor = processStyle(aColor);
909
+ this.colors_.push({offset: aOffset,
910
+ color: aColor.color,
911
+ alpha: aColor.alpha});
912
+ };
913
+
914
+ function CanvasPattern_() {}
915
+
916
+ // set up externs
917
+ G_vmlCanvasManager = G_vmlCanvasManager_;
918
+ CanvasRenderingContext2D = CanvasRenderingContext2D_;
919
+ CanvasGradient = CanvasGradient_;
920
+ CanvasPattern = CanvasPattern_;
921
+
922
+ })();
923
+
924
+ } // if