Get off off rails-assets

Just moving the things we currently use (and should drop in the ASAP) to vendor/assets
This commit is contained in:
Henne Vogelsang 2024-03-05 15:30:53 +01:00
parent 9fd0807a9a
commit b6560dbb34
659 changed files with 129299 additions and 67 deletions

View file

@ -0,0 +1,114 @@
var gulp = require('gulp');
var concat = require('gulp-concat');
var uglify = require('gulp-uglify');
var header = require('gulp-header');
var jshint = require('gulp-jshint');
var todo = require('gulp-todo');
var gulputil = require('gulp-util');
var replace = require('gulp-replace');
var webpack = require('webpack-stream');
var beautify = require('gulp-jsbeautifier');
var rename = require('gulp-rename');
var moment = require('moment');
var pkg = require('./package.json');
var banner =
'/*!\n\n' +
'<%= pkg.officialName %> - <%= pkg.summary %>\nVersion <%= pkg.version %>+<%= build %>\n' +
'\u00A9 <%= year %> <%= pkg.author.name %> - <%= pkg.author.url %>\n\n' +
'Site: <%= pkg.homepage %>\n' +
'Issues: <%= pkg.bugs.url %>\n' +
'License: <%= pkg.license %>\n\n' +
'*/\n';
function generateBuild() {
var date = new Date;
return Math.floor((date - (new Date(date.getFullYear(), 0, 0))) / 1000).toString(36)
}
var build = generateBuild();
gulp.task('jshint', function() {
return gulp.src([
'src/lib/*.js',
'src/lib/renderers/*.js',
'src/renderers/*.js',
'src/index.js'
])
.pipe(jshint())
.pipe(jshint.reporter('default'));
});
gulp.task('todo', function() {
return gulp.src([
'src/lib/*.js',
'src/lib/renderers/*.js',
'src/renderers/*.js',
'src/index.js'
])
.pipe(todo())
.pipe(gulp.dest('./'));
});
gulp.task('build', ['jshint'], function() {
return gulp.src('src/index.js')
.pipe(webpack({
output: {
library: 'Holder',
filename: 'holder.js',
libraryTarget: 'umd'
}
}))
.pipe(gulp.dest('./'));
});
gulp.task('bundle', ['build'], function() {
return gulp.src([
'src/lib/vendor/polyfills.js',
'holder.js',
'src/meteor/shim.js'
])
.pipe(concat('holder.js'))
.pipe(gulp.dest('./'));
});
gulp.task('minify', ['bundle'], function() {
return gulp.src('holder.js')
.pipe(uglify())
.pipe(rename('holder.min.js'))
.pipe(gulp.dest('./'));
});
gulp.task('banner', ['minify'], function() {
return gulp.src(['holder*.js'])
.pipe(replace('%version%', pkg.version))
.pipe(header(banner, {
pkg: pkg,
year: moment().format('YYYY'),
build: build
}))
.pipe(gulp.dest('./'));
});
gulp.task('beautify', function() {
return gulp.src(['src/lib/*.js'])
.pipe(beautify())
.pipe(gulp.dest('src/lib/'));
});
gulp.task('meteor', function() {
return gulp.src('src/meteor/package.js')
.pipe(replace('%version%', pkg.version))
.pipe(replace('%summary%', pkg.description))
.pipe(gulp.dest('./'));
});
gulp.task('watch', function() {
gulp.watch('src/*.js', ['default']);
});
gulp.task('default', ['todo', 'bundle', 'minify', 'banner', 'meteor'], function() {
gulputil.log('Finished build ' + build);
build = generateBuild();
});

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,12 @@
Package.describe({
summary: 'Holder uses SVG to render image placeholders entirely in browser.',
version: '2.9.6',
name: 'imsky:holder',
git: 'https://github.com/imsky/holder',
});
Package.onUse(function(api) {
api.versionsFrom('0.9.0');
api.export('Holder', 'client');
api.addFiles('holder.js', 'client');
});

View file

@ -0,0 +1,6 @@
/*
Holder.js - client side image placeholders
(c) 2012-2015 Ivan Malopinsky - http://imsky.co
*/
module.exports = require('./lib');

View file

@ -0,0 +1,202 @@
var Color = function(color, options) {
//todo: support rgba, hsla, and rrggbbaa notation
//todo: use CIELAB internally
//todo: add clamp function (with sign)
if (typeof color !== 'string') return;
this.original = color;
if (color.charAt(0) === '#') {
color = color.slice(1);
}
if (/[^a-f0-9]+/i.test(color)) return;
if (color.length === 3) {
color = color.replace(/./g, '$&$&');
}
if (color.length !== 6) return;
this.alpha = 1;
if (options && options.alpha) {
this.alpha = options.alpha;
}
this.set(parseInt(color, 16));
};
//todo: jsdocs
Color.rgb2hex = function(r, g, b) {
function format (decimal) {
var hex = (decimal | 0).toString(16);
if (decimal < 16) {
hex = '0' + hex;
}
return hex;
}
return [r, g, b].map(format).join('');
};
//todo: jsdocs
Color.hsl2rgb = function (h, s, l) {
var H = h / 60;
var C = (1 - Math.abs(2 * l - 1)) * s;
var X = C * (1 - Math.abs(parseInt(H) % 2 - 1));
var m = l - (C / 2);
var r = 0, g = 0, b = 0;
if (H >= 0 && H < 1) {
r = C;
g = X;
} else if (H >= 1 && H < 2) {
r = X;
g = C;
} else if (H >= 2 && H < 3) {
g = C;
b = X;
} else if (H >= 3 && H < 4) {
g = X;
b = C;
} else if (H >= 4 && H < 5) {
r = X;
b = C;
} else if (H >= 5 && H < 6) {
r = C;
b = X;
}
r += m;
g += m;
b += m;
r = parseInt(r * 255);
g = parseInt(g * 255);
b = parseInt(b * 255);
return [r, g, b];
};
/**
* Sets the color from a raw RGB888 integer
* @param raw RGB888 representation of color
*/
//todo: refactor into a static method
//todo: factor out individual color spaces
//todo: add HSL, CIELAB, and CIELUV
Color.prototype.set = function (val) {
this.raw = val;
var r = (this.raw & 0xFF0000) >> 16;
var g = (this.raw & 0x00FF00) >> 8;
var b = (this.raw & 0x0000FF);
// BT.709
var y = 0.2126 * r + 0.7152 * g + 0.0722 * b;
var u = -0.09991 * r - 0.33609 * g + 0.436 * b;
var v = 0.615 * r - 0.55861 * g - 0.05639 * b;
this.rgb = {
r: r,
g: g,
b: b
};
this.yuv = {
y: y,
u: u,
v: v
};
return this;
};
/**
* Lighten or darken a color
* @param multiplier Amount to lighten or darken (-1 to 1)
*/
Color.prototype.lighten = function(multiplier) {
var cm = Math.min(1, Math.max(0, Math.abs(multiplier))) * (multiplier < 0 ? -1 : 1);
var bm = (255 * cm) | 0;
var cr = Math.min(255, Math.max(0, this.rgb.r + bm));
var cg = Math.min(255, Math.max(0, this.rgb.g + bm));
var cb = Math.min(255, Math.max(0, this.rgb.b + bm));
var hex = Color.rgb2hex(cr, cg, cb);
return new Color(hex);
};
/**
* Output color in hex format
* @param addHash Add a hash character to the beginning of the output
*/
Color.prototype.toHex = function(addHash) {
return (addHash ? '#' : '') + this.raw.toString(16);
};
/**
* Returns whether or not current color is lighter than another color
* @param color Color to compare against
*/
Color.prototype.lighterThan = function(color) {
if (!(color instanceof Color)) {
color = new Color(color);
}
return this.yuv.y > color.yuv.y;
};
/**
* Returns the result of mixing current color with another color
* @param color Color to mix with
* @param multiplier How much to mix with the other color
*/
/*
Color.prototype.mix = function (color, multiplier) {
if (!(color instanceof Color)) {
color = new Color(color);
}
var r = this.rgb.r;
var g = this.rgb.g;
var b = this.rgb.b;
var a = this.alpha;
var m = typeof multiplier !== 'undefined' ? multiplier : 0.5;
//todo: write a lerp function
r = r + m * (color.rgb.r - r);
g = g + m * (color.rgb.g - g);
b = b + m * (color.rgb.b - b);
a = a + m * (color.alpha - a);
return new Color(Color.rgbToHex(r, g, b), {
'alpha': a
});
};
*/
/**
* Returns the result of blending another color on top of current color with alpha
* @param color Color to blend on top of current color, i.e. "Ca"
*/
//todo: see if .blendAlpha can be merged into .mix
Color.prototype.blendAlpha = function(color) {
if (!(color instanceof Color)) {
color = new Color(color);
}
var Ca = color;
var Cb = this;
//todo: write alpha blending function
var r = Ca.alpha * Ca.rgb.r + (1 - Ca.alpha) * Cb.rgb.r;
var g = Ca.alpha * Ca.rgb.g + (1 - Ca.alpha) * Cb.rgb.g;
var b = Ca.alpha * Ca.rgb.b + (1 - Ca.alpha) * Cb.rgb.b;
return new Color(Color.rgb2hex(r, g, b));
};
module.exports = Color;

View file

@ -0,0 +1,4 @@
module.exports = {
'version': '%version%',
'svg_ns': 'http://www.w3.org/2000/svg'
};

View file

@ -0,0 +1,62 @@
/**
* Generic new DOM element function
*
* @param tag Tag to create
* @param namespace Optional namespace value
*/
exports.newEl = function(tag, namespace) {
if (!global.document) return;
if (namespace == null) {
return global.document.createElement(tag);
} else {
return global.document.createElementNS(namespace, tag);
}
};
/**
* Generic setAttribute function
*
* @param el Reference to DOM element
* @param attrs Object with attribute keys and values
*/
exports.setAttr = function (el, attrs) {
for (var a in attrs) {
el.setAttribute(a, attrs[a]);
}
};
/**
* Creates a XML document
* @private
*/
exports.createXML = function() {
if (!global.DOMParser) return;
return new DOMParser().parseFromString('<xml />', 'application/xml');
};
/**
* Converts a value into an array of DOM nodes
*
* @param val A string, a NodeList, a Node, or an HTMLCollection
*/
exports.getNodeArray = function(val) {
var retval = null;
if (typeof(val) == 'string') {
retval = document.querySelectorAll(val);
} else if (global.NodeList && val instanceof global.NodeList) {
retval = val;
} else if (global.Node && val instanceof global.Node) {
retval = [val];
} else if (global.HTMLCollection && val instanceof global.HTMLCollection) {
retval = val;
} else if (val instanceof Array) {
retval = val;
} else if (val === null) {
retval = [];
}
retval = Array.prototype.slice.call(retval);
return retval;
};

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,64 @@
var DOM = require('../dom');
var utils = require('../utils');
module.exports = (function() {
var canvas = DOM.newEl('canvas');
var ctx = null;
return function(sceneGraph) {
if (ctx == null) {
ctx = canvas.getContext('2d');
}
var dpr = utils.canvasRatio();
var root = sceneGraph.root;
canvas.width = dpr * root.properties.width;
canvas.height = dpr * root.properties.height ;
ctx.textBaseline = 'middle';
var bg = root.children.holderBg;
var bgWidth = dpr * bg.width;
var bgHeight = dpr * bg.height;
//todo: parametrize outline width (e.g. in scene object)
var outlineWidth = 2;
var outlineOffsetWidth = outlineWidth / 2;
ctx.fillStyle = bg.properties.fill;
ctx.fillRect(0, 0, bgWidth, bgHeight);
if (bg.properties.outline) {
//todo: abstract this into a method
ctx.strokeStyle = bg.properties.outline.fill;
ctx.lineWidth = bg.properties.outline.width;
ctx.moveTo(outlineOffsetWidth, outlineOffsetWidth);
// TL, TR, BR, BL
ctx.lineTo(bgWidth - outlineOffsetWidth, outlineOffsetWidth);
ctx.lineTo(bgWidth - outlineOffsetWidth, bgHeight - outlineOffsetWidth);
ctx.lineTo(outlineOffsetWidth, bgHeight - outlineOffsetWidth);
ctx.lineTo(outlineOffsetWidth, outlineOffsetWidth);
// Diagonals
ctx.moveTo(0, outlineOffsetWidth);
ctx.lineTo(bgWidth, bgHeight - outlineOffsetWidth);
ctx.moveTo(0, bgHeight - outlineOffsetWidth);
ctx.lineTo(bgWidth, outlineOffsetWidth);
ctx.stroke();
}
var textGroup = root.children.holderTextGroup;
ctx.font = textGroup.properties.font.weight + ' ' + (dpr * textGroup.properties.font.size) + textGroup.properties.font.units + ' ' + textGroup.properties.font.family + ', monospace';
ctx.fillStyle = textGroup.properties.fill;
for (var lineKey in textGroup.children) {
var line = textGroup.children[lineKey];
for (var wordKey in line.children) {
var word = line.children[wordKey];
var x = dpr * (textGroup.x + line.x + word.x);
var y = dpr * (textGroup.y + line.y + word.y + (textGroup.properties.leading / 2));
ctx.fillText(word.properties.text, x, y);
}
}
return canvas.toDataURL('image/png');
};
})();

View file

@ -0,0 +1,122 @@
var SVG = require('../svg');
var DOM = require('../dom');
var utils = require('../utils');
var constants = require('../constants');
var SVG_NS = constants.svg_ns;
var generatorComment = '\n' +
'Created with Holder.js ' + constants.version + '.\n' +
'Learn more at http://holderjs.com\n' +
'(c) 2012-2015 Ivan Malopinsky - http://imsky.co\n';
module.exports = (function() {
//Prevent IE <9 from initializing SVG renderer
if (!global.XMLSerializer) return;
var xml = DOM.createXML();
var svg = SVG.initSVG(null, 0, 0);
var bgEl = DOM.newEl('rect', SVG_NS);
svg.appendChild(bgEl);
//todo: create a reusable pool for textNodes, resize if more words present
return function(sceneGraph, renderSettings) {
var root = sceneGraph.root;
SVG.initSVG(svg, root.properties.width, root.properties.height);
var groups = svg.querySelectorAll('g');
for (var i = 0; i < groups.length; i++) {
groups[i].parentNode.removeChild(groups[i]);
}
var holderURL = renderSettings.holderSettings.flags.holderURL;
var holderId = 'holder_' + (Number(new Date()) + 32768 + (0 | Math.random() * 32768)).toString(16);
var sceneGroupEl = DOM.newEl('g', SVG_NS);
var textGroup = root.children.holderTextGroup;
var tgProps = textGroup.properties;
var textGroupEl = DOM.newEl('g', SVG_NS);
var tpdata = textGroup.textPositionData;
var textCSSRule = '#' + holderId + ' text { ' +
utils.cssProps({
'fill': tgProps.fill,
'font-weight': tgProps.font.weight,
'font-family': tgProps.font.family + ', monospace',
'font-size': tgProps.font.size + tgProps.font.units
}) + ' } ';
var commentNode = xml.createComment('\n' + 'Source URL: ' + holderURL + generatorComment);
var holderCSS = xml.createCDATASection(textCSSRule);
var styleEl = svg.querySelector('style');
var bg = root.children.holderBg;
DOM.setAttr(sceneGroupEl, {
id: holderId
});
svg.insertBefore(commentNode, svg.firstChild);
styleEl.appendChild(holderCSS);
sceneGroupEl.appendChild(bgEl);
//todo: abstract this into a cross-browser SVG outline method
if (bg.properties.outline) {
var outlineEl = DOM.newEl('path', SVG_NS);
var outlineWidth = bg.properties.outline.width;
var outlineOffsetWidth = outlineWidth / 2;
DOM.setAttr(outlineEl, {
'd': [
'M', outlineOffsetWidth, outlineOffsetWidth,
'H', bg.width - outlineOffsetWidth,
'V', bg.height - outlineOffsetWidth,
'H', outlineOffsetWidth,
'V', 0,
'M', 0, outlineOffsetWidth,
'L', bg.width, bg.height - outlineOffsetWidth,
'M', 0, bg.height - outlineOffsetWidth,
'L', bg.width, outlineOffsetWidth
].join(' '),
'stroke-width': bg.properties.outline.width,
'stroke': bg.properties.outline.fill,
'fill': 'none'
});
sceneGroupEl.appendChild(outlineEl);
}
sceneGroupEl.appendChild(textGroupEl);
svg.appendChild(sceneGroupEl);
DOM.setAttr(bgEl, {
'width': bg.width,
'height': bg.height,
'fill': bg.properties.fill
});
textGroup.y += tpdata.boundingBox.height * 0.8;
for (var lineKey in textGroup.children) {
var line = textGroup.children[lineKey];
for (var wordKey in line.children) {
var word = line.children[wordKey];
var x = textGroup.x + line.x + word.x;
var y = textGroup.y + line.y + word.y;
var textEl = DOM.newEl('text', SVG_NS);
var textNode = document.createTextNode(null);
DOM.setAttr(textEl, {
'x': x,
'y': y
});
textNode.nodeValue = word.properties.text;
textEl.appendChild(textNode);
textGroupEl.appendChild(textEl);
}
}
//todo: factor the background check up the chain, perhaps only return reference
var svgString = SVG.svgStringToDataURI(SVG.serializeSVG(svg, renderSettings.engineSettings), renderSettings.mode === 'background');
return svgString;
};
})();

View file

@ -0,0 +1,157 @@
var shaven = require('shaven');
var SVG = require('../svg');
var constants = require('../constants');
var utils = require('../utils');
var SVG_NS = constants.svg_ns;
var templates = {
'element': function (options) {
var tag = options.tag;
var content = options.content || '';
delete options.tag;
delete options.content;
return [tag, content, options];
}
};
//todo: deprecate tag arg, infer tag from shape object
function convertShape (shape, tag) {
return templates.element({
'tag': tag,
'width': shape.width,
'height': shape.height,
'fill': shape.properties.fill
});
}
function textCss (properties) {
return utils.cssProps({
'fill': properties.fill,
'font-weight': properties.font.weight,
'font-family': properties.font.family + ', monospace',
'font-size': properties.font.size + properties.font.units
});
}
function outlinePath (bgWidth, bgHeight, outlineWidth) {
var outlineOffsetWidth = outlineWidth / 2;
return [
'M', outlineOffsetWidth, outlineOffsetWidth,
'H', bgWidth - outlineOffsetWidth,
'V', bgHeight - outlineOffsetWidth,
'H', outlineOffsetWidth,
'V', 0,
'M', 0, outlineOffsetWidth,
'L', bgWidth, bgHeight - outlineOffsetWidth,
'M', 0, bgHeight - outlineOffsetWidth,
'L', bgWidth, outlineOffsetWidth
].join(' ');
}
module.exports = function (sceneGraph, renderSettings) {
var engineSettings = renderSettings.engineSettings;
var stylesheets = engineSettings.stylesheets;
var stylesheetXml = stylesheets.map(function (stylesheet) {
return '<?xml-stylesheet rel="stylesheet" href="' + stylesheet + '"?>';
}).join('\n');
var holderId = 'holder_' + Number(new Date()).toString(16);
var root = sceneGraph.root;
var textGroup = root.children.holderTextGroup;
var css = '#' + holderId + ' text { ' + textCss(textGroup.properties) + ' } ';
// push text down to be equally vertically aligned with canvas renderer
textGroup.y += textGroup.textPositionData.boundingBox.height * 0.8;
var wordTags = [];
Object.keys(textGroup.children).forEach(function (lineKey) {
var line = textGroup.children[lineKey];
Object.keys(line.children).forEach(function (wordKey) {
var word = line.children[wordKey];
var x = textGroup.x + line.x + word.x;
var y = textGroup.y + line.y + word.y;
var wordTag = templates.element({
'tag': 'text',
'content': word.properties.text,
'x': x,
'y': y
});
wordTags.push(wordTag);
});
});
var text = templates.element({
'tag': 'g',
'content': wordTags
});
var outline = null;
if (root.children.holderBg.properties.outline) {
var outlineProperties = root.children.holderBg.properties.outline;
outline = templates.element({
'tag': 'path',
'd': outlinePath(root.children.holderBg.width, root.children.holderBg.height, outlineProperties.width),
'stroke-width': outlineProperties.width,
'stroke': outlineProperties.fill,
'fill': 'none'
});
}
var bg = convertShape(root.children.holderBg, 'rect');
var sceneContent = [];
sceneContent.push(bg);
if (outlineProperties) {
sceneContent.push(outline);
}
sceneContent.push(text);
var scene = templates.element({
'tag': 'g',
'id': holderId,
'content': sceneContent
});
var style = templates.element({
'tag': 'style',
//todo: figure out how to add CDATA directive
'content': css,
'type': 'text/css'
});
var defs = templates.element({
'tag': 'defs',
'content': style
});
var svg = templates.element({
'tag': 'svg',
'content': [defs, scene],
'width': root.properties.width,
'height': root.properties.height,
'xmlns': SVG_NS,
'viewBox': [0, 0, root.properties.width, root.properties.height].join(' '),
'preserveAspectRatio': 'none'
});
var output = shaven(svg);
if (/\&amp;(x)?#[0-9A-Fa-f]/.test(output[0])) {
output[0] = output[0].replace(/&amp;#/gm, '&#');
}
output = stylesheetXml + output[0];
var svgString = SVG.svgStringToDataURI(output, renderSettings.mode === 'background');
return svgString;
};

View file

@ -0,0 +1,105 @@
var SceneGraph = function(sceneProperties) {
var nodeCount = 1;
//todo: move merge to helpers section
function merge(parent, child) {
for (var prop in child) {
parent[prop] = child[prop];
}
return parent;
}
var SceneNode = function(name) {
nodeCount++;
this.parent = null;
this.children = {};
this.id = nodeCount;
this.name = 'n' + nodeCount;
if (typeof name !== 'undefined') {
this.name = name;
}
this.x = this.y = this.z = 0;
this.width = this.height = 0;
};
SceneNode.prototype.resize = function(width, height) {
if (width != null) {
this.width = width;
}
if (height != null) {
this.height = height;
}
};
SceneNode.prototype.moveTo = function(x, y, z) {
this.x = x != null ? x : this.x;
this.y = y != null ? y : this.y;
this.z = z != null ? z : this.z;
};
SceneNode.prototype.add = function(child) {
var name = child.name;
if (typeof this.children[name] === 'undefined') {
this.children[name] = child;
child.parent = this;
} else {
throw 'SceneGraph: child already exists: ' + name;
}
};
var RootNode = function() {
SceneNode.call(this, 'root');
this.properties = sceneProperties;
};
RootNode.prototype = new SceneNode();
var Shape = function(name, props) {
SceneNode.call(this, name);
this.properties = {
'fill': '#000000'
};
if (typeof props !== 'undefined') {
merge(this.properties, props);
} else if (typeof name !== 'undefined' && typeof name !== 'string') {
throw 'SceneGraph: invalid node name';
}
};
Shape.prototype = new SceneNode();
var Group = function() {
Shape.apply(this, arguments);
this.type = 'group';
};
Group.prototype = new Shape();
var Rect = function() {
Shape.apply(this, arguments);
this.type = 'rect';
};
Rect.prototype = new Shape();
var Text = function(text) {
Shape.call(this);
this.type = 'text';
this.properties.text = text;
};
Text.prototype = new Shape();
var root = new RootNode();
this.Shape = {
'Rect': Rect,
'Text': Text,
'Group': Group
};
this.root = root;
return this;
};
module.exports = SceneGraph;

View file

@ -0,0 +1,109 @@
var DOM = require('./dom');
var SVG_NS = 'http://www.w3.org/2000/svg';
var NODE_TYPE_COMMENT = 8;
/**
* Generic SVG element creation function
*
* @param svg SVG context, set to null if new
* @param width Document width
* @param height Document height
*/
exports.initSVG = function(svg, width, height) {
var defs, style, initialize = false;
if (svg && svg.querySelector) {
style = svg.querySelector('style');
if (style === null) {
initialize = true;
}
} else {
svg = DOM.newEl('svg', SVG_NS);
initialize = true;
}
if (initialize) {
defs = DOM.newEl('defs', SVG_NS);
style = DOM.newEl('style', SVG_NS);
DOM.setAttr(style, {
'type': 'text/css'
});
defs.appendChild(style);
svg.appendChild(defs);
}
//IE throws an exception if this is set and Chrome requires it to be set
if (svg.webkitMatchesSelector) {
svg.setAttribute('xmlns', SVG_NS);
}
//Remove comment nodes
for (var i = 0; i < svg.childNodes.length; i++) {
if (svg.childNodes[i].nodeType === NODE_TYPE_COMMENT) {
svg.removeChild(svg.childNodes[i]);
}
}
//Remove CSS
while (style.childNodes.length) {
style.removeChild(style.childNodes[0]);
}
DOM.setAttr(svg, {
'width': width,
'height': height,
'viewBox': '0 0 ' + width + ' ' + height,
'preserveAspectRatio': 'none'
});
return svg;
};
/**
* Converts serialized SVG to a string suitable for data URI use
* @param svgString Serialized SVG string
* @param [base64] Use base64 encoding for data URI
*/
exports.svgStringToDataURI = function() {
var rawPrefix = 'data:image/svg+xml;charset=UTF-8,';
var base64Prefix = 'data:image/svg+xml;charset=UTF-8;base64,';
return function(svgString, base64) {
if (base64) {
return base64Prefix + btoa(global.unescape(encodeURIComponent(svgString)));
} else {
return rawPrefix + encodeURIComponent(svgString);
}
};
}();
/**
* Returns serialized SVG with XML processing instructions
*
* @param svg SVG context
* @param stylesheets CSS stylesheets to include
*/
exports.serializeSVG = function(svg, engineSettings) {
if (!global.XMLSerializer) return;
var serializer = new XMLSerializer();
var svgCSS = '';
var stylesheets = engineSettings.stylesheets;
//External stylesheets: Processing Instruction method
if (engineSettings.svgXMLStylesheet) {
var xml = DOM.createXML();
//Add <?xml-stylesheet ?> directives
for (var i = stylesheets.length - 1; i >= 0; i--) {
var csspi = xml.createProcessingInstruction('xml-stylesheet', 'href="' + stylesheets[i] + '" rel="stylesheet"');
xml.insertBefore(csspi, xml.firstChild);
}
xml.removeChild(xml.documentElement);
svgCSS = serializer.serializeToString(xml);
}
var svgText = serializer.serializeToString(svg);
svgText = svgText.replace(/\&amp;(\#[0-9]{2,}\;)/g, '&$1');
return svgCSS + svgText;
};

View file

@ -0,0 +1,173 @@
/**
* Shallow object clone and merge
*
* @param a Object A
* @param b Object B
* @returns {Object} New object with all of A's properties, and all of B's properties, overwriting A's properties
*/
exports.extend = function(a, b) {
var c = {};
for (var x in a) {
if (a.hasOwnProperty(x)) {
c[x] = a[x];
}
}
if (b != null) {
for (var y in b) {
if (b.hasOwnProperty(y)) {
c[y] = b[y];
}
}
}
return c;
};
/**
* Takes a k/v list of CSS properties and returns a rule
*
* @param props CSS properties object
*/
exports.cssProps = function(props) {
var ret = [];
for (var p in props) {
if (props.hasOwnProperty(p)) {
ret.push(p + ':' + props[p]);
}
}
return ret.join(';');
};
/**
* Encodes HTML entities in a string
*
* @param str Input string
*/
exports.encodeHtmlEntity = function(str) {
var buf = [];
var charCode = 0;
for (var i = str.length - 1; i >= 0; i--) {
charCode = str.charCodeAt(i);
if (charCode > 128) {
buf.unshift(['&#', charCode, ';'].join(''));
} else {
buf.unshift(str[i]);
}
}
return buf.join('');
};
/**
* Checks if an image exists
*
* @param src URL of image
* @param callback Callback to call once image status has been found
*/
exports.imageExists = function(src, callback) {
var image = new Image();
image.onerror = function() {
callback.call(this, false);
};
image.onload = function() {
callback.call(this, true);
};
image.src = src;
};
/**
* Decodes HTML entities in a string
*
* @param str Input string
*/
exports.decodeHtmlEntity = function(str) {
return str.replace(/&#(\d+);/g, function(match, dec) {
return String.fromCharCode(dec);
});
};
/**
* Returns an element's dimensions if it's visible, `false` otherwise.
*
* @param el DOM element
*/
exports.dimensionCheck = function(el) {
var dimensions = {
height: el.clientHeight,
width: el.clientWidth
};
if (dimensions.height && dimensions.width) {
return dimensions;
} else {
return false;
}
};
/**
* Returns true if value is truthy or if it is "semantically truthy"
* @param val
*/
exports.truthy = function(val) {
if (typeof val === 'string') {
return val === 'true' || val === 'yes' || val === '1' || val === 'on' || val === '✓';
}
return !!val;
};
/**
* Parses input into a well-formed CSS color
* @param val
*/
exports.parseColor = function(val) {
var hexre = /(^(?:#?)[0-9a-f]{6}$)|(^(?:#?)[0-9a-f]{3}$)/i;
var rgbre = /^rgb\((\d{1,3})\s*,\s*(\d{1,3})\s*,\s*(\d{1,3})\s*\)$/;
var rgbare = /^rgba\((\d{1,3})\s*,\s*(\d{1,3})\s*,\s*(\d{1,3})\s*,\s*(0\.\d{1,}|1)\)$/;
var match = val.match(hexre);
var retval;
if (match !== null) {
retval = match[1] || match[2];
if (retval[0] !== '#') {
return '#' + retval;
} else {
return retval;
}
}
match = val.match(rgbre);
if (match !== null) {
retval = 'rgb(' + match.slice(1).join(',') + ')';
return retval;
}
match = val.match(rgbare);
if (match !== null) {
retval = 'rgba(' + match.slice(1).join(',') + ')';
return retval;
}
return null;
};
/**
* Provides the correct scaling ratio for canvas drawing operations on HiDPI screens (e.g. Retina displays)
*/
exports.canvasRatio = function () {
var devicePixelRatio = 1;
var backingStoreRatio = 1;
if (global.document) {
var canvas = global.document.createElement('canvas');
if (canvas.getContext) {
var ctx = canvas.getContext('2d');
devicePixelRatio = global.devicePixelRatio || 1;
backingStoreRatio = ctx.webkitBackingStorePixelRatio || ctx.mozBackingStorePixelRatio || ctx.msBackingStorePixelRatio || ctx.oBackingStorePixelRatio || ctx.backingStorePixelRatio || 1;
}
}
return devicePixelRatio / backingStoreRatio;
};

View file

@ -0,0 +1,155 @@
/*!
* onDomReady.js 1.4.0 (c) 2013 Tubal Martin - MIT license
*
* Specially modified to work with Holder.js
*/
function _onDomReady(win) {
//Lazy loading fix for Firefox < 3.6
//http://webreflection.blogspot.com/2009/11/195-chars-to-help-lazy-loading.html
if (document.readyState == null && document.addEventListener) {
document.addEventListener("DOMContentLoaded", function DOMContentLoaded() {
document.removeEventListener("DOMContentLoaded", DOMContentLoaded, false);
document.readyState = "complete";
}, false);
document.readyState = "loading";
}
var doc = win.document,
docElem = doc.documentElement,
LOAD = "load",
FALSE = false,
ONLOAD = "on"+LOAD,
COMPLETE = "complete",
READYSTATE = "readyState",
ATTACHEVENT = "attachEvent",
DETACHEVENT = "detachEvent",
ADDEVENTLISTENER = "addEventListener",
DOMCONTENTLOADED = "DOMContentLoaded",
ONREADYSTATECHANGE = "onreadystatechange",
REMOVEEVENTLISTENER = "removeEventListener",
// W3C Event model
w3c = ADDEVENTLISTENER in doc,
_top = FALSE,
// isReady: Is the DOM ready to be used? Set to true once it occurs.
isReady = FALSE,
// Callbacks pending execution until DOM is ready
callbacks = [];
// Handle when the DOM is ready
function ready( fn ) {
if ( !isReady ) {
// Make sure body exists, at least, in case IE gets a little overzealous (ticket #5443).
if ( !doc.body ) {
return defer( ready );
}
// Remember that the DOM is ready
isReady = true;
// Execute all callbacks
while ( fn = callbacks.shift() ) {
defer( fn );
}
}
}
// The ready event handler
function completed( event ) {
// readyState === "complete" is good enough for us to call the dom ready in oldIE
if ( w3c || event.type === LOAD || doc[READYSTATE] === COMPLETE ) {
detach();
ready();
}
}
// Clean-up method for dom ready events
function detach() {
if ( w3c ) {
doc[REMOVEEVENTLISTENER]( DOMCONTENTLOADED, completed, FALSE );
win[REMOVEEVENTLISTENER]( LOAD, completed, FALSE );
} else {
doc[DETACHEVENT]( ONREADYSTATECHANGE, completed );
win[DETACHEVENT]( ONLOAD, completed );
}
}
// Defers a function, scheduling it to run after the current call stack has cleared.
function defer( fn, wait ) {
// Allow 0 to be passed
setTimeout( fn, +wait >= 0 ? wait : 1 );
}
// Attach the listeners:
// Catch cases where onDomReady is called after the browser event has already occurred.
// we once tried to use readyState "interactive" here, but it caused issues like the one
// discovered by ChrisS here: http://bugs.jquery.com/ticket/12282#comment:15
if ( doc[READYSTATE] === COMPLETE ) {
// Handle it asynchronously to allow scripts the opportunity to delay ready
defer( ready );
// Standards-based browsers support DOMContentLoaded
} else if ( w3c ) {
// Use the handy event callback
doc[ADDEVENTLISTENER]( DOMCONTENTLOADED, completed, FALSE );
// A fallback to window.onload, that will always work
win[ADDEVENTLISTENER]( LOAD, completed, FALSE );
// If IE event model is used
} else {
// Ensure firing before onload, maybe late but safe also for iframes
doc[ATTACHEVENT]( ONREADYSTATECHANGE, completed );
// A fallback to window.onload, that will always work
win[ATTACHEVENT]( ONLOAD, completed );
// If IE and not a frame
// continually check to see if the document is ready
try {
_top = win.frameElement == null && docElem;
} catch(e) {}
if ( _top && _top.doScroll ) {
(function doScrollCheck() {
if ( !isReady ) {
try {
// Use the trick by Diego Perini
// http://javascript.nwbox.com/IEContentLoaded/
_top.doScroll("left");
} catch(e) {
return defer( doScrollCheck, 50 );
}
// detach all dom ready events
detach();
// and execute any waiting functions
ready();
}
})();
}
}
function onDomReady( fn ) {
// If DOM is ready, execute the function (async), otherwise wait
isReady ? defer( fn ) : callbacks.push( fn );
}
// Add version
onDomReady.version = "1.4.0";
// Add method to check if DOM is ready
onDomReady.isReady = function(){
return isReady;
};
return onDomReady;
}
module.exports = typeof window !== "undefined" && _onDomReady(window);

View file

@ -0,0 +1,234 @@
(function (window) {
if (!window.document) return;
var document = window.document;
//https://github.com/inexorabletash/polyfill/blob/master/web.js
if (!document.querySelectorAll) {
document.querySelectorAll = function (selectors) {
var style = document.createElement('style'), elements = [], element;
document.documentElement.firstChild.appendChild(style);
document._qsa = [];
style.styleSheet.cssText = selectors + '{x-qsa:expression(document._qsa && document._qsa.push(this))}';
window.scrollBy(0, 0);
style.parentNode.removeChild(style);
while (document._qsa.length) {
element = document._qsa.shift();
element.style.removeAttribute('x-qsa');
elements.push(element);
}
document._qsa = null;
return elements;
};
}
if (!document.querySelector) {
document.querySelector = function (selectors) {
var elements = document.querySelectorAll(selectors);
return (elements.length) ? elements[0] : null;
};
}
if (!document.getElementsByClassName) {
document.getElementsByClassName = function (classNames) {
classNames = String(classNames).replace(/^|\s+/g, '.');
return document.querySelectorAll(classNames);
};
}
//https://github.com/inexorabletash/polyfill
// ES5 15.2.3.14 Object.keys ( O )
// https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Object/keys
if (!Object.keys) {
Object.keys = function (o) {
if (o !== Object(o)) { throw TypeError('Object.keys called on non-object'); }
var ret = [], p;
for (p in o) {
if (Object.prototype.hasOwnProperty.call(o, p)) {
ret.push(p);
}
}
return ret;
};
}
// ES5 15.4.4.18 Array.prototype.forEach ( callbackfn [ , thisArg ] )
// From https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Array/forEach
if (!Array.prototype.forEach) {
Array.prototype.forEach = function (fun /*, thisp */) {
if (this === void 0 || this === null) { throw TypeError(); }
var t = Object(this);
var len = t.length >>> 0;
if (typeof fun !== "function") { throw TypeError(); }
var thisp = arguments[1], i;
for (i = 0; i < len; i++) {
if (i in t) {
fun.call(thisp, t[i], i, t);
}
}
};
}
//https://github.com/inexorabletash/polyfill/blob/master/web.js
(function (global) {
var B64_ALPHABET = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=';
global.atob = global.atob || function (input) {
input = String(input);
var position = 0,
output = [],
buffer = 0, bits = 0, n;
input = input.replace(/\s/g, '');
if ((input.length % 4) === 0) { input = input.replace(/=+$/, ''); }
if ((input.length % 4) === 1) { throw Error('InvalidCharacterError'); }
if (/[^+/0-9A-Za-z]/.test(input)) { throw Error('InvalidCharacterError'); }
while (position < input.length) {
n = B64_ALPHABET.indexOf(input.charAt(position));
buffer = (buffer << 6) | n;
bits += 6;
if (bits === 24) {
output.push(String.fromCharCode((buffer >> 16) & 0xFF));
output.push(String.fromCharCode((buffer >> 8) & 0xFF));
output.push(String.fromCharCode(buffer & 0xFF));
bits = 0;
buffer = 0;
}
position += 1;
}
if (bits === 12) {
buffer = buffer >> 4;
output.push(String.fromCharCode(buffer & 0xFF));
} else if (bits === 18) {
buffer = buffer >> 2;
output.push(String.fromCharCode((buffer >> 8) & 0xFF));
output.push(String.fromCharCode(buffer & 0xFF));
}
return output.join('');
};
global.btoa = global.btoa || function (input) {
input = String(input);
var position = 0,
out = [],
o1, o2, o3,
e1, e2, e3, e4;
if (/[^\x00-\xFF]/.test(input)) { throw Error('InvalidCharacterError'); }
while (position < input.length) {
o1 = input.charCodeAt(position++);
o2 = input.charCodeAt(position++);
o3 = input.charCodeAt(position++);
// 111111 112222 222233 333333
e1 = o1 >> 2;
e2 = ((o1 & 0x3) << 4) | (o2 >> 4);
e3 = ((o2 & 0xf) << 2) | (o3 >> 6);
e4 = o3 & 0x3f;
if (position === input.length + 2) {
e3 = 64; e4 = 64;
}
else if (position === input.length + 1) {
e4 = 64;
}
out.push(B64_ALPHABET.charAt(e1),
B64_ALPHABET.charAt(e2),
B64_ALPHABET.charAt(e3),
B64_ALPHABET.charAt(e4));
}
return out.join('');
};
}(window));
//https://gist.github.com/jimeh/332357
if (!Object.prototype.hasOwnProperty){
/*jshint -W001, -W103 */
Object.prototype.hasOwnProperty = function(prop) {
var proto = this.__proto__ || this.constructor.prototype;
return (prop in this) && (!(prop in proto) || proto[prop] !== this[prop]);
};
/*jshint +W001, +W103 */
}
// @license http://opensource.org/licenses/MIT
// copyright Paul Irish 2015
// Date.now() is supported everywhere except IE8. For IE8 we use the Date.now polyfill
// github.com/Financial-Times/polyfill-service/blob/master/polyfills/Date.now/polyfill.js
// as Safari 6 doesn't have support for NavigationTiming, we use a Date.now() timestamp for relative values
// if you want values similar to what you'd get with real perf.now, place this towards the head of the page
// but in reality, you're just getting the delta between now() calls, so it's not terribly important where it's placed
(function(){
if ('performance' in window === false) {
window.performance = {};
}
Date.now = (Date.now || function () { // thanks IE8
return new Date().getTime();
});
if ('now' in window.performance === false){
var nowOffset = Date.now();
if (performance.timing && performance.timing.navigationStart){
nowOffset = performance.timing.navigationStart;
}
window.performance.now = function now(){
return Date.now() - nowOffset;
};
}
})();
//requestAnimationFrame polyfill for older Firefox/Chrome versions
if (!window.requestAnimationFrame) {
if (window.webkitRequestAnimationFrame && window.webkitCancelAnimationFrame) {
//https://github.com/Financial-Times/polyfill-service/blob/master/polyfills/requestAnimationFrame/polyfill-webkit.js
(function (global) {
global.requestAnimationFrame = function (callback) {
return webkitRequestAnimationFrame(function () {
callback(global.performance.now());
});
};
global.cancelAnimationFrame = global.webkitCancelAnimationFrame;
}(window));
} else if (window.mozRequestAnimationFrame && window.mozCancelAnimationFrame) {
//https://github.com/Financial-Times/polyfill-service/blob/master/polyfills/requestAnimationFrame/polyfill-moz.js
(function (global) {
global.requestAnimationFrame = function (callback) {
return mozRequestAnimationFrame(function () {
callback(global.performance.now());
});
};
global.cancelAnimationFrame = global.mozCancelAnimationFrame;
}(window));
} else {
(function (global) {
global.requestAnimationFrame = function (callback) {
return global.setTimeout(callback, 1000 / 60);
};
global.cancelAnimationFrame = global.clearTimeout;
})(window);
}
}
})(this);

View file

@ -0,0 +1,102 @@
//Modified version of component/querystring
//Changes: updated dependencies, dot notation parsing, JSHint fixes
//Fork at https://github.com/imsky/querystring
/**
* Module dependencies.
*/
var encode = encodeURIComponent;
var decode = decodeURIComponent;
var trim = require('trim');
var type = require('component-type');
var arrayRegex = /(\w+)\[(\d+)\]/;
var objectRegex = /\w+\.\w+/;
/**
* Parse the given query `str`.
*
* @param {String} str
* @return {Object}
* @api public
*/
exports.parse = function(str){
if ('string' !== typeof str) return {};
str = trim(str);
if ('' === str) return {};
if ('?' === str.charAt(0)) str = str.slice(1);
var obj = {};
var pairs = str.split('&');
for (var i = 0; i < pairs.length; i++) {
var parts = pairs[i].split('=');
var key = decode(parts[0]);
var m, ctx, prop;
if (m = arrayRegex.exec(key)) {
obj[m[1]] = obj[m[1]] || [];
obj[m[1]][m[2]] = decode(parts[1]);
continue;
}
if (m = objectRegex.test(key)) {
m = key.split('.');
ctx = obj;
while (m.length) {
prop = m.shift();
if (!prop.length) continue;
if (!ctx[prop]) {
ctx[prop] = {};
} else if (ctx[prop] && typeof ctx[prop] !== 'object') {
break;
}
if (!m.length) {
ctx[prop] = decode(parts[1]);
}
ctx = ctx[prop];
}
continue;
}
obj[parts[0]] = null == parts[1] ? '' : decode(parts[1]);
}
return obj;
};
/**
* Stringify the given `obj`.
*
* @param {Object} obj
* @return {String}
* @api public
*/
exports.stringify = function(obj){
if (!obj) return '';
var pairs = [];
for (var key in obj) {
var value = obj[key];
if ('array' == type(value)) {
for (var i = 0; i < value.length; ++i) {
pairs.push(encode(key + '[' + i + ']') + '=' + encode(value[i]));
}
continue;
}
pairs.push(encode(key) + '=' + encode(obj[key]));
}
return pairs.join('&');
};

View file

@ -0,0 +1,12 @@
Package.describe({
summary: '%summary%',
version: '%version%',
name: 'imsky:holder',
git: 'https://github.com/imsky/holder',
});
Package.onUse(function(api) {
api.versionsFrom('0.9.0');
api.export('Holder', 'client');
api.addFiles('holder.js', 'client');
});

View file

@ -0,0 +1,5 @@
(function(ctx, isMeteorPackage) {
if (isMeteorPackage) {
Holder = ctx.Holder;
}
})(this, typeof Meteor !== 'undefined' && typeof Package !== 'undefined');

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,19 @@
var runner = require('./runner');
var server = require('node-http-server');
server.deploy({
'port': 8000,
'root': __dirname
});
runner({
'browserName': 'chrome'
}, function (err, retval) {
console.log('Test result: ', retval);
if (!retval) {
process.exitCode = -1;
}
process.exit();
});

View file

@ -0,0 +1,15 @@
var page = require('webpage').create();
page.onConsoleMessage = function (message) {
console.log('Page: ', message);
};
page.open('index.html', function (status) {
console.log(status);
if (status === 'success') {
page.render('phantom.png');
}
phantom.exit();
});

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,37 @@
var client = require('webdriverio');
module.exports = function (options, cb) {
var retval = true;
client.remote({
'user': process.env.SAUCE_USERNAME,
'key': process.env.SAUCE_ACCESS_KEY,
'host': 'localhost',
'port': 4445,
'desiredCapabilities': {
'browserName': options.browserName,
'tunnel-identifier': process.env.TRAVIS_JOB_NUMBER,
'name': 'Holder.js Test',
'tags': [options.browserName]
}
})
.init()
.url('http://localhost:8000')
.execute(function () {
var expectImages = document.querySelectorAll('img').length - document.querySelectorAll('img[data-exclude]').length;
var renderedImages = document.querySelectorAll('img[data-holder-rendered]').length;
return {'expected': expectImages, 'rendered': renderedImages};
}, function (err, ret) {
var expected = ret.value.expected;
var rendered = ret.value.rendered;
console.log('Expected', expected);
console.log('Rendered', rendered);
if (expected !== rendered) {
retval = false;
}
})
.pause(15 * 1000)
.end(function () {
cb(null, retval);
});
};