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.