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