', resp)
303 | # this removes a 3-second pause in the next test
304 | gc.collect()
305 |
306 | def test_dozer_media(self):
307 | app = self.make_test_app()
308 | resp = app.get('/_dozer/media/css/main.css')
309 | self.assertEqual(resp.status_int, 200)
310 | self.assertEqual(resp.content_type, 'text/css')
311 | self.assertIn('.typename {', resp)
312 |
313 | def test_dozer_not_found(self):
314 | app = self.make_test_app()
315 | resp = app.get('/_dozer/nosuchpage', status=404)
316 |
317 | def test_dozer_forbidden(self):
318 | app = self.make_test_app()
319 | resp = app.get('/_dozer/dowse', status=403)
320 |
321 | def test_dozer_sortby(self):
322 | app = self.make_test_app()
323 | app.app.history['mymodule.AnotherType'] = [10, 20, 30, 40, 50]
324 | resp = app.get('/_dozer/?sortby=-monotonicity')
325 | self.assertEqual(resp.status_int, 200)
326 |
327 | def test_dozer_floor(self):
328 | app = self.make_test_app()
329 | app.app.history['mymodule.AnotherType'] = [10, 20, 30, 40, 50]
330 | resp = app.get('/_dozer/?floor=4')
331 | self.assertEqual(resp.status_int, 200)
332 | self.assertIn('
', resp)
333 | self.assertIn('mymodule.AnotherType', resp)
334 | self.assertIn('mymodule.MyType', resp)
335 |
336 | resp = app.get('/_dozer/?floor=10')
337 | self.assertEqual(resp.status_int, 200)
338 | self.assertIn('
', resp)
339 | self.assertIn('mymodule.AnotherType', resp)
340 | self.assertNotIn('mymodule.MyType', resp)
341 |
342 | def test_dozer_filter(self):
343 | app = self.make_test_app()
344 | app.app.history['mymodule.AnotherType'] = [10, 20, 30, 40, 50]
345 | resp = app.get('/_dozer/?filter=type')
346 | self.assertEqual(resp.status_int, 200)
347 | self.assertIn('
', resp)
348 | self.assertIn('mymodule.AnotherType', resp)
349 | self.assertIn('mymodule.MyType', resp)
350 |
351 | resp = app.get('/_dozer/?filter=another')
352 | self.assertEqual(resp.status_int, 200)
353 | self.assertIn('
', resp)
354 | self.assertIn('mymodule.AnotherType', resp)
355 | self.assertNotIn('mymodule.MyType', resp)
356 |
357 | def test_dozer_filter_broken_re_500_with_traceback(self):
358 | app = self.make_test_app()
359 | resp = app.get('/_dozer/?filter=(', status=500)
360 | self.assertEqual(resp.status_int, 500)
361 | self.assertIn('500 Internal Server Error', resp)
362 | self.assertIn('Traceback (most recent call last)', resp)
363 | self.assertIn(
364 | # Python 3.12 and older raise re.error: ...
365 | # Python 3.13 raises re.PatternError: ...
366 | 'rror: missing ), unterminated subpattern at position 0', resp
367 | )
368 |
--------------------------------------------------------------------------------
/dozer/leak.py:
--------------------------------------------------------------------------------
1 | import collections
2 | import gc
3 | import pathlib
4 | import re
5 | import sys
6 | import threading
7 | import time
8 | import traceback
9 | import types
10 | import warnings
11 | from html import escape
12 | from io import BytesIO
13 | from types import FrameType, GeneratorType, ModuleType
14 |
15 | from webob import Request, Response, exc, static
16 |
17 | from dozer import reftree
18 | from dozer.util import monotonicity, sort_dict_by_val
19 |
20 |
21 | try:
22 | from PIL import Image, ImageDraw
23 | except ImportError:
24 | Image = ImageDraw = None
25 |
26 |
27 | localDir = pathlib.Path(__file__).parent.resolve()
28 |
29 |
30 | def get_repr(obj, limit=250):
31 | return escape(reftree.get_repr(obj, limit))
32 |
33 |
34 | method_types = [
35 | types.BuiltinFunctionType, # 'builtin_function_or_method'
36 | types.BuiltinMethodType, # 'builtin_function_or_method'
37 | types.MethodWrapperType, # 'method-wrapper'
38 | types.WrapperDescriptorType, # 'wrapper_descriptor'
39 | types.MethodType, # 'method' aka bound method
40 | types.FunctionType, # 'function' and also unbound method
41 | # should GeneratorType and AsyncGeneratorType also be here?
42 | ]
43 |
44 |
45 | sort_keys = {
46 | "monotonicity": monotonicity
47 | }
48 |
49 |
50 | def url(req, path):
51 | if path.startswith('/'):
52 | path = path[1:]
53 | base_path = req.base_path
54 | if base_path.endswith('/'):
55 | return base_path + path
56 | else:
57 | return base_path + '/' + path
58 |
59 |
60 | def template(req, name, **params):
61 | p = {
62 | 'maincss': url(req, "/media/css/main.css"),
63 | 'home': url(req, "/index"),
64 | }
65 | p.update(params)
66 | return localDir.joinpath('media', name).read_text() % p
67 |
68 |
69 | def get_sort_key(sortby):
70 | if len(sortby) < 1:
71 | return None, False
72 | if sortby[0] == '-':
73 | return sort_keys.get(sortby[1:], None), True
74 | else:
75 | return sort_keys.get(sortby, None), False
76 |
77 |
78 | class Dozer(object):
79 | """Sets up a page that displays object information to help
80 | troubleshoot memory leaks"""
81 | period = 5
82 | maxhistory = 300
83 |
84 | def __init__(self, app, global_conf=None, media_paths=None, path='/_dozer',
85 | **kwargs):
86 | self.app = app
87 | self.media_paths = media_paths or {}
88 | if path.endswith('/'):
89 | path = path[:-1]
90 | self.path = path
91 | self.history = {}
92 | self.samples = 0
93 | self._start_thread()
94 | self._maybe_warn_about_PIL()
95 |
96 | def _start_thread(self):
97 | self.runthread = threading.Thread(name='Dozer', target=self.start)
98 | self.runthread.daemon = True
99 | self.runthread.start()
100 |
101 | def _maybe_warn_about_PIL(self):
102 | if Image is None or ImageDraw is None:
103 | warnings.warn('PIL is not installed, cannot show charts in Dozer')
104 |
105 | def __call__(self, environ, start_response):
106 | assert not environ['wsgi.multiprocess'], (
107 | "Dozer middleware is not usable in a "
108 | "multi-process environment")
109 | req = Request(environ)
110 | req.base_path = req.application_url + self.path
111 | if (req.path_info.startswith(self.path+'/')
112 | or req.path_info == self.path):
113 | req.script_name += self.path
114 | req.path_info = req.path_info[len(self.path):]
115 | try:
116 | return self.dowse(req)(environ, start_response)
117 | except Exception as ex:
118 | error_text = traceback.format_exc()
119 |
120 | acceptable_offers = req.accept.acceptable_offers(
121 | offers=['text/html', 'application/json']
122 | )
123 | match = acceptable_offers[0][0] if acceptable_offers else None
124 | if match != 'application/json':
125 | # Strangely, webob.exc.WSGIHTTPException.plain_body replaces newlines
126 | # to spaces for plain/text, but replaces "
" tags to newlines.
127 | error_text = error_text.replace('\n', '
')
128 |
129 | return exc.HTTPInternalServerError(
130 | str(ex), body_template=error_text
131 | )(environ, start_response)
132 | else:
133 | return self.app(environ, start_response)
134 |
135 | def dowse(self, req):
136 | next_part = req.path_info_pop() or 'index'
137 | method = getattr(self, next_part, None)
138 | if method is None:
139 | return exc.HTTPNotFound('Nothing could be found to match %r' % next_part)
140 | if not getattr(method, 'exposed', False):
141 | return exc.HTTPForbidden('Access to %r is forbidden' % next_part)
142 | return method(req)
143 |
144 | def media(self, req):
145 | """Static path where images and other files live"""
146 | path = localDir.joinpath('media')
147 | return static.DirectoryApp(path)
148 | media.exposed = True
149 |
150 | def start(self):
151 | self.running = True
152 | while self.running:
153 | self.tick()
154 | time.sleep(self.period)
155 |
156 | def tick(self):
157 | gc.collect()
158 |
159 | typenamecounts = collections.defaultdict(int)
160 | for obj in gc.get_objects():
161 | objtype = type(obj)
162 | typename = "%s.%s" % (objtype.__module__, objtype.__name__)
163 | typenamecounts[typename] += 1
164 |
165 | for typename, count in typenamecounts.items():
166 | if typename not in self.history:
167 | self.history[typename] = [0] * self.samples
168 | self.history[typename].append(count)
169 |
170 | samples = self.samples + 1
171 |
172 | # Add dummy entries for any types which no longer exist
173 | for typename, hist in self.history.items():
174 | diff = samples - len(hist)
175 | if diff > 0:
176 | hist.extend([0] * diff)
177 |
178 | # Truncate history to self.maxhistory
179 | if samples > self.maxhistory:
180 | for typename, hist in self.history.items():
181 | hist.pop(0)
182 | else:
183 | self.samples = samples
184 |
185 | def stop(self):
186 | self.running = False
187 |
188 | def index(self, req):
189 | floor = req.GET.get('floor', 0) or 0
190 | filtertext = req.GET.get('filter', '')
191 | sortby = req.GET.get('sortby', '')
192 | filterre = re.compile(filtertext, re.IGNORECASE) if filtertext else None
193 | rows = []
194 | typenames = sorted(self.history)
195 | sort_key, reversed = get_sort_key(sortby)
196 | if sort_key is not None:
197 | sorted_history = sort_dict_by_val(self.history, sort_key, reversed)
198 | else:
199 | sorted_history = sorted(self.history.items())
200 |
201 | for typename, hist in sorted_history:
202 | maxhist = max(hist)
203 | if (
204 | maxhist > int(floor)
205 | and (not filterre or filterre.search(typename))
206 | ):
207 | row = ('
%s
'
208 | '
'
209 | 'Min: %s Cur: %s Max: %s
TRACE '
210 | % (escape(typename),
211 | url(req, "chart/%s" % typename),
212 | min(hist), hist[-1], maxhist,
213 | url(req, "trace/%s" % typename),
214 | )
215 | )
216 | rows.append(row)
217 | res = Response()
218 | res.text = template(
219 | req,
220 | "graphs.html",
221 | output="\n".join(rows),
222 | floor=floor,
223 | filter=escape(filtertext),
224 | sortby=sortby,
225 | jquery=url(req, "media/javascript/jquery-1.2.6.min.js")
226 | )
227 | return res
228 | index.exposed = True
229 |
230 | def chart(self, req):
231 | """Return a sparkline chart of the given type."""
232 | if Image is None or ImageDraw is None:
233 | # Cannot render
234 | return Response('Cannot render; PIL/Pillow is not installed',
235 | status='404 Not Found')
236 | typename = req.path_info_pop()
237 | data = self.history[typename]
238 | height = 20.0
239 | scale = height / max(data)
240 | im = Image.new("RGB", (len(data), int(height)), 'white')
241 | draw = ImageDraw.Draw(im)
242 | draw.line([(i, int(height - (v * scale))) for i, v in enumerate(data)],
243 | fill="#009900")
244 | del draw
245 |
246 | f = BytesIO()
247 | im.save(f, "PNG")
248 | result = f.getvalue()
249 |
250 | res = Response()
251 | res.headers["Content-Type"] = "image/png"
252 | res.body = result
253 | return res
254 | chart.exposed = True
255 |
256 | def trace(self, req):
257 | typename = req.path_info_pop()
258 | objid = req.path_info_pop()
259 | gc.collect()
260 |
261 | if objid is None:
262 | rows = self.trace_all(req, typename)
263 | else:
264 | rows = self.trace_one(req, typename, objid)
265 |
266 | res = Response()
267 | res.text = template(req, "trace.html", output="\n".join(rows),
268 | typename=escape(typename),
269 | objid=str(objid or ''))
270 | return res
271 | trace.exposed = True
272 |
273 | def trace_all(self, req, typename):
274 | rows = []
275 | for obj in gc.get_objects():
276 | objtype = type(obj)
277 | if "%s.%s" % (objtype.__module__, objtype.__name__) == typename:
278 | rows.append("
%s
"
279 | % ReferrerTree(obj, req).get_repr(obj))
280 | if not rows:
281 | rows = ["
The type you requested was not found. "]
282 | return rows
283 |
284 | def trace_one(self, req, typename, objid):
285 | rows = []
286 | objid = int(objid)
287 | all_objs = gc.get_objects()
288 | for obj in all_objs:
289 | if id(obj) == objid:
290 | objtype = type(obj)
291 | if "%s.%s" % (objtype.__module__, objtype.__name__) != typename:
292 | rows = ["
The object you requested is no longer "
293 | "of the correct type. "]
294 | else:
295 | # Attributes
296 | rows.append('
Attributes ')
297 | for k in dir(obj):
298 | try:
299 | v = getattr(obj, k, AttributeError)
300 | except Exception as ex:
301 | v = ex
302 | if type(v) not in method_types:
303 | rows.append('
%s: %s
' %
304 | (k, get_repr(v)))
305 | del v
306 | rows.append('
')
307 |
308 | # Referrers
309 | rows.append('
Referrers (Parents) ')
310 | rows.append('
Show the '
311 | 'entire tree of reachable objects
'
312 | % url(req, "/tree/%s/%s" % (typename, objid)))
313 | tree = ReferrerTree(obj, req)
314 | tree.ignore(all_objs)
315 | for depth, parentid, parentrepr in tree.walk(maxdepth=1):
316 | if parentid:
317 | rows.append("
%s
" % parentrepr)
318 | rows.append('
')
319 |
320 | # Referents
321 | rows.append('
Referents (Children) ')
322 | for child in gc.get_referents(obj):
323 | rows.append("
%s
" % tree.get_repr(child))
324 | rows.append('
')
325 | break
326 | if not rows:
327 | rows = ["
The object you requested was not found. "]
328 | return rows
329 |
330 | def tree(self, req):
331 | typename = req.path_info_pop()
332 | objid = req.path_info_pop()
333 | gc.collect()
334 |
335 | rows = []
336 | objid = int(objid)
337 | all_objs = gc.get_objects()
338 | for obj in all_objs:
339 | if id(obj) == objid:
340 | objtype = type(obj)
341 | if "%s.%s" % (objtype.__module__, objtype.__name__) != typename:
342 | rows = ["
The object you requested is no longer "
343 | "of the correct type. "]
344 | else:
345 | rows.append('
')
346 |
347 | tree = ReferrerTree(obj, req)
348 | tree.ignore(all_objs)
349 | for depth, parentid, parentrepr in tree.walk(maxresults=1000):
350 | rows.append(parentrepr)
351 |
352 | rows.append('
')
353 | break
354 | if not rows:
355 | rows = ["
The object you requested was not found. "]
356 |
357 | params = {'output': "\n".join(rows),
358 | 'typename': escape(typename),
359 | 'objid': str(objid),
360 | }
361 | res = Response()
362 | res.text = template(req, "tree.html", **params)
363 | return res
364 | tree.exposed = True
365 |
366 |
367 | class ReferrerTree(reftree.Tree):
368 |
369 | ignore_modules = True
370 |
371 | def _gen(self, obj, depth=0):
372 | if self.maxdepth and depth >= self.maxdepth:
373 | yield depth, 0, "---- Max depth reached ----"
374 | return
375 |
376 | if isinstance(obj, ModuleType) and self.ignore_modules:
377 | return
378 |
379 | refs = gc.get_referrers(obj)
380 | refiter = iter(refs)
381 | self.ignore(refs, refiter)
382 | thisfile = sys._getframe().f_code.co_filename
383 | for ref in refiter:
384 | # Exclude all frames that are from this module or reftree.
385 | if (isinstance(ref, FrameType)
386 | and ref.f_code.co_filename in (thisfile, self.filename)):
387 | continue # pragma: nocover -- on Python 3.11 this is never hit?
388 | if (isinstance(ref, GeneratorType)
389 | and ref.gi_code.co_filename in (thisfile, self.filename)):
390 | continue # pragma: nocover -- this is only hit on Python 3.14
391 |
392 | # Exclude all functions and classes from this module or reftree.
393 | mod = str(getattr(ref, "__module__", ""))
394 | if "dozer" in mod or "reftree" in mod or mod == '__main__':
395 | continue # pragma: nocover -- avoid bug in coverage due to Python's peephole optimizer
396 |
397 | # Exclude all parents in our ignore list.
398 | if id(ref) in self._ignore:
399 | continue
400 |
401 | # Yield the (depth, id, repr) of our object.
402 | yield depth, 0, '%s
' % (" " * depth)
403 | if id(ref) in self.seen:
404 | yield depth, id(ref), "see %s above" % id(ref)
405 | else:
406 | self.seen[id(ref)] = None
407 | yield depth, id(ref), self.get_repr(ref, obj)
408 |
409 | for parent in self._gen(ref, depth + 1):
410 | yield parent
411 | yield depth, 0, '%s
' % (" " * depth)
412 |
413 | def get_repr(self, obj, referent=None):
414 | """Return an HTML tree block describing the given object."""
415 | objtype = type(obj)
416 | typename = "%s.%s" % (objtype.__module__, objtype.__name__)
417 | prettytype = typename.replace("__builtin__.", "")
418 |
419 | name = getattr(obj, "__name__", "")
420 | if name:
421 | prettytype = "%s %r" % (prettytype, name)
422 |
423 | key = ""
424 | if referent:
425 | key = self.get_refkey(obj, referent)
426 | return ('
%s '
427 | '
%s %s
'
428 | '
%s '
429 | % (url(self.req, "/trace/%s/%s" % (typename, id(obj))),
430 | id(obj), prettytype, key, get_repr(obj, 100))
431 | )
432 |
433 | def get_refkey(self, obj, referent):
434 | """Return the dict key or attribute name of obj which refers to referent."""
435 | if isinstance(obj, dict):
436 | for k, v in obj.items():
437 | if v is referent:
438 | return " (via its %r key)" % repr(k)
439 |
440 | for k in dir(obj) + ['__dict__']:
441 | if getattr(obj, k, None) is referent:
442 | return " (via its %r attribute)" % repr(k)
443 | return ""
444 |
--------------------------------------------------------------------------------
/dozer/media/javascript/canviz.js:
--------------------------------------------------------------------------------
1 | // $Id: canviz.js 390 2007-03-29 10:45:46Z rschmidt $
2 |
3 | var Tokenizer = Class.create();
4 | Tokenizer.prototype = {
5 | initialize: function(str) {
6 | this.str = str;
7 | },
8 | takeChars: function(num) {
9 | if (!num) {
10 | num = 1;
11 | }
12 | var tokens = new Array();
13 | while (num--) {
14 | var matches = this.str.match(/^(\S+)\s*/);
15 | if (matches) {
16 | this.str = this.str.substr(matches[0].length);
17 | tokens.push(matches[1]);
18 | } else {
19 | tokens.push(false);
20 | }
21 | }
22 | if (1 == tokens.length) {
23 | return tokens[0];
24 | } else {
25 | return tokens;
26 | }
27 | },
28 | takeNumber: function(num) {
29 | if (!num) {
30 | num = 1;
31 | }
32 | if (1 == num) {
33 | return Number(this.takeChars())
34 | } else {
35 | var tokens = this.takeChars(num);
36 | while (num--) {
37 | tokens[num] = Number(tokens[num]);
38 | }
39 | return tokens;
40 | }
41 | },
42 | takeString: function() {
43 | var chars = Number(this.takeChars());
44 | if ('-' != this.str.charAt(0)) {
45 | return false;
46 | }
47 | var str = this.str.substr(1, chars);
48 | this.str = this.str.substr(1 + chars).replace(/^\s+/, '');
49 | return str;
50 | }
51 | }
52 |
53 | var Graph = Class.create();
54 | Graph.prototype = {
55 | initialize: function(ctx, file, engine) {
56 | this.maxXdotVersion = 1.2;
57 | this.systemScale = 4/3;
58 | this.scale = 1;
59 | this.padding = 8;
60 | this.ctx = ctx;
61 | this.images = new Hash();
62 | this.numImages = 0;
63 | this.numImagesFinished = 0;
64 | if (file) {
65 | this.load(file, engine);
66 | }
67 | },
68 | setImagePath: function(imagePath) {
69 | this.imagePath = imagePath;
70 | },
71 | load: function(file, engine) {
72 | $('debug_output').innerHTML = '';
73 | var url = 'graph.php';
74 | var params = 'file=' + file;
75 | if (engine) {
76 | params += '&engine=' + engine;
77 | }
78 | new Ajax.Request(url, {
79 | method: 'get',
80 | parameters: params,
81 | onComplete: this.parse.bind(this)
82 | });
83 | },
84 | parse: function(request) {
85 | this.xdotversion = false;
86 | this.commands = new Array();
87 | this.width = 0;
88 | this.height = 0;
89 | this.maxWidth = false;
90 | this.maxHeight = false;
91 | this.bbEnlarge = false;
92 | this.bbScale = 1;
93 | this.orientation = 'portrait';
94 | this.bgcolor = '#ffffff';
95 | this.dashLength = 6;
96 | this.dotSpacing = 4;
97 | this.fontName = 'Times New Roman';
98 | this.fontSize = 14;
99 | var graph_src = request.responseText;
100 | var lines = graph_src.split('\n');
101 | var i = 0;
102 | var line, lastchar, matches, is_graph, entity, params, param_name, param_value;
103 | var container_stack = new Array();
104 | while (i < lines.length) {
105 | line = lines[i++].replace(/^\s+/, '');
106 | if ('' != line && '#' != line.substr(0, 1)) {
107 | while (i < lines.length && ';' != (lastchar = line.substr(line.length - 1, line.length)) && '{' != lastchar && '}' != lastchar) {
108 | if ('\\' == lastchar) {
109 | line = line.substr(0, line.length - 1);
110 | }
111 | line += lines[i++];
112 | }
113 | // debug(line);
114 | matches = line.match(/^(.*?)\s*{$/);
115 | if (matches) {
116 | container_stack.push(matches[1]);
117 | // debug('begin container ' + container_stack.last());
118 | } else if ('}' == line) {
119 | // debug('end container ' + container_stack.last());
120 | container_stack.pop();
121 | } else {
122 | // matches = line.match(/^(".*?[^\\]"|\S+?)\s+\[(.+)\];$/);
123 | matches = line.match(/^(.*?)\s+\[(.+)\];$/);
124 | if (matches) {
125 | is_graph = ('graph' == matches[1]);
126 | // entity = this.unescape(matches[1]);
127 | entity = matches[1];
128 | params = matches[2];
129 | do {
130 | matches = params.match(/^(\S+?)=(""|".*?[^\\]"|<(<[^>]+>|[^<>]+?)+>|\S+?)(?:,\s*|$)/);
131 | if (matches) {
132 | params = params.substr(matches[0].length);
133 | param_name = matches[1];
134 | param_value = this.unescape(matches[2]);
135 | // debug(param_name + ' ' + param_value);
136 | if (is_graph && 1 == container_stack.length) {
137 | switch (param_name) {
138 | case 'bb':
139 | var bb = param_value.split(/,/);
140 | this.width = Number(bb[2]);
141 | this.height = Number(bb[3]);
142 | break;
143 | case 'bgcolor':
144 | this.bgcolor = this.parseColor(param_value);
145 | break;
146 | case 'size':
147 | var size = param_value.match(/^(\d+|\d*(?:\.\d+)),\s*(\d+|\d*(?:\.\d+))(!?)$/);
148 | if (size) {
149 | this.maxWidth = 72 * Number(size[1]);
150 | this.maxHeight = 72 * Number(size[2]);
151 | this.bbEnlarge = ('!' == size[3]);
152 | } else {
153 | debug('can\'t parse size');
154 | }
155 | break;
156 | case 'orientation':
157 | if (param_value.match(/^l/i)) {
158 | this.orientation = 'landscape';
159 | }
160 | break;
161 | case 'rotate':
162 | if (90 == param_value) {
163 | this.orientation = 'landscape';
164 | }
165 | break;
166 | case 'xdotversion':
167 | this.xdotversion = parseFloat(param_value);
168 | if (this.maxXdotVersion < this.xdotversion) {
169 | debug('unsupported xdotversion ' + this.xdotversion + '; this script currently supports up to xdotversion ' + this.maxXdotVersion);
170 | }
171 | break;
172 | }
173 | }
174 | switch (param_name) {
175 | case '_draw_':
176 | case '_ldraw_':
177 | case '_hdraw_':
178 | case '_tdraw_':
179 | case '_hldraw_':
180 | case '_tldraw_':
181 | // debug(entity + ': ' + param_value);
182 | this.commands.push(param_value);
183 | break;
184 | }
185 | }
186 | } while (matches);
187 | }
188 | }
189 | }
190 | }
191 | if (!this.xdotversion) {
192 | this.xdotversion = 1.0;
193 | }
194 | /*
195 | if (this.maxWidth && this.maxHeight) {
196 | if (this.width > this.maxWidth || this.height > this.maxHeight || this.bbEnlarge) {
197 | this.bbScale = Math.min(this.maxWidth / this.width, this.maxHeight / this.height);
198 | this.width = Math.round(this.width * this.bbScale);
199 | this.height = Math.round(this.height * this.bbScale);
200 | }
201 | if ('landscape' == this.orientation) {
202 | var temp = this.width;
203 | this.width = this.height;
204 | this.height = temp;
205 | }
206 | }
207 | */
208 | // debug('done');
209 | this.draw();
210 | },
211 | draw: function(redraw_canvas) {
212 | if (!redraw_canvas) redraw_canvas = false;
213 | var width = Math.round(this.scale * this.systemScale * this.width + 2 * this.padding);
214 | var height = Math.round(this.scale * this.systemScale * this.height + 2 * this.padding);
215 | if (!redraw_canvas) {
216 | canvas.width = width;
217 | canvas.height = height;
218 | Element.setStyle(canvas, {
219 | width: width + 'px',
220 | height: height + 'px'
221 | });
222 | Element.setStyle('graph_container', {
223 | width: width + 'px'
224 | });
225 | $('graph_texts').innerHTML = '';
226 | }
227 | this.ctx.save();
228 | this.ctx.lineCap = 'round';
229 | this.ctx.fillStyle = this.bgcolor;
230 | this.ctx.fillRect(0, 0, width, height);
231 | this.ctx.translate(this.padding, this.padding);
232 | this.ctx.scale(this.scale * this.systemScale, this.scale * this.systemScale);
233 | this.ctx.lineWidth = 1 / this.systemScale;
234 | var i, tokens;
235 | var entity_id = 0;
236 | var text_divs = '';
237 | for (var command_index = 0; command_index < this.commands.length; command_index++) {
238 | var command = this.commands[command_index];
239 | // debug(command);
240 | var tokenizer = new Tokenizer(command);
241 | var token = tokenizer.takeChars();
242 | if (token) {
243 | ++entity_id;
244 | var entity_text_divs = '';
245 | this.dashStyle = 'solid';
246 | this.ctx.save();
247 | while (token) {
248 | // debug('processing token ' + token);
249 | switch (token) {
250 | case 'E': // filled ellipse
251 | case 'e': // unfilled ellipse
252 | var filled = ('E' == token);
253 | var cx = tokenizer.takeNumber();
254 | var cy = this.height - tokenizer.takeNumber();
255 | var rx = tokenizer.takeNumber();
256 | var ry = tokenizer.takeNumber();
257 | this.render(new Ellipse(cx, cy, rx, ry), filled);
258 | break;
259 | case 'P': // filled polygon
260 | case 'p': // unfilled polygon
261 | case 'L': // polyline
262 | var filled = ('P' == token);
263 | var closed = ('L' != token);
264 | var num_points = tokenizer.takeNumber();
265 | tokens = tokenizer.takeNumber(2 * num_points); // points
266 | var path = new Path();
267 | for (i = 2; i < 2 * num_points; i += 2) {
268 | path.addBezier([
269 | new Point(tokens[i - 2], this.height - tokens[i - 1]),
270 | new Point(tokens[i], this.height - tokens[i + 1])
271 | ]);
272 | }
273 | if (closed) {
274 | path.addBezier([
275 | new Point(tokens[2 * num_points - 2], this.height - tokens[2 * num_points - 1]),
276 | new Point(tokens[0], this.height - tokens[1])
277 | ]);
278 | }
279 | this.render(path, filled);
280 | break;
281 | case 'B': // unfilled b-spline
282 | case 'b': // filled b-spline
283 | var filled = ('b' == token);
284 | var num_points = tokenizer.takeNumber();
285 | tokens = tokenizer.takeNumber(2 * num_points); // points
286 | var path = new Path();
287 | for (i = 2; i < 2 * num_points; i += 6) {
288 | path.addBezier([
289 | new Point(tokens[i - 2], this.height - tokens[i - 1]),
290 | new Point(tokens[i], this.height - tokens[i + 1]),
291 | new Point(tokens[i + 2], this.height - tokens[i + 3]),
292 | new Point(tokens[i + 4], this.height - tokens[i + 5])
293 | ]);
294 | }
295 | this.render(path, filled);
296 | break;
297 | case 'I': // image
298 | var x = tokenizer.takeNumber();
299 | var y = this.height - tokenizer.takeNumber();
300 | var w = tokenizer.takeNumber();
301 | var h = tokenizer.takeNumber();
302 | var src = tokenizer.takeString();
303 | if (!this.images[src]) {
304 | y -= h;
305 | this.images[src] = new GraphImage(this, src, x, y, w, h);
306 | }
307 | this.images[src].draw();
308 | break;
309 | case 'T': // text
310 | var x = Math.round(this.scale * this.systemScale * tokenizer.takeNumber() + this.padding);
311 | var y = Math.round(height - (this.scale * this.systemScale * (tokenizer.takeNumber() + this.bbScale * this.fontSize) + this.padding));
312 | var text_align = tokenizer.takeNumber();
313 | var text_width = Math.round(this.scale * this.systemScale * tokenizer.takeNumber());
314 | var str = tokenizer.takeString();
315 | if (!redraw_canvas && !str.match(/^\s*$/)) {
316 | // debug('draw text ' + str + ' ' + x + ' ' + y + ' ' + text_align + ' ' + text_width);
317 | str = str.escapeHTML();
318 | do {
319 | matches = str.match(/ ( +)/);
320 | if (matches) {
321 | var spaces = ' ';
322 | matches[1].length.times(function() {
323 | spaces += ' ';
324 | });
325 | str = str.replace(/ +/, spaces);
326 | }
327 | } while (matches);
328 | entity_text_divs += '
' + str + '
';
342 | }
343 | break;
344 | case 'C': // set fill color
345 | case 'c': // set pen color
346 | var fill = ('C' == token);
347 | var color = this.parseColor(tokenizer.takeString());
348 | if (fill) {
349 | this.ctx.fillStyle = color;
350 | } else {
351 | this.ctx.strokeStyle = color;
352 | }
353 | break;
354 | case 'F': // set font
355 | this.fontSize = tokenizer.takeNumber();
356 | this.fontName = tokenizer.takeString();
357 | switch (this.fontName) {
358 | case 'Times-Roman':
359 | this.fontName = 'Times New Roman';
360 | break;
361 | case 'Courier':
362 | this.fontName = 'Courier New';
363 | break;
364 | case 'Helvetica':
365 | this.fontName = 'Arial';
366 | break;
367 | default:
368 | // nothing
369 | }
370 | // debug('set font ' + this.fontSize + 'pt ' + this.fontName);
371 | break;
372 | case 'S': // set style
373 | var style = tokenizer.takeString();
374 | switch (style) {
375 | case 'solid':
376 | case 'filled':
377 | // nothing
378 | break;
379 | case 'dashed':
380 | case 'dotted':
381 | this.dashStyle = style;
382 | break;
383 | case 'bold':
384 | this.ctx.lineWidth = 2 / this.systemScale;
385 | break;
386 | default:
387 | matches = style.match(/^setlinewidth\((.*)\)$/);
388 | if (matches) {
389 | this.ctx.lineWidth = Number(matches[1]) / this.systemScale;
390 | } else {
391 | debug('unknown style ' + style);
392 | }
393 | }
394 | break;
395 | default:
396 | debug('unknown token ' + token);
397 | return;
398 | }
399 | token = tokenizer.takeChars();
400 | }
401 | this.ctx.restore();
402 | if (entity_text_divs) {
403 | text_divs += '
' + entity_text_divs + '
';
404 | }
405 | }
406 | };
407 | this.ctx.restore();
408 | if (!redraw_canvas) $('graph_texts').innerHTML = text_divs;
409 | },
410 | render: function(path, filled) {
411 | if (filled) {
412 | this.ctx.beginPath();
413 | path.draw(this.ctx);
414 | this.ctx.fill();
415 | }
416 | if (this.ctx.fillStyle != this.ctx.strokeStyle || !filled) {
417 | switch (this.dashStyle) {
418 | case 'dashed':
419 | this.ctx.beginPath();
420 | path.drawDashed(this.ctx, this.dashLength);
421 | break;
422 | case 'dotted':
423 | var oldLineWidth = this.ctx.lineWidth;
424 | this.ctx.lineWidth *= 2;
425 | this.ctx.beginPath();
426 | path.drawDotted(this.ctx, this.dotSpacing);
427 | break;
428 | case 'solid':
429 | default:
430 | if (!filled) {
431 | this.ctx.beginPath();
432 | path.draw(this.ctx);
433 | }
434 | }
435 | this.ctx.stroke();
436 | if (oldLineWidth) this.ctx.lineWidth = oldLineWidth;
437 | }
438 | },
439 | unescape: function(str) {
440 | var matches = str.match(/^"(.*)"$/);
441 | if (matches) {
442 | return matches[1].replace(/\\"/g, '"');
443 | } else {
444 | return str;
445 | }
446 | },
447 | parseColor: function(color) {
448 | if (gvcolors[color]) { // named color
449 | return 'rgb(' + gvcolors[color][0] + ',' + gvcolors[color][1] + ',' + gvcolors[color][2] + ')';
450 | } else {
451 | var matches = color.match(/^#([0-9a-f]{2})([0-9a-f]{2})([0-9a-f]{2})([0-9a-f]{2})$/i);
452 | if (matches) { // rgba
453 | return 'rgba(' + parseInt(matches[1], 16) + ',' + parseInt(matches[2], 16) + ',' + parseInt(matches[3], 16) + ',' + (parseInt(matches[4], 16) / 255) + ')';
454 | } else {
455 | matches = color.match(/(\d+(?:\.\d+)?)\s+(\d+(?:\.\d+)?)\s+(\d+(?:\.\d+)?)/);
456 | if (matches) { // hsv
457 | return this.hsvToRgbColor(matches[1], matches[2], matches[3]);
458 | } else if (color.match(/^#[0-9a-f]{6}$/i)) {
459 | return color;
460 | }
461 | }
462 | }
463 | debug('unknown color ' + color);
464 | return '#000000';
465 | },
466 | hsvToRgbColor: function(h, s, v) {
467 | var i, f, p, q, t, r, g, b;
468 | h *= 360;
469 | i = Math.floor(h / 60) % 6;
470 | f = h / 60 - i;
471 | p = v * (1 - s);
472 | q = v * (1 - f * s);
473 | t = v * (1 - (1 - f) * s)
474 | switch (i) {
475 | case 0: r = v; g = t; b = p; break;
476 | case 1: r = q; g = v; b = p; break;
477 | case 2: r = p; g = v; b = t; break;
478 | case 3: r = p; g = q; b = v; break;
479 | case 4: r = t; g = p; b = v; break;
480 | case 5: r = v; g = p; b = q; break;
481 | }
482 | return 'rgb(' + Math.round(255 * r) + ',' + Math.round(255 * g) + ',' + Math.round(255 * b) + ')';
483 | }
484 | }
485 |
486 | var GraphImage = Class.create();
487 | GraphImage.prototype = {
488 | initialize: function(graph, src, x, y, w, h) {
489 | this.graph = graph;
490 | ++this.graph.numImages;
491 | this.src = this.graph.imagePath + '/' + src;
492 | this.x = x;
493 | this.y = y;
494 | this.w = w;
495 | this.h = h;
496 | this.loaded = false;
497 | this.img = new Image();
498 | this.img.onload = this.succeeded.bind(this);
499 | this.img.onerror = this.finished.bind(this);
500 | this.img.onabort = this.finished.bind(this);
501 | this.img.src = this.src;
502 | },
503 | succeeded: function() {
504 | this.loaded = true;
505 | this.finished();
506 | },
507 | finished: function() {
508 | ++this.graph.numImagesFinished;
509 | if (this.graph.numImages == this.graph.numImagesFinished) {
510 | this.graph.draw(true);
511 | }
512 | },
513 | draw: function() {
514 | if (this.loaded) {
515 | this.graph.ctx.drawImage(this.img, this.x, this.y, this.w, this.h);
516 | }
517 | }
518 | }
519 |
520 | function debug(str) {
521 | $('debug_output').innerHTML += '»' + String(str).escapeHTML() + '«
';
522 | }
523 |
--------------------------------------------------------------------------------
/dozer/media/javascript/gvcolors.js:
--------------------------------------------------------------------------------
1 | // $Id: gvcolors.js 367 2007-03-13 08:57:11Z rschmidt $
2 |
3 | gvcolors={
4 | aliceblue:[240,248,255],
5 | antiquewhite:[250,235,215],
6 | antiquewhite1:[255,239,219],
7 | antiquewhite2:[238,223,204],
8 | antiquewhite3:[205,192,176],
9 | antiquewhite4:[139,131,120],
10 | aquamarine:[127,255,212],
11 | aquamarine1:[127,255,212],
12 | aquamarine2:[118,238,198],
13 | aquamarine3:[102,205,170],
14 | aquamarine4:[69,139,116],
15 | azure:[240,255,255],
16 | azure1:[240,255,255],
17 | azure2:[224,238,238],
18 | azure3:[193,205,205],
19 | azure4:[131,139,139],
20 | beige:[245,245,220],
21 | bisque:[255,228,196],
22 | bisque1:[255,228,196],
23 | bisque2:[238,213,183],
24 | bisque3:[205,183,158],
25 | bisque4:[139,125,107],
26 | black:[0,0,0],
27 | blanchedalmond:[255,235,205],
28 | blue:[0,0,255],
29 | blue1:[0,0,255],
30 | blue2:[0,0,238],
31 | blue3:[0,0,205],
32 | blue4:[0,0,139],
33 | blueviolet:[138,43,226],
34 | brown:[165,42,42],
35 | brown1:[255,64,64],
36 | brown2:[238,59,59],
37 | brown3:[205,51,51],
38 | brown4:[139,35,35],
39 | burlywood:[222,184,135],
40 | burlywood1:[255,211,155],
41 | burlywood2:[238,197,145],
42 | burlywood3:[205,170,125],
43 | burlywood4:[139,115,85],
44 | cadetblue:[95,158,160],
45 | cadetblue1:[152,245,255],
46 | cadetblue2:[142,229,238],
47 | cadetblue3:[122,197,205],
48 | cadetblue4:[83,134,139],
49 | chartreuse:[127,255,0],
50 | chartreuse1:[127,255,0],
51 | chartreuse2:[118,238,0],
52 | chartreuse3:[102,205,0],
53 | chartreuse4:[69,139,0],
54 | chocolate:[210,105,30],
55 | chocolate1:[255,127,36],
56 | chocolate2:[238,118,33],
57 | chocolate3:[205,102,29],
58 | chocolate4:[139,69,19],
59 | coral:[255,127,80],
60 | coral1:[255,114,86],
61 | coral2:[238,106,80],
62 | coral3:[205,91,69],
63 | coral4:[139,62,47],
64 | cornflowerblue:[100,149,237],
65 | cornsilk:[255,248,220],
66 | cornsilk1:[255,248,220],
67 | cornsilk2:[238,232,205],
68 | cornsilk3:[205,200,177],
69 | cornsilk4:[139,136,120],
70 | crimson:[220,20,60],
71 | cyan:[0,255,255],
72 | cyan1:[0,255,255],
73 | cyan2:[0,238,238],
74 | cyan3:[0,205,205],
75 | cyan4:[0,139,139],
76 | darkgoldenrod:[184,134,11],
77 | darkgoldenrod1:[255,185,15],
78 | darkgoldenrod2:[238,173,14],
79 | darkgoldenrod3:[205,149,12],
80 | darkgoldenrod4:[139,101,8],
81 | darkgreen:[0,100,0],
82 | darkkhaki:[189,183,107],
83 | darkolivegreen:[85,107,47],
84 | darkolivegreen1:[202,255,112],
85 | darkolivegreen2:[188,238,104],
86 | darkolivegreen3:[162,205,90],
87 | darkolivegreen4:[110,139,61],
88 | darkorange:[255,140,0],
89 | darkorange1:[255,127,0],
90 | darkorange2:[238,118,0],
91 | darkorange3:[205,102,0],
92 | darkorange4:[139,69,0],
93 | darkorchid:[153,50,204],
94 | darkorchid1:[191,62,255],
95 | darkorchid2:[178,58,238],
96 | darkorchid3:[154,50,205],
97 | darkorchid4:[104,34,139],
98 | darksalmon:[233,150,122],
99 | darkseagreen:[143,188,143],
100 | darkseagreen1:[193,255,193],
101 | darkseagreen2:[180,238,180],
102 | darkseagreen3:[155,205,155],
103 | darkseagreen4:[105,139,105],
104 | darkslateblue:[72,61,139],
105 | darkslategray:[47,79,79],
106 | darkslategray1:[151,255,255],
107 | darkslategray2:[141,238,238],
108 | darkslategray3:[121,205,205],
109 | darkslategray4:[82,139,139],
110 | darkslategrey:[47,79,79],
111 | darkturquoise:[0,206,209],
112 | darkviolet:[148,0,211],
113 | deeppink:[255,20,147],
114 | deeppink1:[255,20,147],
115 | deeppink2:[238,18,137],
116 | deeppink3:[205,16,118],
117 | deeppink4:[139,10,80],
118 | deepskyblue:[0,191,255],
119 | deepskyblue1:[0,191,255],
120 | deepskyblue2:[0,178,238],
121 | deepskyblue3:[0,154,205],
122 | deepskyblue4:[0,104,139],
123 | dimgray:[105,105,105],
124 | dimgrey:[105,105,105],
125 | dodgerblue:[30,144,255],
126 | dodgerblue1:[30,144,255],
127 | dodgerblue2:[28,134,238],
128 | dodgerblue3:[24,116,205],
129 | dodgerblue4:[16,78,139],
130 | firebrick:[178,34,34],
131 | firebrick1:[255,48,48],
132 | firebrick2:[238,44,44],
133 | firebrick3:[205,38,38],
134 | firebrick4:[139,26,26],
135 | floralwhite:[255,250,240],
136 | forestgreen:[34,139,34],
137 | gainsboro:[220,220,220],
138 | ghostwhite:[248,248,255],
139 | gold:[255,215,0],
140 | gold1:[255,215,0],
141 | gold2:[238,201,0],
142 | gold3:[205,173,0],
143 | gold4:[139,117,0],
144 | goldenrod:[218,165,32],
145 | goldenrod1:[255,193,37],
146 | goldenrod2:[238,180,34],
147 | goldenrod3:[205,155,29],
148 | goldenrod4:[139,105,20],
149 | gray:[192,192,192],
150 | gray0:[0,0,0],
151 | gray1:[3,3,3],
152 | gray10:[26,26,26],
153 | gray100:[255,255,255],
154 | gray11:[28,28,28],
155 | gray12:[31,31,31],
156 | gray13:[33,33,33],
157 | gray14:[36,36,36],
158 | gray15:[38,38,38],
159 | gray16:[41,41,41],
160 | gray17:[43,43,43],
161 | gray18:[46,46,46],
162 | gray19:[48,48,48],
163 | gray2:[5,5,5],
164 | gray20:[51,51,51],
165 | gray21:[54,54,54],
166 | gray22:[56,56,56],
167 | gray23:[59,59,59],
168 | gray24:[61,61,61],
169 | gray25:[64,64,64],
170 | gray26:[66,66,66],
171 | gray27:[69,69,69],
172 | gray28:[71,71,71],
173 | gray29:[74,74,74],
174 | gray3:[8,8,8],
175 | gray30:[77,77,77],
176 | gray31:[79,79,79],
177 | gray32:[82,82,82],
178 | gray33:[84,84,84],
179 | gray34:[87,87,87],
180 | gray35:[89,89,89],
181 | gray36:[92,92,92],
182 | gray37:[94,94,94],
183 | gray38:[97,97,97],
184 | gray39:[99,99,99],
185 | gray4:[10,10,10],
186 | gray40:[102,102,102],
187 | gray41:[105,105,105],
188 | gray42:[107,107,107],
189 | gray43:[110,110,110],
190 | gray44:[112,112,112],
191 | gray45:[115,115,115],
192 | gray46:[117,117,117],
193 | gray47:[120,120,120],
194 | gray48:[122,122,122],
195 | gray49:[125,125,125],
196 | gray5:[13,13,13],
197 | gray50:[127,127,127],
198 | gray51:[130,130,130],
199 | gray52:[133,133,133],
200 | gray53:[135,135,135],
201 | gray54:[138,138,138],
202 | gray55:[140,140,140],
203 | gray56:[143,143,143],
204 | gray57:[145,145,145],
205 | gray58:[148,148,148],
206 | gray59:[150,150,150],
207 | gray6:[15,15,15],
208 | gray60:[153,153,153],
209 | gray61:[156,156,156],
210 | gray62:[158,158,158],
211 | gray63:[161,161,161],
212 | gray64:[163,163,163],
213 | gray65:[166,166,166],
214 | gray66:[168,168,168],
215 | gray67:[171,171,171],
216 | gray68:[173,173,173],
217 | gray69:[176,176,176],
218 | gray7:[18,18,18],
219 | gray70:[179,179,179],
220 | gray71:[181,181,181],
221 | gray72:[184,184,184],
222 | gray73:[186,186,186],
223 | gray74:[189,189,189],
224 | gray75:[191,191,191],
225 | gray76:[194,194,194],
226 | gray77:[196,196,196],
227 | gray78:[199,199,199],
228 | gray79:[201,201,201],
229 | gray8:[20,20,20],
230 | gray80:[204,204,204],
231 | gray81:[207,207,207],
232 | gray82:[209,209,209],
233 | gray83:[212,212,212],
234 | gray84:[214,214,214],
235 | gray85:[217,217,217],
236 | gray86:[219,219,219],
237 | gray87:[222,222,222],
238 | gray88:[224,224,224],
239 | gray89:[227,227,227],
240 | gray9:[23,23,23],
241 | gray90:[229,229,229],
242 | gray91:[232,232,232],
243 | gray92:[235,235,235],
244 | gray93:[237,237,237],
245 | gray94:[240,240,240],
246 | gray95:[242,242,242],
247 | gray96:[245,245,245],
248 | gray97:[247,247,247],
249 | gray98:[250,250,250],
250 | gray99:[252,252,252],
251 | green:[0,255,0],
252 | green1:[0,255,0],
253 | green2:[0,238,0],
254 | green3:[0,205,0],
255 | green4:[0,139,0],
256 | greenyellow:[173,255,47],
257 | grey:[192,192,192],
258 | grey0:[0,0,0],
259 | grey1:[3,3,3],
260 | grey10:[26,26,26],
261 | grey100:[255,255,255],
262 | grey11:[28,28,28],
263 | grey12:[31,31,31],
264 | grey13:[33,33,33],
265 | grey14:[36,36,36],
266 | grey15:[38,38,38],
267 | grey16:[41,41,41],
268 | grey17:[43,43,43],
269 | grey18:[46,46,46],
270 | grey19:[48,48,48],
271 | grey2:[5,5,5],
272 | grey20:[51,51,51],
273 | grey21:[54,54,54],
274 | grey22:[56,56,56],
275 | grey23:[59,59,59],
276 | grey24:[61,61,61],
277 | grey25:[64,64,64],
278 | grey26:[66,66,66],
279 | grey27:[69,69,69],
280 | grey28:[71,71,71],
281 | grey29:[74,74,74],
282 | grey3:[8,8,8],
283 | grey30:[77,77,77],
284 | grey31:[79,79,79],
285 | grey32:[82,82,82],
286 | grey33:[84,84,84],
287 | grey34:[87,87,87],
288 | grey35:[89,89,89],
289 | grey36:[92,92,92],
290 | grey37:[94,94,94],
291 | grey38:[97,97,97],
292 | grey39:[99,99,99],
293 | grey4:[10,10,10],
294 | grey40:[102,102,102],
295 | grey41:[105,105,105],
296 | grey42:[107,107,107],
297 | grey43:[110,110,110],
298 | grey44:[112,112,112],
299 | grey45:[115,115,115],
300 | grey46:[117,117,117],
301 | grey47:[120,120,120],
302 | grey48:[122,122,122],
303 | grey49:[125,125,125],
304 | grey5:[13,13,13],
305 | grey50:[127,127,127],
306 | grey51:[130,130,130],
307 | grey52:[133,133,133],
308 | grey53:[135,135,135],
309 | grey54:[138,138,138],
310 | grey55:[140,140,140],
311 | grey56:[143,143,143],
312 | grey57:[145,145,145],
313 | grey58:[148,148,148],
314 | grey59:[150,150,150],
315 | grey6:[15,15,15],
316 | grey60:[153,153,153],
317 | grey61:[156,156,156],
318 | grey62:[158,158,158],
319 | grey63:[161,161,161],
320 | grey64:[163,163,163],
321 | grey65:[166,166,166],
322 | grey66:[168,168,168],
323 | grey67:[171,171,171],
324 | grey68:[173,173,173],
325 | grey69:[176,176,176],
326 | grey7:[18,18,18],
327 | grey70:[179,179,179],
328 | grey71:[181,181,181],
329 | grey72:[184,184,184],
330 | grey73:[186,186,186],
331 | grey74:[189,189,189],
332 | grey75:[191,191,191],
333 | grey76:[194,194,194],
334 | grey77:[196,196,196],
335 | grey78:[199,199,199],
336 | grey79:[201,201,201],
337 | grey8:[20,20,20],
338 | grey80:[204,204,204],
339 | grey81:[207,207,207],
340 | grey82:[209,209,209],
341 | grey83:[212,212,212],
342 | grey84:[214,214,214],
343 | grey85:[217,217,217],
344 | grey86:[219,219,219],
345 | grey87:[222,222,222],
346 | grey88:[224,224,224],
347 | grey89:[227,227,227],
348 | grey9:[23,23,23],
349 | grey90:[229,229,229],
350 | grey91:[232,232,232],
351 | grey92:[235,235,235],
352 | grey93:[237,237,237],
353 | grey94:[240,240,240],
354 | grey95:[242,242,242],
355 | grey96:[245,245,245],
356 | grey97:[247,247,247],
357 | grey98:[250,250,250],
358 | grey99:[252,252,252],
359 | honeydew:[240,255,240],
360 | honeydew1:[240,255,240],
361 | honeydew2:[224,238,224],
362 | honeydew3:[193,205,193],
363 | honeydew4:[131,139,131],
364 | hotpink:[255,105,180],
365 | hotpink1:[255,110,180],
366 | hotpink2:[238,106,167],
367 | hotpink3:[205,96,144],
368 | hotpink4:[139,58,98],
369 | indianred:[205,92,92],
370 | indianred1:[255,106,106],
371 | indianred2:[238,99,99],
372 | indianred3:[205,85,85],
373 | indianred4:[139,58,58],
374 | indigo:[75,0,130],
375 | ivory:[255,255,240],
376 | ivory1:[255,255,240],
377 | ivory2:[238,238,224],
378 | ivory3:[205,205,193],
379 | ivory4:[139,139,131],
380 | khaki:[240,230,140],
381 | khaki1:[255,246,143],
382 | khaki2:[238,230,133],
383 | khaki3:[205,198,115],
384 | khaki4:[139,134,78],
385 | lavender:[230,230,250],
386 | lavenderblush:[255,240,245],
387 | lavenderblush1:[255,240,245],
388 | lavenderblush2:[238,224,229],
389 | lavenderblush3:[205,193,197],
390 | lavenderblush4:[139,131,134],
391 | lawngreen:[124,252,0],
392 | lemonchiffon:[255,250,205],
393 | lemonchiffon1:[255,250,205],
394 | lemonchiffon2:[238,233,191],
395 | lemonchiffon3:[205,201,165],
396 | lemonchiffon4:[139,137,112],
397 | lightblue:[173,216,230],
398 | lightblue1:[191,239,255],
399 | lightblue2:[178,223,238],
400 | lightblue3:[154,192,205],
401 | lightblue4:[104,131,139],
402 | lightcoral:[240,128,128],
403 | lightcyan:[224,255,255],
404 | lightcyan1:[224,255,255],
405 | lightcyan2:[209,238,238],
406 | lightcyan3:[180,205,205],
407 | lightcyan4:[122,139,139],
408 | lightgoldenrod:[238,221,130],
409 | lightgoldenrod1:[255,236,139],
410 | lightgoldenrod2:[238,220,130],
411 | lightgoldenrod3:[205,190,112],
412 | lightgoldenrod4:[139,129,76],
413 | lightgoldenrodyellow:[250,250,210],
414 | lightgray:[211,211,211],
415 | lightgrey:[211,211,211],
416 | lightpink:[255,182,193],
417 | lightpink1:[255,174,185],
418 | lightpink2:[238,162,173],
419 | lightpink3:[205,140,149],
420 | lightpink4:[139,95,101],
421 | lightsalmon:[255,160,122],
422 | lightsalmon1:[255,160,122],
423 | lightsalmon2:[238,149,114],
424 | lightsalmon3:[205,129,98],
425 | lightsalmon4:[139,87,66],
426 | lightseagreen:[32,178,170],
427 | lightskyblue:[135,206,250],
428 | lightskyblue1:[176,226,255],
429 | lightskyblue2:[164,211,238],
430 | lightskyblue3:[141,182,205],
431 | lightskyblue4:[96,123,139],
432 | lightslateblue:[132,112,255],
433 | lightslategray:[119,136,153],
434 | lightslategrey:[119,136,153],
435 | lightsteelblue:[176,196,222],
436 | lightsteelblue1:[202,225,255],
437 | lightsteelblue2:[188,210,238],
438 | lightsteelblue3:[162,181,205],
439 | lightsteelblue4:[110,123,139],
440 | lightyellow:[255,255,224],
441 | lightyellow1:[255,255,224],
442 | lightyellow2:[238,238,209],
443 | lightyellow3:[205,205,180],
444 | lightyellow4:[139,139,122],
445 | limegreen:[50,205,50],
446 | linen:[250,240,230],
447 | magenta:[255,0,255],
448 | magenta1:[255,0,255],
449 | magenta2:[238,0,238],
450 | magenta3:[205,0,205],
451 | magenta4:[139,0,139],
452 | maroon:[176,48,96],
453 | maroon1:[255,52,179],
454 | maroon2:[238,48,167],
455 | maroon3:[205,41,144],
456 | maroon4:[139,28,98],
457 | mediumaquamarine:[102,205,170],
458 | mediumblue:[0,0,205],
459 | mediumorchid:[186,85,211],
460 | mediumorchid1:[224,102,255],
461 | mediumorchid2:[209,95,238],
462 | mediumorchid3:[180,82,205],
463 | mediumorchid4:[122,55,139],
464 | mediumpurple:[147,112,219],
465 | mediumpurple1:[171,130,255],
466 | mediumpurple2:[159,121,238],
467 | mediumpurple3:[137,104,205],
468 | mediumpurple4:[93,71,139],
469 | mediumseagreen:[60,179,113],
470 | mediumslateblue:[123,104,238],
471 | mediumspringgreen:[0,250,154],
472 | mediumturquoise:[72,209,204],
473 | mediumvioletred:[199,21,133],
474 | midnightblue:[25,25,112],
475 | mintcream:[245,255,250],
476 | mistyrose:[255,228,225],
477 | mistyrose1:[255,228,225],
478 | mistyrose2:[238,213,210],
479 | mistyrose3:[205,183,181],
480 | mistyrose4:[139,125,123],
481 | moccasin:[255,228,181],
482 | navajowhite:[255,222,173],
483 | navajowhite1:[255,222,173],
484 | navajowhite2:[238,207,161],
485 | navajowhite3:[205,179,139],
486 | navajowhite4:[139,121,94],
487 | navy:[0,0,128],
488 | navyblue:[0,0,128],
489 | oldlace:[253,245,230],
490 | olivedrab:[107,142,35],
491 | olivedrab1:[192,255,62],
492 | olivedrab2:[179,238,58],
493 | olivedrab3:[154,205,50],
494 | olivedrab4:[105,139,34],
495 | orange:[255,165,0],
496 | orange1:[255,165,0],
497 | orange2:[238,154,0],
498 | orange3:[205,133,0],
499 | orange4:[139,90,0],
500 | orangered:[255,69,0],
501 | orangered1:[255,69,0],
502 | orangered2:[238,64,0],
503 | orangered3:[205,55,0],
504 | orangered4:[139,37,0],
505 | orchid:[218,112,214],
506 | orchid1:[255,131,250],
507 | orchid2:[238,122,233],
508 | orchid3:[205,105,201],
509 | orchid4:[139,71,137],
510 | palegoldenrod:[238,232,170],
511 | palegreen:[152,251,152],
512 | palegreen1:[154,255,154],
513 | palegreen2:[144,238,144],
514 | palegreen3:[124,205,124],
515 | palegreen4:[84,139,84],
516 | paleturquoise:[175,238,238],
517 | paleturquoise1:[187,255,255],
518 | paleturquoise2:[174,238,238],
519 | paleturquoise3:[150,205,205],
520 | paleturquoise4:[102,139,139],
521 | palevioletred:[219,112,147],
522 | palevioletred1:[255,130,171],
523 | palevioletred2:[238,121,159],
524 | palevioletred3:[205,104,137],
525 | palevioletred4:[139,71,93],
526 | papayawhip:[255,239,213],
527 | peachpuff:[255,218,185],
528 | peachpuff1:[255,218,185],
529 | peachpuff2:[238,203,173],
530 | peachpuff3:[205,175,149],
531 | peachpuff4:[139,119,101],
532 | peru:[205,133,63],
533 | pink:[255,192,203],
534 | pink1:[255,181,197],
535 | pink2:[238,169,184],
536 | pink3:[205,145,158],
537 | pink4:[139,99,108],
538 | plum:[221,160,221],
539 | plum1:[255,187,255],
540 | plum2:[238,174,238],
541 | plum3:[205,150,205],
542 | plum4:[139,102,139],
543 | powderblue:[176,224,230],
544 | purple:[160,32,240],
545 | purple1:[155,48,255],
546 | purple2:[145,44,238],
547 | purple3:[125,38,205],
548 | purple4:[85,26,139],
549 | red:[255,0,0],
550 | red1:[255,0,0],
551 | red2:[238,0,0],
552 | red3:[205,0,0],
553 | red4:[139,0,0],
554 | rosybrown:[188,143,143],
555 | rosybrown1:[255,193,193],
556 | rosybrown2:[238,180,180],
557 | rosybrown3:[205,155,155],
558 | rosybrown4:[139,105,105],
559 | royalblue:[65,105,225],
560 | royalblue1:[72,118,255],
561 | royalblue2:[67,110,238],
562 | royalblue3:[58,95,205],
563 | royalblue4:[39,64,139],
564 | saddlebrown:[139,69,19],
565 | salmon:[250,128,114],
566 | salmon1:[255,140,105],
567 | salmon2:[238,130,98],
568 | salmon3:[205,112,84],
569 | salmon4:[139,76,57],
570 | sandybrown:[244,164,96],
571 | seagreen:[46,139,87],
572 | seagreen1:[84,255,159],
573 | seagreen2:[78,238,148],
574 | seagreen3:[67,205,128],
575 | seagreen4:[46,139,87],
576 | seashell:[255,245,238],
577 | seashell1:[255,245,238],
578 | seashell2:[238,229,222],
579 | seashell3:[205,197,191],
580 | seashell4:[139,134,130],
581 | sienna:[160,82,45],
582 | sienna1:[255,130,71],
583 | sienna2:[238,121,66],
584 | sienna3:[205,104,57],
585 | sienna4:[139,71,38],
586 | skyblue:[135,206,235],
587 | skyblue1:[135,206,255],
588 | skyblue2:[126,192,238],
589 | skyblue3:[108,166,205],
590 | skyblue4:[74,112,139],
591 | slateblue:[106,90,205],
592 | slateblue1:[131,111,255],
593 | slateblue2:[122,103,238],
594 | slateblue3:[105,89,205],
595 | slateblue4:[71,60,139],
596 | slategray:[112,128,144],
597 | slategray1:[198,226,255],
598 | slategray2:[185,211,238],
599 | slategray3:[159,182,205],
600 | slategray4:[108,123,139],
601 | slategrey:[112,128,144],
602 | snow:[255,250,250],
603 | snow1:[255,250,250],
604 | snow2:[238,233,233],
605 | snow3:[205,201,201],
606 | snow4:[139,137,137],
607 | springgreen:[0,255,127],
608 | springgreen1:[0,255,127],
609 | springgreen2:[0,238,118],
610 | springgreen3:[0,205,102],
611 | springgreen4:[0,139,69],
612 | steelblue:[70,130,180],
613 | steelblue1:[99,184,255],
614 | steelblue2:[92,172,238],
615 | steelblue3:[79,148,205],
616 | steelblue4:[54,100,139],
617 | tan:[210,180,140],
618 | tan1:[255,165,79],
619 | tan2:[238,154,73],
620 | tan3:[205,133,63],
621 | tan4:[139,90,43],
622 | thistle:[216,191,216],
623 | thistle1:[255,225,255],
624 | thistle2:[238,210,238],
625 | thistle3:[205,181,205],
626 | thistle4:[139,123,139],
627 | tomato:[255,99,71],
628 | tomato1:[255,99,71],
629 | tomato2:[238,92,66],
630 | tomato3:[205,79,57],
631 | tomato4:[139,54,38],
632 | transparent:[255,255,254],
633 | turquoise:[64,224,208],
634 | turquoise1:[0,245,255],
635 | turquoise2:[0,229,238],
636 | turquoise3:[0,197,205],
637 | turquoise4:[0,134,139],
638 | violet:[238,130,238],
639 | violetred:[208,32,144],
640 | violetred1:[255,62,150],
641 | violetred2:[238,58,140],
642 | violetred3:[205,50,120],
643 | violetred4:[139,34,82],
644 | wheat:[245,222,179],
645 | wheat1:[255,231,186],
646 | wheat2:[238,216,174],
647 | wheat3:[205,186,150],
648 | wheat4:[139,126,102],
649 | white:[255,255,255],
650 | whitesmoke:[245,245,245],
651 | yellow:[255,255,0],
652 | yellow1:[255,255,0],
653 | yellow2:[238,238,0],
654 | yellow3:[205,205,0],
655 | yellow4:[139,139,0],
656 | yellowgreen:[154,205,50]
657 | };
658 |
--------------------------------------------------------------------------------
/dozer/media/javascript/excanvas.js:
--------------------------------------------------------------------------------
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 | // TODO: Patterns
16 | // TODO: Radial gradient
17 | // TODO: Clipping paths
18 | // TODO: Coordsize
19 | // TODO: Painting mode
20 | // TODO: Optimize
21 | // TODO: canvas width/height sets content size in moz, border size in ie
22 | // TODO: Painting outside the canvas should not be allowed
23 |
24 | // only add this code if we do not already have a canvas implementation
25 | if (!window.CanvasRenderingContext2D) {
26 |
27 | (function () {
28 |
29 | var G_vmlCanvasManager_ = {
30 | init: function (opt_doc) {
31 | var doc = opt_doc || document;
32 | if (/MSIE/.test(navigator.userAgent) && !window.opera) {
33 | var self = this;
34 | doc.attachEvent("onreadystatechange", function () {
35 | self.init_(doc);
36 | });
37 | }
38 | },
39 |
40 | init_: function (doc, e) {
41 | if (doc.readyState == "complete") {
42 | // create xmlns
43 | if (!doc.namespaces["g_vml_"]) {
44 | doc.namespaces.add("g_vml_", "urn:schemas-microsoft-com:vml");
45 | }
46 |
47 | // setup default css
48 | var ss = doc.createStyleSheet();
49 | ss.cssText = "canvas{display:inline-block;overflow:hidden;" +
50 | "text-align:left;}" +
51 | "canvas *{behavior:url(#default#VML)}";
52 |
53 | // find all canvas elements
54 | var els = doc.getElementsByTagName("canvas");
55 | for (var i = 0; i < els.length; i++) {
56 | if (!els[i].getContext) {
57 | this.initElement(els[i]);
58 | }
59 | }
60 | }
61 | },
62 |
63 | fixElement_: function (el) {
64 | // in IE before version 5.5 we would need to add HTML: to the tag name
65 | // but we do not care about IE before version 6
66 | var outerHTML = el.outerHTML;
67 | var newEl = document.createElement(outerHTML);
68 | // if the tag is still open IE has created the children as siblings and
69 | // it has also created a tag with the name "/FOO"
70 | if (outerHTML.slice(-2) != "/>") {
71 | var tagName = "/" + el.tagName;
72 | var ns;
73 | // remove content
74 | while ((ns = el.nextSibling) && ns.tagName != tagName) {
75 | ns.removeNode();
76 | }
77 | // remove the incorrect closing tag
78 | if (ns) {
79 | ns.removeNode();
80 | }
81 | }
82 | el.parentNode.replaceChild(newEl, el);
83 | return newEl;
84 | },
85 |
86 | /**
87 | * Public initializes a canvas element so that it can be used as canvas
88 | * element from now on. This is called automatically before the page is
89 | * loaded but if you are creating elements using createElement yuo need to
90 | * make sure this is called on the element.
91 | * @param el {HTMLElement} The canvas element to initialize.
92 | */
93 | initElement: function (el) {
94 | el = this.fixElement_(el);
95 | el.getContext = function () {
96 | if (this.context_) {
97 | return this.context_;
98 | }
99 | return this.context_ = new CanvasRenderingContext2D_(this);
100 | };
101 |
102 | var self = this; //bind
103 | el.attachEvent("onpropertychange", function (e) {
104 | // we need to watch changes to width and height
105 | switch (e.propertyName) {
106 | case "width":
107 | case "height":
108 | // coord size changed?
109 | break;
110 | }
111 | });
112 |
113 | // if style.height is set
114 |
115 | var attrs = el.attributes;
116 | if (attrs.width && attrs.width.specified) {
117 | // TODO: use runtimeStyle and coordsize
118 | // el.getContext().setWidth_(attrs.width.nodeValue);
119 | el.style.width = attrs.width.nodeValue + "px";
120 | }
121 | if (attrs.height && attrs.height.specified) {
122 | // TODO: use runtimeStyle and coordsize
123 | // el.getContext().setHeight_(attrs.height.nodeValue);
124 | el.style.height = attrs.height.nodeValue + "px";
125 | }
126 | //el.getContext().setCoordsize_()
127 | }
128 | };
129 |
130 | G_vmlCanvasManager_.init();
131 |
132 | // precompute "00" to "FF"
133 | var dec2hex = [];
134 | for (var i = 0; i < 16; i++) {
135 | for (var j = 0; j < 16; j++) {
136 | dec2hex[i * 16 + j] = i.toString(16) + j.toString(16);
137 | }
138 | }
139 |
140 | function createMatrixIdentity() {
141 | return [
142 | [1, 0, 0],
143 | [0, 1, 0],
144 | [0, 0, 1]
145 | ];
146 | }
147 |
148 | function matrixMultiply(m1, m2) {
149 | var result = createMatrixIdentity();
150 |
151 | for (var x = 0; x < 3; x++) {
152 | for (var y = 0; y < 3; y++) {
153 | var sum = 0;
154 |
155 | for (var z = 0; z < 3; z++) {
156 | sum += m1[x][z] * m2[z][y];
157 | }
158 |
159 | result[x][y] = sum;
160 | }
161 | }
162 | return result;
163 | }
164 |
165 | function copyState(o1, o2) {
166 | o2.fillStyle = o1.fillStyle;
167 | o2.lineCap = o1.lineCap;
168 | o2.lineJoin = o1.lineJoin;
169 | o2.lineWidth = o1.lineWidth;
170 | o2.miterLimit = o1.miterLimit;
171 | o2.shadowBlur = o1.shadowBlur;
172 | o2.shadowColor = o1.shadowColor;
173 | o2.shadowOffsetX = o1.shadowOffsetX;
174 | o2.shadowOffsetY = o1.shadowOffsetY;
175 | o2.strokeStyle = o1.strokeStyle;
176 | }
177 |
178 | function processStyle(styleString) {
179 | var str, alpha = 1;
180 |
181 | styleString = String(styleString);
182 | if (styleString.substring(0, 3) == "rgb") {
183 | var start = styleString.indexOf("(", 3);
184 | var end = styleString.indexOf(")", start + 1);
185 | var guts = styleString.substring(start + 1, end).split(",");
186 |
187 | str = "#";
188 | for (var i = 0; i < 3; i++) {
189 | str += dec2hex[parseInt(guts[i])];
190 | }
191 |
192 | if ((guts.length == 4) && (styleString.substr(3, 1) == "a")) {
193 | alpha = guts[3];
194 | }
195 | } else {
196 | str = styleString;
197 | }
198 |
199 | return [str, alpha];
200 | }
201 |
202 | function processLineCap(lineCap) {
203 | switch (lineCap) {
204 | case "butt":
205 | return "flat";
206 | case "round":
207 | return "round";
208 | case "square":
209 | default:
210 | return "square";
211 | }
212 | }
213 |
214 | /**
215 | * This class implements CanvasRenderingContext2D interface as described by
216 | * the WHATWG.
217 | * @param surfaceElement {HTMLElement} The element that the 2D context should
218 | * be associated with
219 | */
220 | function CanvasRenderingContext2D_(surfaceElement) {
221 | this.m_ = createMatrixIdentity();
222 | this.element_ = surfaceElement;
223 |
224 | this.mStack_ = [];
225 | this.aStack_ = [];
226 | this.currentPath_ = [];
227 |
228 | // Canvas context properties
229 | this.strokeStyle = "#000";
230 | this.fillStyle = "#ccc";
231 |
232 | this.lineWidth = 1;
233 | this.lineJoin = "miter";
234 | this.lineCap = "butt";
235 | this.miterLimit = 10;
236 | this.globalAlpha = 1;
237 | };
238 |
239 | var contextPrototype = CanvasRenderingContext2D_.prototype;
240 | contextPrototype.clearRect = function() {
241 | this.element_.innerHTML = "";
242 | this.currentPath_ = [];
243 | };
244 |
245 | contextPrototype.beginPath = function() {
246 | // TODO: Branch current matrix so that save/restore has no effect
247 | // as per safari docs.
248 |
249 | this.currentPath_ = [];
250 | };
251 |
252 | contextPrototype.moveTo = function(aX, aY) {
253 | this.currentPath_.push({type: "moveTo", x: aX, y: aY});
254 | };
255 |
256 | contextPrototype.lineTo = function(aX, aY) {
257 | this.currentPath_.push({type: "lineTo", x: aX, y: aY});
258 | };
259 |
260 | contextPrototype.bezierCurveTo = function(aCP1x, aCP1y,
261 | aCP2x, aCP2y,
262 | aX, aY) {
263 | this.currentPath_.push({type: "bezierCurveTo",
264 | cp1x: aCP1x,
265 | cp1y: aCP1y,
266 | cp2x: aCP2x,
267 | cp2y: aCP2y,
268 | x: aX,
269 | y: aY});
270 | };
271 |
272 | contextPrototype.quadraticCurveTo = function(aCPx, aCPy, aX, aY) {
273 | // VML's qb produces different output to Firefox's
274 | // FF's behaviour seems to have changed in 1.5.0.1, check this
275 | this.bezierCurveTo(aCPx, aCPy, aCPx, aCPy, aX, aY);
276 | };
277 |
278 | contextPrototype.arc = function(aX, aY, aRadius,
279 | aStartAngle, aEndAngle, aClockwise) {
280 | if (!aClockwise) {
281 | var t = aStartAngle;
282 | aStartAngle = aEndAngle;
283 | aEndAngle = t;
284 | }
285 |
286 | var xStart = aX + (Math.cos(aStartAngle) * aRadius);
287 | var yStart = aY + (Math.sin(aStartAngle) * aRadius);
288 |
289 | var xEnd = aX + (Math.cos(aEndAngle) * aRadius);
290 | var yEnd = aY + (Math.sin(aEndAngle) * aRadius);
291 |
292 | this.currentPath_.push({type: "arc",
293 | x: aX,
294 | y: aY,
295 | radius: aRadius,
296 | xStart: xStart,
297 | yStart: yStart,
298 | xEnd: xEnd,
299 | yEnd: yEnd});
300 |
301 | };
302 |
303 | contextPrototype.rect = function(aX, aY, aWidth, aHeight) {
304 | this.moveTo(aX, aY);
305 | this.lineTo(aX + aWidth, aY);
306 | this.lineTo(aX + aWidth, aY + aHeight);
307 | this.lineTo(aX, aY + aHeight);
308 | this.closePath();
309 | };
310 |
311 | contextPrototype.strokeRect = function(aX, aY, aWidth, aHeight) {
312 | // Will destroy any existing path (same as FF behaviour)
313 | this.beginPath();
314 | this.moveTo(aX, aY);
315 | this.lineTo(aX + aWidth, aY);
316 | this.lineTo(aX + aWidth, aY + aHeight);
317 | this.lineTo(aX, aY + aHeight);
318 | this.closePath();
319 | this.stroke();
320 | };
321 |
322 | contextPrototype.fillRect = function(aX, aY, aWidth, aHeight) {
323 | // Will destroy any existing path (same as FF behaviour)
324 | this.beginPath();
325 | this.moveTo(aX, aY);
326 | this.lineTo(aX + aWidth, aY);
327 | this.lineTo(aX + aWidth, aY + aHeight);
328 | this.lineTo(aX, aY + aHeight);
329 | this.closePath();
330 | this.fill();
331 | };
332 |
333 | contextPrototype.createLinearGradient = function(aX0, aY0, aX1, aY1) {
334 | var gradient = new CanvasGradient_("gradient");
335 | return gradient;
336 | };
337 |
338 | contextPrototype.createRadialGradient = function(aX0, aY0,
339 | aR0, aX1,
340 | aY1, aR1) {
341 | var gradient = new CanvasGradient_("gradientradial");
342 | gradient.radius1_ = aR0;
343 | gradient.radius2_ = aR1;
344 | gradient.focus_.x = aX0;
345 | gradient.focus_.y = aY0;
346 | return gradient;
347 | };
348 |
349 | contextPrototype.drawImage = function (image, var_args) {
350 | var dx, dy, dw, dh, sx, sy, sw, sh;
351 | var w = image.width;
352 | var h = image.height;
353 |
354 | if (arguments.length == 3) {
355 | dx = arguments[1];
356 | dy = arguments[2];
357 | sx = sy = 0;
358 | sw = dw = w;
359 | sh = dh = h;
360 | } else if (arguments.length == 5) {
361 | dx = arguments[1];
362 | dy = arguments[2];
363 | dw = arguments[3];
364 | dh = arguments[4];
365 | sx = sy = 0;
366 | sw = w;
367 | sh = h;
368 | } else if (arguments.length == 9) {
369 | sx = arguments[1];
370 | sy = arguments[2];
371 | sw = arguments[3];
372 | sh = arguments[4];
373 | dx = arguments[5];
374 | dy = arguments[6];
375 | dw = arguments[7];
376 | dh = arguments[8];
377 | } else {
378 | throw "Invalid number of arguments";
379 | }
380 |
381 | var d = this.getCoords_(dx, dy);
382 |
383 | var w2 = (sw / 2);
384 | var h2 = (sh / 2);
385 |
386 | var vmlStr = [];
387 |
388 | // For some reason that I've now forgotten, using divs didn't work
389 | vmlStr.push('
' ,
428 | ' ',
436 | ' ');
437 |
438 | this.element_.insertAdjacentHTML("BeforeEnd",
439 | vmlStr.join(""));
440 | };
441 |
442 | contextPrototype.stroke = function(aFill) {
443 | var lineStr = [];
444 | var lineOpen = false;
445 | var a = processStyle(aFill ? this.fillStyle : this.strokeStyle);
446 | var color = a[0];
447 | var opacity = a[1] * this.globalAlpha;
448 |
449 | lineStr.push('
max.x) {
515 | max.x = c.x;
516 | }
517 | if (min.y == null || c.y < min.y) {
518 | min.y = c.y;
519 | }
520 | if (max.y == null || c.y > max.y) {
521 | max.y = c.y;
522 | }
523 | }
524 | }
525 | lineStr.push(' ">');
526 |
527 | if (typeof this.fillStyle == "object") {
528 | var focus = {x: "50%", y: "50%"};
529 | var width = (max.x - min.x);
530 | var height = (max.y - min.y);
531 | var dimension = (width > height) ? width : height;
532 |
533 | focus.x = Math.floor((this.fillStyle.focus_.x / width) * 100 + 50) + "%";
534 | focus.y = Math.floor((this.fillStyle.focus_.y / height) * 100 + 50) + "%";
535 |
536 | var colors = [];
537 |
538 | // inside radius (%)
539 | if (this.fillStyle.type_ == "gradientradial") {
540 | var inside = (this.fillStyle.radius1_ / dimension * 100);
541 |
542 | // percentage that outside radius exceeds inside radius
543 | var expansion = (this.fillStyle.radius2_ / dimension * 100) - inside;
544 | } else {
545 | var inside = 0;
546 | var expansion = 100;
547 | }
548 |
549 | var insidecolor = {offset: null, color: null};
550 | var outsidecolor = {offset: null, color: null};
551 |
552 | // We need to sort 'colors' by percentage, from 0 > 100 otherwise ie
553 | // won't interpret it correctly
554 | this.fillStyle.colors_.sort(function (cs1, cs2) {
555 | return cs1.offset - cs2.offset;
556 | });
557 |
558 | for (var i = 0; i < this.fillStyle.colors_.length; i++) {
559 | var fs = this.fillStyle.colors_[i];
560 |
561 | colors.push( (fs.offset * expansion) + inside, "% ", fs.color, ",");
562 |
563 | if (fs.offset > insidecolor.offset || insidecolor.offset == null) {
564 | insidecolor.offset = fs.offset;
565 | insidecolor.color = fs.color;
566 | }
567 |
568 | if (fs.offset < outsidecolor.offset || outsidecolor.offset == null) {
569 | outsidecolor.offset = fs.offset;
570 | outsidecolor.color = fs.color;
571 | }
572 | }
573 | colors.pop();
574 |
575 | lineStr.push(' ');
582 | } else if (aFill) {
583 | lineStr.push(' ');
584 | } else {
585 | lineStr.push(
586 | ' '
593 | );
594 | }
595 |
596 | lineStr.push(" ");
597 |
598 | this.element_.insertAdjacentHTML("beforeEnd", lineStr.join(""));
599 |
600 | this.currentPath_ = [];
601 | };
602 |
603 | contextPrototype.fill = function() {
604 | this.stroke(true);
605 | }
606 |
607 | contextPrototype.closePath = function() {
608 | this.currentPath_.push({type: "close"});
609 | };
610 |
611 | /**
612 | * @private
613 | */
614 | contextPrototype.getCoords_ = function(aX, aY) {
615 | return {
616 | x: (aX * this.m_[0][0] + aY * this.m_[1][0] + this.m_[2][0]),
617 | y: (aX * this.m_[0][1] + aY * this.m_[1][1] + this.m_[2][1])
618 | }
619 | };
620 |
621 | contextPrototype.save = function() {
622 | var o = {};
623 | copyState(this, o);
624 | this.aStack_.push(o);
625 | this.mStack_.push(this.m_);
626 | this.m_ = matrixMultiply(createMatrixIdentity(), this.m_);
627 | };
628 |
629 | contextPrototype.restore = function() {
630 | copyState(this.aStack_.pop(), this);
631 | this.m_ = this.mStack_.pop();
632 | };
633 |
634 | contextPrototype.translate = function(aX, aY) {
635 | var m1 = [
636 | [1, 0, 0],
637 | [0, 1, 0],
638 | [aX, aY, 1]
639 | ];
640 |
641 | this.m_ = matrixMultiply(m1, this.m_);
642 | };
643 |
644 | contextPrototype.rotate = function(aRot) {
645 | var c = Math.cos(aRot);
646 | var s = Math.sin(aRot);
647 |
648 | var m1 = [
649 | [c, s, 0],
650 | [-s, c, 0],
651 | [0, 0, 1]
652 | ];
653 |
654 | this.m_ = matrixMultiply(m1, this.m_);
655 | };
656 |
657 | contextPrototype.scale = function(aX, aY) {
658 | var m1 = [
659 | [aX, 0, 0],
660 | [0, aY, 0],
661 | [0, 0, 1]
662 | ];
663 |
664 | this.m_ = matrixMultiply(m1, this.m_);
665 | };
666 |
667 | /******** STUBS ********/
668 | contextPrototype.clip = function() {
669 | // TODO: Implement
670 | };
671 |
672 | contextPrototype.arcTo = function() {
673 | // TODO: Implement
674 | };
675 |
676 | contextPrototype.createPattern = function() {
677 | return new CanvasPattern_;
678 | };
679 |
680 | // Gradient / Pattern Stubs
681 | function CanvasGradient_(aType) {
682 | this.type_ = aType;
683 | this.radius1_ = 0;
684 | this.radius2_ = 0;
685 | this.colors_ = [];
686 | this.focus_ = {x: 0, y: 0};
687 | }
688 |
689 | CanvasGradient_.prototype.addColorStop = function(aOffset, aColor) {
690 | aColor = processStyle(aColor);
691 | this.colors_.push({offset: 1-aOffset, color: aColor});
692 | };
693 |
694 | function CanvasPattern_() {}
695 |
696 | // set up externs
697 | G_vmlCanvasManager = G_vmlCanvasManager_;
698 | CanvasRenderingContext2D = CanvasRenderingContext2D_;
699 | CanvasGradient = CanvasGradient_;
700 | CanvasPattern = CanvasPattern_;
701 |
702 | })();
703 |
704 | } // if
--------------------------------------------------------------------------------