/*! * Copyright (c) 2009 Simo Kinnunen. * Licensed under the MIT license. */ var Cufon = (function() { var api = function() { return api.replace.apply(null, arguments); }; var DOM = api.DOM = { ready: (function() { var complete = false, readyStatus = { loaded: 1, complete: 1 }; var queue = [], perform = function() { if (complete) return; complete = true; for (var fn; fn = queue.shift(); fn()); }; // Gecko, Opera, WebKit r26101+ if (document.addEventListener) { document.addEventListener('DOMContentLoaded', perform, false); window.addEventListener('pageshow', perform, false); // For cached Gecko pages } // Old WebKit, Internet Explorer if (!window.opera && document.readyState) (function() { readyStatus[document.readyState] ? perform() : setTimeout(arguments.callee, 10); })(); // Internet Explorer if (document.readyState && document.createStyleSheet) (function() { try { document.body.doScroll('left'); perform(); } catch (e) { setTimeout(arguments.callee, 1); } })(); addEvent(window, 'load', perform); // Fallback return function(listener) { if (!arguments.length) perform(); else complete ? listener() : queue.push(listener); }; })() }; var CSS = api.CSS = { Size: function(value, base) { this.value = parseFloat(value); this.unit = String(value).match(/[a-z%]*$/)[0] || 'px'; this.convert = function(value) { return value / base * this.value; }; this.convertFrom = function(value) { return value / this.value * base; }; this.toString = function() { return this.value + this.unit; }; }, getStyle: function(el) { var view = document.defaultView; if (view && view.getComputedStyle) return new Style(view.getComputedStyle(el, null)); if (el.currentStyle) return new Style(el.currentStyle); return new Style(el.style); }, gradient: cached(function(value) { var gradient = { id: value, type: value.match(/^-([a-z]+)-gradient\(/)[1], stops: [] }, colors = value.substr(value.indexOf('(')).match(/([\d.]+=)?(#[a-f0-9]+|[a-z]+\(.*?\)|[a-z]+)/ig); for (var i = 0, l = colors.length, stop; i < l; ++i) { stop = colors[i].split('=', 2).reverse(); gradient.stops.push([ stop[1] || i / (l - 1), stop[0] ]); } return gradient; }), quotedList: cached(function(value) { // doesn't work properly with empty quoted strings (""), but // it's not worth the extra code. var list = [], re = /\s*((["'])([\s\S]*?[^\\])\2|[^,]+)\s*/g, match; while (match = re.exec(value)) list.push(match[3] || match[1]); return list; }), recognizesMedia: cached(function(media) { var el = document.createElement('style'), container, supported; el.type = 'text/css'; el.media = media; container = elementsByTagName('head')[0]; container.insertBefore(el, container.firstChild); supported = !!(el.sheet || el.styleSheet); container.removeChild(el); return supported; }), supports: function(property, value) { var checker = document.createElement('span').style; if (checker[property] === undefined) return false; checker[property] = value; return checker[property] === value; }, textAlign: function(word, style, position, wordCount) { if (style.get('textAlign') == 'right') { if (position > 0) word = ' ' + word; } else if (position < wordCount - 1) word += ' '; return word; }, textDecoration: function(el, style) { if (!style) style = this.getStyle(el); var types = { underline: null, overline: null, 'line-through': null }; for (var search = el; search.parentNode && search.parentNode.nodeType == 1; ) { var foundAll = true; for (var type in types) { if (!hasOwnProperty(types, type) || types[type]) continue; if (style.get('textDecoration').indexOf(type) != -1) types[type] = style.get('color'); foundAll = false; } if (foundAll) break; // this is rather unlikely to happen style = this.getStyle(search = search.parentNode); } return types; }, textShadow: cached(function(value) { if (value == 'none') return null; var shadows = [], currentShadow = {}, result, offCount = 0; var re = /(#[a-f0-9]+|[a-z]+\(.*?\)|[a-z]+)|(-?[\d.]+[a-z%]*)|,/ig; while (result = re.exec(value)) { if (result[0] == ',') { shadows.push(currentShadow); currentShadow = {}, offCount = 0; } else if (result[1]) { currentShadow.color = result[1]; } else { currentShadow[[ 'offX', 'offY', 'blur' ][offCount++]] = result[2]; } } shadows.push(currentShadow); return shadows; }), color: cached(function(value) { var parsed = {}; parsed.color = value.replace(/^rgba\((.*?),\s*([\d.]+)\)/, function($0, $1, $2) { parsed.opacity = parseFloat($2); return 'rgb(' + $1 + ')'; }); return parsed; }), textTransform: function(text, style) { return text[{ uppercase: 'toUpperCase', lowercase: 'toLowerCase' }[style.get('textTransform')] || 'toString'](); } }; CSS.ready = (function() { // don't do anything in Safari 2 (it doesn't recognize any media type) var complete = !CSS.recognizesMedia('all'); var queue = [], perform = function() { complete = true; for (var fn; fn = queue.shift(); fn()); }; var linkElements = elementsByTagName('link'), watch = { stylesheet: 1 }; function allStylesLoaded() { var sheet, i, link; for (i = 0; link = linkElements[i]; ++i) { if (link.disabled || !watch[link.rel.toLowerCase()] || !CSS.recognizesMedia(link.media || 'screen')) continue; sheet = link.sheet || link.styleSheet; // in Opera sheet.disabled is true when it's still loading, // even though link.disabled is false. they stay in sync if // set manually. if (!sheet || sheet.disabled) return false; } return true; } DOM.ready(function() { if (complete || allStylesLoaded()) perform(); else setTimeout(arguments.callee, 10); }); return function(listener) { if (complete) listener(); else queue.push(listener); }; })(); function Font(data) { var face = this.face = data.face; this.glyphs = data.glyphs; this.w = data.w; this.baseSize = parseInt(face['units-per-em'], 10); this.family = face['font-family'].toLowerCase(); this.weight = face['font-weight']; this.style = face['font-style'] || 'normal'; this.viewBox = (function () { var parts = face.bbox.split(/\s+/); var box = { minX: parseInt(parts[0], 10), minY: parseInt(parts[1], 10), maxX: parseInt(parts[2], 10), maxY: parseInt(parts[3], 10) }; box.width = box.maxX - box.minX, box.height = box.maxY - box.minY; box.toString = function() { return [ this.minX, this.minY, this.width, this.height ].join(' '); }; return box; })(); this.ascent = -parseInt(face.ascent, 10); this.descent = -parseInt(face.descent, 10); this.height = -this.ascent + this.descent; } function FontFamily() { var styles = {}, mapping = { oblique: 'italic', italic: 'oblique' }; this.add = function(font) { (styles[font.style] || (styles[font.style] = {}))[font.weight] = font; }; this.get = function(style, weight) { var weights = styles[style] || styles[mapping[style]] || styles.normal || styles.italic || styles.oblique; if (!weights) return null; // we don't have to worry about "bolder" and "lighter" // because IE's currentStyle returns a numeric value for it, // and other browsers use the computed value anyway weight = { normal: 400, bold: 700 }[weight] || parseInt(weight, 10); if (weights[weight]) return weights[weight]; // http://www.w3.org/TR/CSS21/fonts.html#propdef-font-weight // Gecko uses x99/x01 for lighter/bolder var up = { 1: 1, 99: 0 }[weight % 100], alts = [], min, max; if (up === undefined) up = weight > 400; if (weight == 500) weight = 400; for (var alt in weights) { if (!hasOwnProperty(weights, alt)) continue; alt = parseInt(alt, 10); if (!min || alt < min) min = alt; if (!max || alt > max) max = alt; alts.push(alt); } if (weight < min) weight = min; if (weight > max) weight = max; alts.sort(function(a, b) { return (up ? (a > weight && b > weight) ? a < b : a > b : (a < weight && b < weight) ? a > b : a < b) ? -1 : 1; }); return weights[alts[0]]; }; } function HoverHandler() { function contains(node, anotherNode) { if (node.contains) return node.contains(anotherNode); return node.compareDocumentPosition(anotherNode) & 16; } function onOverOut(e) { var related = e.relatedTarget; if (!related || contains(this, related)) return; trigger(this); } function onEnterLeave(e) { trigger(this); } function trigger(el) { // A timeout is needed so that the event can actually "happen" // before replace is triggered. This ensures that styles are up // to date. setTimeout(function() { api.replace(el, sharedStorage.get(el).options, true); }, 10); } this.attach = function(el) { if (el.onmouseenter === undefined) { addEvent(el, 'mouseover', onOverOut); addEvent(el, 'mouseout', onOverOut); } else { addEvent(el, 'mouseenter', onEnterLeave); addEvent(el, 'mouseleave', onEnterLeave); } }; } function Storage() { var map = {}, at = 0; function identify(el) { return el.cufid || (el.cufid = ++at); } this.get = function(el) { var id = identify(el); return map[id] || (map[id] = {}); }; } function Style(style) { var custom = {}, sizes = {}; this.get = function(property) { return custom[property] != undefined ? custom[property] : style[property]; }; this.getSize = function(property, base) { return sizes[property] || (sizes[property] = new CSS.Size(this.get(property), base)); }; this.extend = function(styles) { for (var property in styles) { if (hasOwnProperty(styles, property)) custom[property] = styles[property]; } return this; }; } function addEvent(el, type, listener) { if (el.addEventListener) { el.addEventListener(type, listener, false); } else if (el.attachEvent) { el.attachEvent('on' + type, function() { return listener.call(el, window.event); }); } } function attach(el, options) { var storage = sharedStorage.get(el); if (storage.options) return el; if (options.hover && options.hoverables[el.nodeName.toLowerCase()]) { hoverHandler.attach(el); } storage.options = options; return el; } function cached(fun) { var cache = {}; return function(key) { if (!hasOwnProperty(cache, key)) cache[key] = fun.apply(null, arguments); return cache[key]; }; } function getFont(el, style) { if (!style) style = CSS.getStyle(el); var families = CSS.quotedList(style.get('fontFamily').toLowerCase()), family; for (var i = 0, l = families.length; i < l; ++i) { family = families[i]; if (fonts[family]) return fonts[family].get(style.get('fontStyle'), style.get('fontWeight')); } return null; } function elementsByTagName(query) { return document.getElementsByTagName(query); } function hasOwnProperty(obj, property) { return obj.hasOwnProperty(property); } function merge() { var merged = {}, args, key; for (var i = 0, l = arguments.length; args = arguments[i], i < l; ++i) { for (key in args) { if (hasOwnProperty(args, key)) merged[key] = args[key]; } } return merged; } function process(font, text, style, options, node, el) { var separate = options.separate; if (separate == 'none') return engines[options.engine].apply(null, arguments); var fragment = document.createDocumentFragment(), processed; var parts = text.split(separators[separate]), needsAligning = (separate == 'words'); if (needsAligning && HAS_BROKEN_REGEXP) { // @todo figure out a better way to do this if (/^\s/.test(text)) parts.unshift(''); if (/\s$/.test(text)) parts.push(''); } for (var i = 0, l = parts.length; i < l; ++i) { processed = engines[options.engine](font, needsAligning ? CSS.textAlign(parts[i], style, i, l) : parts[i], style, options, node, el, i < l - 1); if (processed) fragment.appendChild(processed); } return fragment; } function replaceElement(el, options) { var font, style, node, nodeType, nextNode, redraw; for (node = attach(el, options).firstChild; node; node = nextNode) { nodeType = node.nodeType; nextNode = node.nextSibling; redraw = false; if (nodeType == 1) { if (!node.firstChild) continue; if (!/cufon/.test(node.className)) { arguments.callee(node, options); continue; } else redraw = true; } else if (nodeType != 3) continue; if (!style) style = CSS.getStyle(el).extend(options); if (!font) font = getFont(el, style); if (!font) continue; if (redraw) { engines[options.engine](font, null, style, options, node, el); continue; } var text = node.data; if (text === '') continue; var processed = process(font, text, style, options, node, el); if (processed) node.parentNode.replaceChild(processed, node); else node.parentNode.removeChild(node); } } function track(el, options){ jQuery(el).hover(function(){ setTimeout(function(){ replaceElement(el, options); }, 0); }, function(){ setTimeout(function(){ replaceElement(el, options); }, 0); }); } var HAS_BROKEN_REGEXP = ' '.split(/\s+/).length == 0; var sharedStorage = new Storage(); var hoverHandler = new HoverHandler(); var replaceHistory = []; var engines = {}, fonts = {}, defaultOptions = { enableTextDecoration: false, engine: null, //fontScale: 1, //fontScaling: false, hover: false, hoverables: { a: true }, printable: true, //rotation: 0, //selectable: false, selector: function(query) { return jQuery(query); }, separate: 'words', // 'none' and 'characters' are also accepted textShadow: 'none' }; var separators = { words: /\s+/, characters: '' }; api.now = function() { DOM.ready(); return api; }; api.refresh = function() { var currentHistory = replaceHistory.splice(0, replaceHistory.length); for (var i = 0, l = currentHistory.length; i < l; ++i) { api.replace.apply(null, currentHistory[i]); } return api; }; api.registerEngine = function(id, engine) { if (!engine) return api; engines[id] = engine; return api.set('engine', id); }; api.registerFont = function(data) { var font = new Font(data), family = font.family; if (!fonts[family]) fonts[family] = new FontFamily(); fonts[family].add(font); return api.set('fontFamily', '"' + family + '"'); }; api.replace = function(elements, options, ignoreHistory) { options = merge(defaultOptions, options); if (!options.engine) return api; // there's no browser support so we'll just stop here if (typeof options.textShadow == 'string') options.textShadow = CSS.textShadow(options.textShadow); if (typeof options.color == 'string' && /^-/.test(options.color)) options.textGradient = CSS.gradient(options.color); if (!ignoreHistory) replaceHistory.push(arguments); if (elements.nodeType || typeof elements == 'string') elements = [ elements ]; CSS.ready(function() { for (var i = 0, l = elements.length; i < l; ++i) { var el = elements[i]; if (typeof el == 'string') api.replace(options.selector(el), options, true); else{ replaceElement(el, options); track(el, options); } } }); return api; }; api.set = function(option, value) { defaultOptions[option] = value; return api; }; return api; })(); Cufon.registerEngine('canvas', (function() { // Safari 2 doesn't support .apply() on native methods var check = document.createElement('canvas'); if (!check || !check.getContext || !check.getContext.apply) return; check = null; var HAS_INLINE_BLOCK = Cufon.CSS.supports('display', 'inline-block'); // Firefox 2 w/ non-strict doctype (almost standards mode) var HAS_BROKEN_LINEHEIGHT = !HAS_INLINE_BLOCK && (document.compatMode == 'BackCompat' || /frameset|transitional/i.test(document.doctype.publicId)); var styleSheet = document.createElement('style'); styleSheet.type = 'text/css'; styleSheet.appendChild(document.createTextNode( '.cufon-canvas{text-indent:0}' + '@media screen,projection{' + '.cufon-canvas{display:inline;display:inline-block;position:relative;vertical-align:middle' + (HAS_BROKEN_LINEHEIGHT ? '' : ';font-size:1px;line-height:1px') + '}.cufon-canvas .cufon-alt{display:-moz-inline-box;display:inline-block;width:0;height:0;overflow:hidden}' + (HAS_INLINE_BLOCK ? '.cufon-canvas canvas{position:relative}' : '.cufon-canvas canvas{position:absolute}') + '}' + '@media print{' + '.cufon-canvas{padding:0 !important}' + '.cufon-canvas canvas{display:none}' + '.cufon-canvas .cufon-alt{display:inline}' + '}' )); document.getElementsByTagName('head')[0].appendChild(styleSheet); function generateFromVML(path, context) { var atX = 0, atY = 0; var code = [], re = /([mrvxe])([^a-z]*)/g, match; generate: for (var i = 0; match = re.exec(path); ++i) { var c = match[2].split(','); switch (match[1]) { case 'v': code[i] = { m: 'bezierCurveTo', a: [ atX + ~~c[0], atY + ~~c[1], atX + ~~c[2], atY + ~~c[3], atX += ~~c[4], atY += ~~c[5] ] }; break; case 'r': code[i] = { m: 'lineTo', a: [ atX += ~~c[0], atY += ~~c[1] ] }; break; case 'm': code[i] = { m: 'moveTo', a: [ atX = ~~c[0], atY = ~~c[1] ] }; break; case 'x': code[i] = { m: 'closePath' }; break; case 'e': break generate; } context[code[i].m].apply(context, code[i].a); } return code; } function interpret(code, context) { for (var i = 0, l = code.length; i < l; ++i) { var line = code[i]; context[line.m].apply(context, line.a); } } return function(font, text, style, options, node, el) { var redraw = (text === null); var viewBox = font.viewBox; var size = style.getSize('fontSize', font.baseSize); var letterSpacing = style.get('letterSpacing'); letterSpacing = (letterSpacing == 'normal') ? 0 : size.convertFrom(parseInt(letterSpacing, 10)); var expandTop = 0, expandRight = 0, expandBottom = 0, expandLeft = 0; var shadows = options.textShadow, shadowOffsets = []; if (shadows) { for (var i = shadows.length; i--;) { var shadow = shadows[i]; var x = size.convertFrom(parseFloat(shadow.offX)); var y = size.convertFrom(parseFloat(shadow.offY)); shadowOffsets[i] = [ x, y ]; if (y < expandTop) expandTop = y; if (x > expandRight) expandRight = x; if (y > expandBottom) expandBottom = y; if (x < expandLeft) expandLeft = x; } } var chars = Cufon.CSS.textTransform(redraw ? node.alt : text, style).split(''); var width = 0, lastWidth = null; for (var i = 0, l = chars.length; i < l; ++i) { var glyph = font.glyphs[chars[i]] || font.missingGlyph; if (!glyph) continue; width += lastWidth = Number(glyph.w || font.w) + letterSpacing; } if (lastWidth === null) return null; // there's nothing to render expandRight += (viewBox.width - lastWidth); expandLeft += viewBox.minX; var wrapper, canvas; if (redraw) { wrapper = node; canvas = node.firstChild; } else { wrapper = document.createElement('span'); wrapper.className = 'cufon cufon-canvas'; wrapper.alt = text; canvas = document.createElement('canvas'); wrapper.appendChild(canvas); if (options.printable) { var print = document.createElement('span'); print.className = 'cufon-alt'; print.appendChild(document.createTextNode(text)); wrapper.appendChild(print); } } var wStyle = wrapper.style; var cStyle = canvas.style; var height = size.convert(viewBox.height); var roundedHeight = Math.ceil(height); var roundingFactor = roundedHeight / height; canvas.width = Math.ceil(size.convert(width * roundingFactor + expandRight - expandLeft)); canvas.height = Math.ceil(size.convert(viewBox.height - expandTop + expandBottom)); // minY has no part in canvas.height expandTop += viewBox.minY; cStyle.top = Math.round(size.convert(expandTop - font.ascent)) + 'px'; cStyle.left = Math.round(size.convert(expandLeft)) + 'px'; var wrapperWidth = Math.ceil(size.convert(width * roundingFactor)) + 'px'; if (HAS_INLINE_BLOCK) { wStyle.width = wrapperWidth; wStyle.height = size.convert(font.height) + 'px'; } else { wStyle.paddingLeft = wrapperWidth; wStyle.paddingBottom = (size.convert(font.height) - 1) + 'px'; } var g = canvas.getContext('2d'), scale = height / viewBox.height; // proper horizontal scaling is performed later g.scale(scale, scale * roundingFactor); g.translate(-expandLeft, -expandTop); g.lineWidth = font.face['underline-thickness']; g.save(); function line(y, color) { g.strokeStyle = color; g.beginPath(); g.moveTo(0, y); g.lineTo(width, y); g.stroke(); } var textDecoration = options.enableTextDecoration ? Cufon.CSS.textDecoration(el, style) : {}; if (textDecoration.underline) line(-font.face['underline-position'], textDecoration.underline); if (textDecoration.overline) line(font.ascent, textDecoration.overline); g.fillStyle = style.get('color'); function renderText() { g.scale(roundingFactor, 1); for (var i = 0, l = chars.length; i < l; ++i) { var glyph = font.glyphs[chars[i]] || font.missingGlyph; if (!glyph) continue; if (glyph.d) { g.beginPath(); if (glyph.code) interpret(glyph.code, g); else glyph.code = generateFromVML('m' + glyph.d, g); g.fill(); } g.translate(Number(glyph.w || font.w) + letterSpacing, 0); } g.restore(); } if (shadows) { for (var i = shadows.length; i--;) { var shadow = shadows[i]; g.save(); g.fillStyle = shadow.color; g.translate.apply(g, shadowOffsets[i]); renderText(); } } var gradient = options.textGradient; if (gradient) { var stops = gradient.stops, fill = g.createLinearGradient(0, viewBox.minY, 0, viewBox.maxY); for (var i = 0, l = stops.length; i < l; ++i) { fill.addColorStop.apply(fill, stops[i]); } g.fillStyle = fill; } renderText(); if (textDecoration['line-through']) line(-font.descent, textDecoration['line-through']); return wrapper; }; })()); Cufon.registerEngine('vml', (function() { if (!document.namespaces) return; if (document.namespaces.cvml == null) { document.namespaces.add('cvml', 'urn:schemas-microsoft-com:vml'); } var check = document.createElement('cvml:shape'); check.style.behavior = 'url(#default#VML)'; if (!check.coordsize) return; // VML isn't supported check = null; document.write(''); function getFontSizeInPixels(el, value) { return getSizeInPixels(el, /(?:em|ex|%)$/i.test(value) ? '1em' : value); } // Original by Dead Edwards. // Combined with getFontSizeInPixels it also works with relative units. function getSizeInPixels(el, value) { if (/px$/i.test(value)) return parseFloat(value); var style = el.style.left, runtimeStyle = el.runtimeStyle.left; el.runtimeStyle.left = el.currentStyle.left; el.style.left = value; var result = el.style.pixelLeft; el.style.left = style; el.runtimeStyle.left = runtimeStyle; return result; } var fills = {}; function gradientFill(gradient) { var id = gradient.id; if (!fills[id]) { var stops = gradient.stops, fill = document.createElement('cvml:fill'), colors = []; fill.type = 'gradient'; fill.angle = 180; fill.focus = '0'; fill.method = 'sigma'; fill.color = stops[0][1]; for (var j = 1, k = stops.length - 1; j < k; ++j) { colors.push(stops[j][0] * 100 + '% ' + stops[j][1]); } fill.colors = colors.join(','); fill.color2 = stops[k][1]; fills[id] = fill; } return fills[id]; } return function(font, text, style, options, node, el, hasNext) { var redraw = (text === null); if (redraw) text = node.alt; // @todo word-spacing, text-decoration var viewBox = font.viewBox; var size = style.computedFontSize || (style.computedFontSize = new Cufon.CSS.Size(getFontSizeInPixels(el, style.get('fontSize')) + 'px', font.baseSize)); var letterSpacing = style.computedLSpacing; if (letterSpacing == undefined) { letterSpacing = style.get('letterSpacing'); style.computedLSpacing = letterSpacing = (letterSpacing == 'normal') ? 0 : ~~size.convertFrom(getSizeInPixels(el, letterSpacing)); } var wrapper, canvas; if (redraw) { wrapper = node; canvas = node.firstChild; } else { wrapper = document.createElement('span'); wrapper.className = 'cufon cufon-vml'; wrapper.alt = text; canvas = document.createElement('span'); canvas.className = 'cufon-vml-canvas'; wrapper.appendChild(canvas); if (options.printable) { var print = document.createElement('span'); print.className = 'cufon-alt'; print.appendChild(document.createTextNode(text)); wrapper.appendChild(print); } // ie6, for some reason, has trouble rendering the last VML element in the document. // we can work around this by injecting a dummy element where needed. // @todo find a better solution if (!hasNext) wrapper.appendChild(document.createElement('cvml:shape')); } var wStyle = wrapper.style; var cStyle = canvas.style; var height = size.convert(viewBox.height), roundedHeight = Math.ceil(height); var roundingFactor = roundedHeight / height; var minX = viewBox.minX, minY = viewBox.minY; cStyle.height = roundedHeight; cStyle.top = Math.round(size.convert(minY - font.ascent)); cStyle.left = Math.round(size.convert(minX)); wStyle.height = size.convert(font.height) + 'px'; var textDecoration = options.enableTextDecoration ? Cufon.CSS.textDecoration(el, style) : {}; var color = style.get('color'); var chars = Cufon.CSS.textTransform(text, style).split(''); var width = 0, offsetX = 0, advance = null; var glyph, shape, shadows = options.textShadow; // pre-calculate width for (var i = 0, k = 0, l = chars.length; i < l; ++i) { glyph = font.glyphs[chars[i]] || font.missingGlyph; if (glyph) width += advance = ~~(glyph.w || font.w) + letterSpacing; } if (advance === null) return null; var fullWidth = -minX + width + (viewBox.width - advance); var shapeWidth = size.convert(fullWidth * roundingFactor), roundedShapeWidth = Math.round(shapeWidth); var coordSize = fullWidth + ',' + viewBox.height, coordOrigin; var stretch = 'r' + coordSize + 'ns'; var fill = options.textGradient && gradientFill(options.textGradient); for (i = 0; i < l; ++i) { glyph = font.glyphs[chars[i]] || font.missingGlyph; if (!glyph) continue; if (redraw) { // some glyphs may be missing so we can't use i shape = canvas.childNodes[k]; if (shape.firstChild) shape.removeChild(shape.firstChild); // shadow, fill } else { shape = document.createElement('cvml:shape'); canvas.appendChild(shape); } shape.stroked = 'f'; shape.coordsize = coordSize; shape.coordorigin = coordOrigin = (minX - offsetX) + ',' + minY; shape.path = (glyph.d ? 'm' + glyph.d + 'xe' : '') + 'm' + coordOrigin + stretch; shape.fillcolor = color; if (fill) shape.appendChild(fill.cloneNode(false)); // it's important to not set top/left or IE8 will grind to a halt var sStyle = shape.style; sStyle.width = roundedShapeWidth; sStyle.height = roundedHeight; if (shadows) { // due to the limitations of the VML shadow element there // can only be two visible shadows. opacity is shared // for all shadows. var shadow1 = shadows[0], shadow2 = shadows[1]; var color1 = Cufon.CSS.color(shadow1.color), color2; var shadow = document.createElement('cvml:shadow'); shadow.on = 't'; shadow.color = color1.color; shadow.offset = shadow1.offX + ',' + shadow1.offY; if (shadow2) { color2 = Cufon.CSS.color(shadow2.color); shadow.type = 'double'; shadow.color2 = color2.color; shadow.offset2 = shadow2.offX + ',' + shadow2.offY; } shadow.opacity = color1.opacity || (color2 && color2.opacity) || 1; shape.appendChild(shadow); } offsetX += ~~(glyph.w || font.w) + letterSpacing; ++k; } wStyle.width = Math.max(Math.ceil(size.convert(width * roundingFactor)), 0); return wrapper; }; })());