PageSourceSearch

https://smuxi.im/_include/js/cufon.js

js smuxi.im collected 2026-10-02 18:27:46 UTC 39,451 bytes, 1,378 lines download raw bytes

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

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.