1 /* Documentation Note:
  2  *   Public methods and properties are commented with /** some text *\/
  3  *   and private methods and properties are commented with //
  4  *   
  5  *   Please leave it that way to keep this documentation sane
  6  */
  7 
  8 
  9 /*
 10 *	Karma Framework
 11 *	http://karmaeducation.org
 12 *	
 13 *	Copyright (c)  2009
 14 *	Bryan W Berry		bryan@olenepal.org
 15 * 	Felipe López Toledo	zer.subzero@gmail.com
 16 *      
 17 *	Under MIT License:
 18 *	Permission is hereby granted, free of charge, to any person
 19 *	obtaining a copy of this software and associated documentation
 20 *	files (the "Software"), to deal in the Software without
 21 *	restriction, including without limitation the rights to use,
 22 *	copy, modify, merge, publish, distribute, sublicense, and/or sell
 23 *	copies of the Software, and to permit persons to whom the
 24 *	Software is furnished to do so, subject to the following
 25 *	conditions:
 26 *	
 27 *	The above copyright notice and this permission notice shall be
 28 *	included in all copies or substantial portions of the Software.	
 29 * 
 30 *	THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
 31 *	EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
 32 *	OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
 33 *	NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
 34 *	HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
 35 *	WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 36 *	FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
 37 *	OTHER DEALINGS IN THE SOFTWARE.
 38 */
 39 
 40 /**
 41 * @fileOverview Contains karma library
 42 * @author Bryan Berry <bryan@olenepal.org> 
 43 * @author Felipe Lopez Toledo <zer.subzero@gmail.com>
 44 */
 45 
 46 
 47 //common.js modules use exports object
 48 if(!this.exports) {
 49     exports = {};
 50 }
 51 
 52 
 53 
 54 /** Karma is the namespace for the Karma library and Karma() is the constructor 
 55  * function for the Karma library object Karma. 
 56  * Karma() checks if the current document type is set to HTML 5, throws
 57  * an error if not. Otherwise, initializes the karma object and returns
 58  * a reference to that object.
 59  * @namespace Global namespace for Karma library
 60  * @constructor
 61  * @param {Object} [options={}] options for intializing Karma library
 62  * @param {String} [options.locale=''] sets current locale Not Yet Implemented
 63  * @param {Array} [options.image=[]] array of images to be converted into a collection
 64  * @param {Array} [options.audio=[]] array of audio to be converted into a collection
 65  * @param {Array} [options.video=[]] NYI array of videos to be converted into a collection
 66  * @param {Array} [options.svg=[]] array of SVG elements to be 
 67  * converted into a collection. Each SVG element must already exist in the html document
 68  * @param {Array} [options.canvas=[]] array of canvas elements 
 69  * to be converted into a collection. Each canvas element must already exist in the 
 70  * html document and width and height of each element must be set as attributes
 71  * @throws {Error} if the document type declaration is not set to HTML 5, e.g. 
 72  * <!DOCTYPE html>
 73  * @throws {Error} If any of the initialization parameters are invalid values
 74  * @returns {Object} Karma -- reference to the initialized Karma library
 75  * @example
 76  * 
 77  * var k = Karma({ 
 78  *                 image: [ 
 79  *                    {name: "ninja", file: "ninja.png"}, 
 80  *                    {name: "cowboy", file: "cowboy.png"}
 81  *                         ],
 82  *                 audio: [
 83  *                    {name: "woosh", file: "woosh.ogg"},
 84  *                    {name: "yeehaw", file: "yeehaw.ogg"}
 85  *                         ],
 86  *                 video: [  //Not Yet Implemented
 87  *                    {name: "attack", file: "attack.ogv"},
 88  *                    {name: "ride", file: "ride.ogv"}
 89  *                         ]
 90  *                 canvas: [
 91  *                    {name: "ninja", domId: "ninjaCanvas"},
 92  *                    {name: "cowboy", domId: "cowboyCanvas"}
 93  *                         ],
 94  *                 svg: [ 
 95  *                    {name: "ninja", domId: "ninjaSvg"},
 96  *                    {name: "cowboy", domId: "cowboySvg"}
 97  *                         ],
 98  *                 });
 99  * Next, call the ready function with a callback to your program code
100  * 
101  * k.ready(function () { ... your application code . . . }                       
102  * 
103  * after that you can access each asset like so
104  * k.image.ninja;
105  * k.svg.cowboy;
106  * k.audio.yeehaw.play();
107  * k.canvas.ninja.drawImage(k.image.ninja, 0, 0);
108  * 
109  */	
110 var Karma = exports.Karma  = function (options) {
111     Karma._isHtml5(document.doctype.nodeName);
112 
113     if ( Karma._initialized === true ) {
114 	return Karma;
115     } else {
116 	return Karma._init(options);
117     }
118 };
119 
120 
121 //helper functions
122 
123 /**This emulates the Object.create method in ecmascript 5 spec
124  * This isn't a full implementation as it doesn't support an all of Object.create's features
125  * This has the same functionality as Crockford's beget method
126  * and this primary building block for prototypal inheritance in
127  * this library
128  * @param {Object} parent that the new object's prototype should point to
129  * @returns {Object} a new object whose prototype is parent
130  * @example
131  * 
132  * var ninja = { weapon : "sword" };
133  * var ninja1 = Karma.create(ninja);
134  * ninja1.weapon === "sword"
135  */
136 Karma.create = function (parent){
137     function F () {};
138     F.prototype = parent;
139     return new F();
140 };
141 
142 /** Returns a shallow copy of the passed in object
143  * @param {Object} target to be copied
144  * @returns {Object} a shallow copy of target
145  */
146 Karma.clone = function (target){
147     var copy = {};
148     for ( var i in target ) {
149 	if(target.hasOwnProperty(i)){
150 	    copy[i] = target[i];
151 	}
152     }
153     return copy;
154 };
155 
156 /** Extends properties of the target object with those of 
157  * the source object
158  * @param {Object} target object to be extended 
159  * @param {Object} source whose properties will extend target
160  * @returns {Object} target extended by source
161  */
162 Karma.objectPlus = function (target, source){
163     for ( var i in source){
164 	if (source.hasOwnProperty(i)){
165 	    target[i] = source[i];
166 	}
167     }
168     return target;
169 };
170 
171 Karma.extend = Karma.objectPlus;
172 
173 /** Creates a new object that is a prototype of the first argument
174  * then extends it with the properties of the second argument
175  * @param {Object} parent1 will be prototype of returned object
176  * @param {Object} parent2 will extend properties of returned object
177  * @returns {Object} object that whose prototype is parent1 and has 
178  * been extended with properties of parent2
179  */ 
180 Karma.copyObjectPlus = function (parent1, parent2){
181     function F () {};
182     F.prototype = parent1;
183     var G = new F();
184     return Karma.objectPlus(G, parent2);
185 };
186 
187 
188 //Throws big ugly error if doctype isn't html5
189 Karma._isHtml5 = function (doctype){
190     var regex = new RegExp('^html$', 'i');
191     if(!regex.test(doctype)){
192 	var errorMsg =  "ERROR: The doctype must be set to <!DOCTYPE html> " +
193 	    "in order to use Karma. Karma require you use html5";
194 	var errorElem = document.createElement('div');
195 	errorElem.setAttribute('id', 'errorDoctype');
196 	errorElem.innerText = errorMsg;
197 	document.body.appendChild(errorElem);
198 	   throw new Error(errorMsg);
199 	}
200 };
201 
202 /**
203  * Shuffles an array of items randomly
204  * @param {Array} oldList of choices to be shuffled
205  * @returns {Array} newlist of choices randomly reordered 
206  */
207 Karma.shuffle = function (oldList) {
208     var newList = oldList.slice(0);
209     for (var i = newList.length - 1; i > 0; i -= 1) {
210         var j = Karma.rand(0, i);
211         var t = newList[i];
212         newList[i] = newList[j];
213         newList[j] = t;
214     }
215     return newList;
216 };
217 
218 
219 /**
220  * Converts a number to numerals in the specified locale. Currently only
221  * supports Nepali
222  * @param {Number} Number to be converted
223  * @param {locale} locale that number should be converted to
224  * @returns {String} Unicode string for localized numeral 
225  */
226 Karma.convertNumToLocale = function(num, locale){
227     locale = locale || Karma.locale;
228     //48 is the base for western numerals
229     var convertDigit = function(digit){
230 	
231 	var numBase = 48;
232 	var prefix = "u00";
233 	
234 	if (locale === "ne"){
235 	    prefix = "u0";
236 	    numBase = 2406;
237 	}
238 	
239 	return '\\' + prefix + 
240 	    (numBase + parseInt(digit)).toString(16);
241     };
242     
243     var charArray = num.toString().split("").map(convertDigit);
244     return eval('"' + charArray.join('') + '"');
245 };
246 
247 /**
248  * @name Karma._n
249  * @function
250  * @public
251  * Alias for Karma.convertNumToLocale. Converts a number to numerals to 
252  * Karma.locale or to specified locale. Currently only supports Nepali
253  * @param {Number} Number to be converted
254  * @param {locale} locale that number should be converted to
255  * @returns {String} Unicode string for localized numeral 
256  */
257 Karma._n = Karma.convertNumToLocale;
258 
259 /* Scales the dimensions of document.body to the innerHeight and innerWidth
260  * of the viewport, i.e. browser window, with a minor offset to the height to 
261  * make sure the scrollbars do not appear
262  */
263 Karma.scaleToViewport = function(){
264     var width = window.innerWidth;
265     var height = window.innerHeight;
266     
267     //hack to ensure scrollbars don't appear
268     if (height === 900){
269 	height = "" + 900 + "px";
270     } else {
271 	height = "" + (height - 13) + "px";
272     }
273     
274     document.body.style.width = "" + width + "px";
275     document.body.style.height = height;
276 };
277 
278 Karma.scaleWindow = function(){
279     var width = "1200px";
280     var height = "900px";
281     var viewportHeight = "760px";
282     var $body = $('body');
283     var $kMain = $('#kMain');
284 
285     if (window.innerWidth < 1150){
286 	width = "950px";
287 	height = "600px";
288 	viewportHeight = "460px";
289 	$body.css('border', '2px solid black');
290 
291 	//  460/760 * 16 = 9.6
292 	$kMain.css('font-size', '9.6px');
293     } 
294 
295     $body.css({border: '2px solid black', width: width, height: height});
296     $kMain.css({width: width, height: viewportHeight});
297 
298 
299 };
300 
301     // Below are geometry and math helper methods
302     
303 /**
304  * Converts a value from degrees to radians.
305  * @param {Number} angle The angle in degrees
306  * @returns {Number} The angle in radians 
307  */
308 Karma.radians = function( angle ){
309 	return ( angle / 180 ) * Math.PI;
310 };
311 
312 /**
313  *  Gets the square of the Euclidian (ordinary) distance between 2 points.
314  * @param {Object} Point No. 0
315  * @param {Number} Point0.x
316  * @param {Number} Point0.y
317  * @param {Object} Point No. 1
318  * @param {Number} Point1.x
319  * @param {Number} Point1.y
320  * @returns {Number} The square of the Euclidian distance 
321  * @example
322  * 
323  * p0 = {x:0, y:1};
324  * p1 = {x:50, y:70};
325  * var d = distance2(p0, p1);
326  * 
327  */
328 Karma.distance2 = function ( p0, p1 ) {
329     return   (p1.x - p0.x) * (p1.x - p0.x) + (p1.y - p1.y) * (p1.y - p1.y); 
330 };
331 
332 /**
333  * Gets the Euclidian (ordinary) distance between 2 points.<br>
334  * <b>Warning:</b> It's slower than distance2 function
335  * @param {Object} Point No. 0
336  * @param {Number} Point0.x
337  * @param {Number} Point0.y
338  * @param {Object} Point No. 1
339  * @param {Number} Point1.x
340  * @param {Number} Point1.y
341  * @returns {Number} The Euclidian distance 
342  * @example
343  * 
344  * p0 = {x:0, y:1};
345  * p1 = {x:50, y:70};
346  * var d = distance2(p0, p1);
347  * 
348  */
349 Karma.distance = function ( p0, p1 ) {
350 	return   Math.sqrt( this.distance2( p0, p1 ) ); 
351 };
352 
353 /** Returns a random number within the range provided
354  * @param {Number} lower limit of the range, lowest number that can be returned
355  * @param {Number} upper limit of the range, highest number that can be returned
356  * @returns {Number} number that is >= lower and <= upper
357  * @example
358  * 
359  * var num = rand(0, 10);
360  * 
361  * //num could be 0, 1, 2, 3 ... or 10
362  * 
363  */
364 Karma.rand = function ( lower, upper ){
365   return Math.floor(Math.random() * (upper - lower + 1) + lower);  
366 };
367 
368 
369 Karma.extend(Karma, {      
370     /** This is the global locale as passed to Karma(),
371      * such as "en", "es_SP"
372      * @fieldOf Karma
373      * @property {string} locale This is the global locale as passed to Karma()
374      * @default 'en'
375      */
376     locale : 'en',
377     /** Collection of images with special helper
378      * methods added to each reference
379      * @fieldOf Karma
380      * @type object
381      * @default empty object
382      */
383     image : {},
384     /** Collection of audio files with special helper
385      * methods added to each reference
386      * @fieldOf Karma
387      * @type object
388      * @default empty object
389      */
390     audio : {},
391     /** Collection of html 5 canvases with special helper
392      * methods added to each reference
393      * @fieldOf Karma
394      * @type object
395      * @default empty object
396      */
397     canvas : {},
398     /** Collection of svgs with special helper
399      * methods added to each reference
400      * @fieldOf Karma
401      * @type object
402      * @default empty object
403      */
404     svg : {},
405     /** Collection of videos with special helper
406      * methods added to each reference
407      * @fieldOf Karma
408      * @type object
409      * @default empty object
410      */
411     video : {},
412     _localized : false,
413     _assetPath : "assets/",
414     _localePath : "",
415     _initialized : false,
416     _statusDiv: undefined,
417     _loaderDiv : undefined,
418     _counters : { total : 0, errors : 0, loaded : 0},
419 
420     //This constructs the Karma object per values provided by the user
421     _init: function(options) {
422 	this._initialized = true;
423 	
424 	//set up message that show count of assets loaded
425 	//and has an ordered list to append error messages to
426 	var _statusDiv = this._statusDiv = document.createElement('div');
427 	this._loaderDiv = this._loaderDiv = document.createElement('div');	
428 	var errorList = document.createElement('ol');
429 
430 	_statusDiv.setAttribute('id', 'karma-status');
431 	_statusDiv.setAttribute('style', 'position:absolute;');
432 	_statusDiv.innerHTML = 'Karma is loading ...';
433 	this._loaderDiv.setAttribute('id', 'karma-loader');
434 	this._loaderDiv.setAttribute('class', 'status');
435 	errorList.setAttribute('id', 'errorList');
436 
437 	_statusDiv.appendChild(this._loaderDiv);
438 	this._statusDiv.appendChild(errorList);
439 	document.body.appendChild(_statusDiv);
440 
441 	//regular expression that matches the name of aprivate property
442 	// the karma object
443 	var regexPrivate = new RegExp('^_.*');
444 	
445 	for ( var option in options ) {
446 	    if (options.hasOwnProperty(option)){
447 		if (option === "image" || option === "audio" || option === 
448 		    "svg" || option === "video" || option === "canvas"){ 
449 		    
450 		    if(!(options[option] instanceof Array)){
451 			throw new Error("" + option + " must be an array");
452 		    } else if (options[option].length === 0){
453 			continue;
454 		    }
455 		} else if (regexPrivate.test(option)){
456 		    //don't overwrite a private property of karma object
457 		    continue;
458 		}
459 		
460 		switch (option){
461 		case "locale":
462 
463 		    if (this._isValidLocale(options[option])){
464 			this.locale = this._normalizeLocale(options[option]);
465 			this._localized = true;
466 			this._localePath = Karma._computeLocalePath(this.locale);
467 		    } else {
468 			throw new Error("locale provided to karma._init() is invalid");
469 		    }
470 		    
471 		    break;
472 		case "image":
473 		    options[option]._type = 'image';
474 		    Karma._makeCollection(options[option], 'image');
475 		    break;
476 		case "audio":
477 		    options[option]._type = 'audio';
478 		    Karma._makeCollection(options[option], 'audio');
479 		    break;
480 		case "video":
481 		    options[option]._type = 'video';
482 		    Karma._makeCollection(options[option], 'video');
483 		    break;
484 		case "svg":
485 		    options[option]._type = 'svg';
486 		    Karma._makeCollection(options[option], 'svg');
487 		    break;
488 		case "canvas":
489 		    options[option]._type = 'canvas';
490 		    Karma._makeCollection(options[option], 'canvas');
491 		    break;
492 		}
493 	    }
494 	}
495 
496 
497 
498 	return this;
499     },
500     
501     /** Waits until all assets loaded(ready), then calls callback cb
502      * @memberOf Karma
503      * @param {Function} [cb] callback function
504      * @returns this
505      * @throws {Error} if Karma is not initialized with the 
506      * Karma({ options }) function
507      * @example
508      * 
509      * var k = Karma({ . . . your assets here . . . });
510      * k.ready(function(){ .. your code here . . .});
511      * 
512      * your code will not be called until all assets have been loaded
513      * into collections
514      * 
515      */
516     ready : function( cb ) {
517 	var that = this;
518 	if (Karma._initialized !== true){
519 	    throw new Error("Karma not initialized");
520 	}
521 
522 	if (this._counters.loaded !== this._counters.total){
523 	    setTimeout(function(){ that.ready(cb);}, 5);
524 	} else if (cb) {
525 	    //hide the "Karma is loading..." message
526 	    this._statusDiv.setAttribute('style', 'display:none;');
527 
528 	     cb();
529 	} else if (!cb) {
530 	    //hide the "Karma is loading..." message
531 	    this._statusDiv.setAttribute('style', 'display:none;');
532 	    
533 	    //if no options passed, show it works message
534 	    this._showStarterMessage();
535 	}
536 	
537 	
538 	   
539 
540 	return this;
541     },
542 
543     //Display Apache-like "It works" message if no options
544     _showStarterMessage : function (){
545 	var starterMsg = document.createElement('div');
546 	starterMsg.setAttribute('id', 'starterMsg');
547 	starterMsg.innerHTML = "<h1>It Works</h1>";
548 	document.body.appendChild(starterMsg);
549     },
550 
551     //Updates visible counter of how many assets are loaded
552     _updateStatus : function (errorMsg) {
553 	var loaded = this._counters.loaded;
554 	var total = this._counters.total;
555 	var errors = this._counters.errors;
556 	this._loaderDiv.innerHTML = "Loaded " + loaded + " / " + total + 
557 	    "" + (errors > 0 ? " Errors [ " + errors +" ]" : '');
558 	if (errorMsg) {
559 	    var liError = document.createElement('li');
560 	    liError.innerHTML = errorMsg;
561 	    var errorList = document.getElementById('errorList');
562 	    errorList.appendChild(liError);  
563 	}
564     },	    
565 
566     //matches 2 letter country code then optionally
567     //a dash or underscore followed by a country or language identifier
568     //i currently only allow a language identifier 2-3 chars long
569     _isValidLocale : function (locale) {
570 	var localeRegex = new RegExp('^[a-zA-Z][a-zA-Z]([-_][a-zA-z]{2,3})?$');
571 	return localeRegex.test(locale);
572     },
573 
574     _normalizeLocale : function(locale) {
575 	var lang = "";
576 	var country = "";
577 	var divider = "";
578 
579 	lang = locale.slice(0, 2).toLowerCase();
580 	divider = "_";
581 	country = locale.slice(3, 6).toUpperCase();
582 	
583 	return locale.length > 2 ? "" + lang + divider + country : lang;
584     },
585     
586 
587     
588 });
589 
590 //Helper functions for creating assets
591 Karma._isLocalized = function (boolLocalized) {
592     if (typeof boolLocalized === "boolean" ) {
593 	if(boolLocalized === true && 
594 	   Karma.locale === undefined){
595 	    throw new Error("You cannot localize a media asset" +
596 			    " if the global locale for Karma isn't set");
597 	} else {
598 	    return boolLocalized;
599 	}
600     } else if (typeof boolLocalized === undefined){
601 	return false;
602     } else{ 
603 	throw new Error("This is not a valid value for the localized option");
604     }
605 };
606 
607 Karma._computeLocalePath = function(locale) {
608     return Karma._assetPath + locale + "/";
609 };
610 
611 
612 
613 
614 Karma._makeCollection = function (configs, type){
615     var makeAsset = function (config){
616 	var asset = undefined;
617 	var target = undefined;
618 	switch(type){
619 	    case "image":
620 		target = Karma.kImage;
621 		break;
622 	    case "audio":
623 		target = Karma.kAudio;
624 		break;
625 	    case "video":
626 		target = Karma.kVideo;
627 		break;
628 	    case "svg":
629 		target = Karma.kSvg;
630 		break;
631 	    case "canvas":
632 		target = Karma.kCanvas;
633 		break;
634 	}
635 
636 	asset = Karma.create(target)._init(config);
637 	Karma[type][config.name] = asset;
638     };
639 		       
640     configs.forEach(function(config){ makeAsset(config);});
641 };
642 
643 
644 
645 
646 
647 //Prototype objects for assets
648 
649 
650 /** Prototype object for images
651  *  @class This object is the prototype for images submitted to Karma in the
652  *  Karma() method
653  *  @ throws {Error} if the image asset is set to be localized but 
654  *  the global locale is not set on the Karma object
655  *  @ throws {Error} if the name and file properties are not supplied
656  *  @example
657  *  kImage is the prototype object for images. This 'media' asset is loaded 
658  *  in a distinctly different way from the canvas or svg assets.    
659  *
660  */
661 Karma.kImage = 
662     {
663     /** file location of image
664      * @type String
665      * @default ""
666      */
667     file : "",
668     /** media object
669      * @type Image
670      * @default undefined 
671      */	
672     media : undefined,
673     //actual path to the file
674     _path : "",
675     //if using localized version of this image
676     _localized : false,
677     _type : "image", 
678     //initializes kImage instance with values provided by user
679     _init : function (image) {
680 	image._localized = image._localized || false;
681 	Karma._counters.total++;
682 
683 	if (image.name === undefined || image.file === undefined){
684 	    throw new Error("properties name and file have to be defined");
685 	} else {
686 	    this.name = image.name;
687 	    this.file = image.file;
688 	}
689 
690 	this.media = new Image(); 
691 	
692 	if(Karma._isLocalized(image._localized)){
693 	    this._localized = image._localized;
694 	    this._path = Karma._localePath + "image/";
695 	} else {
696 	    this._path = Karma._assetPath + "image/";
697 	}
698 
699 	//IMPORTANT: This one magic line loads the file
700 	this.media.src = this.src = this._path + this.file;
701 	
702 	//add event handlers
703 	this._addEventHandlers();
704 
705 	
706 	return this;
707     },
708     //Adds event handlers to update the counters when 
709     //the image is successfully or unsuccessfully loaded
710     _addEventHandlers : function () {
711 	var that = this;
712 
713 	that.media.addEventListener(
714 	    "load", 
715 	    function (e) { 
716 		Karma._counters.loaded++;
717 		Karma._updateStatus();
718 		that.status = "loaded";}, false);
719 	
720 	that.media.addEventListener(
721 	    "error", 
722 	    function (e) { 
723 		Karma._counters.errors++;
724 		that.status = "error";
725 		var errorMsg = "Error: " + that._type.toUpperCase() +
726 		    " " + that.name + " cannot be loaded."; 
727 		Karma._updateStatus(errorMsg);
728 	    }, 
729 	    false);
730 	that.media.addEventListener(
731 	    "abort", 
732 	    function (e) { 
733 		Karma._counters.total++;
734 		that.status = "aborted";
735 		var errorMsg = "ABORT: " + that._type.toUpperCase() +
736 		    " " + that.name + " loading was aborted."; 
737 		Karma._updateStatus(errorMsg);
738 
739 	    }, false);
740     }
741     
742 };
743 
744 /** Prototype object for audio files 
745  *  @class This object is the prototype for audio files submitted to Karma in the
746  * Karma() method
747  *  @ throws {Error} if the individual audio asset is set to be localized but 
748  *  the globale locale is not set on the Karma object
749  *  @ throws {Error} if the name and file properties are not supplied
750  *  @example
751  *  kAudio is the prototype object for audio
752  *  The audio assets are loaded in a distinctly different way
753  *  from the canvas or svg assets. They also have distinctly different
754  *  helper methods 
755  *  
756  *  You initialize the kAudio assets by passing an array of objects
757  */
758 Karma.kAudio = {
759     /** file location of asset
760      * @type String
761      * @default ""
762      */
763     file : "",
764     /**  Media object. You can access the src, autobuffer, autoplay, loop, and 
765      * controls attributes 
766      * via the media property of kAudio. Read more about the properties of the <a href="http://www.whatwg.org/specs/web-apps/current-work/multipage/video.html#media-element-attributes">HTML 5 media element</a>
767      * @type Audio
768      * @default undefined
769      */	
770     media : undefined,
771     //actual path to the file
772     _path : "",
773     //if using localized version of this asset
774     _localized : false,
775     _type : "audio", 
776     //initializes kAudio instance with values provided by user
777     _init : function (audio) {
778 	audio._localized = audio._localized || false;
779 	Karma._counters.total++;
780 
781 	if (audio.name === undefined || audio.file === undefined){
782 	    throw new Error("properties name and file have to be defined");
783 	} else {
784 	    this.name = audio.name;
785 	    this.file = audio.file;
786 	}
787 
788 	this.media = new Audio(); 
789 	
790 	if(Karma._isLocalized(audio._localized)){
791 	    this._localized = audio._localized;
792 	    this._path = Karma._localePath  + "audio/";
793 	} else {
794 	    this._path = Karma._assetPath + "audio/";
795 	}
796 
797 
798 	//IMPORTANT: This one magic line loads the file
799 	this.media.src = this.src = this._path + this.file;
800 	
801 	//add event handlers
802 	this._addEventHandlers();
803 
804 	this.media.autobuffer = true;
805 	this.media.load();
806 
807 	
808 	return this;
809     },
810     //Adds event handlers to update the counters when 
811     //the asset is successfully or unsuccessfully loaded
812     _addEventHandlers : function () {
813 	var that = this;
814 	//'canplaythrough' event is a Browser Hack recommended by chromium devs
815 	//http://code.google.com/p/chromium/issues/detail?id=20251&q=loading%20audio&colspec=ID%20Stars%20Pri%20Area%20Type%20Status%20Summary%20Modified%20Owner%20Mstone%20OS#c4
816 
817 	that.media.addEventListener(
818 	    "canplaythrough", 
819 	    function (e) { 
820 		Karma._counters.loaded++;
821 		Karma._updateStatus();
822 		that.status = "loaded";}, false);
823 	
824 	that.media.addEventListener(
825 	    "error", 
826 	    function (e) { 
827 		Karma._counters.errors++;
828 		that.status = "error";
829 		var errorMsg = "Error: " + that._type.toUpperCase() +
830 		    " " + that.name + " cannot be loaded."; 
831 		Karma._updateStatus(errorMsg);
832 	    }, 
833 	    false);
834 	that.media.addEventListener(
835 	    "abort", 
836 	    function (e) { 
837 		Karma._counters.total++;
838 		that.status = "aborted";
839 		var errorMsg = "ABORT: " + that._type.toUpperCase() +
840 		    " " + that.name + " loading was aborted."; 
841 		Karma._updateStatus(errorMsg);
842 
843 	    }, false);
844 
845     },
846     /** Plays the audio file  */
847     play : function () {
848 	    this.media.play();  
849     }
850     
851 };
852 
853 /** NYI:Prototype object for Video files 
854  *  @class Not Yet Implemented:This object is the prototype for video files submitted 
855  * to Karma in the Karma() method
856  *  @ throws {Error} if the individual video asset is set to be localized but 
857  *  the globale locale is not set on the Karma object
858  *  @ throws {Error} if the name and file properties are not supplied
859  */
860 Karma.kVideo = {
861     /** file location of asset
862      * @type String
863      * @default ""
864      */
865     file : "",
866     /** media object
867      * @type Video
868      * @default undefined 
869      */	
870     media : undefined,
871     //actual path to the file
872     _path : "",
873     //if using localized version of this asset
874     _localized : false,
875     _type : "video", 
876     //initializes kVideo instance with values provided by user
877     _init : function (video) {
878 	//Not Yet Implemented
879 	Karma._counters.errors++;
880 	throw new Error("Video is not Yet Implemented");
881 
882 	video._localized = video._localized || false;
883 	Karma._counters.total++;
884 
885 	if (video.name === undefined || video.file === undefined){
886 	    throw new Error("properties name and file have to be defined");
887 	} else {
888 	    this.name = video.name;
889 	    this.file = video.file;
890 	}
891 
892 	this.media = new Video(); 
893 	
894 	if(Karma._isLocalized(video._localized)){
895 	    this._localized = video._localized;
896 	    this._path = Karma._localePath  + "video/";
897 	} else {
898 	    this._path = Karma._assetPath + "video/";
899 	}
900 
901 
902 	//IMPORTANT: This one magic line loads the file
903 	this.media.src = this.src = this._path + this.file;
904 	
905 	//add event handlers
906 	this._addEventHandlers();
907 
908 	return this;
909     },
910     //Adds event handlers to update the counters when 
911     //the asset is successfully or unsuccessfully loaded
912     _addEventHandlers : function () {
913 	var that = this;
914 	//'canplaythrough' event is a Browser Hack recommended by chromium devs
915 	//http://code.google.com/p/chromium/issues/detail?id=20251&q=loading%20audio&colspec=ID%20Stars%20Pri%20Area%20Type%20Status%20Summary%20Modified%20Owner%20Mstone%20OS#c4
916 
917 	that.media.addEventListener(
918 	    "canplaythrough", 
919 	    function (e) { 
920 		Karma._counters.loaded++;
921 		Karma._updateStatus();
922 		that.status = "loaded";}, false);
923 	
924 	that.media.addEventListener(
925 	    "error", 
926 	    function (e) { 
927 		Karma._counters.errors++;
928 		that.status = "error";
929 		var errorMsg = "Error: " + that._type.toUpperCase() +
930 		    " " + that.name + " cannot be loaded."; 
931 		Karma._updateStatus(errorMsg);
932 	    }, 
933 	    false);
934 	that.media.addEventListener(
935 	    "abort", 
936 	    function (e) { 
937 		Karma._counters.total++;
938 		that.status = "aborted";
939 		var errorMsg = "ABORT: " + that._type.toUpperCase() +
940 		    " " + that.name + " loading was aborted."; 
941 		Karma._updateStatus(errorMsg);
942 
943 	    }, false);
944 
945     }
946     
947 };
948 
949 
950 
951 /** Prototype object for each canvas element submitted to Karma in the
952  * Karma() method
953  * @throws {Error} if the name and domId for the canvas element are not specified
954  * @thows {Error} if the supplied domId does not match an element in the DOM
955  * @class This object is the prototype for each canvas element submitted to Karma in the
956  * Karma() method
957  */
958 Karma.kCanvas = {
959     /** Name of the canvas, used internally by karma.js
960      * @type String
961      * @default ''
962      */
963     name : '',
964     /** Width of canvas element
965      * @type Number
966      * @default 0
967      */
968     width: 0,
969     /** Height of canvas element
970      * @type Number
971      * @default 0
972      */
973     height: 0,
974     /**  Whether canvas is visible
975      * @type boolean
976      * @default true
977      */
978     visible: true,
979     /** Element ID for canvas element in html document. This value is read-only
980      * @type String
981      * @default undefined
982      */
983     domId: undefined,
984     /** Reference to the DOM element
985      * @type DOMElement
986      * @default undefined
987      * @example
988      * //You can access all properties and methods of the underlying DOM element
989      * //using the 'node' property
990      * Karma.canvas.someCanvas.node.dispatchEvent( ... some event ...);
991      * var stuff = Karma.canvas.someCanvas.node.innerHTML;
992      * 
993      */
994     node: undefined,
995     /** The 2 Dimensional Rendering context property for this canvas
996      * @type 2DRenderingContext
997      * @default undefined
998      * @example
999      * //Almost all of the context attributes and methods are wrapped in helper functions
1000      * //but you can also access them directly using the ctx property
1001      * Karma.canvas.someCanvas.ctx.drawImage(someImage, x, y);
1002      * Karma.canvas.someCanvas.ctx.fillStyle = "#ffffff";
1003      */
1004     ctx: undefined,
1005 
1006     //initializes object with values provides by user
1007     _init: function (config) {
1008 	for (var option in config){
1009 	    if (config.hasOwnProperty(option)){
1010 		switch (option){
1011 		case "name":
1012 		    this.name = config[option];
1013 		    break;
1014 		case "domId":
1015 		    this.domId = config[option];
1016 		    break;
1017 		case "width":
1018 		    if(!this.height){
1019 			throw new Error("If you specify a width you must also" +
1020 					"specify a height");
1021 		    }
1022 		    this.width = config[option];
1023 		    break;
1024 		case "height":
1025 		    if(!this.width){
1026 			throw new Error("If you specify a height you must also" +
1027 					"specify a width");
1028 		    }
1029 		    this.height = parseInt(config.option, 10);
1030 		    break;
1031 		case "fps":
1032 		    this.fps = parseInt(config.option, 10);
1033 		    break;
1034 		}
1035 	    }
1036 	}
1037 	
1038 	if(this.domId && document.getElementById(this.domId)){
1039 	       	this.node = document.getElementById(this.domId);
1040 		this.ctx = this.node.getContext('2d');
1041 	} else {
1042 	    throw new Error('you must specify a valid domId that' +
1043 			    'is in your html page');
1044 	}
1045 
1046 	if(!config.height && !config.width){
1047 	    this.width = parseInt(this.node.getAttribute('width'), 10);
1048 	    this.height = parseInt(this.node.getAttribute('height'), 10);
1049 	}
1050 
1051 	return this;
1052     },
1053     /** Clear area of canvas element specified by parameters, if no
1054      * parameters supplied, clears entire canvas
1055      * @param {Number} [x=0] x coordinate, defaults to zero if left blank
1056      * @param {Number} [y=0] y coordinate, defaults to zero if left blank  
1057      * @param {Number} [width=0] width of area to be cleared, defaults 
1058      * entire width of canvas
1059      * @param {Number} [height=0] height of area to be cleared, defaults 
1060      * entire height of canvas
1061      * @returns this
1062      * @example
1063      * 
1064      * k.canvas.ninja.clear();
1065      * // clears the entire ninja canvas
1066      * 
1067      * k.canvas.ninja.clear(0, 10, 20, 30);
1068      * //clears a specific portion of the ninja canvas
1069      * 
1070      */
1071     clear : function ( x, y, width, height ) {
1072 	var that = this;
1073 	that.ctx.clearRect(
1074 	    x || 0,
1075 	    y || 0, 
1076 	    width  || that.width, 
1077 	    height || that.height
1078 	);
1079 	return that;
1080     },
1081   
1082      /** The globalAlpha attribute gives an alpha value that is applied to shapes 
1083      * and images before they are composited onto the canvas
1084      * @param {Number} number in the range from 0.0 to 1.0
1085      * @returns this
1086      */
1087     globalAlpha : function (attribute){
1088 	var name = 'globalAlpha';
1089 	this.ctx[name] = attribute;
1090 	return this;
1091     },
1092  
1093    /** Sets the globalCompositeOperation attribute, which sets how shapes and images 
1094      * are drawn onto the existing bitmap, once they have had globalAlpha and the 
1095      * current transformation matrix applied.
1096      * For full details see <a href="http://www.whatwg.org/specs/web-apps/current-work/multipage/the-canvas-element.html#dom-context-2d-globalcompositeoperation">W3C docs</a>
1097      * @param {String} globalCompositeOperation source-atop, 
1098      * source-in, source-out, 
1099      * source-over, destination-atop, destination-in, destination-out, destination-over,
1100      * lighter
1101      * @returns this
1102      */
1103     globalCompositeOperation: function (attribute){
1104 	var name = ' globalCompositeOperation';
1105 	this.ctx[name] = attribute;
1106 	return this;
1107     },
1108 
1109     /** Sets the lineWidth attribute which gives the width of lines, in coordinate space 
1110      * units.
1111      *  For full details see <a href="http://www.whatwg.org/specs/web-apps/current-work/multipage/the-canvas-element.html#dom-context-2d-globalcompositeoperation">W3C docs</a>
1112      * @param {Number} lineWidth
1113      * @returns this
1114      */
1115     lineWidth: function (attribute){
1116 	var name = 'lineWidth';
1117 	this.ctx[name] = attribute;
1118 	return this;
1119     },
1120     /** The lineCap attribute defines the type of endings that UAs will place on 
1121      * the end of lines.  
1122      *  For full details see <a href="http://www.whatwg.org/specs/web-apps/current-work/multipage/the-canvas-element.html#dom-context-2d-globalcompositeoperation">W3C docs</a>
1123      * @param {String} type butt, round, square 
1124      * @returns this
1125      */
1126     lineCap: function (attribute){
1127 	var name = 'lineCap';
1128 	this.ctx[name] = attribute;
1129 	return this;
1130     },
1131     /** The lineJoin attribute defines the type of corners that UAs will place 
1132      * where two lines meet. The three valid values are bevel, round, and miter. 
1133      *  For full details see <a href="http://www.whatwg.org/specs/web-apps/current-work/multipage/the-canvas-element.html#dom-context-2d-globalcompositeoperation">W3C docs</a>
1134      * @param {String} type
1135      * @returns this
1136      */
1137     lineJoin: function (attribute){
1138 	var name = 'lineJoin';
1139 	this.ctx[name] = attribute;
1140 	return this;
1141     },
1142    
1143     /** Sets the miter limit 
1144      *  For full details see <a href="http://www.whatwg.org/specs/web-apps/current-work/multipage/the-canvas-element.html#dom-context-2d-globalcompositeoperation">W3C docs</a>
1145      * @param {Number} number
1146      * @returns this
1147      */
1148     miterLimit: function (attribute){
1149 	var name = 'miterLimit';
1150 	this.ctx[name] = attribute;
1151 	return this;
1152     },
1153     /** Sets the font property and takes the same syntax as setting the font property 
1154      *  in CSS
1155      *  For full details see <a href="http://www.whatwg.org/specs/web-apps/current-work/multipage/the-canvas-element.html#dom-context-2d-globalcompositeoperation">W3C docs</a>
1156      * @param {String} 
1157      * @returns this
1158      */
1159     font: function (attribute){
1160 	var name = 'font';
1161 	this.ctx[name] = attribute;
1162 	return this;
1163     },
1164 
1165     /** Changes the text alignment. The possible values are start, end, left, right, 
1166      * and center. The default is start. Other values are ignored.
1167      *  For full details see <a href="http://www.whatwg.org/specs/web-apps/current-work/multipage/the-canvas-element.html#dom-context-2d-globalcompositeoperation">W3C docs</a>
1168      * @param {string} alignment 
1169      * @returns this
1170      */
1171     textAlign: function (attribute){
1172 	var name = 'textAlign';
1173 	this.ctx[name] = attribute;
1174 	return this;
1175     },
1176 
1177     /** Changes the baseline alignment. If the value is one of top, hanging, middle, 
1178      * alphabetic, ideographic, or bottom, then the value must be changed to the new value.
1179      *  For full details see <a href="http://www.whatwg.org/specs/web-apps/current-work/multipage/the-canvas-element.html#dom-context-2d-globalcompositeoperation">W3C docs</a>
1180      * @param {String} alignment
1181      * @returns this
1182      */
1183     textBaseline: function (attribute){
1184 	var name = 'textBaseline';
1185 	this.ctx[name] = attribute;
1186 	return this;
1187     },
1188     
1189     /** Save the current state of the context
1190      *  For full details see <a href="http://www.whatwg.org/specs/web-apps/current-work/multipage/the-canvas-element.html#dom-context-2d-globalcompositeoperation">W3C docs</a>
1191      * @param 
1192      * @returns this
1193      */
1194     save : function ( ){
1195 	var name =  'save'; 
1196 	this.ctx[name].apply(this.ctx, arguments);
1197 	return this;
1198     },
1199     /** Restore the saved context
1200      *  For full details see <a href="http://www.whatwg.org/specs/web-apps/current-work/multipage/the-canvas-element.html#dom-context-2d-globalcompositeoperation">W3C docs</a>
1201      * @param 
1202      * @returns this
1203      */
1204     restore : function ( ){
1205 	var name =  'restore'; 
1206 	this.ctx[name].apply(this.ctx, arguments);
1207 	return this;
1208     },
1209      /** Perform a scale transformation
1210      *  For full details see <a href="http://www.whatwg.org/specs/web-apps/current-work/multipage/the-canvas-element.html#dom-context-2d-globalcompositeoperation">W3C docs</a>
1211      * @param 
1212      * @returns this
1213      */
1214     scale : function ( ){
1215 	var name =  'scale'; 
1216 	this.ctx[name].apply(this.ctx, arguments);
1217 	return this;
1218     },
1219    /** Perform a rotation transformation
1220      *  For full details see <a href="http://www.whatwg.org/specs/web-apps/current-work/multipage/the-canvas-element.html#dom-context-2d-globalcompositeoperation">W3C docs</a>
1221      * @param 
1222      * @returns this
1223      */
1224     rotate : function ( ){
1225 	var name =  'rotate'; 
1226 	this.ctx[name].apply(this.ctx, arguments);
1227 	return this;
1228     },
1229      /** Performa a translation transformation
1230      *  For full details see <a href="http://www.whatwg.org/specs/web-apps/current-work/multipage/the-canvas-element.html#dom-context-2d-globalcompositeoperation">W3C docs</a>
1231      * @param 
1232      * @returns this
1233      */
1234     translate : function ( ){
1235 	var name =  'translate'; 
1236 	this.ctx[name].apply(this.ctx, arguments);
1237 	return this;
1238     },
1239     
1240     /** Transform the identity matrix
1241      *  For full details see <a href="http://www.whatwg.org/specs/web-apps/current-work/multipage/the-canvas-element.html#dom-context-2d-globalcompositeoperation">W3C docs</a>
1242      * @param 
1243      * @returns this
1244      */
1245     transform : function ( ){
1246 	var name =  'transform'; 
1247 	this.ctx[name].apply(this.ctx, arguments);
1248 	return this;
1249     },
1250     /** Set the transform
1251      *  For full details see <a href="http://www.whatwg.org/specs/web-apps/current-work/multipage/the-canvas-element.html#dom-context-2d-globalcompositeoperation">W3C docs</a>
1252      * @param 
1253      * @returns this
1254      */
1255     setTransform : function ( ){
1256 	var name =  'setTransform'; 
1257 	this.ctx[name].apply(this.ctx, arguments);
1258 	return this;
1259     },
1260     /** Clear a rectangular area 
1261      *  For full details see <a href="http://www.whatwg.org/specs/web-apps/current-work/multipage/the-canvas-element.html#dom-context-2d-globalcompositeoperation">W3C docs</a>
1262      * @param 
1263      * @returns this
1264      */
1265     clearRect : function ( ){
1266 	var name =  'clearRect'; 
1267 	this.ctx[name].apply(this.ctx, arguments);
1268 	return this;
1269     },
1270     /** Fill a rectangular area 
1271      *  For full details see <a href="http://www.whatwg.org/specs/web-apps/current-work/multipage/the-canvas-element.html#dom-context-2d-globalcompositeoperation">W3C docs</a>
1272      * @param 
1273      * @returns this
1274      */
1275     fillRect : function ( ){
1276 	var name =  'fillRect'; 
1277 	this.ctx[name].apply(this.ctx, arguments);
1278 	return this;
1279     },
1280       
1281     /** Draw the outline of the rectangle
1282      *  For full details see <a href="http://www.whatwg.org/specs/web-apps/current-work/multipage/the-canvas-element.html#dom-context-2d-globalcompositeoperation">W3C docs</a>
1283      * @param 
1284      * @returns this
1285      */
1286     strokeRect : function ( ){
1287 	var name =  'strokeRect'; 
1288 	this.ctx[name].apply(this.ctx, arguments);
1289 	return this;
1290     },
1291     /** Begin a path 
1292      *  For full details see <a href="http://www.whatwg.org/specs/web-apps/current-work/multipage/the-canvas-element.html#dom-context-2d-globalcompositeoperation">W3C docs</a>
1293      * @param 
1294      * @returns this
1295      */
1296     beginPath : function ( ){
1297 	var name =  'beginPath'; 
1298 	this.ctx[name].apply(this.ctx, arguments);
1299 	return this;
1300     },
1301     /** End a path 
1302      *  For full details see <a href="http://www.whatwg.org/specs/web-apps/current-work/multipage/the-canvas-element.html#dom-context-2d-globalcompositeoperation">W3C docs</a>
1303      * @param 
1304      * @returns this
1305      */
1306     closePath : function ( ){
1307 	var name =  'closePath'; 
1308 	this.ctx[name].apply(this.ctx, arguments);
1309 	return this;
1310     },
1311     /** Move to specified coordinates 
1312      *  For full details see <a href="http://www.whatwg.org/specs/web-apps/current-work/multipage/the-canvas-element.html#dom-context-2d-globalcompositeoperation">W3C docs</a>
1313      * @param 
1314      * @returns this
1315      */
1316     moveTo : function ( ){
1317 	var name =  'moveTo'; 
1318 	this.ctx[name].apply(this.ctx, arguments);
1319 	return this;
1320     },
1321 
1322 
1323     /** Draw a line to the given coordinates 
1324      *  For full details see <a href="http://www.whatwg.org/specs/web-apps/current-work/multipage/the-canvas-element.html#dom-context-2d-globalcompositeoperation">W3C docs</a>
1325      * @param 
1326      * @returns this
1327      */
1328     lineTo : function ( ){
1329 	var name =  'lineTo'; 
1330 	this.ctx[name].apply(this.ctx, arguments);
1331 	return this;
1332     },
1333 
1334     /** Draw a quadratic curve to given coordinates 
1335      *  For full details see <a href="http://www.whatwg.org/specs/web-apps/current-work/multipage/the-canvas-element.html#dom-context-2d-globalcompositeoperation">W3C docs</a>
1336      * @param 
1337      * @returns this
1338      */
1339     quadraticCurveTo : function ( ){
1340 	var name =  'quadraticCurveTo'; 
1341 	this.ctx[name].apply(this.ctx, arguments);
1342 	return this;
1343     },
1344     /** Draw a bezier curve to given coordinates 
1345      *  For full details see <a href="http://www.whatwg.org/specs/web-apps/current-work/multipage/the-canvas-element.html#dom-context-2d-globalcompositeoperation">W3C docs</a>
1346      * @param 
1347      * @returns this
1348      */
1349     bezierCurveTo : function ( ){
1350 	var name =  'bezierCurveTo'; 
1351 	this.ctx[name].apply(this.ctx, arguments);
1352 	return this;
1353     },
1354     /** Draw an arc to the given points
1355      *  For full details see <a href="http://www.whatwg.org/specs/web-apps/current-work/multipage/the-canvas-element.html#dom-context-2d-globalcompositeoperation">W3C docs</a>
1356      * @param 
1357      * @returns this
1358      */
1359     arcTo : function ( ){
1360 	var name =  'arcTo'; 
1361 	this.ctx[name].apply(this.ctx, arguments);
1362 	return this;
1363     },
1364     /** Create an arc 
1365      *  For full details see <a href="http://www.whatwg.org/specs/web-apps/current-work/multipage/the-canvas-element.html#dom-context-2d-globalcompositeoperation">W3C docs</a>
1366      * @param 
1367      * @returns this
1368      */
1369     arc : function ( ){
1370 	var name =  'arc'; 
1371 	this.ctx[name].apply(this.ctx, arguments);
1372 	return this;
1373     },
1374 
1375     /** Create a rectangle 
1376      *  For full details see <a href="http://www.whatwg.org/specs/web-apps/current-work/multipage/the-canvas-element.html#dom-context-2d-globalcompositeoperation">W3C docs</a>
1377      * @param 
1378      * @returns this
1379      */
1380     rect : function ( ){
1381 	var name =  'rect'; 
1382 	this.ctx[name].apply(this.ctx, arguments);
1383 	return this;
1384     },
1385     /** fill in the current subpaths with the current fillstyle
1386      *  For full details see <a href="http://www.whatwg.org/specs/web-apps/current-work/multipage/the-canvas-element.html#dom-context-2d-globalcompositeoperation">W3C docs</a>
1387      * @param 
1388      * @returns this
1389      */
1390     fill : function ( ){
1391 	var name =  'fill'; 
1392 	this.ctx[name].apply(this.ctx, arguments);
1393 	return this;
1394     },
1395     /** Stroke the subpaths
1396      *  For full details see <a href="http://www.whatwg.org/specs/web-apps/current-work/multipage/the-canvas-element.html#dom-context-2d-globalcompositeoperation">W3C docs</a>
1397      * @param 
1398      * @returns this
1399      */
1400     stroke : function ( ){
1401 	var name =  'stroke'; 
1402 	this.ctx[name].apply(this.ctx, arguments);
1403 	return this;
1404     },
1405     
1406     /** description 
1407      *  For full details see <a href="http://www.whatwg.org/specs/web-apps/current-work/multipage/the-canvas-element.html#dom-context-2d-globalcompositeoperation">W3C docs</a>
1408      * @param 
1409      * @returns this
1410      */
1411     clip : function ( ){
1412 	var name =  'clip'; 
1413 	this.ctx[name].apply(this.ctx, arguments);
1414 	return this;
1415     },
1416     /** description 
1417      *  For full details see <a href="http://www.whatwg.org/specs/web-apps/current-work/multipage/the-canvas-element.html#dom-context-2d-globalcompositeoperation">W3C docs</a>
1418      * @param 
1419      * @returns this
1420      */
1421     fillText : function ( ){
1422 	var name =  'fillText'; 
1423 	this.ctx[name].apply(this.ctx, arguments);
1424 	return this;
1425     },
1426     /** description 
1427      *  For full details see <a href="http://www.whatwg.org/specs/web-apps/current-work/multipage/the-canvas-element.html#dom-context-2d-globalcompositeoperation">W3C docs</a>
1428      * @param 
1429      * @returns this
1430      */
1431     strokeText : function ( ){
1432 	var name =  'strokeText'; 
1433 	this.ctx[name].apply(this.ctx, arguments);
1434 	return this;
1435     },
1436     /** description 
1437      *  For full details see <a href="http://www.whatwg.org/specs/web-apps/current-work/multipage/the-canvas-element.html#dom-context-2d-globalcompositeoperation">W3C docs</a>
1438      * @param 
1439      * @returns this
1440      */
1441     measureText : function ( ){
1442 	var name =  'measureText'; 
1443 	this.ctx[name].apply(this.ctx, arguments);
1444 	return this;
1445     },
1446     /** description 
1447      *  For full details see <a href="http://www.whatwg.org/specs/web-apps/current-work/multipage/the-canvas-element.html#dom-context-2d-globalcompositeoperation">W3C docs</a>
1448      * @param 
1449      * @returns this
1450      */
1451     isPointInPath : function ( ){
1452 	var name =  'isPointInPath'; 
1453 	this.ctx[name].apply(this.ctx, arguments);
1454 	return this;
1455     },
1456     
1457     /** Sets the stroke style 
1458      *  For full details see <a href="http://www.whatwg.org/specs/web-apps/current-work/multipage/the-canvas-element.html#dom-context-2d-globalcompositeoperation">W3C docs</a>
1459      * @param 
1460      * @returns this
1461      */
1462     strokeStyle: function (attribute){
1463 	var name = 'strokeStyle';
1464 	this.ctx[name] = attribute;
1465 	return this;
1466     },
1467 
1468     /** Sets the fill style
1469      *  For full details see <a href="http://www.whatwg.org/specs/web-apps/current-work/multipage/the-canvas-element.html#dom-context-2d-globalcompositeoperation">W3C docs</a>
1470      * @param 
1471      * @returns this
1472      */
1473     fillStyle: function (attribute){
1474 	var name = 'fillStyle';
1475 	this.ctx[name] = attribute;
1476 	return this;
1477     },
1478      /** description 
1479      *  For full details see <a href="http://www.whatwg.org/specs/web-apps/current-work/multipage/the-canvas-element.html#dom-context-2d-globalcompositeoperation">W3C docs</a>
1480      * @param 
1481      * @returns this
1482      */
1483     createLinearGradient : function ( ){
1484 	var name =  'createLinearGradient'; 
1485 	this.ctx[name].apply(this.ctx, arguments);
1486 	return this;
1487     },
1488     /** description 
1489      *  For full details see <a href="http://www.whatwg.org/specs/web-apps/current-work/multipage/the-canvas-element.html#dom-context-2d-globalcompositeoperation">W3C docs</a>
1490      * @param 
1491      * @returns this
1492      */
1493     createRadialGradient : function ( ){
1494 	var name =  'createRadialGradient'; 
1495 	this.ctx[name].apply(this.ctx, arguments);
1496 	return this;
1497     },
1498     /** description 
1499      *  For full details see <a href="http://www.whatwg.org/specs/web-apps/current-work/multipage/the-canvas-element.html#dom-context-2d-globalcompositeoperation">W3C docs</a>
1500      * @param 
1501      * @returns this
1502      */
1503     createPattern : function ( ){
1504 	var name =  'createPattern'; 
1505 	this.ctx[name].apply(this.ctx, arguments);
1506 	return this;
1507     },
1508      /** description 
1509      *  For full details see <a href="http://www.whatwg.org/specs/web-apps/current-work/multipage/the-canvas-element.html#dom-context-2d-globalcompositeoperation">W3C docs</a>
1510      * @param 
1511      * @returns this
1512      */
1513     shadowOffsetX: function (attribute){
1514 	var name = 'shadowOffsetX';
1515 	this.ctx[name] = attribute;
1516 	return this;
1517     },
1518     /** description 
1519      *  For full details see <a href="http://www.whatwg.org/specs/web-apps/current-work/multipage/the-canvas-element.html#dom-context-2d-globalcompositeoperation">W3C docs</a>
1520      * @param 
1521      * @returns this
1522      */
1523     shadowOffsetY: function (attribute){
1524 	var name = 'shadowOffsetY';
1525 	this.ctx[name] = attribute;
1526 	return this;
1527     },
1528     /** description 
1529      *  For full details see <a href="http://www.whatwg.org/specs/web-apps/current-work/multipage/the-canvas-element.html#dom-context-2d-globalcompositeoperation">W3C docs</a>
1530      * @param 
1531      * @returns this
1532      */
1533     shadowBlur: function (attribute){
1534 	var name = 'shadowBlur';
1535 	this.ctx[name] = attribute;
1536 	return this;
1537     },
1538     /** description 
1539      *  For full details see <a href="http://www.whatwg.org/specs/web-apps/current-work/multipage/the-canvas-element.html#dom-context-2d-globalcompositeoperation">W3C docs</a>
1540      * @param 
1541      * @returns this
1542      */
1543     shadowColor: function (attribute){
1544 	var name = 'shadowColor';
1545 	this.ctx[name] = attribute;
1546 	return this;
1547     },
1548    /** description 
1549      *  For full details see <a href="http://www.whatwg.org/specs/web-apps/current-work/multipage/the-canvas-element.html#dom-context-2d-globalcompositeoperation">W3C docs</a>
1550      * @param 
1551      * @returns this
1552      */
1553     drawImage : function ( ){
1554 	var name =  'drawImage'; 
1555 	this.ctx[name].apply(this.ctx, arguments);
1556 	return this;
1557     },
1558     /** description 
1559      *  For full details see <a href="http://www.whatwg.org/specs/web-apps/current-work/multipage/the-canvas-element.html#dom-context-2d-globalcompositeoperation">W3C docs</a>
1560      * @param 
1561      * @returns this
1562      */
1563     getImageData : function ( ){
1564 	var name =  'getImageData'; 
1565 	this.ctx[name].apply(this.ctx, arguments);
1566 	return this;
1567     },
1568     /** description 
1569      *  For full details see <a href="http://www.whatwg.org/specs/web-apps/current-work/multipage/the-canvas-element.html#dom-context-2d-globalcompositeoperation">W3C docs</a>
1570      * @param 
1571      * @returns this
1572      */
1573     putImageData : function ( ){
1574 	var name =  'putImageData'; 
1575 	this.ctx[name].apply(this.ctx, arguments);
1576 	return this;
1577     },
1578     /** description 
1579      *  For full details see <a href="http://www.whatwg.org/specs/web-apps/current-work/multipage/the-canvas-element.html#dom-context-2d-globalcompositeoperation">W3C docs</a>
1580      * @param 
1581      * @returns this
1582      */
1583     createImageData : function ( ){
1584 	var name =  'createImageData'; 
1585 	this.ctx[name].apply(this.ctx, arguments);
1586 	return this;
1587     },
1588     /** description 
1589      *  For full details see <a href="http://www.whatwg.org/specs/web-apps/current-work/multipage/the-canvas-element.html#dom-context-2d-globalcompositeoperation">W3C docs</a>
1590      * @param 
1591      * @returns this
1592      */
1593     drawWindow : function ( ){
1594 	var name =  'drawWindow'; 
1595 	this.ctx[name].apply(this.ctx, arguments);
1596 	return this;
1597     },
1598     
1599 
1600  
1601    
1602 };
1603 
1604 
1605 /** Prototype object for each svg element submitted to Karma in the
1606  * Karma() method
1607  * @throws {Error} if the name and domId for the svg element are not specified
1608  * @thows {Error} if the supplied domId does not match an element in the DOM
1609  * @class This object is the prototype for each svg element submitted to Karma in the
1610  * Karma() method
1611  */
1612 Karma.kSvg = {
1613     /** name of instance, used internally 
1614      * @typeof string
1615      * @default ""
1616      */
1617     name : "",
1618     /** width of element 
1619      * @type number
1620      * @default 0
1621      */
1622     width: 0,
1623     /** height of element 
1624      * @type number
1625      * @default 0
1626      */
1627     height: 0,
1628     /** Status of element, either "loaded" or "error"
1629      * @type string
1630      * @default ""
1631      */
1632     status: "",
1633     /**  Whether canvas is visible. This value is read-only
1634      * @type boolean
1635      * @default true
1636      */
1637     visible: true,
1638     /** Element ID for canvas element in html document. 
1639      * @type String
1640      * @default undefined
1641      */
1642     domId: undefined,
1643     /** Reference to the DOM element.
1644      * @type DOMElement
1645      * @default undefined
1646      * @example 
1647      * //You can access all properties and methods of the underlying DOM element
1648      * //using the 'node' property
1649      * Karma.svg.someSvg.node.dispatchEvent;
1650      * Karma.svg.someSvg.node.addEvenListener(...);
1651      */
1652     node: undefined,
1653     /** Reference to the SVGDocument. You can use the this.doc to manipulate 
1654      * the SVG document 
1655      * @type SVGDocument
1656      * @default undefined
1657      * @example
1658      * var myElem = Karma.svg.someSvg.doc.getElementById('foobar');
1659      * Karma.svg.someSvg.doc.createElement(...);
1660      * Karma.svg.someSvg.doc.removeChild(someNode);
1661      *     
1662      */
1663     doc: undefined,
1664     /** Reference to the root element of the SVG Document 
1665      * @type DocumentElement
1666      * @default undefined
1667      * @example
1668      * // The root element is equivalent to "document" in a regular html document
1669      * // The root attribute is used frequently with the jQuery SVG plugin for CSS selectors
1670      * $('#someId', Karma.svg.someSvg.root).css(.. manipulate css attributes ...);
1671      */
1672     root: undefined,
1673     _localized : undefined,
1674     _init: function (config) {
1675 	Karma._counters.total++;
1676 
1677 	for (var option in config){
1678 	    if (config.hasOwnProperty(option)){
1679 		switch (option){
1680 		case "name":
1681 		    this.name = config[option];
1682 		    break;
1683 		case "domId":
1684 		    this.domId = config[option];
1685 		    break;
1686 		case "width":
1687 		    if(!this.height){
1688 			throw new Error("If you specify a width you must also" +
1689 					"specify a height");
1690 		    }
1691 		    this.width = parseInt(config[option], 10);
1692 		    break;
1693 		case "height":
1694 		    if(!this.width){
1695 			throw new Error("If you specify a height you must also" +
1696 					"specify a width");
1697 		    }
1698 		    this.height = config[option];
1699 		    break;
1700 		}
1701 	    }
1702 	}
1703 	
1704 	if(this.domId && document.getElementById(this.domId)){
1705 	       	this.node = document.getElementById(this.domId);
1706 	} else {
1707 	    throw new Error('you must specify a valid domId that' +
1708 			    'is in your html page');
1709 	}
1710 
1711 	if(!config.height && !config.width){
1712 	    this.width = parseInt(this.node.getAttribute('width'), 10);
1713 	    this.height = parseInt(this.node.getAttribute('height'), 10);
1714 	}
1715 	
1716 	var that = this;
1717 	that._addEventHandlers();
1718 		   
1719 	return this;
1720 	
1721 	
1722     },
1723     _addEventHandlers : function () {
1724 	var that = this;	
1725 	that.doc = that.node.getSVGDocument();	
1726 	that.node.addEventListener(
1727 		"load", 
1728 	    function (e) { 
1729 		that.doc = that.node.getSVGDocument();    
1730 		that.root = that.doc.documentElement;
1731 		Karma._counters.loaded++;
1732 		Karma._updateStatus();
1733 		that.status = "loaded";
1734 	    }, false);
1735 
1736 	that.node.addEventListener(
1737 	    "error", 
1738 	    function (e) { 
1739 		Karma._counters.loaded--;
1740 		Karma._counters.errors++;
1741 		that.status = "error";
1742 		var errorMsg = "Error: " + that._type.toUpperCase() +
1743 		    " " + that.name + " cannot be loaded."; 
1744 		Karma._updateStatus(errorMsg);
1745 	    }, 
1746 	    false);
1747 	that.node.addEventListener(
1748 	    "abort", 
1749 	    function (e) { 
1750 		that.status = "aborted";
1751 		var errorMsg = "ABORT: " + that._type.toUpperCase() +
1752 		    " " + that.name + " loading was aborted."; 
1753 		Karma._updateStatus(errorMsg);
1754 
1755 	    }, false);
1756 
1757     }
1758 };
1759