Файл: DOCUMENTATION/assets/js/cufon.js
Строк: 1788
<?php
/*!
* Copyright (c) 2011 Simo Kinnunen.
* Licensed under the MIT license.
*
* @version ${Version}
*/
var Cufon = (function () {
var api = function () {
return api.replace.apply(null, arguments);
};
var DOM = api.DOM = {
ready: (function () {
var complete = false, readyStatus = { loaded: 1, complete: 1 };
var queue = [], perform = function () {
if (complete) return;
complete = true;
for (var fn; fn = queue.shift(); fn()){};
};
// Gecko, Opera, WebKit r26101+
if (document.addEventListener) {
document.addEventListener('DOMContentLoaded', perform, false);
window.addEventListener('pageshow', perform, false); // For cached Gecko pages
}
// Old WebKit, Internet Explorer
if (!window.opera && document.readyState) (function () {
readyStatus[document.readyState] ? perform() : setTimeout(arguments.callee, 10);
})();
// Internet Explorer
if (document.readyState && document.createStyleSheet) (function () {
try {
document.body.doScroll('left');
perform();
}
catch (e) {
setTimeout(arguments.callee, 1);
}
})();
addEvent(window, 'load', perform); // Fallback
return function(listener) {
if (!arguments.length) perform();
else complete ? listener() : queue.push(listener);
};
})(),
root: function () {
return document.documentElement || document.body;
},
strict: (function () {
var doctype;
// no doctype (doesn't always catch it though.. IE I'm looking at you)
if (document.compatMode == 'BackCompat') return false;
// WebKit, Gecko, Opera, IE9+
doctype = document.doctype;
if (doctype) {
return !/frameset|transitional/i.test(doctype.publicId);
}
// IE<9, firstChild is the doctype even if there's an XML declaration
doctype = document.firstChild;
if (doctype.nodeType != 8 || /^DOCTYPE.+(transitional|frameset)/i.test(doctype.data)) {
return false;
}
return true;
})()
};
var CSS = api.CSS = {
Size: function(value, base) {
this.value = parseFloat(value);
this.unit = String(value).match(/[a-z%]*$/)[0] || 'px';
this.convert = function(value) {
return value / base * this.value;
};
this.convertFrom = function(value) {
return value / this.value * base;
};
this.toString = function () {
return this.value + this.unit;
};
},
addClass: function(el, className) {
var current = el.className;
el.className = current + (current && ' ') + className;
return el;
},
color: cached(function(value) {
var parsed = {};
parsed.color = value.replace(/^rgba((.*?),s*([d.]+))/, function($0, $1, $2) {
parsed.opacity = parseFloat($2);
return 'rgb(' + $1 + ')';
});
return parsed;
}),
// has no direct CSS equivalent.
// @see http://msdn.microsoft.com/en-us/library/system.windows.fontstretches.aspx
fontStretch: cached(function(value) {
if (typeof value == 'number') return value;
if (/%$/.test(value)) return parseFloat(value) / 100;
return {
'ultra-condensed': 0.5,
'extra-condensed': 0.625,
condensed: 0.75,
'semi-condensed': 0.875,
'semi-expanded': 1.125,
expanded: 1.25,
'extra-expanded': 1.5,
'ultra-expanded': 2
}[value] || 1;
}),
getStyle: function(el) {
var view = document.defaultView;
if (view && view.getComputedStyle) return new Style(view.getComputedStyle(el, null));
if (el.currentStyle) return new Style(el.currentStyle);
return new Style(el.style);
},
gradient: cached(function(value) {
var gradient = {
id: value,
type: value.match(/^-([a-z]+)-gradient(/)[1],
stops: []
}, colors = value.substr(value.indexOf('(')).match(/([d.]+=)?(#[a-f0-9]+|[a-z]+(.*?)|[a-z]+)/ig);
for (var i = 0, l = colors.length, stop; i < l; ++i) {
stop = colors[i].split('=', 2).reverse();
gradient.stops.push([ stop[1] || i / (l - 1), stop[0] ]);
}
return gradient;
}),
quotedList: cached(function(value) {
// doesn't work properly with empty quoted strings (""), but
// it's not worth the extra code.
var list = [], re = /s*((["'])([sS]*?[^\])2|[^,]+)s*/g, match;
while (match = re.exec(value)) list.push(match[3] || match[1]);
return list;
}),
recognizesMedia: cached(function(media) {
var el = document.createElement('style'), sheet, container, supported;
el.type = 'text/css';
el.media = media;
try { // this is cached anyway
el.appendChild(document.createTextNode('/**/'));
} catch (e) {}
container = elementsByTagName('head')[0];
container.insertBefore(el, container.firstChild);
sheet = (el.sheet || el.styleSheet);
supported = sheet && !sheet.disabled;
container.removeChild(el);
return supported;
}),
removeClass: function(el, className) {
var re = RegExp('(?:^|\s+)' + className + '(?=\s|$)', 'g');
el.className = el.className.replace(re, '');
return el;
},
supports: function(property, value) {
var checker = document.createElement('span').style;
if (checker[property] === undefined) return false;
checker[property] = value;
return checker[property] === value;
},
textAlign: function(word, style, position, wordCount) {
if (style.get('textAlign') == 'right') {
if (position > 0) word = ' ' + word;
}
else if (position < wordCount - 1) word += ' ';
return word;
},
textShadow: cached(function(value) {
if (value == 'none') return null;
var shadows = [], currentShadow = {}, result, offCount = 0;
var re = /(#[a-f0-9]+|[a-z]+(.*?)|[a-z]+)|(-?[d.]+[a-z%]*)|,/ig;
while (result = re.exec(value)) {
if (result[0] == ',') {
shadows.push(currentShadow);
currentShadow = {};
offCount = 0;
}
else if (result[1]) {
currentShadow.color = result[1];
}
else {
currentShadow[[ 'offX', 'offY', 'blur' ][offCount++]] = result[2];
}
}
shadows.push(currentShadow);
return shadows;
}),
textTransform: (function () {
var map = {
uppercase: function(s) {
return s.toUpperCase();
},
lowercase: function(s) {
return s.toLowerCase();
},
capitalize: function(s) {
return s.replace(/(?:^|s)./g, function($0) {
return $0.toUpperCase();
});
}
};
return function(text, style) {
var transform = map[style.get('textTransform')];
return transform ? transform(text) : text;
};
})(),
whiteSpace: (function () {
var ignore = {
inline: 1,
'inline-block': 1,
'run-in': 1
};
var wsStart = /^s+/, wsEnd = /s+$/;
return function(text, style, node, previousElement, simple) {
if (simple) return text.replace(wsStart, '').replace(wsEnd, ''); // @fixme too simple
if (previousElement) {
if (previousElement.nodeName.toLowerCase() == 'br') {
text = text.replace(wsStart, '');
}
}
if (ignore[style.get('display')]) return text;
if (!node.previousSibling) text = text.replace(wsStart, '');
if (!node.nextSibling) text = text.replace(wsEnd, '');
return text;
};
})()
};
CSS.ready = (function () {
// don't do anything in Safari 2 (it doesn't recognize any media type)
var complete = !CSS.recognizesMedia('all'), hasLayout = false;
var queue = [], perform = function () {
complete = true;
for (var fn; fn = queue.shift(); fn()){};
};
var links = elementsByTagName('link'), styles = elementsByTagName('style');
var checkTypes = {
'': 1,
'text/css': 1
};
function isContainerReady(el) {
if (!checkTypes[el.type.toLowerCase()]) return true;
return el.disabled || isSheetReady(el.sheet, el.media || 'screen');
}
function isSheetReady(sheet, media) {
// in Opera sheet.disabled is true when it's still loading,
// even though link.disabled is false. they stay in sync if
// set manually.
if (!CSS.recognizesMedia(media || 'all')) return true;
if (!sheet || sheet.disabled) return false;
try {
var rules = sheet.cssRules, rule;
if (rules) {
// needed for Safari 3 and Chrome 1.0.
// in standards-conforming browsers cssRules contains @-rules.
// Chrome 1.0 weirdness: rules[<number larger than .length - 1>]
// returns the last rule, so a for loop is the only option.
search: for (var i = 0, l = rules.length; rule = rules[i], i < l; ++i) {
switch (rule.type) {
case 2: // @charset
break;
case 3: // @import
if (!isSheetReady(rule.styleSheet, rule.media.mediaText)) return false;
break;
default:
// only @charset can precede @import
break search;
}
}
}
}
catch (e) {} // probably a style sheet from another domain
return true;
}
function allStylesLoaded() {
// Internet Explorer's style sheet model, there's no need to do anything
if (document.createStyleSheet) return true;
// standards-compliant browsers
var el, i;
for (i = 0; el = links[i]; ++i) {
if (el.rel.toLowerCase() == 'stylesheet' && !isContainerReady(el)) return false;
}
for (i = 0; el = styles[i]; ++i) {
if (!isContainerReady(el)) return false;
}
return true;
}
DOM.ready(function () {
// getComputedStyle returns null in Gecko if used in an iframe with display: none
if (!hasLayout) hasLayout = CSS.getStyle(document.body).isUsable();
if (complete || (hasLayout && allStylesLoaded())) perform();
else setTimeout(arguments.callee, 10);
});
return function(listener) {
if (complete) listener();
else queue.push(listener);
};
})();
function Font(data) {
var face = this.face = data.face, ligatureCache = [], 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;
};
this.applyLigatures = function(text, ligatures) {
// find cached ligature configuration for this font
for (var i=0, ligatureConfig; i<ligatureCache.length && !ligatureConfig; i++)
if (ligatureCache[i].ligatures === ligatures)
ligatureConfig = ligatureCache[i];
// if there is none, it needs to be created and cached
if (!ligatureConfig) {
// identify letter groups to prepare regular expression that matches these
var letterGroups = [];
for (var letterGroup in ligatures) {
if (this.glyphs[ligatures[letterGroup]]) {
letterGroups.push(letterGroup);
}
}
// sort by longer groups first, then alphabetically (to aid caching by this key)
var regexpText = letterGroups.sort(function(a, b) {
return b.length - a.length || a > b;
}).join('|');
ligatureCache.push(ligatureConfig = {
ligatures: ligatures,
// create regular expression for matching desired ligatures that are present in the font
regexp: regexpText.length > 0
? regexpCache[regexpText] || (regexpCache[regexpText] = new RegExp(regexpText, 'g'))
: null
});
}
// return applied ligatures or original text if none exist for given configuration
return ligatureConfig.regexp
? text.replace(ligatureConfig.regexp, function(match) {
return ligatures[match] || match;
})
: text;
};
}
function FontFamily() {
var styles = {}, mapping = {
oblique: 'italic',
italic: 'oblique'
};
this.add = function(font) {
(styles[font.style] || (styles[font.style] = {}))[font.weight] = font;
};
this.get = function(style, weight) {
var weights = styles[style] || styles[mapping[style]]
|| styles.normal || styles.italic || styles.oblique;
if (!weights) return null;
// we don't have to worry about "bolder" and "lighter"
// because IE's currentStyle returns a numeric value for it,
// and other browsers use the computed value anyway
weight = {
normal: 400,
bold: 700
}[weight] || parseInt(weight, 10);
if (weights[weight]) return weights[weight];
// http://www.w3.org/TR/CSS21/fonts.html#propdef-font-weight
// Gecko uses x99/x01 for lighter/bolder
var up = {
1: 1,
99: 0
}[weight % 100], alts = [], min, max;
if (up === undefined) up = weight > 400;
if (weight == 500) weight = 400;
for (var alt in weights) {
if (!hasOwnProperty(weights, alt)) continue;
alt = parseInt(alt, 10);
if (!min || alt < min) min = alt;
if (!max || alt > max) max = alt;
alts.push(alt);
}
if (weight < min) weight = min;
if (weight > max) weight = max;
alts.sort(function(a, b) {
return (up
? (a >= weight && b >= weight) ? a < b : a > b
: (a <= weight && b <= weight) ? a > b : a < b) ? -1 : 1;
});
return weights[alts[0]];
};
}
function HoverHandler() {
function contains(node, anotherNode) {
try {
if (node.contains) return node.contains(anotherNode);
return node.compareDocumentPosition(anotherNode) & 16;
}
catch(e) {} // probably a XUL element such as a scrollbar
return false;
}
// mouseover/mouseout (standards) mode
function onOverOut(e) {
var related = e.relatedTarget;
// there might be no relatedTarget if the element is right next
// to the window frame
if (related && contains(this, related)) return;
trigger(this, e.type == 'mouseover');
}
// mouseenter/mouseleave (probably ie) mode
function onEnterLeave(e) {
if (!e) e = window.event;
// ie model, we don't have access to "this", but
// mouseenter/leave doesn't bubble so it's fine.
trigger(e.target || e.srcElement, e.type == 'mouseenter');
}
function trigger(el, hoverState) {
// A timeout is needed so that the event can actually "happen"
// before replace is triggered. This ensures that styles are up
// to date.
setTimeout(function () {
var options = sharedStorage.get(el).options;
if (hoverState) {
options = merge(options, options.hover);
options._mediatorMode = 1;
}
api.replace(el, options, true);
}, 10);
}
this.attach = function(el) {
if (el.onmouseenter === undefined) {
addEvent(el, 'mouseover', onOverOut);
addEvent(el, 'mouseout', onOverOut);
}
else {
addEvent(el, 'mouseenter', onEnterLeave);
addEvent(el, 'mouseleave', onEnterLeave);
}
};
this.detach = function(el) {
if (el.onmouseenter === undefined) {
removeEvent(el, 'mouseover', onOverOut);
removeEvent(el, 'mouseout', onOverOut);
}
else {
removeEvent(el, 'mouseenter', onEnterLeave);
removeEvent(el, 'mouseleave', onEnterLeave);
}
};
}
function ReplaceHistory() {
var list = [], map = {};
function filter(keys) {
var values = [], key;
for (var i = 0; key = keys[i]; ++i) values[i] = list[map[key]];
return values;
}
this.add = function(key, args) {
map[key] = list.push(args) - 1;
};
this.repeat = function () {
var snapshot = arguments.length ? filter(arguments) : list, args;
for (var i = 0; args = snapshot[i++];) api.replace(args[0], args[1], true);
};
}
function Storage() {
var map = {}, at = 0;
function identify(el) {
return el.cufid || (el.cufid = ++at);
}
this.get = function(el) {
var id = identify(el);
return map[id] || (map[id] = {});
};
}
function Style(style) {
var custom = {}, sizes = {};
this.extend = function(styles) {
for (var property in styles) {
if (hasOwnProperty(styles, property)) custom[property] = styles[property];
}
return this;
};
this.get = function(property) {
return custom[property] != undefined ? custom[property] : style[property];
};
this.getSize = function(property, base) {
return sizes[property] || (sizes[property] = new CSS.Size(this.get(property), base));
};
this.isUsable = function () {
return !!style;
};
}
function addEvent(el, type, listener) {
if (el.addEventListener) {
el.addEventListener(type, listener, false);
}
else if (el.attachEvent) {
// we don't really need "this" right now, saves code
el.attachEvent('on' + type, listener);
}
}
function attach(el, options) {
if (options._mediatorMode) return el;
var storage = sharedStorage.get(el);
var oldOptions = storage.options;
if (oldOptions) {
if (oldOptions === options) return el;
if (oldOptions.hover) hoverHandler.detach(el);
}
if (options.hover && options.hoverables[el.nodeName.toLowerCase()]) {
hoverHandler.attach(el);
}
storage.options = options;
return el;
}
function cached(fun) {
var cache = {};
return function(key) {
if (!hasOwnProperty(cache, key)) cache[key] = fun.apply(null, arguments);
return cache[key];
};
}
function getFont(el, style) {
var families = CSS.quotedList(style.get('fontFamily').toLowerCase()), family;
for (var i = 0; family = families[i]; ++i) {
if (fonts[family]) return fonts[family].get(style.get('fontStyle'), style.get('fontWeight'));
}
return null;
}
function elementsByTagName(query) {
return document.getElementsByTagName(query);
}
function hasOwnProperty(obj, property) {
return obj.hasOwnProperty(property);
}
function merge() {
var merged = {}, arg, key;
for (var i = 0, l = arguments.length; arg = arguments[i], i < l; ++i) {
for (key in arg) {
if (hasOwnProperty(arg, key)) merged[key] = arg[key];
}
}
return merged;
}
function process(font, text, style, options, node, el) {
var fragment = document.createDocumentFragment(), processed;
if (text === '') return fragment;
var separate = options.separate;
var parts = text.split(separators[separate]), needsAligning = (separate == 'words');
if (needsAligning && HAS_BROKEN_REGEXP) {
// @todo figure out a better way to do this
if (/^s/.test(text)) parts.unshift('');
if (/s$/.test(text)) parts.push('');
}
for (var i = 0, l = parts.length; i < l; ++i) {
processed = engines[options.engine](font,
needsAligning ? CSS.textAlign(parts[i], style, i, l) : parts[i],
style, options, node, el, i < l - 1);
if (processed) fragment.appendChild(processed);
}
return fragment;
}
function removeEvent(el, type, listener) {
if (el.removeEventListener) {
el.removeEventListener(type, listener, false);
}
else if (el.detachEvent) {
el.detachEvent('on' + type, listener);
}
}
function replaceElement(el, options) {
var name = el.nodeName.toLowerCase();
if (options.ignore[name]) return;
if (options.ignoreClass && options.ignoreClass.test(el.className)) return;
if (options.onBeforeReplace) options.onBeforeReplace(el, options);
var replace = !options.textless[name], simple = (options.trim === 'simple');
var style = CSS.getStyle(attach(el, options)).extend(options);
// may cause issues if the element contains other elements
// with larger fontSize, however such cases are rare and can
// be fixed by using a more specific selector
if (parseFloat(style.get('fontSize')) === 0) return;
var font = getFont(el, style), node, type, next, anchor, text, lastElement;
var isShy = options.softHyphens, anyShy = false, pos, shy, reShy = /u00ad/g;
var modifyText = options.modifyText;
if (!font) return;
for (node = el.firstChild; node; node = next) {
type = node.nodeType;
next = node.nextSibling;
if (replace && type == 3) {
if (isShy && el.nodeName.toLowerCase() != TAG_SHY) {
pos = node.data.indexOf('u00ad');
if (pos >= 0) {
node.splitText(pos);
next = node.nextSibling;
next.deleteData(0, 1);
shy = document.createElement(TAG_SHY);
shy.appendChild(document.createTextNode('u00ad'));
el.insertBefore(shy, next);
next = shy;
anyShy = true;
}
}
// Node.normalize() is broken in IE 6, 7, 8
if (anchor) {
anchor.appendData(node.data);
el.removeChild(node);
}
else anchor = node;
if (next) continue;
}
if (anchor) {
text = anchor.data;
if (!isShy) text = text.replace(reShy, '');
text = CSS.whiteSpace(text, style, anchor, lastElement, simple);
// modify text only on the first replace
if (modifyText) text = modifyText(text, anchor, el, options);
el.replaceChild(process(font, text, style, options, node, el), anchor);
anchor = null;
}
if (type == 1) {
if (node.firstChild) {
if (node.nodeName.toLowerCase() == 'cufon') {
engines[options.engine](font, null, style, options, node, el);
}
else arguments.callee(node, options);
}
lastElement = node;
}
}
if (isShy && anyShy) {
updateShy(el);
if (!trackingShy) addEvent(window, 'resize', updateShyOnResize);
trackingShy = true;
}
if (options.onAfterReplace) options.onAfterReplace(el, options);
}
function updateShy(context) {
var shys, shy, parent, glue, newGlue, next, prev, i;
shys = context.getElementsByTagName(TAG_SHY);
// unfortunately there doesn't seem to be any easy
// way to avoid having to loop through the shys twice.
for (i = 0; shy = shys[i]; ++i) {
shy.className = C_SHY_DISABLED;
glue = parent = shy.parentNode;
if (glue.nodeName.toLowerCase() != TAG_GLUE) {
newGlue = document.createElement(TAG_GLUE);
newGlue.appendChild(shy.previousSibling);
parent.insertBefore(newGlue, shy);
newGlue.appendChild(shy);
}
else {
// get rid of double glue (edge case fix)
glue = glue.parentNode;
if (glue.nodeName.toLowerCase() == TAG_GLUE) {
parent = glue.parentNode;
while (glue.firstChild) {
parent.insertBefore(glue.firstChild, glue);
}
parent.removeChild(glue);
}
}
}
for (i = 0; shy = shys[i]; ++i) {
shy.className = '';
glue = shy.parentNode;
parent = glue.parentNode;
next = glue.nextSibling || parent.nextSibling;
// make sure we're comparing same types
prev = (next.nodeName.toLowerCase() == TAG_GLUE) ? glue : shy.previousSibling;
if (prev.offsetTop >= next.offsetTop) {
shy.className = C_SHY_DISABLED;
if (prev.offsetTop < next.offsetTop) {
// we have an annoying edge case, double the glue
newGlue = document.createElement(TAG_GLUE);
parent.insertBefore(newGlue, glue);
newGlue.appendChild(glue);
newGlue.appendChild(next);
}
}
}
}
function updateShyOnResize() {
if (ignoreResize) return; // needed for IE
CSS.addClass(DOM.root(), C_VIEWPORT_RESIZING);
clearTimeout(shyTimer);
shyTimer = setTimeout(function () {
ignoreResize = true;
CSS.removeClass(DOM.root(), C_VIEWPORT_RESIZING);
updateShy(document);
ignoreResize = false;
}, 100);
}
var HAS_BROKEN_REGEXP = ' '.split(/s+/).length == 0;
var TAG_GLUE = 'cufonglue';
var TAG_SHY = 'cufonshy';
var C_SHY_DISABLED = 'cufon-shy-disabled';
var C_VIEWPORT_RESIZING = 'cufon-viewport-resizing';
var regexpCache = {};
var sharedStorage = new Storage();
var hoverHandler = new HoverHandler();
var replaceHistory = new ReplaceHistory();
var initialized = false;
var trackingShy = false;
var shyTimer;
var ignoreResize = false;
var engines = {}, fonts = {}, defaultOptions = {
autoDetect: false,
engine: null,
forceHitArea: false,
hover: false,
hoverables: {
a: true
},
ignore: {
applet: 1,
canvas: 1,
col: 1,
colgroup: 1,
head: 1,
iframe: 1,
map: 1,
noscript: 1,
optgroup: 1,
option: 1,
script: 1,
select: 1,
style: 1,
textarea: 1,
title: 1,
pre: 1
},
ignoreClass: null,
modifyText: null,
onAfterReplace: null,
onBeforeReplace: null,
printable: true,
selector: (
window.Sizzle
|| (window.jQuery && function(query) { return jQuery(query); }) // avoid noConflict issues
|| (window.dojo && dojo.query)
|| (window.glow && glow.dom && glow.dom.get)
|| (window.Ext && Ext.query)
|| (window.YAHOO && YAHOO.util && YAHOO.util.Selector && YAHOO.util.Selector.query)
|| (window.$$ && function(query) { return $$(query); })
|| (window.$ && function(query) { return $(query); })
|| (document.querySelectorAll && function(query) { return document.querySelectorAll(query); })
|| elementsByTagName
),
separate: 'words', // 'none' and 'characters' are also accepted
softHyphens: true,
textless: {
dl: 1,
html: 1,
ol: 1,
table: 1,
tbody: 1,
thead: 1,
tfoot: 1,
tr: 1,
ul: 1
},
textShadow: 'none',
trim: 'advanced',
ligatures: {
'ff': 'ufb00',
'fi': 'ufb01',
'fl': 'ufb02',
'ffi': 'ufb03',
'ffl': 'ufb04',
'u017ft': 'ufb05',
'st': 'ufb06'
}
};
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') ? /[^Su00a0]+/ : /s+/,
characters: '',
none: /^/
};
api.now = function () {
DOM.ready();
return api;
};
api.refresh = function () {
replaceHistory.repeat.apply(replaceHistory, arguments);
return api;
};
api.registerEngine = function(id, engine) {
if (!engine) return api;
engines[id] = engine;
return api.set('engine', id);
};
api.registerFont = function(data) {
if (!data) return api;
var font = new Font(data), family = font.family;
if (!fonts[family]) fonts[family] = new FontFamily();
fonts[family].add(font);
return api.set('fontFamily', '"' + family + '"');
};
api.replace = function(elements, options, ignoreHistory) {
options = merge(defaultOptions, options);
if (!options.engine) return api; // there's no browser support so we'll just stop here
if (!initialized) {
CSS.addClass(DOM.root(), 'cufon-active cufon-loading');
CSS.ready(function () {
// fires before any replace() calls, but it doesn't really matter
CSS.addClass(CSS.removeClass(DOM.root(), 'cufon-loading'), 'cufon-ready');
});
initialized = true;
}
if (options.hover) options.forceHitArea = true;
if (options.autoDetect) delete options.fontFamily;
if (typeof options.ignoreClass == 'string') {
options.ignoreClass = new RegExp('(?:^|\s)(?:' + options.ignoreClass.replace(/s+/g, '|') + ')(?:\s|$)');
}
if (typeof options.textShadow == 'string') {
options.textShadow = CSS.textShadow(options.textShadow);
}
if (typeof options.color == 'string' && /^-/.test(options.color)) {
options.textGradient = CSS.gradient(options.color);
}
else delete options.textGradient;
if (typeof elements == 'string') {
if (!ignoreHistory) replaceHistory.add(elements, arguments);
elements = [ elements ];
}
else if (elements.nodeType) elements = [ elements ];
CSS.ready(function () {
for (var i = 0, l = elements.length; i < l; ++i) {
var el = elements[i];
if (typeof el == 'string') api.replace(options.selector(el), options, true);
else replaceElement(el, options);
}
});
return api;
};
api.set = function(option, value) {
defaultOptions[option] = value;
return api;
};
return api;
})();
Cufon.registerEngine('vml', (function () {
var ns = document.namespaces;
if (!ns) return;
ns.add('cvml', 'urn:schemas-microsoft-com:vml');
ns = null;
var check = document.createElement('cvml:shape');
check.style.behavior = 'url(#default#VML)';
if (!check.coordsize) return; // VML isn't supported
check = null;
var HAS_BROKEN_LINEHEIGHT = (document.documentMode || 0) < 8;
var styleSheet = document.createElement('style');
styleSheet.type = 'text/css';
styleSheet.styleSheet.cssText = (
'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;}' +
'}'
).replace(/;/g, '!important;');
document.getElementsByTagName('head')[0].appendChild(styleSheet);
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(options.ligatures ? font.applyLigatures(text, options.ligatures) : 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(options.ligatures ? font.applyLigatures(text, options.ligatures) : 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;
var pixelRatio = window.devicePixelRatio || 1;
if (pixelRatio != 1) {
canvas.width = canvasWidth * pixelRatio;
canvas.height = canvasHeight * pixelRatio;
g.scale(pixelRatio, pixelRatio);
}
// 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();
// the following moveTo is for Opera 9.2. if we don't
// do this, it won't forget the previous path which
// results in garbled text.
g.moveTo(0, 0);
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;
};
})());
?>