Advertisement
Souheyel11

cufon-yui.js

Jul 11th, 2014
640
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
  1. /*!
  2.  * Copyright (c) 2011 Simo Kinnunen.
  3.  * Licensed under the MIT license.
  4.  *
  5.  * @version ${Version}
  6.  */
  7.  
  8. var Cufon = (function() {
  9.  
  10.     var api = function() {
  11.         return api.replace.apply(null, arguments);
  12.     };
  13.  
  14.     var DOM = api.DOM = {
  15.  
  16.         ready: (function() {
  17.  
  18.             var complete = false, readyStatus = { loaded: 1, complete: 1 };
  19.  
  20.             var queue = [], perform = function() {
  21.                 if (complete) return;
  22.                 complete = true;
  23.                 for (var fn; fn = queue.shift(); fn());
  24.             };
  25.  
  26.             // Gecko, Opera, WebKit r26101+
  27.  
  28.             if (document.addEventListener) {
  29.                 document.addEventListener('DOMContentLoaded', perform, false);
  30.                 window.addEventListener('pageshow', perform, false); // For cached Gecko pages
  31.             }
  32.  
  33.             // Old WebKit, Internet Explorer
  34.  
  35.             if (!window.opera && document.readyState) (function() {
  36.                 readyStatus[document.readyState] ? perform() : setTimeout(arguments.callee, 10);
  37.             })();
  38.  
  39.             // Internet Explorer
  40.  
  41.             if (document.readyState && document.createStyleSheet) (function() {
  42.                 try {
  43.                     document.body.doScroll('left');
  44.                     perform();
  45.                 }
  46.                 catch (e) {
  47.                     setTimeout(arguments.callee, 1);
  48.                 }
  49.             })();
  50.  
  51.             addEvent(window, 'load', perform); // Fallback
  52.  
  53.             return function(listener) {
  54.                 if (!arguments.length) perform();
  55.                 else complete ? listener() : queue.push(listener);
  56.             };
  57.  
  58.         })(),
  59.  
  60.         root: function() {
  61.             return document.documentElement || document.body;
  62.         },
  63.  
  64.         strict: (function() {
  65.             var doctype;
  66.             // no doctype (doesn't always catch it though.. IE I'm looking at you)
  67.             if (document.compatMode == 'BackCompat') return false;
  68.             // WebKit, Gecko, Opera, IE9+
  69.             doctype = document.doctype;
  70.             if (doctype) {
  71.                 return !/frameset|transitional/i.test(doctype.publicId);
  72.             }
  73.             // IE<9, firstChild is the doctype even if there's an XML declaration
  74.             doctype = document.firstChild;
  75.             if (doctype.nodeType != 8 || /^DOCTYPE.+(transitional|frameset)/i.test(doctype.data)) {
  76.                 return false;
  77.             }
  78.             return true;
  79.         })()
  80.  
  81.     };
  82.  
  83.     var CSS = api.CSS = {
  84.  
  85.         Size: function(value, base) {
  86.  
  87.             this.value = parseFloat(value);
  88.             this.unit = String(value).match(/[a-z%]*$/)[0] || 'px';
  89.  
  90.             this.convert = function(value) {
  91.                 return value / base * this.value;
  92.             };
  93.  
  94.             this.convertFrom = function(value) {
  95.                 return value / this.value * base;
  96.             };
  97.  
  98.             this.toString = function() {
  99.                 return this.value + this.unit;
  100.             };
  101.  
  102.         },
  103.  
  104.         addClass: function(el, className) {
  105.             var current = el.className;
  106.             el.className = current + (current && ' ') + className;
  107.             return el;
  108.         },
  109.  
  110.         color: cached(function(value) {
  111.             var parsed = {};
  112.             parsed.color = value.replace(/^rgba\((.*?),\s*([\d.]+)\)/, function($0, $1, $2) {
  113.                 parsed.opacity = parseFloat($2);
  114.                 return 'rgb(' + $1 + ')';
  115.             });
  116.             return parsed;
  117.         }),
  118.  
  119.         // has no direct CSS equivalent.
  120.         // @see http://msdn.microsoft.com/en-us/library/system.windows.fontstretches.aspx
  121.         fontStretch: cached(function(value) {
  122.             if (typeof value == 'number') return value;
  123.             if (/%$/.test(value)) return parseFloat(value) / 100;
  124.             return {
  125.                 'ultra-condensed': 0.5,
  126.                 'extra-condensed': 0.625,
  127.                 condensed: 0.75,
  128.                 'semi-condensed': 0.875,
  129.                 'semi-expanded': 1.125,
  130.                 expanded: 1.25,
  131.                 'extra-expanded': 1.5,
  132.                 'ultra-expanded': 2
  133.             }[value] || 1;
  134.         }),
  135.  
  136.         getStyle: function(el) {
  137.             var view = document.defaultView;
  138.             if (view && view.getComputedStyle) return new Style(view.getComputedStyle(el, null));
  139.             if (el.currentStyle) return new Style(el.currentStyle);
  140.             return new Style(el.style);
  141.         },
  142.  
  143.         gradient: cached(function(value) {
  144.             var gradient = {
  145.                 id: value,
  146.                 type: value.match(/^-([a-z]+)-gradient\(/)[1],
  147.                 stops: []
  148.             }, colors = value.substr(value.indexOf('(')).match(/([\d.]+=)?(#[a-f0-9]+|[a-z]+\(.*?\)|[a-z]+)/ig);
  149.             for (var i = 0, l = colors.length, stop; i < l; ++i) {
  150.                 stop = colors[i].split('=', 2).reverse();
  151.                 gradient.stops.push([ stop[1] || i / (l - 1), stop[0] ]);
  152.             }
  153.             return gradient;
  154.         }),
  155.  
  156.         quotedList: cached(function(value) {
  157.             // doesn't work properly with empty quoted strings (""), but
  158.             // it's not worth the extra code.
  159.             var list = [], re = /\s*((["'])([\s\S]*?[^\\])\2|[^,]+)\s*/g, match;
  160.             while (match = re.exec(value)) list.push(match[3] || match[1]);
  161.             return list;
  162.         }),
  163.  
  164.         recognizesMedia: cached(function(media) {
  165.             var el = document.createElement('style'), sheet, container, supported;
  166.             el.type = 'text/css';
  167.             el.media = media;
  168.             try { // this is cached anyway
  169.                 el.appendChild(document.createTextNode('/**/'));
  170.             } catch (e) {}
  171.             container = elementsByTagName('head')[0];
  172.             container.insertBefore(el, container.firstChild);
  173.             sheet = (el.sheet || el.styleSheet);
  174.             supported = sheet && !sheet.disabled;
  175.             container.removeChild(el);
  176.             return supported;
  177.         }),
  178.  
  179.         removeClass: function(el, className) {
  180.             var re = RegExp('(?:^|\\s+)' + className +  '(?=\\s|$)', 'g');
  181.             el.className = el.className.replace(re, '');
  182.             return el;
  183.         },
  184.  
  185.         supports: function(property, value) {
  186.             var checker = document.createElement('span').style;
  187.             if (checker[property] === undefined) return false;
  188.             checker[property] = value;
  189.             return checker[property] === value;
  190.         },
  191.  
  192.         textAlign: function(word, style, position, wordCount) {
  193.             if (style.get('textAlign') == 'right') {
  194.                 if (position > 0) word = ' ' + word;
  195.             }
  196.             else if (position < wordCount - 1) word += ' ';
  197.             return word;
  198.         },
  199.  
  200.         textShadow: cached(function(value) {
  201.             if (value == 'none') return null;
  202.             var shadows = [], currentShadow = {}, result, offCount = 0;
  203.             var re = /(#[a-f0-9]+|[a-z]+\(.*?\)|[a-z]+)|(-?[\d.]+[a-z%]*)|,/ig;
  204.             while (result = re.exec(value)) {
  205.                 if (result[0] == ',') {
  206.                     shadows.push(currentShadow);
  207.                     currentShadow = {};
  208.                     offCount = 0;
  209.                 }
  210.                 else if (result[1]) {
  211.                     currentShadow.color = result[1];
  212.                 }
  213.                 else {
  214.                     currentShadow[[ 'offX', 'offY', 'blur' ][offCount++]] = result[2];
  215.                 }
  216.             }
  217.             shadows.push(currentShadow);
  218.             return shadows;
  219.         }),
  220.  
  221.         textTransform: (function() {
  222.             var map = {
  223.                 uppercase: function(s) {
  224.                     return s.toUpperCase();
  225.                 },
  226.                 lowercase: function(s) {
  227.                     return s.toLowerCase();
  228.                 },
  229.                 capitalize: function(s) {
  230.                     return s.replace(/(?:^|\s)./g, function($0) {
  231.                         return $0.toUpperCase();
  232.                     });
  233.                 }
  234.             };
  235.             return function(text, style) {
  236.                 var transform = map[style.get('textTransform')];
  237.                 return transform ? transform(text) : text;
  238.             };
  239.         })(),
  240.  
  241.         whiteSpace: (function() {
  242.             var ignore = {
  243.                 inline: 1,
  244.                 'inline-block': 1,
  245.                 'run-in': 1
  246.             };
  247.             var wsStart = /^\s+/, wsEnd = /\s+$/;
  248.             return function(text, style, node, previousElement, simple) {
  249.                 if (simple) return text.replace(wsStart, '').replace(wsEnd, ''); // @fixme too simple
  250.                 if (previousElement) {
  251.                     if (previousElement.nodeName.toLowerCase() == 'br') {
  252.                         text = text.replace(wsStart, '');
  253.                     }
  254.                 }
  255.                 if (ignore[style.get('display')]) return text;
  256.                 if (!node.previousSibling) text = text.replace(wsStart, '');
  257.                 if (!node.nextSibling) text = text.replace(wsEnd, '');
  258.                 return text;
  259.             };
  260.         })()
  261.  
  262.     };
  263.  
  264.     CSS.ready = (function() {
  265.  
  266.         // don't do anything in Safari 2 (it doesn't recognize any media type)
  267.         var complete = !CSS.recognizesMedia('all'), hasLayout = false;
  268.  
  269.         var queue = [], perform = function() {
  270.             complete = true;
  271.             for (var fn; fn = queue.shift(); fn());
  272.         };
  273.  
  274.         var links = elementsByTagName('link'), styles = elementsByTagName('style');
  275.  
  276.         var checkTypes = {
  277.             '': 1,
  278.             'text/css': 1
  279.         };
  280.  
  281.         function isContainerReady(el) {
  282.             if (!checkTypes[el.type.toLowerCase()]) return true;
  283.             return el.disabled || isSheetReady(el.sheet, el.media || 'screen');
  284.         }
  285.  
  286.         function isSheetReady(sheet, media) {
  287.             // in Opera sheet.disabled is true when it's still loading,
  288.             // even though link.disabled is false. they stay in sync if
  289.             // set manually.
  290.             if (!CSS.recognizesMedia(media || 'all')) return true;
  291.             if (!sheet || sheet.disabled) return false;
  292.             try {
  293.                 var rules = sheet.cssRules, rule;
  294.                 if (rules) {
  295.                     // needed for Safari 3 and Chrome 1.0.
  296.                     // in standards-conforming browsers cssRules contains @-rules.
  297.                     // Chrome 1.0 weirdness: rules[<number larger than .length - 1>]
  298.                     // returns the last rule, so a for loop is the only option.
  299.                     search: for (var i = 0, l = rules.length; rule = rules[i], i < l; ++i) {
  300.                         switch (rule.type) {
  301.                             case 2: // @charset
  302.                                 break;
  303.                             case 3: // @import
  304.                                 if (!isSheetReady(rule.styleSheet, rule.media.mediaText)) return false;
  305.                                 break;
  306.                             default:
  307.                                 // only @charset can precede @import
  308.                                 break search;
  309.                         }
  310.                     }
  311.                 }
  312.             }
  313.             catch (e) {} // probably a style sheet from another domain
  314.             return true;
  315.         }
  316.  
  317.         function allStylesLoaded() {
  318.             // Internet Explorer's style sheet model, there's no need to do anything
  319.             if (document.createStyleSheet) return true;
  320.             // standards-compliant browsers
  321.             var el, i;
  322.             for (i = 0; el = links[i]; ++i) {
  323.                 if (el.rel.toLowerCase() == 'stylesheet' && !isContainerReady(el)) return false;
  324.             }
  325.             for (i = 0; el = styles[i]; ++i) {
  326.                 if (!isContainerReady(el)) return false;
  327.             }
  328.             return true;
  329.         }
  330.  
  331.         DOM.ready(function() {
  332.             // getComputedStyle returns null in Gecko if used in an iframe with display: none
  333.             if (!hasLayout) hasLayout = CSS.getStyle(document.body).isUsable();
  334.             if (complete || (hasLayout && allStylesLoaded())) perform();
  335.             else setTimeout(arguments.callee, 10);
  336.         });
  337.  
  338.         return function(listener) {
  339.             if (complete) listener();
  340.             else queue.push(listener);
  341.         };
  342.  
  343.     })();
  344.  
  345.     function Font(data) {
  346.  
  347.         var face = this.face = data.face, wordSeparators = {
  348.             '\u0020': 1,
  349.             '\u00a0': 1,
  350.             '\u3000': 1
  351.         };
  352.  
  353.         this.glyphs = (function(glyphs) {
  354.             var key, fallbacks = {
  355.                 '\u2011': '\u002d',
  356.                 '\u00ad': '\u2011'
  357.             };
  358.             for (key in fallbacks) {
  359.                 if (!hasOwnProperty(fallbacks, key)) continue;
  360.                 if (!glyphs[key]) glyphs[key] = glyphs[fallbacks[key]];
  361.             }
  362.             return glyphs;
  363.         })(data.glyphs);
  364.  
  365.         this.w = data.w;
  366.         this.baseSize = parseInt(face['units-per-em'], 10);
  367.  
  368.         this.family = face['font-family'].toLowerCase();
  369.         this.weight = face['font-weight'];
  370.         this.style = face['font-style'] || 'normal';
  371.  
  372.         this.viewBox = (function () {
  373.             var parts = face.bbox.split(/\s+/);
  374.             var box = {
  375.                 minX: parseInt(parts[0], 10),
  376.                 minY: parseInt(parts[1], 10),
  377.                 maxX: parseInt(parts[2], 10),
  378.                 maxY: parseInt(parts[3], 10)
  379.             };
  380.             box.width = box.maxX - box.minX;
  381.             box.height = box.maxY - box.minY;
  382.             box.toString = function() {
  383.                 return [ this.minX, this.minY, this.width, this.height ].join(' ');
  384.             };
  385.             return box;
  386.         })();
  387.  
  388.         this.ascent = -parseInt(face.ascent, 10);
  389.         this.descent = -parseInt(face.descent, 10);
  390.  
  391.         this.height = -this.ascent + this.descent;
  392.  
  393.         this.spacing = function(chars, letterSpacing, wordSpacing) {
  394.             var glyphs = this.glyphs, glyph,
  395.                 kerning, k,
  396.                 jumps = [],
  397.                 width = 0, w,
  398.                 i = -1, j = -1, chr;
  399.             while (chr = chars[++i]) {
  400.                 glyph = glyphs[chr] || this.missingGlyph;
  401.                 if (!glyph) continue;
  402.                 if (kerning) {
  403.                     width -= k = kerning[chr] || 0;
  404.                     jumps[j] -= k;
  405.                 }
  406.                 w = glyph.w;
  407.                 if (isNaN(w)) w = +this.w; // may have been a String in old fonts
  408.                 if (w > 0) {
  409.                     w += letterSpacing;
  410.                     if (wordSeparators[chr]) w += wordSpacing;
  411.                 }
  412.                 width += jumps[++j] = ~~w; // get rid of decimals
  413.                 kerning = glyph.k;
  414.             }
  415.             jumps.total = width;
  416.             return jumps;
  417.         };
  418.  
  419.     }
  420.  
  421.     function FontFamily() {
  422.  
  423.         var styles = {}, mapping = {
  424.             oblique: 'italic',
  425.             italic: 'oblique'
  426.         };
  427.  
  428.         this.add = function(font) {
  429.             (styles[font.style] || (styles[font.style] = {}))[font.weight] = font;
  430.         };
  431.  
  432.         this.get = function(style, weight) {
  433.             var weights = styles[style] || styles[mapping[style]]
  434.                 || styles.normal || styles.italic || styles.oblique;
  435.             if (!weights) return null;
  436.             // we don't have to worry about "bolder" and "lighter"
  437.             // because IE's currentStyle returns a numeric value for it,
  438.             // and other browsers use the computed value anyway
  439.             weight = {
  440.                 normal: 400,
  441.                 bold: 700
  442.             }[weight] || parseInt(weight, 10);
  443.             if (weights[weight]) return weights[weight];
  444.             // http://www.w3.org/TR/CSS21/fonts.html#propdef-font-weight
  445.             // Gecko uses x99/x01 for lighter/bolder
  446.             var up = {
  447.                 1: 1,
  448.                 99: 0
  449.             }[weight % 100], alts = [], min, max;
  450.             if (up === undefined) up = weight > 400;
  451.             if (weight == 500) weight = 400;
  452.             for (var alt in weights) {
  453.                 if (!hasOwnProperty(weights, alt)) continue;
  454.                 alt = parseInt(alt, 10);
  455.                 if (!min || alt < min) min = alt;
  456.                 if (!max || alt > max) max = alt;
  457.                 alts.push(alt);
  458.             }
  459.             if (weight < min) weight = min;
  460.             if (weight > max) weight = max;
  461.             alts.sort(function(a, b) {
  462.                 return (up
  463.                     ? (a >= weight && b >= weight) ? a < b : a > b
  464.                     : (a <= weight && b <= weight) ? a > b : a < b) ? -1 : 1;
  465.             });
  466.             return weights[alts[0]];
  467.         };
  468.  
  469.     }
  470.  
  471.     function HoverHandler() {
  472.  
  473.         function contains(node, anotherNode) {
  474.             try {
  475.                 if (node.contains) return node.contains(anotherNode);
  476.                 return node.compareDocumentPosition(anotherNode) & 16;
  477.             }
  478.             catch(e) {} // probably a XUL element such as a scrollbar
  479.             return false;
  480.         }
  481.  
  482.         // mouseover/mouseout (standards) mode
  483.         function onOverOut(e) {
  484.             var related = e.relatedTarget;
  485.             // there might be no relatedTarget if the element is right next
  486.             // to the window frame
  487.             if (related && contains(this, related)) return;
  488.             trigger(this, e.type == 'mouseover');
  489.         }
  490.  
  491.         // mouseenter/mouseleave (probably ie) mode
  492.         function onEnterLeave(e) {
  493.             if (!e) e = window.event;
  494.             // ie model, we don't have access to "this", but
  495.             // mouseenter/leave doesn't bubble so it's fine.
  496.             trigger(e.target || e.srcElement, e.type == 'mouseenter');
  497.         }
  498.  
  499.         function trigger(el, hoverState) {
  500.             // A timeout is needed so that the event can actually "happen"
  501.             // before replace is triggered. This ensures that styles are up
  502.             // to date.
  503.             setTimeout(function() {
  504.                 var options = sharedStorage.get(el).options;
  505.                 if (hoverState) {
  506.                     options = merge(options, options.hover);
  507.                     options._mediatorMode = 1;
  508.                 }
  509.                 api.replace(el, options, true);
  510.             }, 10);
  511.         }
  512.  
  513.         this.attach = function(el) {
  514.             if (el.onmouseenter === undefined) {
  515.                 addEvent(el, 'mouseover', onOverOut);
  516.                 addEvent(el, 'mouseout', onOverOut);
  517.             }
  518.             else {
  519.                 addEvent(el, 'mouseenter', onEnterLeave);
  520.                 addEvent(el, 'mouseleave', onEnterLeave);
  521.             }
  522.         };
  523.  
  524.         this.detach = function(el) {
  525.             if (el.onmouseenter === undefined) {
  526.                 removeEvent(el, 'mouseover', onOverOut);
  527.                 removeEvent(el, 'mouseout', onOverOut);
  528.             }
  529.             else {
  530.                 removeEvent(el, 'mouseenter', onEnterLeave);
  531.                 removeEvent(el, 'mouseleave', onEnterLeave);
  532.             }
  533.         };
  534.  
  535.     }
  536.  
  537.     function ReplaceHistory() {
  538.  
  539.         var list = [], map = {};
  540.  
  541.         function filter(keys) {
  542.             var values = [], key;
  543.             for (var i = 0; key = keys[i]; ++i) values[i] = list[map[key]];
  544.             return values;
  545.         }
  546.  
  547.         this.add = function(key, args) {
  548.             map[key] = list.push(args) - 1;
  549.         };
  550.  
  551.         this.repeat = function() {
  552.             var snapshot = arguments.length ? filter(arguments) : list, args;
  553.             for (var i = 0; args = snapshot[i++];) api.replace(args[0], args[1], true);
  554.         };
  555.  
  556.     }
  557.  
  558.     function Storage() {
  559.  
  560.         var map = {}, at = 0;
  561.  
  562.         function identify(el) {
  563.             return el.cufid || (el.cufid = ++at);
  564.         }
  565.  
  566.         this.get = function(el) {
  567.             var id = identify(el);
  568.             return map[id] || (map[id] = {});
  569.         };
  570.  
  571.     }
  572.  
  573.     function Style(style) {
  574.  
  575.         var custom = {}, sizes = {};
  576.  
  577.         this.extend = function(styles) {
  578.             for (var property in styles) {
  579.                 if (hasOwnProperty(styles, property)) custom[property] = styles[property];
  580.             }
  581.             return this;
  582.         };
  583.  
  584.         this.get = function(property) {
  585.             return custom[property] != undefined ? custom[property] : style[property];
  586.         };
  587.  
  588.         this.getSize = function(property, base) {
  589.             return sizes[property] || (sizes[property] = new CSS.Size(this.get(property), base));
  590.         };
  591.  
  592.         this.isUsable = function() {
  593.             return !!style;
  594.         };
  595.  
  596.     }
  597.  
  598.     function addEvent(el, type, listener) {
  599.         if (el.addEventListener) {
  600.             el.addEventListener(type, listener, false);
  601.         }
  602.         else if (el.attachEvent) {
  603.             // we don't really need "this" right now, saves code
  604.             el.attachEvent('on' + type, listener);
  605.         }
  606.     }
  607.  
  608.     function attach(el, options) {
  609.         if (options._mediatorMode) return el;
  610.         var storage = sharedStorage.get(el);
  611.         var oldOptions = storage.options;
  612.         if (oldOptions) {
  613.             if (oldOptions === options) return el;
  614.             if (oldOptions.hover) hoverHandler.detach(el);
  615.         }
  616.         if (options.hover && options.hoverables[el.nodeName.toLowerCase()]) {
  617.             hoverHandler.attach(el);
  618.         }
  619.         storage.options = options;
  620.         return el;
  621.     }
  622.  
  623.     function cached(fun) {
  624.         var cache = {};
  625.         return function(key) {
  626.             if (!hasOwnProperty(cache, key)) cache[key] = fun.apply(null, arguments);
  627.             return cache[key];
  628.         };
  629.     }
  630.  
  631.     function getFont(el, style) {
  632.         var families = CSS.quotedList(style.get('fontFamily').toLowerCase()), family;
  633.         for (var i = 0; family = families[i]; ++i) {
  634.             if (fonts[family]) return fonts[family].get(style.get('fontStyle'), style.get('fontWeight'));
  635.         }
  636.         return null;
  637.     }
  638.  
  639.     function elementsByTagName(query) {
  640.         return document.getElementsByTagName(query);
  641.     }
  642.  
  643.     function hasOwnProperty(obj, property) {
  644.         return obj.hasOwnProperty(property);
  645.     }
  646.  
  647.     function merge() {
  648.         var merged = {}, arg, key;
  649.         for (var i = 0, l = arguments.length; arg = arguments[i], i < l; ++i) {
  650.             for (key in arg) {
  651.                 if (hasOwnProperty(arg, key)) merged[key] = arg[key];
  652.             }
  653.         }
  654.         return merged;
  655.     }
  656.  
  657.     function process(font, text, style, options, node, el) {
  658.         var fragment = document.createDocumentFragment(), processed;
  659.         if (text === '') return fragment;
  660.         var separate = options.separate;
  661.         var parts = text.split(separators[separate]), needsAligning = (separate == 'words');
  662.         if (needsAligning && HAS_BROKEN_REGEXP) {
  663.             // @todo figure out a better way to do this
  664.             if (/^\s/.test(text)) parts.unshift('');
  665.             if (/\s$/.test(text)) parts.push('');
  666.         }
  667.         for (var i = 0, l = parts.length; i < l; ++i) {
  668.             processed = engines[options.engine](font,
  669.                 needsAligning ? CSS.textAlign(parts[i], style, i, l) : parts[i],
  670.                 style, options, node, el, i < l - 1);
  671.             if (processed) fragment.appendChild(processed);
  672.         }
  673.         return fragment;
  674.     }
  675.  
  676.     function removeEvent(el, type, listener) {
  677.         if (el.removeEventListener) {
  678.             el.removeEventListener(type, listener, false);
  679.         }
  680.         else if (el.detachEvent) {
  681.             el.detachEvent('on' + type, listener);
  682.         }
  683.     }
  684.  
  685.     function replaceElement(el, options) {
  686.         var name = el.nodeName.toLowerCase();
  687.         if (options.ignore[name]) return;
  688.         if (options.ignoreClass && options.ignoreClass.test(el.className)) return;
  689.         if (options.onBeforeReplace) options.onBeforeReplace(el, options);
  690.         var replace = !options.textless[name], simple = (options.trim === 'simple');
  691.         var style = CSS.getStyle(attach(el, options)).extend(options);
  692.         // may cause issues if the element contains other elements
  693.         // with larger fontSize, however such cases are rare and can
  694.         // be fixed by using a more specific selector
  695.         if (parseFloat(style.get('fontSize')) === 0) return;
  696.         var font = getFont(el, style), node, type, next, anchor, text, lastElement;
  697.         var isShy = options.softHyphens, anyShy = false, pos, shy, reShy = /\u00ad/g;
  698.         var modifyText = options.modifyText;
  699.         if (!font) return;
  700.         for (node = el.firstChild; node; node = next) {
  701.             type = node.nodeType;
  702.             next = node.nextSibling;
  703.             if (replace && type == 3) {
  704.                 if (isShy && el.nodeName.toLowerCase() != TAG_SHY) {
  705.                     pos = node.data.indexOf('\u00ad');
  706.                     if (pos >= 0) {
  707.                         node.splitText(pos);
  708.                         next = node.nextSibling;
  709.                         next.deleteData(0, 1);
  710.                         shy = document.createElement(TAG_SHY);
  711.                         shy.appendChild(document.createTextNode('\u00ad'));
  712.                         el.insertBefore(shy, next);
  713.                         next = shy;
  714.                         anyShy = true;
  715.                     }
  716.                 }
  717.                 // Node.normalize() is broken in IE 6, 7, 8
  718.                 if (anchor) {
  719.                     anchor.appendData(node.data);
  720.                     el.removeChild(node);
  721.                 }
  722.                 else anchor = node;
  723.                 if (next) continue;
  724.             }
  725.             if (anchor) {
  726.                 text = anchor.data;
  727.                 if (!isShy) text = text.replace(reShy, '');
  728.                 text = CSS.whiteSpace(text, style, anchor, lastElement, simple);
  729.                 // modify text only on the first replace
  730.                 if (modifyText) text = modifyText(text, anchor, el, options);
  731.                 el.replaceChild(process(font, text, style, options, node, el), anchor);
  732.                 anchor = null;
  733.             }
  734.             if (type == 1) {
  735.                 if (node.firstChild) {
  736.                     if (node.nodeName.toLowerCase() == 'cufon') {
  737.                         engines[options.engine](font, null, style, options, node, el);
  738.                     }
  739.                     else arguments.callee(node, options);
  740.                 }
  741.                 lastElement = node;
  742.             }
  743.         }
  744.         if (isShy && anyShy) {
  745.             updateShy(el);
  746.             if (!trackingShy) addEvent(window, 'resize', updateShyOnResize);
  747.             trackingShy = true;
  748.         }
  749.         if (options.onAfterReplace) options.onAfterReplace(el, options);
  750.     }
  751.  
  752.     function updateShy(context) {
  753.         var shys, shy, parent, glue, newGlue, next, prev, i;
  754.         shys = context.getElementsByTagName(TAG_SHY);
  755.         // unfortunately there doesn't seem to be any easy
  756.         // way to avoid having to loop through the shys twice.
  757.         for (i = 0; shy = shys[i]; ++i) {
  758.             shy.className = C_SHY_DISABLED;
  759.             glue = parent = shy.parentNode;
  760.             if (glue.nodeName.toLowerCase() != TAG_GLUE) {
  761.                 newGlue = document.createElement(TAG_GLUE);
  762.                 newGlue.appendChild(shy.previousSibling);
  763.                 parent.insertBefore(newGlue, shy);
  764.                 newGlue.appendChild(shy);
  765.             }
  766.             else {
  767.                 // get rid of double glue (edge case fix)
  768.                 glue = glue.parentNode;
  769.                 if (glue.nodeName.toLowerCase() == TAG_GLUE) {
  770.                     parent = glue.parentNode;
  771.                     while (glue.firstChild) {
  772.                         parent.insertBefore(glue.firstChild, glue);
  773.                     }
  774.                     parent.removeChild(glue);
  775.                 }
  776.             }
  777.         }
  778.         for (i = 0; shy = shys[i]; ++i) {
  779.             shy.className = '';
  780.             glue = shy.parentNode;
  781.             parent = glue.parentNode;
  782.             next = glue.nextSibling || parent.nextSibling;
  783.             // make sure we're comparing same types
  784.             prev = (next.nodeName.toLowerCase() == TAG_GLUE) ? glue : shy.previousSibling;
  785.             if (prev.offsetTop >= next.offsetTop) {
  786.                 shy.className = C_SHY_DISABLED;
  787.                 if (prev.offsetTop < next.offsetTop) {
  788.                     // we have an annoying edge case, double the glue
  789.                     newGlue = document.createElement(TAG_GLUE);
  790.                     parent.insertBefore(newGlue, glue);
  791.                     newGlue.appendChild(glue);
  792.                     newGlue.appendChild(next);
  793.                 }
  794.             }
  795.         }
  796.     }
  797.  
  798.     function updateShyOnResize() {
  799.         if (ignoreResize) return; // needed for IE
  800.         CSS.addClass(DOM.root(), C_VIEWPORT_RESIZING);
  801.         clearTimeout(shyTimer);
  802.         shyTimer = setTimeout(function() {
  803.             ignoreResize = true;
  804.             CSS.removeClass(DOM.root(), C_VIEWPORT_RESIZING);
  805.             updateShy(document);
  806.             ignoreResize = false;
  807.         }, 100);
  808.     }
  809.  
  810.     var HAS_BROKEN_REGEXP = ' '.split(/\s+/).length == 0;
  811.     var TAG_GLUE = 'cufonglue';
  812.     var TAG_SHY = 'cufonshy';
  813.     var C_SHY_DISABLED = 'cufon-shy-disabled';
  814.     var C_VIEWPORT_RESIZING = 'cufon-viewport-resizing';
  815.  
  816.     var sharedStorage = new Storage();
  817.     var hoverHandler = new HoverHandler();
  818.     var replaceHistory = new ReplaceHistory();
  819.     var initialized = false;
  820.     var trackingShy = false;
  821.     var shyTimer;
  822.     var ignoreResize = false;
  823.  
  824.     var engines = {}, fonts = {}, defaultOptions = {
  825.         autoDetect: false,
  826.         engine: null,
  827.         forceHitArea: false,
  828.         hover: false,
  829.         hoverables: {
  830.             a: true
  831.         },
  832.         ignore: {
  833.             applet: 1,
  834.             canvas: 1,
  835.             col: 1,
  836.             colgroup: 1,
  837.             head: 1,
  838.             iframe: 1,
  839.             map: 1,
  840.             noscript: 1,
  841.             optgroup: 1,
  842.             option: 1,
  843.             script: 1,
  844.             select: 1,
  845.             style: 1,
  846.             textarea: 1,
  847.             title: 1,
  848.             pre: 1
  849.         },
  850.         ignoreClass: null,
  851.         modifyText: null,
  852.         onAfterReplace: null,
  853.         onBeforeReplace: null,
  854.         printable: true,
  855.         selector: (
  856.                 window.Sizzle
  857.             ||  (window.jQuery && function(query) { return jQuery(query); }) // avoid noConflict issues
  858.             ||  (window.dojo && dojo.query)
  859.             ||  (window.glow && glow.dom && glow.dom.get)
  860.             ||  (window.Ext && Ext.query)
  861.             ||  (window.YAHOO && YAHOO.util && YAHOO.util.Selector && YAHOO.util.Selector.query)
  862.             ||  (window.$$ && function(query) { return $$(query); })
  863.             ||  (window.$ && function(query) { return $(query); })
  864.             ||  (document.querySelectorAll && function(query) { return document.querySelectorAll(query); })
  865.             ||  elementsByTagName
  866.         ),
  867.         separate: 'words', // 'none' and 'characters' are also accepted
  868.         softHyphens: true,
  869.         textless: {
  870.             dl: 1,
  871.             html: 1,
  872.             ol: 1,
  873.             table: 1,
  874.             tbody: 1,
  875.             thead: 1,
  876.             tfoot: 1,
  877.             tr: 1,
  878.             ul: 1
  879.         },
  880.         textShadow: 'none',
  881.         trim: 'advanced'
  882.     };
  883.  
  884.     var separators = {
  885.         // The first pattern may cause unicode characters above
  886.         // code point 255 to be removed in Safari 3.0. Luckily enough
  887.         // Safari 3.0 does not include non-breaking spaces in \s, so
  888.         // we can just use a simple alternative pattern.
  889.         words: /\s/.test('\u00a0') ? /[^\S\u00a0]+/ : /\s+/,
  890.         characters: '',
  891.         none: /^/
  892.     };
  893.  
  894.     api.now = function() {
  895.         DOM.ready();
  896.         return api;
  897.     };
  898.  
  899.     api.refresh = function() {
  900.         replaceHistory.repeat.apply(replaceHistory, arguments);
  901.         return api;
  902.     };
  903.  
  904.     api.registerEngine = function(id, engine) {
  905.         if (!engine) return api;
  906.         engines[id] = engine;
  907.         return api.set('engine', id);
  908.     };
  909.  
  910.     api.registerFont = function(data) {
  911.         if (!data) return api;
  912.         var font = new Font(data), family = font.family;
  913.         if (!fonts[family]) fonts[family] = new FontFamily();
  914.         fonts[family].add(font);
  915.         return api.set('fontFamily', '"' + family + '"');
  916.     };
  917.  
  918.     api.replace = function(elements, options, ignoreHistory) {
  919.         options = merge(defaultOptions, options);
  920.         if (!options.engine) return api; // there's no browser support so we'll just stop here
  921.         if (!initialized) {
  922.             CSS.addClass(DOM.root(), 'cufon-active cufon-loading');
  923.             CSS.ready(function() {
  924.                 // fires before any replace() calls, but it doesn't really matter
  925.                 CSS.addClass(CSS.removeClass(DOM.root(), 'cufon-loading'), 'cufon-ready');
  926.             });
  927.             initialized = true;
  928.         }
  929.         if (options.hover) options.forceHitArea = true;
  930.         if (options.autoDetect) delete options.fontFamily;
  931.         if (typeof options.ignoreClass == 'string') {
  932.             options.ignoreClass = new RegExp('(?:^|\\s)(?:' + options.ignoreClass.replace(/\s+/g, '|') + ')(?:\\s|$)');
  933.         }
  934.         if (typeof options.textShadow == 'string') {
  935.             options.textShadow = CSS.textShadow(options.textShadow);
  936.         }
  937.         if (typeof options.color == 'string' && /^-/.test(options.color)) {
  938.             options.textGradient = CSS.gradient(options.color);
  939.         }
  940.         else delete options.textGradient;
  941.         if (!ignoreHistory) replaceHistory.add(elements, arguments);
  942.         if (elements.nodeType || typeof elements == 'string') elements = [ elements ];
  943.         CSS.ready(function() {
  944.             for (var i = 0, l = elements.length; i < l; ++i) {
  945.                 var el = elements[i];
  946.                 if (typeof el == 'string') api.replace(options.selector(el), options, true);
  947.                 else replaceElement(el, options);
  948.             }
  949.         });
  950.         return api;
  951.     };
  952.  
  953.     api.set = function(option, value) {
  954.         defaultOptions[option] = value;
  955.         return api;
  956.     };
  957.  
  958.     return api;
  959.  
  960. })();
  961.  
  962. Cufon.registerEngine('vml', (function() {
  963.  
  964.     var ns = document.namespaces;
  965.     if (!ns) return;
  966.     ns.add('cvml', 'urn:schemas-microsoft-com:vml');
  967.     ns = null;
  968.  
  969.     var check = document.createElement('cvml:shape');
  970.     check.style.behavior = 'url(#default#VML)';
  971.     if (!check.coordsize) return; // VML isn't supported
  972.     check = null;
  973.  
  974.     var HAS_BROKEN_LINEHEIGHT = (document.documentMode || 0) < 8;
  975.  
  976.     document.write(('<style type="text/css">' +
  977.         'cufoncanvas{text-indent:0;}' +
  978.         '@media screen{' +
  979.             'cvml\\:shape,cvml\\:rect,cvml\\:fill,cvml\\:shadow{behavior:url(#default#VML);display:block;antialias:true;position:absolute;}' +
  980.             'cufoncanvas{position:absolute;text-align:left;}' +
  981.             'cufon{display:inline-block;position:relative;vertical-align:' +
  982.             (HAS_BROKEN_LINEHEIGHT
  983.                 ? 'middle'
  984.                 : 'text-bottom') +
  985.             ';}' +
  986.             'cufon cufontext{position:absolute;left:-10000in;font-size:1px;text-align:left;}' +
  987.             'cufonshy.cufon-shy-disabled,.cufon-viewport-resizing cufonshy{display:none;}' +
  988.             'cufonglue{white-space:nowrap;display:inline-block;}' +
  989.             '.cufon-viewport-resizing cufonglue{white-space:normal;}' +
  990.             'a cufon{cursor:pointer}' + // ignore !important here
  991.         '}' +
  992.         '@media print{' +
  993.             'cufon cufoncanvas{display:none;}' +
  994.         '}' +
  995.     '</style>').replace(/;/g, '!important;'));
  996.  
  997.     function getFontSizeInPixels(el, value) {
  998.         return getSizeInPixels(el, /(?:em|ex|%)$|^[a-z-]+$/i.test(value) ? '1em' : value);
  999.     }
  1000.  
  1001.     // Original by Dead Edwards.
  1002.     // Combined with getFontSizeInPixels it also works with relative units.
  1003.     function getSizeInPixels(el, value) {
  1004.         if (!isNaN(value) || /px$/i.test(value)) return parseFloat(value);
  1005.         var style = el.style.left, runtimeStyle = el.runtimeStyle.left;
  1006.         el.runtimeStyle.left = el.currentStyle.left;
  1007.         el.style.left = value.replace('%', 'em');
  1008.         var result = el.style.pixelLeft;
  1009.         el.style.left = style;
  1010.         el.runtimeStyle.left = runtimeStyle;
  1011.         return result;
  1012.     }
  1013.  
  1014.     function getSpacingValue(el, style, size, property) {
  1015.         var key = 'computed' + property, value = style[key];
  1016.         if (isNaN(value)) {
  1017.             value = style.get(property);
  1018.             style[key] = value = (value == 'normal') ? 0 : ~~size.convertFrom(getSizeInPixels(el, value));
  1019.         }
  1020.         return value;
  1021.     }
  1022.  
  1023.     var fills = {};
  1024.  
  1025.     function gradientFill(gradient) {
  1026.         var id = gradient.id;
  1027.         if (!fills[id]) {
  1028.             var stops = gradient.stops, fill = document.createElement('cvml:fill'), colors = [];
  1029.             fill.type = 'gradient';
  1030.             fill.angle = 180;
  1031.             fill.focus = '0';
  1032.             fill.method = 'none';
  1033.             fill.color = stops[0][1];
  1034.             for (var j = 1, k = stops.length - 1; j < k; ++j) {
  1035.                 colors.push(stops[j][0] * 100 + '% ' + stops[j][1]);
  1036.             }
  1037.             fill.colors = colors.join(',');
  1038.             fill.color2 = stops[k][1];
  1039.             fills[id] = fill;
  1040.         }
  1041.         return fills[id];
  1042.     }
  1043.  
  1044.     return function(font, text, style, options, node, el, hasNext) {
  1045.  
  1046.         var redraw = (text === null);
  1047.  
  1048.         if (redraw) text = node.alt;
  1049.  
  1050.         var viewBox = font.viewBox;
  1051.  
  1052.         var size = style.computedFontSize || (style.computedFontSize = new Cufon.CSS.Size(getFontSizeInPixels(el, style.get('fontSize')) + 'px', font.baseSize));
  1053.  
  1054.         var wrapper, canvas;
  1055.  
  1056.         if (redraw) {
  1057.             wrapper = node;
  1058.             canvas = node.firstChild;
  1059.         }
  1060.         else {
  1061.             wrapper = document.createElement('cufon');
  1062.             wrapper.className = 'cufon cufon-vml';
  1063.             wrapper.alt = text;
  1064.  
  1065.             canvas = document.createElement('cufoncanvas');
  1066.             wrapper.appendChild(canvas);
  1067.  
  1068.             if (options.printable) {
  1069.                 var print = document.createElement('cufontext');
  1070.                 print.appendChild(document.createTextNode(text));
  1071.                 wrapper.appendChild(print);
  1072.             }
  1073.  
  1074.             // ie6, for some reason, has trouble rendering the last VML element in the document.
  1075.             // we can work around this by injecting a dummy element where needed.
  1076.             // @todo find a better solution
  1077.             if (!hasNext) wrapper.appendChild(document.createElement('cvml:shape'));
  1078.         }
  1079.  
  1080.         var wStyle = wrapper.style;
  1081.         var cStyle = canvas.style;
  1082.  
  1083.         var height = size.convert(viewBox.height), roundedHeight = Math.ceil(height);
  1084.         var roundingFactor = roundedHeight / height;
  1085.         var stretchFactor = roundingFactor * Cufon.CSS.fontStretch(style.get('fontStretch'));
  1086.         var minX = viewBox.minX, minY = viewBox.minY;
  1087.  
  1088.         cStyle.height = roundedHeight;
  1089.         cStyle.top = Math.round(size.convert(minY - font.ascent));
  1090.         cStyle.left = Math.round(size.convert(minX));
  1091.  
  1092.         wStyle.height = size.convert(font.height) + 'px';
  1093.  
  1094.         var color = style.get('color');
  1095.         var chars = Cufon.CSS.textTransform(text, style).split('');
  1096.  
  1097.         var jumps = font.spacing(chars,
  1098.             getSpacingValue(el, style, size, 'letterSpacing'),
  1099.             getSpacingValue(el, style, size, 'wordSpacing')
  1100.         );
  1101.  
  1102.         if (!jumps.length) return null;
  1103.  
  1104.         var width = jumps.total;
  1105.         var fullWidth = -minX + width + (viewBox.width - jumps[jumps.length - 1]);
  1106.  
  1107.         var shapeWidth = size.convert(fullWidth * stretchFactor), roundedShapeWidth = Math.round(shapeWidth);
  1108.  
  1109.         var coordSize = fullWidth + ',' + viewBox.height, coordOrigin;
  1110.         var stretch = 'r' + coordSize + 'ns';
  1111.  
  1112.         var fill = options.textGradient && gradientFill(options.textGradient);
  1113.  
  1114.         var glyphs = font.glyphs, offsetX = 0;
  1115.         var shadows = options.textShadow;
  1116.         var i = -1, j = 0, chr;
  1117.  
  1118.         while (chr = chars[++i]) {
  1119.  
  1120.             var glyph = glyphs[chars[i]] || font.missingGlyph, shape;
  1121.             if (!glyph) continue;
  1122.  
  1123.             if (redraw) {
  1124.                 // some glyphs may be missing so we can't use i
  1125.                 shape = canvas.childNodes[j];
  1126.                 while (shape.firstChild) shape.removeChild(shape.firstChild); // shadow, fill
  1127.             }
  1128.             else {
  1129.                 shape = document.createElement('cvml:shape');
  1130.                 canvas.appendChild(shape);
  1131.             }
  1132.  
  1133.             shape.stroked = 'f';
  1134.             shape.coordsize = coordSize;
  1135.             shape.coordorigin = coordOrigin = (minX - offsetX) + ',' + minY;
  1136.             shape.path = (glyph.d ? 'm' + glyph.d + 'xe' : '') + 'm' + coordOrigin + stretch;
  1137.             shape.fillcolor = color;
  1138.  
  1139.             if (fill) shape.appendChild(fill.cloneNode(false));
  1140.  
  1141.             // it's important to not set top/left or IE8 will grind to a halt
  1142.             var sStyle = shape.style;
  1143.             sStyle.width = roundedShapeWidth;
  1144.             sStyle.height = roundedHeight;
  1145.  
  1146.             if (shadows) {
  1147.                 // due to the limitations of the VML shadow element there
  1148.                 // can only be two visible shadows. opacity is shared
  1149.                 // for all shadows.
  1150.                 var shadow1 = shadows[0], shadow2 = shadows[1];
  1151.                 var color1 = Cufon.CSS.color(shadow1.color), color2;
  1152.                 var shadow = document.createElement('cvml:shadow');
  1153.                 shadow.on = 't';
  1154.                 shadow.color = color1.color;
  1155.                 shadow.offset = shadow1.offX + ',' + shadow1.offY;
  1156.                 if (shadow2) {
  1157.                     color2 = Cufon.CSS.color(shadow2.color);
  1158.                     shadow.type = 'double';
  1159.                     shadow.color2 = color2.color;
  1160.                     shadow.offset2 = shadow2.offX + ',' + shadow2.offY;
  1161.                 }
  1162.                 shadow.opacity = color1.opacity || (color2 && color2.opacity) || 1;
  1163.                 shape.appendChild(shadow);
  1164.             }
  1165.  
  1166.             offsetX += jumps[j++];
  1167.         }
  1168.  
  1169.         // addresses flickering issues on :hover
  1170.  
  1171.         var cover = shape.nextSibling, coverFill, vStyle;
  1172.  
  1173.         if (options.forceHitArea) {
  1174.  
  1175.             if (!cover) {
  1176.                 cover = document.createElement('cvml:rect');
  1177.                 cover.stroked = 'f';
  1178.                 cover.className = 'cufon-vml-cover';
  1179.                 coverFill = document.createElement('cvml:fill');
  1180.                 coverFill.opacity = 0;
  1181.                 cover.appendChild(coverFill);
  1182.                 canvas.appendChild(cover);
  1183.             }
  1184.  
  1185.             vStyle = cover.style;
  1186.  
  1187.             vStyle.width = roundedShapeWidth;
  1188.             vStyle.height = roundedHeight;
  1189.  
  1190.         }
  1191.         else if (cover) canvas.removeChild(cover);
  1192.  
  1193.         wStyle.width = Math.max(Math.ceil(size.convert(width * stretchFactor)), 0);
  1194.  
  1195.         if (HAS_BROKEN_LINEHEIGHT) {
  1196.  
  1197.             var yAdjust = style.computedYAdjust;
  1198.  
  1199.             if (yAdjust === undefined) {
  1200.                 var lineHeight = style.get('lineHeight');
  1201.                 if (lineHeight == 'normal') lineHeight = '1em';
  1202.                 else if (!isNaN(lineHeight)) lineHeight += 'em'; // no unit
  1203.                 style.computedYAdjust = yAdjust = 0.5 * (getSizeInPixels(el, lineHeight) - parseFloat(wStyle.height));
  1204.             }
  1205.  
  1206.             if (yAdjust) {
  1207.                 wStyle.marginTop = Math.ceil(yAdjust) + 'px';
  1208.                 wStyle.marginBottom = yAdjust + 'px';
  1209.             }
  1210.  
  1211.         }
  1212.  
  1213.         return wrapper;
  1214.  
  1215.     };
  1216.  
  1217. })());
  1218.  
  1219. Cufon.registerEngine('canvas', (function() {
  1220.  
  1221.     // Safari 2 doesn't support .apply() on native methods
  1222.  
  1223.     var check = document.createElement('canvas');
  1224.     if (!check || !check.getContext || !check.getContext.apply) return;
  1225.     check = null;
  1226.  
  1227.     var HAS_INLINE_BLOCK = Cufon.CSS.supports('display', 'inline-block');
  1228.  
  1229.     // Firefox 2 w/ non-strict doctype (almost standards mode)
  1230.     var HAS_BROKEN_LINEHEIGHT = !HAS_INLINE_BLOCK && (document.compatMode == 'BackCompat' || /frameset|transitional/i.test(document.doctype.publicId));
  1231.  
  1232.     var styleSheet = document.createElement('style');
  1233.     styleSheet.type = 'text/css';
  1234.     styleSheet.appendChild(document.createTextNode((
  1235.         'cufon{text-indent:0;}' +
  1236.         '@media screen,projection{' +
  1237.             'cufon{display:inline;display:inline-block;position:relative;vertical-align:middle;' +
  1238.             (HAS_BROKEN_LINEHEIGHT
  1239.                 ? ''
  1240.                 : 'font-size:1px;line-height:1px;') +
  1241.             '}cufon cufontext{display:-moz-inline-box;display:inline-block;width:0;height:0;text-align:left;text-indent:-10000in;}' +
  1242.             (HAS_INLINE_BLOCK
  1243.                 ? 'cufon canvas{position:relative;}'
  1244.                 : 'cufon canvas{position:absolute;}') +
  1245.             'cufonshy.cufon-shy-disabled,.cufon-viewport-resizing cufonshy{display:none;}' +
  1246.             'cufonglue{white-space:nowrap;display:inline-block;}' +
  1247.             '.cufon-viewport-resizing cufonglue{white-space:normal;}' +
  1248.         '}' +
  1249.         '@media print{' +
  1250.             'cufon{padding:0;}' + // Firefox 2
  1251.             'cufon canvas{display:none;}' +
  1252.         '}'
  1253.     ).replace(/;/g, '!important;')));
  1254.     document.getElementsByTagName('head')[0].appendChild(styleSheet);
  1255.  
  1256.     function generateFromVML(path, context) {
  1257.         var atX = 0, atY = 0;
  1258.         var code = [], re = /([mrvxe])([^a-z]*)/g, match;
  1259.         generate: for (var i = 0; match = re.exec(path); ++i) {
  1260.             var c = match[2].split(',');
  1261.             switch (match[1]) {
  1262.                 case 'v':
  1263.                     code[i] = { m: 'bezierCurveTo', a: [ atX + ~~c[0], atY + ~~c[1], atX + ~~c[2], atY + ~~c[3], atX += ~~c[4], atY += ~~c[5] ] };
  1264.                     break;
  1265.                 case 'r':
  1266.                     code[i] = { m: 'lineTo', a: [ atX += ~~c[0], atY += ~~c[1] ] };
  1267.                     break;
  1268.                 case 'm':
  1269.                     code[i] = { m: 'moveTo', a: [ atX = ~~c[0], atY = ~~c[1] ] };
  1270.                     break;
  1271.                 case 'x':
  1272.                     code[i] = { m: 'closePath' };
  1273.                     break;
  1274.                 case 'e':
  1275.                     break generate;
  1276.             }
  1277.             context[code[i].m].apply(context, code[i].a);
  1278.         }
  1279.         return code;
  1280.     }
  1281.  
  1282.     function interpret(code, context) {
  1283.         for (var i = 0, l = code.length; i < l; ++i) {
  1284.             var line = code[i];
  1285.             context[line.m].apply(context, line.a);
  1286.         }
  1287.     }
  1288.  
  1289.     return function(font, text, style, options, node, el) {
  1290.  
  1291.         var redraw = (text === null);
  1292.  
  1293.         if (redraw) text = node.getAttribute('alt');
  1294.  
  1295.         var viewBox = font.viewBox;
  1296.  
  1297.         var size = style.getSize('fontSize', font.baseSize);
  1298.  
  1299.         var expandTop = 0, expandRight = 0, expandBottom = 0, expandLeft = 0;
  1300.         var shadows = options.textShadow, shadowOffsets = [];
  1301.         if (shadows) {
  1302.             for (var i = shadows.length; i--;) {
  1303.                 var shadow = shadows[i];
  1304.                 var x = size.convertFrom(parseFloat(shadow.offX));
  1305.                 var y = size.convertFrom(parseFloat(shadow.offY));
  1306.                 shadowOffsets[i] = [ x, y ];
  1307.                 if (y < expandTop) expandTop = y;
  1308.                 if (x > expandRight) expandRight = x;
  1309.                 if (y > expandBottom) expandBottom = y;
  1310.                 if (x < expandLeft) expandLeft = x;
  1311.             }
  1312.         }
  1313.  
  1314.         var chars = Cufon.CSS.textTransform(text, style).split('');
  1315.  
  1316.         var jumps = font.spacing(chars,
  1317.             ~~size.convertFrom(parseFloat(style.get('letterSpacing')) || 0),
  1318.             ~~size.convertFrom(parseFloat(style.get('wordSpacing')) || 0)
  1319.         );
  1320.  
  1321.         if (!jumps.length) return null; // there's nothing to render
  1322.  
  1323.         var width = jumps.total;
  1324.  
  1325.         expandRight += viewBox.width - jumps[jumps.length - 1];
  1326.         expandLeft += viewBox.minX;
  1327.  
  1328.         var wrapper, canvas;
  1329.  
  1330.         if (redraw) {
  1331.             wrapper = node;
  1332.             canvas = node.firstChild;
  1333.         }
  1334.         else {
  1335.             wrapper = document.createElement('cufon');
  1336.             wrapper.className = 'cufon cufon-canvas';
  1337.             wrapper.setAttribute('alt', text);
  1338.  
  1339.             canvas = document.createElement('canvas');
  1340.             wrapper.appendChild(canvas);
  1341.  
  1342.             if (options.printable) {
  1343.                 var print = document.createElement('cufontext');
  1344.                 print.appendChild(document.createTextNode(text));
  1345.                 wrapper.appendChild(print);
  1346.             }
  1347.         }
  1348.  
  1349.         var wStyle = wrapper.style;
  1350.         var cStyle = canvas.style;
  1351.  
  1352.         var height = size.convert(viewBox.height);
  1353.         var roundedHeight = Math.ceil(height);
  1354.         var roundingFactor = roundedHeight / height;
  1355.         var stretchFactor = roundingFactor * Cufon.CSS.fontStretch(style.get('fontStretch'));
  1356.         var stretchedWidth = width * stretchFactor;
  1357.  
  1358.         var canvasWidth = Math.ceil(size.convert(stretchedWidth + expandRight - expandLeft));
  1359.         var canvasHeight = Math.ceil(size.convert(viewBox.height - expandTop + expandBottom));
  1360.  
  1361.         canvas.width = canvasWidth;
  1362.         canvas.height = canvasHeight;
  1363.  
  1364.         // needed for WebKit and full page zoom
  1365.         cStyle.width = canvasWidth + 'px';
  1366.         cStyle.height = canvasHeight + 'px';
  1367.  
  1368.         // minY has no part in canvas.height
  1369.         expandTop += viewBox.minY;
  1370.  
  1371.         cStyle.top = Math.round(size.convert(expandTop - font.ascent)) + 'px';
  1372.         cStyle.left = Math.round(size.convert(expandLeft)) + 'px';
  1373.  
  1374.         var wrapperWidth = Math.max(Math.ceil(size.convert(stretchedWidth)), 0) + 'px';
  1375.  
  1376.         if (HAS_INLINE_BLOCK) {
  1377.             wStyle.width = wrapperWidth;
  1378.             wStyle.height = size.convert(font.height) + 'px';
  1379.         }
  1380.         else {
  1381.             wStyle.paddingLeft = wrapperWidth;
  1382.             wStyle.paddingBottom = (size.convert(font.height) - 1) + 'px';
  1383.         }
  1384.  
  1385.         var g = canvas.getContext('2d'), scale = height / viewBox.height;
  1386.  
  1387.         // proper horizontal scaling is performed later
  1388.         g.scale(scale, scale * roundingFactor);
  1389.         g.translate(-expandLeft, -expandTop);
  1390.         g.save();
  1391.  
  1392.         function renderText() {
  1393.             var glyphs = font.glyphs, glyph, i = -1, j = -1, chr;
  1394.             g.scale(stretchFactor, 1);
  1395.             while (chr = chars[++i]) {
  1396.                 var glyph = glyphs[chars[i]] || font.missingGlyph;
  1397.                 if (!glyph) continue;
  1398.                 if (glyph.d) {
  1399.                     g.beginPath();
  1400.                     if (glyph.code) interpret(glyph.code, g);
  1401.                     else glyph.code = generateFromVML('m' + glyph.d, g);
  1402.                     g.fill();
  1403.                 }
  1404.                 g.translate(jumps[++j], 0);
  1405.             }
  1406.             g.restore();
  1407.         }
  1408.  
  1409.         if (shadows) {
  1410.             for (var i = shadows.length; i--;) {
  1411.                 var shadow = shadows[i];
  1412.                 g.save();
  1413.                 g.fillStyle = shadow.color;
  1414.                 g.translate.apply(g, shadowOffsets[i]);
  1415.                 renderText();
  1416.             }
  1417.         }
  1418.  
  1419.         var gradient = options.textGradient;
  1420.         if (gradient) {
  1421.             var stops = gradient.stops, fill = g.createLinearGradient(0, viewBox.minY, 0, viewBox.maxY);
  1422.             for (var i = 0, l = stops.length; i < l; ++i) {
  1423.                 fill.addColorStop.apply(fill, stops[i]);
  1424.             }
  1425.             g.fillStyle = fill;
  1426.         }
  1427.         else g.fillStyle = style.get('color');
  1428.  
  1429.         renderText();
  1430.  
  1431.         return wrapper;
  1432.  
  1433.     };
  1434.  
  1435. })());
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement