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