/*!
* Copyright (c) 2011 Simo Kinnunen.
* Licensed under the MIT license.
*
* @version ${Version}
*/

var Cufon = (function() {

        var api = function() {
               return api.replace.apply(null, arguments);
        };

        var DOM = api.DOM = {

               ready: (function() {

                       var complete = false, readyStatus = { loaded: 1, complete: 1 };

                       var queue = [], perform = function() {
                               if (complete) return;
                               complete = true;
                               for (var fn; fn = queue.shift(); fn());
                       };

                       // Gecko, Opera, WebKit r26101+

                       if (document.addEventListener) {
                               document.addEventListener('DOMContentLoaded', perform, false);
                               window.addEventListener('pageshow', perform, false); // For cached Gecko pages
                       }

                       // Old WebKit, Internet Explorer

                       if (!window.opera && document.readyState) (function() {
                               readyStatus[document.readyState] ? perform() : setTimeout(arguments.callee, 10);
                       })();

                       // Internet Explorer

                       if (document.readyState && document.createStyleSheet) (function() {
                               try {
                                      document.body.doScroll('left');
                                      perform();
                               }
                               catch (e) {
                                      setTimeout(arguments.callee, 1);
                               }
                       })();

                       addEvent(window, 'load', perform); // Fallback

                       return function(listener) {
                               if (!arguments.length) perform();
                               else complete ? listener() : queue.push(listener);
                       };

               })(),

               root: function() {
                       return document.documentElement || document.body;
               },

               strict: (function() {
                       var doctype;
                       // no doctype (doesn't always catch it though.. IE I'm looking at you)
                       if (document.compatMode == 'BackCompat') return false;
                       // WebKit, Gecko, Opera, IE9+
                       doctype = document.doctype;
                       if (doctype) {
                               return !/frameset|transitional/i.test(doctype.publicId);
                       }
                       // IE<9, firstChild is the doctype even if there's an XML declaration
                       doctype = document.firstChild;
                       if (doctype.nodeType != 8 || /^DOCTYPE.+(transitional|frameset)/i.test(doctype.data)) {
                               return false;
                       }
                       return true;
               })()

        };

        var CSS = api.CSS = {

               Size: function(value, base) {

                       this.value = parseFloat(value);
                       this.unit = String(value).match(/[a-z%]*$/)[0] || 'px';

                       this.convert = function(value) {
                               return value / base * this.value;
                       };

                       this.convertFrom = function(value) {
                               return value / this.value * base;
                       };

                       this.toString = function() {
                               return this.value + this.unit;
                       };

               },

               addClass: function(el, className) {
                       var current = el.className;
                       el.className = current + (current && ' ') + className;
                       return el;
               },

               color: cached(function(value) {
                       var parsed = {};
                       parsed.color = value.replace(/^rgba\((.*?),\s*([\d.]+)\)/, function($0, $1, $2) {
                               parsed.opacity = parseFloat($2);
                               return 'rgb(' + $1 + ')';
                       });
                       return parsed;
               }),

               // has no direct CSS equivalent.
               // @see http://msdn.microsoft.com/en-us/library/system.windows.fontstretches.aspx
               fontStretch: cached(function(value) {
                       if (typeof value == 'number') return value;
                       if (/%$/.test(value)) return parseFloat(value) / 100;
                       return {
                               'ultra-condensed': 0.5,
                               'extra-condensed': 0.625,
                               condensed: 0.75,
                               'semi-condensed': 0.875,
                               'semi-expanded': 1.125,
                               expanded: 1.25,
                               'extra-expanded': 1.5,
                               'ultra-expanded': 2
                       }[value] || 1;
               }),

               getStyle: function(el) {
                       var view = document.defaultView;
                       if (view && view.getComputedStyle) return new Style(view.getComputedStyle(el, null));
                       if (el.currentStyle) return new Style(el.currentStyle);
                       return new Style(el.style);
               },

               gradient: cached(function(value) {
                       var gradient = {
                               id: value,
                               type: value.match(/^-([a-z]+)-gradient\(/)[1],
                               stops: []
                       }, colors = value.substr(value.indexOf('(')).match(/([\d.]+=)?(#[a-f0-9]+|[a-z]+\(.*?\)|[a-z]+)/ig);
                       for (var i = 0, l = colors.length, stop; i < l; ++i) {
                               stop = colors[i].split('=', 2).reverse();
                               gradient.stops.push([ stop[1] || i / (l - 1), stop[0] ]);
                       }
                       return gradient;
               }),

               quotedList: cached(function(value) {
                       // doesn't work properly with empty quoted strings (""), but
                       // it's not worth the extra code.
                       var list = [], re = /\s*((["'])([\s\S]*?[^\\])\2|[^,]+)\s*/g, match;
                       while (match = re.exec(value)) list.push(match[3] || match[1]);
                       return list;
               }),

               recognizesMedia: cached(function(media) {
                       var el = document.createElement('style'), sheet, container, supported;
                       el.type = 'text/css';
                       el.media = media;
                       try { // this is cached anyway
                               el.appendChild(document.createTextNode('/**/'));
                       } catch (e) {}
                       container = elementsByTagName('head')[0];
                       container.insertBefore(el, container.firstChild);
                       sheet = (el.sheet || el.styleSheet);
                       supported = sheet && !sheet.disabled;
                       container.removeChild(el);
                       return supported;
               }),

               removeClass: function(el, className) {
                       var re = RegExp('(?:^|\\s+)' + className +  '(?=\\s|$)', 'g');
                       el.className = el.className.replace(re, '');
                       return el;
               },

               supports: function(property, value) {
                       var checker = document.createElement('span').style;
                       if (checker[property] === undefined) return false;
                       checker[property] = value;
                       return checker[property] === value;
               },

               textAlign: function(word, style, position, wordCount) {
                       if (style.get('textAlign') == 'right') {
                               if (position > 0) word = ' ' + word;
                       }
                       else if (position < wordCount - 1) word += ' ';
                       return word;
               },

               textShadow: cached(function(value) {
                       if (value == 'none') return null;
                       var shadows = [], currentShadow = {}, result, offCount = 0;
                       var re = /(#[a-f0-9]+|[a-z]+\(.*?\)|[a-z]+)|(-?[\d.]+[a-z%]*)|,/ig;
                       while (result = re.exec(value)) {
                               if (result[0] == ',') {
                                      shadows.push(currentShadow);
                                      currentShadow = {};
                                      offCount = 0;
                               }
                               else if (result[1]) {
                                      currentShadow.color = result[1];
                               }
                               else {
                                      currentShadow[[ 'offX', 'offY', 'blur' ][offCount++]] = result[2];
                               }
                       }
                       shadows.push(currentShadow);
                       return shadows;
               }),

               textTransform: (function() {
                       var map = {
                               uppercase: function(s) {
                                      return s.toUpperCase();
                               },
                               lowercase: function(s) {
                                      return s.toLowerCase();
                               },
                               capitalize: function(s) {
                                      return s.replace(/(?:^|\s)./g, function($0) {
                                              return $0.toUpperCase();
                                      });
                               }
                       };
                       return function(text, style) {
                               var transform = map[style.get('textTransform')];
                               return transform ? transform(text) : text;
                       };
               })(),

               whiteSpace: (function() {
                       var ignore = {
                               inline: 1,
                               'inline-block': 1,
                               'run-in': 1
                       };
                       var wsStart = /^\s+/, wsEnd = /\s+$/;
                       return function(text, style, node, previousElement, simple) {
                               if (simple) return text.replace(wsStart, '').replace(wsEnd, ''); // @fixme too simple
                               if (previousElement) {
                                      if (previousElement.nodeName.toLowerCase() == 'br') {
                                              text = text.replace(wsStart, '');
                                      }
                               }
                               if (ignore[style.get('display')]) return text;
                               if (!node.previousSibling) text = text.replace(wsStart, '');
                               if (!node.nextSibling) text = text.replace(wsEnd, '');
                               return text;
                       };
               })()

        };

        CSS.ready = (function() {

               // don't do anything in Safari 2 (it doesn't recognize any media type)
               var complete = !CSS.recognizesMedia('all'), hasLayout = false;

               var queue = [], perform = function() {
                       complete = true;
                       for (var fn; fn = queue.shift(); fn());
               };

               var links = elementsByTagName('link'), styles = elementsByTagName('style');

               var checkTypes = {
                       '': 1,
                       'text/css': 1
               };

               function isContainerReady(el) {
                       if (!checkTypes[el.type.toLowerCase()]) return true;
                       return el.disabled || isSheetReady(el.sheet, el.media || 'screen');
               }

               function isSheetReady(sheet, media) {
                       // in Opera sheet.disabled is true when it's still loading,
                       // even though link.disabled is false. they stay in sync if
                       // set manually.
                       if (!CSS.recognizesMedia(media || 'all')) return true;
                       if (!sheet || sheet.disabled) return false;
                       try {
                               var rules = sheet.cssRules, rule;
                               if (rules) {
                                      // needed for Safari 3 and Chrome 1.0.
                                      // in standards-conforming browsers cssRules contains @-rules.
                                      // Chrome 1.0 weirdness: rules[<number larger than .length - 1>]
                                      // returns the last rule, so a for loop is the only option.
                                      search: for (var i = 0, l = rules.length; rule = rules[i], i < l; ++i) {
                                              switch (rule.type) {
                                                     case 2: // @charset
                                                             break;
                                                     case 3: // @import
                                                             if (!isSheetReady(rule.styleSheet, rule.media.mediaText)) return false;
                                                             break;
                                                     default:
                                                             // only @charset can precede @import
                                                             break search;
                                              }
                                      }
                               }
                       }
                       catch (e) {} // probably a style sheet from another domain
                       return true;
               }

               function allStylesLoaded() {
                       // Internet Explorer's style sheet model, there's no need to do anything
                       if (document.createStyleSheet) return true;
                       // standards-compliant browsers
                       var el, i;
                       for (i = 0; el = links[i]; ++i) {
                               if (el.rel.toLowerCase() == 'stylesheet' && !isContainerReady(el)) return false;
                       }
                       for (i = 0; el = styles[i]; ++i) {
                               if (!isContainerReady(el)) return false;
                       }
                       return true;
               }

               DOM.ready(function() {
                       // getComputedStyle returns null in Gecko if used in an iframe with display: none
                       if (!hasLayout) hasLayout = CSS.getStyle(document.body).isUsable();
                       if (complete || (hasLayout && allStylesLoaded())) perform();
                       else setTimeout(arguments.callee, 10);
               });

               return function(listener) {
                       if (complete) listener();
                       else queue.push(listener);
               };

        })();

        function Font(data) {

               var face = this.face = data.face, wordSeparators = {
                       '\u0020': 1,
                       '\u00a0': 1,
                       '\u3000': 1
               };

               this.glyphs = (function(glyphs) {
                       var key, fallbacks = {
                               '\u2011': '\u002d',
                               '\u00ad': '\u2011'
                       };
                       for (key in fallbacks) {
                               if (!hasOwnProperty(fallbacks, key)) continue;
                               if (!glyphs[key]) glyphs[key] = glyphs[fallbacks[key]];
                       }
                       return glyphs;
               })(data.glyphs);

               this.w = data.w;
               this.baseSize = parseInt(face['units-per-em'], 10);

               this.family = face['font-family'].toLowerCase();
                this.weight = face['font-weight'];
               this.style = face['font-style'] || 'normal';

               this.viewBox = (function () {
                       var parts = face.bbox.split(/\s+/);
                       var box = {
                               minX: parseInt(parts[0], 10),
                               minY: parseInt(parts[1], 10),
                               maxX: parseInt(parts[2], 10),
                               maxY: parseInt(parts[3], 10)
                       };
                       box.width = box.maxX - box.minX;
                       box.height = box.maxY - box.minY;
                       box.toString = function() {
                               return [ this.minX, this.minY, this.width, this.height ].join(' ');
                       };
                       return box;
               })();

               this.ascent = -parseInt(face.ascent, 10);
               this.descent = -parseInt(face.descent, 10);

               this.height = -this.ascent + this.descent;

               this.spacing = function(chars, letterSpacing, wordSpacing) {
                       var glyphs = this.glyphs, glyph,
                               kerning, k,
                               jumps = [],
                               width = 0, w,
                               i = -1, j = -1, chr;
                       while (chr = chars[++i]) {
                               glyph = glyphs[chr] || this.missingGlyph;
                               if (!glyph) continue;
                               if (kerning) {
                                      width -= k = kerning[chr] || 0;
                                      jumps[j] -= k;
                               }
                               w = glyph.w;
                               if (isNaN(w)) w = +this.w; // may have been a String in old fonts
                               if (w > 0) {
                                      w += letterSpacing;
                                      if (wordSeparators[chr]) w += wordSpacing;
                               }
                               width += jumps[++j] = ~~w; // get rid of decimals
                               kerning = glyph.k;
                       }
                       jumps.total = width;
                       return jumps;
               };

        }

        function FontFamily() {

               var styles = {}, mapping = {
                       oblique: 'italic',
                       italic: 'oblique'
               };

               this.add = function(font) {
                       (styles[font.style] || (styles[font.style] = {}))[font.weight] = font;
               };

               this.get = function(style, weight) {
                       var weights = styles[style] || styles[mapping[style]]
                               || styles.normal || styles.italic || styles.oblique;
                       if (!weights) return null;
                       // we don't have to worry about "bolder" and "lighter"
                       // because IE's currentStyle returns a numeric value for it,
                       // and other browsers use the computed value anyway
                       weight = {
                               normal: 400,
                               bold: 700
                       }[weight] || parseInt(weight, 10);
                       if (weights[weight]) return weights[weight];
                       // http://www.w3.org/TR/CSS21/fonts.html#propdef-font-weight
                       // Gecko uses x99/x01 for lighter/bolder
                       var up = {
                               1: 1,
                               99: 0
                       }[weight % 100], alts = [], min, max;
                       if (up === undefined) up = weight > 400;
                       if (weight == 500) weight = 400;
                       for (var alt in weights) {
                               if (!hasOwnProperty(weights, alt)) continue;
                               alt = parseInt(alt, 10);
                               if (!min || alt < min) min = alt;
                               if (!max || alt > max) max = alt;
                               alts.push(alt);
                       }
                       if (weight < min) weight = min;
                       if (weight > max) weight = max;
                       alts.sort(function(a, b) {
                               return (up
                                      ? (a >= weight && b >= weight) ? a < b : a > b
                                      : (a <= weight && b <= weight) ? a > b : a < b) ? -1 : 1;
                       });
                       return weights[alts[0]];
               };

        }

        function HoverHandler() {

               function contains(node, anotherNode) {
                       try {
                               if (node.contains) return node.contains(anotherNode);
                               return node.compareDocumentPosition(anotherNode) & 16;
                       }
                       catch(e) {} // probably a XUL element such as a scrollbar
                       return false;
               }

               // mouseover/mouseout (standards) mode
               function onOverOut(e) {
                       var related = e.relatedTarget;
                       // there might be no relatedTarget if the element is right next
                       // to the window frame
                       if (related && contains(this, related)) return;
                       trigger(this, e.type == 'mouseover');
               }

               // mouseenter/mouseleave (probably ie) mode
               function onEnterLeave(e) {
                       if (!e) e = window.event;
                       // ie model, we don't have access to "this", but
                       // mouseenter/leave doesn't bubble so it's fine.
                       trigger(e.target || e.srcElement, e.type == 'mouseenter');
               }

               function trigger(el, hoverState) {
                       // A timeout is needed so that the event can actually "happen"
                       // before replace is triggered. This ensures that styles are up
                       // to date.
                       setTimeout(function() {
                               var options = sharedStorage.get(el).options;
                               if (hoverState) {
                                      options = merge(options, options.hover);
                                      options._mediatorMode = 1;
                               }
                               api.replace(el, options, true);
                       }, 10);
               }

               this.attach = function(el) {
                       if (el.onmouseenter === undefined) {
                               addEvent(el, 'mouseover', onOverOut);
                               addEvent(el, 'mouseout', onOverOut);
                       }
                       else {
                               addEvent(el, 'mouseenter', onEnterLeave);
                               addEvent(el, 'mouseleave', onEnterLeave);
                       }
               };

               this.detach = function(el) {
                       if (el.onmouseenter === undefined) {
                               removeEvent(el, 'mouseover', onOverOut);
                               removeEvent(el, 'mouseout', onOverOut);
                       }
                       else {
                               removeEvent(el, 'mouseenter', onEnterLeave);
                               removeEvent(el, 'mouseleave', onEnterLeave);
                       }
               };

        }

        function ReplaceHistory() {

               var list = [], map = {};

               function filter(keys) {
                       var values = [], key;
                       for (var i = 0; key = keys[i]; ++i) values[i] = list[map[key]];
                       return values;
               }

               this.add = function(key, args) {
                       map[key] = list.push(args) - 1;
               };

               this.repeat = function() {
                       var snapshot = arguments.length ? filter(arguments) : list, args;
                       for (var i = 0; args = snapshot[i++];) api.replace(args[0], args[1], true);
               };

        }

        function Storage() {

               var map = {}, at = 0;

               function identify(el) {
                       return el.cufid || (el.cufid = ++at);
               }

               this.get = function(el) {
                       var id = identify(el);
                       return map[id] || (map[id] = {});
               };

        }

        function Style(style) {

               var custom = {}, sizes = {};

               this.extend = function(styles) {
                       for (var property in styles) {
                               if (hasOwnProperty(styles, property)) custom[property] = styles[property];
                       }
                       return this;
               };

               this.get = function(property) {
                       return custom[property] != undefined ? custom[property] : style[property];
               };

               this.getSize = function(property, base) {
                       return sizes[property] || (sizes[property] = new CSS.Size(this.get(property), base));
               };

               this.isUsable = function() {
                       return !!style;
               };

        }

        function addEvent(el, type, listener) {
               if (el.addEventListener) {
                       el.addEventListener(type, listener, false);
               }
               else if (el.attachEvent) {
                       // we don't really need "this" right now, saves code
                       el.attachEvent('on' + type, listener);
               }
        }

        function attach(el, options) {
               if (options._mediatorMode) return el;
               var storage = sharedStorage.get(el);
               var oldOptions = storage.options;
               if (oldOptions) {
                       if (oldOptions === options) return el;
                       if (oldOptions.hover) hoverHandler.detach(el);
               }
               if (options.hover && options.hoverables[el.nodeName.toLowerCase()]) {
                       hoverHandler.attach(el);
               }
               storage.options = options;
               return el;
        }

        function cached(fun) {
               var cache = {};
               return function(key) {
                       if (!hasOwnProperty(cache, key)) cache[key] = fun.apply(null, arguments);
                       return cache[key];
               };
        }

        function getFont(el, style) {
               var families = CSS.quotedList(style.get('fontFamily').toLowerCase()), family;
               for (var i = 0; family = families[i]; ++i) {
                       if (fonts[family]) return fonts[family].get(style.get('fontStyle'), style.get('fontWeight'));
               }
               return null;
        }

        function elementsByTagName(query) {
               return document.getElementsByTagName(query);
        }

        function hasOwnProperty(obj, property) {
               return obj.hasOwnProperty(property);
        }

        function merge() {
               var merged = {}, arg, key;
               for (var i = 0, l = arguments.length; arg = arguments[i], i < l; ++i) {
                       for (key in arg) {
                               if (hasOwnProperty(arg, key)) merged[key] = arg[key];
                       }
               }
               return merged;
        }

        function process(font, text, style, options, node, el) {
               var fragment = document.createDocumentFragment(), processed;
               if (text === '') return fragment;
               var separate = options.separate;
               var parts = text.split(separators[separate]), needsAligning = (separate == 'words');
               if (needsAligning && HAS_BROKEN_REGEXP) {
                       // @todo figure out a better way to do this
                       if (/^\s/.test(text)) parts.unshift('');
                       if (/\s$/.test(text)) parts.push('');
               }
               for (var i = 0, l = parts.length; i < l; ++i) {
                       processed = engines[options.engine](font,
                               needsAligning ? CSS.textAlign(parts[i], style, i, l) : parts[i],
                               style, options, node, el, i < l - 1);
                       if (processed) fragment.appendChild(processed);
               }
               return fragment;
        }

        function removeEvent(el, type, listener) {
               if (el.removeEventListener) {
                       el.removeEventListener(type, listener, false);
               }
               else if (el.detachEvent) {
                       el.detachEvent('on' + type, listener);
               }
        }

        function replaceElement(el, options) {
               var name = el.nodeName.toLowerCase();
               if (options.ignore[name]) return;
               if (options.ignoreClass && options.ignoreClass.test(el.className)) return;
               if (options.onBeforeReplace) options.onBeforeReplace(el, options);
               var replace = !options.textless[name], simple = (options.trim === 'simple');
               var style = CSS.getStyle(attach(el, options)).extend(options);
               // may cause issues if the element contains other elements
               // with larger fontSize, however such cases are rare and can
               // be fixed by using a more specific selector
               if (parseFloat(style.get('fontSize')) === 0) return;
               var font = getFont(el, style), node, type, next, anchor, text, lastElement;
               var isShy = options.softHyphens, anyShy = false, pos, shy, reShy = /\u00ad/g;
               var modifyText = options.modifyText;
               if (!font) return;
               for (node = el.firstChild; node; node = next) {
                       type = node.nodeType;
                       next = node.nextSibling;
                       if (replace && type == 3) {
                               if (isShy && el.nodeName.toLowerCase() != TAG_SHY) {
                                      pos = node.data.indexOf('\u00ad');
                                      if (pos >= 0) {
                                              node.splitText(pos);
                                              next = node.nextSibling;
                                              next.deleteData(0, 1);
                                              shy = document.createElement(TAG_SHY);
                                              shy.appendChild(document.createTextNode('\u00ad'));
                                              el.insertBefore(shy, next);
                                              next = shy;
                                              anyShy = true;
                                      }
                               }
                               // Node.normalize() is broken in IE 6, 7, 8
                               if (anchor) {
                                      anchor.appendData(node.data);
                                      el.removeChild(node);
                               }
                               else anchor = node;
                               if (next) continue;
                       }
                       if (anchor) {
                               text = anchor.data;
                               if (!isShy) text = text.replace(reShy, '');
                               text = CSS.whiteSpace(text, style, anchor, lastElement, simple);
                               // modify text only on the first replace
                               if (modifyText) text = modifyText(text, anchor, el, options);
                               el.replaceChild(process(font, text, style, options, node, el), anchor);
                               anchor = null;
                       }
                       if (type == 1) {
                               if (node.firstChild) {
                                      if (node.nodeName.toLowerCase() == 'cufon') {
                                              engines[options.engine](font, null, style, options, node, el);
                                      }
                                      else arguments.callee(node, options);
                               }
                               lastElement = node;
                       }
               }
               if (isShy && anyShy) {
                       updateShy(el);
                       if (!trackingShy) addEvent(window, 'resize', updateShyOnResize);
                       trackingShy = true;
               }
               if (options.onAfterReplace) options.onAfterReplace(el, options);
        }

        function updateShy(context) {
               var shys, shy, parent, glue, newGlue, next, prev, i;
               shys = context.getElementsByTagName(TAG_SHY);
               // unfortunately there doesn't seem to be any easy
               // way to avoid having to loop through the shys twice.
               for (i = 0; shy = shys[i]; ++i) {
                       shy.className = C_SHY_DISABLED;
                       glue = parent = shy.parentNode;
                       if (glue.nodeName.toLowerCase() != TAG_GLUE) {
                               newGlue = document.createElement(TAG_GLUE);
                               newGlue.appendChild(shy.previousSibling);
                               parent.insertBefore(newGlue, shy);
                               newGlue.appendChild(shy);
                       }
                       else {
                               // get rid of double glue (edge case fix)
                               glue = glue.parentNode;
                               if (glue.nodeName.toLowerCase() == TAG_GLUE) {
                                      parent = glue.parentNode;
                                      while (glue.firstChild) {
                                              parent.insertBefore(glue.firstChild, glue);
                                      }
                                      parent.removeChild(glue);
                               }
                       }
               }
               for (i = 0; shy = shys[i]; ++i) {
                       shy.className = '';
                       glue = shy.parentNode;
                       parent = glue.parentNode;
                       next = glue.nextSibling || parent.nextSibling;
                       // make sure we're comparing same types
                       prev = (next.nodeName.toLowerCase() == TAG_GLUE) ? glue : shy.previousSibling;
                       if (prev.offsetTop >= next.offsetTop) {
                               shy.className = C_SHY_DISABLED;
                               if (prev.offsetTop < next.offsetTop) {
                                      // we have an annoying edge case, double the glue
                                      newGlue = document.createElement(TAG_GLUE);
                                      parent.insertBefore(newGlue, glue);
                                      newGlue.appendChild(glue);
                                      newGlue.appendChild(next);
                               }
                       }
               }
        }

        function updateShyOnResize() {
               if (ignoreResize) return; // needed for IE
               CSS.addClass(DOM.root(), C_VIEWPORT_RESIZING);
               clearTimeout(shyTimer);
               shyTimer = setTimeout(function() {
                       ignoreResize = true;
                       CSS.removeClass(DOM.root(), C_VIEWPORT_RESIZING);
                       updateShy(document);
                       ignoreResize = false;
               }, 100);
        }

        var HAS_BROKEN_REGEXP = ' '.split(/\s+/).length == 0;
        var TAG_GLUE = 'cufonglue';
        var TAG_SHY = 'cufonshy';
        var C_SHY_DISABLED = 'cufon-shy-disabled';
        var C_VIEWPORT_RESIZING = 'cufon-viewport-resizing';

        var sharedStorage = new Storage();
        var hoverHandler = new HoverHandler();
        var replaceHistory = new ReplaceHistory();
        var initialized = false;
        var trackingShy = false;
        var shyTimer;
        var ignoreResize = false;

        var engines = {}, fonts = {}, defaultOptions = {
               autoDetect: false,
               engine: null,
               forceHitArea: false,
               hover: false,
               hoverables: {
                       a: true
               },
               ignore: {
                       applet: 1,
                       canvas: 1,
                       col: 1,
                       colgroup: 1,
                       head: 1,
                       iframe: 1,
                       map: 1,
                       noscript: 1,
                       optgroup: 1,
                       option: 1,
                       script: 1,
                       select: 1,
                       style: 1,
                       textarea: 1,
                       title: 1,
                       pre: 1
               },
               ignoreClass: null,
               modifyText: null,
               onAfterReplace: null,
               onBeforeReplace: null,
               printable: true,
               selector: (
                               window.Sizzle
                       ||      (window.jQuery && function(query) { return jQuery(query); }) // avoid noConflict issues
                       ||      (window.dojo && dojo.query)
                       ||      (window.glow && glow.dom && glow.dom.get)
                       ||      (window.Ext && Ext.query)
                       ||      (window.YAHOO && YAHOO.util && YAHOO.util.Selector && YAHOO.util.Selector.query)
                       ||      (window.$$ && function(query) { return $$(query); })
                       ||      (window.$ && function(query) { return $(query); })
                       ||      (document.querySelectorAll && function(query) { return document.querySelectorAll(query); })
                       ||      elementsByTagName
               ),
               separate: 'words', // 'none' and 'characters' are also accepted
               softHyphens: true,
               textless: {
                       dl: 1,
                       html: 1,
                       ol: 1,
                       table: 1,
                       tbody: 1,
                       thead: 1,
                       tfoot: 1,
                       tr: 1,
                       ul: 1
               },
               textShadow: 'none',
               trim: 'advanced'
        };

        var separators = {
               // The first pattern may cause unicode characters above
               // code point 255 to be removed in Safari 3.0. Luckily enough
               // Safari 3.0 does not include non-breaking spaces in \s, so
               // we can just use a simple alternative pattern.
               words: /\s/.test('\u00a0') ? /[^\S\u00a0]+/ : /\s+/,
               characters: '',
               none: /^/
        };

        api.now = function() {
               DOM.ready();
               return api;
        };

        api.refresh = function() {
               replaceHistory.repeat.apply(replaceHistory, arguments);
               return api;
        };

        api.registerEngine = function(id, engine) {
               if (!engine) return api;
               engines[id] = engine;
               return api.set('engine', id);
        };

        api.registerFont = function(data) {
               if (!data) return api;
               var font = new Font(data), family = font.family;
               if (!fonts[family]) fonts[family] = new FontFamily();
               fonts[family].add(font);
               return api.set('fontFamily', '"' + family + '"');
        };

        api.replace = function(elements, options, ignoreHistory) {
               options = merge(defaultOptions, options);
               if (!options.engine) return api; // there's no browser support so we'll just stop here
               if (!initialized) {
                       CSS.addClass(DOM.root(), 'cufon-active cufon-loading');
                       CSS.ready(function() {
                               // fires before any replace() calls, but it doesn't really matter
                               CSS.addClass(CSS.removeClass(DOM.root(), 'cufon-loading'), 'cufon-ready');
                       });
                       initialized = true;
               }
               if (options.hover) options.forceHitArea = true;
               if (options.autoDetect) delete options.fontFamily;
               if (typeof options.ignoreClass == 'string') {
                       options.ignoreClass = new RegExp('(?:^|\\s)(?:' + options.ignoreClass.replace(/\s+/g, '|') + ')(?:\\s|$)');
               }
               if (typeof options.textShadow == 'string') {
                       options.textShadow = CSS.textShadow(options.textShadow);
               }
               if (typeof options.color == 'string' && /^-/.test(options.color)) {
                       options.textGradient = CSS.gradient(options.color);
               }
               else delete options.textGradient;
               if (typeof elements == 'string') {
                       if (!ignoreHistory) replaceHistory.add(elements, arguments);
                       elements = [ elements ];
               }
               else if (elements.nodeType) elements = [ elements ];
               CSS.ready(function() {
                       for (var i = 0, l = elements.length; i < l; ++i) {
                               var el = elements[i];
                               if (typeof el == 'string') api.replace(options.selector(el), options, true);
                               else replaceElement(el, options);
                       }
               });
               return api;
        };

        api.set = function(option, value) {
               defaultOptions[option] = value;
               return api;
        };

        return api;

})();

Cufon.registerEngine('vml', (function() {

        var ns = document.namespaces;
        if (!ns) return;
        ns.add('cvml', 'urn:schemas-microsoft-com:vml');
        ns = null;

        var check = document.createElement('cvml:shape');
        check.style.behavior = 'url(#default#VML)';
        if (!check.coordsize) return; // VML isn't supported
        check = null;

        var HAS_BROKEN_LINEHEIGHT = (document.documentMode || 0) < 8;

        document.write(('<style type="text/css">' +
               'cufoncanvas{text-indent:0;}' +
               '@media screen{' +
                       'cvml\\:shape,cvml\\:rect,cvml\\:fill,cvml\\:shadow{behavior:url(#default#VML);display:block;antialias:true;position:absolute;}' +
                       'cufoncanvas{position:absolute;text-align:left;}' +
                       'cufon{display:inline-block;position:relative;vertical-align:' +
                       (HAS_BROKEN_LINEHEIGHT
                               ? 'middle'
                               : 'text-bottom') +
                       ';}' +
                       'cufon cufontext{position:absolute;left:-10000in;font-size:1px;text-align:left;}' +
                       'cufonshy.cufon-shy-disabled,.cufon-viewport-resizing cufonshy{display:none;}' +
                       'cufonglue{white-space:nowrap;display:inline-block;}' +
                       '.cufon-viewport-resizing cufonglue{white-space:normal;}' +
                       'a cufon{cursor:pointer}' + // ignore !important here
               '}' +
               '@media print{' +
                       'cufon cufoncanvas{display:none;}' +
               '}' +
        '</style>').replace(/;/g, '!important;'));

        function getFontSizeInPixels(el, value) {
               return getSizeInPixels(el, /(?:em|ex|%)$|^[a-z-]+$/i.test(value) ? '1em' : value);
        }

        // Original by Dead Edwards.
        // Combined with getFontSizeInPixels it also works with relative units.
        function getSizeInPixels(el, value) {
               if (!isNaN(value) || /px$/i.test(value)) return parseFloat(value);
               var style = el.style.left, runtimeStyle = el.runtimeStyle.left;
               el.runtimeStyle.left = el.currentStyle.left;
               el.style.left = value.replace('%', 'em');
               var result = el.style.pixelLeft;
               el.style.left = style;
               el.runtimeStyle.left = runtimeStyle;
               return result;
        }

        function getSpacingValue(el, style, size, property) {
               var key = 'computed' + property, value = style[key];
               if (isNaN(value)) {
                       value = style.get(property);
                       style[key] = value = (value == 'normal') ? 0 : ~~size.convertFrom(getSizeInPixels(el, value));
               }
               return value;
        }

        var fills = {};

        function gradientFill(gradient) {
               var id = gradient.id;
               if (!fills[id]) {
                       var stops = gradient.stops, fill = document.createElement('cvml:fill'), colors = [];
                       fill.type = 'gradient';
                       fill.angle = 180;
                       fill.focus = '0';
                       fill.method = 'none';
                       fill.color = stops[0][1];
                       for (var j = 1, k = stops.length - 1; j < k; ++j) {
                               colors.push(stops[j][0] * 100 + '% ' + stops[j][1]);
                       }
                       fill.colors = colors.join(',');
                       fill.color2 = stops[k][1];
                       fills[id] = fill;
               }
               return fills[id];
        }

        return function(font, text, style, options, node, el, hasNext) {

               var redraw = (text === null);

               if (redraw) text = node.alt;

               var viewBox = font.viewBox;

               var size = style.computedFontSize || (style.computedFontSize = new Cufon.CSS.Size(getFontSizeInPixels(el, style.get('fontSize')) + 'px', font.baseSize));

               var wrapper, canvas;

               if (redraw) {
                       wrapper = node;
                       canvas = node.firstChild;
               }
               else {
                       wrapper = document.createElement('cufon');
                       wrapper.className = 'cufon cufon-vml';
                       wrapper.alt = text;

                       canvas = document.createElement('cufoncanvas');
                       wrapper.appendChild(canvas);

                       if (options.printable) {
                               var print = document.createElement('cufontext');
                               print.appendChild(document.createTextNode(text));
                               wrapper.appendChild(print);
                       }

                       // ie6, for some reason, has trouble rendering the last VML element in the document.
                       // we can work around this by injecting a dummy element where needed.
                       // @todo find a better solution
                       if (!hasNext) wrapper.appendChild(document.createElement('cvml:shape'));
               }

               var wStyle = wrapper.style;
               var cStyle = canvas.style;

               var height = size.convert(viewBox.height), roundedHeight = Math.ceil(height);
               var roundingFactor = roundedHeight / height;
               var stretchFactor = roundingFactor * Cufon.CSS.fontStretch(style.get('fontStretch'));
               var minX = viewBox.minX, minY = viewBox.minY;

               cStyle.height = roundedHeight;
               cStyle.top = Math.round(size.convert(minY - font.ascent));
               cStyle.left = Math.round(size.convert(minX));

               wStyle.height = size.convert(font.height) + 'px';

               var color = style.get('color');
               var chars = Cufon.CSS.textTransform(text, style).split('');

               var jumps = font.spacing(chars,
                       getSpacingValue(el, style, size, 'letterSpacing'),
                       getSpacingValue(el, style, size, 'wordSpacing')
               );

               if (!jumps.length) return null;

               var width = jumps.total;
               var fullWidth = -minX + width + (viewBox.width - jumps[jumps.length - 1]);

               var shapeWidth = size.convert(fullWidth * stretchFactor), roundedShapeWidth = Math.round(shapeWidth);

               var coordSize = fullWidth + ',' + viewBox.height, coordOrigin;
               var stretch = 'r' + coordSize + 'ns';

               var fill = options.textGradient && gradientFill(options.textGradient);

               var glyphs = font.glyphs, offsetX = 0;
               var shadows = options.textShadow;
               var i = -1, j = 0, chr;

               while (chr = chars[++i]) {

                       var glyph = glyphs[chars[i]] || font.missingGlyph, shape;
                       if (!glyph) continue;

                       if (redraw) {
                               // some glyphs may be missing so we can't use i
                               shape = canvas.childNodes[j];
                               while (shape.firstChild) shape.removeChild(shape.firstChild); // shadow, fill
                       }
                       else {
                               shape = document.createElement('cvml:shape');
                               canvas.appendChild(shape);
                       }

                       shape.stroked = 'f';
                       shape.coordsize = coordSize;
                       shape.coordorigin = coordOrigin = (minX - offsetX) + ',' + minY;
                       shape.path = (glyph.d ? 'm' + glyph.d + 'xe' : '') + 'm' + coordOrigin + stretch;
                       shape.fillcolor = color;

                       if (fill) shape.appendChild(fill.cloneNode(false));

                       // it's important to not set top/left or IE8 will grind to a halt
                       var sStyle = shape.style;
                       sStyle.width = roundedShapeWidth;
                       sStyle.height = roundedHeight;

                       if (shadows) {
                               // due to the limitations of the VML shadow element there
                               // can only be two visible shadows. opacity is shared
                               // for all shadows.
                               var shadow1 = shadows[0], shadow2 = shadows[1];
                               var color1 = Cufon.CSS.color(shadow1.color), color2;
                               var shadow = document.createElement('cvml:shadow');
                               shadow.on = 't';
                               shadow.color = color1.color;
                               shadow.offset = shadow1.offX + ',' + shadow1.offY;
                               if (shadow2) {
                                      color2 = Cufon.CSS.color(shadow2.color);
                                      shadow.type = 'double';
                                      shadow.color2 = color2.color;
                                      shadow.offset2 = shadow2.offX + ',' + shadow2.offY;
                               }
                               shadow.opacity = color1.opacity || (color2 && color2.opacity) || 1;
                               shape.appendChild(shadow);
                       }

                       offsetX += jumps[j++];
               }

               // addresses flickering issues on :hover

               var cover = shape.nextSibling, coverFill, vStyle;

               if (options.forceHitArea) {

                       if (!cover) {
                               cover = document.createElement('cvml:rect');
                               cover.stroked = 'f';
                               cover.className = 'cufon-vml-cover';
                               coverFill = document.createElement('cvml:fill');
                               coverFill.opacity = 0;
                               cover.appendChild(coverFill);
                               canvas.appendChild(cover);
                       }

                       vStyle = cover.style;

                       vStyle.width = roundedShapeWidth;
                       vStyle.height = roundedHeight;

               }
               else if (cover) canvas.removeChild(cover);

               wStyle.width = Math.max(Math.ceil(size.convert(width * stretchFactor)), 0);

               if (HAS_BROKEN_LINEHEIGHT) {

                       var yAdjust = style.computedYAdjust;

                       if (yAdjust === undefined) {
                               var lineHeight = style.get('lineHeight');
                               if (lineHeight == 'normal') lineHeight = '1em';
                               else if (!isNaN(lineHeight)) lineHeight += 'em'; // no unit
                               style.computedYAdjust = yAdjust = 0.5 * (getSizeInPixels(el, lineHeight) - parseFloat(wStyle.height));
                       }

                       if (yAdjust) {
                               wStyle.marginTop = Math.ceil(yAdjust) + 'px';
                               wStyle.marginBottom = yAdjust + 'px';
                       }

               }

               return wrapper;

        };

})());

Cufon.registerEngine('canvas', (function() {

        // Safari 2 doesn't support .apply() on native methods

        var check = document.createElement('canvas');
        if (!check || !check.getContext || !check.getContext.apply) return;
        check = null;

        var HAS_INLINE_BLOCK = Cufon.CSS.supports('display', 'inline-block');

        // Firefox 2 w/ non-strict doctype (almost standards mode)
        var HAS_BROKEN_LINEHEIGHT = !HAS_INLINE_BLOCK && (document.compatMode == 'BackCompat' || /frameset|transitional/i.test(document.doctype.publicId));

        var styleSheet = document.createElement('style');
        styleSheet.type = 'text/css';
        styleSheet.appendChild(document.createTextNode((
               'cufon{text-indent:0;}' +
               '@media screen,projection{' +
                       'cufon{display:inline;display:inline-block;position:relative;vertical-align:middle;' +
                       (HAS_BROKEN_LINEHEIGHT
                               ? ''
                               : 'font-size:1px;line-height:1px;') +
                       '}cufon cufontext{display:-moz-inline-box;display:inline-block;width:0;height:0;text-align:left;text-indent:-10000in;}' +
                       (HAS_INLINE_BLOCK
                               ? 'cufon canvas{position:relative;}'
                               : 'cufon canvas{position:absolute;}') +
                       'cufonshy.cufon-shy-disabled,.cufon-viewport-resizing cufonshy{display:none;}' +
                       'cufonglue{white-space:nowrap;display:inline-block;}' +
                       '.cufon-viewport-resizing cufonglue{white-space:normal;}' +
               '}' +
               '@media print{' +
                       'cufon{padding:0;}' + // Firefox 2
                       'cufon canvas{display:none;}' +
               '}'
        ).replace(/;/g, '!important;')));
        document.getElementsByTagName('head')[0].appendChild(styleSheet);

        function generateFromVML(path, context) {
               var atX = 0, atY = 0;
               var code = [], re = /([mrvxe])([^a-z]*)/g, match;
               generate: for (var i = 0; match = re.exec(path); ++i) {
                       var c = match[2].split(',');
                       switch (match[1]) {
                               case 'v':
                                      code[i] = { m: 'bezierCurveTo', a: [ atX + ~~c[0], atY + ~~c[1], atX + ~~c[2], atY + ~~c[3], atX += ~~c[4], atY += ~~c[5] ] };
                                      break;
                               case 'r':
                                      code[i] = { m: 'lineTo', a: [ atX += ~~c[0], atY += ~~c[1] ] };
                                      break;
                               case 'm':
                                      code[i] = { m: 'moveTo', a: [ atX = ~~c[0], atY = ~~c[1] ] };
                                      break;
                               case 'x':
                                      code[i] = { m: 'closePath' };
                                      break;
                               case 'e':
                                      break generate;
                       }
                       context[code[i].m].apply(context, code[i].a);
               }
               return code;
        }

        function interpret(code, context) {
               for (var i = 0, l = code.length; i < l; ++i) {
                       var line = code[i];
                       context[line.m].apply(context, line.a);
               }
        }

        return function(font, text, style, options, node, el) {

               var redraw = (text === null);

               if (redraw) text = node.getAttribute('alt');

               var viewBox = font.viewBox;

               var size = style.getSize('fontSize', font.baseSize);

               var expandTop = 0, expandRight = 0, expandBottom = 0, expandLeft = 0;
               var shadows = options.textShadow, shadowOffsets = [];
               if (shadows) {
                       for (var i = shadows.length; i--;) {
                               var shadow = shadows[i];
                               var x = size.convertFrom(parseFloat(shadow.offX));
                               var y = size.convertFrom(parseFloat(shadow.offY));
                               shadowOffsets[i] = [ x, y ];
                               if (y < expandTop) expandTop = y;
                               if (x > expandRight) expandRight = x;
                               if (y > expandBottom) expandBottom = y;
                               if (x < expandLeft) expandLeft = x;
                       }
               }

               var chars = Cufon.CSS.textTransform(text, style).split('');

               var jumps = font.spacing(chars,
                       ~~size.convertFrom(parseFloat(style.get('letterSpacing')) || 0),
                       ~~size.convertFrom(parseFloat(style.get('wordSpacing')) || 0)
               );

               if (!jumps.length) return null; // there's nothing to render

               var width = jumps.total;

               expandRight += viewBox.width - jumps[jumps.length - 1];
               expandLeft += viewBox.minX;

               var wrapper, canvas;

               if (redraw) {
                       wrapper = node;
                       canvas = node.firstChild;
               }
               else {
                       wrapper = document.createElement('cufon');
                       wrapper.className = 'cufon cufon-canvas';
                       wrapper.setAttribute('alt', text);

                       canvas = document.createElement('canvas');
                       wrapper.appendChild(canvas);

                       if (options.printable) {
                               var print = document.createElement('cufontext');
                               print.appendChild(document.createTextNode(text));
                               wrapper.appendChild(print);
                       }
               }

               var wStyle = wrapper.style;
               var cStyle = canvas.style;

               var height = size.convert(viewBox.height);
               var roundedHeight = Math.ceil(height);
               var roundingFactor = roundedHeight / height;
               var stretchFactor = roundingFactor * Cufon.CSS.fontStretch(style.get('fontStretch'));
               var stretchedWidth = width * stretchFactor;

               var canvasWidth = Math.ceil(size.convert(stretchedWidth + expandRight - expandLeft));
               var canvasHeight = Math.ceil(size.convert(viewBox.height - expandTop + expandBottom));

               canvas.width = canvasWidth;
               canvas.height = canvasHeight;

               // needed for WebKit and full page zoom
               cStyle.width = canvasWidth + 'px';
               cStyle.height = canvasHeight + 'px';

               // minY has no part in canvas.height
               expandTop += viewBox.minY;

               cStyle.top = Math.round(size.convert(expandTop - font.ascent)) + 'px';
               cStyle.left = Math.round(size.convert(expandLeft)) + 'px';

               var wrapperWidth = Math.max(Math.ceil(size.convert(stretchedWidth)), 0) + 'px';

               if (HAS_INLINE_BLOCK) {
                       wStyle.width = wrapperWidth;
                       wStyle.height = size.convert(font.height) + 'px';
               }
               else {
                       wStyle.paddingLeft = wrapperWidth;
                       wStyle.paddingBottom = (size.convert(font.height) - 1) + 'px';
               }

               var g = canvas.getContext('2d'), scale = height / viewBox.height;

               // proper horizontal scaling is performed later
               g.scale(scale, scale * roundingFactor);
               g.translate(-expandLeft, -expandTop);
               g.save();

               function renderText() {
                       var glyphs = font.glyphs, glyph, i = -1, j = -1, chr;
                       g.scale(stretchFactor, 1);
                       while (chr = chars[++i]) {
                               var glyph = glyphs[chars[i]] || font.missingGlyph;
                               if (!glyph) continue;
                               if (glyph.d) {
                                      g.beginPath();
                                      if (glyph.code) interpret(glyph.code, g);
                                      else glyph.code = generateFromVML('m' + glyph.d, g);
                                      g.fill();
                               }
                               g.translate(jumps[++j], 0);
                       }
                       g.restore();
               }

               if (shadows) {
                       for (var i = shadows.length; i--;) {
                               var shadow = shadows[i];
                               g.save();
                               g.fillStyle = shadow.color;
                               g.translate.apply(g, shadowOffsets[i]);
                               renderText();
                       }
               }

               var gradient = options.textGradient;
               if (gradient) {
                       var stops = gradient.stops, fill = g.createLinearGradient(0, viewBox.minY, 0, viewBox.maxY);
                       for (var i = 0, l = stops.length; i < l; ++i) {
                               fill.addColorStop.apply(fill, stops[i]);
                       }
                       g.fillStyle = fill;
               }
               else g.fillStyle = style.get('color');

               renderText();

               return wrapper;

        };

})());


