// JavaScript Document
/*!
 * Copyright (c) 2009 Simo Kinnunen.
 * Licensed under the MIT license.
 */

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);
            };

        })()

    };

    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;
            };

        },

        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;
        }),

        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;
        }),

        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;
        },

        textDecoration: function(el, style) {
            if (!style) style = this.getStyle(el);
            var types = {
                underline: null,
                overline: null,
                'line-through': null
            };
            for (var search = el; search.parentNode && search.parentNode.nodeType == 1; ) {
                var foundAll = true;
                for (var type in types) {
                    if (!hasOwnProperty(types, type) || types[type]) continue;
                    if (style.get('textDecoration').indexOf(type) != -1) types[type] = style.get('color');
                    foundAll = false;
                }
                if (foundAll) break; // this is rather unlikely to happen
                style = this.getStyle(search = search.parentNode);
            }
            return types;
        },

        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(text, style) {
            return text[{
                uppercase: 'toUpperCase',
                lowercase: 'toLowerCase'
}[style.get('textTransform')] || 'toString']();
            },

            whiteSpace: (function() {
                var ignore = {
                    inline: 1,
                    'inline-block': 1,
                    'run-in': 1
                };
                return function(text, style, node) {
                    if (ignore[style.get('display')]) return text;
                    if (!node.previousSibling) text = text.replace(/^\s+/, '');
                    if (!node.nextSibling) text = text.replace(/\s+$/, '');
                    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');

            function isContainerReady(el) {
                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;
            this.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;

        }

        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) {
                        if (node.contains) return node.contains(anotherNode);
                        return node.compareDocumentPosition(anotherNode) & 16;
                    }

                    function onOverOut(e) {
                        var related = e.relatedTarget;
                        if (!related || contains(this, related)) return;
                        trigger(this);
                    }

                    function onEnterLeave(e) {
                        trigger(this);
                    }

                    function trigger(el) {
                        // 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() {
                            api.replace(el, sharedStorage.get(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);
                        }
                    };

                }

                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) {
                        el.attachEvent('on' + type, function() {
                            return listener.call(el, window.event);
                        });
                    }
                }

                function attach(el, options) {
                    var storage = sharedStorage.get(el);
                    if (storage.options) return 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) {
                    if (!style) style = CSS.getStyle(el);
                    var families = CSS.quotedList(style.get('fontFamily').toLowerCase()), family;
                    for (var i = 0, l = families.length; i < l; ++i) {
                        family = families[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 = {}, args, key;
                    for (var i = 0, l = arguments.length; args = arguments[i], i < l; ++i) {
                        for (key in args) {
                            if (hasOwnProperty(args, key)) merged[key] = args[key];
                        }
                    }
                    return merged;
                }

                function process(font, text, style, options, node, el) {
                    var separate = options.separate;
                    if (separate == 'none') return engines[options.engine].apply(null, arguments);
                    var fragment = document.createDocumentFragment(), processed;
                    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 replaceElement(el, options) {
                    var font, style, node, nodeType, nextNode, redraw;
                    for (node = attach(el, options).firstChild; node; node = nextNode) {
                        nodeType = node.nodeType;
                        nextNode = node.nextSibling;
                        redraw = false;
                        if (nodeType == 1) {
                            if (!node.firstChild) continue;
                            if (!/cufon/.test(node.className)) {
                                arguments.callee(node, options);
                                continue;
                            }
                            else redraw = true;
                        }
                        else if (nodeType != 3) continue;
                        if (!style) style = CSS.getStyle(el).extend(options);
                        if (!font) font = getFont(el, style);
                        if (!font) continue;
                        if (redraw) {
                            engines[options.engine](font, null, style, options, node, el);
                            continue;
                        }
                        var text = CSS.whiteSpace(node.data, style, node);
                        if (text === '') continue;
                        var processed = process(font, text, style, options, node, el);
                        if (processed) node.parentNode.replaceChild(processed, node);
                        else node.parentNode.removeChild(node);
                    }
                }

                var HAS_BROKEN_REGEXP = ' '.split(/\s+/).length == 0;

                var sharedStorage = new Storage();
                var hoverHandler = new HoverHandler();
                var replaceHistory = new ReplaceHistory();

                var engines = {}, fonts = {}, defaultOptions = {
                    enableTextDecoration: false,
                    engine: null,
                    //fontScale: 1,
                    //fontScaling: false,
                    hover: false,
                    hoverables: {
                        a: true
                    },
                    printable: true,
                    //rotation: 0,
                    //selectable: false,
                    selector: (
				window.Sizzle
			|| (window.jQuery && function(query) { return jQuery(query); }) // avoid noConflict issues
			|| (window.dojo && dojo.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
                    textShadow: 'none'
                };

                var separators = {
                    words: /[^\S\u00a0]+/,
                    characters: ''
                };

                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) {
                    var font = new Font(data), family = font.family;
                    if (!fonts[family]) fonts[family] = new FontFamily();
                    fonts[family].add(font);
                    return api.set('fontFamily', '"' + family + '"');
                };

                //@EDIT ---------------
                api.callbackEach = function() { };
                api.callbackFinished = function() { };
                //---------------------

                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 (typeof options.textShadow == 'string')
                        options.textShadow = CSS.textShadow(options.textShadow);
                    if (typeof options.color == 'string' && /^-/.test(options.color))
                        options.textGradient = CSS.gradient(options.color);
                    if (!ignoreHistory) replaceHistory.add(elements, arguments);
                    if (elements.nodeType || typeof elements == 'string') 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);
                            //@EDIT ---------------
                            api.callbackEach.apply(el);
                            //---------------------
                        }
                        //@EDIT ---------------
                        api.callbackFinished.apply(elements);
                        //---------------------
                    });

                    return api;
                };

                api.set = function(option, value) {
                    defaultOptions[option] = value;
                    return api;
                };

                return api;

            })();

            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-canvas{text-indent:0;}' +
		'@media screen,projection{' +
			'.cufon-canvas{display:inline;display:inline-block;position:relative;vertical-align:middle;' +
			(HAS_BROKEN_LINEHEIGHT
				? ''
				: 'font-size:1px;line-height:1px;') +
			'}.cufon-canvas .cufon-alt{display:-moz-inline-box;display:inline-block;width:0;height:0;overflow:hidden;}' +
			(HAS_INLINE_BLOCK
				? '.cufon-canvas canvas{position:relative;}'
				: '.cufon-canvas canvas{position:absolute;}') +
		'}' +
		'@media print{' +
			'.cufon-canvas{padding:0;}' +
			'.cufon-canvas canvas{display:none;}' +
			'.cufon-canvas .cufon-alt{display:inline;}' +
		'}'
	).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.alt;
                    
                    var viewBox = font.viewBox;

                    var size = style.getSize('fontSize', font.baseSize);

                    var letterSpacing = style.get('letterSpacing');
                    letterSpacing = (letterSpacing == 'normal') ? 0 : size.convertFrom(parseInt(letterSpacing, 10));

                    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(''), chr;

                    var glyphs = font.glyphs, glyph, kerning, k;
                    var width = 0, advance, jumps = [];

                    for (var i = 0, j = 0, l = chars.length; i < l; ++i) {
                        glyph = glyphs[chr = chars[i]] || font.missingGlyph;
                        if (!glyph) continue;
                        if (kerning) {
                            width -= k = kerning[chr] || 0;
                            jumps[j - 1] -= k;
                        }
                        width += advance = jumps[j++] = ~ ~(glyph.w || font.w) + letterSpacing;
                        kerning = glyph.k;
                    }

                    if (advance === undefined) return null; // there's nothing to render

                    expandRight += viewBox.width - advance;
                    expandLeft += viewBox.minX;

                    var wrapper, canvas;

                    if (redraw) {
                        wrapper = node;
                        canvas = node.firstChild;
                    }
                    else {
                        wrapper = document.createElement('span');
                        wrapper.className = 'cufon cufon-canvas';
                        wrapper.alt = text;

                        canvas = document.createElement('canvas');
                        wrapper.appendChild(canvas);

                        if (options.printable) {
                            var print = document.createElement('span');
                            print.className = 'cufon-alt';
                            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;

                    canvas.width = Math.ceil(size.convert(width * roundingFactor + expandRight - expandLeft));
                    canvas.height = Math.ceil(size.convert(viewBox.height - expandTop + expandBottom));

                    // 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.ceil(size.convert(width * roundingFactor)) + '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.lineWidth = font.face['underline-thickness'];

                    g.save();

                    function line(y, color) {
                        g.strokeStyle = color;

                        g.beginPath();

                        g.moveTo(0, y);
                        g.lineTo(width, y);

                        g.stroke();
                    }

                    var textDecoration = options.enableTextDecoration ? Cufon.CSS.textDecoration(el, style) : {};

                    if (textDecoration.underline) line(-font.face['underline-position'], textDecoration.underline);
                    if (textDecoration.overline) line(font.ascent, textDecoration.overline);

                    function renderText() {
                        g.scale(roundingFactor, 1);
                        for (var i = 0, j = 0, l = chars.length; i < l; ++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();

                    if (textDecoration['line-through']) line(-font.descent, textDecoration['line-through']);

                    return wrapper;

                };

            })());

Cufon.registerEngine('vml', (function() {

	if (!document.namespaces) return;
	
	if (document.namespaces.cvml == null) {
		document.namespaces.add('cvml', 'urn:schemas-microsoft-com:vml');
	}
	
	var check = document.createElement('cvml:shape');
	check.style.behavior = 'url(#default#VML)';
	if (!check.coordsize) return; // VML isn't supported
	check = null;
	
	document.write(('<style type="text/css">' +
		'.cufon-vml-canvas{text-indent:0;}' +
		'@media screen{' + 
			'cvml\\:shape,cvml\\:rect,cvml\\:fill,cvml\\:shadow{behavior:url(#default#VML);display:block;antialias:true;position:absolute;}' +
			'.cufon-vml-canvas{position:absolute;text-align:left;}' +
			'.cufon-vml{display:inline-block;position:relative;vertical-align:middle;}' +
			'.cufon-vml .cufon-alt{position:absolute;left:-10000in;font-size:1px;}' +
			'a .cufon-vml{cursor:pointer}' + // ignore !important here
		'}' +
		'@media print{' + 
			'.cufon-vml *{display:none;}' +
			'.cufon-vml .cufon-alt{display:inline;}' +
		'}' +
	'</style>').replace(/;/g, '!important;'));

	function getFontSizeInPixels(el, value) {
		return getSizeInPixels(el, /(?:em|ex|%)$/i.test(value) ? '1em' : value);
	}
	
	// Original by Dead Edwards.
	// Combined with getFontSizeInPixels it also works with relative units.
	function getSizeInPixels(el, value) {
		if (/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;
		var result = el.style.pixelLeft;
		el.style.left = style;
		el.runtimeStyle.left = runtimeStyle;
		return result;
	}
	
	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 = 'sigma';
			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;
		
		// @todo word-spacing, text-decoration
	
		var viewBox = font.viewBox;
		
		var size = style.computedFontSize || (style.computedFontSize = new Cufon.CSS.Size(getFontSizeInPixels(el, style.get('fontSize')) + 'px', font.baseSize));
		
		var letterSpacing = style.computedLSpacing;
		
		if (letterSpacing == undefined) {
			letterSpacing = style.get('letterSpacing');
			style.computedLSpacing = letterSpacing = (letterSpacing == 'normal') ? 0 : ~~size.convertFrom(getSizeInPixels(el, letterSpacing));
		}
		
		var wrapper, canvas;
		
		if (redraw) {
			wrapper = node;
			canvas = node.firstChild;
		}
		else {
			wrapper = document.createElement('span');
			wrapper.className = 'cufon cufon-vml';
			wrapper.alt = text;
			
			canvas = document.createElement('span');
			canvas.className = 'cufon-vml-canvas';
			wrapper.appendChild(canvas);
			
			if (options.printable) {
				var print = document.createElement('span');
				print.className = 'cufon-alt';
				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 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 textDecoration = options.enableTextDecoration ? Cufon.CSS.textDecoration(el, style) : {};
		
		var color = style.get('color');
		var chars = Cufon.CSS.textTransform(text, style).split(''), chr;
		
		var glyphs = font.glyphs, glyph, kerning, k;
		var width = 0, jumps = [], offsetX = 0, advance;
		
		var shape, shadows = options.textShadow;
		
		// pre-calculate width
		for (var i = 0, j = 0, l = chars.length; i < l; ++i) {
			glyph = glyphs[chr = chars[i]] || font.missingGlyph;
			if (!glyph) continue;
			if (kerning) {
				width -= k = kerning[chr] || 0;
				jumps[j - 1] -= k;
			}
			width += advance = jumps[j++] = ~~(glyph.w || font.w) + letterSpacing;
			kerning = glyph.k;
		}
		
		if (advance === undefined) return null;
		
		var fullWidth = -minX + width + (viewBox.width - advance);
	
		var shapeWidth = size.convert(fullWidth * roundingFactor), roundedShapeWidth = Math.round(shapeWidth);
		
		var coordSize = fullWidth + ',' + viewBox.height, coordOrigin;
		var stretch = 'r' + coordSize + 'ns';
		
		var fill = options.textGradient && gradientFill(options.textGradient);
		
		for (i = 0, j = 0; i < l; ++i) {
			
			glyph = glyphs[chars[i]] || font.missingGlyph;
			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.hover) {
			
			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 * roundingFactor)), 0);
		
		return wrapper;
		
	};
	
})());


