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