For more info please visit our web page timecase.net
+
+
+ Liability
+
We cannot guarantee that this software will run in every environment or together with any other application, without producing errors. Any liability for damage of any sort is hereby denied.
+
+ Please evaluate this software with non-critical data before use.
+
+
+
+ Copyright
+
Designed & developed by Milos Stojanovic / interactive32.com.
+ Assign projects to customers and categorize work by type.
+
+ Enter time tracking records by easily selecting time span.
+
+ Auto-track time with simple start/stop timer button.
+
+ Create users and assign them appropriate roles.
+
+ Assign projects to customers and allow them to login and see their reports.
+
+ Four different user roles and special customer role provides advanced security features.
+
+ See time tracking reports, filter data with ajax, export as csv or print as tables.
+
+ Modern, easy to use ajax interface.
+
+ Responsive design adapts to any screen size, from desktop to phones.
+
+
+
+
+
+
+
Installation
+
+ Follow these steps to install TimeCase:
+
+
Make sure your have minimum PHP version 5.2 + Apache2
+
Copy all files to your server (via FTP or similar)
+
Import database.sql from database folder into your mysql server
+
Update _machine_config.php to reflect your database settings
+
Default administrator username/password is admin/admin123
+
+
+
+
+
+
Using TimeCase
+
Some options will not be available for all access levels - for more information about access levels please read the next chapter.
+
+ Time Tracking
+
Track your time either by manually selecting time span or by auto-tracking. Auto-tracking is done vie the start/stop button. When start button is pressed time will start to flow and after you click on stop timer button dialog will appear and you can save this entry. On this screen you can also select default project and default work type so you don't have to select this every time. By clicking on existing time entry you can edit this record.
+
Available record options are:
+
Start - time when current job/task has started
+
End - time when your job/task has ended
+
User - if you want to assign this entry to another user. Default is current logged in user.
+
Project - project on which this entry will be assigned
+
Wotk Type - type or category
+
Description
+
+
+ Customers
+
Here you can manage your customers.
+
Available record options are:
+
Name - customer's name
+
Status - customer status (see below)
+
Allow Login - if you switch this on customer will be able to login with customer access level
+
Email / Username - login name
+
Password - login password
+
Other contact info data (address, phone...)
+
Details
+
+
+ Projects
+
Here you can manage your projects.
+
Available record options are:
+
Title - project title
+
Status - project status (see below)
+
Customer - project is always assigned to specific customer. You can create one customer for internal projects
+
Created, Closed, Deadline - dates associated with this project (info record)
+
Progress - this is percentage record - from 0 to 100. It represents this project current progress (info record)
+
Description
+
+
+ Reports
+
Reports allows you, your users or customers to see current time tracking records in real-time. With ajax filters you can easily select range you are interested in. You can also download currently selected records as CSV file or show all records in html printable table. At the bottom you will see total duration which will show total duration in hours:minutes for currently selected/filtered records.
+
+ Users
+
On this page admin can add or manage users, their levels, passwords etc.
+
+ Work Types
+
On this page admin can add or manage work types (categories)
+
+ Statuses
+
All customers and projects records have status field. There are three default statuses - important, active and closed. All records will be sorted by status so important will go first. If record is has status closed then this record will not show on drop-down menus except on reports. This gives you ability to archive old customer/project records and make drop-down selections smaller and faster. You can add additional statuses but you cannot change basic three types.
+
+ Account Settings
+
All users except customers have ability to change their personal account settings like full name, email, password and details.
+
+
+
+
+
+
Access Levels
+
+ TimeCase has five different access levels:
+
+
+ Administrator
+
Administrator can perform all actions: track and manage tracked time, manage customers, projects, users, work types, statuses and see reports.
+
+ Manager
+
Manager can perform all actions as administrator except manage users, work types and statuses. This access level can also track time on behalf of other user and edit all tracking records.
+
+ User
+
+ User can track time (auto-track or choose specific time span), see reports and manage account. User can only track, edit and see own tracking records.
+
+
+
+ Basic user
+
+
+ Basic user can track time in auto-tracking mode only.
+
+ Basic user can start and stop timer but cannot choose specific time span.
+
+ This access level cannot see reports.
+
+
+
+ Customer
+
+
+ Customers who are allowed to login can access reports based on assigned projects.
+
+ They can only see their own projects and tracking times assigned to them.
+
+
+
+
+
+
+
+
Configuration
+
+ MACHINE-SPECIFIC CONFIGURATION:
+
_machine_config.php
+
This settings include database server settings, timezone configuration etc.
+
+
+
+ APPLICATION-WIDE CONFIGURATION:
+
_app_config.php
+
This settings include application-wide configuration settings.
+
+
All configuration options begins with explanation so read them carefully.
+
+
+
+
+
+
+
Compatibility
+
+ TimeCase is tested and found compatibile with:
+
+
+ Browsers
+
Microsoft Internet Explorer 9+
+
Firefox
+
Google Chrome
+
Safari
+
+
+
+ Server Versions
+
Minimum PHP 5.2 + Apache2
+
+
+
+ Operating Systems
+
Windows (Tested with NTFS / Windows NT 6.1 build 7600 aka Windows 7)
+
Linux (CentOS and Debian)
+
+
(Tested on x86 and x86_64 platforms)
+
+
+
+
+
diff --git a/documentation/js/cufon.js b/documentation/js/cufon.js
new file mode 100755
index 0000000..a035fc2
--- /dev/null
+++ b/documentation/js/cufon.js
@@ -0,0 +1,1498 @@
+/*!
+ * Copyright (c) 2011 Simo Kinnunen.
+ * Licensed under the MIT license.
+ *
+ * @version ${Version}
+ */
+
+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);
+ };
+
+ })(),
+
+ root: function() {
+ return document.documentElement || document.body;
+ },
+
+ strict: (function() {
+ var doctype;
+ // no doctype (doesn't always catch it though.. IE I'm looking at you)
+ if (document.compatMode == 'BackCompat') return false;
+ // WebKit, Gecko, Opera, IE9+
+ doctype = document.doctype;
+ if (doctype) {
+ return !/frameset|transitional/i.test(doctype.publicId);
+ }
+ // IE<9, firstChild is the doctype even if there's an XML declaration
+ doctype = document.firstChild;
+ if (doctype.nodeType != 8 || /^DOCTYPE.+(transitional|frameset)/i.test(doctype.data)) {
+ return false;
+ }
+ return true;
+ })()
+
+ };
+
+ 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;
+ };
+
+ },
+
+ addClass: function(el, className) {
+ var current = el.className;
+ el.className = current + (current && ' ') + className;
+ return el;
+ },
+
+ 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;
+ }),
+
+ // has no direct CSS equivalent.
+ // @see http://msdn.microsoft.com/en-us/library/system.windows.fontstretches.aspx
+ fontStretch: cached(function(value) {
+ if (typeof value == 'number') return value;
+ if (/%$/.test(value)) return parseFloat(value) / 100;
+ return {
+ 'ultra-condensed': 0.5,
+ 'extra-condensed': 0.625,
+ condensed: 0.75,
+ 'semi-condensed': 0.875,
+ 'semi-expanded': 1.125,
+ expanded: 1.25,
+ 'extra-expanded': 1.5,
+ 'ultra-expanded': 2
+ }[value] || 1;
+ }),
+
+ 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'), sheet, container, supported;
+ el.type = 'text/css';
+ el.media = media;
+ try { // this is cached anyway
+ el.appendChild(document.createTextNode('/**/'));
+ } catch (e) {}
+ container = elementsByTagName('head')[0];
+ container.insertBefore(el, container.firstChild);
+ sheet = (el.sheet || el.styleSheet);
+ supported = sheet && !sheet.disabled;
+ container.removeChild(el);
+ return supported;
+ }),
+
+ removeClass: function(el, className) {
+ var re = RegExp('(?:^|\\s+)' + className + '(?=\\s|$)', 'g');
+ el.className = el.className.replace(re, '');
+ return el;
+ },
+
+ 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;
+ },
+
+ 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;
+ }),
+
+ textTransform: (function() {
+ var map = {
+ uppercase: function(s) {
+ return s.toUpperCase();
+ },
+ lowercase: function(s) {
+ return s.toLowerCase();
+ },
+ capitalize: function(s) {
+ return s.replace(/(?:^|\s)./g, function($0) {
+ return $0.toUpperCase();
+ });
+ }
+ };
+ return function(text, style) {
+ var transform = map[style.get('textTransform')];
+ return transform ? transform(text) : text;
+ };
+ })(),
+
+ whiteSpace: (function() {
+ var ignore = {
+ inline: 1,
+ 'inline-block': 1,
+ 'run-in': 1
+ };
+ var wsStart = /^\s+/, wsEnd = /\s+$/;
+ return function(text, style, node, previousElement, simple) {
+ if (simple) return text.replace(wsStart, '').replace(wsEnd, ''); // @fixme too simple
+ if (previousElement) {
+ if (previousElement.nodeName.toLowerCase() == 'br') {
+ text = text.replace(wsStart, '');
+ }
+ }
+ if (ignore[style.get('display')]) return text;
+ if (!node.previousSibling) text = text.replace(wsStart, '');
+ if (!node.nextSibling) text = text.replace(wsEnd, '');
+ return text;
+ };
+ })()
+
+ };
+
+ CSS.ready = (function() {
+
+ // don't do anything in Safari 2 (it doesn't recognize any media type)
+ var complete = !CSS.recognizesMedia('all'), hasLayout = false;
+
+ var queue = [], perform = function() {
+ complete = true;
+ for (var fn; fn = queue.shift(); fn());
+ };
+
+ var links = elementsByTagName('link'), styles = elementsByTagName('style');
+
+ var checkTypes = {
+ '': 1,
+ 'text/css': 1
+ };
+
+ function isContainerReady(el) {
+ if (!checkTypes[el.type.toLowerCase()]) return true;
+ return el.disabled || isSheetReady(el.sheet, el.media || 'screen');
+ }
+
+ function isSheetReady(sheet, media) {
+ // 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 (!CSS.recognizesMedia(media || 'all')) return true;
+ if (!sheet || sheet.disabled) return false;
+ try {
+ var rules = sheet.cssRules, rule;
+ if (rules) {
+ // needed for Safari 3 and Chrome 1.0.
+ // in standards-conforming browsers cssRules contains @-rules.
+ // Chrome 1.0 weirdness: rules[]
+ // returns the last rule, so a for loop is the only option.
+ search: for (var i = 0, l = rules.length; rule = rules[i], i < l; ++i) {
+ switch (rule.type) {
+ case 2: // @charset
+ break;
+ case 3: // @import
+ if (!isSheetReady(rule.styleSheet, rule.media.mediaText)) return false;
+ break;
+ default:
+ // only @charset can precede @import
+ break search;
+ }
+ }
+ }
+ }
+ catch (e) {} // probably a style sheet from another domain
+ return true;
+ }
+
+ function allStylesLoaded() {
+ // Internet Explorer's style sheet model, there's no need to do anything
+ if (document.createStyleSheet) return true;
+ // standards-compliant browsers
+ var el, i;
+ for (i = 0; el = links[i]; ++i) {
+ if (el.rel.toLowerCase() == 'stylesheet' && !isContainerReady(el)) return false;
+ }
+ for (i = 0; el = styles[i]; ++i) {
+ if (!isContainerReady(el)) return false;
+ }
+ return true;
+ }
+
+ DOM.ready(function() {
+ // getComputedStyle returns null in Gecko if used in an iframe with display: none
+ if (!hasLayout) hasLayout = CSS.getStyle(document.body).isUsable();
+ if (complete || (hasLayout && 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, ligatureCache = [], wordSeparators = {
+ '\u0020': 1,
+ '\u00a0': 1,
+ '\u3000': 1
+ };
+
+ this.glyphs = (function(glyphs) {
+ var key, fallbacks = {
+ '\u2011': '\u002d',
+ '\u00ad': '\u2011'
+ };
+ for (key in fallbacks) {
+ if (!hasOwnProperty(fallbacks, key)) continue;
+ if (!glyphs[key]) glyphs[key] = glyphs[fallbacks[key]];
+ }
+ return 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;
+
+ this.spacing = function(chars, letterSpacing, wordSpacing) {
+ var glyphs = this.glyphs, glyph,
+ kerning, k,
+ jumps = [],
+ width = 0, w,
+ i = -1, j = -1, chr;
+ while (chr = chars[++i]) {
+ glyph = glyphs[chr] || this.missingGlyph;
+ if (!glyph) continue;
+ if (kerning) {
+ width -= k = kerning[chr] || 0;
+ jumps[j] -= k;
+ }
+ w = glyph.w;
+ if (isNaN(w)) w = +this.w; // may have been a String in old fonts
+ if (w > 0) {
+ w += letterSpacing;
+ if (wordSeparators[chr]) w += wordSpacing;
+ }
+ width += jumps[++j] = ~~w; // get rid of decimals
+ kerning = glyph.k;
+ }
+ jumps.total = width;
+ return jumps;
+ };
+
+ this.applyLigatures = function(text, ligatures) {
+ // find cached ligature configuration for this font
+ for (var i=0, ligatureConfig; i b;
+ }).join('|');
+
+ ligatureCache.push(ligatureConfig = {
+ ligatures: ligatures,
+ // create regular expression for matching desired ligatures that are present in the font
+ regexp: regexpText.length > 0
+ ? regexpCache[regexpText] || (regexpCache[regexpText] = new RegExp(regexpText, 'g'))
+ : null
+ });
+ }
+
+ // return applied ligatures or original text if none exist for given configuration
+ return ligatureConfig.regexp
+ ? text.replace(ligatureConfig.regexp, function(match) {
+ return ligatures[match] || match;
+ })
+ : text;
+ };
+ }
+
+ 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) {
+ try {
+ if (node.contains) return node.contains(anotherNode);
+ return node.compareDocumentPosition(anotherNode) & 16;
+ }
+ catch(e) {} // probably a XUL element such as a scrollbar
+ return false;
+ }
+
+ // mouseover/mouseout (standards) mode
+ function onOverOut(e) {
+ var related = e.relatedTarget;
+ // there might be no relatedTarget if the element is right next
+ // to the window frame
+ if (related && contains(this, related)) return;
+ trigger(this, e.type == 'mouseover');
+ }
+
+ // mouseenter/mouseleave (probably ie) mode
+ function onEnterLeave(e) {
+ if (!e) e = window.event;
+ // ie model, we don't have access to "this", but
+ // mouseenter/leave doesn't bubble so it's fine.
+ trigger(e.target || e.srcElement, e.type == 'mouseenter');
+ }
+
+ function trigger(el, hoverState) {
+ // 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() {
+ var options = sharedStorage.get(el).options;
+ if (hoverState) {
+ options = merge(options, options.hover);
+ options._mediatorMode = 1;
+ }
+ api.replace(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);
+ }
+ };
+
+ this.detach = function(el) {
+ if (el.onmouseenter === undefined) {
+ removeEvent(el, 'mouseover', onOverOut);
+ removeEvent(el, 'mouseout', onOverOut);
+ }
+ else {
+ removeEvent(el, 'mouseenter', onEnterLeave);
+ removeEvent(el, 'mouseleave', onEnterLeave);
+ }
+ };
+
+ }
+
+ function ReplaceHistory() {
+
+ var list = [], map = {};
+
+ function filter(keys) {
+ var values = [], key;
+ for (var i = 0; key = keys[i]; ++i) values[i] = list[map[key]];
+ return values;
+ }
+
+ this.add = function(key, args) {
+ map[key] = list.push(args) - 1;
+ };
+
+ this.repeat = function() {
+ var snapshot = arguments.length ? filter(arguments) : list, args;
+ for (var i = 0; args = snapshot[i++];) api.replace(args[0], args[1], true);
+ };
+
+ }
+
+ 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.extend = function(styles) {
+ for (var property in styles) {
+ if (hasOwnProperty(styles, property)) custom[property] = styles[property];
+ }
+ return this;
+ };
+
+ 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.isUsable = function() {
+ return !!style;
+ };
+
+ }
+
+ function addEvent(el, type, listener) {
+ if (el.addEventListener) {
+ el.addEventListener(type, listener, false);
+ }
+ else if (el.attachEvent) {
+ // we don't really need "this" right now, saves code
+ el.attachEvent('on' + type, listener);
+ }
+ }
+
+ function attach(el, options) {
+ if (options._mediatorMode) return el;
+ var storage = sharedStorage.get(el);
+ var oldOptions = storage.options;
+ if (oldOptions) {
+ if (oldOptions === options) return el;
+ if (oldOptions.hover) hoverHandler.detach(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) {
+ var families = CSS.quotedList(style.get('fontFamily').toLowerCase()), family;
+ for (var i = 0; family = families[i]; ++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 = {}, arg, key;
+ for (var i = 0, l = arguments.length; arg = arguments[i], i < l; ++i) {
+ for (key in arg) {
+ if (hasOwnProperty(arg, key)) merged[key] = arg[key];
+ }
+ }
+ return merged;
+ }
+
+ function process(font, text, style, options, node, el) {
+ var fragment = document.createDocumentFragment(), processed;
+ if (text === '') return fragment;
+ var separate = options.separate;
+ 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 removeEvent(el, type, listener) {
+ if (el.removeEventListener) {
+ el.removeEventListener(type, listener, false);
+ }
+ else if (el.detachEvent) {
+ el.detachEvent('on' + type, listener);
+ }
+ }
+
+ function replaceElement(el, options) {
+ var name = el.nodeName.toLowerCase();
+ if (options.ignore[name]) return;
+ if (options.ignoreClass && options.ignoreClass.test(el.className)) return;
+ if (options.onBeforeReplace) options.onBeforeReplace(el, options);
+ var replace = !options.textless[name], simple = (options.trim === 'simple');
+ var style = CSS.getStyle(attach(el, options)).extend(options);
+ // may cause issues if the element contains other elements
+ // with larger fontSize, however such cases are rare and can
+ // be fixed by using a more specific selector
+ if (parseFloat(style.get('fontSize')) === 0) return;
+ var font = getFont(el, style), node, type, next, anchor, text, lastElement;
+ var isShy = options.softHyphens, anyShy = false, pos, shy, reShy = /\u00ad/g;
+ var modifyText = options.modifyText;
+ if (!font) return;
+ for (node = el.firstChild; node; node = next) {
+ type = node.nodeType;
+ next = node.nextSibling;
+ if (replace && type == 3) {
+ if (isShy && el.nodeName.toLowerCase() != TAG_SHY) {
+ pos = node.data.indexOf('\u00ad');
+ if (pos >= 0) {
+ node.splitText(pos);
+ next = node.nextSibling;
+ next.deleteData(0, 1);
+ shy = document.createElement(TAG_SHY);
+ shy.appendChild(document.createTextNode('\u00ad'));
+ el.insertBefore(shy, next);
+ next = shy;
+ anyShy = true;
+ }
+ }
+ // Node.normalize() is broken in IE 6, 7, 8
+ if (anchor) {
+ anchor.appendData(node.data);
+ el.removeChild(node);
+ }
+ else anchor = node;
+ if (next) continue;
+ }
+ if (anchor) {
+ text = anchor.data;
+ if (!isShy) text = text.replace(reShy, '');
+ text = CSS.whiteSpace(text, style, anchor, lastElement, simple);
+ // modify text only on the first replace
+ if (modifyText) text = modifyText(text, anchor, el, options);
+ el.replaceChild(process(font, text, style, options, node, el), anchor);
+ anchor = null;
+ }
+ if (type == 1) {
+ if (node.firstChild) {
+ if (node.nodeName.toLowerCase() == 'cufon') {
+ engines[options.engine](font, null, style, options, node, el);
+ }
+ else arguments.callee(node, options);
+ }
+ lastElement = node;
+ }
+ }
+ if (isShy && anyShy) {
+ updateShy(el);
+ if (!trackingShy) addEvent(window, 'resize', updateShyOnResize);
+ trackingShy = true;
+ }
+ if (options.onAfterReplace) options.onAfterReplace(el, options);
+ }
+
+ function updateShy(context) {
+ var shys, shy, parent, glue, newGlue, next, prev, i;
+ shys = context.getElementsByTagName(TAG_SHY);
+ // unfortunately there doesn't seem to be any easy
+ // way to avoid having to loop through the shys twice.
+ for (i = 0; shy = shys[i]; ++i) {
+ shy.className = C_SHY_DISABLED;
+ glue = parent = shy.parentNode;
+ if (glue.nodeName.toLowerCase() != TAG_GLUE) {
+ newGlue = document.createElement(TAG_GLUE);
+ newGlue.appendChild(shy.previousSibling);
+ parent.insertBefore(newGlue, shy);
+ newGlue.appendChild(shy);
+ }
+ else {
+ // get rid of double glue (edge case fix)
+ glue = glue.parentNode;
+ if (glue.nodeName.toLowerCase() == TAG_GLUE) {
+ parent = glue.parentNode;
+ while (glue.firstChild) {
+ parent.insertBefore(glue.firstChild, glue);
+ }
+ parent.removeChild(glue);
+ }
+ }
+ }
+ for (i = 0; shy = shys[i]; ++i) {
+ shy.className = '';
+ glue = shy.parentNode;
+ parent = glue.parentNode;
+ next = glue.nextSibling || parent.nextSibling;
+ // make sure we're comparing same types
+ prev = (next.nodeName.toLowerCase() == TAG_GLUE) ? glue : shy.previousSibling;
+ if (prev.offsetTop >= next.offsetTop) {
+ shy.className = C_SHY_DISABLED;
+ if (prev.offsetTop < next.offsetTop) {
+ // we have an annoying edge case, double the glue
+ newGlue = document.createElement(TAG_GLUE);
+ parent.insertBefore(newGlue, glue);
+ newGlue.appendChild(glue);
+ newGlue.appendChild(next);
+ }
+ }
+ }
+ }
+
+ function updateShyOnResize() {
+ if (ignoreResize) return; // needed for IE
+ CSS.addClass(DOM.root(), C_VIEWPORT_RESIZING);
+ clearTimeout(shyTimer);
+ shyTimer = setTimeout(function() {
+ ignoreResize = true;
+ CSS.removeClass(DOM.root(), C_VIEWPORT_RESIZING);
+ updateShy(document);
+ ignoreResize = false;
+ }, 100);
+ }
+
+ var HAS_BROKEN_REGEXP = ' '.split(/\s+/).length == 0;
+ var TAG_GLUE = 'cufonglue';
+ var TAG_SHY = 'cufonshy';
+ var C_SHY_DISABLED = 'cufon-shy-disabled';
+ var C_VIEWPORT_RESIZING = 'cufon-viewport-resizing';
+
+ var regexpCache = {};
+ var sharedStorage = new Storage();
+ var hoverHandler = new HoverHandler();
+ var replaceHistory = new ReplaceHistory();
+ var initialized = false;
+ var trackingShy = false;
+ var shyTimer;
+ var ignoreResize = false;
+
+ var engines = {}, fonts = {}, defaultOptions = {
+ autoDetect: false,
+ engine: null,
+ forceHitArea: false,
+ hover: false,
+ hoverables: {
+ a: true
+ },
+ ignore: {
+ applet: 1,
+ canvas: 1,
+ col: 1,
+ colgroup: 1,
+ head: 1,
+ iframe: 1,
+ map: 1,
+ noscript: 1,
+ optgroup: 1,
+ option: 1,
+ script: 1,
+ select: 1,
+ style: 1,
+ textarea: 1,
+ title: 1,
+ pre: 1
+ },
+ ignoreClass: null,
+ modifyText: null,
+ onAfterReplace: null,
+ onBeforeReplace: null,
+ printable: true,
+ selector: (
+ window.Sizzle
+ || (window.jQuery && function(query) { return jQuery(query); }) // avoid noConflict issues
+ || (window.dojo && dojo.query)
+ || (window.glow && glow.dom && glow.dom.get)
+ || (window.Ext && Ext.query)
+ || (window.YAHOO && YAHOO.util && YAHOO.util.Selector && YAHOO.util.Selector.query)
+ || (window.$$ && function(query) { return $$(query); })
+ || (window.$ && function(query) { return $(query); })
+ || (document.querySelectorAll && function(query) { return document.querySelectorAll(query); })
+ || elementsByTagName
+ ),
+ separate: 'words', // 'none' and 'characters' are also accepted
+ softHyphens: true,
+ textless: {
+ dl: 1,
+ html: 1,
+ ol: 1,
+ table: 1,
+ tbody: 1,
+ thead: 1,
+ tfoot: 1,
+ tr: 1,
+ ul: 1
+ },
+ textShadow: 'none',
+ trim: 'advanced',
+ ligatures: {
+ 'ff': '\ufb00',
+ 'fi': '\ufb01',
+ 'fl': '\ufb02',
+ 'ffi': '\ufb03',
+ 'ffl': '\ufb04',
+ '\u017ft': '\ufb05',
+ 'st': '\ufb06'
+ }
+ };
+
+ var separators = {
+ // The first pattern may cause unicode characters above
+ // code point 255 to be removed in Safari 3.0. Luckily enough
+ // Safari 3.0 does not include non-breaking spaces in \s, so
+ // we can just use a simple alternative pattern.
+ words: /\s/.test('\u00a0') ? /[^\S\u00a0]+/ : /\s+/,
+ characters: '',
+ none: /^/
+ };
+
+ api.now = function() {
+ DOM.ready();
+ return api;
+ };
+
+ api.refresh = function() {
+ replaceHistory.repeat.apply(replaceHistory, arguments);
+ return api;
+ };
+
+ api.registerEngine = function(id, engine) {
+ if (!engine) return api;
+ engines[id] = engine;
+ return api.set('engine', id);
+ };
+
+ api.registerFont = function(data) {
+ if (!data) return api;
+ 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 (!initialized) {
+ CSS.addClass(DOM.root(), 'cufon-active cufon-loading');
+ CSS.ready(function() {
+ // fires before any replace() calls, but it doesn't really matter
+ CSS.addClass(CSS.removeClass(DOM.root(), 'cufon-loading'), 'cufon-ready');
+ });
+ initialized = true;
+ }
+ if (options.hover) options.forceHitArea = true;
+ if (options.autoDetect) delete options.fontFamily;
+ if (typeof options.ignoreClass == 'string') {
+ options.ignoreClass = new RegExp('(?:^|\\s)(?:' + options.ignoreClass.replace(/\s+/g, '|') + ')(?:\\s|$)');
+ }
+ 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);
+ }
+ else delete options.textGradient;
+ if (typeof elements == 'string') {
+ if (!ignoreHistory) replaceHistory.add(elements, arguments);
+ elements = [ elements ];
+ }
+ else if (elements.nodeType) 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);
+ }
+ });
+ return api;
+ };
+
+ api.set = function(option, value) {
+ defaultOptions[option] = value;
+ return api;
+ };
+
+ return api;
+
+})();
+
+Cufon.registerEngine('vml', (function() {
+
+ var ns = document.namespaces;
+ if (!ns) return;
+ ns.add('cvml', 'urn:schemas-microsoft-com:vml');
+ ns = null;
+
+ var check = document.createElement('cvml:shape');
+ check.style.behavior = 'url(#default#VML)';
+ if (!check.coordsize) return; // VML isn't supported
+ check = null;
+
+ var HAS_BROKEN_LINEHEIGHT = (document.documentMode || 0) < 8;
+
+ var styleSheet = document.createElement('style');
+ styleSheet.type = 'text/css';
+ styleSheet.styleSheet.cssText = (
+ 'cufoncanvas{text-indent:0;}' +
+ '@media screen{' +
+ 'cvml\\:shape,cvml\\:rect,cvml\\:fill,cvml\\:shadow{behavior:url(#default#VML);display:block;antialias:true;position:absolute;}' +
+ 'cufoncanvas{position:absolute;text-align:left;}' +
+ 'cufon{display:inline-block;position:relative;vertical-align:' +
+ (HAS_BROKEN_LINEHEIGHT
+ ? 'middle'
+ : 'text-bottom') +
+ ';}' +
+ 'cufon cufontext{position:absolute;left:-10000in;font-size:1px;text-align:left;}' +
+ 'cufonshy.cufon-shy-disabled,.cufon-viewport-resizing cufonshy{display:none;}' +
+ 'cufonglue{white-space:nowrap;display:inline-block;}' +
+ '.cufon-viewport-resizing cufonglue{white-space:normal;}' +
+ 'a cufon{cursor:pointer}' + // ignore !important here
+ '}' +
+ '@media print{' +
+ 'cufon cufoncanvas{display:none;}' +
+ '}'
+ ).replace(/;/g, '!important;');
+ document.getElementsByTagName('head')[0].appendChild(styleSheet);
+
+ function getFontSizeInPixels(el, value) {
+ return getSizeInPixels(el, /(?:em|ex|%)$|^[a-z-]+$/i.test(value) ? '1em' : value);
+ }
+
+ // Original by Dead Edwards.
+ // Combined with getFontSizeInPixels it also works with relative units.
+ function getSizeInPixels(el, value) {
+ if (!isNaN(value) || /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.replace('%', 'em');
+ var result = el.style.pixelLeft;
+ el.style.left = style;
+ el.runtimeStyle.left = runtimeStyle;
+ return result;
+ }
+
+ function getSpacingValue(el, style, size, property) {
+ var key = 'computed' + property, value = style[key];
+ if (isNaN(value)) {
+ value = style.get(property);
+ style[key] = value = (value == 'normal') ? 0 : ~~size.convertFrom(getSizeInPixels(el, value));
+ }
+ return value;
+ }
+
+ 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 = 'none';
+ 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;
+
+ var viewBox = font.viewBox;
+
+ var size = style.computedFontSize || (style.computedFontSize = new Cufon.CSS.Size(getFontSizeInPixels(el, style.get('fontSize')) + 'px', font.baseSize));
+
+ var wrapper, canvas;
+
+ if (redraw) {
+ wrapper = node;
+ canvas = node.firstChild;
+ }
+ else {
+ wrapper = document.createElement('cufon');
+ wrapper.className = 'cufon cufon-vml';
+ wrapper.alt = text;
+
+ canvas = document.createElement('cufoncanvas');
+ wrapper.appendChild(canvas);
+
+ if (options.printable) {
+ var print = document.createElement('cufontext');
+ 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 stretchFactor = roundingFactor * Cufon.CSS.fontStretch(style.get('fontStretch'));
+ 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 color = style.get('color');
+ var chars = Cufon.CSS.textTransform(options.ligatures ? font.applyLigatures(text, options.ligatures) : text, style).split('');
+
+ var jumps = font.spacing(chars,
+ getSpacingValue(el, style, size, 'letterSpacing'),
+ getSpacingValue(el, style, size, 'wordSpacing')
+ );
+
+ if (!jumps.length) return null;
+
+ var width = jumps.total;
+ var fullWidth = -minX + width + (viewBox.width - jumps[jumps.length - 1]);
+
+ var shapeWidth = size.convert(fullWidth * stretchFactor), roundedShapeWidth = Math.round(shapeWidth);
+
+ var coordSize = fullWidth + ',' + viewBox.height, coordOrigin;
+ var stretch = 'r' + coordSize + 'ns';
+
+ var fill = options.textGradient && gradientFill(options.textGradient);
+
+ var glyphs = font.glyphs, offsetX = 0;
+ var shadows = options.textShadow;
+ var i = -1, j = 0, chr;
+
+ while (chr = chars[++i]) {
+
+ var glyph = glyphs[chars[i]] || font.missingGlyph, shape;
+ if (!glyph) continue;
+
+ if (redraw) {
+ // some glyphs may be missing so we can't use i
+ shape = canvas.childNodes[j];
+ while (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 += jumps[j++];
+ }
+
+ // addresses flickering issues on :hover
+
+ var cover = shape.nextSibling, coverFill, vStyle;
+
+ if (options.forceHitArea) {
+
+ if (!cover) {
+ cover = document.createElement('cvml:rect');
+ cover.stroked = 'f';
+ cover.className = 'cufon-vml-cover';
+ coverFill = document.createElement('cvml:fill');
+ coverFill.opacity = 0;
+ cover.appendChild(coverFill);
+ canvas.appendChild(cover);
+ }
+
+ vStyle = cover.style;
+
+ vStyle.width = roundedShapeWidth;
+ vStyle.height = roundedHeight;
+
+ }
+ else if (cover) canvas.removeChild(cover);
+
+ wStyle.width = Math.max(Math.ceil(size.convert(width * stretchFactor)), 0);
+
+ if (HAS_BROKEN_LINEHEIGHT) {
+
+ var yAdjust = style.computedYAdjust;
+
+ if (yAdjust === undefined) {
+ var lineHeight = style.get('lineHeight');
+ if (lineHeight == 'normal') lineHeight = '1em';
+ else if (!isNaN(lineHeight)) lineHeight += 'em'; // no unit
+ style.computedYAdjust = yAdjust = 0.5 * (getSizeInPixels(el, lineHeight) - parseFloat(wStyle.height));
+ }
+
+ if (yAdjust) {
+ wStyle.marginTop = Math.ceil(yAdjust) + 'px';
+ wStyle.marginBottom = yAdjust + 'px';
+ }
+
+ }
+
+ return wrapper;
+
+ };
+
+})());
+
+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{text-indent:0;}' +
+ '@media screen,projection{' +
+ 'cufon{display:inline;display:inline-block;position:relative;vertical-align:middle;' +
+ (HAS_BROKEN_LINEHEIGHT
+ ? ''
+ : 'font-size:1px;line-height:1px;') +
+ '}cufon cufontext{display:-moz-inline-box;display:inline-block;width:0;height:0;text-align:left;text-indent:-10000in;}' +
+ (HAS_INLINE_BLOCK
+ ? 'cufon canvas{position:relative;}'
+ : 'cufon canvas{position:absolute;}') +
+ 'cufonshy.cufon-shy-disabled,.cufon-viewport-resizing cufonshy{display:none;}' +
+ 'cufonglue{white-space:nowrap;display:inline-block;}' +
+ '.cufon-viewport-resizing cufonglue{white-space:normal;}' +
+ '}' +
+ '@media print{' +
+ 'cufon{padding:0;}' + // Firefox 2
+ 'cufon canvas{display:none;}' +
+ '}'
+ ).replace(/;/g, '!important;')));
+ 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);
+
+ if (redraw) text = node.getAttribute('alt');
+
+ var viewBox = font.viewBox;
+
+ var size = style.getSize('fontSize', font.baseSize);
+
+ 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(options.ligatures ? font.applyLigatures(text, options.ligatures) : text, style).split('');
+
+ var jumps = font.spacing(chars,
+ ~~size.convertFrom(parseFloat(style.get('letterSpacing')) || 0),
+ ~~size.convertFrom(parseFloat(style.get('wordSpacing')) || 0)
+ );
+
+ if (!jumps.length) return null; // there's nothing to render
+
+ var width = jumps.total;
+
+ expandRight += viewBox.width - jumps[jumps.length - 1];
+ expandLeft += viewBox.minX;
+
+ var wrapper, canvas;
+
+ if (redraw) {
+ wrapper = node;
+ canvas = node.firstChild;
+ }
+ else {
+ wrapper = document.createElement('cufon');
+ wrapper.className = 'cufon cufon-canvas';
+ wrapper.setAttribute('alt', text);
+
+ canvas = document.createElement('canvas');
+ wrapper.appendChild(canvas);
+
+ if (options.printable) {
+ var print = document.createElement('cufontext');
+ 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;
+ var stretchFactor = roundingFactor * Cufon.CSS.fontStretch(style.get('fontStretch'));
+ var stretchedWidth = width * stretchFactor;
+
+ var canvasWidth = Math.ceil(size.convert(stretchedWidth + expandRight - expandLeft));
+ var canvasHeight = Math.ceil(size.convert(viewBox.height - expandTop + expandBottom));
+
+ canvas.width = canvasWidth;
+ canvas.height = canvasHeight;
+
+ // needed for WebKit and full page zoom
+ cStyle.width = canvasWidth + 'px';
+ cStyle.height = canvasHeight + 'px';
+
+ // 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.max(Math.ceil(size.convert(stretchedWidth)), 0) + '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;
+ var pixelRatio = window.devicePixelRatio || 1;
+ if (pixelRatio != 1) {
+ canvas.width = canvasWidth * pixelRatio;
+ canvas.height = canvasHeight * pixelRatio;
+ g.scale(pixelRatio, pixelRatio);
+ }
+
+ // proper horizontal scaling is performed later
+ g.scale(scale, scale * roundingFactor);
+ g.translate(-expandLeft, -expandTop);
+ g.save();
+
+ function renderText() {
+ var glyphs = font.glyphs, glyph, i = -1, j = -1, chr;
+ g.scale(stretchFactor, 1);
+ while (chr = chars[++i]) {
+ var glyph = glyphs[chars[i]] || font.missingGlyph;
+ if (!glyph) continue;
+ if (glyph.d) {
+ g.beginPath();
+ // the following moveTo is for Opera 9.2. if we don't
+ // do this, it won't forget the previous path which
+ // results in garbled text.
+ g.moveTo(0, 0);
+ if (glyph.code) interpret(glyph.code, g);
+ else glyph.code = generateFromVML('m' + glyph.d, g);
+ g.fill();
+ }
+ g.translate(jumps[++j], 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;
+ }
+ else g.fillStyle = style.get('color');
+
+ renderText();
+
+ return wrapper;
+
+ };
+
+})());
\ No newline at end of file
diff --git a/documentation/js/font.js b/documentation/js/font.js
new file mode 100755
index 0000000..fe64c9e
--- /dev/null
+++ b/documentation/js/font.js
@@ -0,0 +1,19 @@
+/*
+ * Copyright (c) 2013
+ * ==================================
+ *
+ *
+ * This is part of an item on themeforest
+ * You can check out the screenshots and purchase it on themeforest:
+ * http://rxa.li/themeforest
+ *
+ *
+ * ===========================================
+ * original filename: font.js
+ * filesize: 19182 Bytes
+ * last modified: Tue, 29 Jan 2013 14:34:13 +0100
+ *
+ */
+eval(function(d,e,a,c,b,f){b=function(a){return(a":{"d":"48,cS,-cR,-6C-2H,-2u,-cQ,6E,1K","w":2e},"?":{"d":"1y,1a,-1J,1a,2f-1m,cW,-cV-cu,ct,-1u,81,-6D,4w,-c2,0,6x,2X,6x,6a,1W,-70,1A,-3j,c4-46,24,-56,44,-56,c5-2R,1d-4,-75,1,-4G,70,-c8,-19,93,-33,93,-5H,-34,-19,-51,-58,-c6-48,0,-67,41,-63,85","w":7n},"@":{"d":"c0,-bZ-47,bT-5,18,-9,56,10,bS,0,87,-46,87,-bR,-2a,-2A,-bU,-bV,-bY-1G,0,-6r,3Z,-6r,2s,2S,6v,2J,1M,bW,0,1s,-20,2N,-c9,1d-72,1o,-3q,ca,-6I,cn-4h,0,-6q,-2i,-6q,-cl,-4E,3q,-co,cp,-cs,0,4t,3k,4t,au,1Y,-1f,2C,-1S,cq-35,0,-51,-20,-59,-3m-19,29,-50,50,-3l,3m-1l,0,-3A,-81,-3A,-cj,-6v,88,-4f,1m,-cc,0,91,16,1z,cb,-ce,cf,-ci,39,26,75,66,ch,0,95,-48,95,-cg,-44,-31,-76,-72,-d8-49,0,-89,47,-89,99","w":9W},"A":{"d":"-8,8E,-8D,8C,1r-4r,1b-30,-6G-4k,1b-32,6G-1p,8J,-8I,1b-64,-8H-2,0","w":8B,"k":{"y":18,"w":18,"v":18,"Y":74,"W":37,"V":30,"T":68}},"B":{"d":"2w,-6n,8u,-7,6J,28,1T,-8t-2,-71,-3J,-44,-1T,-8s,1a,-8v,8w,0,4r,1B,4r,6Q,72,-35,1c,-88,8x,21,1u,73,1u,6S,2D,-1C,2F,-8K,8L-8X,8W,-8Z,9a,-8,3h,31,3V,-9c,-91,-3k,-60,-3V,-65","w":3b},"C":{"d":"8V,-8U-2G,1d-5,-26,-24,-1n,-3Z,-8N-1t,0,-3j,1w,-3j,4y,96,33,2t,3j,8P,0,1l,-51,3c,-8Q,3p,1x,-2X,1E,-3B,8T-4d,0,-1j,-2y,-1j,-2Y,-3F,1i,-2z,1j,-8r,0,3K,1t,9d,1M","w":2V},"D":{"d":"2w,-5N,7U,7T,0,2a,-1c,2a,-6S,-45,-13,-1y,-3q,-2l-70,7Q,1a,-7S,7P,0,6T,1g,6T,6O,1h,-3c,4t,-8q,8k-6I,0","w":4l},"E":{"d":"57,1a,-8l,1a,2c-8m,1a,8p,1a,2l-8o,1a,8n,1a,2c-8h,0","w":5X},"F":{"d":"57,1a,-8a,1a,2c-3C,1a,8b,1a,2l-8d,1a,8R-1h,0","w":2L,"k":{"A":55,".":1e,",":1e}},"G":{"d":"a4,1b-7,-9e-49,64,-1u,90,-2h,6H-3h,0,-4q,-1G,-4q,-a6,-2H,6N,-4e,a7,-a1,0,1E,98,9U,9T-3h,1d-8,-52,-52,-89,-1U,-9V-3w,0,-4a,1H,-4a,9X,67,28,6V,4g,ab,0,1t,-26,1N,-ao-97,1a,-an,1a,am-3O,0","w":4l},"H":{"d":"57,1a,-2O,1a,as,1a,-ar,1a,1r-1h,1a,-5V-5U,1a,5V-1h,0","w":2V},"I":{"d":"57,1a,-2O,1a,1r-1h,0","w":5Q},"J":{"d":"af,-1V,aj,ai,-88,3f,-4j,9R-2N,0,-3X,-1W,-1S,-9r,1X,68,-14,1I,59,9q,0,53,-69,53,-9p,-9t,0","w":2Q},"K":{"d":"63,1a,-2O,1a,9u,9o,-9n,1b-7r,9h,3W-3f,1b-1g,-9g-61,5P,9i-1h,0","w":4l},"L":{"d":"57,1a,-2O,1a,9k,1a,2c-9x,0","w":2L,"k":{"y":37,"Y":1c,"W":55,"V":92,"T":1e}},"M":{"d":"56,1a,-9L,5K,9K,5K,-9M,1a,1r-1m,1a,-2P-2,1b-1u,2P-1G,1b-1u,-2P-2,1a,2P-1m,0","w":9P},"N":{"d":"58,1a,-9I,9H,9B,1a,-9A,1a,1r-2G,1b-4h,-5I-2,1a,5I-1m,0","w":2V},"O":{"d":"1q,-5M,1H,86,2t,1i,9G,0,1i,-37,1i,-4y,-1H,-86,-2t,-1i,-6i-56,0,-1i,37,-1i,9F,-5M,-1v,1A,-2z,1j,-6j,0,1j,1P,1j,2Y,1v,-1A,2z,-1j,a3-2F,0,-1j,-1P,-1j,-2z","w":6l},"P":{"d":"2w,-5N,9Q,-6,2r,27,2r,-ag,-99,-3c,-61,-2r,-ae,1a,-ap,9Z,0,1S,2D,1S,8e,4c,-3c,8c,-3z,7Y-3k,1a,7Z-1h,0","w":5X,"k":{"A":74,".":1e,",":1e}},"Q":{"d":"7L,-7K-99,8Y-96,-5Y-40,25,-1l,38,-3v,iu-2F,0,-1j,-1P,-1j,-2Y,-1v,1A,-2z,1j,-6j,0,1j,1P,1j,2Y,1w,-30,2R,-83,gL,-gM,-gP,gO,-26,14,-58,14,-6K,-1H,-86,-2t,-1i,-6i-56,0,-1i,37,-1i,4y,1H,86,2t,1i,gA,0,19,-1,29,-4","w":6l},"R":{"d":"2w,-6n,gB,-8,2b,33,2b,-gC,-45,-25,-71,-2o,-4o-1t,gF,1d-36,-95,15,-4s,-2X,-6g-2X,1a,6g-1h,1a,-gR,h4,0,1Y,69,1Y,h2,74,-34,1F,-1o,h5,23,97,89,1n,h9,34,3,1z,24,1R-1h,0","w":3b,"k":{"Y":37,"W":18,"V":4,"T":18}},"S":{"d":"12,-h1,3p,17,3,33,8,h0,43,64,52,1n,gU,0,91,-12,91,-gT,-36,-31,-47,-2i,-gS-2E,-32,-5Z,-63,-5Z,-gW,-2d,3s,-3Y,6s,-gX,0,1Z,60,6e,6f-1m,g3,-51,-45,-75,-91,-g5-30,0,-79,8,-79,g9,50,1o,63,6c,g7,28,3D,74,3D,6a,6b,-1y,3g,-fU,fT-80,0,-2q,-29,-2q,-6h","w":5J},"T":{"d":"3F,1a,-6m-3R,1a,-fZ,1a,2c-3R,1a,6m-1h,0","k":{"y":1c,"w":1c,"u":92,"s":1c,"r":92,"o":1c,"e":1c,"c":1c,"a":1c,"A":74,";":87,":":87,".":1c,"-":1e,",":1c}},"U":{"d":"ga,-1V,gb,gp,-1n,1Z,-go,gn-1h,0,-3B,-94,-3B,-gu,-gt,1a,gs,72,-1,1G,1o,gm,0,1U,-92,1U,-gl,-gf,0","w":2V},"V":{"d":"gc,-1r-6k,1r-3g,1b-2Z,-gg,gh,gk,gj,-gi,0","w":1L,"k":{"y":18,"u":37,"r":37,"o":55,"i":18,"e":55,"a":55,"A":55,";":50,":":50,".":1n,"-":55,",":1e}},"W":{"d":"6X,-1r-3R,1r-2G,1b-77,-3W-2,1b-75,3W-3V,1b-7f,-i2,i5,i6,i9,-i8,7G,i7,i1,-i0,0","w":5D,"k":{"u":18,"r":18,"o":37,"e":37,"a":37,"A":55,";":18,":":18,".":74,",":74}},"X":{"d":"-12,hT,-hS-hR,-hV,hW,hZ,-hY,1b-3P,ia,ib-io,1b-1z,-5O-1K,5O-3u,0","w":3b},"Y":{"d":"1p,1a,-5L-6F,-iq,it,is,-ir,1b-1q,il,5L-1h,0","w":5J,"k":{"v":55,"u":74,"q":92,"p":74,"o":92,"i":13,"e":92,"a":92,"A":74,";":80,":":92,".":1c,"-":1c,",":1c}},"Z":{"d":"24,1a,-if,-ig-3N,1a,-ii,1a,ih-hQ,hP,1a,2c-ho,0","w":hn},"[":{"d":"38,3n,-hq,1a,1R-2i,1a,hr,1a,1R-3C,0","w":2n},"\\\\":{"d":"1I,-hu,3x-1F,1b-3z,-5W,0","w":5T},"]":{"d":"5R,-7t,5S-3C,1a,-hc,1a,-hg-2i,1a,-hh,0","w":2n},"^":{"d":"65,-hk,-hj,hi,hv-1s,1b-82,-6o-82,6o-1s,0","w":2e},"hJ":{"d":"0,hI,-hH,1a,hK-6p,0","w":6p},"a":{"d":"hN,-hM,6M,39,1,83,24,hF-2r,1d-7,-14,-4,-38,-9,-hz-1w,1o,-6L,83,-6L,-6K,-2d,6J,-2a,1Z,-hD,-5,54,-13,54,-6P,-35,-33,-48,-65,-6U-60,0,-74,31,-74,fP-6V,6t,-1I,1A,-1y,6y,-e2,0,3g,1t,3g,e4,-e5-44,27,-1A,19,-1A,e7,24,26,48,62,e0,0,92,-52,83,-1e","w":2Q},"b":{"d":"54,1a,-3e,1a,dT,dS,-44,82,-70,1i,-dR,0,1p,1I,1p,7D,2x,-71,3X,-1v,dQ-98,1,-1K,-41,-2i,-3I,dU-1T,dV,-2T,-61,-16,-1k,-87,-3T-71,0,-87,67,-87,2K,61,16,1e,87,6w,0,87,-68,87,-1e","w":1L},"c":{"d":"4O,-dY-3,-42,-29,-65,-72,-7q-76,0,-87,66,-87,7H,64,11,1k,87,ea,0,73,-35,77,-eo,1d-19,3s,-3k,1h,-2J,6R-1P,0,-3H,-1O,-3H,-2s,-1G,1C,-5c,3H,-et,0,6F,66,3o,ek-3E,0","w":2Q},"d":{"d":"5l,-2T,-61,-16,-1k,-87,-3T-71,0,-87,67,-87,2K,61,16,1e,87,6w,0,87,-68,87,-ed,-1V,1r-1T,1d-2,-18,4,-43,-2,-ec-24,32,-56,70,-1F,7F-1I,0,-1v,-1x,-1v,-5p,-2W,47,-2J,1p,-eb,0,2o,29,1x,3I,-ee,0","w":1L},"e":{"d":"6u,-ei-eh,6t,60,38,3l,3l,dP,0,67,-12,83,-dO,1d-40,3a,-3y,1G,-6y,dm-3A,0,-2B,-98,-2B,-dp,-3w,2o,-3f,3f,-dr,0,3i,1N,3i,dk,-dj,3p,-48,-36,-87,-87,-dc-60,0,-93,31,-1l,87","w":2L},"f":{"d":"87,1a,-4Q-81,1a,-db,1d-3,-51,3,-1B,41,-da,-59,1H,-50,1S,-dh,dg-52,-13,-1z,-14,-2o,dt,1a,7m-1w,1a,4Q-1f,0","w":4O,"k":{"f":18}},"g":{"d":"4D,dJ,24,36,58,69,dN,-6,84,-98,76,-dL-32,47,-79,72,-3O,dE-1s,0,-4n,-1N,-4n,-dx,-3S,77,-4S,dv,-dz,0,1c,27,7w,dA,-dD,1a,dC,1U,-27,4S,-2J,eu-2W,0,-3u,-33,-4X,-fp,fo,-fn,59,18,4Y,89,fr,0,81,-70,81,-2K,-57,-27,-1l,-86,-3U-57,0,-84,48,-84,1t","w":2L},"h":{"d":"54,1a,-3e,1a,fs,-37,78,-69,1G,-fl,0,1D,57,1D,2p,3Q-1f,1a,-2s,-60,-7,-1l,-65,-3U-35,0,-79,17,-79,2u,5g-1f,0","w":1L},"i":{"d":"1q,-1V,4Z-1f,1a,-4F,fe,1a,-fd,1a,2g-1f,0","w":2m},"j":{"d":"1q,-1V,4Z-1f,1a,-4F,fk-30,3n,-fj,1,88,2,88,-7y,-fi,1a,fv,83,-4,4G,-46,fJ-61,49,-1I,32,-4E,35","w":2m},"k":{"d":"58,1a,-3e,1a,fH,-fK,1b-2R,fO,fN-4D,1b-1B,-fG-35,fF,fz-1f,0","w":2Q},"l":{"d":"58,1a,-3e,1a,1r-1f,0","w":2m},"m":{"d":"53,1a,-5C,1X,22,-4,51,2,fb,-1o,3u,-2E,5s,-eH,-5,48,-81,eL,-eO,0,1D,92,1D,2p,3Q-1f,1a,-5t,-49,-5,-93,-65,-5z-51,0,-67,38,-67,5E,5F-1f,1a,-5t,-49,-5,-93,-65,-5z-51,0,-67,38,-67,5E,5F-1f,0","w":5D},"n":{"d":"54,1a,-5C,1X,21,-4,49,2,ex,-52,89,-79,3v,-ew,0,1D,57,1D,2p,3Q-1f,1a,-2s,-60,-7,-1l,-65,-3U-35,0,-79,17,-79,2u,5g-1f,0","w":1L},"o":{"d":"eD,-2T,-55,-9,-1k,-85,-3T-76,0,-85,73,-85,2K,55,9,1e,85,eP,0,85,-74,85,-eQ,-2T,1x,-88,5c,-1M,f2-5d,0,-1M,-1e,-1M,-f6,-2D,88,-2H,1M,-f9,0,1M,1e,1M,2H","w":2L},"p":{"d":"54,3n,-f0,1X,18,-4,43,2,eU,-32,56,-70,1F,-eS,0,1v,1x,1v,5p,2W,-47,2J,-1p,eZ-60,0,-1c,-26,-1i,-eY-2,1a,7A-1f,eX,-6d,61,16,1k,87,7E,0,87,-67,87,-2K,-61,-16,-1e,-87,-7j-71,0,-87,68,-87,1e","w":1L},"q":{"d":"5l,-6d,-68,-15,-1e,-87,-7j-71,0,-87,68,-87,7H,61,16,1k,87,7E,0,87,-67,87,-eV,-3M,eR-1f,1a,-7A-2,1d-31,44,-82,70,-1i,7F-4m,0,-1p,-1I,-1p,-7D,-2x,71,-3X,1v,-f3,-1,1K,41,2i,3I,-f4,0","w":1L},"r":{"d":"54,1a,-eE,1X,26,-4,61,2,eA,-80,1B,-3a,2N,-ey,ez-20,-7,-38,-13,-78,-eM-79,0,-3J,45,-3J,eN,eI-1f,0","w":eJ,"k":{"y":-10,"w":-10,"v":-10,"q":18,"o":18,"n":-18,"e":18,"d":18,"c":18,".":92,"-":55,",":92}},"s":{"d":"2G,-fL,35,30,61,77,fI,0,67,-10,67,-7b,-30,-17,-39,-2o,-7i-3w,-25,-2b,-74,-2b,-7e,-2x,3s,-2d,7d,-fq,0,ev,31,3u,7z-dw,1d-3,-32,-33,-54,-67,-dI-28,0,-60,7,-60,7b,15,12,22,23,dd,13,1O,19,4m,do,21,1u,60,1u,dl,1x,-7w,7x,-3o,eg-1z,0,-1q,-49,-7r,-ef,0","w":7n},"t":{"d":"3G,-es,eq,1a,7m-1B,1a,ep-9,68,45,76,1B,en,e8-1I,2,-3K,48,-3K,-e3,-fQ-88,1a,-hC,1a,-hE,0","w":2n},"u":{"d":"hA,-3M,2g-1T,1d-2,-21,4,-49,-2,-7q-37,52,-89,79,-3v,hd-93,0,-1D,-57,-1D,-2p,-hl,1a,2s,60,6,1l,65,hs,0,79,-17,79,-2u,-hp,0","w":1L},"v":{"d":"ie,-2g-4c,2g-3L,1b-1y,-ik,im,in,hX,-i3,0","w":3r,"k":{".":60,",":60}},"w":{"d":"i4,-2g-2d,2g-2h,1b-75,-7a-2,1b-77,7a-2h,1b-6Z,-ge,gr,fY,fW,-fR,fS,g0,7g,-g1,0","w":g8,"k":{".":55,",":55}},"x":{"d":"-14,gY,-7C-2y,-gZ,gV,h3,-gQ,1b-2y,gD,7C-1v,1b-76,-7B-73,7B-2F,0","w":3r},"y":{"d":"35,d9,-4,1k,18,1C,-gI-41,-1x,-1K,-4q,-7I,-gK,cr,7X,7G,-9s,1b-4c,9E-16,47,-33,1B,-65,9N-73,85,-4m,60,-9f,7y,-7h","w":3r,"k":{".":92,",":92}},"z":{"d":"17,1a,-aa,-9Y-1p,1a,-9S,1a,a0-4f,a8,1a,a9-a5,0","w":3r},"{":{"d":"8f,-7t,1R-35,1d-47,0,-51,36,-51,7o,7N,75,-77,83,-1Q,7R,3,1Q,10,1Q,7s,7v,25,5,61,58,8S,1a,7c-2x,5,-1q,7,-1q,-7u,-7l,-58,-61,-65,-84,-7k,-8M,0,84,-5,84,-6Y,-8O,-2k,1C,-2k,1q,-1k","w":2n},"|":{"d":"35,8z,-8G,1a,8F-1s,0","w":4n},"}":{"d":"38,3n,-ck,cm,0,58,-36,58,-7o,-bX-1,-75,77,-83,1Q,-c3-46,-3,-1Q,-9,-1Q,-7s,-7v,-25,-5,-61,-58,-d4-28,1a,-cP,-5,1q,-7,1q,7u,7l,59,59,65,84,7k,7c-25,0,-84,5,-84,6Y,aW-12,2k,-1C,2k,-1q,1k","w":2n},"~":{"d":"7f,-av,0,bw,67,2Z,bK,0,65,-36,88,-bt,gN-29,43,-63,86,-1C,cO-80,1,-1k,-67,-2F,-7p-44,0,-68,36,-84,4o-36,-gH,-43,59,-86,1C,-86","w":2e},"\'":{"d":"72,-4p,-4A,1a,4B-1s,0","w":2C},"`":{"d":"1n,-hy-4x,-hx,e6,dZ-2I,0","w":4w},"\\ft":{"w":2j}}});',
+62,1147,"||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||0r0|0r|111|0v|129|198|176|220|142|362|128|102|208|105|107|221|256|714r|153|103|124|218|100|144|170|122|145|106|125|178|297|152|164|159|147|198r208|126|648|283|114|108|156|119|153r|262|191|104|714r0|121|0v2|233|293|||||||||||157|212|183r|165|600|198r|519r|203|154|334|133|170r|315|407|109|176r0|314|202|272v0|196|100r0||277|131|163|374|110|291|296|143|113|217|214|272|137|270|128v0|630|269|204|714r220|458r|611|185|168|260v0|185r|759|123|117|374v0|213|||||||||||112|741|116|190|714r198|274|246|211|295|136|130|101|50v|168r0|264|0v0|173|556|141|309|252|169|151|748r|150|280|166|326|332|201|188|224|292|281|70r0|120|304|234|519r0|313|139|229|356r|200|135|128v|102v|215|435r|275|236|115|||||||||||146|282|172|231|381|237|161|219|286|268|227|778|174|222|71r|335r0|341|228|243|358|105r|505|278|171|196v0|font|379r153|379r|149|238|240|139r198|134|290|284|239|0xm63|93v0|96v0|321|389|367v|397r|331v0|248|348v0|138r|118|77v0|271|127|139r|||||||||||168r|182v0|273|195|103v46|103v0|274r|103v|0r26|117r|underline|403|0r16|0r25|181r133|275v0|514v0|190v122|310|281v0|610|899|176r|114r|12r0|93v|190v|190v0|519r191|963|93r0|281r|356|78v0|390r|667|0r102|263r|357v0|531r0|213r|67r0|333|370|899r|426|206|283r|748r152|704|99v|242|||||||||||206v0|186|207|259v0|298|233r|249r|257|196v|374v217|223|796|531r|544r0|189r|500|386|287|288|0v5|601|132|129v71|266|267|553|161r|153r505|99r|255|197r0|251|102r|90v|318|193|95v0|350|213v0|192|356v0|46v0|179v0|220v|178v0|322|48v|184|94r|970|57r0|162|||||||||||319r|40v0|153v|245|163v0|194|0r72|158|55v|129v|65r0|180v0|122r|574|61r0|67v|66v|259|98r0|731r0|128r0|136v0|138|183|64r0|164r|225r|127r|273r|270v0|128v71|70v|0r84|129v0|175|74v0|20r|762|73v28|145v1|190xm245|0v237|0xm57|92v46|714r361|0v136|348r86|203v0|731r171|298r2|254r|216r|||||||||||714r552|89r285|254|285|244v0|369|43xm309|602|549v0|583|358r|714r591|371|89r382|337|89r337|365|374v197|49xm57|59v|108v77|714r391|0v188|140v64|203v101|214r0|0v122|722|0r260|714r215|0r263|1000r|1000r153|220r|255r135|0xm293|250|217r|153v23|105v|188v12|196v74|119r216|272r|61r28|297v|448r|721|0xm277|405|98r|303r0|||||||||||133v90|167|68v4|323|73v|299|278r|279r301|211r|185v0|531r316|130v0|185v133|246r271|0r196|101r0|147v56|317r198|298r205|460r220|246r2|26r410|184r55|536|201v|1019|382r208|382r2|410|748r110|479v|196xm36|196v56|0r208|714r225|232v0|420r2|714r313|420r313|144v|523|944|145v90|274v|144r475|267r|329|89v|800|208v0|222r|0v195|152r|381v183|101v|374v|587|518|367v0|352|223r258|144r|153r243|184v51|54r165|173r146|69xm57|555|76v0|68v13|197|457v0|175r|0xm378|393r|153r292|79r|714r368|184r|248r220|248r206|67v32|298v0|363v65|222v0|54v134|143v47|257v|381v151|338v|0xm340|194r|510v|436|41v124|94v43|324|236v0|312|227r197|306|245v|92v0|96v60|191v192|513v0|78xm55|78v|132r110|191v0|188v|153v0|148v77|73v0|73v49|210|162v|696r0|534r|417v67|493v0|170r537|109r|401r478|52r|101v0|360v36|603|315r315|85v|264v|59v0|136v57|114v57|305|232|403r|696r|151r164|301|301v169|179|219v1|71r36|61v|87r|155|388r94|370r199|0xm339|503r|69v59|219v8|195r170|188r318|339|240v203|153r176|0v1|463|67v40|272r190|219v|151v0|220v0|153r0|899r173|160v0|56v36|239v|226|258|270v97|145v|226v|537r|622|616|255v195|92v|179v|90r|51v|0v141|176v31|57r114|160|59r10|237v51|203v|41r95|0xm320|98v0|75v53|334v0|181v0|153r28|373v0|0v53|160v|375|393|296v|0r83|375v223|0v8|209|241|381v|317|41r36|191r183|0xm327|321r0|94v54|546v|260v204|260|43r0|77v49|146xm222|183v4|47v90|221v0|105r125|140r|86v|153v131|161r505|161r272|8r0|43r38|132r121|476r|0xm224|279|41r80|450r0|513r0|71r85|87v51|99r272|61r|0xm553|209r0|401r|76v|10v56|146v55|122r81|87v|27v32|168r208|289r0|136v|45r0|289r|318r189|317xm222|145v0|164v|519|40v67|271v0|0xm53|274v206|0xm290|55r104|261|225|180|253v0|stemv|248v61|76r0|stemh|452v0|63r198|72v|unicode|007E|glyphs|54v|8v13|0020|167v|range|56v79|37r185|101v32|275v|70v174|0v31|252r2|57r|0xm419|117r85|181r71|322v|149r|48v62|181r78|170v248|149r0|203xm370|233v|0r100|81v0|150v|164r313|128v43|270v62|57v|129xm594|252r198|183r189|183v|380|215r|117r86|210r|96r||66r0|76r189|181v|158r106|0r13|677r0|273v137|248v|247|79v93|66v37|88r0|175v|83v37|LT|Neue|400|519r190|Pro|weight|1v8|205r|444|900|177|13v|157r0|81v133|129v76|129xm598|687r|70v147|registerFont|57v24|128xm594|Cufon|0xm245|70r|270v|687r191|668|273v|275v98|57r191|Helvetica|273v0|face|family|272v195|87v49|69v65|bbox|519r198|0xm58|811|height|descent|465r198|158v56|0xm|69v93|thickness|285v0|0xm220|173r196|165v112|127v73|251v37|u00a0|position|484v0|714|1000|panose|150r|185r0|per|stretch|normal|units|38r0|188r|345r126|61v26|168v|150r224|170v4|ascent|323r|196r214|53r|248r|321r185|0r69|246v|330|127v47|0r66|268r0|321r2|183r620|321r4|321r206|66xm311|0v9|473r0|75v|61v0|90v101|870|48v0|705|438v0|665|97r0|519r206|389r220|714r222|0r117|433r226|0r115|433r2|164r0|164v104|293v|325|199|141xm363|0r77|389v0|438r220|293r0|100v|731|356v|356xm228|103xm67|196v9|142v92|71v0|246r184|176r153|0xm511|144v100|138v21|44v|176r176|485r212|250xm367|227r99|138v|61v9|96r60|103r212|714r419|82v|65v0|52v36|0r57|207v0|236v155|0r181|246r218|47v16|240r220|204v0|103r58|0v124|179v60|190xm197|103xm601|356v127|178v3|255r214|231r0|153r154|79v|311|282v|593r|153r332|0r174|365r122|331r174|356r198|215v0|685|638|274r198|899r332|593r154|102v35|234r0|731r280|365r|96r71|149r208|572r|49v|594|96r77|122r88|177v27|158r198|116r|17r280|50r500|125r0|_|50r|398r|329r0|560|0xm380|371r351|335|235|380r|0r258|247v0|334r258|0r108|0r82|194r244|194r106|447r217|0r89|714r216|316r202|878|0r87|441r2|447r2|441r194|0r86|336r255|378r|80v140|75r0|566|172r332|359r|160r|183r603|80r52|519r209|451r0|0r81|316r2|265|112v259|451r243|263r241|263r124|0r119|38v".split("|"),
+0,{}));
diff --git a/documentation/js/jquery.1.6.4.js b/documentation/js/jquery.1.6.4.js
new file mode 100755
index 0000000..4d34fca
--- /dev/null
+++ b/documentation/js/jquery.1.6.4.js
@@ -0,0 +1,4 @@
+/*! jQuery v1.6.4 http://jquery.com/ | http://jquery.org/license */
+(function(a,b){function cu(a){return f.isWindow(a)?a:a.nodeType===9?a.defaultView||a.parentWindow:!1}function cr(a){if(!cg[a]){var b=c.body,d=f("<"+a+">").appendTo(b),e=d.css("display");d.remove();if(e==="none"||e===""){ch||(ch=c.createElement("iframe"),ch.frameBorder=ch.width=ch.height=0),b.appendChild(ch);if(!ci||!ch.createElement)ci=(ch.contentWindow||ch.contentDocument).document,ci.write((c.compatMode==="CSS1Compat"?"":"")+""),ci.close();d=ci.createElement(a),ci.body.appendChild(d),e=f.css(d,"display"),b.removeChild(ch)}cg[a]=e}return cg[a]}function cq(a,b){var c={};f.each(cm.concat.apply([],cm.slice(0,b)),function(){c[this]=a});return c}function cp(){cn=b}function co(){setTimeout(cp,0);return cn=f.now()}function cf(){try{return new a.ActiveXObject("Microsoft.XMLHTTP")}catch(b){}}function ce(){try{return new a.XMLHttpRequest}catch(b){}}function b$(a,c){a.dataFilter&&(c=a.dataFilter(c,a.dataType));var d=a.dataTypes,e={},g,h,i=d.length,j,k=d[0],l,m,n,o,p;for(g=1;g0){c!=="border"&&f.each(e,function(){c||(d-=parseFloat(f.css(a,"padding"+this))||0),c==="margin"?d+=parseFloat(f.css(a,c+this))||0:d-=parseFloat(f.css(a,"border"+this+"Width"))||0});return d+"px"}d=bv(a,b,b);if(d<0||d==null)d=a.style[b]||0;d=parseFloat(d)||0,c&&f.each(e,function(){d+=parseFloat(f.css(a,"padding"+this))||0,c!=="padding"&&(d+=parseFloat(f.css(a,"border"+this+"Width"))||0),c==="margin"&&(d+=parseFloat(f.css(a,c+this))||0)});return d+"px"}function bl(a,b){b.src?f.ajax({url:b.src,async:!1,dataType:"script"}):f.globalEval((b.text||b.textContent||b.innerHTML||"").replace(bd,"/*$0*/")),b.parentNode&&b.parentNode.removeChild(b)}function bk(a){f.nodeName(a,"input")?bj(a):"getElementsByTagName"in a&&f.grep(a.getElementsByTagName("input"),bj)}function bj(a){if(a.type==="checkbox"||a.type==="radio")a.defaultChecked=a.checked}function bi(a){return"getElementsByTagName"in a?a.getElementsByTagName("*"):"querySelectorAll"in a?a.querySelectorAll("*"):[]}function bh(a,b){var c;if(b.nodeType===1){b.clearAttributes&&b.clearAttributes(),b.mergeAttributes&&b.mergeAttributes(a),c=b.nodeName.toLowerCase();if(c==="object")b.outerHTML=a.outerHTML;else if(c!=="input"||a.type!=="checkbox"&&a.type!=="radio"){if(c==="option")b.selected=a.defaultSelected;else if(c==="input"||c==="textarea")b.defaultValue=a.defaultValue}else a.checked&&(b.defaultChecked=b.checked=a.checked),b.value!==a.value&&(b.value=a.value);b.removeAttribute(f.expando)}}function bg(a,b){if(b.nodeType===1&&!!f.hasData(a)){var c=f.expando,d=f.data(a),e=f.data(b,d);if(d=d[c]){var g=d.events;e=e[c]=f.extend({},d);if(g){delete e.handle,e.events={};for(var h in g)for(var i=0,j=g[h].length;i=0===c})}function U(a){return!a||!a.parentNode||a.parentNode.nodeType===11}function M(a,b){return(a&&a!=="*"?a+".":"")+b.replace(y,"`").replace(z,"&")}function L(a){var b,c,d,e,g,h,i,j,k,l,m,n,o,p=[],q=[],r=f._data(this,"events");if(!(a.liveFired===this||!r||!r.live||a.target.disabled||a.button&&a.type==="click")){a.namespace&&(n=new RegExp("(^|\\.)"+a.namespace.split(".").join("\\.(?:.*\\.)?")+"(\\.|$)")),a.liveFired=this;var s=r.live.slice(0);for(i=0;ic)break;a.currentTarget=e.elem,a.data=e.handleObj.data,a.handleObj=e.handleObj,o=e.handleObj.origHandler.apply(e.elem,arguments);if(o===!1||a.isPropagationStopped()){c=e.level,o===!1&&(b=!1);if(a.isImmediatePropagationStopped())break}}return b}}function J(a,c,d){var e=f.extend({},d[0]);e.type=a,e.originalEvent={},e.liveFired=b,f.event.handle.call(c,e),e.isDefaultPrevented()&&d[0].preventDefault()}function D(){return!0}function C(){return!1}function m(a,c,d){var e=c+"defer",g=c+"queue",h=c+"mark",i=f.data(a,e,b,!0);i&&(d==="queue"||!f.data(a,g,b,!0))&&(d==="mark"||!f.data(a,h,b,!0))&&setTimeout(function(){!f.data(a,g,b,!0)&&!f.data(a,h,b,!0)&&(f.removeData(a,e,!0),i.resolve())},0)}function l(a){for(var b in a)if(b!=="toJSON")return!1;return!0}function k(a,c,d){if(d===b&&a.nodeType===1){var e="data-"+c.replace(j,"-$1").toLowerCase();d=a.getAttribute(e);if(typeof d=="string"){try{d=d==="true"?!0:d==="false"?!1:d==="null"?null:f.isNaN(d)?i.test(d)?f.parseJSON(d):d:parseFloat(d)}catch(g){}f.data(a,c,d)}else d=b}return d}var c=a.document,d=a.navigator,e=a.location,f=function(){function K(){if(!e.isReady){try{c.documentElement.doScroll("left")}catch(a){setTimeout(K,1);return}e.ready()}}var e=function(a,b){return new e.fn.init(a,b,h)},f=a.jQuery,g=a.$,h,i=/^(?:[^#<]*(<[\w\W]+>)[^>]*$|#([\w\-]*)$)/,j=/\S/,k=/^\s+/,l=/\s+$/,m=/\d/,n=/^<(\w+)\s*\/?>(?:<\/\1>)?$/,o=/^[\],:{}\s]*$/,p=/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g,q=/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g,r=/(?:^|:|,)(?:\s*\[)+/g,s=/(webkit)[ \/]([\w.]+)/,t=/(opera)(?:.*version)?[ \/]([\w.]+)/,u=/(msie) ([\w.]+)/,v=/(mozilla)(?:.*? rv:([\w.]+))?/,w=/-([a-z]|[0-9])/ig,x=/^-ms-/,y=function(a,b){return(b+"").toUpperCase()},z=d.userAgent,A,B,C,D=Object.prototype.toString,E=Object.prototype.hasOwnProperty,F=Array.prototype.push,G=Array.prototype.slice,H=String.prototype.trim,I=Array.prototype.indexOf,J={};e.fn=e.prototype={constructor:e,init:function(a,d,f){var g,h,j,k;if(!a)return this;if(a.nodeType){this.context=this[0]=a,this.length=1;return this}if(a==="body"&&!d&&c.body){this.context=c,this[0]=c.body,this.selector=a,this.length=1;return this}if(typeof a=="string"){a.charAt(0)!=="<"||a.charAt(a.length-1)!==">"||a.length<3?g=i.exec(a):g=[null,a,null];if(g&&(g[1]||!d)){if(g[1]){d=d instanceof e?d[0]:d,k=d?d.ownerDocument||d:c,j=n.exec(a),j?e.isPlainObject(d)?(a=[c.createElement(j[1])],e.fn.attr.call(a,d,!0)):a=[k.createElement(j[1])]:(j=e.buildFragment([g[1]],[k]),a=(j.cacheable?e.clone(j.fragment):j.fragment).childNodes);return e.merge(this,a)}h=c.getElementById(g[2]);if(h&&h.parentNode){if(h.id!==g[2])return f.find(a);this.length=1,this[0]=h}this.context=c,this.selector=a;return this}return!d||d.jquery?(d||f).find(a):this.constructor(d).find(a)}if(e.isFunction(a))return f.ready(a);a.selector!==b&&(this.selector=a.selector,this.context=a.context);return e.makeArray(a,this)},selector:"",jquery:"1.6.4",length:0,size:function(){return this.length},toArray:function(){return G.call(this,0)},get:function(a){return a==null?this.toArray():a<0?this[this.length+a]:this[a]},pushStack:function(a,b,c){var d=this.constructor();e.isArray(a)?F.apply(d,a):e.merge(d,a),d.prevObject=this,d.context=this.context,b==="find"?d.selector=this.selector+(this.selector?" ":"")+c:b&&(d.selector=this.selector+"."+b+"("+c+")");return d},each:function(a,b){return e.each(this,a,b)},ready:function(a){e.bindReady(),B.done(a);return this},eq:function(a){return a===-1?this.slice(a):this.slice(a,+a+1)},first:function(){return this.eq(0)},last:function(){return this.eq(-1)},slice:function(){return this.pushStack(G.apply(this,arguments),"slice",G.call(arguments).join(","))},map:function(a){return this.pushStack(e.map(this,function(b,c){return a.call(b,c,b)}))},end:function(){return this.prevObject||this.constructor(null)},push:F,sort:[].sort,splice:[].splice},e.fn.init.prototype=e.fn,e.extend=e.fn.extend=function(){var a,c,d,f,g,h,i=arguments[0]||{},j=1,k=arguments.length,l=!1;typeof i=="boolean"&&(l=i,i=arguments[1]||{},j=2),typeof i!="object"&&!e.isFunction(i)&&(i={}),k===j&&(i=this,--j);for(;j0)return;B.resolveWith(c,[e]),e.fn.trigger&&e(c).trigger("ready").unbind("ready")}},bindReady:function(){if(!B){B=e._Deferred();if(c.readyState==="complete")return setTimeout(e.ready,1);if(c.addEventListener)c.addEventListener("DOMContentLoaded",C,!1),a.addEventListener("load",e.ready,!1);else if(c.attachEvent){c.attachEvent("onreadystatechange",C),a.attachEvent("onload",e.ready);var b=!1;try{b=a.frameElement==null}catch(d){}c.documentElement.doScroll&&b&&K()}}},isFunction:function(a){return e.type(a)==="function"},isArray:Array.isArray||function(a){return e.type(a)==="array"},isWindow:function(a){return a&&typeof a=="object"&&"setInterval"in a},isNaN:function(a){return a==null||!m.test(a)||isNaN(a)},type:function(a){return a==null?String(a):J[D.call(a)]||"object"},isPlainObject:function(a){if(!a||e.type(a)!=="object"||a.nodeType||e.isWindow(a))return!1;try{if(a.constructor&&!E.call(a,"constructor")&&!E.call(a.constructor.prototype,"isPrototypeOf"))return!1}catch(c){return!1}var d;for(d in a);return d===b||E.call(a,d)},isEmptyObject:function(a){for(var b in a)return!1;return!0},error:function(a){throw a},parseJSON:function(b){if(typeof b!="string"||!b)return null;b=e.trim(b);if(a.JSON&&a.JSON.parse)return a.JSON.parse(b);if(o.test(b.replace(p,"@").replace(q,"]").replace(r,"")))return(new Function("return "+b))();e.error("Invalid JSON: "+b)},parseXML:function(c){var d,f;try{a.DOMParser?(f=new DOMParser,d=f.parseFromString(c,"text/xml")):(d=new ActiveXObject("Microsoft.XMLDOM"),d.async="false",d.loadXML(c))}catch(g){d=b}(!d||!d.documentElement||d.getElementsByTagName("parsererror").length)&&e.error("Invalid XML: "+c);return d},noop:function(){},globalEval:function(b){b&&j.test(b)&&(a.execScript||function(b){a.eval.call(a,b)})(b)},camelCase:function(a){return a.replace(x,"ms-").replace(w,y)},nodeName:function(a,b){return a.nodeName&&a.nodeName.toUpperCase()===b.toUpperCase()},each:function(a,c,d){var f,g=0,h=a.length,i=h===b||e.isFunction(a);if(d){if(i){for(f in a)if(c.apply(a[f],d)===!1)break}else for(;g0&&a[0]&&a[j-1]||j===0||e.isArray(a));if(k)for(;i1?h.call(arguments,0):c,--e||g.resolveWith(g,h.call(b,0))}}var b=arguments,c=0,d=b.length,e=d,g=d<=1&&a&&f.isFunction(a.promise)?a:f.Deferred();if(d>1){for(;c
a",d=a.getElementsByTagName("*"),e=a.getElementsByTagName("a")[0];if(!d||!d.length||!e)return{};g=c.createElement("select"),h=g.appendChild(c.createElement("option")),i=a.getElementsByTagName("input")[0],k={leadingWhitespace:a.firstChild.nodeType===3,tbody:!a.getElementsByTagName("tbody").length,htmlSerialize:!!a.getElementsByTagName("link").length,style:/top/.test(e.getAttribute("style")),hrefNormalized:e.getAttribute("href")==="/a",opacity:/^0.55$/.test(e.style.opacity),cssFloat:!!e.style.cssFloat,checkOn:i.value==="on",optSelected:h.selected,getSetAttribute:a.className!=="t",submitBubbles:!0,changeBubbles:!0,focusinBubbles:!1,deleteExpando:!0,noCloneEvent:!0,inlineBlockNeedsLayout:!1,shrinkWrapBlocks:!1,reliableMarginRight:!0},i.checked=!0,k.noCloneChecked=i.cloneNode(!0).checked,g.disabled=!0,k.optDisabled=!h.disabled;try{delete a.test}catch(v){k.deleteExpando=!1}!a.addEventListener&&a.attachEvent&&a.fireEvent&&(a.attachEvent("onclick",function(){k.noCloneEvent=!1}),a.cloneNode(!0).fireEvent("onclick")),i=c.createElement("input"),i.value="t",i.setAttribute("type","radio"),k.radioValue=i.value==="t",i.setAttribute("checked","checked"),a.appendChild(i),l=c.createDocumentFragment(),l.appendChild(a.firstChild),k.checkClone=l.cloneNode(!0).cloneNode(!0).lastChild.checked,a.innerHTML="",a.style.width=a.style.paddingLeft="1px",m=c.getElementsByTagName("body")[0],o=c.createElement(m?"div":"body"),p={visibility:"hidden",width:0,height:0,border:0,margin:0,background:"none"},m&&f.extend(p,{position:"absolute",left:"-1000px",top:"-1000px"});for(t in p)o.style[t]=p[t];o.appendChild(a),n=m||b,n.insertBefore(o,n.firstChild),k.appendChecked=i.checked,k.boxModel=a.offsetWidth===2,"zoom"in a.style&&(a.style.display="inline",a.style.zoom=1,k.inlineBlockNeedsLayout=a.offsetWidth===2,a.style.display="",a.innerHTML="",k.shrinkWrapBlocks=a.offsetWidth!==2),a.innerHTML="
!is",
+ '@@@SAVANT:TRIM:PRE@@@',
+ $buffer
+ );
+
+ // Pull out the textarea blocks
+ preg_match_all("!!is", $buffer, $match);
+ $textarea_blocks = $match[0];
+ $buffer = preg_replace(
+ "!!is",
+ '@@@SAVANT:TRIM:TEXTAREA@@@',
+ $buffer
+ );
+
+ // remove all leading spaces, tabs and carriage returns NOT
+ // preceeded by a php close tag.
+ $buffer = trim(preg_replace('/((?)\n)[\s]+/m', '\1', $buffer));
+
+ // replace script blocks
+ Savant3_Filter_trimwhitespace::replace(
+ "@@@SAVANT:TRIM:SCRIPT@@@",
+ $script_blocks,
+ $buffer
+ );
+
+ // replace pre blocks
+ Savant3_Filter_trimwhitespace::replace(
+ "@@@SAVANT:TRIM:PRE@@@",
+ $pre_blocks,
+ $buffer
+ );
+
+ // replace textarea blocks
+ Savant3_Filter_trimwhitespace::replace(
+ "@@@SAVANT:TRIM:TEXTAREA@@@",
+ $textarea_blocks,
+ $buffer
+ );
+
+ return $buffer;
+ }
+
+
+ /**
+ *
+ * Does a simple search-and-replace on the source text.
+ *
+ * @access protected
+ *
+ * @param string $search The string to search for.
+ *
+ * @param string $replace Replace with this text.
+ *
+ * @param string &$buffer The source text.
+ *
+ * @return string The text after search-and-replace.
+ *
+ */
+
+ protected static function replace($search, $replace, &$buffer)
+ {
+ $len = strlen($search);
+ $pos = 0;
+ $count = count($replace);
+
+ for ($i = 0; $i < $count; $i++) {
+ // does the search-string exist in the buffer?
+ $pos = strpos($buffer, $search, $pos);
+ if ($pos !== false) {
+ // replace the search-string
+ $buffer = substr_replace($buffer, $replace[$i], $pos, $len);
+ } else {
+ break;
+ }
+ }
+ }
+}
+?>
\ No newline at end of file
diff --git a/libs/savant/Savant3/resources/Savant3_Plugin_ahref.php b/libs/savant/Savant3/resources/Savant3_Plugin_ahref.php
new file mode 100755
index 0000000..6d6bb31
--- /dev/null
+++ b/libs/savant/Savant3/resources/Savant3_Plugin_ahref.php
@@ -0,0 +1,109 @@
+... tag.
+*
+* @package Savant3
+*
+* @author Paul M. Jones
+*
+* @license http://www.gnu.org/copyleft/lesser.html LGPL
+*
+* @version $Id: Savant3_Plugin_ahref.php,v 1.4 2005/08/09 12:56:14 pmjones Exp $
+*
+*/
+
+/**
+*
+* Generates an ... tag.
+*
+* @package Savant3
+*
+* @author Paul M. Jones
+*
+*/
+
+class Savant3_Plugin_ahref extends Savant3_Plugin {
+
+ /**
+ *
+ * Generate an HTML ... tag.
+ *
+ * @access public
+ *
+ * @param string|array $href A string URL for the resulting tag. May
+ * also be an array with any combination of the keys 'scheme',
+ * 'host', 'path', 'query', and 'fragment' (c.f. PHP's native
+ * parse_url() function).
+ *
+ * @param string $text The displayed text of the link.
+ *
+ * @param string|array $attr Any extra attributes for the tag.
+ *
+ * @return string The ... tag.
+ *
+ */
+
+ public function ahref($href, $text, $attr = null)
+ {
+ $html = ' $val) {
+ $key = htmlspecialchars($key);
+ $val = htmlspecialchars($val);
+ $html .= " $key=\"$val\"";
+ }
+ } elseif (! is_null($attr)) {
+ // from scalar
+ $html .= htmlspecialchars(" $attr");
+ }
+
+ // set the link text, close the tag, and return
+ $html .= '>' . $text . '';
+ return $html;
+ }
+}
+?>
\ No newline at end of file
diff --git a/libs/savant/Savant3/resources/Savant3_Plugin_date.php b/libs/savant/Savant3/resources/Savant3_Plugin_date.php
new file mode 100755
index 0000000..c35db4c
--- /dev/null
+++ b/libs/savant/Savant3/resources/Savant3_Plugin_date.php
@@ -0,0 +1,123 @@
+
+*
+* @license http://www.gnu.org/copyleft/lesser.html LGPL
+*
+* @version $Id: Savant3_Plugin_date.php,v 1.3 2005/03/07 14:40:16 pmjones Exp $
+*
+*/
+
+/**
+*
+* Plugin to generate a formatted date using strftime() conventions.
+*
+* @package Savant3
+*
+* @author Paul M. Jones
+*
+*/
+
+class Savant3_Plugin_date extends Savant3_Plugin {
+
+ /**
+ *
+ * The default strftime() format string.
+ *
+ * @access public
+ *
+ * @var array
+ *
+ */
+
+ public $default = '%c';
+
+
+ /**
+ *
+ * Custom strftime() format strings to use for dates.
+ *
+ * You can preset the format strings via Savant3::setPluginConf().
+ *
+ *
+ * $conf = array(
+ * 'custom' => array(
+ * 'mydate' => '%Y-%m-%d',
+ * 'mytime' => '%R'
+ * )
+ * );
+ *
+ * $Savant->setPluginConf('date', $conf);
+ *
+ *
+ * ... and in your template, to use a preset custom string by name:
+ *
+ *
+ * echo $this->date($value, 'mydate');
+ *
+ *
+ * @access public
+ *
+ * @var array
+ *
+ */
+
+ public $custom = array(
+ 'date' => '%Y-%m-%d',
+ 'time' => '%H:%M:%S'
+ );
+
+
+ /**
+ *
+ * Outputs a formatted date using strftime() conventions.
+ *
+ * @access public
+ *
+ * @param string $datestring Any date-time string suitable for
+ * strtotime().
+ *
+ * @param string $format The strftime() formatting string, or a named
+ * custom string key from $this->custom.
+ *
+ * @return string The formatted date string.
+ *
+ */
+
+ function date($datestring, $format = null)
+ {
+ settype($format, 'string');
+
+ if (is_null($format)) {
+ $format = $this->default;
+ }
+
+ // does the format string have a % sign in it?
+ if (strpos($format, '%') === false) {
+ // no, look for a custom format string
+ if (! empty($this->custom[$format])) {
+ // found a custom format string
+ $format = $this->custom[$format];
+ } else {
+ // did not find the custom format, revert to default
+ $format = $this->default;
+ }
+ }
+
+ // convert the date string to the specified format
+ if (trim($datestring != '')) {
+ return strftime($format, strtotime($datestring));
+ } else {
+ // no datestring, return VOID
+ return;
+ }
+ }
+
+}
+?>
\ No newline at end of file
diff --git a/libs/savant/Savant3/resources/Savant3_Plugin_htmlAttribs.php b/libs/savant/Savant3/resources/Savant3_Plugin_htmlAttribs.php
new file mode 100755
index 0000000..09257f7
--- /dev/null
+++ b/libs/savant/Savant3/resources/Savant3_Plugin_htmlAttribs.php
@@ -0,0 +1,63 @@
+
+*
+* @license http://www.gnu.org/copyleft/lesser.html LGPL
+*
+* @version $Id: Savant3_Plugin_htmlAttribs.php,v 1.3 2005/09/12 17:49:27 pmjones Exp $
+*
+*/
+
+/**
+*
+* Plugin to convert an associative array to a string of tag attributes.
+*
+* @package Savant3
+*
+* @author Paul M. Jones
+*
+*/
+
+class Savant3_Plugin_htmlAttribs extends Savant3_Plugin {
+
+ /**
+ *
+ * Converts an associative array to a string of tag attributes.
+ *
+ * @access public
+ *
+ * @param array $attribs From this array, each key-value pair is
+ * converted to an attribute name and value.
+ *
+ * @return string The XHTML for the attributes.
+ *
+ */
+
+ public function htmlAttribs($attribs)
+ {
+ $xhtml = '';
+ foreach ((array) $attribs as $key => $val) {
+
+ if ($val === null) {
+ continue;
+ }
+
+ if (is_array($val)) {
+ $val = implode(' ', $val);
+ }
+
+ $key = htmlspecialchars($key);
+ $val = htmlspecialchars($val);
+
+ $xhtml .= " $key=\"$val\"";
+ }
+ return $xhtml;
+ }
+}
+?>
\ No newline at end of file
diff --git a/libs/savant/Savant3/resources/Savant3_Plugin_image.php b/libs/savant/Savant3/resources/Savant3_Plugin_image.php
new file mode 100755
index 0000000..4b316e7
--- /dev/null
+++ b/libs/savant/Savant3/resources/Savant3_Plugin_image.php
@@ -0,0 +1,199 @@
+ tag.
+*
+* @package Savant3
+*
+* @author Paul M. Jones
+*
+* @license http://www.gnu.org/copyleft/lesser.html LGPL
+*
+* @version $Id: Savant3_Plugin_image.php,v 1.7 2005/08/12 14:34:09 pmjones Exp $
+*
+*/
+
+/**
+*
+* Plugin to generate an tag.
+*
+* Support for alpha transparency of PNG files in Microsoft IE added by
+* Edward Ritter; thanks, Edward.
+*
+* @package Savant3
+*
+* @author Paul M. Jones
+*
+*/
+
+class Savant3_Plugin_image extends Savant3_Plugin {
+
+
+ /**
+ *
+ * The document root.
+ *
+ * @access public
+ *
+ * @var string
+ *
+ */
+
+ protected $documentRoot = null;
+
+
+ /**
+ *
+ * The base directory for images within the document root.
+ *
+ * @access public
+ *
+ * @var string
+ *
+ */
+
+ protected $imageDir = null;
+
+
+ /**
+ *
+ * Outputs an tag.
+ *
+ * Microsoft IE alpha PNG support added by Edward Ritter.
+ *
+ * @access public
+ *
+ * @param string $file The path to the image on the local file system
+ * relative to $this->imageDir.
+ *
+ * @param string $alt Alternative descriptive text for the image;
+ * defaults to the filename of the image.
+ *
+ * @param int $border The border width for the image; defaults to zero.
+ *
+ * @param int $width The displayed image width in pixels; defaults to
+ * the width of the image.
+ *
+ * @param int $height The displayed image height in pixels; defaults to
+ * the height of the image.
+ *
+ * @return string An tag.
+ *
+ */
+
+ public function image($file, $alt = null, $height = null, $width = null,
+ $attr = null)
+ {
+ // is the document root set?
+ if (is_null($this->documentRoot) && isset($_SERVER['DOCUMENT_ROOT'])) {
+ // no, so set it
+ $this->documentRoot = $_SERVER['DOCUMENT_ROOT'];
+ }
+
+ // make sure there's a DIRECTORY_SEPARATOR between the docroot
+ // and the image dir
+ if (substr($this->documentRoot, -1) != DIRECTORY_SEPARATOR &&
+ substr($this->imageDir, 0, 1) != DIRECTORY_SEPARATOR) {
+ $this->documentRoot .= DIRECTORY_SEPARATOR;
+ }
+
+ // make sure there's a separator between the imageDir and the
+ // file name
+ if (substr($this->imageDir, -1) != DIRECTORY_SEPARATOR &&
+ substr($file, 0, 1) != DIRECTORY_SEPARATOR) {
+ $this->imageDir .= DIRECTORY_SEPARATOR;
+ }
+
+ // the image file type code (PNG = 3)
+ $type = null;
+
+ // get the file information
+ $info = false;
+
+ if (strpos($file, '://') === false) {
+ // no "://" in the file, so it's local
+ $file = $this->imageDir . $file;
+ $tmp = $this->documentRoot . $file;
+ $info = @getimagesize($tmp);
+ } else {
+ // don't attempt to get file info from streams, it takes
+ // way too long.
+ $info = false;
+ }
+
+ // did we find the file info?
+ if (is_array($info)) {
+
+ // capture type info regardless
+ $type = $info[2];
+
+ // capture size info where both not specified
+ if (is_null($width) && is_null($height)) {
+ $width = $info[0];
+ $height = $info[1];
+ }
+ }
+
+ // clean up
+ unset($info);
+
+ // is the file a PNG? if so, check user agent, we will need to
+ // make special allowances for Microsoft IE.
+ if (stristr($_SERVER['HTTP_USER_AGENT'], 'MSIE') && $type === 3) {
+
+ // support alpha transparency for PNG files in MSIE
+ $html = 'Savant->htmlAttribs($attr);
+
+ // done
+ $html .= '>';
+
+ } else {
+
+ // not IE, so build a normal image tag.
+ $html = 'Savant->htmlAttribs($attr);
+
+ // done
+ $html .= ' />';
+
+ }
+
+ // done!
+ return $html;
+ }
+}
+
+?>
\ No newline at end of file
diff --git a/libs/savant/Savant3/resources/Savant3_Plugin_plural.php b/libs/savant/Savant3/resources/Savant3_Plugin_plural.php
new file mode 100755
index 0000000..05cb0ff
--- /dev/null
+++ b/libs/savant/Savant3/resources/Savant3_Plugin_plural.php
@@ -0,0 +1,21 @@
+
\ No newline at end of file
diff --git a/libs/savant/Savant3/resources/Savant3_Plugin_studlycaps.php b/libs/savant/Savant3/resources/Savant3_Plugin_studlycaps.php
new file mode 100755
index 0000000..a4301a2
--- /dev/null
+++ b/libs/savant/Savant3/resources/Savant3_Plugin_studlycaps.php
@@ -0,0 +1,14 @@
+
\ No newline at end of file
diff --git a/libs/savant/tests/00_prepend.php b/libs/savant/tests/00_prepend.php
new file mode 100755
index 0000000..1bbd346
--- /dev/null
+++ b/libs/savant/tests/00_prepend.php
@@ -0,0 +1,32 @@
+\n(\s+)/m", "] => ", $output);
+ echo $output;
+}
+
+// add to the include_path
+$add = realpath(dirname(__FILE__) . '/../');
+set_include_path($add);
+
+chdir(dirname(__FILE__));
+
+// make sure we have Savant ;-)
+require_once 'Savant3.php';
+
+?>
\ No newline at end of file
diff --git a/libs/savant/tests/01_assign.phpt b/libs/savant/tests/01_assign.phpt
new file mode 100755
index 0000000..40dea3c
--- /dev/null
+++ b/libs/savant/tests/01_assign.phpt
@@ -0,0 +1,79 @@
+--TEST--
+assign variables and references
+--FILE--
+assign('nullvar', null);
+
+// assign booleans by name
+$result['bool_true'] = $tpl->assign('truevar', true);
+$result['bool_false'] = $tpl->assign('falsevar', false);
+
+// assign scalar by name
+$result['name'] = $tpl->assign('variable', 'value');
+
+// assign array
+$array = array(
+ 'arr1' => 'val1',
+ 'arr2' => 'val2'
+);
+$result['array'] = $tpl->assign($array);
+
+// assign object
+$object = new StdClass();
+$object->obj1 = 'foo';
+$object->obj2 = 'bar';
+$object->obj3 = 'baz';
+$result['object'] = $tpl->assign($object);
+
+// assign var without value
+$result['novalue'] = $tpl->assign('novalue');
+
+// assign direct
+$tpl->direct = 'direct';
+
+// assign reference
+$ref = 'ref';
+$result['ref'] = $tpl->assignRef('ref', $ref);
+
+// assign direct reference
+$ref_direct = 'ref_direct';
+$tpl->ref_direct =& $ref_direct;
+
+// get variables and generate output
+$vars = get_object_vars($tpl);
+dump($vars);
+
+// get results and generate output
+dump($result);
+
+?>
+--EXPECT--
+array(12) {
+ ["nullvar"] => NULL
+ ["truevar"] => bool(true)
+ ["falsevar"] => bool(false)
+ ["variable"] => string(5) "value"
+ ["arr1"] => string(4) "val1"
+ ["arr2"] => string(4) "val2"
+ ["obj1"] => string(3) "foo"
+ ["obj2"] => string(3) "bar"
+ ["obj3"] => string(3) "baz"
+ ["direct"] => string(6) "direct"
+ ["ref"] => &string(3) "ref"
+ ["ref_direct"] => &string(10) "ref_direct"
+}
+array(8) {
+ ["null"] => bool(true)
+ ["bool_true"] => bool(true)
+ ["bool_false"] => bool(true)
+ ["name"] => bool(true)
+ ["array"] => bool(true)
+ ["object"] => bool(true)
+ ["novalue"] => bool(false)
+ ["ref"] => bool(true)
+}
\ No newline at end of file
diff --git a/libs/savant/tests/02_output.phpt b/libs/savant/tests/02_output.phpt
new file mode 100755
index 0000000..8c6cb94
--- /dev/null
+++ b/libs/savant/tests/02_output.phpt
@@ -0,0 +1,83 @@
+--TEST--
+general output
+--FILE--
+ 'templates',
+ 'resource_path' => 'resources'
+);
+
+$tpl = new Savant3($conf);
+
+// assign variables
+$array = array(
+ 'key0' => 'val0',
+ 'key1' => 'val1',
+ 'key2' => 'val2',
+);
+
+$var1 = 'variable1';
+$var2 = 'variable2';
+$var3 = 'variable3';
+
+$ref1 = 'reference1';
+$ref2 = 'reference2';
+$ref3 = 'reference3';
+
+$tpl->assign($var1, $var1);
+$tpl->assign($var2, $var2);
+$tpl->assign($var3, $var3);
+
+$tpl->assign('set', $array);
+$tpl->assign($array);
+
+$tpl->$ref1 =& $ref1;
+$tpl->$ref2 =& $ref2;
+$tpl->$ref3 =& $ref3;
+
+// echo non-existent template
+$tpl->setTemplate('no_such_template.tpl.php');
+echo $tpl;
+
+// echo existing template
+$tpl->setTemplate('02_output.tpl.php');
+echo $tpl;
+
+// fetch existent template
+echo $tpl->fetch('02_output.tpl.php');
+
+?>
+--EXPECT--
+template error, examine fetch() result
+
+
variable1
+
variable2
+
variable3
+
val0
+
val1
+
val2
+
reference1
+
reference2
+
reference3
+
+
key0 = val0
+
key1 = val1
+
key2 = val2
+
+
variable1
+
variable2
+
variable3
+
val0
+
val1
+
val2
+
reference1
+
reference2
+
reference3
+
+
key0 = val0
+
key1 = val1
+
key2 = val2
+
\ No newline at end of file
diff --git a/libs/savant/tests/03_filters.phpt b/libs/savant/tests/03_filters.phpt
new file mode 100755
index 0000000..9b33849
--- /dev/null
+++ b/libs/savant/tests/03_filters.phpt
@@ -0,0 +1,188 @@
+--TEST--
+filters (manage and apply)
+--FILE--
+ 'templates',
+ 'resource_path' => 'resources'
+);
+
+$tpl = new Savant3($conf);
+$tpl->setTemplate('03_filters.tpl.php');
+
+// add a function filter and output
+$tpl->addFilters('htmlspecialchars');
+echo $tpl->fetch();
+
+// add a static method and output
+$tpl->addFilters(array('TestFilter', 'thisthat'));
+echo $tpl;
+
+// add an instance method and output
+$testFilter = new TestFilter();
+$tpl->addFilters(array($testFilter, 'overunder'));
+echo $tpl;
+
+// clear them all and output
+$tpl->setFilters();
+echo $tpl;
+
+// set many at once and output
+$tpl->setFilters(
+ array('Savant3_Filter_trimwhitespace', 'filter'),
+ 'htmlspecialchars',
+ array('TestFilter', 'thisthat'),
+ array($testFilter, 'overunder')
+);
+echo $tpl;
+
+// reset, add many at once, and output
+$tpl->setFilters();
+$tpl->addFilters(
+ array('Savant3_Filter_trimwhitespace', 'filter'),
+ 'htmlspecialchars',
+ array('TestFilter', 'thisthat'),
+ array($testFilter, 'overunder')
+);
+echo $tpl;
+
+?>
+--EXPECT--
+<p>This is a paragraph</p>
+
+
+
+<pre>
+Some
+
+Special
+
+Test
+</pre>
+
+
+
+<p>Change this to that</p>
+
+
+
+<p>Switch from over to under</p>
+
+
+
+----- END -----
+
+<p>This is a paragraph</p>
+
+
+
+<pre>
+Some
+
+Special
+
+Test
+</pre>
+
+
+
+<p>Change these to those</p>
+
+
+
+<p>Switch from over to under</p>
+
+
+
+----- END -----
+
+<p>This is a paragraph</p>
+
+
+
+<pre>
+Some
+
+Special
+
+Test
+</pre>
+
+
+
+<p>Change these to those</p>
+
+
+
+<p>Switch from here to there</p>
+
+
+
+----- END -----
+
+
This is a paragraph
+
+
+
+
+Some
+
+Special
+
+Test
+
+
+
+
+
Change this to that
+
+
+
+
Switch from over to under
+
+
+
+----- END -----
+
+<p>This is a paragraph</p>
+<pre>
+Some
+
+Special
+
+Test
+</pre>
+<p>Change these to those</p>
+<p>Switch from here to there</p>
+----- END -----<p>This is a paragraph</p>
+<pre>
+Some
+
+Special
+
+Test
+</pre>
+<p>Change these to those</p>
+<p>Switch from here to there</p>
+----- END -----
\ No newline at end of file
diff --git a/libs/savant/tests/04_plugins_ahref.phpt b/libs/savant/tests/04_plugins_ahref.phpt
new file mode 100755
index 0000000..84fa49a
--- /dev/null
+++ b/libs/savant/tests/04_plugins_ahref.phpt
@@ -0,0 +1,36 @@
+--TEST--
+ahref plugin
+--FILE--
+ 'templates',
+ 'resource_path' => 'resources'
+);
+
+$tpl = new Savant3($conf);
+$tpl->setTemplate('04_plugins_ahref.tpl.php');
+
+// tests
+
+$tpl->text = 'Plain old regular text';
+$tpl->href = 'http://example.com?foo=bar&baz=boo#frag';
+$tpl->attr = 'title="My Links & Stuff"';
+echo $tpl;
+
+$tpl->text = '';
+$tpl->attr = array('title' => 'My Links & Stuff');
+echo $tpl;
+
+$tpl->text = "This & that & the other thing";
+$tpl->href = parse_url('http://example.com?foo=bar&baz=boo#frag');
+echo $tpl;
+
+?>
+--EXPECT--
+Plain old regular text
+
+This & that & the other thing
diff --git a/libs/savant/tests/04_plugins_date.phpt b/libs/savant/tests/04_plugins_date.phpt
new file mode 100755
index 0000000..17540bc
--- /dev/null
+++ b/libs/savant/tests/04_plugins_date.phpt
@@ -0,0 +1,32 @@
+--TEST--
+date plugin
+--FILE--
+ 'templates',
+ 'resource_path' => 'resources'
+);
+
+$tpl = new Savant3($conf);
+$tpl->setTemplate('04_plugins_date.tpl.php');
+
+$conf = array(
+ 'default' => '%b %d, %Y'
+);
+
+$tpl->setPluginConf('date', $conf);
+
+$tpl->date = '2004-12-24';
+echo $tpl;
+
+$tpl->date = '1970-08-08';
+echo $tpl;
+
+?>
+--EXPECT--
+
This is a paragraph with a date of date($this->date) ?>
diff --git a/libs/savant/tests/templates/04_plugins_fester.tpl.php b/libs/savant/tests/templates/04_plugins_fester.tpl.php
new file mode 100755
index 0000000..fae3cc4
--- /dev/null
+++ b/libs/savant/tests/templates/04_plugins_fester.tpl.php
@@ -0,0 +1,6 @@
+fester('Gomez') ?>
+
+fester('Morticia') ?>
+
+fester('Thing') ?>
+
diff --git a/libs/savant/tests/templates/04_plugins_form.tpl.php b/libs/savant/tests/templates/04_plugins_form.tpl.php
new file mode 100755
index 0000000..729974f
--- /dev/null
+++ b/libs/savant/tests/templates/04_plugins_form.tpl.php
@@ -0,0 +1,138 @@
+
+
+ Form Plugin Test
+
+
+
+ form('set', 'class', 'Savant-Form');
+
+ echo $this->form('start');
+
+ // add a hidden value before the layout
+ echo $this->form('hidden', 'hideme', 'hidden & valued');
+
+ // start a block
+ echo $this->form('block', 'start', 'First Section', 'row');
+
+ // text field
+ // type, name, value, label, attribs, require, message
+ echo $this->form('text', 'mytext', $this->mytext, 'Enter some text here:', array('size' => '20'), true, $this->valid['mytext']);
+
+ // checkbox with default value (array(checked, not-checked))
+ echo $this->form('checkbox', 'xbox', $this->xbox, 'Check this:', array(1,0), 'style="text-align: center;"');
+
+ // single select
+ echo $this->form('select', 'picker', $this->picker, 'Pick one:', $this->opts);
+
+ // NEW BLOCK
+ echo $this->form('block', 'start', "Second Section", 'row');
+
+ // multi-select with note
+ echo $this->form('group', 'start', 'Pick many:');
+ echo $this->form('select', 'picker2', $this->picker2, 'Pick many:', $this->opts, 'multiple="multiple"');
+ echo $this->form('note', " Pick as many as you like; use the Ctrl key on Windows, or the Cmd key on Macintosh.");
+ echo $this->form('group', 'end');
+
+ // radio buttons
+ echo $this->form('radio', 'chooser', $this->chooser, 'Choose one:', $this->opts);
+
+ // NEW BLOCK
+ echo $this->form('block', 'start', null, 'row');
+
+ // text area
+ echo $this->form('textarea', 'myarea', $this->myarea, 'Long text:', array('rows'=>12,'cols'=>40));
+ echo $this->form('block', 'end');
+
+ // NEW BLOCK (clears floats)
+ echo $this->form('block', 'start', null, 'row');
+ echo $this->form('submit', 'op', 'Save');
+ echo $this->form('reset', 'op', 'Reset');
+ echo $this->form('button', '', 'Click Me!', null, array('onClick' => 'return alert("hello!")'));
+
+ // end the form
+ echo $this->form('block', 'end');
+ echo $this->form('note', '* Indicates a required field.');
+ echo $this->form('end');
+ ?>
+
+
+
+
diff --git a/libs/savant/tests/templates/04_plugins_image.tpl.php b/libs/savant/tests/templates/04_plugins_image.tpl.php
new file mode 100755
index 0000000..56e6890
--- /dev/null
+++ b/libs/savant/tests/templates/04_plugins_image.tpl.php
@@ -0,0 +1,3 @@
+image('savant.gif', 'Savant Template System') ?>
+
+image('http://phpsavant.com/etc/fester.jpg') ?>
diff --git a/libs/savant/tests/templates/06_includes.tpl.php b/libs/savant/tests/templates/06_includes.tpl.php
new file mode 100755
index 0000000..1c803d9
--- /dev/null
+++ b/libs/savant/tests/templates/06_includes.tpl.php
@@ -0,0 +1,14 @@
+template('header.tpl.php') ?>
+
variable1 ?>
+
variable2 ?>
+
variable3 ?>
+
key0 ?>
+
key1 ?>
+
key2 ?>
+
reference1 ?>
+
reference2 ?>
+
reference3 ?>
+
+set as $key => $val) echo "
$key = $val
\n" ?>
+
+template('footer.tpl.php') ?>
\ No newline at end of file
diff --git a/libs/savant/tests/templates/07_escape.tpl.php b/libs/savant/tests/templates/07_escape.tpl.php
new file mode 100755
index 0000000..0426ccd
--- /dev/null
+++ b/libs/savant/tests/templates/07_escape.tpl.php
@@ -0,0 +1,3 @@
+escape($this->text) ?>
+
+eprint($this->text) ?>
\ No newline at end of file
diff --git a/libs/savant/tests/templates/footer.tpl.php b/libs/savant/tests/templates/footer.tpl.php
new file mode 100755
index 0000000..85eac81
--- /dev/null
+++ b/libs/savant/tests/templates/footer.tpl.php
@@ -0,0 +1,3 @@
+
+
+